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/dist/index.js CHANGED
@@ -222,8 +222,8 @@ var require_utils = __commonJS((exports) => {
222
222
  }
223
223
  return output;
224
224
  };
225
- exports.basename = (path3, { windows } = {}) => {
226
- const segs = path3.split(windows ? /[\\/]/ : "/");
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/omo.ts
1478
- var OMO_SYSTEM_PROMPT = `<Role>
1479
- You are OmO, the orchestrator agent for OpenCode.
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
- **Identity**: Elite software engineer working at SF, Bay Area. You work, delegate, verify, deliver.
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, no agents |
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" | Assess scope, then search |
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
- - Can direct tools answer this? (grep/glob/LSP) \u2192 Use them first
1527
+ - Do I have any implicit assumptions that might affect the outcome?
1518
1528
  - Is the search scope clear?
1519
- - Does this involve external libraries/frameworks? \u2192 Fire librarian in background
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 | Always try first |
1569
- | \`explore\` agent | CHEAP | Multiple search angles, unfamiliar modules, cross-layer patterns |
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**: Direct tools \u2192 explore/librarian (background) \u2192 oracle (blocking, justified)
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 = fire-and-forget tools**. Treat them like grep, not consultants.
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. Before final answer: \`background_cancel(all=true)\`
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 immediately
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
- | Frontend UI/UX | \`frontend-ui-ux-engineer\` | .tsx/.jsx/.vue/.svelte/.css, visual changes |
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 3+ agents when grep suffices |
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 omoAgent = {
1902
- description: "Powerful AI orchestrator for OpenCode. 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.",
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: OMO_SYSTEM_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: Claude is 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.
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-pro-preview",
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
- OmO: omoAgent,
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 === "OmO" || agentName === "librarian") && directory && config.prompt) {
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 === "OmO" && systemDefaultModel && !override?.model) {
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 join5 } from "path";
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 join4 } from "path";
3246
+ import { join as join5 } from "path";
3206
3247
 
3207
3248
  // src/features/hook-message-injector/constants.ts
3208
- import { join as join3 } from "path";
3209
- import { homedir as homedir2 } from "os";
3210
- var xdgData = process.env.XDG_DATA_HOME || join3(homedir2(), ".local", "share");
3211
- var OPENCODE_STORAGE = join3(xdgData, "opencode", "storage");
3212
- var MESSAGE_STORAGE = join3(OPENCODE_STORAGE, "message");
3213
- var PART_STORAGE = join3(OPENCODE_STORAGE, "part");
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(join4(messageDir, file), "utf-8");
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 = join4(MESSAGE_STORAGE, sessionID);
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 = join4(MESSAGE_STORAGE, dir, sessionID);
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(join4(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3309
- const partDir = join4(PART_STORAGE, messageID);
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(join4(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
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 = join5(MESSAGE_STORAGE, sessionID);
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 = join5(MESSAGE_STORAGE, dir, sessionID);
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
- }, 200);
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 join7 } from "path";
3863
+ import { join as join8 } from "path";
3817
3864
 
3818
3865
  // src/hooks/session-recovery/constants.ts
3819
- import { join as join6 } from "path";
3866
+ import { join as join7 } from "path";
3820
3867
 
3821
3868
  // node_modules/xdg-basedir/index.js
3822
- import os2 from "os";
3823
- import path2 from "path";
3824
- var homeDirectory = os2.homedir();
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 ? path2.join(homeDirectory, ".local", "share") : undefined);
3827
- var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path2.join(homeDirectory, ".config") : undefined);
3828
- var xdgState = env.XDG_STATE_HOME || (homeDirectory ? path2.join(homeDirectory, ".local", "state") : undefined);
3829
- var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ? path2.join(homeDirectory, ".cache") : undefined);
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 = join6(xdgData2 ?? "", "opencode", "storage");
3842
- var MESSAGE_STORAGE2 = join6(OPENCODE_STORAGE2, "message");
3843
- var PART_STORAGE2 = join6(OPENCODE_STORAGE2, "part");
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 = join7(MESSAGE_STORAGE2, sessionID);
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 = join7(MESSAGE_STORAGE2, dir, sessionID);
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(join7(messageDir, file), "utf-8");
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 = join7(PART_STORAGE2, messageID);
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(join7(partDir, file), "utf-8");
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 = join7(PART_STORAGE2, messageID);
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(join7(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
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 = join7(PART_STORAGE2, messageID);
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(join7(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
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 = join7(PART_STORAGE2, messageID);
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 = join7(partDir, file);
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
- var PLACEHOLDER_TEXT = "[user interrupted]";
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
- } else if (errorType === "empty_content_message") {
4359
- success = await recoverEmptyContentMessage(ctx.client, sessionID, failedMsg, ctx.directory, info.error);
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 join9 } from "path";
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 join8 } from "path";
4391
- import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
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 = join8(tmpdir2(), "comment-checker-debug.log");
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 || join8(homedir3(), ".cache");
4413
- return join8(base, "oh-my-opencode", "bin");
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 = join8(getCacheDir(), getBinaryName());
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 = join8(cacheDir, binaryName);
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: os3, arch, ext } = platformInfo;
4474
- const assetName = `comment-checker_v${version}_${os3}_${arch}.${ext}`;
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 = join8(cacheDir, assetName);
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 = join9(tmpdir3(), "comment-checker-debug.log");
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 = join9(cliDir, "bin", binaryName);
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((path3) => {
4587
- debugLog2("background init complete:", path3 || "no binary");
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 join10 } from "path";
4597
+ import { join as join11 } from "path";
4637
4598
  var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
4638
- var DEBUG_FILE3 = join10(tmpdir4(), "comment-checker-debug.log");
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((path3) => {
4663
- debugLog3("CLI path resolved:", path3 || "disabled (no binary)");
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 join13, resolve as resolve2 } from "path";
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 join12 } from "path";
4755
+ import { join as join13 } from "path";
4795
4756
 
4796
4757
  // src/hooks/directory-agents-injector/constants.ts
4797
- import { join as join11 } from "path";
4798
- var OPENCODE_STORAGE3 = join11(xdgData2 ?? "", "opencode", "storage");
4799
- var AGENTS_INJECTOR_STORAGE = join11(OPENCODE_STORAGE3, "directory-agents");
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 join12(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
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 = join13(current, AGENTS_FILENAME);
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: path3, content } of toInject) {
4854
+ for (const { path: path4, content } of toInject) {
4894
4855
  output.output += `
4895
4856
 
4896
- [Directory Context: ${path3}]
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 join16, resolve as resolve3 } from "path";
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 join15 } from "path";
4896
+ import { join as join16 } from "path";
4936
4897
 
