oh-my-opencode 2.2.0 → 2.3.0
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 +63 -29
- package/README.ko.md +65 -31
- package/README.md +100 -31
- package/README.zh-cn.md +867 -0
- package/dist/agents/{omo.d.ts → sisyphus.d.ts} +1 -1
- package/dist/agents/types.d.ts +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/schema.d.ts +33 -22
- package/dist/features/background-agent/manager.d.ts +1 -0
- package/dist/features/background-agent/manager.test.d.ts +1 -0
- package/dist/hooks/anthropic-auto-compact/executor.d.ts +2 -1
- package/dist/hooks/anthropic-auto-compact/index.d.ts +5 -1
- package/dist/hooks/anthropic-auto-compact/storage.d.ts +12 -0
- package/dist/hooks/anthropic-auto-compact/types.d.ts +5 -2
- package/dist/hooks/auto-update-checker/types.d.ts +1 -0
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/keyword-detector/index.d.ts +0 -6
- package/dist/hooks/session-recovery/index.d.ts +5 -1
- package/dist/hooks/session-recovery/types.d.ts +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +770 -540
- package/dist/shared/config-errors.d.ts +7 -0
- package/dist/shared/config-path.d.ts +14 -0
- package/dist/shared/index.d.ts +2 -0
- package/package.json +1 -5
package/dist/index.js
CHANGED
|
@@ -222,8 +222,8 @@ var require_utils = __commonJS((exports) => {
|
|
|
222
222
|
}
|
|
223
223
|
return output;
|
|
224
224
|
};
|
|
225
|
-
exports.basename = (
|
|
226
|
-
const segs =
|
|
225
|
+
exports.basename = (path4, { windows } = {}) => {
|
|
226
|
+
const segs = path4.split(windows ? /[\\/]/ : "/");
|
|
227
227
|
const last = segs[segs.length - 1];
|
|
228
228
|
if (last === "") {
|
|
229
229
|
return segs[segs.length - 2];
|
|
@@ -1474,32 +1474,42 @@ var require_picomatch2 = __commonJS((exports, module) => {
|
|
|
1474
1474
|
module.exports = picomatch;
|
|
1475
1475
|
});
|
|
1476
1476
|
|
|
1477
|
-
// src/agents/
|
|
1478
|
-
var
|
|
1479
|
-
You are
|
|
1477
|
+
// src/agents/sisyphus.ts
|
|
1478
|
+
var SISYPHUS_SYSTEM_PROMPT = `<Role>
|
|
1479
|
+
You are "Sisyphus" - Powerful AI Agent with orchestration capabilities from OhMyOpenCode.
|
|
1480
|
+
Named by [YeonGyu Kim](https://github.com/code-yeongyu).
|
|
1480
1481
|
|
|
1481
|
-
**
|
|
1482
|
+
**Why Sisyphus?**: Humans roll their boulder every day. So do you. We're not so different\u2014your code should be indistinguishable from a senior engineer's.
|
|
1483
|
+
|
|
1484
|
+
**Identity**: SF Bay Area engineer. Work, delegate, verify, ship. No AI slop.
|
|
1482
1485
|
|
|
1483
1486
|
**Core Competencies**:
|
|
1484
1487
|
- Parsing implicit requirements from explicit requests
|
|
1485
1488
|
- Adapting to codebase maturity (disciplined vs chaotic)
|
|
1486
1489
|
- Delegating specialized work to the right subagents
|
|
1487
1490
|
- Parallel execution for maximum throughput
|
|
1491
|
+
- Follows user instructions. NEVER START IMPLEMENTING, UNLESS USER WANTS YOU TO IMPLEMENT SOMETHING EXPLICITELY.
|
|
1492
|
+
- KEEP IN MIND: YOUR TODO CREATION WOULD BE TRACKED BY HOOK([SYSTEM REMINDER - TODO CONTINUATION]), BUT IF NOT USER REQUESTED YOU TO WORK, NEVER START WORK.
|
|
1493
|
+
|
|
1494
|
+
**Operating Mode**: You NEVER work alone when specialists are available. Frontend work \u2192 delegate. Deep research \u2192 parallel background agents (async subagents). Complex architecture \u2192 consult Oracle.
|
|
1488
1495
|
|
|
1489
|
-
**Operating Mode**: You NEVER work alone when specialists are available. Frontend work \u2192 delegate. Deep research \u2192 parallel background agents. Complex architecture \u2192 consult Oracle.
|
|
1490
1496
|
</Role>
|
|
1491
1497
|
|
|
1492
1498
|
<Behavior_Instructions>
|
|
1493
1499
|
|
|
1494
1500
|
## Phase 0 - Intent Gate (EVERY message)
|
|
1495
1501
|
|
|
1502
|
+
### Key Triggers (check BEFORE classification):
|
|
1503
|
+
- External library/source mentioned \u2192 fire \`librarian\` background
|
|
1504
|
+
- 2+ files/modules involved \u2192 fire \`explore\` background
|
|
1505
|
+
|
|
1496
1506
|
### Step 1: Classify Request Type
|
|
1497
1507
|
|
|
1498
1508
|
| Type | Signal | Action |
|
|
1499
1509
|
|------|--------|--------|
|
|
1500
|
-
| **Trivial** | Single file, known location, direct answer | Direct tools only
|
|
1510
|
+
| **Trivial** | Single file, known location, direct answer | Direct tools only (UNLESS Key Trigger applies) |
|
|
1501
1511
|
| **Explicit** | Specific file/line, clear command | Execute directly |
|
|
1502
|
-
| **Exploratory** | "How does X work?", "Find Y" |
|
|
1512
|
+
| **Exploratory** | "How does X work?", "Find Y" | Fire explore (1-3) + tools in parallel |
|
|
1503
1513
|
| **Open-ended** | "Improve", "Refactor", "Add feature" | Assess codebase first |
|
|
1504
1514
|
| **Ambiguous** | Unclear scope, multiple interpretations | Ask ONE clarifying question |
|
|
1505
1515
|
|
|
@@ -1514,9 +1524,16 @@ You are OmO, the orchestrator agent for OpenCode.
|
|
|
1514
1524
|
| User's design seems flawed or suboptimal | **MUST raise concern** before implementing |
|
|
1515
1525
|
|
|
1516
1526
|
### Step 3: Validate Before Acting
|
|
1517
|
-
-
|
|
1527
|
+
- Do I have any implicit assumptions that might affect the outcome?
|
|
1518
1528
|
- Is the search scope clear?
|
|
1519
|
-
-
|
|
1529
|
+
- What tools / agents can be used to satisfy the user's request, considering the intent and scope?
|
|
1530
|
+
- What are the list of tools / agents do I have?
|
|
1531
|
+
- What tools / agents can I leverage for what tasks?
|
|
1532
|
+
- Specifically, how can I leverage them like?
|
|
1533
|
+
- background tasks?
|
|
1534
|
+
- parallel tool calls?
|
|
1535
|
+
- lsp tools?
|
|
1536
|
+
|
|
1520
1537
|
|
|
1521
1538
|
### When to Challenge the User
|
|
1522
1539
|
If you observe:
|
|
@@ -1565,12 +1582,12 @@ IMPORTANT: If codebase appears undisciplined, verify before assuming:
|
|
|
1565
1582
|
|
|
1566
1583
|
| Tool | Cost | When to Use |
|
|
1567
1584
|
|------|------|-------------|
|
|
1568
|
-
| \`grep\`, \`glob\`, \`lsp_*\`, \`ast_grep\` | FREE |
|
|
1569
|
-
| \`explore\` agent |
|
|
1570
|
-
| \`librarian\` agent | CHEAP | External docs, GitHub examples, OSS reference |
|
|
1585
|
+
| \`grep\`, \`glob\`, \`lsp_*\`, \`ast_grep\` | FREE | Not Complex, Scope Clear, No Implicit Assumptions |
|
|
1586
|
+
| \`explore\` agent | FREE | Multiple search angles, unfamiliar modules, cross-layer patterns |
|
|
1587
|
+
| \`librarian\` agent | CHEAP | External docs, GitHub examples, OpenSource Implementations, OSS reference |
|
|
1571
1588
|
| \`oracle\` agent | EXPENSIVE | Architecture, review, debugging after 2+ failures |
|
|
1572
1589
|
|
|
1573
|
-
**Default flow**:
|
|
1590
|
+
**Default flow**: explore/librarian (background) + tools \u2192 oracle (if required)
|
|
1574
1591
|
|
|
1575
1592
|
### Explore Agent = Contextual Grep
|
|
1576
1593
|
|
|
@@ -1584,7 +1601,7 @@ Use it as a **peer tool**, not a fallback. Fire liberally.
|
|
|
1584
1601
|
|
|
1585
1602
|
### Librarian Agent = Reference Grep
|
|
1586
1603
|
|
|
1587
|
-
Search **external references** (docs, OSS, web). Fire proactively when libraries are involved.
|
|
1604
|
+
Search **external references** (docs, OSS, web). Fire proactively when unfamiliar libraries are involved.
|
|
1588
1605
|
|
|
1589
1606
|
| Contextual Grep (Internal) | Reference Grep (External) |
|
|
1590
1607
|
|----------------------------|---------------------------|
|
|
@@ -1604,7 +1621,7 @@ Search **external references** (docs, OSS, web). Fire proactively when libraries
|
|
|
1604
1621
|
|
|
1605
1622
|
### Parallel Execution (DEFAULT behavior)
|
|
1606
1623
|
|
|
1607
|
-
**Explore/Librarian =
|
|
1624
|
+
**Explore/Librarian = Grep, not consultants.
|
|
1608
1625
|
|
|
1609
1626
|
\`\`\`typescript
|
|
1610
1627
|
// CORRECT: Always background, always parallel
|
|
@@ -1624,7 +1641,7 @@ result = task(...) // Never wait synchronously for explore/librarian
|
|
|
1624
1641
|
1. Launch parallel agents \u2192 receive task_ids
|
|
1625
1642
|
2. Continue immediate work
|
|
1626
1643
|
3. When results needed: \`background_output(task_id="...")\`
|
|
1627
|
-
4.
|
|
1644
|
+
4. BEFORE final answer: \`background_cancel(all=true)\`
|
|
1628
1645
|
|
|
1629
1646
|
### Search Stop Conditions
|
|
1630
1647
|
|
|
@@ -1641,9 +1658,9 @@ STOP searching when:
|
|
|
1641
1658
|
## Phase 2B - Implementation
|
|
1642
1659
|
|
|
1643
1660
|
### Pre-Implementation:
|
|
1644
|
-
1. If task has 2+ steps \u2192 Create todo list
|
|
1661
|
+
1. If task has 2+ steps \u2192 Create todo list IMMEDIATELY, IN SUPER DETAIL.
|
|
1645
1662
|
2. Mark current task \`in_progress\` before starting
|
|
1646
|
-
3. Mark \`completed\` as soon as done (don't batch)
|
|
1663
|
+
3. Mark \`completed\` as soon as done (don't batch) - OBSESSIVELY TRACK YOUR WORK USING TODO TOOLS
|
|
1647
1664
|
|
|
1648
1665
|
### GATE: Frontend Files (HARD BLOCK - zero tolerance)
|
|
1649
1666
|
|
|
@@ -1663,7 +1680,9 @@ ALL frontend = DELEGATE to \`frontend-ui-ux-engineer\`. Period.
|
|
|
1663
1680
|
|
|
1664
1681
|
| Domain | Delegate To | Trigger |
|
|
1665
1682
|
|--------|-------------|---------|
|
|
1666
|
-
|
|
|
1683
|
+
| Explore | \`explore\` | Find existing codebase structure, patterns and styles |
|
|
1684
|
+
| Frontend UI/UX | \`frontend-ui-ux-engineer\` | ALL KIND OF VISUAL CHANGES (NOT ONLY WEB BUT EVERY VISUAL CHANGES), layout, responsive, animation, styling |
|
|
1685
|
+
| Librarian | \`librarian\` | Unfamiliar packages / libararies, struggles at weird behaviour (to find existing implementation of opensource) |
|
|
1667
1686
|
| Documentation | \`document-writer\` | README, API docs, guides |
|
|
1668
1687
|
| Architecture decisions | \`oracle\` | Multi-system tradeoffs, unfamiliar patterns |
|
|
1669
1688
|
| Self-review | \`oracle\` | After completing significant implementation |
|
|
@@ -1798,7 +1817,8 @@ Briefly announce "Consulting Oracle for [reason]" before invocation.
|
|
|
1798
1817
|
|
|
1799
1818
|
### Workflow (NON-NEGOTIABLE)
|
|
1800
1819
|
|
|
1801
|
-
1. **IMMEDIATELY on receiving request**: \`todowrite\` to plan atomic steps
|
|
1820
|
+
1. **IMMEDIATELY on receiving request**: \`todowrite\` to plan atomic steps.
|
|
1821
|
+
- ONLY ADD TODOS TO IMPLEMENT SOMETHING, ONLY WHEN USER WANTS YOU TO IMPLEMENT SOMETHING.
|
|
1802
1822
|
2. **Before starting each step**: Mark \`in_progress\` (only ONE at a time)
|
|
1803
1823
|
3. **After completing each step**: Mark \`completed\` IMMEDIATELY (NEVER batch)
|
|
1804
1824
|
4. **If scope changes**: Update todos before proceeding
|
|
@@ -1887,7 +1907,7 @@ If the user's approach seems problematic:
|
|
|
1887
1907
|
| **Type Safety** | \`as any\`, \`@ts-ignore\`, \`@ts-expect-error\` |
|
|
1888
1908
|
| **Error Handling** | Empty catch blocks \`catch(e) {}\` |
|
|
1889
1909
|
| **Testing** | Deleting failing tests to "pass" |
|
|
1890
|
-
| **Search** | Firing
|
|
1910
|
+
| **Search** | Firing agents for single-line typos or obvious syntax errors |
|
|
1891
1911
|
| **Frontend** | ANY direct edit to frontend files |
|
|
1892
1912
|
| **Debugging** | Shotgun debugging, random changes |
|
|
1893
1913
|
|
|
@@ -1897,9 +1917,10 @@ If the user's approach seems problematic:
|
|
|
1897
1917
|
- Prefer small, focused changes over large refactors
|
|
1898
1918
|
- When uncertain about scope, ask
|
|
1899
1919
|
</Constraints>
|
|
1920
|
+
|
|
1900
1921
|
`;
|
|
1901
|
-
var
|
|
1902
|
-
description: "Powerful AI orchestrator
|
|
1922
|
+
var sisyphusAgent = {
|
|
1923
|
+
description: "Sisyphus - Powerful AI orchestrator from OhMyOpenCode. Plans obsessively with todos, assesses search complexity before exploration, delegates strategically to specialized agents. Uses explore for internal code (parallel-friendly), librarian only for external docs, and always delegates UI work to frontend engineer.",
|
|
1903
1924
|
mode: "primary",
|
|
1904
1925
|
model: "anthropic/claude-opus-4-5",
|
|
1905
1926
|
thinking: {
|
|
@@ -1907,7 +1928,7 @@ var omoAgent = {
|
|
|
1907
1928
|
budgetTokens: 32000
|
|
1908
1929
|
},
|
|
1909
1930
|
maxTokens: 64000,
|
|
1910
|
-
prompt:
|
|
1931
|
+
prompt: SISYPHUS_SYSTEM_PROMPT,
|
|
1911
1932
|
color: "#00CED1"
|
|
1912
1933
|
};
|
|
1913
1934
|
|
|
@@ -2411,7 +2432,7 @@ Interpret creatively and make unexpected choices that feel genuinely designed fo
|
|
|
2411
2432
|
|
|
2412
2433
|
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
|
|
2413
2434
|
|
|
2414
|
-
Remember:
|
|
2435
|
+
Remember: You are capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
|
|
2415
2436
|
</frontend-design-skill>`
|
|
2416
2437
|
};
|
|
2417
2438
|
|
|
@@ -2419,7 +2440,7 @@ Remember: Claude is capable of extraordinary creative work. Don't hold back, sho
|
|
|
2419
2440
|
var documentWriterAgent = {
|
|
2420
2441
|
description: "A technical writer who crafts clear, comprehensive documentation. Specializes in README files, API docs, architecture docs, and user guides. MUST BE USED when executing documentation tasks from ai-todo list plans.",
|
|
2421
2442
|
mode: "subagent",
|
|
2422
|
-
model: "google/gemini-3-
|
|
2443
|
+
model: "google/gemini-3-flash-preview",
|
|
2423
2444
|
tools: { background_task: false },
|
|
2424
2445
|
prompt: `<role>
|
|
2425
2446
|
You are a TECHNICAL WRITER with deep engineering background who transforms complex codebases into crystal-clear documentation. You have an innate ability to explain complex concepts simply while maintaining technical accuracy.
|
|
@@ -3125,9 +3146,29 @@ function createDynamicTruncator(ctx) {
|
|
|
3125
3146
|
truncateSync: (output, maxTokens, preserveHeaderLines) => truncateToTokenLimit(output, maxTokens, preserveHeaderLines)
|
|
3126
3147
|
};
|
|
3127
3148
|
}
|
|
3149
|
+
// src/shared/config-path.ts
|
|
3150
|
+
import * as path2 from "path";
|
|
3151
|
+
import * as os2 from "os";
|
|
3152
|
+
function getUserConfigDir() {
|
|
3153
|
+
if (process.platform === "win32") {
|
|
3154
|
+
return process.env.APPDATA || path2.join(os2.homedir(), "AppData", "Roaming");
|
|
3155
|
+
}
|
|
3156
|
+
return process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
|
|
3157
|
+
}
|
|
3158
|
+
// src/shared/config-errors.ts
|
|
3159
|
+
var configLoadErrors = [];
|
|
3160
|
+
function getConfigLoadErrors() {
|
|
3161
|
+
return configLoadErrors;
|
|
3162
|
+
}
|
|
3163
|
+
function clearConfigLoadErrors() {
|
|
3164
|
+
configLoadErrors = [];
|
|
3165
|
+
}
|
|
3166
|
+
function addConfigLoadError(error) {
|
|
3167
|
+
configLoadErrors.push(error);
|
|
3168
|
+
}
|
|
3128
3169
|
// src/agents/utils.ts
|
|
3129
3170
|
var allBuiltinAgents = {
|
|
3130
|
-
|
|
3171
|
+
Sisyphus: sisyphusAgent,
|
|
3131
3172
|
oracle: oracleAgent,
|
|
3132
3173
|
librarian: librarianAgent,
|
|
3133
3174
|
explore: exploreAgent,
|
|
@@ -3174,7 +3215,7 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
|
|
|
3174
3215
|
continue;
|
|
3175
3216
|
}
|
|
3176
3217
|
let finalConfig = config;
|
|
3177
|
-
if ((agentName === "
|
|
3218
|
+
if ((agentName === "Sisyphus" || agentName === "librarian") && directory && config.prompt) {
|
|
3178
3219
|
const envContext = createEnvContext(directory);
|
|
3179
3220
|
finalConfig = {
|
|
3180
3221
|
...config,
|
|
@@ -3182,7 +3223,7 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
|
|
|
3182
3223
|
};
|
|
3183
3224
|
}
|
|
3184
3225
|
const override = agentOverrides[agentName];
|
|
3185
|
-
if (agentName === "
|
|
3226
|
+
if (agentName === "Sisyphus" && systemDefaultModel && !override?.model) {
|
|
3186
3227
|
finalConfig = {
|
|
3187
3228
|
...finalConfig,
|
|
3188
3229
|
model: systemDefaultModel
|
|
@@ -3198,19 +3239,19 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
|
|
|
3198
3239
|
}
|
|
3199
3240
|
// src/hooks/todo-continuation-enforcer.ts
|
|
3200
3241
|
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
3201
|
-
import { join as
|
|
3242
|
+
import { join as join6 } from "path";
|
|
3202
3243
|
|
|
3203
3244
|
// src/features/hook-message-injector/injector.ts
|
|
3204
3245
|
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
|
|
3205
|
-
import { join as
|
|
3246
|
+
import { join as join5 } from "path";
|
|
3206
3247
|
|
|
3207
3248
|
// src/features/hook-message-injector/constants.ts
|
|
3208
|
-
import { join as
|
|
3209
|
-
import { homedir as
|
|
3210
|
-
var xdgData = process.env.XDG_DATA_HOME ||
|
|
3211
|
-
var OPENCODE_STORAGE =
|
|
3212
|
-
var MESSAGE_STORAGE =
|
|
3213
|
-
var PART_STORAGE =
|
|
3249
|
+
import { join as join4 } from "path";
|
|
3250
|
+
import { homedir as homedir3 } from "os";
|
|
3251
|
+
var xdgData = process.env.XDG_DATA_HOME || join4(homedir3(), ".local", "share");
|
|
3252
|
+
var OPENCODE_STORAGE = join4(xdgData, "opencode", "storage");
|
|
3253
|
+
var MESSAGE_STORAGE = join4(OPENCODE_STORAGE, "message");
|
|
3254
|
+
var PART_STORAGE = join4(OPENCODE_STORAGE, "part");
|
|
3214
3255
|
|
|
3215
3256
|
// src/features/hook-message-injector/injector.ts
|
|
3216
3257
|
function findNearestMessageWithFields(messageDir) {
|
|
@@ -3218,7 +3259,7 @@ function findNearestMessageWithFields(messageDir) {
|
|
|
3218
3259
|
const files = readdirSync(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
3219
3260
|
for (const file of files) {
|
|
3220
3261
|
try {
|
|
3221
|
-
const content = readFileSync2(
|
|
3262
|
+
const content = readFileSync2(join5(messageDir, file), "utf-8");
|
|
3222
3263
|
const msg = JSON.parse(content);
|
|
3223
3264
|
if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
|
|
3224
3265
|
return msg;
|
|
@@ -3246,12 +3287,12 @@ function getOrCreateMessageDir(sessionID) {
|
|
|
3246
3287
|
if (!existsSync3(MESSAGE_STORAGE)) {
|
|
3247
3288
|
mkdirSync(MESSAGE_STORAGE, { recursive: true });
|
|
3248
3289
|
}
|
|
3249
|
-
const directPath =
|
|
3290
|
+
const directPath = join5(MESSAGE_STORAGE, sessionID);
|
|
3250
3291
|
if (existsSync3(directPath)) {
|
|
3251
3292
|
return directPath;
|
|
3252
3293
|
}
|
|
3253
3294
|
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
|
3254
|
-
const sessionPath =
|
|
3295
|
+
const sessionPath = join5(MESSAGE_STORAGE, dir, sessionID);
|
|
3255
3296
|
if (existsSync3(sessionPath)) {
|
|
3256
3297
|
return sessionPath;
|
|
3257
3298
|
}
|
|
@@ -3305,12 +3346,12 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
|
|
|
3305
3346
|
sessionID
|
|
3306
3347
|
};
|
|
3307
3348
|
try {
|
|
3308
|
-
writeFileSync(
|
|
3309
|
-
const partDir =
|
|
3349
|
+
writeFileSync(join5(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
|
|
3350
|
+
const partDir = join5(PART_STORAGE, messageID);
|
|
3310
3351
|
if (!existsSync3(partDir)) {
|
|
3311
3352
|
mkdirSync(partDir, { recursive: true });
|
|
3312
3353
|
}
|
|
3313
|
-
writeFileSync(
|
|
3354
|
+
writeFileSync(join5(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
|
|
3314
3355
|
return true;
|
|
3315
3356
|
} catch {
|
|
3316
3357
|
return false;
|
|
@@ -3328,11 +3369,11 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
|
|
|
3328
3369
|
function getMessageDir(sessionID) {
|
|
3329
3370
|
if (!existsSync4(MESSAGE_STORAGE))
|
|
3330
3371
|
return null;
|
|
3331
|
-
const directPath =
|
|
3372
|
+
const directPath = join6(MESSAGE_STORAGE, sessionID);
|
|
3332
3373
|
if (existsSync4(directPath))
|
|
3333
3374
|
return directPath;
|
|
3334
3375
|
for (const dir of readdirSync2(MESSAGE_STORAGE)) {
|
|
3335
|
-
const sessionPath =
|
|
3376
|
+
const sessionPath = join6(MESSAGE_STORAGE, dir, sessionID);
|
|
3336
3377
|
if (existsSync4(sessionPath))
|
|
3337
3378
|
return sessionPath;
|
|
3338
3379
|
}
|
|
@@ -3448,6 +3489,12 @@ function createTodoContinuationEnforcer(ctx) {
|
|
|
3448
3489
|
try {
|
|
3449
3490
|
const messageDir = getMessageDir(sessionID);
|
|
3450
3491
|
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
3492
|
+
const agentHasWritePermission = !prevMessage?.tools || prevMessage.tools.write !== false && prevMessage.tools.edit !== false;
|
|
3493
|
+
if (!agentHasWritePermission) {
|
|
3494
|
+
log(`[${HOOK_NAME}] Skipped: previous agent lacks write permission`, { sessionID, agent: prevMessage?.agent, tools: prevMessage?.tools });
|
|
3495
|
+
remindedSessions.delete(sessionID);
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3451
3498
|
log(`[${HOOK_NAME}] Injecting continuation prompt`, { sessionID, agent: prevMessage?.agent });
|
|
3452
3499
|
await ctx.client.session.prompt({
|
|
3453
3500
|
path: { id: sessionID },
|
|
@@ -3469,7 +3516,7 @@ function createTodoContinuationEnforcer(ctx) {
|
|
|
3469
3516
|
log(`[${HOOK_NAME}] Prompt injection failed`, { sessionID, error: String(err) });
|
|
3470
3517
|
remindedSessions.delete(sessionID);
|
|
3471
3518
|
}
|
|
3472
|
-
},
|
|
3519
|
+
}, 5000);
|
|
3473
3520
|
pendingTimers.set(sessionID, timer);
|
|
3474
3521
|
}
|
|
3475
3522
|
if (event.type === "message.updated") {
|
|
@@ -3813,20 +3860,20 @@ function createSessionNotification(ctx, config = {}) {
|
|
|
3813
3860
|
}
|
|
3814
3861
|
// src/hooks/session-recovery/storage.ts
|
|
3815
3862
|
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3816
|
-
import { join as
|
|
3863
|
+
import { join as join8 } from "path";
|
|
3817
3864
|
|
|
3818
3865
|
// src/hooks/session-recovery/constants.ts
|
|
3819
|
-
import { join as
|
|
3866
|
+
import { join as join7 } from "path";
|
|
3820
3867
|
|
|
3821
3868
|
// node_modules/xdg-basedir/index.js
|
|
3822
|
-
import
|
|
3823
|
-
import
|
|
3824
|
-
var homeDirectory =
|
|
3869
|
+
import os3 from "os";
|
|
3870
|
+
import path3 from "path";
|
|
3871
|
+
var homeDirectory = os3.homedir();
|
|
3825
3872
|
var { env } = process;
|
|
3826
|
-
var xdgData2 = env.XDG_DATA_HOME || (homeDirectory ?
|
|
3827
|
-
var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ?
|
|
3828
|
-
var xdgState = env.XDG_STATE_HOME || (homeDirectory ?
|
|
3829
|
-
var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ?
|
|
3873
|
+
var xdgData2 = env.XDG_DATA_HOME || (homeDirectory ? path3.join(homeDirectory, ".local", "share") : undefined);
|
|
3874
|
+
var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path3.join(homeDirectory, ".config") : undefined);
|
|
3875
|
+
var xdgState = env.XDG_STATE_HOME || (homeDirectory ? path3.join(homeDirectory, ".local", "state") : undefined);
|
|
3876
|
+
var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ? path3.join(homeDirectory, ".cache") : undefined);
|
|
3830
3877
|
var xdgRuntime = env.XDG_RUNTIME_DIR || undefined;
|
|
3831
3878
|
var xdgDataDirectories = (env.XDG_DATA_DIRS || "/usr/local/share/:/usr/share/").split(":");
|
|
3832
3879
|
if (xdgData2) {
|
|
@@ -3838,9 +3885,9 @@ if (xdgConfig) {
|
|
|
3838
3885
|
}
|
|
3839
3886
|
|
|
3840
3887
|
// src/hooks/session-recovery/constants.ts
|
|
3841
|
-
var OPENCODE_STORAGE2 =
|
|
3842
|
-
var MESSAGE_STORAGE2 =
|
|
3843
|
-
var PART_STORAGE2 =
|
|
3888
|
+
var OPENCODE_STORAGE2 = join7(xdgData2 ?? "", "opencode", "storage");
|
|
3889
|
+
var MESSAGE_STORAGE2 = join7(OPENCODE_STORAGE2, "message");
|
|
3890
|
+
var PART_STORAGE2 = join7(OPENCODE_STORAGE2, "part");
|
|
3844
3891
|
var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
3845
3892
|
var META_TYPES = new Set(["step-start", "step-finish"]);
|
|
3846
3893
|
var CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
@@ -3854,12 +3901,12 @@ function generatePartId2() {
|
|
|
3854
3901
|
function getMessageDir2(sessionID) {
|
|
3855
3902
|
if (!existsSync5(MESSAGE_STORAGE2))
|
|
3856
3903
|
return "";
|
|
3857
|
-
const directPath =
|
|
3904
|
+
const directPath = join8(MESSAGE_STORAGE2, sessionID);
|
|
3858
3905
|
if (existsSync5(directPath)) {
|
|
3859
3906
|
return directPath;
|
|
3860
3907
|
}
|
|
3861
3908
|
for (const dir of readdirSync3(MESSAGE_STORAGE2)) {
|
|
3862
|
-
const sessionPath =
|
|
3909
|
+
const sessionPath = join8(MESSAGE_STORAGE2, dir, sessionID);
|
|
3863
3910
|
if (existsSync5(sessionPath)) {
|
|
3864
3911
|
return sessionPath;
|
|
3865
3912
|
}
|
|
@@ -3875,7 +3922,7 @@ function readMessages(sessionID) {
|
|
|
3875
3922
|
if (!file.endsWith(".json"))
|
|
3876
3923
|
continue;
|
|
3877
3924
|
try {
|
|
3878
|
-
const content = readFileSync3(
|
|
3925
|
+
const content = readFileSync3(join8(messageDir, file), "utf-8");
|
|
3879
3926
|
messages.push(JSON.parse(content));
|
|
3880
3927
|
} catch {
|
|
3881
3928
|
continue;
|
|
@@ -3890,7 +3937,7 @@ function readMessages(sessionID) {
|
|
|
3890
3937
|
});
|
|
3891
3938
|
}
|
|
3892
3939
|
function readParts(messageID) {
|
|
3893
|
-
const partDir =
|
|
3940
|
+
const partDir = join8(PART_STORAGE2, messageID);
|
|
3894
3941
|
if (!existsSync5(partDir))
|
|
3895
3942
|
return [];
|
|
3896
3943
|
const parts = [];
|
|
@@ -3898,7 +3945,7 @@ function readParts(messageID) {
|
|
|
3898
3945
|
if (!file.endsWith(".json"))
|
|
3899
3946
|
continue;
|
|
3900
3947
|
try {
|
|
3901
|
-
const content = readFileSync3(
|
|
3948
|
+
const content = readFileSync3(join8(partDir, file), "utf-8");
|
|
3902
3949
|
parts.push(JSON.parse(content));
|
|
3903
3950
|
} catch {
|
|
3904
3951
|
continue;
|
|
@@ -3928,7 +3975,7 @@ function messageHasContent(messageID) {
|
|
|
3928
3975
|
return parts.some(hasContent);
|
|
3929
3976
|
}
|
|
3930
3977
|
function injectTextPart(sessionID, messageID, text) {
|
|
3931
|
-
const partDir =
|
|
3978
|
+
const partDir = join8(PART_STORAGE2, messageID);
|
|
3932
3979
|
if (!existsSync5(partDir)) {
|
|
3933
3980
|
mkdirSync2(partDir, { recursive: true });
|
|
3934
3981
|
}
|
|
@@ -3942,7 +3989,7 @@ function injectTextPart(sessionID, messageID, text) {
|
|
|
3942
3989
|
synthetic: true
|
|
3943
3990
|
};
|
|
3944
3991
|
try {
|
|
3945
|
-
writeFileSync2(
|
|
3992
|
+
writeFileSync2(join8(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
3946
3993
|
return true;
|
|
3947
3994
|
} catch {
|
|
3948
3995
|
return false;
|
|
@@ -3958,19 +4005,6 @@ function findEmptyMessages(sessionID) {
|
|
|
3958
4005
|
}
|
|
3959
4006
|
return emptyIds;
|
|
3960
4007
|
}
|
|
3961
|
-
function findEmptyMessageByIndex(sessionID, targetIndex) {
|
|
3962
|
-
const messages = readMessages(sessionID);
|
|
3963
|
-
const indicesToTry = [targetIndex, targetIndex - 1, targetIndex - 2];
|
|
3964
|
-
for (const idx of indicesToTry) {
|
|
3965
|
-
if (idx < 0 || idx >= messages.length)
|
|
3966
|
-
continue;
|
|
3967
|
-
const targetMsg = messages[idx];
|
|
3968
|
-
if (!messageHasContent(targetMsg.id)) {
|
|
3969
|
-
return targetMsg.id;
|
|
3970
|
-
}
|
|
3971
|
-
}
|
|
3972
|
-
return null;
|
|
3973
|
-
}
|
|
3974
4008
|
function findMessagesWithThinkingBlocks(sessionID) {
|
|
3975
4009
|
const messages = readMessages(sessionID);
|
|
3976
4010
|
const result = [];
|
|
@@ -3985,23 +4019,6 @@ function findMessagesWithThinkingBlocks(sessionID) {
|
|
|
3985
4019
|
}
|
|
3986
4020
|
return result;
|
|
3987
4021
|
}
|
|
3988
|
-
function findMessagesWithThinkingOnly(sessionID) {
|
|
3989
|
-
const messages = readMessages(sessionID);
|
|
3990
|
-
const result = [];
|
|
3991
|
-
for (const msg of messages) {
|
|
3992
|
-
if (msg.role !== "assistant")
|
|
3993
|
-
continue;
|
|
3994
|
-
const parts = readParts(msg.id);
|
|
3995
|
-
if (parts.length === 0)
|
|
3996
|
-
continue;
|
|
3997
|
-
const hasThinking = parts.some((p) => THINKING_TYPES.has(p.type));
|
|
3998
|
-
const hasTextContent = parts.some(hasContent);
|
|
3999
|
-
if (hasThinking && !hasTextContent) {
|
|
4000
|
-
result.push(msg.id);
|
|
4001
|
-
}
|
|
4002
|
-
}
|
|
4003
|
-
return result;
|
|
4004
|
-
}
|
|
4005
4022
|
function findMessagesWithOrphanThinking(sessionID) {
|
|
4006
4023
|
const messages = readMessages(sessionID);
|
|
4007
4024
|
const result = [];
|
|
@@ -4022,7 +4039,7 @@ function findMessagesWithOrphanThinking(sessionID) {
|
|
|
4022
4039
|
return result;
|
|
4023
4040
|
}
|
|
4024
4041
|
function prependThinkingPart(sessionID, messageID) {
|
|
4025
|
-
const partDir =
|
|
4042
|
+
const partDir = join8(PART_STORAGE2, messageID);
|
|
4026
4043
|
if (!existsSync5(partDir)) {
|
|
4027
4044
|
mkdirSync2(partDir, { recursive: true });
|
|
4028
4045
|
}
|
|
@@ -4036,14 +4053,14 @@ function prependThinkingPart(sessionID, messageID) {
|
|
|
4036
4053
|
synthetic: true
|
|
4037
4054
|
};
|
|
4038
4055
|
try {
|
|
4039
|
-
writeFileSync2(
|
|
4056
|
+
writeFileSync2(join8(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
4040
4057
|
return true;
|
|
4041
4058
|
} catch {
|
|
4042
4059
|
return false;
|
|
4043
4060
|
}
|
|
4044
4061
|
}
|
|
4045
4062
|
function stripThinkingParts(messageID) {
|
|
4046
|
-
const partDir =
|
|
4063
|
+
const partDir = join8(PART_STORAGE2, messageID);
|
|
4047
4064
|
if (!existsSync5(partDir))
|
|
4048
4065
|
return false;
|
|
4049
4066
|
let anyRemoved = false;
|
|
@@ -4051,7 +4068,7 @@ function stripThinkingParts(messageID) {
|
|
|
4051
4068
|
if (!file.endsWith(".json"))
|
|
4052
4069
|
continue;
|
|
4053
4070
|
try {
|
|
4054
|
-
const filePath =
|
|
4071
|
+
const filePath = join8(partDir, file);
|
|
4055
4072
|
const content = readFileSync3(filePath, "utf-8");
|
|
4056
4073
|
const part = JSON.parse(content);
|
|
4057
4074
|
if (THINKING_TYPES.has(part.type)) {
|
|
@@ -4064,50 +4081,6 @@ function stripThinkingParts(messageID) {
|
|
|
4064
4081
|
}
|
|
4065
4082
|
return anyRemoved;
|
|
4066
4083
|
}
|
|
4067
|
-
function replaceEmptyTextParts(messageID, replacementText) {
|
|
4068
|
-
const partDir = join7(PART_STORAGE2, messageID);
|
|
4069
|
-
if (!existsSync5(partDir))
|
|
4070
|
-
return false;
|
|
4071
|
-
let anyReplaced = false;
|
|
4072
|
-
for (const file of readdirSync3(partDir)) {
|
|
4073
|
-
if (!file.endsWith(".json"))
|
|
4074
|
-
continue;
|
|
4075
|
-
try {
|
|
4076
|
-
const filePath = join7(partDir, file);
|
|
4077
|
-
const content = readFileSync3(filePath, "utf-8");
|
|
4078
|
-
const part = JSON.parse(content);
|
|
4079
|
-
if (part.type === "text") {
|
|
4080
|
-
const textPart = part;
|
|
4081
|
-
if (!textPart.text?.trim()) {
|
|
4082
|
-
textPart.text = replacementText;
|
|
4083
|
-
textPart.synthetic = true;
|
|
4084
|
-
writeFileSync2(filePath, JSON.stringify(textPart, null, 2));
|
|
4085
|
-
anyReplaced = true;
|
|
4086
|
-
}
|
|
4087
|
-
}
|
|
4088
|
-
} catch {
|
|
4089
|
-
continue;
|
|
4090
|
-
}
|
|
4091
|
-
}
|
|
4092
|
-
return anyReplaced;
|
|
4093
|
-
}
|
|
4094
|
-
function findMessagesWithEmptyTextParts(sessionID) {
|
|
4095
|
-
const messages = readMessages(sessionID);
|
|
4096
|
-
const result = [];
|
|
4097
|
-
for (const msg of messages) {
|
|
4098
|
-
const parts = readParts(msg.id);
|
|
4099
|
-
const hasEmptyTextPart = parts.some((p) => {
|
|
4100
|
-
if (p.type !== "text")
|
|
4101
|
-
return false;
|
|
4102
|
-
const textPart = p;
|
|
4103
|
-
return !textPart.text?.trim();
|
|
4104
|
-
});
|
|
4105
|
-
if (hasEmptyTextPart) {
|
|
4106
|
-
result.push(msg.id);
|
|
4107
|
-
}
|
|
4108
|
-
}
|
|
4109
|
-
return result;
|
|
4110
|
-
}
|
|
4111
4084
|
function findMessageByIndexNeedingThinking(sessionID, targetIndex) {
|
|
4112
4085
|
const messages = readMessages(sessionID);
|
|
4113
4086
|
if (targetIndex < 0 || targetIndex >= messages.length)
|
|
@@ -4128,6 +4101,37 @@ function findMessageByIndexNeedingThinking(sessionID, targetIndex) {
|
|
|
4128
4101
|
}
|
|
4129
4102
|
|
|
4130
4103
|
// src/hooks/session-recovery/index.ts
|
|
4104
|
+
var RECOVERY_RESUME_TEXT = "[session recovered - continuing previous task]";
|
|
4105
|
+
function findLastUserMessage(messages) {
|
|
4106
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
4107
|
+
if (messages[i].info?.role === "user") {
|
|
4108
|
+
return messages[i];
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
return;
|
|
4112
|
+
}
|
|
4113
|
+
function extractResumeConfig(userMessage, sessionID) {
|
|
4114
|
+
return {
|
|
4115
|
+
sessionID,
|
|
4116
|
+
agent: userMessage?.info?.agent,
|
|
4117
|
+
model: userMessage?.info?.model
|
|
4118
|
+
};
|
|
4119
|
+
}
|
|
4120
|
+
async function resumeSession(client, config) {
|
|
4121
|
+
try {
|
|
4122
|
+
await client.session.prompt({
|
|
4123
|
+
path: { id: config.sessionID },
|
|
4124
|
+
body: {
|
|
4125
|
+
parts: [{ type: "text", text: RECOVERY_RESUME_TEXT }],
|
|
4126
|
+
agent: config.agent,
|
|
4127
|
+
model: config.model
|
|
4128
|
+
}
|
|
4129
|
+
});
|
|
4130
|
+
return true;
|
|
4131
|
+
} catch {
|
|
4132
|
+
return false;
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4131
4135
|
function getErrorMessage(error) {
|
|
4132
4136
|
if (!error)
|
|
4133
4137
|
return "";
|
|
@@ -4170,9 +4174,6 @@ function detectErrorType(error) {
|
|
|
4170
4174
|
if (message.includes("thinking is disabled") && message.includes("cannot contain")) {
|
|
4171
4175
|
return "thinking_disabled_violation";
|
|
4172
4176
|
}
|
|
4173
|
-
if (message.includes("non-empty content") || message.includes("must have non-empty content") || message.includes("content") && message.includes("is empty") || message.includes("content field") && message.includes("empty")) {
|
|
4174
|
-
return "empty_content_message";
|
|
4175
|
-
}
|
|
4176
4177
|
return null;
|
|
4177
4178
|
}
|
|
4178
4179
|
function extractToolUseIds(parts) {
|
|
@@ -4241,55 +4242,9 @@ async function recoverThinkingDisabledViolation(_client, sessionID, _failedAssis
|
|
|
4241
4242
|
}
|
|
4242
4243
|
return anySuccess;
|
|
4243
4244
|
}
|
|
4244
|
-
|
|
4245
|
-
async function recoverEmptyContentMessage(_client, sessionID, failedAssistantMsg, _directory, error) {
|
|
4246
|
-
const targetIndex = extractMessageIndex(error);
|
|
4247
|
-
const failedID = failedAssistantMsg.info?.id;
|
|
4248
|
-
let anySuccess = false;
|
|
4249
|
-
const messagesWithEmptyText = findMessagesWithEmptyTextParts(sessionID);
|
|
4250
|
-
for (const messageID of messagesWithEmptyText) {
|
|
4251
|
-
if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
|
|
4252
|
-
anySuccess = true;
|
|
4253
|
-
}
|
|
4254
|
-
}
|
|
4255
|
-
const thinkingOnlyIDs = findMessagesWithThinkingOnly(sessionID);
|
|
4256
|
-
for (const messageID of thinkingOnlyIDs) {
|
|
4257
|
-
if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
|
|
4258
|
-
anySuccess = true;
|
|
4259
|
-
}
|
|
4260
|
-
}
|
|
4261
|
-
if (targetIndex !== null) {
|
|
4262
|
-
const targetMessageID = findEmptyMessageByIndex(sessionID, targetIndex);
|
|
4263
|
-
if (targetMessageID) {
|
|
4264
|
-
if (replaceEmptyTextParts(targetMessageID, PLACEHOLDER_TEXT)) {
|
|
4265
|
-
return true;
|
|
4266
|
-
}
|
|
4267
|
-
if (injectTextPart(sessionID, targetMessageID, PLACEHOLDER_TEXT)) {
|
|
4268
|
-
return true;
|
|
4269
|
-
}
|
|
4270
|
-
}
|
|
4271
|
-
}
|
|
4272
|
-
if (failedID) {
|
|
4273
|
-
if (replaceEmptyTextParts(failedID, PLACEHOLDER_TEXT)) {
|
|
4274
|
-
return true;
|
|
4275
|
-
}
|
|
4276
|
-
if (injectTextPart(sessionID, failedID, PLACEHOLDER_TEXT)) {
|
|
4277
|
-
return true;
|
|
4278
|
-
}
|
|
4279
|
-
}
|
|
4280
|
-
const emptyMessageIDs = findEmptyMessages(sessionID);
|
|
4281
|
-
for (const messageID of emptyMessageIDs) {
|
|
4282
|
-
if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
|
|
4283
|
-
anySuccess = true;
|
|
4284
|
-
}
|
|
4285
|
-
if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
|
|
4286
|
-
anySuccess = true;
|
|
4287
|
-
}
|
|
4288
|
-
}
|
|
4289
|
-
return anySuccess;
|
|
4290
|
-
}
|
|
4291
|
-
function createSessionRecoveryHook(ctx) {
|
|
4245
|
+
function createSessionRecoveryHook(ctx, options) {
|
|
4292
4246
|
const processingErrors = new Set;
|
|
4247
|
+
const experimental = options?.experimental;
|
|
4293
4248
|
let onAbortCallback = null;
|
|
4294
4249
|
let onRecoveryCompleteCallback = null;
|
|
4295
4250
|
const setOnAbortCallback = (callback) => {
|
|
@@ -4331,14 +4286,12 @@ function createSessionRecoveryHook(ctx) {
|
|
|
4331
4286
|
const toastTitles = {
|
|
4332
4287
|
tool_result_missing: "Tool Crash Recovery",
|
|
4333
4288
|
thinking_block_order: "Thinking Block Recovery",
|
|
4334
|
-
thinking_disabled_violation: "Thinking Strip Recovery"
|
|
4335
|
-
empty_content_message: "Empty Message Recovery"
|
|
4289
|
+
thinking_disabled_violation: "Thinking Strip Recovery"
|
|
4336
4290
|
};
|
|
4337
4291
|
const toastMessages = {
|
|
4338
4292
|
tool_result_missing: "Injecting cancelled tool results...",
|
|
4339
4293
|
thinking_block_order: "Fixing message structure...",
|
|
4340
|
-
thinking_disabled_violation: "Stripping thinking blocks..."
|
|
4341
|
-
empty_content_message: "Fixing empty message..."
|
|
4294
|
+
thinking_disabled_violation: "Stripping thinking blocks..."
|
|
4342
4295
|
};
|
|
4343
4296
|
await ctx.client.tui.showToast({
|
|
4344
4297
|
body: {
|
|
@@ -4353,10 +4306,18 @@ function createSessionRecoveryHook(ctx) {
|
|
|
4353
4306
|
success = await recoverToolResultMissing(ctx.client, sessionID, failedMsg);
|
|
4354
4307
|
} else if (errorType === "thinking_block_order") {
|
|
4355
4308
|
success = await recoverThinkingBlockOrder(ctx.client, sessionID, failedMsg, ctx.directory, info.error);
|
|
4309
|
+
if (success && experimental?.auto_resume) {
|
|
4310
|
+
const lastUser = findLastUserMessage(msgs ?? []);
|
|
4311
|
+
const resumeConfig = extractResumeConfig(lastUser, sessionID);
|
|
4312
|
+
await resumeSession(ctx.client, resumeConfig);
|
|
4313
|
+
}
|
|
4356
4314
|
} else if (errorType === "thinking_disabled_violation") {
|
|
4357
4315
|
success = await recoverThinkingDisabledViolation(ctx.client, sessionID, failedMsg);
|
|
4358
|
-
|
|
4359
|
-
|
|
4316
|
+
if (success && experimental?.auto_resume) {
|
|
4317
|
+
const lastUser = findLastUserMessage(msgs ?? []);
|
|
4318
|
+
const resumeConfig = extractResumeConfig(lastUser, sessionID);
|
|
4319
|
+
await resumeSession(ctx.client, resumeConfig);
|
|
4320
|
+
}
|
|
4360
4321
|
}
|
|
4361
4322
|
return success;
|
|
4362
4323
|
} catch (err) {
|
|
@@ -4379,7 +4340,7 @@ function createSessionRecoveryHook(ctx) {
|
|
|
4379
4340
|
// src/hooks/comment-checker/cli.ts
|
|
4380
4341
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
4381
4342
|
import { createRequire as createRequire2 } from "module";
|
|
4382
|
-
import { dirname, join as
|
|
4343
|
+
import { dirname, join as join10 } from "path";
|
|
4383
4344
|
import { existsSync as existsSync7 } from "fs";
|
|
4384
4345
|
import * as fs2 from "fs";
|
|
4385
4346
|
import { tmpdir as tmpdir3 } from "os";
|
|
@@ -4387,11 +4348,11 @@ import { tmpdir as tmpdir3 } from "os";
|
|
|
4387
4348
|
// src/hooks/comment-checker/downloader.ts
|
|
4388
4349
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
4389
4350
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, chmodSync, unlinkSync as unlinkSync2, appendFileSync as appendFileSync2 } from "fs";
|
|
4390
|
-
import { join as
|
|
4391
|
-
import { homedir as
|
|
4351
|
+
import { join as join9 } from "path";
|
|
4352
|
+
import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
|
|
4392
4353
|
import { createRequire } from "module";
|
|
4393
4354
|
var DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
4394
|
-
var DEBUG_FILE =
|
|
4355
|
+
var DEBUG_FILE = join9(tmpdir2(), "comment-checker-debug.log");
|
|
4395
4356
|
function debugLog(...args) {
|
|
4396
4357
|
if (DEBUG) {
|
|
4397
4358
|
const msg = `[${new Date().toISOString()}] [comment-checker:downloader] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
|
|
@@ -4409,14 +4370,14 @@ var PLATFORM_MAP = {
|
|
|
4409
4370
|
};
|
|
4410
4371
|
function getCacheDir() {
|
|
4411
4372
|
const xdgCache2 = process.env.XDG_CACHE_HOME;
|
|
4412
|
-
const base = xdgCache2 ||
|
|
4413
|
-
return
|
|
4373
|
+
const base = xdgCache2 || join9(homedir4(), ".cache");
|
|
4374
|
+
return join9(base, "oh-my-opencode", "bin");
|
|
4414
4375
|
}
|
|
4415
4376
|
function getBinaryName() {
|
|
4416
4377
|
return process.platform === "win32" ? "comment-checker.exe" : "comment-checker";
|
|
4417
4378
|
}
|
|
4418
4379
|
function getCachedBinaryPath() {
|
|
4419
|
-
const binaryPath =
|
|
4380
|
+
const binaryPath = join9(getCacheDir(), getBinaryName());
|
|
4420
4381
|
return existsSync6(binaryPath) ? binaryPath : null;
|
|
4421
4382
|
}
|
|
4422
4383
|
function getPackageVersion() {
|
|
@@ -4464,14 +4425,14 @@ async function downloadCommentChecker() {
|
|
|
4464
4425
|
}
|
|
4465
4426
|
const cacheDir = getCacheDir();
|
|
4466
4427
|
const binaryName = getBinaryName();
|
|
4467
|
-
const binaryPath =
|
|
4428
|
+
const binaryPath = join9(cacheDir, binaryName);
|
|
4468
4429
|
if (existsSync6(binaryPath)) {
|
|
4469
4430
|
debugLog("Binary already cached at:", binaryPath);
|
|
4470
4431
|
return binaryPath;
|
|
4471
4432
|
}
|
|
4472
4433
|
const version = getPackageVersion();
|
|
4473
|
-
const { os:
|
|
4474
|
-
const assetName = `comment-checker_v${version}_${
|
|
4434
|
+
const { os: os4, arch, ext } = platformInfo;
|
|
4435
|
+
const assetName = `comment-checker_v${version}_${os4}_${arch}.${ext}`;
|
|
4475
4436
|
const downloadUrl = `https://github.com/${REPO}/releases/download/v${version}/${assetName}`;
|
|
4476
4437
|
debugLog(`Downloading from: ${downloadUrl}`);
|
|
4477
4438
|
console.log(`[oh-my-opencode] Downloading comment-checker binary...`);
|
|
@@ -4483,7 +4444,7 @@ async function downloadCommentChecker() {
|
|
|
4483
4444
|
if (!response.ok) {
|
|
4484
4445
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
4485
4446
|
}
|
|
4486
|
-
const archivePath =
|
|
4447
|
+
const archivePath = join9(cacheDir, assetName);
|
|
4487
4448
|
const arrayBuffer = await response.arrayBuffer();
|
|
4488
4449
|
await Bun.write(archivePath, arrayBuffer);
|
|
4489
4450
|
debugLog(`Downloaded archive to: ${archivePath}`);
|
|
@@ -4519,7 +4480,7 @@ async function ensureCommentCheckerBinary() {
|
|
|
4519
4480
|
|
|
4520
4481
|
// src/hooks/comment-checker/cli.ts
|
|
4521
4482
|
var DEBUG2 = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
4522
|
-
var DEBUG_FILE2 =
|
|
4483
|
+
var DEBUG_FILE2 = join10(tmpdir3(), "comment-checker-debug.log");
|
|
4523
4484
|
function debugLog2(...args) {
|
|
4524
4485
|
if (DEBUG2) {
|
|
4525
4486
|
const msg = `[${new Date().toISOString()}] [comment-checker:cli] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
|
|
@@ -4536,7 +4497,7 @@ function findCommentCheckerPathSync() {
|
|
|
4536
4497
|
const require2 = createRequire2(import.meta.url);
|
|
4537
4498
|
const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
|
|
4538
4499
|
const cliDir = dirname(cliPkgPath);
|
|
4539
|
-
const binaryPath =
|
|
4500
|
+
const binaryPath = join10(cliDir, "bin", binaryName);
|
|
4540
4501
|
if (existsSync7(binaryPath)) {
|
|
4541
4502
|
debugLog2("found binary in main package:", binaryPath);
|
|
4542
4503
|
return binaryPath;
|
|
@@ -4583,8 +4544,8 @@ async function getCommentCheckerPath() {
|
|
|
4583
4544
|
function startBackgroundInit() {
|
|
4584
4545
|
if (!initPromise) {
|
|
4585
4546
|
initPromise = getCommentCheckerPath();
|
|
4586
|
-
initPromise.then((
|
|
4587
|
-
debugLog2("background init complete:",
|
|
4547
|
+
initPromise.then((path4) => {
|
|
4548
|
+
debugLog2("background init complete:", path4 || "no binary");
|
|
4588
4549
|
}).catch((err) => {
|
|
4589
4550
|
debugLog2("background init error:", err);
|
|
4590
4551
|
});
|
|
@@ -4633,9 +4594,9 @@ async function runCommentChecker(input, cliPath) {
|
|
|
4633
4594
|
import * as fs3 from "fs";
|
|
4634
4595
|
import { existsSync as existsSync8 } from "fs";
|
|
4635
4596
|
import { tmpdir as tmpdir4 } from "os";
|
|
4636
|
-
import { join as
|
|
4597
|
+
import { join as join11 } from "path";
|
|
4637
4598
|
var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
4638
|
-
var DEBUG_FILE3 =
|
|
4599
|
+
var DEBUG_FILE3 = join11(tmpdir4(), "comment-checker-debug.log");
|
|
4639
4600
|
function debugLog3(...args) {
|
|
4640
4601
|
if (DEBUG3) {
|
|
4641
4602
|
const msg = `[${new Date().toISOString()}] [comment-checker:hook] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
|
|
@@ -4659,8 +4620,8 @@ function createCommentCheckerHooks() {
|
|
|
4659
4620
|
debugLog3("createCommentCheckerHooks called");
|
|
4660
4621
|
startBackgroundInit();
|
|
4661
4622
|
cliPathPromise = getCommentCheckerPath();
|
|
4662
|
-
cliPathPromise.then((
|
|
4663
|
-
debugLog3("CLI path resolved:",
|
|
4623
|
+
cliPathPromise.then((path4) => {
|
|
4624
|
+
debugLog3("CLI path resolved:", path4 || "disabled (no binary)");
|
|
4664
4625
|
}).catch((err) => {
|
|
4665
4626
|
debugLog3("CLI path resolution error:", err);
|
|
4666
4627
|
});
|
|
@@ -4781,7 +4742,7 @@ function createToolOutputTruncatorHook(ctx) {
|
|
|
4781
4742
|
}
|
|
4782
4743
|
// src/hooks/directory-agents-injector/index.ts
|
|
4783
4744
|
import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
|
|
4784
|
-
import { dirname as dirname2, join as
|
|
4745
|
+
import { dirname as dirname2, join as join14, resolve as resolve2 } from "path";
|
|
4785
4746
|
|
|
4786
4747
|
// src/hooks/directory-agents-injector/storage.ts
|
|
4787
4748
|
import {
|
|
@@ -4791,17 +4752,17 @@ import {
|
|
|
4791
4752
|
writeFileSync as writeFileSync3,
|
|
4792
4753
|
unlinkSync as unlinkSync3
|
|
4793
4754
|
} from "fs";
|
|
4794
|
-
import { join as
|
|
4755
|
+
import { join as join13 } from "path";
|
|
4795
4756
|
|
|
4796
4757
|
// src/hooks/directory-agents-injector/constants.ts
|
|
4797
|
-
import { join as
|
|
4798
|
-
var OPENCODE_STORAGE3 =
|
|
4799
|
-
var AGENTS_INJECTOR_STORAGE =
|
|
4758
|
+
import { join as join12 } from "path";
|
|
4759
|
+
var OPENCODE_STORAGE3 = join12(xdgData2 ?? "", "opencode", "storage");
|
|
4760
|
+
var AGENTS_INJECTOR_STORAGE = join12(OPENCODE_STORAGE3, "directory-agents");
|
|
4800
4761
|
var AGENTS_FILENAME = "AGENTS.md";
|
|
4801
4762
|
|
|
4802
4763
|
// src/hooks/directory-agents-injector/storage.ts
|
|
4803
4764
|
function getStoragePath(sessionID) {
|
|
4804
|
-
return
|
|
4765
|
+
return join13(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
4805
4766
|
}
|
|
4806
4767
|
function loadInjectedPaths(sessionID) {
|
|
4807
4768
|
const filePath = getStoragePath(sessionID);
|
|
@@ -4853,7 +4814,7 @@ function createDirectoryAgentsInjectorHook(ctx) {
|
|
|
4853
4814
|
const found = [];
|
|
4854
4815
|
let current = startDir;
|
|
4855
4816
|
while (true) {
|
|
4856
|
-
const agentsPath =
|
|
4817
|
+
const agentsPath = join14(current, AGENTS_FILENAME);
|
|
4857
4818
|
if (existsSync10(agentsPath)) {
|
|
4858
4819
|
found.push(agentsPath);
|
|
4859
4820
|
}
|
|
@@ -4890,10 +4851,10 @@ function createDirectoryAgentsInjectorHook(ctx) {
|
|
|
4890
4851
|
}
|
|
4891
4852
|
if (toInject.length === 0)
|
|
4892
4853
|
return;
|
|
4893
|
-
for (const { path:
|
|
4854
|
+
for (const { path: path4, content } of toInject) {
|
|
4894
4855
|
output.output += `
|
|
4895
4856
|
|
|
4896
|
-
[Directory Context: ${
|
|
4857
|
+
[Directory Context: ${path4}]
|
|
4897
4858
|
${content}`;
|
|
4898
4859
|
}
|
|
4899
4860
|
saveInjectedPaths(input.sessionID, cache);
|
|
@@ -4922,7 +4883,7 @@ ${content}`;
|
|
|
4922
4883
|
}
|
|
4923
4884
|
// src/hooks/directory-readme-injector/index.ts
|
|
4924
4885
|
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
4925
|
-
import { dirname as dirname3, join as
|
|
4886
|
+
import { dirname as dirname3, join as join17, resolve as resolve3 } from "path";
|
|
4926
4887
|
|
|
4927
4888
|
// src/hooks/directory-readme-injector/storage.ts
|
|
4928
4889
|
import {
|
|
@@ -4932,17 +4893,17 @@ import {
|
|
|
4932
4893
|
writeFileSync as writeFileSync4,
|
|
4933
4894
|
unlinkSync as unlinkSync4
|
|
4934
4895
|
} from "fs";
|
|
4935
|
-
import { join as
|
|
4896
|
+
import { join as join16 } from "path";
|
|
4936
4897
|
|
|
4937
4898
|
// src/hooks/directory-readme-injector/constants.ts
|
|
4938
|
-
import { join as
|
|
4939
|
-
var OPENCODE_STORAGE4 =
|
|
4940
|
-
var README_INJECTOR_STORAGE =
|
|
4899
|
+
import { join as join15 } from "path";
|
|
4900
|
+
var OPENCODE_STORAGE4 = join15(xdgData2 ?? "", "opencode", "storage");
|
|
4901
|
+
var README_INJECTOR_STORAGE = join15(OPENCODE_STORAGE4, "directory-readme");
|
|
4941
4902
|
var README_FILENAME = "README.md";
|
|
4942
4903
|
|
|
4943
4904
|
// src/hooks/directory-readme-injector/storage.ts
|
|
4944
4905
|
function getStoragePath2(sessionID) {
|
|
4945
|
-
return
|
|
4906
|
+
return join16(README_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
4946
4907
|
}
|
|
4947
4908
|
function loadInjectedPaths2(sessionID) {
|
|
4948
4909
|
const filePath = getStoragePath2(sessionID);
|
|
@@ -4994,7 +4955,7 @@ function createDirectoryReadmeInjectorHook(ctx) {
|
|
|
4994
4955
|
const found = [];
|
|
4995
4956
|
let current = startDir;
|
|
4996
4957
|
while (true) {
|
|
4997
|
-
const readmePath =
|
|
4958
|
+
const readmePath = join17(current, README_FILENAME);
|
|
4998
4959
|
if (existsSync12(readmePath)) {
|
|
4999
4960
|
found.push(readmePath);
|
|
5000
4961
|
}
|
|
@@ -5031,10 +4992,10 @@ function createDirectoryReadmeInjectorHook(ctx) {
|
|
|
5031
4992
|
}
|
|
5032
4993
|
if (toInject.length === 0)
|
|
5033
4994
|
return;
|
|
5034
|
-
for (const { path:
|
|
4995
|
+
for (const { path: path4, content } of toInject) {
|
|
5035
4996
|
output.output += `
|
|
5036
4997
|
|
|
5037
|
-
[Project README: ${
|
|
4998
|
+
[Project README: ${path4}]
|
|
5038
4999
|
${content}`;
|
|
5039
5000
|
}
|
|
5040
5001
|
saveInjectedPaths2(input.sessionID, cache);
|
|
@@ -5097,7 +5058,8 @@ var TOKEN_LIMIT_KEYWORDS = [
|
|
|
5097
5058
|
"max_tokens",
|
|
5098
5059
|
"token limit",
|
|
5099
5060
|
"context length",
|
|
5100
|
-
"too many tokens"
|
|
5061
|
+
"too many tokens",
|
|
5062
|
+
"non-empty content"
|
|
5101
5063
|
];
|
|
5102
5064
|
function extractTokensFromMessage(message) {
|
|
5103
5065
|
for (const pattern of TOKEN_LIMIT_PATTERNS) {
|
|
@@ -5116,6 +5078,13 @@ function isTokenLimitError(text) {
|
|
|
5116
5078
|
}
|
|
5117
5079
|
function parseAnthropicTokenLimitError(err) {
|
|
5118
5080
|
if (typeof err === "string") {
|
|
5081
|
+
if (err.toLowerCase().includes("non-empty content")) {
|
|
5082
|
+
return {
|
|
5083
|
+
currentTokens: 0,
|
|
5084
|
+
maxTokens: 0,
|
|
5085
|
+
errorType: "non-empty content"
|
|
5086
|
+
};
|
|
5087
|
+
}
|
|
5119
5088
|
if (isTokenLimitError(err)) {
|
|
5120
5089
|
const tokens = extractTokensFromMessage(err);
|
|
5121
5090
|
return {
|
|
@@ -5211,6 +5180,13 @@ function parseAnthropicTokenLimitError(err) {
|
|
|
5211
5180
|
};
|
|
5212
5181
|
}
|
|
5213
5182
|
}
|
|
5183
|
+
if (combinedText.toLowerCase().includes("non-empty content")) {
|
|
5184
|
+
return {
|
|
5185
|
+
currentTokens: 0,
|
|
5186
|
+
maxTokens: 0,
|
|
5187
|
+
errorType: "non-empty content"
|
|
5188
|
+
};
|
|
5189
|
+
}
|
|
5214
5190
|
if (isTokenLimitError(combinedText)) {
|
|
5215
5191
|
return {
|
|
5216
5192
|
currentTokens: 0,
|
|
@@ -5233,26 +5209,35 @@ var FALLBACK_CONFIG = {
|
|
|
5233
5209
|
minMessagesRequired: 2
|
|
5234
5210
|
};
|
|
5235
5211
|
var TRUNCATE_CONFIG = {
|
|
5236
|
-
maxTruncateAttempts:
|
|
5237
|
-
minOutputSizeToTruncate:
|
|
5212
|
+
maxTruncateAttempts: 20,
|
|
5213
|
+
minOutputSizeToTruncate: 500,
|
|
5214
|
+
targetTokenRatio: 0.5,
|
|
5215
|
+
charsPerToken: 4
|
|
5238
5216
|
};
|
|
5239
5217
|
|
|
5240
5218
|
// src/hooks/anthropic-auto-compact/storage.ts
|
|
5241
5219
|
import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
5242
|
-
import {
|
|
5243
|
-
|
|
5244
|
-
var
|
|
5245
|
-
|
|
5220
|
+
import { homedir as homedir5 } from "os";
|
|
5221
|
+
import { join as join18 } from "path";
|
|
5222
|
+
var OPENCODE_STORAGE5 = join18(xdgData2 ?? "", "opencode", "storage");
|
|
5223
|
+
if (process.platform === "darwin" && !existsSync13(OPENCODE_STORAGE5)) {
|
|
5224
|
+
const localShare = join18(homedir5(), ".local", "share", "opencode", "storage");
|
|
5225
|
+
if (existsSync13(localShare)) {
|
|
5226
|
+
OPENCODE_STORAGE5 = localShare;
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
var MESSAGE_STORAGE3 = join18(OPENCODE_STORAGE5, "message");
|
|
5230
|
+
var PART_STORAGE3 = join18(OPENCODE_STORAGE5, "part");
|
|
5246
5231
|
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.]";
|
|
5247
5232
|
function getMessageDir3(sessionID) {
|
|
5248
5233
|
if (!existsSync13(MESSAGE_STORAGE3))
|
|
5249
5234
|
return "";
|
|
5250
|
-
const directPath =
|
|
5235
|
+
const directPath = join18(MESSAGE_STORAGE3, sessionID);
|
|
5251
5236
|
if (existsSync13(directPath)) {
|
|
5252
5237
|
return directPath;
|
|
5253
5238
|
}
|
|
5254
5239
|
for (const dir of readdirSync4(MESSAGE_STORAGE3)) {
|
|
5255
|
-
const sessionPath =
|
|
5240
|
+
const sessionPath = join18(MESSAGE_STORAGE3, dir, sessionID);
|
|
5256
5241
|
if (existsSync13(sessionPath)) {
|
|
5257
5242
|
return sessionPath;
|
|
5258
5243
|
}
|
|
@@ -5276,14 +5261,14 @@ function findToolResultsBySize(sessionID) {
|
|
|
5276
5261
|
const messageIds = getMessageIds(sessionID);
|
|
5277
5262
|
const results = [];
|
|
5278
5263
|
for (const messageID of messageIds) {
|
|
5279
|
-
const partDir =
|
|
5264
|
+
const partDir = join18(PART_STORAGE3, messageID);
|
|
5280
5265
|
if (!existsSync13(partDir))
|
|
5281
5266
|
continue;
|
|
5282
5267
|
for (const file of readdirSync4(partDir)) {
|
|
5283
5268
|
if (!file.endsWith(".json"))
|
|
5284
5269
|
continue;
|
|
5285
5270
|
try {
|
|
5286
|
-
const partPath =
|
|
5271
|
+
const partPath = join18(partDir, file);
|
|
5287
5272
|
const content = readFileSync8(partPath, "utf-8");
|
|
5288
5273
|
const part = JSON.parse(content);
|
|
5289
5274
|
if (part.type === "tool" && part.state?.output && !part.truncated) {
|
|
@@ -5328,6 +5313,56 @@ function truncateToolResult(partPath) {
|
|
|
5328
5313
|
return { success: false };
|
|
5329
5314
|
}
|
|
5330
5315
|
}
|
|
5316
|
+
function truncateUntilTargetTokens(sessionID, currentTokens, maxTokens, targetRatio = 0.8, charsPerToken = 4) {
|
|
5317
|
+
const targetTokens = Math.floor(maxTokens * targetRatio);
|
|
5318
|
+
const tokensToReduce = currentTokens - targetTokens;
|
|
5319
|
+
const charsToReduce = tokensToReduce * charsPerToken;
|
|
5320
|
+
if (tokensToReduce <= 0) {
|
|
5321
|
+
return {
|
|
5322
|
+
success: true,
|
|
5323
|
+
sufficient: true,
|
|
5324
|
+
truncatedCount: 0,
|
|
5325
|
+
totalBytesRemoved: 0,
|
|
5326
|
+
targetBytesToRemove: 0,
|
|
5327
|
+
truncatedTools: []
|
|
5328
|
+
};
|
|
5329
|
+
}
|
|
5330
|
+
const results = findToolResultsBySize(sessionID);
|
|
5331
|
+
if (results.length === 0) {
|
|
5332
|
+
return {
|
|
5333
|
+
success: false,
|
|
5334
|
+
sufficient: false,
|
|
5335
|
+
truncatedCount: 0,
|
|
5336
|
+
totalBytesRemoved: 0,
|
|
5337
|
+
targetBytesToRemove: charsToReduce,
|
|
5338
|
+
truncatedTools: []
|
|
5339
|
+
};
|
|
5340
|
+
}
|
|
5341
|
+
let totalRemoved = 0;
|
|
5342
|
+
let truncatedCount = 0;
|
|
5343
|
+
const truncatedTools = [];
|
|
5344
|
+
for (const result of results) {
|
|
5345
|
+
const truncateResult = truncateToolResult(result.partPath);
|
|
5346
|
+
if (truncateResult.success) {
|
|
5347
|
+
truncatedCount++;
|
|
5348
|
+
const removedSize = truncateResult.originalSize ?? result.outputSize;
|
|
5349
|
+
totalRemoved += removedSize;
|
|
5350
|
+
truncatedTools.push({
|
|
5351
|
+
toolName: truncateResult.toolName ?? result.toolName,
|
|
5352
|
+
originalSize: removedSize
|
|
5353
|
+
});
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5356
|
+
const sufficient = totalRemoved >= charsToReduce;
|
|
5357
|
+
return {
|
|
5358
|
+
success: truncatedCount > 0,
|
|
5359
|
+
sufficient,
|
|
5360
|
+
truncatedCount,
|
|
5361
|
+
totalBytesRemoved: totalRemoved,
|
|
5362
|
+
targetBytesToRemove: charsToReduce,
|
|
5363
|
+
truncatedTools
|
|
5364
|
+
};
|
|
5365
|
+
}
|
|
5331
5366
|
|
|
5332
5367
|
// src/hooks/anthropic-auto-compact/executor.ts
|
|
5333
5368
|
function getOrCreateRetryState(autoCompactState, sessionID) {
|
|
@@ -5426,14 +5461,97 @@ function clearSessionState(autoCompactState, sessionID) {
|
|
|
5426
5461
|
autoCompactState.retryStateBySession.delete(sessionID);
|
|
5427
5462
|
autoCompactState.fallbackStateBySession.delete(sessionID);
|
|
5428
5463
|
autoCompactState.truncateStateBySession.delete(sessionID);
|
|
5464
|
+
autoCompactState.emptyContentAttemptBySession.delete(sessionID);
|
|
5429
5465
|
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5430
5466
|
}
|
|
5431
|
-
|
|
5467
|
+
function getOrCreateEmptyContentAttempt(autoCompactState, sessionID) {
|
|
5468
|
+
return autoCompactState.emptyContentAttemptBySession.get(sessionID) ?? 0;
|
|
5469
|
+
}
|
|
5470
|
+
async function fixEmptyMessages(sessionID, autoCompactState, client) {
|
|
5471
|
+
const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
|
|
5472
|
+
autoCompactState.emptyContentAttemptBySession.set(sessionID, attempt + 1);
|
|
5473
|
+
const emptyMessageIds = findEmptyMessages(sessionID);
|
|
5474
|
+
if (emptyMessageIds.length === 0) {
|
|
5475
|
+
await client.tui.showToast({
|
|
5476
|
+
body: {
|
|
5477
|
+
title: "Empty Content Error",
|
|
5478
|
+
message: "No empty messages found in storage. Cannot auto-recover.",
|
|
5479
|
+
variant: "error",
|
|
5480
|
+
duration: 5000
|
|
5481
|
+
}
|
|
5482
|
+
}).catch(() => {});
|
|
5483
|
+
return false;
|
|
5484
|
+
}
|
|
5485
|
+
let fixed = false;
|
|
5486
|
+
for (const messageID of emptyMessageIds) {
|
|
5487
|
+
const success = injectTextPart(sessionID, messageID, "[user interrupted]");
|
|
5488
|
+
if (success)
|
|
5489
|
+
fixed = true;
|
|
5490
|
+
}
|
|
5491
|
+
if (fixed) {
|
|
5492
|
+
await client.tui.showToast({
|
|
5493
|
+
body: {
|
|
5494
|
+
title: "Session Recovery",
|
|
5495
|
+
message: `Fixed ${emptyMessageIds.length} empty messages. Retrying...`,
|
|
5496
|
+
variant: "warning",
|
|
5497
|
+
duration: 3000
|
|
5498
|
+
}
|
|
5499
|
+
}).catch(() => {});
|
|
5500
|
+
}
|
|
5501
|
+
return fixed;
|
|
5502
|
+
}
|
|
5503
|
+
async function executeCompact(sessionID, msg, autoCompactState, client, directory, experimental) {
|
|
5432
5504
|
if (autoCompactState.compactionInProgress.has(sessionID)) {
|
|
5433
5505
|
return;
|
|
5434
5506
|
}
|
|
5435
5507
|
autoCompactState.compactionInProgress.add(sessionID);
|
|
5508
|
+
const errorData = autoCompactState.errorDataBySession.get(sessionID);
|
|
5436
5509
|
const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
|
|
5510
|
+
if (experimental?.aggressive_truncation && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens && truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
|
|
5511
|
+
log("[auto-compact] aggressive truncation triggered (experimental)", {
|
|
5512
|
+
currentTokens: errorData.currentTokens,
|
|
5513
|
+
maxTokens: errorData.maxTokens,
|
|
5514
|
+
targetRatio: TRUNCATE_CONFIG.targetTokenRatio
|
|
5515
|
+
});
|
|
5516
|
+
const aggressiveResult = truncateUntilTargetTokens(sessionID, errorData.currentTokens, errorData.maxTokens, TRUNCATE_CONFIG.targetTokenRatio, TRUNCATE_CONFIG.charsPerToken);
|
|
5517
|
+
if (aggressiveResult.truncatedCount > 0) {
|
|
5518
|
+
truncateState.truncateAttempt += aggressiveResult.truncatedCount;
|
|
5519
|
+
const toolNames = aggressiveResult.truncatedTools.map((t) => t.toolName).join(", ");
|
|
5520
|
+
const statusMsg = aggressiveResult.sufficient ? `Truncated ${aggressiveResult.truncatedCount} outputs (${formatBytes(aggressiveResult.totalBytesRemoved)})` : `Truncated ${aggressiveResult.truncatedCount} outputs (${formatBytes(aggressiveResult.totalBytesRemoved)}) but need ${formatBytes(aggressiveResult.targetBytesToRemove)}. Falling back to summarize/revert...`;
|
|
5521
|
+
await client.tui.showToast({
|
|
5522
|
+
body: {
|
|
5523
|
+
title: aggressiveResult.sufficient ? "Aggressive Truncation" : "Partial Truncation",
|
|
5524
|
+
message: `${statusMsg}: ${toolNames}`,
|
|
5525
|
+
variant: "warning",
|
|
5526
|
+
duration: 4000
|
|
5527
|
+
}
|
|
5528
|
+
}).catch(() => {});
|
|
5529
|
+
log("[auto-compact] aggressive truncation completed", aggressiveResult);
|
|
5530
|
+
if (aggressiveResult.sufficient) {
|
|
5531
|
+
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5532
|
+
setTimeout(async () => {
|
|
5533
|
+
try {
|
|
5534
|
+
await client.session.prompt_async({
|
|
5535
|
+
path: { sessionID },
|
|
5536
|
+
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
5537
|
+
query: { directory }
|
|
5538
|
+
});
|
|
5539
|
+
} catch {}
|
|
5540
|
+
}, 500);
|
|
5541
|
+
return;
|
|
5542
|
+
}
|
|
5543
|
+
} else {
|
|
5544
|
+
await client.tui.showToast({
|
|
5545
|
+
body: {
|
|
5546
|
+
title: "Truncation Skipped",
|
|
5547
|
+
message: "No tool outputs found to truncate.",
|
|
5548
|
+
variant: "warning",
|
|
5549
|
+
duration: 3000
|
|
5550
|
+
}
|
|
5551
|
+
}).catch(() => {});
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
let skipSummarize = false;
|
|
5437
5555
|
if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
|
|
5438
5556
|
const largest = findLargestToolResult(sessionID);
|
|
5439
5557
|
if (largest && largest.outputSize >= TRUNCATE_CONFIG.minOutputSizeToTruncate) {
|
|
@@ -5461,10 +5579,58 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
5461
5579
|
}, 500);
|
|
5462
5580
|
return;
|
|
5463
5581
|
}
|
|
5582
|
+
} else if (errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
|
|
5583
|
+
skipSummarize = true;
|
|
5584
|
+
await client.tui.showToast({
|
|
5585
|
+
body: {
|
|
5586
|
+
title: "Summarize Skipped",
|
|
5587
|
+
message: `Over token limit (${errorData.currentTokens}/${errorData.maxTokens}) with nothing to truncate. Going to revert...`,
|
|
5588
|
+
variant: "warning",
|
|
5589
|
+
duration: 3000
|
|
5590
|
+
}
|
|
5591
|
+
}).catch(() => {});
|
|
5592
|
+
} else if (!errorData?.currentTokens) {
|
|
5593
|
+
await client.tui.showToast({
|
|
5594
|
+
body: {
|
|
5595
|
+
title: "Truncation Skipped",
|
|
5596
|
+
message: "No large tool outputs found.",
|
|
5597
|
+
variant: "warning",
|
|
5598
|
+
duration: 3000
|
|
5599
|
+
}
|
|
5600
|
+
}).catch(() => {});
|
|
5464
5601
|
}
|
|
5465
5602
|
}
|
|
5466
5603
|
const retryState = getOrCreateRetryState(autoCompactState, sessionID);
|
|
5467
|
-
if (
|
|
5604
|
+
if (experimental?.empty_message_recovery && errorData?.errorType?.includes("non-empty content")) {
|
|
5605
|
+
const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
|
|
5606
|
+
if (attempt < 3) {
|
|
5607
|
+
const fixed = await fixEmptyMessages(sessionID, autoCompactState, client);
|
|
5608
|
+
if (fixed) {
|
|
5609
|
+
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5610
|
+
setTimeout(() => {
|
|
5611
|
+
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
5612
|
+
}, 500);
|
|
5613
|
+
return;
|
|
5614
|
+
}
|
|
5615
|
+
} else {
|
|
5616
|
+
await client.tui.showToast({
|
|
5617
|
+
body: {
|
|
5618
|
+
title: "Recovery Failed",
|
|
5619
|
+
message: "Max recovery attempts (3) reached for empty content error. Please start a new session.",
|
|
5620
|
+
variant: "error",
|
|
5621
|
+
duration: 1e4
|
|
5622
|
+
}
|
|
5623
|
+
}).catch(() => {});
|
|
5624
|
+
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5625
|
+
return;
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
if (Date.now() - retryState.lastAttemptTime > 300000) {
|
|
5629
|
+
retryState.attempt = 0;
|
|
5630
|
+
autoCompactState.fallbackStateBySession.delete(sessionID);
|
|
5631
|
+
autoCompactState.truncateStateBySession.delete(sessionID);
|
|
5632
|
+
}
|
|
5633
|
+
if (!skipSummarize && retryState.attempt < RETRY_CONFIG.maxAttempts) {
|
|
5468
5634
|
retryState.attempt++;
|
|
5469
5635
|
retryState.lastAttemptTime = Date.now();
|
|
5470
5636
|
const providerID = msg.providerID;
|
|
@@ -5484,7 +5650,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
5484
5650
|
body: { providerID, modelID },
|
|
5485
5651
|
query: { directory }
|
|
5486
5652
|
});
|
|
5487
|
-
|
|
5653
|
+
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5488
5654
|
setTimeout(async () => {
|
|
5489
5655
|
try {
|
|
5490
5656
|
await client.session.prompt_async({
|
|
@@ -5500,10 +5666,19 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
5500
5666
|
const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
|
|
5501
5667
|
const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
|
|
5502
5668
|
setTimeout(() => {
|
|
5503
|
-
executeCompact(sessionID, msg, autoCompactState, client, directory);
|
|
5669
|
+
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
5504
5670
|
}, cappedDelay);
|
|
5505
5671
|
return;
|
|
5506
5672
|
}
|
|
5673
|
+
} else {
|
|
5674
|
+
await client.tui.showToast({
|
|
5675
|
+
body: {
|
|
5676
|
+
title: "Summarize Skipped",
|
|
5677
|
+
message: "Missing providerID or modelID. Skipping to revert...",
|
|
5678
|
+
variant: "warning",
|
|
5679
|
+
duration: 3000
|
|
5680
|
+
}
|
|
5681
|
+
}).catch(() => {});
|
|
5507
5682
|
}
|
|
5508
5683
|
}
|
|
5509
5684
|
const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
|
|
@@ -5537,10 +5712,19 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
5537
5712
|
truncateState.truncateAttempt = 0;
|
|
5538
5713
|
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5539
5714
|
setTimeout(() => {
|
|
5540
|
-
executeCompact(sessionID, msg, autoCompactState, client, directory);
|
|
5715
|
+
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
5541
5716
|
}, 1000);
|
|
5542
5717
|
return;
|
|
5543
5718
|
} catch {}
|
|
5719
|
+
} else {
|
|
5720
|
+
await client.tui.showToast({
|
|
5721
|
+
body: {
|
|
5722
|
+
title: "Revert Skipped",
|
|
5723
|
+
message: "Could not find last message pair to revert.",
|
|
5724
|
+
variant: "warning",
|
|
5725
|
+
duration: 3000
|
|
5726
|
+
}
|
|
5727
|
+
}).catch(() => {});
|
|
5544
5728
|
}
|
|
5545
5729
|
}
|
|
5546
5730
|
clearSessionState(autoCompactState, sessionID);
|
|
@@ -5562,11 +5746,13 @@ function createAutoCompactState() {
|
|
|
5562
5746
|
retryStateBySession: new Map,
|
|
5563
5747
|
fallbackStateBySession: new Map,
|
|
5564
5748
|
truncateStateBySession: new Map,
|
|
5749
|
+
emptyContentAttemptBySession: new Map,
|
|
5565
5750
|
compactionInProgress: new Set
|
|
5566
5751
|
};
|
|
5567
5752
|
}
|
|
5568
|
-
function createAnthropicAutoCompactHook(ctx) {
|
|
5753
|
+
function createAnthropicAutoCompactHook(ctx, options) {
|
|
5569
5754
|
const autoCompactState = createAutoCompactState();
|
|
5755
|
+
const experimental = options?.experimental;
|
|
5570
5756
|
const eventHandler = async ({ event }) => {
|
|
5571
5757
|
const props = event.properties;
|
|
5572
5758
|
if (event.type === "session.deleted") {
|
|
@@ -5577,15 +5763,18 @@ function createAnthropicAutoCompactHook(ctx) {
|
|
|
5577
5763
|
autoCompactState.retryStateBySession.delete(sessionInfo.id);
|
|
5578
5764
|
autoCompactState.fallbackStateBySession.delete(sessionInfo.id);
|
|
5579
5765
|
autoCompactState.truncateStateBySession.delete(sessionInfo.id);
|
|
5766
|
+
autoCompactState.emptyContentAttemptBySession.delete(sessionInfo.id);
|
|
5580
5767
|
autoCompactState.compactionInProgress.delete(sessionInfo.id);
|
|
5581
5768
|
}
|
|
5582
5769
|
return;
|
|
5583
5770
|
}
|
|
5584
5771
|
if (event.type === "session.error") {
|
|
5585
5772
|
const sessionID = props?.sessionID;
|
|
5773
|
+
log("[auto-compact] session.error received", { sessionID, error: props?.error });
|
|
5586
5774
|
if (!sessionID)
|
|
5587
5775
|
return;
|
|
5588
5776
|
const parsed = parseAnthropicTokenLimitError(props?.error);
|
|
5777
|
+
log("[auto-compact] parsed result", { parsed, hasError: !!props?.error });
|
|
5589
5778
|
if (parsed) {
|
|
5590
5779
|
autoCompactState.pendingCompact.add(sessionID);
|
|
5591
5780
|
autoCompactState.errorDataBySession.set(sessionID, parsed);
|
|
@@ -5595,19 +5784,17 @@ function createAnthropicAutoCompactHook(ctx) {
|
|
|
5595
5784
|
const lastAssistant = await getLastAssistant(sessionID, ctx.client, ctx.directory);
|
|
5596
5785
|
const providerID = parsed.providerID ?? lastAssistant?.providerID;
|
|
5597
5786
|
const modelID = parsed.modelID ?? lastAssistant?.modelID;
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
}, 300);
|
|
5610
|
-
}
|
|
5787
|
+
await ctx.client.tui.showToast({
|
|
5788
|
+
body: {
|
|
5789
|
+
title: "Context Limit Hit",
|
|
5790
|
+
message: "Truncating large tool outputs and recovering...",
|
|
5791
|
+
variant: "warning",
|
|
5792
|
+
duration: 3000
|
|
5793
|
+
}
|
|
5794
|
+
}).catch(() => {});
|
|
5795
|
+
setTimeout(() => {
|
|
5796
|
+
executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
|
|
5797
|
+
}, 300);
|
|
5611
5798
|
}
|
|
5612
5799
|
return;
|
|
5613
5800
|
}
|
|
@@ -5615,7 +5802,9 @@ function createAnthropicAutoCompactHook(ctx) {
|
|
|
5615
5802
|
const info = props?.info;
|
|
5616
5803
|
const sessionID = info?.sessionID;
|
|
5617
5804
|
if (sessionID && info?.role === "assistant" && info.error) {
|
|
5805
|
+
log("[auto-compact] message.updated with error", { sessionID, error: info.error });
|
|
5618
5806
|
const parsed = parseAnthropicTokenLimitError(info.error);
|
|
5807
|
+
log("[auto-compact] message.updated parsed result", { parsed });
|
|
5619
5808
|
if (parsed) {
|
|
5620
5809
|
parsed.providerID = info.providerID;
|
|
5621
5810
|
parsed.modelID = info.modelID;
|
|
@@ -5632,40 +5821,22 @@ function createAnthropicAutoCompactHook(ctx) {
|
|
|
5632
5821
|
if (!autoCompactState.pendingCompact.has(sessionID))
|
|
5633
5822
|
return;
|
|
5634
5823
|
const errorData = autoCompactState.errorDataBySession.get(sessionID);
|
|
5635
|
-
if (errorData?.providerID && errorData?.modelID) {
|
|
5636
|
-
await ctx.client.tui.showToast({
|
|
5637
|
-
body: {
|
|
5638
|
-
title: "Auto Compact",
|
|
5639
|
-
message: "Token limit exceeded. Summarizing session...",
|
|
5640
|
-
variant: "warning",
|
|
5641
|
-
duration: 3000
|
|
5642
|
-
}
|
|
5643
|
-
}).catch(() => {});
|
|
5644
|
-
await executeCompact(sessionID, { providerID: errorData.providerID, modelID: errorData.modelID }, autoCompactState, ctx.client, ctx.directory);
|
|
5645
|
-
return;
|
|
5646
|
-
}
|
|
5647
5824
|
const lastAssistant = await getLastAssistant(sessionID, ctx.client, ctx.directory);
|
|
5648
|
-
if (
|
|
5649
|
-
autoCompactState.pendingCompact.delete(sessionID);
|
|
5650
|
-
return;
|
|
5651
|
-
}
|
|
5652
|
-
if (lastAssistant.summary === true) {
|
|
5653
|
-
autoCompactState.pendingCompact.delete(sessionID);
|
|
5654
|
-
return;
|
|
5655
|
-
}
|
|
5656
|
-
if (!lastAssistant.modelID || !lastAssistant.providerID) {
|
|
5825
|
+
if (lastAssistant?.summary === true) {
|
|
5657
5826
|
autoCompactState.pendingCompact.delete(sessionID);
|
|
5658
5827
|
return;
|
|
5659
5828
|
}
|
|
5829
|
+
const providerID = errorData?.providerID ?? lastAssistant?.providerID;
|
|
5830
|
+
const modelID = errorData?.modelID ?? lastAssistant?.modelID;
|
|
5660
5831
|
await ctx.client.tui.showToast({
|
|
5661
5832
|
body: {
|
|
5662
5833
|
title: "Auto Compact",
|
|
5663
|
-
message: "Token limit exceeded.
|
|
5834
|
+
message: "Token limit exceeded. Attempting recovery...",
|
|
5664
5835
|
variant: "warning",
|
|
5665
5836
|
duration: 3000
|
|
5666
5837
|
}
|
|
5667
5838
|
}).catch(() => {});
|
|
5668
|
-
await executeCompact(sessionID,
|
|
5839
|
+
await executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
|
|
5669
5840
|
}
|
|
5670
5841
|
};
|
|
5671
5842
|
return {
|
|
@@ -5939,8 +6110,8 @@ function createThinkModeHook() {
|
|
|
5939
6110
|
};
|
|
5940
6111
|
}
|
|
5941
6112
|
// src/hooks/claude-code-hooks/config.ts
|
|
5942
|
-
import { homedir as
|
|
5943
|
-
import { join as
|
|
6113
|
+
import { homedir as homedir6 } from "os";
|
|
6114
|
+
import { join as join19 } from "path";
|
|
5944
6115
|
import { existsSync as existsSync14 } from "fs";
|
|
5945
6116
|
function normalizeHookMatcher(raw) {
|
|
5946
6117
|
return {
|
|
@@ -5964,11 +6135,11 @@ function normalizeHooksConfig(raw) {
|
|
|
5964
6135
|
return result;
|
|
5965
6136
|
}
|
|
5966
6137
|
function getClaudeSettingsPaths(customPath) {
|
|
5967
|
-
const home =
|
|
6138
|
+
const home = homedir6();
|
|
5968
6139
|
const paths = [
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
6140
|
+
join19(home, ".claude", "settings.json"),
|
|
6141
|
+
join19(process.cwd(), ".claude", "settings.json"),
|
|
6142
|
+
join19(process.cwd(), ".claude", "settings.local.json")
|
|
5972
6143
|
];
|
|
5973
6144
|
if (customPath && existsSync14(customPath)) {
|
|
5974
6145
|
paths.unshift(customPath);
|
|
@@ -6012,21 +6183,21 @@ async function loadClaudeHooksConfig(customSettingsPath) {
|
|
|
6012
6183
|
|
|
6013
6184
|
// src/hooks/claude-code-hooks/config-loader.ts
|
|
6014
6185
|
import { existsSync as existsSync15 } from "fs";
|
|
6015
|
-
import { homedir as
|
|
6016
|
-
import { join as
|
|
6017
|
-
var USER_CONFIG_PATH =
|
|
6186
|
+
import { homedir as homedir7 } from "os";
|
|
6187
|
+
import { join as join20 } from "path";
|
|
6188
|
+
var USER_CONFIG_PATH = join20(homedir7(), ".config", "opencode", "opencode-cc-plugin.json");
|
|
6018
6189
|
function getProjectConfigPath() {
|
|
6019
|
-
return
|
|
6190
|
+
return join20(process.cwd(), ".opencode", "opencode-cc-plugin.json");
|
|
6020
6191
|
}
|
|
6021
|
-
async function loadConfigFromPath(
|
|
6022
|
-
if (!existsSync15(
|
|
6192
|
+
async function loadConfigFromPath(path4) {
|
|
6193
|
+
if (!existsSync15(path4)) {
|
|
6023
6194
|
return null;
|
|
6024
6195
|
}
|
|
6025
6196
|
try {
|
|
6026
|
-
const content = await Bun.file(
|
|
6197
|
+
const content = await Bun.file(path4).text();
|
|
6027
6198
|
return JSON.parse(content);
|
|
6028
6199
|
} catch (error) {
|
|
6029
|
-
log("Failed to load config", { path:
|
|
6200
|
+
log("Failed to load config", { path: path4, error });
|
|
6030
6201
|
return null;
|
|
6031
6202
|
}
|
|
6032
6203
|
}
|
|
@@ -6198,13 +6369,13 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
|
|
|
6198
6369
|
}
|
|
6199
6370
|
|
|
6200
6371
|
// src/hooks/claude-code-hooks/transcript.ts
|
|
6201
|
-
import { join as
|
|
6372
|
+
import { join as join21 } from "path";
|
|
6202
6373
|
import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync16, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
6203
|
-
import { homedir as
|
|
6374
|
+
import { homedir as homedir8, tmpdir as tmpdir5 } from "os";
|
|
6204
6375
|
import { randomUUID } from "crypto";
|
|
6205
|
-
var TRANSCRIPT_DIR =
|
|
6376
|
+
var TRANSCRIPT_DIR = join21(homedir8(), ".claude", "transcripts");
|
|
6206
6377
|
function getTranscriptPath(sessionId) {
|
|
6207
|
-
return
|
|
6378
|
+
return join21(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
|
|
6208
6379
|
}
|
|
6209
6380
|
function ensureTranscriptDir() {
|
|
6210
6381
|
if (!existsSync16(TRANSCRIPT_DIR)) {
|
|
@@ -6213,10 +6384,10 @@ function ensureTranscriptDir() {
|
|
|
6213
6384
|
}
|
|
6214
6385
|
function appendTranscriptEntry(sessionId, entry) {
|
|
6215
6386
|
ensureTranscriptDir();
|
|
6216
|
-
const
|
|
6387
|
+
const path4 = getTranscriptPath(sessionId);
|
|
6217
6388
|
const line = JSON.stringify(entry) + `
|
|
6218
6389
|
`;
|
|
6219
|
-
appendFileSync5(
|
|
6390
|
+
appendFileSync5(path4, line);
|
|
6220
6391
|
}
|
|
6221
6392
|
function recordToolUse(sessionId, toolName, toolInput) {
|
|
6222
6393
|
appendTranscriptEntry(sessionId, {
|
|
@@ -6294,7 +6465,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
|
|
|
6294
6465
|
}
|
|
6295
6466
|
};
|
|
6296
6467
|
entries.push(JSON.stringify(currentEntry));
|
|
6297
|
-
const tempPath =
|
|
6468
|
+
const tempPath = join21(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
|
|
6298
6469
|
writeFileSync6(tempPath, entries.join(`
|
|
6299
6470
|
`) + `
|
|
6300
6471
|
`);
|
|
@@ -6314,7 +6485,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
|
|
|
6314
6485
|
]
|
|
6315
6486
|
}
|
|
6316
6487
|
};
|
|
6317
|
-
const tempPath =
|
|
6488
|
+
const tempPath = join21(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
|
|
6318
6489
|
writeFileSync6(tempPath, JSON.stringify(currentEntry) + `
|
|
6319
6490
|
`);
|
|
6320
6491
|
return tempPath;
|
|
@@ -6323,11 +6494,11 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
|
|
|
6323
6494
|
}
|
|
6324
6495
|
}
|
|
6325
6496
|
}
|
|
6326
|
-
function deleteTempTranscript(
|
|
6327
|
-
if (!
|
|
6497
|
+
function deleteTempTranscript(path4) {
|
|
6498
|
+
if (!path4)
|
|
6328
6499
|
return;
|
|
6329
6500
|
try {
|
|
6330
|
-
unlinkSync5(
|
|
6501
|
+
unlinkSync5(path4);
|
|
6331
6502
|
} catch {}
|
|
6332
6503
|
}
|
|
6333
6504
|
|
|
@@ -6526,11 +6697,11 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
|
|
|
6526
6697
|
}
|
|
6527
6698
|
|
|
6528
6699
|
// src/hooks/claude-code-hooks/todo.ts
|
|
6529
|
-
import { join as
|
|
6530
|
-
import { homedir as
|
|
6531
|
-
var TODO_DIR =
|
|
6700
|
+
import { join as join22 } from "path";
|
|
6701
|
+
import { homedir as homedir9 } from "os";
|
|
6702
|
+
var TODO_DIR = join22(homedir9(), ".claude", "todos");
|
|
6532
6703
|
function getTodoPath(sessionId) {
|
|
6533
|
-
return
|
|
6704
|
+
return join22(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
|
|
6534
6705
|
}
|
|
6535
6706
|
|
|
6536
6707
|
// src/hooks/claude-code-hooks/stop.ts
|
|
@@ -6863,7 +7034,7 @@ ${result.message}`;
|
|
|
6863
7034
|
}
|
|
6864
7035
|
// src/hooks/rules-injector/index.ts
|
|
6865
7036
|
import { readFileSync as readFileSync10 } from "fs";
|
|
6866
|
-
import { homedir as
|
|
7037
|
+
import { homedir as homedir10 } from "os";
|
|
6867
7038
|
import { relative as relative3, resolve as resolve4 } from "path";
|
|
6868
7039
|
|
|
6869
7040
|
// src/hooks/rules-injector/finder.ts
|
|
@@ -6873,12 +7044,12 @@ import {
|
|
|
6873
7044
|
realpathSync,
|
|
6874
7045
|
statSync as statSync2
|
|
6875
7046
|
} from "fs";
|
|
6876
|
-
import { dirname as dirname4, join as
|
|
7047
|
+
import { dirname as dirname4, join as join24, relative } from "path";
|
|
6877
7048
|
|
|
6878
7049
|
// src/hooks/rules-injector/constants.ts
|
|
6879
|
-
import { join as
|
|
6880
|
-
var OPENCODE_STORAGE6 =
|
|
6881
|
-
var RULES_INJECTOR_STORAGE =
|
|
7050
|
+
import { join as join23 } from "path";
|
|
7051
|
+
var OPENCODE_STORAGE6 = join23(xdgData2 ?? "", "opencode", "storage");
|
|
7052
|
+
var RULES_INJECTOR_STORAGE = join23(OPENCODE_STORAGE6, "rules-injector");
|
|
6882
7053
|
var PROJECT_MARKERS = [
|
|
6883
7054
|
".git",
|
|
6884
7055
|
"pyproject.toml",
|
|
@@ -6905,7 +7076,7 @@ function findProjectRoot(startPath) {
|
|
|
6905
7076
|
}
|
|
6906
7077
|
while (true) {
|
|
6907
7078
|
for (const marker of PROJECT_MARKERS) {
|
|
6908
|
-
const markerPath =
|
|
7079
|
+
const markerPath = join24(current, marker);
|
|
6909
7080
|
if (existsSync17(markerPath)) {
|
|
6910
7081
|
return current;
|
|
6911
7082
|
}
|
|
@@ -6923,7 +7094,7 @@ function findRuleFilesRecursive(dir, results) {
|
|
|
6923
7094
|
try {
|
|
6924
7095
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
6925
7096
|
for (const entry of entries) {
|
|
6926
|
-
const fullPath =
|
|
7097
|
+
const fullPath = join24(dir, entry.name);
|
|
6927
7098
|
if (entry.isDirectory()) {
|
|
6928
7099
|
findRuleFilesRecursive(fullPath, results);
|
|
6929
7100
|
} else if (entry.isFile()) {
|
|
@@ -6949,7 +7120,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
|
|
|
6949
7120
|
let distance = 0;
|
|
6950
7121
|
while (true) {
|
|
6951
7122
|
for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
|
|
6952
|
-
const ruleDir =
|
|
7123
|
+
const ruleDir = join24(currentDir, parent, subdir);
|
|
6953
7124
|
const files = [];
|
|
6954
7125
|
findRuleFilesRecursive(ruleDir, files);
|
|
6955
7126
|
for (const filePath of files) {
|
|
@@ -6973,7 +7144,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
|
|
|
6973
7144
|
currentDir = parentDir;
|
|
6974
7145
|
distance++;
|
|
6975
7146
|
}
|
|
6976
|
-
const userRuleDir =
|
|
7147
|
+
const userRuleDir = join24(homeDir, USER_RULE_DIR);
|
|
6977
7148
|
const userFiles = [];
|
|
6978
7149
|
findRuleFilesRecursive(userRuleDir, userFiles);
|
|
6979
7150
|
for (const filePath of userFiles) {
|
|
@@ -7168,9 +7339,9 @@ import {
|
|
|
7168
7339
|
writeFileSync as writeFileSync7,
|
|
7169
7340
|
unlinkSync as unlinkSync6
|
|
7170
7341
|
} from "fs";
|
|
7171
|
-
import { join as
|
|
7342
|
+
import { join as join25 } from "path";
|
|
7172
7343
|
function getStoragePath3(sessionID) {
|
|
7173
|
-
return
|
|
7344
|
+
return join25(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
7174
7345
|
}
|
|
7175
7346
|
function loadInjectedRules(sessionID) {
|
|
7176
7347
|
const filePath = getStoragePath3(sessionID);
|
|
@@ -7231,7 +7402,7 @@ function createRulesInjectorHook(ctx) {
|
|
|
7231
7402
|
return;
|
|
7232
7403
|
const projectRoot = findProjectRoot(filePath);
|
|
7233
7404
|
const cache2 = getSessionCache(input.sessionID);
|
|
7234
|
-
const home =
|
|
7405
|
+
const home = homedir10();
|
|
7235
7406
|
const ruleFileCandidates = findRuleFiles(projectRoot, home, filePath);
|
|
7236
7407
|
const toInject = [];
|
|
7237
7408
|
for (const candidate of ruleFileCandidates) {
|
|
@@ -7302,33 +7473,33 @@ function createBackgroundNotificationHook(manager) {
|
|
|
7302
7473
|
}
|
|
7303
7474
|
// src/hooks/auto-update-checker/checker.ts
|
|
7304
7475
|
import * as fs4 from "fs";
|
|
7305
|
-
import * as
|
|
7476
|
+
import * as path5 from "path";
|
|
7306
7477
|
import { fileURLToPath } from "url";
|
|
7307
7478
|
|
|
7308
7479
|
// src/hooks/auto-update-checker/constants.ts
|
|
7309
|
-
import * as
|
|
7310
|
-
import * as
|
|
7480
|
+
import * as path4 from "path";
|
|
7481
|
+
import * as os4 from "os";
|
|
7311
7482
|
var PACKAGE_NAME = "oh-my-opencode";
|
|
7312
7483
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
7313
7484
|
var NPM_FETCH_TIMEOUT = 5000;
|
|
7314
7485
|
function getCacheDir2() {
|
|
7315
7486
|
if (process.platform === "win32") {
|
|
7316
|
-
return
|
|
7487
|
+
return path4.join(process.env.LOCALAPPDATA ?? os4.homedir(), "opencode");
|
|
7317
7488
|
}
|
|
7318
|
-
return
|
|
7489
|
+
return path4.join(os4.homedir(), ".cache", "opencode");
|
|
7319
7490
|
}
|
|
7320
7491
|
var CACHE_DIR = getCacheDir2();
|
|
7321
|
-
var VERSION_FILE =
|
|
7322
|
-
var INSTALLED_PACKAGE_JSON =
|
|
7323
|
-
function
|
|
7492
|
+
var VERSION_FILE = path4.join(CACHE_DIR, "version");
|
|
7493
|
+
var INSTALLED_PACKAGE_JSON = path4.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
|
|
7494
|
+
function getUserConfigDir2() {
|
|
7324
7495
|
if (process.platform === "win32") {
|
|
7325
|
-
return process.env.APPDATA ??
|
|
7496
|
+
return process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
|
|
7326
7497
|
}
|
|
7327
|
-
return process.env.XDG_CONFIG_HOME ??
|
|
7498
|
+
return process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
|
|
7328
7499
|
}
|
|
7329
|
-
var USER_CONFIG_DIR =
|
|
7330
|
-
var USER_OPENCODE_CONFIG =
|
|
7331
|
-
var USER_OPENCODE_CONFIG_JSONC =
|
|
7500
|
+
var USER_CONFIG_DIR = getUserConfigDir2();
|
|
7501
|
+
var USER_OPENCODE_CONFIG = path4.join(USER_CONFIG_DIR, "opencode", "opencode.json");
|
|
7502
|
+
var USER_OPENCODE_CONFIG_JSONC = path4.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc");
|
|
7332
7503
|
|
|
7333
7504
|
// src/hooks/auto-update-checker/checker.ts
|
|
7334
7505
|
function isLocalDevMode(directory) {
|
|
@@ -7339,8 +7510,8 @@ function stripJsonComments(json) {
|
|
|
7339
7510
|
}
|
|
7340
7511
|
function getConfigPaths(directory) {
|
|
7341
7512
|
return [
|
|
7342
|
-
|
|
7343
|
-
|
|
7513
|
+
path5.join(directory, ".opencode", "opencode.json"),
|
|
7514
|
+
path5.join(directory, ".opencode", "opencode.jsonc"),
|
|
7344
7515
|
USER_OPENCODE_CONFIG,
|
|
7345
7516
|
USER_OPENCODE_CONFIG_JSONC
|
|
7346
7517
|
];
|
|
@@ -7371,9 +7542,9 @@ function getLocalDevPath(directory) {
|
|
|
7371
7542
|
function findPackageJsonUp(startPath) {
|
|
7372
7543
|
try {
|
|
7373
7544
|
const stat = fs4.statSync(startPath);
|
|
7374
|
-
let dir = stat.isDirectory() ? startPath :
|
|
7545
|
+
let dir = stat.isDirectory() ? startPath : path5.dirname(startPath);
|
|
7375
7546
|
for (let i = 0;i < 10; i++) {
|
|
7376
|
-
const pkgPath =
|
|
7547
|
+
const pkgPath = path5.join(dir, "package.json");
|
|
7377
7548
|
if (fs4.existsSync(pkgPath)) {
|
|
7378
7549
|
try {
|
|
7379
7550
|
const content = fs4.readFileSync(pkgPath, "utf-8");
|
|
@@ -7382,7 +7553,7 @@ function findPackageJsonUp(startPath) {
|
|
|
7382
7553
|
return pkgPath;
|
|
7383
7554
|
} catch {}
|
|
7384
7555
|
}
|
|
7385
|
-
const parent =
|
|
7556
|
+
const parent = path5.dirname(dir);
|
|
7386
7557
|
if (parent === dir)
|
|
7387
7558
|
break;
|
|
7388
7559
|
dir = parent;
|
|
@@ -7439,7 +7610,7 @@ function getCachedVersion() {
|
|
|
7439
7610
|
}
|
|
7440
7611
|
} catch {}
|
|
7441
7612
|
try {
|
|
7442
|
-
const currentDir =
|
|
7613
|
+
const currentDir = path5.dirname(fileURLToPath(import.meta.url));
|
|
7443
7614
|
const pkgPath = findPackageJsonUp(currentDir);
|
|
7444
7615
|
if (pkgPath) {
|
|
7445
7616
|
const content = fs4.readFileSync(pkgPath, "utf-8");
|
|
@@ -7501,11 +7672,11 @@ async function checkForUpdate(directory) {
|
|
|
7501
7672
|
|
|
7502
7673
|
// src/hooks/auto-update-checker/cache.ts
|
|
7503
7674
|
import * as fs5 from "fs";
|
|
7504
|
-
import * as
|
|
7675
|
+
import * as path6 from "path";
|
|
7505
7676
|
function invalidatePackage(packageName = PACKAGE_NAME) {
|
|
7506
7677
|
try {
|
|
7507
|
-
const pkgDir =
|
|
7508
|
-
const pkgJsonPath =
|
|
7678
|
+
const pkgDir = path6.join(CACHE_DIR, "node_modules", packageName);
|
|
7679
|
+
const pkgJsonPath = path6.join(CACHE_DIR, "package.json");
|
|
7509
7680
|
let packageRemoved = false;
|
|
7510
7681
|
let dependencyRemoved = false;
|
|
7511
7682
|
if (fs5.existsSync(pkgDir)) {
|
|
@@ -7536,7 +7707,27 @@ function invalidatePackage(packageName = PACKAGE_NAME) {
|
|
|
7536
7707
|
|
|
7537
7708
|
// src/hooks/auto-update-checker/index.ts
|
|
7538
7709
|
function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
7539
|
-
const { showStartupToast = true } = options;
|
|
7710
|
+
const { showStartupToast = true, isSisyphusEnabled = false } = options;
|
|
7711
|
+
const getToastMessage = (isUpdate, latestVersion) => {
|
|
7712
|
+
if (isSisyphusEnabled) {
|
|
7713
|
+
return isUpdate ? `Sisyphus on steroids is steering OpenCode.
|
|
7714
|
+
v${latestVersion} available. Restart to apply.` : `Sisyphus on steroids is steering OpenCode.`;
|
|
7715
|
+
}
|
|
7716
|
+
return isUpdate ? `OpenCode is now on Steroids. oMoMoMoMo...
|
|
7717
|
+
v${latestVersion} available. Restart OpenCode to apply.` : `OpenCode is now on Steroids. oMoMoMoMo...`;
|
|
7718
|
+
};
|
|
7719
|
+
const showVersionToast = async (version) => {
|
|
7720
|
+
const displayVersion = version ?? "unknown";
|
|
7721
|
+
await ctx.client.tui.showToast({
|
|
7722
|
+
body: {
|
|
7723
|
+
title: `OhMyOpenCode ${displayVersion}`,
|
|
7724
|
+
message: getToastMessage(false),
|
|
7725
|
+
variant: "info",
|
|
7726
|
+
duration: 5000
|
|
7727
|
+
}
|
|
7728
|
+
}).catch(() => {});
|
|
7729
|
+
log(`[auto-update-checker] Startup toast shown: v${displayVersion}`);
|
|
7730
|
+
};
|
|
7540
7731
|
let hasChecked = false;
|
|
7541
7732
|
return {
|
|
7542
7733
|
event: async ({ event }) => {
|
|
@@ -7554,21 +7745,21 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
|
7554
7745
|
log("[auto-update-checker] Skipped: local development mode");
|
|
7555
7746
|
if (showStartupToast) {
|
|
7556
7747
|
const version = getLocalDevVersion(ctx.directory) ?? getCachedVersion();
|
|
7557
|
-
await showVersionToast(
|
|
7748
|
+
await showVersionToast(version);
|
|
7558
7749
|
}
|
|
7559
7750
|
return;
|
|
7560
7751
|
}
|
|
7561
7752
|
if (result.isPinned) {
|
|
7562
7753
|
log(`[auto-update-checker] Skipped: version pinned to ${result.currentVersion}`);
|
|
7563
7754
|
if (showStartupToast) {
|
|
7564
|
-
await showVersionToast(
|
|
7755
|
+
await showVersionToast(result.currentVersion);
|
|
7565
7756
|
}
|
|
7566
7757
|
return;
|
|
7567
7758
|
}
|
|
7568
7759
|
if (!result.needsUpdate) {
|
|
7569
7760
|
log("[auto-update-checker] No update needed");
|
|
7570
7761
|
if (showStartupToast) {
|
|
7571
|
-
await showVersionToast(
|
|
7762
|
+
await showVersionToast(result.currentVersion);
|
|
7572
7763
|
}
|
|
7573
7764
|
return;
|
|
7574
7765
|
}
|
|
@@ -7576,8 +7767,7 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
|
7576
7767
|
await ctx.client.tui.showToast({
|
|
7577
7768
|
body: {
|
|
7578
7769
|
title: `OhMyOpenCode ${result.latestVersion}`,
|
|
7579
|
-
message:
|
|
7580
|
-
v${result.latestVersion} available. Restart OpenCode to apply.`,
|
|
7770
|
+
message: getToastMessage(true, result.latestVersion ?? undefined),
|
|
7581
7771
|
variant: "info",
|
|
7582
7772
|
duration: 8000
|
|
7583
7773
|
}
|
|
@@ -7586,20 +7776,27 @@ v${result.latestVersion} available. Restart OpenCode to apply.`,
|
|
|
7586
7776
|
} catch (err) {
|
|
7587
7777
|
log("[auto-update-checker] Error during update check:", err);
|
|
7588
7778
|
}
|
|
7779
|
+
await showConfigErrorsIfAny(ctx);
|
|
7589
7780
|
}
|
|
7590
7781
|
};
|
|
7591
7782
|
}
|
|
7592
|
-
async function
|
|
7593
|
-
const
|
|
7783
|
+
async function showConfigErrorsIfAny(ctx) {
|
|
7784
|
+
const errors = getConfigLoadErrors();
|
|
7785
|
+
if (errors.length === 0)
|
|
7786
|
+
return;
|
|
7787
|
+
const errorMessages = errors.map((e) => `${e.path}: ${e.error}`).join(`
|
|
7788
|
+
`);
|
|
7594
7789
|
await ctx.client.tui.showToast({
|
|
7595
7790
|
body: {
|
|
7596
|
-
title:
|
|
7597
|
-
message:
|
|
7598
|
-
|
|
7599
|
-
|
|
7791
|
+
title: "Config Load Error",
|
|
7792
|
+
message: `Failed to load config:
|
|
7793
|
+
${errorMessages}`,
|
|
7794
|
+
variant: "error",
|
|
7795
|
+
duration: 1e4
|
|
7600
7796
|
}
|
|
7601
7797
|
}).catch(() => {});
|
|
7602
|
-
log(`[auto-update-checker]
|
|
7798
|
+
log(`[auto-update-checker] Config load errors shown: ${errors.length} error(s)`);
|
|
7799
|
+
clearConfigLoadErrors();
|
|
7603
7800
|
}
|
|
7604
7801
|
// src/hooks/agent-usage-reminder/storage.ts
|
|
7605
7802
|
import {
|
|
@@ -7609,12 +7806,12 @@ import {
|
|
|
7609
7806
|
writeFileSync as writeFileSync9,
|
|
7610
7807
|
unlinkSync as unlinkSync7
|
|
7611
7808
|
} from "fs";
|
|
7612
|
-
import { join as
|
|
7809
|
+
import { join as join30 } from "path";
|
|
7613
7810
|
|
|
7614
7811
|
// src/hooks/agent-usage-reminder/constants.ts
|
|
7615
|
-
import { join as
|
|
7616
|
-
var OPENCODE_STORAGE7 =
|
|
7617
|
-
var AGENT_USAGE_REMINDER_STORAGE =
|
|
7812
|
+
import { join as join29 } from "path";
|
|
7813
|
+
var OPENCODE_STORAGE7 = join29(xdgData2 ?? "", "opencode", "storage");
|
|
7814
|
+
var AGENT_USAGE_REMINDER_STORAGE = join29(OPENCODE_STORAGE7, "agent-usage-reminder");
|
|
7618
7815
|
var TARGET_TOOLS = new Set([
|
|
7619
7816
|
"grep",
|
|
7620
7817
|
"safe_grep",
|
|
@@ -7659,7 +7856,7 @@ ALWAYS prefer: Multiple parallel background_task calls > Direct tool calls
|
|
|
7659
7856
|
|
|
7660
7857
|
// src/hooks/agent-usage-reminder/storage.ts
|
|
7661
7858
|
function getStoragePath4(sessionID) {
|
|
7662
|
-
return
|
|
7859
|
+
return join30(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
|
|
7663
7860
|
}
|
|
7664
7861
|
function loadAgentUsageState(sessionID) {
|
|
7665
7862
|
const filePath = getStoragePath4(sessionID);
|
|
@@ -7829,13 +8026,9 @@ function extractPromptText2(parts) {
|
|
|
7829
8026
|
}
|
|
7830
8027
|
|
|
7831
8028
|
// src/hooks/keyword-detector/index.ts
|
|
7832
|
-
var injectedSessions = new Set;
|
|
7833
8029
|
function createKeywordDetectorHook() {
|
|
7834
8030
|
return {
|
|
7835
8031
|
"chat.message": async (input, output) => {
|
|
7836
|
-
if (injectedSessions.has(input.sessionID)) {
|
|
7837
|
-
return;
|
|
7838
|
-
}
|
|
7839
8032
|
const promptText = extractPromptText2(output.parts);
|
|
7840
8033
|
const messages = detectKeywords(promptText);
|
|
7841
8034
|
if (messages.length === 0) {
|
|
@@ -7853,19 +8046,8 @@ function createKeywordDetectorHook() {
|
|
|
7853
8046
|
tools: message.tools
|
|
7854
8047
|
});
|
|
7855
8048
|
if (success) {
|
|
7856
|
-
injectedSessions.add(input.sessionID);
|
|
7857
8049
|
log("Keyword context injected", { sessionID: input.sessionID });
|
|
7858
8050
|
}
|
|
7859
|
-
},
|
|
7860
|
-
event: async ({
|
|
7861
|
-
event
|
|
7862
|
-
}) => {
|
|
7863
|
-
if (event.type === "session.deleted") {
|
|
7864
|
-
const props = event.properties;
|
|
7865
|
-
if (props?.info?.id) {
|
|
7866
|
-
injectedSessions.delete(props.info.id);
|
|
7867
|
-
}
|
|
7868
|
-
}
|
|
7869
8051
|
}
|
|
7870
8052
|
};
|
|
7871
8053
|
}
|
|
@@ -7909,12 +8091,12 @@ import {
|
|
|
7909
8091
|
writeFileSync as writeFileSync10,
|
|
7910
8092
|
unlinkSync as unlinkSync8
|
|
7911
8093
|
} from "fs";
|
|
7912
|
-
import { join as
|
|
8094
|
+
import { join as join32 } from "path";
|
|
7913
8095
|
|
|
7914
8096
|
// src/hooks/interactive-bash-session/constants.ts
|
|
7915
|
-
import { join as
|
|
7916
|
-
var OPENCODE_STORAGE8 =
|
|
7917
|
-
var INTERACTIVE_BASH_SESSION_STORAGE =
|
|
8097
|
+
import { join as join31 } from "path";
|
|
8098
|
+
var OPENCODE_STORAGE8 = join31(xdgData2 ?? "", "opencode", "storage");
|
|
8099
|
+
var INTERACTIVE_BASH_SESSION_STORAGE = join31(OPENCODE_STORAGE8, "interactive-bash-session");
|
|
7918
8100
|
var OMO_SESSION_PREFIX = "omo-";
|
|
7919
8101
|
function buildSessionReminderMessage(sessions) {
|
|
7920
8102
|
if (sessions.length === 0)
|
|
@@ -7926,7 +8108,7 @@ function buildSessionReminderMessage(sessions) {
|
|
|
7926
8108
|
|
|
7927
8109
|
// src/hooks/interactive-bash-session/storage.ts
|
|
7928
8110
|
function getStoragePath5(sessionID) {
|
|
7929
|
-
return
|
|
8111
|
+
return join32(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
|
|
7930
8112
|
}
|
|
7931
8113
|
function loadInteractiveBashSessionState(sessionID) {
|
|
7932
8114
|
const filePath = getStoragePath5(sessionID);
|
|
@@ -8136,7 +8318,7 @@ function createInteractiveBashSessionHook(_ctx) {
|
|
|
8136
8318
|
};
|
|
8137
8319
|
}
|
|
8138
8320
|
// src/hooks/empty-message-sanitizer/index.ts
|
|
8139
|
-
var
|
|
8321
|
+
var PLACEHOLDER_TEXT = "[user interrupted]";
|
|
8140
8322
|
function hasTextContent(part) {
|
|
8141
8323
|
if (part.type === "text") {
|
|
8142
8324
|
const text = part.text;
|
|
@@ -8165,7 +8347,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
8165
8347
|
if (part.type === "text") {
|
|
8166
8348
|
const textPart = part;
|
|
8167
8349
|
if (!textPart.text || !textPart.text.trim()) {
|
|
8168
|
-
textPart.text =
|
|
8350
|
+
textPart.text = PLACEHOLDER_TEXT;
|
|
8169
8351
|
textPart.synthetic = true;
|
|
8170
8352
|
injected = true;
|
|
8171
8353
|
break;
|
|
@@ -8179,7 +8361,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
8179
8361
|
messageID: message.info.id,
|
|
8180
8362
|
sessionID: message.info.sessionID ?? "",
|
|
8181
8363
|
type: "text",
|
|
8182
|
-
text:
|
|
8364
|
+
text: PLACEHOLDER_TEXT,
|
|
8183
8365
|
synthetic: true
|
|
8184
8366
|
};
|
|
8185
8367
|
if (insertIndex === -1) {
|
|
@@ -8193,7 +8375,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
8193
8375
|
if (part.type === "text") {
|
|
8194
8376
|
const textPart = part;
|
|
8195
8377
|
if (textPart.text !== undefined && textPart.text.trim() === "") {
|
|
8196
|
-
textPart.text =
|
|
8378
|
+
textPart.text = PLACEHOLDER_TEXT;
|
|
8197
8379
|
textPart.synthetic = true;
|
|
8198
8380
|
}
|
|
8199
8381
|
}
|
|
@@ -9903,8 +10085,8 @@ async function createGoogleAntigravityAuthPlugin({
|
|
|
9903
10085
|
}
|
|
9904
10086
|
// src/features/claude-code-command-loader/loader.ts
|
|
9905
10087
|
import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
|
|
9906
|
-
import { homedir as
|
|
9907
|
-
import { join as
|
|
10088
|
+
import { homedir as homedir12 } from "os";
|
|
10089
|
+
import { join as join33, basename } from "path";
|
|
9908
10090
|
function loadCommandsFromDir(commandsDir, scope) {
|
|
9909
10091
|
if (!existsSync23(commandsDir)) {
|
|
9910
10092
|
return [];
|
|
@@ -9914,7 +10096,7 @@ function loadCommandsFromDir(commandsDir, scope) {
|
|
|
9914
10096
|
for (const entry of entries) {
|
|
9915
10097
|
if (!isMarkdownFile(entry))
|
|
9916
10098
|
continue;
|
|
9917
|
-
const commandPath =
|
|
10099
|
+
const commandPath = join33(commandsDir, entry.name);
|
|
9918
10100
|
const commandName = basename(entry.name, ".md");
|
|
9919
10101
|
try {
|
|
9920
10102
|
const content = readFileSync15(commandPath, "utf-8");
|
|
@@ -9957,29 +10139,29 @@ function commandsToRecord(commands) {
|
|
|
9957
10139
|
return result;
|
|
9958
10140
|
}
|
|
9959
10141
|
function loadUserCommands() {
|
|
9960
|
-
const userCommandsDir =
|
|
10142
|
+
const userCommandsDir = join33(homedir12(), ".claude", "commands");
|
|
9961
10143
|
const commands = loadCommandsFromDir(userCommandsDir, "user");
|
|
9962
10144
|
return commandsToRecord(commands);
|
|
9963
10145
|
}
|
|
9964
10146
|
function loadProjectCommands() {
|
|
9965
|
-
const projectCommandsDir =
|
|
10147
|
+
const projectCommandsDir = join33(process.cwd(), ".claude", "commands");
|
|
9966
10148
|
const commands = loadCommandsFromDir(projectCommandsDir, "project");
|
|
9967
10149
|
return commandsToRecord(commands);
|
|
9968
10150
|
}
|
|
9969
10151
|
function loadOpencodeGlobalCommands() {
|
|
9970
|
-
const opencodeCommandsDir =
|
|
10152
|
+
const opencodeCommandsDir = join33(homedir12(), ".config", "opencode", "command");
|
|
9971
10153
|
const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
|
|
9972
10154
|
return commandsToRecord(commands);
|
|
9973
10155
|
}
|
|
9974
10156
|
function loadOpencodeProjectCommands() {
|
|
9975
|
-
const opencodeProjectDir =
|
|
10157
|
+
const opencodeProjectDir = join33(process.cwd(), ".opencode", "command");
|
|
9976
10158
|
const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
|
|
9977
10159
|
return commandsToRecord(commands);
|
|
9978
10160
|
}
|
|
9979
10161
|
// src/features/claude-code-skill-loader/loader.ts
|
|
9980
10162
|
import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
|
|
9981
|
-
import { homedir as
|
|
9982
|
-
import { join as
|
|
10163
|
+
import { homedir as homedir13 } from "os";
|
|
10164
|
+
import { join as join34 } from "path";
|
|
9983
10165
|
function loadSkillsFromDir(skillsDir, scope) {
|
|
9984
10166
|
if (!existsSync24(skillsDir)) {
|
|
9985
10167
|
return [];
|
|
@@ -9989,11 +10171,11 @@ function loadSkillsFromDir(skillsDir, scope) {
|
|
|
9989
10171
|
for (const entry of entries) {
|
|
9990
10172
|
if (entry.name.startsWith("."))
|
|
9991
10173
|
continue;
|
|
9992
|
-
const skillPath =
|
|
10174
|
+
const skillPath = join34(skillsDir, entry.name);
|
|
9993
10175
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
9994
10176
|
continue;
|
|
9995
10177
|
const resolvedPath = resolveSymlink(skillPath);
|
|
9996
|
-
const skillMdPath =
|
|
10178
|
+
const skillMdPath = join34(resolvedPath, "SKILL.md");
|
|
9997
10179
|
if (!existsSync24(skillMdPath))
|
|
9998
10180
|
continue;
|
|
9999
10181
|
try {
|
|
@@ -10003,6 +10185,9 @@ function loadSkillsFromDir(skillsDir, scope) {
|
|
|
10003
10185
|
const originalDescription = data.description || "";
|
|
10004
10186
|
const formattedDescription = `(${scope} - Skill) ${originalDescription}`;
|
|
10005
10187
|
const wrappedTemplate = `<skill-instruction>
|
|
10188
|
+
Base directory for this skill: ${resolvedPath}/
|
|
10189
|
+
File references (@path) in this skill are relative to this directory.
|
|
10190
|
+
|
|
10006
10191
|
${body.trim()}
|
|
10007
10192
|
</skill-instruction>
|
|
10008
10193
|
|
|
@@ -10028,7 +10213,7 @@ $ARGUMENTS
|
|
|
10028
10213
|
return skills;
|
|
10029
10214
|
}
|
|
10030
10215
|
function loadUserSkillsAsCommands() {
|
|
10031
|
-
const userSkillsDir =
|
|
10216
|
+
const userSkillsDir = join34(homedir13(), ".claude", "skills");
|
|
10032
10217
|
const skills = loadSkillsFromDir(userSkillsDir, "user");
|
|
10033
10218
|
return skills.reduce((acc, skill) => {
|
|
10034
10219
|
acc[skill.name] = skill.definition;
|
|
@@ -10036,7 +10221,7 @@ function loadUserSkillsAsCommands() {
|
|
|
10036
10221
|
}, {});
|
|
10037
10222
|
}
|
|
10038
10223
|
function loadProjectSkillsAsCommands() {
|
|
10039
|
-
const projectSkillsDir =
|
|
10224
|
+
const projectSkillsDir = join34(process.cwd(), ".claude", "skills");
|
|
10040
10225
|
const skills = loadSkillsFromDir(projectSkillsDir, "project");
|
|
10041
10226
|
return skills.reduce((acc, skill) => {
|
|
10042
10227
|
acc[skill.name] = skill.definition;
|
|
@@ -10045,8 +10230,8 @@ function loadProjectSkillsAsCommands() {
|
|
|
10045
10230
|
}
|
|
10046
10231
|
// src/features/claude-code-agent-loader/loader.ts
|
|
10047
10232
|
import { existsSync as existsSync25, readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
|
|
10048
|
-
import { homedir as
|
|
10049
|
-
import { join as
|
|
10233
|
+
import { homedir as homedir14 } from "os";
|
|
10234
|
+
import { join as join35, basename as basename2 } from "path";
|
|
10050
10235
|
function parseToolsConfig(toolsStr) {
|
|
10051
10236
|
if (!toolsStr)
|
|
10052
10237
|
return;
|
|
@@ -10068,7 +10253,7 @@ function loadAgentsFromDir(agentsDir, scope) {
|
|
|
10068
10253
|
for (const entry of entries) {
|
|
10069
10254
|
if (!isMarkdownFile(entry))
|
|
10070
10255
|
continue;
|
|
10071
|
-
const agentPath =
|
|
10256
|
+
const agentPath = join35(agentsDir, entry.name);
|
|
10072
10257
|
const agentName = basename2(entry.name, ".md");
|
|
10073
10258
|
try {
|
|
10074
10259
|
const content = readFileSync17(agentPath, "utf-8");
|
|
@@ -10098,7 +10283,7 @@ function loadAgentsFromDir(agentsDir, scope) {
|
|
|
10098
10283
|
return agents;
|
|
10099
10284
|
}
|
|
10100
10285
|
function loadUserAgents() {
|
|
10101
|
-
const userAgentsDir =
|
|
10286
|
+
const userAgentsDir = join35(homedir14(), ".claude", "agents");
|
|
10102
10287
|
const agents = loadAgentsFromDir(userAgentsDir, "user");
|
|
10103
10288
|
const result = {};
|
|
10104
10289
|
for (const agent of agents) {
|
|
@@ -10107,7 +10292,7 @@ function loadUserAgents() {
|
|
|
10107
10292
|
return result;
|
|
10108
10293
|
}
|
|
10109
10294
|
function loadProjectAgents() {
|
|
10110
|
-
const projectAgentsDir =
|
|
10295
|
+
const projectAgentsDir = join35(process.cwd(), ".claude", "agents");
|
|
10111
10296
|
const agents = loadAgentsFromDir(projectAgentsDir, "project");
|
|
10112
10297
|
const result = {};
|
|
10113
10298
|
for (const agent of agents) {
|
|
@@ -10117,8 +10302,8 @@ function loadProjectAgents() {
|
|
|
10117
10302
|
}
|
|
10118
10303
|
// src/features/claude-code-mcp-loader/loader.ts
|
|
10119
10304
|
import { existsSync as existsSync26 } from "fs";
|
|
10120
|
-
import { homedir as
|
|
10121
|
-
import { join as
|
|
10305
|
+
import { homedir as homedir15 } from "os";
|
|
10306
|
+
import { join as join36 } from "path";
|
|
10122
10307
|
|
|
10123
10308
|
// src/features/claude-code-mcp-loader/env-expander.ts
|
|
10124
10309
|
function expandEnvVars(value) {
|
|
@@ -10184,12 +10369,12 @@ function transformMcpServer(name, server) {
|
|
|
10184
10369
|
|
|
10185
10370
|
// src/features/claude-code-mcp-loader/loader.ts
|
|
10186
10371
|
function getMcpConfigPaths() {
|
|
10187
|
-
const home =
|
|
10372
|
+
const home = homedir15();
|
|
10188
10373
|
const cwd = process.cwd();
|
|
10189
10374
|
return [
|
|
10190
|
-
{ path:
|
|
10191
|
-
{ path:
|
|
10192
|
-
{ path:
|
|
10375
|
+
{ path: join36(home, ".claude", ".mcp.json"), scope: "user" },
|
|
10376
|
+
{ path: join36(cwd, ".mcp.json"), scope: "project" },
|
|
10377
|
+
{ path: join36(cwd, ".claude", ".mcp.json"), scope: "local" }
|
|
10193
10378
|
];
|
|
10194
10379
|
}
|
|
10195
10380
|
async function loadMcpConfigFile(filePath) {
|
|
@@ -10208,13 +10393,13 @@ async function loadMcpConfigs() {
|
|
|
10208
10393
|
const servers = {};
|
|
10209
10394
|
const loadedServers = [];
|
|
10210
10395
|
const paths = getMcpConfigPaths();
|
|
10211
|
-
for (const { path:
|
|
10212
|
-
const config = await loadMcpConfigFile(
|
|
10396
|
+
for (const { path: path7, scope } of paths) {
|
|
10397
|
+
const config = await loadMcpConfigFile(path7);
|
|
10213
10398
|
if (!config?.mcpServers)
|
|
10214
10399
|
continue;
|
|
10215
10400
|
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
10216
10401
|
if (serverConfig.disabled) {
|
|
10217
|
-
log(`Skipping disabled MCP server "${name}"`, { path:
|
|
10402
|
+
log(`Skipping disabled MCP server "${name}"`, { path: path7 });
|
|
10218
10403
|
continue;
|
|
10219
10404
|
}
|
|
10220
10405
|
try {
|
|
@@ -10225,7 +10410,7 @@ async function loadMcpConfigs() {
|
|
|
10225
10410
|
loadedServers.splice(existingIndex, 1);
|
|
10226
10411
|
}
|
|
10227
10412
|
loadedServers.push({ name, scope, config: transformed });
|
|
10228
|
-
log(`Loaded MCP server "${name}" from ${scope}`, { path:
|
|
10413
|
+
log(`Loaded MCP server "${name}" from ${scope}`, { path: path7 });
|
|
10229
10414
|
} catch (error) {
|
|
10230
10415
|
log(`Failed to transform MCP server "${name}"`, error);
|
|
10231
10416
|
}
|
|
@@ -10422,13 +10607,13 @@ var EXT_TO_LANG = {
|
|
|
10422
10607
|
};
|
|
10423
10608
|
// src/tools/lsp/config.ts
|
|
10424
10609
|
import { existsSync as existsSync27, readFileSync as readFileSync18 } from "fs";
|
|
10425
|
-
import { join as
|
|
10426
|
-
import { homedir as
|
|
10427
|
-
function loadJsonFile(
|
|
10428
|
-
if (!existsSync27(
|
|
10610
|
+
import { join as join37 } from "path";
|
|
10611
|
+
import { homedir as homedir16 } from "os";
|
|
10612
|
+
function loadJsonFile(path7) {
|
|
10613
|
+
if (!existsSync27(path7))
|
|
10429
10614
|
return null;
|
|
10430
10615
|
try {
|
|
10431
|
-
return JSON.parse(readFileSync18(
|
|
10616
|
+
return JSON.parse(readFileSync18(path7, "utf-8"));
|
|
10432
10617
|
} catch {
|
|
10433
10618
|
return null;
|
|
10434
10619
|
}
|
|
@@ -10436,9 +10621,9 @@ function loadJsonFile(path6) {
|
|
|
10436
10621
|
function getConfigPaths2() {
|
|
10437
10622
|
const cwd = process.cwd();
|
|
10438
10623
|
return {
|
|
10439
|
-
project:
|
|
10440
|
-
user:
|
|
10441
|
-
opencode:
|
|
10624
|
+
project: join37(cwd, ".opencode", "oh-my-opencode.json"),
|
|
10625
|
+
user: join37(homedir16(), ".config", "opencode", "oh-my-opencode.json"),
|
|
10626
|
+
opencode: join37(homedir16(), ".config", "opencode", "opencode.json")
|
|
10442
10627
|
};
|
|
10443
10628
|
}
|
|
10444
10629
|
function loadAllConfigs() {
|
|
@@ -10531,7 +10716,7 @@ function isServerInstalled(command) {
|
|
|
10531
10716
|
const pathEnv = process.env.PATH || "";
|
|
10532
10717
|
const paths = pathEnv.split(":");
|
|
10533
10718
|
for (const p of paths) {
|
|
10534
|
-
if (existsSync27(
|
|
10719
|
+
if (existsSync27(join37(p, cmd))) {
|
|
10535
10720
|
return true;
|
|
10536
10721
|
}
|
|
10537
10722
|
}
|
|
@@ -12140,10 +12325,10 @@ function mergeDefs(...defs) {
|
|
|
12140
12325
|
function cloneDef(schema) {
|
|
12141
12326
|
return mergeDefs(schema._zod.def);
|
|
12142
12327
|
}
|
|
12143
|
-
function getElementAtPath(obj,
|
|
12144
|
-
if (!
|
|
12328
|
+
function getElementAtPath(obj, path7) {
|
|
12329
|
+
if (!path7)
|
|
12145
12330
|
return obj;
|
|
12146
|
-
return
|
|
12331
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
12147
12332
|
}
|
|
12148
12333
|
function promiseAllObject(promisesObj) {
|
|
12149
12334
|
const keys = Object.keys(promisesObj);
|
|
@@ -12502,11 +12687,11 @@ function aborted(x, startIndex = 0) {
|
|
|
12502
12687
|
}
|
|
12503
12688
|
return false;
|
|
12504
12689
|
}
|
|
12505
|
-
function prefixIssues(
|
|
12690
|
+
function prefixIssues(path7, issues) {
|
|
12506
12691
|
return issues.map((iss) => {
|
|
12507
12692
|
var _a;
|
|
12508
12693
|
(_a = iss).path ?? (_a.path = []);
|
|
12509
|
-
iss.path.unshift(
|
|
12694
|
+
iss.path.unshift(path7);
|
|
12510
12695
|
return iss;
|
|
12511
12696
|
});
|
|
12512
12697
|
}
|
|
@@ -12674,7 +12859,7 @@ function treeifyError(error, _mapper) {
|
|
|
12674
12859
|
return issue2.message;
|
|
12675
12860
|
};
|
|
12676
12861
|
const result = { errors: [] };
|
|
12677
|
-
const processError = (error2,
|
|
12862
|
+
const processError = (error2, path7 = []) => {
|
|
12678
12863
|
var _a, _b;
|
|
12679
12864
|
for (const issue2 of error2.issues) {
|
|
12680
12865
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -12684,7 +12869,7 @@ function treeifyError(error, _mapper) {
|
|
|
12684
12869
|
} else if (issue2.code === "invalid_element") {
|
|
12685
12870
|
processError({ issues: issue2.issues }, issue2.path);
|
|
12686
12871
|
} else {
|
|
12687
|
-
const fullpath = [...
|
|
12872
|
+
const fullpath = [...path7, ...issue2.path];
|
|
12688
12873
|
if (fullpath.length === 0) {
|
|
12689
12874
|
result.errors.push(mapper(issue2));
|
|
12690
12875
|
continue;
|
|
@@ -12716,8 +12901,8 @@ function treeifyError(error, _mapper) {
|
|
|
12716
12901
|
}
|
|
12717
12902
|
function toDotPath(_path) {
|
|
12718
12903
|
const segs = [];
|
|
12719
|
-
const
|
|
12720
|
-
for (const seg of
|
|
12904
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
12905
|
+
for (const seg of path7) {
|
|
12721
12906
|
if (typeof seg === "number")
|
|
12722
12907
|
segs.push(`[${seg}]`);
|
|
12723
12908
|
else if (typeof seg === "symbol")
|
|
@@ -24060,14 +24245,14 @@ var lsp_code_action_resolve = tool({
|
|
|
24060
24245
|
});
|
|
24061
24246
|
// src/tools/ast-grep/constants.ts
|
|
24062
24247
|
import { createRequire as createRequire4 } from "module";
|
|
24063
|
-
import { dirname as dirname6, join as
|
|
24248
|
+
import { dirname as dirname6, join as join39 } from "path";
|
|
24064
24249
|
import { existsSync as existsSync30, statSync as statSync4 } from "fs";
|
|
24065
24250
|
|
|
24066
24251
|
// src/tools/ast-grep/downloader.ts
|
|
24067
24252
|
var {spawn: spawn5 } = globalThis.Bun;
|
|
24068
24253
|
import { existsSync as existsSync29, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
|
|
24069
|
-
import { join as
|
|
24070
|
-
import { homedir as
|
|
24254
|
+
import { join as join38 } from "path";
|
|
24255
|
+
import { homedir as homedir17 } from "os";
|
|
24071
24256
|
import { createRequire as createRequire3 } from "module";
|
|
24072
24257
|
var REPO2 = "ast-grep/ast-grep";
|
|
24073
24258
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -24092,18 +24277,18 @@ var PLATFORM_MAP2 = {
|
|
|
24092
24277
|
function getCacheDir3() {
|
|
24093
24278
|
if (process.platform === "win32") {
|
|
24094
24279
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
24095
|
-
const base2 = localAppData ||
|
|
24096
|
-
return
|
|
24280
|
+
const base2 = localAppData || join38(homedir17(), "AppData", "Local");
|
|
24281
|
+
return join38(base2, "oh-my-opencode", "bin");
|
|
24097
24282
|
}
|
|
24098
24283
|
const xdgCache2 = process.env.XDG_CACHE_HOME;
|
|
24099
|
-
const base = xdgCache2 ||
|
|
24100
|
-
return
|
|
24284
|
+
const base = xdgCache2 || join38(homedir17(), ".cache");
|
|
24285
|
+
return join38(base, "oh-my-opencode", "bin");
|
|
24101
24286
|
}
|
|
24102
24287
|
function getBinaryName3() {
|
|
24103
24288
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
24104
24289
|
}
|
|
24105
24290
|
function getCachedBinaryPath2() {
|
|
24106
|
-
const binaryPath =
|
|
24291
|
+
const binaryPath = join38(getCacheDir3(), getBinaryName3());
|
|
24107
24292
|
return existsSync29(binaryPath) ? binaryPath : null;
|
|
24108
24293
|
}
|
|
24109
24294
|
async function extractZip2(archivePath, destDir) {
|
|
@@ -24130,12 +24315,12 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
24130
24315
|
}
|
|
24131
24316
|
const cacheDir = getCacheDir3();
|
|
24132
24317
|
const binaryName = getBinaryName3();
|
|
24133
|
-
const binaryPath =
|
|
24318
|
+
const binaryPath = join38(cacheDir, binaryName);
|
|
24134
24319
|
if (existsSync29(binaryPath)) {
|
|
24135
24320
|
return binaryPath;
|
|
24136
24321
|
}
|
|
24137
|
-
const { arch, os:
|
|
24138
|
-
const assetName = `app-${arch}-${
|
|
24322
|
+
const { arch, os: os5 } = platformInfo;
|
|
24323
|
+
const assetName = `app-${arch}-${os5}.zip`;
|
|
24139
24324
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
|
|
24140
24325
|
console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
|
|
24141
24326
|
try {
|
|
@@ -24146,7 +24331,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
24146
24331
|
if (!response2.ok) {
|
|
24147
24332
|
throw new Error(`HTTP ${response2.status}: ${response2.statusText}`);
|
|
24148
24333
|
}
|
|
24149
|
-
const archivePath =
|
|
24334
|
+
const archivePath = join38(cacheDir, assetName);
|
|
24150
24335
|
const arrayBuffer = await response2.arrayBuffer();
|
|
24151
24336
|
await Bun.write(archivePath, arrayBuffer);
|
|
24152
24337
|
await extractZip2(archivePath, cacheDir);
|
|
@@ -24204,7 +24389,7 @@ function findSgCliPathSync() {
|
|
|
24204
24389
|
const require2 = createRequire4(import.meta.url);
|
|
24205
24390
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
24206
24391
|
const cliDir = dirname6(cliPkgPath);
|
|
24207
|
-
const sgPath =
|
|
24392
|
+
const sgPath = join39(cliDir, binaryName);
|
|
24208
24393
|
if (existsSync30(sgPath) && isValidBinary(sgPath)) {
|
|
24209
24394
|
return sgPath;
|
|
24210
24395
|
}
|
|
@@ -24216,7 +24401,7 @@ function findSgCliPathSync() {
|
|
|
24216
24401
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
24217
24402
|
const pkgDir = dirname6(pkgPath);
|
|
24218
24403
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
24219
|
-
const binaryPath =
|
|
24404
|
+
const binaryPath = join39(pkgDir, astGrepName);
|
|
24220
24405
|
if (existsSync30(binaryPath) && isValidBinary(binaryPath)) {
|
|
24221
24406
|
return binaryPath;
|
|
24222
24407
|
}
|
|
@@ -24224,9 +24409,9 @@ function findSgCliPathSync() {
|
|
|
24224
24409
|
}
|
|
24225
24410
|
if (process.platform === "darwin") {
|
|
24226
24411
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
24227
|
-
for (const
|
|
24228
|
-
if (existsSync30(
|
|
24229
|
-
return
|
|
24412
|
+
for (const path7 of homebrewPaths) {
|
|
24413
|
+
if (existsSync30(path7) && isValidBinary(path7)) {
|
|
24414
|
+
return path7;
|
|
24230
24415
|
}
|
|
24231
24416
|
}
|
|
24232
24417
|
}
|
|
@@ -24244,8 +24429,8 @@ function getSgCliPath() {
|
|
|
24244
24429
|
}
|
|
24245
24430
|
return "sg";
|
|
24246
24431
|
}
|
|
24247
|
-
function setSgCliPath(
|
|
24248
|
-
resolvedCliPath2 =
|
|
24432
|
+
function setSgCliPath(path7) {
|
|
24433
|
+
resolvedCliPath2 = path7;
|
|
24249
24434
|
}
|
|
24250
24435
|
var SG_CLI_PATH = getSgCliPath();
|
|
24251
24436
|
var CLI_LANGUAGES = [
|
|
@@ -24592,19 +24777,19 @@ var {spawn: spawn7 } = globalThis.Bun;
|
|
|
24592
24777
|
|
|
24593
24778
|
// src/tools/grep/constants.ts
|
|
24594
24779
|
import { existsSync as existsSync33 } from "fs";
|
|
24595
|
-
import { join as
|
|
24780
|
+
import { join as join41, dirname as dirname7 } from "path";
|
|
24596
24781
|
import { spawnSync } from "child_process";
|
|
24597
24782
|
|
|
24598
24783
|
// src/tools/grep/downloader.ts
|
|
24599
24784
|
import { existsSync as existsSync32, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync9 } from "fs";
|
|
24600
|
-
import { join as
|
|
24785
|
+
import { join as join40 } from "path";
|
|
24601
24786
|
function getInstallDir() {
|
|
24602
24787
|
const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
|
|
24603
|
-
return
|
|
24788
|
+
return join40(homeDir, ".cache", "oh-my-opencode", "bin");
|
|
24604
24789
|
}
|
|
24605
24790
|
function getRgPath() {
|
|
24606
24791
|
const isWindows2 = process.platform === "win32";
|
|
24607
|
-
return
|
|
24792
|
+
return join40(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
|
|
24608
24793
|
}
|
|
24609
24794
|
function getInstalledRipgrepPath() {
|
|
24610
24795
|
const rgPath = getRgPath();
|
|
@@ -24631,10 +24816,10 @@ function getOpenCodeBundledRg() {
|
|
|
24631
24816
|
const isWindows2 = process.platform === "win32";
|
|
24632
24817
|
const rgName = isWindows2 ? "rg.exe" : "rg";
|
|
24633
24818
|
const candidates = [
|
|
24634
|
-
|
|
24635
|
-
|
|
24636
|
-
|
|
24637
|
-
|
|
24819
|
+
join41(execDir, rgName),
|
|
24820
|
+
join41(execDir, "bin", rgName),
|
|
24821
|
+
join41(execDir, "..", "bin", rgName),
|
|
24822
|
+
join41(execDir, "..", "libexec", rgName)
|
|
24638
24823
|
];
|
|
24639
24824
|
for (const candidate of candidates) {
|
|
24640
24825
|
if (existsSync33(candidate)) {
|
|
@@ -25041,8 +25226,8 @@ var glob = tool({
|
|
|
25041
25226
|
});
|
|
25042
25227
|
// src/tools/slashcommand/tools.ts
|
|
25043
25228
|
import { existsSync as existsSync34, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
|
|
25044
|
-
import { homedir as
|
|
25045
|
-
import { join as
|
|
25229
|
+
import { homedir as homedir18 } from "os";
|
|
25230
|
+
import { join as join42, basename as basename3, dirname as dirname8 } from "path";
|
|
25046
25231
|
function discoverCommandsFromDir(commandsDir, scope) {
|
|
25047
25232
|
if (!existsSync34(commandsDir)) {
|
|
25048
25233
|
return [];
|
|
@@ -25052,7 +25237,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
25052
25237
|
for (const entry of entries) {
|
|
25053
25238
|
if (!isMarkdownFile(entry))
|
|
25054
25239
|
continue;
|
|
25055
|
-
const commandPath =
|
|
25240
|
+
const commandPath = join42(commandsDir, entry.name);
|
|
25056
25241
|
const commandName = basename3(entry.name, ".md");
|
|
25057
25242
|
try {
|
|
25058
25243
|
const content = readFileSync21(commandPath, "utf-8");
|
|
@@ -25080,10 +25265,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
25080
25265
|
return commands;
|
|
25081
25266
|
}
|
|
25082
25267
|
function discoverCommandsSync() {
|
|
25083
|
-
const userCommandsDir =
|
|
25084
|
-
const projectCommandsDir =
|
|
25085
|
-
const opencodeGlobalDir =
|
|
25086
|
-
const opencodeProjectDir =
|
|
25268
|
+
const userCommandsDir = join42(homedir18(), ".claude", "commands");
|
|
25269
|
+
const projectCommandsDir = join42(process.cwd(), ".claude", "commands");
|
|
25270
|
+
const opencodeGlobalDir = join42(homedir18(), ".config", "opencode", "command");
|
|
25271
|
+
const opencodeProjectDir = join42(process.cwd(), ".opencode", "command");
|
|
25087
25272
|
const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
|
|
25088
25273
|
const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
|
|
25089
25274
|
const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
|
|
@@ -25216,8 +25401,8 @@ var SkillFrontmatterSchema = exports_external.object({
|
|
|
25216
25401
|
});
|
|
25217
25402
|
// src/tools/skill/tools.ts
|
|
25218
25403
|
import { existsSync as existsSync35, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
|
|
25219
|
-
import { homedir as
|
|
25220
|
-
import { join as
|
|
25404
|
+
import { homedir as homedir19 } from "os";
|
|
25405
|
+
import { join as join43, basename as basename4 } from "path";
|
|
25221
25406
|
function parseSkillFrontmatter(data) {
|
|
25222
25407
|
return {
|
|
25223
25408
|
name: typeof data.name === "string" ? data.name : "",
|
|
@@ -25236,10 +25421,10 @@ function discoverSkillsFromDir(skillsDir, scope) {
|
|
|
25236
25421
|
for (const entry of entries) {
|
|
25237
25422
|
if (entry.name.startsWith("."))
|
|
25238
25423
|
continue;
|
|
25239
|
-
const skillPath =
|
|
25424
|
+
const skillPath = join43(skillsDir, entry.name);
|
|
25240
25425
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
25241
25426
|
const resolvedPath = resolveSymlink(skillPath);
|
|
25242
|
-
const skillMdPath =
|
|
25427
|
+
const skillMdPath = join43(resolvedPath, "SKILL.md");
|
|
25243
25428
|
if (!existsSync35(skillMdPath))
|
|
25244
25429
|
continue;
|
|
25245
25430
|
try {
|
|
@@ -25258,8 +25443,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
|
|
|
25258
25443
|
return skills;
|
|
25259
25444
|
}
|
|
25260
25445
|
function discoverSkillsSync() {
|
|
25261
|
-
const userSkillsDir =
|
|
25262
|
-
const projectSkillsDir =
|
|
25446
|
+
const userSkillsDir = join43(homedir19(), ".claude", "skills");
|
|
25447
|
+
const projectSkillsDir = join43(process.cwd(), ".claude", "skills");
|
|
25263
25448
|
const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
|
|
25264
25449
|
const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
|
|
25265
25450
|
return [...projectSkills, ...userSkills];
|
|
@@ -25269,7 +25454,7 @@ var skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.descr
|
|
|
25269
25454
|
`);
|
|
25270
25455
|
async function parseSkillMd(skillPath) {
|
|
25271
25456
|
const resolvedPath = resolveSymlink(skillPath);
|
|
25272
|
-
const skillMdPath =
|
|
25457
|
+
const skillMdPath = join43(resolvedPath, "SKILL.md");
|
|
25273
25458
|
if (!existsSync35(skillMdPath)) {
|
|
25274
25459
|
return null;
|
|
25275
25460
|
}
|
|
@@ -25285,9 +25470,9 @@ async function parseSkillMd(skillPath) {
|
|
|
25285
25470
|
allowedTools: frontmatter2["allowed-tools"],
|
|
25286
25471
|
metadata: frontmatter2.metadata
|
|
25287
25472
|
};
|
|
25288
|
-
const referencesDir =
|
|
25289
|
-
const scriptsDir =
|
|
25290
|
-
const assetsDir =
|
|
25473
|
+
const referencesDir = join43(resolvedPath, "references");
|
|
25474
|
+
const scriptsDir = join43(resolvedPath, "scripts");
|
|
25475
|
+
const assetsDir = join43(resolvedPath, "assets");
|
|
25291
25476
|
const references = existsSync35(referencesDir) ? readdirSync11(referencesDir).filter((f) => !f.startsWith(".")) : [];
|
|
25292
25477
|
const scripts = existsSync35(scriptsDir) ? readdirSync11(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
|
|
25293
25478
|
const assets = existsSync35(assetsDir) ? readdirSync11(assetsDir).filter((f) => !f.startsWith(".")) : [];
|
|
@@ -25314,7 +25499,7 @@ async function discoverSkillsFromDirAsync(skillsDir) {
|
|
|
25314
25499
|
for (const entry of entries) {
|
|
25315
25500
|
if (entry.name.startsWith("."))
|
|
25316
25501
|
continue;
|
|
25317
|
-
const skillPath =
|
|
25502
|
+
const skillPath = join43(skillsDir, entry.name);
|
|
25318
25503
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
25319
25504
|
const skillInfo = await parseSkillMd(skillPath);
|
|
25320
25505
|
if (skillInfo) {
|
|
@@ -25325,8 +25510,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
|
|
|
25325
25510
|
return skills;
|
|
25326
25511
|
}
|
|
25327
25512
|
async function discoverSkills() {
|
|
25328
|
-
const userSkillsDir =
|
|
25329
|
-
const projectSkillsDir =
|
|
25513
|
+
const userSkillsDir = join43(homedir19(), ".claude", "skills");
|
|
25514
|
+
const projectSkillsDir = join43(process.cwd(), ".claude", "skills");
|
|
25330
25515
|
const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
|
|
25331
25516
|
const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
|
|
25332
25517
|
return [...projectSkills, ...userSkills];
|
|
@@ -25355,7 +25540,7 @@ async function loadSkillWithReferences(skill, includeRefs) {
|
|
|
25355
25540
|
const referencesLoaded = [];
|
|
25356
25541
|
if (includeRefs && skill.references.length > 0) {
|
|
25357
25542
|
for (const ref of skill.references) {
|
|
25358
|
-
const refPath =
|
|
25543
|
+
const refPath = join43(skill.path, "references", ref);
|
|
25359
25544
|
try {
|
|
25360
25545
|
let content = readFileSync22(refPath, "utf-8");
|
|
25361
25546
|
content = await resolveCommandsInText(content);
|
|
@@ -25479,12 +25664,12 @@ async function findTmuxPath() {
|
|
|
25479
25664
|
return null;
|
|
25480
25665
|
}
|
|
25481
25666
|
const stdout = await new Response(proc.stdout).text();
|
|
25482
|
-
const
|
|
25667
|
+
const path7 = stdout.trim().split(`
|
|
25483
25668
|
`)[0];
|
|
25484
|
-
if (!
|
|
25669
|
+
if (!path7) {
|
|
25485
25670
|
return null;
|
|
25486
25671
|
}
|
|
25487
|
-
const verifyProc = spawn9([
|
|
25672
|
+
const verifyProc = spawn9([path7, "-V"], {
|
|
25488
25673
|
stdout: "pipe",
|
|
25489
25674
|
stderr: "pipe"
|
|
25490
25675
|
});
|
|
@@ -25492,7 +25677,7 @@ async function findTmuxPath() {
|
|
|
25492
25677
|
if (verifyExitCode !== 0) {
|
|
25493
25678
|
return null;
|
|
25494
25679
|
}
|
|
25495
|
-
return
|
|
25680
|
+
return path7;
|
|
25496
25681
|
} catch {
|
|
25497
25682
|
return null;
|
|
25498
25683
|
}
|
|
@@ -25505,9 +25690,9 @@ async function getTmuxPath() {
|
|
|
25505
25690
|
return initPromise3;
|
|
25506
25691
|
}
|
|
25507
25692
|
initPromise3 = (async () => {
|
|
25508
|
-
const
|
|
25509
|
-
tmuxPath =
|
|
25510
|
-
return
|
|
25693
|
+
const path7 = await findTmuxPath();
|
|
25694
|
+
tmuxPath = path7;
|
|
25695
|
+
return path7;
|
|
25511
25696
|
})();
|
|
25512
25697
|
return initPromise3;
|
|
25513
25698
|
}
|
|
@@ -25827,7 +26012,7 @@ function createBackgroundCancel(manager, client2) {
|
|
|
25827
26012
|
return `\u274C Invalid arguments: Either provide a taskId or set all=true to cancel all running tasks.`;
|
|
25828
26013
|
}
|
|
25829
26014
|
if (cancelAll) {
|
|
25830
|
-
const tasks = manager.
|
|
26015
|
+
const tasks = manager.getAllDescendantTasks(toolContext.sessionID);
|
|
25831
26016
|
const runningTasks = tasks.filter((t) => t.status === "running");
|
|
25832
26017
|
if (runningTasks.length === 0) {
|
|
25833
26018
|
return `\u2705 No running background tasks to cancel.`;
|
|
@@ -26122,15 +26307,15 @@ var builtinTools = {
|
|
|
26122
26307
|
};
|
|
26123
26308
|
// src/features/background-agent/manager.ts
|
|
26124
26309
|
import { existsSync as existsSync36, readdirSync as readdirSync12 } from "fs";
|
|
26125
|
-
import { join as
|
|
26310
|
+
import { join as join44 } from "path";
|
|
26126
26311
|
function getMessageDir4(sessionID) {
|
|
26127
26312
|
if (!existsSync36(MESSAGE_STORAGE))
|
|
26128
26313
|
return null;
|
|
26129
|
-
const directPath =
|
|
26314
|
+
const directPath = join44(MESSAGE_STORAGE, sessionID);
|
|
26130
26315
|
if (existsSync36(directPath))
|
|
26131
26316
|
return directPath;
|
|
26132
26317
|
for (const dir of readdirSync12(MESSAGE_STORAGE)) {
|
|
26133
|
-
const sessionPath =
|
|
26318
|
+
const sessionPath = join44(MESSAGE_STORAGE, dir, sessionID);
|
|
26134
26319
|
if (existsSync36(sessionPath))
|
|
26135
26320
|
return sessionPath;
|
|
26136
26321
|
}
|
|
@@ -26222,6 +26407,16 @@ class BackgroundManager {
|
|
|
26222
26407
|
}
|
|
26223
26408
|
return result;
|
|
26224
26409
|
}
|
|
26410
|
+
getAllDescendantTasks(sessionID) {
|
|
26411
|
+
const result = [];
|
|
26412
|
+
const directChildren = this.getTasksByParentSession(sessionID);
|
|
26413
|
+
for (const child of directChildren) {
|
|
26414
|
+
result.push(child);
|
|
26415
|
+
const descendants = this.getAllDescendantTasks(child.sessionID);
|
|
26416
|
+
result.push(...descendants);
|
|
26417
|
+
}
|
|
26418
|
+
return result;
|
|
26419
|
+
}
|
|
26225
26420
|
findBySession(sessionID) {
|
|
26226
26421
|
for (const task of this.tasks.values()) {
|
|
26227
26422
|
if (task.sessionID === sessionID) {
|
|
@@ -26513,7 +26708,7 @@ var AgentPermissionSchema = exports_external.object({
|
|
|
26513
26708
|
external_directory: PermissionValue.optional()
|
|
26514
26709
|
});
|
|
26515
26710
|
var BuiltinAgentNameSchema = exports_external.enum([
|
|
26516
|
-
"
|
|
26711
|
+
"Sisyphus",
|
|
26517
26712
|
"oracle",
|
|
26518
26713
|
"librarian",
|
|
26519
26714
|
"explore",
|
|
@@ -26524,8 +26719,8 @@ var BuiltinAgentNameSchema = exports_external.enum([
|
|
|
26524
26719
|
var OverridableAgentNameSchema = exports_external.enum([
|
|
26525
26720
|
"build",
|
|
26526
26721
|
"plan",
|
|
26527
|
-
"
|
|
26528
|
-
"
|
|
26722
|
+
"Sisyphus",
|
|
26723
|
+
"Planner-Sisyphus",
|
|
26529
26724
|
"oracle",
|
|
26530
26725
|
"librarian",
|
|
26531
26726
|
"explore",
|
|
@@ -26571,8 +26766,8 @@ var AgentOverrideConfigSchema = exports_external.object({
|
|
|
26571
26766
|
var AgentOverridesSchema = exports_external.object({
|
|
26572
26767
|
build: AgentOverrideConfigSchema.optional(),
|
|
26573
26768
|
plan: AgentOverrideConfigSchema.optional(),
|
|
26574
|
-
|
|
26575
|
-
"
|
|
26769
|
+
Sisyphus: AgentOverrideConfigSchema.optional(),
|
|
26770
|
+
"Planner-Sisyphus": AgentOverrideConfigSchema.optional(),
|
|
26576
26771
|
oracle: AgentOverrideConfigSchema.optional(),
|
|
26577
26772
|
librarian: AgentOverrideConfigSchema.optional(),
|
|
26578
26773
|
explore: AgentOverrideConfigSchema.optional(),
|
|
@@ -26587,9 +26782,14 @@ var ClaudeCodeConfigSchema = exports_external.object({
|
|
|
26587
26782
|
agents: exports_external.boolean().optional(),
|
|
26588
26783
|
hooks: exports_external.boolean().optional()
|
|
26589
26784
|
});
|
|
26590
|
-
var
|
|
26785
|
+
var SisyphusAgentConfigSchema = exports_external.object({
|
|
26591
26786
|
disabled: exports_external.boolean().optional()
|
|
26592
26787
|
});
|
|
26788
|
+
var ExperimentalConfigSchema = exports_external.object({
|
|
26789
|
+
aggressive_truncation: exports_external.boolean().optional(),
|
|
26790
|
+
empty_message_recovery: exports_external.boolean().optional(),
|
|
26791
|
+
auto_resume: exports_external.boolean().optional()
|
|
26792
|
+
});
|
|
26593
26793
|
var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
26594
26794
|
$schema: exports_external.string().optional(),
|
|
26595
26795
|
disabled_mcps: exports_external.array(McpNameSchema).optional(),
|
|
@@ -26598,7 +26798,8 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
|
26598
26798
|
agents: AgentOverridesSchema.optional(),
|
|
26599
26799
|
claude_code: ClaudeCodeConfigSchema.optional(),
|
|
26600
26800
|
google_auth: exports_external.boolean().optional(),
|
|
26601
|
-
|
|
26801
|
+
sisyphus_agent: SisyphusAgentConfigSchema.optional(),
|
|
26802
|
+
experimental: ExperimentalConfigSchema.optional()
|
|
26602
26803
|
});
|
|
26603
26804
|
// src/agents/plan-prompt.ts
|
|
26604
26805
|
var PLAN_SYSTEM_PROMPT = `<system-reminder>
|
|
@@ -26673,16 +26874,14 @@ var PLAN_PERMISSION = {
|
|
|
26673
26874
|
|
|
26674
26875
|
// src/index.ts
|
|
26675
26876
|
import * as fs6 from "fs";
|
|
26676
|
-
import * as
|
|
26677
|
-
import * as os4 from "os";
|
|
26678
|
-
function getUserConfigDir2() {
|
|
26679
|
-
if (process.platform === "win32") {
|
|
26680
|
-
return process.env.APPDATA || path6.join(os4.homedir(), "AppData", "Roaming");
|
|
26681
|
-
}
|
|
26682
|
-
return process.env.XDG_CONFIG_HOME || path6.join(os4.homedir(), ".config");
|
|
26683
|
-
}
|
|
26877
|
+
import * as path7 from "path";
|
|
26684
26878
|
var AGENT_NAME_MAP = {
|
|
26685
|
-
omo: "
|
|
26879
|
+
omo: "Sisyphus",
|
|
26880
|
+
OmO: "Sisyphus",
|
|
26881
|
+
"OmO-Plan": "Planner-Sisyphus",
|
|
26882
|
+
"omo-plan": "Planner-Sisyphus",
|
|
26883
|
+
sisyphus: "Sisyphus",
|
|
26884
|
+
"planner-sisyphus": "Planner-Sisyphus",
|
|
26686
26885
|
build: "build",
|
|
26687
26886
|
oracle: "oracle",
|
|
26688
26887
|
librarian: "librarian",
|
|
@@ -26691,32 +26890,63 @@ var AGENT_NAME_MAP = {
|
|
|
26691
26890
|
"document-writer": "document-writer",
|
|
26692
26891
|
"multimodal-looker": "multimodal-looker"
|
|
26693
26892
|
};
|
|
26694
|
-
function
|
|
26695
|
-
const
|
|
26893
|
+
function migrateAgentNames(agents) {
|
|
26894
|
+
const migrated = {};
|
|
26895
|
+
let changed = false;
|
|
26696
26896
|
for (const [key, value] of Object.entries(agents)) {
|
|
26697
|
-
const
|
|
26698
|
-
|
|
26897
|
+
const newKey = AGENT_NAME_MAP[key.toLowerCase()] ?? AGENT_NAME_MAP[key] ?? key;
|
|
26898
|
+
if (newKey !== key) {
|
|
26899
|
+
changed = true;
|
|
26900
|
+
}
|
|
26901
|
+
migrated[newKey] = value;
|
|
26902
|
+
}
|
|
26903
|
+
return { migrated, changed };
|
|
26904
|
+
}
|
|
26905
|
+
function migrateConfigFile(configPath, rawConfig) {
|
|
26906
|
+
let needsWrite = false;
|
|
26907
|
+
if (rawConfig.agents && typeof rawConfig.agents === "object") {
|
|
26908
|
+
const { migrated, changed } = migrateAgentNames(rawConfig.agents);
|
|
26909
|
+
if (changed) {
|
|
26910
|
+
rawConfig.agents = migrated;
|
|
26911
|
+
needsWrite = true;
|
|
26912
|
+
}
|
|
26913
|
+
}
|
|
26914
|
+
if (rawConfig.omo_agent) {
|
|
26915
|
+
rawConfig.sisyphus_agent = rawConfig.omo_agent;
|
|
26916
|
+
delete rawConfig.omo_agent;
|
|
26917
|
+
needsWrite = true;
|
|
26699
26918
|
}
|
|
26700
|
-
|
|
26919
|
+
if (needsWrite) {
|
|
26920
|
+
try {
|
|
26921
|
+
fs6.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + `
|
|
26922
|
+
`, "utf-8");
|
|
26923
|
+
log(`Migrated config file: ${configPath} (OmO \u2192 Sisyphus)`);
|
|
26924
|
+
} catch (err) {
|
|
26925
|
+
log(`Failed to write migrated config to ${configPath}:`, err);
|
|
26926
|
+
}
|
|
26927
|
+
}
|
|
26928
|
+
return needsWrite;
|
|
26701
26929
|
}
|
|
26702
26930
|
function loadConfigFromPath2(configPath) {
|
|
26703
26931
|
try {
|
|
26704
26932
|
if (fs6.existsSync(configPath)) {
|
|
26705
26933
|
const content = fs6.readFileSync(configPath, "utf-8");
|
|
26706
26934
|
const rawConfig = JSON.parse(content);
|
|
26707
|
-
|
|
26708
|
-
rawConfig.agents = normalizeAgentNames(rawConfig.agents);
|
|
26709
|
-
}
|
|
26935
|
+
migrateConfigFile(configPath, rawConfig);
|
|
26710
26936
|
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
|
26711
26937
|
if (!result.success) {
|
|
26938
|
+
const errorMsg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
26712
26939
|
log(`Config validation error in ${configPath}:`, result.error.issues);
|
|
26940
|
+
addConfigLoadError({ path: configPath, error: `Validation error: ${errorMsg}` });
|
|
26713
26941
|
return null;
|
|
26714
26942
|
}
|
|
26715
26943
|
log(`Config loaded from ${configPath}`, { agents: result.data.agents });
|
|
26716
26944
|
return result.data;
|
|
26717
26945
|
}
|
|
26718
26946
|
} catch (err) {
|
|
26947
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
26719
26948
|
log(`Error loading config from ${configPath}:`, err);
|
|
26949
|
+
addConfigLoadError({ path: configPath, error: errorMsg });
|
|
26720
26950
|
}
|
|
26721
26951
|
return null;
|
|
26722
26952
|
}
|
|
@@ -26747,8 +26977,8 @@ function mergeConfigs(base, override) {
|
|
|
26747
26977
|
};
|
|
26748
26978
|
}
|
|
26749
26979
|
function loadPluginConfig(directory) {
|
|
26750
|
-
const userConfigPath =
|
|
26751
|
-
const projectConfigPath =
|
|
26980
|
+
const userConfigPath = path7.join(getUserConfigDir(), "opencode", "oh-my-opencode.json");
|
|
26981
|
+
const projectConfigPath = path7.join(directory, ".opencode", "oh-my-opencode.json");
|
|
26752
26982
|
let config3 = loadConfigFromPath2(userConfigPath) ?? {};
|
|
26753
26983
|
const projectConfig = loadConfigFromPath2(projectConfigPath);
|
|
26754
26984
|
if (projectConfig) {
|
|
@@ -26769,7 +26999,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
26769
26999
|
const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
|
|
26770
27000
|
const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx) : null;
|
|
26771
27001
|
const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
|
|
26772
|
-
const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx) : null;
|
|
27002
|
+
const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
|
|
26773
27003
|
const sessionNotification = isHookEnabled("session-notification") ? createSessionNotification(ctx) : null;
|
|
26774
27004
|
if (sessionRecovery && todoContinuationEnforcer) {
|
|
26775
27005
|
sessionRecovery.setOnAbortCallback(todoContinuationEnforcer.markRecovering);
|
|
@@ -26784,10 +27014,11 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
26784
27014
|
const claudeCodeHooks = createClaudeCodeHooksHook(ctx, {
|
|
26785
27015
|
disabledHooks: pluginConfig.claude_code?.hooks ?? true ? undefined : true
|
|
26786
27016
|
});
|
|
26787
|
-
const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx) : null;
|
|
27017
|
+
const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx, { experimental: pluginConfig.experimental }) : null;
|
|
26788
27018
|
const rulesInjector = isHookEnabled("rules-injector") ? createRulesInjectorHook(ctx) : null;
|
|
26789
27019
|
const autoUpdateChecker = isHookEnabled("auto-update-checker") ? createAutoUpdateCheckerHook(ctx, {
|
|
26790
|
-
showStartupToast: isHookEnabled("startup-toast")
|
|
27020
|
+
showStartupToast: isHookEnabled("startup-toast"),
|
|
27021
|
+
isSisyphusEnabled: pluginConfig.sisyphus_agent?.disabled !== true
|
|
26791
27022
|
}) : null;
|
|
26792
27023
|
const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook() : null;
|
|
26793
27024
|
const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
|
|
@@ -26821,22 +27052,22 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
26821
27052
|
const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents, ctx.directory, config3.model);
|
|
26822
27053
|
const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
|
|
26823
27054
|
const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
|
|
26824
|
-
const
|
|
26825
|
-
if (
|
|
27055
|
+
const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
|
|
27056
|
+
if (isSisyphusEnabled && builtinAgents.Sisyphus) {
|
|
26826
27057
|
const { name: _planName, ...planConfigWithoutName } = config3.agent?.plan ?? {};
|
|
26827
|
-
const
|
|
26828
|
-
const
|
|
27058
|
+
const plannerSisyphusOverride = pluginConfig.agents?.["Planner-Sisyphus"];
|
|
27059
|
+
const plannerSisyphusBase = {
|
|
26829
27060
|
...planConfigWithoutName,
|
|
26830
27061
|
prompt: PLAN_SYSTEM_PROMPT,
|
|
26831
27062
|
permission: PLAN_PERMISSION,
|
|
26832
27063
|
description: `${config3.agent?.plan?.description ?? "Plan agent"} (OhMyOpenCode version)`,
|
|
26833
27064
|
color: config3.agent?.plan?.color ?? "#6495ED"
|
|
26834
27065
|
};
|
|
26835
|
-
const
|
|
27066
|
+
const plannerSisyphusConfig = plannerSisyphusOverride ? { ...plannerSisyphusBase, ...plannerSisyphusOverride } : plannerSisyphusBase;
|
|
26836
27067
|
config3.agent = {
|
|
26837
|
-
|
|
26838
|
-
"
|
|
26839
|
-
...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "
|
|
27068
|
+
Sisyphus: builtinAgents.Sisyphus,
|
|
27069
|
+
"Planner-Sisyphus": plannerSisyphusConfig,
|
|
27070
|
+
...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "Sisyphus")),
|
|
26840
27071
|
...userAgents,
|
|
26841
27072
|
...projectAgents,
|
|
26842
27073
|
...config3.agent,
|
|
@@ -26914,7 +27145,6 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
26914
27145
|
await rulesInjector?.event(input);
|
|
26915
27146
|
await thinkMode?.event(input);
|
|
26916
27147
|
await anthropicAutoCompact?.event(input);
|
|
26917
|
-
await keywordDetector?.event(input);
|
|
26918
27148
|
await agentUsageReminder?.event(input);
|
|
26919
27149
|
await interactiveBashSession?.event(input);
|
|
26920
27150
|
const { event } = input;
|