4937
4898
  // src/hooks/directory-readme-injector/constants.ts
4938
- import { join as join14 } from "path";
4939
- var OPENCODE_STORAGE4 = join14(xdgData2 ?? "", "opencode", "storage");
4940
- var README_INJECTOR_STORAGE = join14(OPENCODE_STORAGE4, "directory-readme");
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 join15(README_INJECTOR_STORAGE, `${sessionID}.json`);
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 = join16(current, README_FILENAME);
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: path3, content } of toInject) {
4995
+ for (const { path: path4, content } of toInject) {
5035
4996
  output.output += `
5036
4997
 
5037
- [Project README: ${path3}]
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: 10,
5237
- minOutputSizeToTruncate: 1000
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 { join as join17 } from "path";
5243
- var OPENCODE_STORAGE5 = join17(xdgData2 ?? "", "opencode", "storage");
5244
- var MESSAGE_STORAGE3 = join17(OPENCODE_STORAGE5, "message");
5245
- var PART_STORAGE3 = join17(OPENCODE_STORAGE5, "part");
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 = join17(MESSAGE_STORAGE3, sessionID);
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 = join17(MESSAGE_STORAGE3, dir, sessionID);
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 = join17(PART_STORAGE3, messageID);
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 = join17(partDir, file);
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
- async function executeCompact(sessionID, msg, autoCompactState, client, directory) {
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 (retryState.attempt < RETRY_CONFIG.maxAttempts) {
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
- clearSessionState(autoCompactState, sessionID);
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
- if (providerID && modelID) {
5599
- await ctx.client.tui.showToast({
5600
- body: {
5601
- title: "Context Limit Hit",
5602
- message: "Truncating large tool outputs and recovering...",
5603
- variant: "warning",
5604
- duration: 3000
5605
- }
5606
- }).catch(() => {});
5607
- setTimeout(() => {
5608
- executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory);
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 (!lastAssistant) {
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. Summarizing session...",
5834
+ message: "Token limit exceeded. Attempting recovery...",
5664
5835
  variant: "warning",
5665
5836
  duration: 3000
5666
5837
  }
5667
5838
  }).catch(() => {});
5668
- await executeCompact(sessionID, lastAssistant, autoCompactState, ctx.client, ctx.directory);
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 homedir4 } from "os";
5943
- import { join as join18 } from "path";
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 = homedir4();
6138
+ const home = homedir6();
5968
6139
  const paths = [
5969
- join18(home, ".claude", "settings.json"),
5970
- join18(process.cwd(), ".claude", "settings.json"),
5971
- join18(process.cwd(), ".claude", "settings.local.json")
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 homedir5 } from "os";
6016
- import { join as join19 } from "path";
6017
- var USER_CONFIG_PATH = join19(homedir5(), ".config", "opencode", "opencode-cc-plugin.json");
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 join19(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6190
+ return join20(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6020
6191
  }
6021
- async function loadConfigFromPath(path3) {
6022
- if (!existsSync15(path3)) {
6192
+ async function loadConfigFromPath(path4) {
6193
+ if (!existsSync15(path4)) {
6023
6194
  return null;
6024
6195
  }
6025
6196
  try {
6026
- const content = await Bun.file(path3).text();
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: path3, error });
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 join20 } from "path";
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 homedir6, tmpdir as tmpdir5 } from "os";
6374
+ import { homedir as homedir8, tmpdir as tmpdir5 } from "os";
6204
6375
  import { randomUUID } from "crypto";
6205
- var TRANSCRIPT_DIR = join20(homedir6(), ".claude", "transcripts");
6376
+ var TRANSCRIPT_DIR = join21(homedir8(), ".claude", "transcripts");
6206
6377
  function getTranscriptPath(sessionId) {
6207
- return join20(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
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 path3 = getTranscriptPath(sessionId);
6387
+ const path4 = getTranscriptPath(sessionId);
6217
6388
  const line = JSON.stringify(entry) + `
6218
6389
  `;
6219
- appendFileSync5(path3, line);
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 = join20(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
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 = join20(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
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(path3) {
6327
- if (!path3)
6497
+ function deleteTempTranscript(path4) {
6498
+ if (!path4)
6328
6499
  return;
6329
6500
  try {
6330
- unlinkSync5(path3);
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 join21 } from "path";
6530
- import { homedir as homedir7 } from "os";
6531
- var TODO_DIR = join21(homedir7(), ".claude", "todos");
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 join21(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
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 homedir8 } from "os";
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 join23, relative } from "path";
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 join22 } from "path";
6880
- var OPENCODE_STORAGE6 = join22(xdgData2 ?? "", "opencode", "storage");
6881
- var RULES_INJECTOR_STORAGE = join22(OPENCODE_STORAGE6, "rules-injector");
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 = join23(current, marker);
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 = join23(dir, entry.name);
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 = join23(currentDir, parent, subdir);
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 = join23(homeDir, USER_RULE_DIR);
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 join24 } from "path";
7342
+ import { join as join25 } from "path";
7172
7343
  function getStoragePath3(sessionID) {
7173
- return join24(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
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 = homedir8();
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 path4 from "path";
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 path3 from "path";
7310
- import * as os3 from "os";
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 path3.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
7487
+ return path4.join(process.env.LOCALAPPDATA ?? os4.homedir(), "opencode");
7317
7488
  }
7318
- return path3.join(os3.homedir(), ".cache", "opencode");
7489
+ return path4.join(os4.homedir(), ".cache", "opencode");
7319
7490
  }
7320
7491
  var CACHE_DIR = getCacheDir2();
7321
- var VERSION_FILE = path3.join(CACHE_DIR, "version");
7322
- var INSTALLED_PACKAGE_JSON = path3.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
7323
- function getUserConfigDir() {
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 ?? path3.join(os3.homedir(), "AppData", "Roaming");
7496
+ return process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
7326
7497
  }
7327
- return process.env.XDG_CONFIG_HOME ?? path3.join(os3.homedir(), ".config");
7498
+ return process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
7328
7499
  }
7329
- var USER_CONFIG_DIR = getUserConfigDir();
7330
- var USER_OPENCODE_CONFIG = path3.join(USER_CONFIG_DIR, "opencode", "opencode.json");
7331
- var USER_OPENCODE_CONFIG_JSONC = path3.join(USER_CONFIG_DIR, "opencode", "opencode.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
- path4.join(directory, ".opencode", "opencode.json"),
7343
- path4.join(directory, ".opencode", "opencode.jsonc"),
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 : path4.dirname(startPath);
7545
+ let dir = stat.isDirectory() ? startPath : path5.dirname(startPath);
7375
7546
  for (let i = 0;i < 10; i++) {
7376
- const pkgPath = path4.join(dir, "package.json");
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 = path4.dirname(dir);
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 = path4.dirname(fileURLToPath(import.meta.url));
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 path5 from "path";
7675
+ import * as path6 from "path";
7505
7676
  function invalidatePackage(packageName = PACKAGE_NAME) {
7506
7677
  try {
7507
- const pkgDir = path5.join(CACHE_DIR, "node_modules", packageName);
7508
- const pkgJsonPath = path5.join(CACHE_DIR, "package.json");
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(ctx, version);
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(ctx, result.currentVersion);
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(ctx, result.currentVersion);
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: `OpenCode is now on Steroids. oMoMoMoMo...
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 showVersionToast(ctx, version) {
7593
- const displayVersion = version ?? "unknown";
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: `OhMyOpenCode ${displayVersion}`,
7597
- message: "OpenCode is now on Steroids. oMoMoMoMo...",
7598
- variant: "info",
7599
- duration: 5000
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] Startup toast shown: v${displayVersion}`);
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 join29 } from "path";
7809
+ import { join as join30 } from "path";
7613
7810
 
7614
7811
  // src/hooks/agent-usage-reminder/constants.ts
7615
- import { join as join28 } from "path";
7616
- var OPENCODE_STORAGE7 = join28(xdgData2 ?? "", "opencode", "storage");
7617
- var AGENT_USAGE_REMINDER_STORAGE = join28(OPENCODE_STORAGE7, "agent-usage-reminder");
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 join29(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
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 join31 } from "path";
8094
+ import { join as join32 } from "path";
7913
8095
 
7914
8096
  // src/hooks/interactive-bash-session/constants.ts
7915
- import { join as join30 } from "path";
7916
- var OPENCODE_STORAGE8 = join30(xdgData2 ?? "", "opencode", "storage");
7917
- var INTERACTIVE_BASH_SESSION_STORAGE = join30(OPENCODE_STORAGE8, "interactive-bash-session");
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 join31(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
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 PLACEHOLDER_TEXT2 = "[user interrupted]";
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 = PLACEHOLDER_TEXT2;
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: PLACEHOLDER_TEXT2,
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 = PLACEHOLDER_TEXT2;
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 homedir10 } from "os";
9907
- import { join as join32, basename } from "path";
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 = join32(commandsDir, entry.name);
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 = join32(homedir10(), ".claude", "commands");
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 = join32(process.cwd(), ".claude", "commands");
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 = join32(homedir10(), ".config", "opencode", "command");
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 = join32(process.cwd(), ".opencode", "command");
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 homedir11 } from "os";
9982
- import { join as join33 } from "path";
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 = join33(skillsDir, entry.name);
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 = join33(resolvedPath, "SKILL.md");
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 = join33(homedir11(), ".claude", "skills");
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 = join33(process.cwd(), ".claude", "skills");
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 homedir12 } from "os";
10049
- import { join as join34, basename as basename2 } from "path";
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 = join34(agentsDir, entry.name);
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 = join34(homedir12(), ".claude", "agents");
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 = join34(process.cwd(), ".claude", "agents");
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 homedir13 } from "os";
10121
- import { join as join35 } from "path";
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 = homedir13();
10372
+ const home = homedir15();
10188
10373
  const cwd = process.cwd();
10189
10374
  return [
10190
- { path: join35(home, ".claude", ".mcp.json"), scope: "user" },
10191
- { path: join35(cwd, ".mcp.json"), scope: "project" },
10192
- { path: join35(cwd, ".claude", ".mcp.json"), scope: "local" }
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: path6, scope } of paths) {
10212
- const config = await loadMcpConfigFile(path6);
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: path6 });
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: path6 });
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 join36 } from "path";
10426
- import { homedir as homedir14 } from "os";
10427
- function loadJsonFile(path6) {
10428
- if (!existsSync27(path6))
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(path6, "utf-8"));
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: join36(cwd, ".opencode", "oh-my-opencode.json"),
10440
- user: join36(homedir14(), ".config", "opencode", "oh-my-opencode.json"),
10441
- opencode: join36(homedir14(), ".config", "opencode", "opencode.json")
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(join36(p, cmd))) {
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, path6) {
12144
- if (!path6)
12328
+ function getElementAtPath(obj, path7) {
12329
+ if (!path7)
12145
12330
  return obj;
12146
- return path6.reduce((acc, key) => acc?.[key], obj);
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(path6, issues) {
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(path6);
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, path6 = []) => {
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 = [...path6, ...issue2.path];
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 path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12720
- for (const seg of path6) {
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 join38 } from "path";
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 join37 } from "path";
24070
- import { homedir as homedir15 } from "os";
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 || join37(homedir15(), "AppData", "Local");
24096
- return join37(base2, "oh-my-opencode", "bin");
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 || join37(homedir15(), ".cache");
24100
- return join37(base, "oh-my-opencode", "bin");
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 = join37(getCacheDir3(), getBinaryName3());
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 = join37(cacheDir, binaryName);
24318
+ const binaryPath = join38(cacheDir, binaryName);
24134
24319
  if (existsSync29(binaryPath)) {
24135
24320
  return binaryPath;
24136
24321
  }
24137
- const { arch, os: os4 } = platformInfo;
24138
- const assetName = `app-${arch}-${os4}.zip`;
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 = join37(cacheDir, assetName);
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 = join38(cliDir, binaryName);
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 = join38(pkgDir, astGrepName);
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 path6 of homebrewPaths) {
24228
- if (existsSync30(path6) && isValidBinary(path6)) {
24229
- return path6;
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(path6) {
24248
- resolvedCliPath2 = path6;
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 join40, dirname as dirname7 } from "path";
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 join39 } from "path";
24785
+ import { join as join40 } from "path";
24601
24786
  function getInstallDir() {
24602
24787
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
24603
- return join39(homeDir, ".cache", "oh-my-opencode", "bin");
24788
+ return join40(homeDir, ".cache", "oh-my-opencode", "bin");
24604
24789
  }
24605
24790
  function getRgPath() {
24606
24791
  const isWindows2 = process.platform === "win32";
24607
- return join39(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
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
- join40(execDir, rgName),
24635
- join40(execDir, "bin", rgName),
24636
- join40(execDir, "..", "bin", rgName),
24637
- join40(execDir, "..", "libexec", rgName)
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 homedir16 } from "os";
25045
- import { join as join41, basename as basename3, dirname as dirname8 } from "path";
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 = join41(commandsDir, entry.name);
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 = join41(homedir16(), ".claude", "commands");
25084
- const projectCommandsDir = join41(process.cwd(), ".claude", "commands");
25085
- const opencodeGlobalDir = join41(homedir16(), ".config", "opencode", "command");
25086
- const opencodeProjectDir = join41(process.cwd(), ".opencode", "command");
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 homedir17 } from "os";
25220
- import { join as join42, basename as basename4 } from "path";
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 = join42(skillsDir, entry.name);
25424
+ const skillPath = join43(skillsDir, entry.name);
25240
25425
  if (entry.isDirectory() || entry.isSymbolicLink()) {
25241
25426
  const resolvedPath = resolveSymlink(skillPath);
25242
- const skillMdPath = join42(resolvedPath, "SKILL.md");
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 = join42(homedir17(), ".claude", "skills");
25262
- const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
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 = join42(resolvedPath, "SKILL.md");
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 = join42(resolvedPath, "references");
25289
- const scriptsDir = join42(resolvedPath, "scripts");
25290
- const assetsDir = join42(resolvedPath, "assets");
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 = join42(skillsDir, entry.name);
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 = join42(homedir17(), ".claude", "skills");
25329
- const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
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 = join42(skill.path, "references", ref);
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 path6 = stdout.trim().split(`
25667
+ const path7 = stdout.trim().split(`
25483
25668
  `)[0];
25484
- if (!path6) {
25669
+ if (!path7) {
25485
25670
  return null;
25486
25671
  }
25487
- const verifyProc = spawn9([path6, "-V"], {
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 path6;
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 path6 = await findTmuxPath();
25509
- tmuxPath = path6;
25510
- return path6;
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.getTasksByParentSession(toolContext.sessionID);
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 join43 } from "path";
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 = join43(MESSAGE_STORAGE, sessionID);
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 = join43(MESSAGE_STORAGE, dir, sessionID);
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
- "OmO",
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
- "OmO",
26528
- "OmO-Plan",
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
- OmO: AgentOverrideConfigSchema.optional(),
26575
- "OmO-Plan": AgentOverrideConfigSchema.optional(),
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 OmoAgentConfigSchema = exports_external.object({
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
- omo_agent: OmoAgentConfigSchema.optional()
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 path6 from "path";
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: "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 normalizeAgentNames(agents) {
26695
- const normalized = {};
26893
+ function migrateAgentNames(agents) {
26894
+ const migrated = {};
26895
+ let changed = false;
26696
26896
  for (const [key, value] of Object.entries(agents)) {
26697
- const normalizedKey = AGENT_NAME_MAP[key.toLowerCase()] ?? key;
26698
- normalized[normalizedKey] = value;
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
- return normalized;
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
- if (rawConfig.agents && typeof rawConfig.agents === "object") {
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 = path6.join(getUserConfigDir2(), "opencode", "oh-my-opencode.json");
26751
- const projectConfigPath = path6.join(directory, ".opencode", "oh-my-opencode.json");
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 isOmoEnabled = pluginConfig.omo_agent?.disabled !== true;
26825
- if (isOmoEnabled && builtinAgents.OmO) {
27055
+ const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
27056
+ if (isSisyphusEnabled && builtinAgents.Sisyphus) {
26826
27057
  const { name: _planName, ...planConfigWithoutName } = config3.agent?.plan ?? {};
26827
- const omoPlanOverride = pluginConfig.agents?.["OmO-Plan"];
26828
- const omoPlanBase = {
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 omoPlanConfig = omoPlanOverride ? { ...omoPlanBase, ...omoPlanOverride } : omoPlanBase;
27066
+ const plannerSisyphusConfig = plannerSisyphusOverride ? { ...plannerSisyphusBase, ...plannerSisyphusOverride } : plannerSisyphusBase;
26836
27067
  config3.agent = {
26837
- OmO: builtinAgents.OmO,
26838
- "OmO-Plan": omoPlanConfig,
26839
- ...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "OmO")),
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;