oh-my-opencode 2.2.1 → 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,20 +1474,25 @@ 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 - Powerful AI orchestrator from OhMyOpenCode. Pronounced as Oh-Mo.
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
- You will now simulate to work as your identity.
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.
1483
1485
 
1484
1486
  **Core Competencies**:
1485
1487
  - Parsing implicit requirements from explicit requests
1486
1488
  - Adapting to codebase maturity (disciplined vs chaotic)
1487
1489
  - Delegating specialized work to the right subagents
1488
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.
1489
1493
 
1490
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.
1495
+
1491
1496
  </Role>
1492
1497
 
1493
1498
  <Behavior_Instructions>
@@ -1812,7 +1817,8 @@ Briefly announce "Consulting Oracle for [reason]" before invocation.
1812
1817
 
1813
1818
  ### Workflow (NON-NEGOTIABLE)
1814
1819
 
1815
- 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.
1816
1822
  2. **Before starting each step**: Mark \`in_progress\` (only ONE at a time)
1817
1823
  3. **After completing each step**: Mark \`completed\` IMMEDIATELY (NEVER batch)
1818
1824
  4. **If scope changes**: Update todos before proceeding
@@ -1911,9 +1917,10 @@ If the user's approach seems problematic:
1911
1917
  - Prefer small, focused changes over large refactors
1912
1918
  - When uncertain about scope, ask
1913
1919
  </Constraints>
1920
+
1914
1921
  `;
1915
- var omoAgent = {
1916
- description: "OmO - Powerful AI orchestrator from OhMyOpenCode. Pronounced as Oh-Mo. 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.",
1917
1924
  mode: "primary",
1918
1925
  model: "anthropic/claude-opus-4-5",
1919
1926
  thinking: {
@@ -1921,7 +1928,7 @@ var omoAgent = {
1921
1928
  budgetTokens: 32000
1922
1929
  },
1923
1930
  maxTokens: 64000,
1924
- prompt: OMO_SYSTEM_PROMPT,
1931
+ prompt: SISYPHUS_SYSTEM_PROMPT,
1925
1932
  color: "#00CED1"
1926
1933
  };
1927
1934
 
@@ -3139,9 +3146,29 @@ function createDynamicTruncator(ctx) {
3139
3146
  truncateSync: (output, maxTokens, preserveHeaderLines) => truncateToTokenLimit(output, maxTokens, preserveHeaderLines)
3140
3147
  };
3141
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
+ }
3142
3169
  // src/agents/utils.ts
3143
3170
  var allBuiltinAgents = {
3144
- OmO: omoAgent,
3171
+ Sisyphus: sisyphusAgent,
3145
3172
  oracle: oracleAgent,
3146
3173
  librarian: librarianAgent,
3147
3174
  explore: exploreAgent,
@@ -3188,7 +3215,7 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
3188
3215
  continue;
3189
3216
  }
3190
3217
  let finalConfig = config;
3191
- if ((agentName === "OmO" || agentName === "librarian") && directory && config.prompt) {
3218
+ if ((agentName === "Sisyphus" || agentName === "librarian") && directory && config.prompt) {
3192
3219
  const envContext = createEnvContext(directory);
3193
3220
  finalConfig = {
3194
3221
  ...config,
@@ -3196,7 +3223,7 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
3196
3223
  };
3197
3224
  }
3198
3225
  const override = agentOverrides[agentName];
3199
- if (agentName === "OmO" && systemDefaultModel && !override?.model) {
3226
+ if (agentName === "Sisyphus" && systemDefaultModel && !override?.model) {
3200
3227
  finalConfig = {
3201
3228
  ...finalConfig,
3202
3229
  model: systemDefaultModel
@@ -3212,19 +3239,19 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
3212
3239
  }
3213
3240
  // src/hooks/todo-continuation-enforcer.ts
3214
3241
  import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
3215
- import { join as join5 } from "path";
3242
+ import { join as join6 } from "path";
3216
3243
 
3217
3244
  // src/features/hook-message-injector/injector.ts
3218
3245
  import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
3219
- import { join as join4 } from "path";
3246
+ import { join as join5 } from "path";
3220
3247
 
3221
3248
  // src/features/hook-message-injector/constants.ts
3222
- import { join as join3 } from "path";
3223
- import { homedir as homedir2 } from "os";
3224
- var xdgData = process.env.XDG_DATA_HOME || join3(homedir2(), ".local", "share");
3225
- var OPENCODE_STORAGE = join3(xdgData, "opencode", "storage");
3226
- var MESSAGE_STORAGE = join3(OPENCODE_STORAGE, "message");
3227
- 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");
3228
3255
 
3229
3256
  // src/features/hook-message-injector/injector.ts
3230
3257
  function findNearestMessageWithFields(messageDir) {
@@ -3232,7 +3259,7 @@ function findNearestMessageWithFields(messageDir) {
3232
3259
  const files = readdirSync(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
3233
3260
  for (const file of files) {
3234
3261
  try {
3235
- const content = readFileSync2(join4(messageDir, file), "utf-8");
3262
+ const content = readFileSync2(join5(messageDir, file), "utf-8");
3236
3263
  const msg = JSON.parse(content);
3237
3264
  if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
3238
3265
  return msg;
@@ -3260,12 +3287,12 @@ function getOrCreateMessageDir(sessionID) {
3260
3287
  if (!existsSync3(MESSAGE_STORAGE)) {
3261
3288
  mkdirSync(MESSAGE_STORAGE, { recursive: true });
3262
3289
  }
3263
- const directPath = join4(MESSAGE_STORAGE, sessionID);
3290
+ const directPath = join5(MESSAGE_STORAGE, sessionID);
3264
3291
  if (existsSync3(directPath)) {
3265
3292
  return directPath;
3266
3293
  }
3267
3294
  for (const dir of readdirSync(MESSAGE_STORAGE)) {
3268
- const sessionPath = join4(MESSAGE_STORAGE, dir, sessionID);
3295
+ const sessionPath = join5(MESSAGE_STORAGE, dir, sessionID);
3269
3296
  if (existsSync3(sessionPath)) {
3270
3297
  return sessionPath;
3271
3298
  }
@@ -3319,12 +3346,12 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
3319
3346
  sessionID
3320
3347
  };
3321
3348
  try {
3322
- writeFileSync(join4(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3323
- const partDir = join4(PART_STORAGE, messageID);
3349
+ writeFileSync(join5(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3350
+ const partDir = join5(PART_STORAGE, messageID);
3324
3351
  if (!existsSync3(partDir)) {
3325
3352
  mkdirSync(partDir, { recursive: true });
3326
3353
  }
3327
- writeFileSync(join4(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3354
+ writeFileSync(join5(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3328
3355
  return true;
3329
3356
  } catch {
3330
3357
  return false;
@@ -3342,11 +3369,11 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
3342
3369
  function getMessageDir(sessionID) {
3343
3370
  if (!existsSync4(MESSAGE_STORAGE))
3344
3371
  return null;
3345
- const directPath = join5(MESSAGE_STORAGE, sessionID);
3372
+ const directPath = join6(MESSAGE_STORAGE, sessionID);
3346
3373
  if (existsSync4(directPath))
3347
3374
  return directPath;
3348
3375
  for (const dir of readdirSync2(MESSAGE_STORAGE)) {
3349
- const sessionPath = join5(MESSAGE_STORAGE, dir, sessionID);
3376
+ const sessionPath = join6(MESSAGE_STORAGE, dir, sessionID);
3350
3377
  if (existsSync4(sessionPath))
3351
3378
  return sessionPath;
3352
3379
  }
@@ -3462,6 +3489,12 @@ function createTodoContinuationEnforcer(ctx) {
3462
3489
  try {
3463
3490
  const messageDir = getMessageDir(sessionID);
3464
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
+ }
3465
3498
  log(`[${HOOK_NAME}] Injecting continuation prompt`, { sessionID, agent: prevMessage?.agent });
3466
3499
  await ctx.client.session.prompt({
3467
3500
  path: { id: sessionID },
@@ -3483,7 +3516,7 @@ function createTodoContinuationEnforcer(ctx) {
3483
3516
  log(`[${HOOK_NAME}] Prompt injection failed`, { sessionID, error: String(err) });
3484
3517
  remindedSessions.delete(sessionID);
3485
3518
  }
3486
- }, 200);
3519
+ }, 5000);
3487
3520
  pendingTimers.set(sessionID, timer);
3488
3521
  }
3489
3522
  if (event.type === "message.updated") {
@@ -3827,20 +3860,20 @@ function createSessionNotification(ctx, config = {}) {
3827
3860
  }
3828
3861
  // src/hooks/session-recovery/storage.ts
3829
3862
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
3830
- import { join as join7 } from "path";
3863
+ import { join as join8 } from "path";
3831
3864
 
3832
3865
  // src/hooks/session-recovery/constants.ts
3833
- import { join as join6 } from "path";
3866
+ import { join as join7 } from "path";
3834
3867
 
3835
3868
  // node_modules/xdg-basedir/index.js
3836
- import os2 from "os";
3837
- import path2 from "path";
3838
- var homeDirectory = os2.homedir();
3869
+ import os3 from "os";
3870
+ import path3 from "path";
3871
+ var homeDirectory = os3.homedir();
3839
3872
  var { env } = process;
3840
- var xdgData2 = env.XDG_DATA_HOME || (homeDirectory ? path2.join(homeDirectory, ".local", "share") : undefined);
3841
- var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path2.join(homeDirectory, ".config") : undefined);
3842
- var xdgState = env.XDG_STATE_HOME || (homeDirectory ? path2.join(homeDirectory, ".local", "state") : undefined);
3843
- 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);
3844
3877
  var xdgRuntime = env.XDG_RUNTIME_DIR || undefined;
3845
3878
  var xdgDataDirectories = (env.XDG_DATA_DIRS || "/usr/local/share/:/usr/share/").split(":");
3846
3879
  if (xdgData2) {
@@ -3852,9 +3885,9 @@ if (xdgConfig) {
3852
3885
  }
3853
3886
 
3854
3887
  // src/hooks/session-recovery/constants.ts
3855
- var OPENCODE_STORAGE2 = join6(xdgData2 ?? "", "opencode", "storage");
3856
- var MESSAGE_STORAGE2 = join6(OPENCODE_STORAGE2, "message");
3857
- 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");
3858
3891
  var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
3859
3892
  var META_TYPES = new Set(["step-start", "step-finish"]);
3860
3893
  var CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"]);
@@ -3868,12 +3901,12 @@ function generatePartId2() {
3868
3901
  function getMessageDir2(sessionID) {
3869
3902
  if (!existsSync5(MESSAGE_STORAGE2))
3870
3903
  return "";
3871
- const directPath = join7(MESSAGE_STORAGE2, sessionID);
3904
+ const directPath = join8(MESSAGE_STORAGE2, sessionID);
3872
3905
  if (existsSync5(directPath)) {
3873
3906
  return directPath;
3874
3907
  }
3875
3908
  for (const dir of readdirSync3(MESSAGE_STORAGE2)) {
3876
- const sessionPath = join7(MESSAGE_STORAGE2, dir, sessionID);
3909
+ const sessionPath = join8(MESSAGE_STORAGE2, dir, sessionID);
3877
3910
  if (existsSync5(sessionPath)) {
3878
3911
  return sessionPath;
3879
3912
  }
@@ -3889,7 +3922,7 @@ function readMessages(sessionID) {
3889
3922
  if (!file.endsWith(".json"))
3890
3923
  continue;
3891
3924
  try {
3892
- const content = readFileSync3(join7(messageDir, file), "utf-8");
3925
+ const content = readFileSync3(join8(messageDir, file), "utf-8");
3893
3926
  messages.push(JSON.parse(content));
3894
3927
  } catch {
3895
3928
  continue;
@@ -3904,7 +3937,7 @@ function readMessages(sessionID) {
3904
3937
  });
3905
3938
  }
3906
3939
  function readParts(messageID) {
3907
- const partDir = join7(PART_STORAGE2, messageID);
3940
+ const partDir = join8(PART_STORAGE2, messageID);
3908
3941
  if (!existsSync5(partDir))
3909
3942
  return [];
3910
3943
  const parts = [];
@@ -3912,7 +3945,7 @@ function readParts(messageID) {
3912
3945
  if (!file.endsWith(".json"))
3913
3946
  continue;
3914
3947
  try {
3915
- const content = readFileSync3(join7(partDir, file), "utf-8");
3948
+ const content = readFileSync3(join8(partDir, file), "utf-8");
3916
3949
  parts.push(JSON.parse(content));
3917
3950
  } catch {
3918
3951
  continue;
@@ -3942,7 +3975,7 @@ function messageHasContent(messageID) {
3942
3975
  return parts.some(hasContent);
3943
3976
  }
3944
3977
  function injectTextPart(sessionID, messageID, text) {
3945
- const partDir = join7(PART_STORAGE2, messageID);
3978
+ const partDir = join8(PART_STORAGE2, messageID);
3946
3979
  if (!existsSync5(partDir)) {
3947
3980
  mkdirSync2(partDir, { recursive: true });
3948
3981
  }
@@ -3956,7 +3989,7 @@ function injectTextPart(sessionID, messageID, text) {
3956
3989
  synthetic: true
3957
3990
  };
3958
3991
  try {
3959
- writeFileSync2(join7(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
3992
+ writeFileSync2(join8(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
3960
3993
  return true;
3961
3994
  } catch {
3962
3995
  return false;
@@ -3972,19 +4005,6 @@ function findEmptyMessages(sessionID) {
3972
4005
  }
3973
4006
  return emptyIds;
3974
4007
  }
3975
- function findEmptyMessageByIndex(sessionID, targetIndex) {
3976
- const messages = readMessages(sessionID);
3977
- const indicesToTry = [targetIndex, targetIndex - 1, targetIndex - 2];
3978
- for (const idx of indicesToTry) {
3979
- if (idx < 0 || idx >= messages.length)
3980
- continue;
3981
- const targetMsg = messages[idx];
3982
- if (!messageHasContent(targetMsg.id)) {
3983
- return targetMsg.id;
3984
- }
3985
- }
3986
- return null;
3987
- }
3988
4008
  function findMessagesWithThinkingBlocks(sessionID) {
3989
4009
  const messages = readMessages(sessionID);
3990
4010
  const result = [];
@@ -3999,23 +4019,6 @@ function findMessagesWithThinkingBlocks(sessionID) {
3999
4019
  }
4000
4020
  return result;
4001
4021
  }
4002
- function findMessagesWithThinkingOnly(sessionID) {
4003
- const messages = readMessages(sessionID);
4004
- const result = [];
4005
- for (const msg of messages) {
4006
- if (msg.role !== "assistant")
4007
- continue;
4008
- const parts = readParts(msg.id);
4009
- if (parts.length === 0)
4010
- continue;
4011
- const hasThinking = parts.some((p) => THINKING_TYPES.has(p.type));
4012
- const hasTextContent = parts.some(hasContent);
4013
- if (hasThinking && !hasTextContent) {
4014
- result.push(msg.id);
4015
- }
4016
- }
4017
- return result;
4018
- }
4019
4022
  function findMessagesWithOrphanThinking(sessionID) {
4020
4023
  const messages = readMessages(sessionID);
4021
4024
  const result = [];
@@ -4036,7 +4039,7 @@ function findMessagesWithOrphanThinking(sessionID) {
4036
4039
  return result;
4037
4040
  }
4038
4041
  function prependThinkingPart(sessionID, messageID) {
4039
- const partDir = join7(PART_STORAGE2, messageID);
4042
+ const partDir = join8(PART_STORAGE2, messageID);
4040
4043
  if (!existsSync5(partDir)) {
4041
4044
  mkdirSync2(partDir, { recursive: true });
4042
4045
  }
@@ -4050,14 +4053,14 @@ function prependThinkingPart(sessionID, messageID) {
4050
4053
  synthetic: true
4051
4054
  };
4052
4055
  try {
4053
- writeFileSync2(join7(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
4056
+ writeFileSync2(join8(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
4054
4057
  return true;
4055
4058
  } catch {
4056
4059
  return false;
4057
4060
  }
4058
4061
  }
4059
4062
  function stripThinkingParts(messageID) {
4060
- const partDir = join7(PART_STORAGE2, messageID);
4063
+ const partDir = join8(PART_STORAGE2, messageID);
4061
4064
  if (!existsSync5(partDir))
4062
4065
  return false;
4063
4066
  let anyRemoved = false;
@@ -4065,7 +4068,7 @@ function stripThinkingParts(messageID) {
4065
4068
  if (!file.endsWith(".json"))
4066
4069
  continue;
4067
4070
  try {
4068
- const filePath = join7(partDir, file);
4071
+ const filePath = join8(partDir, file);
4069
4072
  const content = readFileSync3(filePath, "utf-8");
4070
4073
  const part = JSON.parse(content);
4071
4074
  if (THINKING_TYPES.has(part.type)) {
@@ -4078,50 +4081,6 @@ function stripThinkingParts(messageID) {
4078
4081
  }
4079
4082
  return anyRemoved;
4080
4083
  }
4081
- function replaceEmptyTextParts(messageID, replacementText) {
4082
- const partDir = join7(PART_STORAGE2, messageID);
4083
- if (!existsSync5(partDir))
4084
- return false;
4085
- let anyReplaced = false;
4086
- for (const file of readdirSync3(partDir)) {
4087
- if (!file.endsWith(".json"))
4088
- continue;
4089
- try {
4090
- const filePath = join7(partDir, file);
4091
- const content = readFileSync3(filePath, "utf-8");
4092
- const part = JSON.parse(content);
4093
- if (part.type === "text") {
4094
- const textPart = part;
4095
- if (!textPart.text?.trim()) {
4096
- textPart.text = replacementText;
4097
- textPart.synthetic = true;
4098
- writeFileSync2(filePath, JSON.stringify(textPart, null, 2));
4099
- anyReplaced = true;
4100
- }
4101
- }
4102
- } catch {
4103
- continue;
4104
- }
4105
- }
4106
- return anyReplaced;
4107
- }
4108
- function findMessagesWithEmptyTextParts(sessionID) {
4109
- const messages = readMessages(sessionID);
4110
- const result = [];
4111
- for (const msg of messages) {
4112
- const parts = readParts(msg.id);
4113
- const hasEmptyTextPart = parts.some((p) => {
4114
- if (p.type !== "text")
4115
- return false;
4116
- const textPart = p;
4117
- return !textPart.text?.trim();
4118
- });
4119
- if (hasEmptyTextPart) {
4120
- result.push(msg.id);
4121
- }
4122
- }
4123
- return result;
4124
- }
4125
4084
  function findMessageByIndexNeedingThinking(sessionID, targetIndex) {
4126
4085
  const messages = readMessages(sessionID);
4127
4086
  if (targetIndex < 0 || targetIndex >= messages.length)
@@ -4142,6 +4101,37 @@ function findMessageByIndexNeedingThinking(sessionID, targetIndex) {
4142
4101
  }
4143
4102
 
4144
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
+ }
4145
4135
  function getErrorMessage(error) {
4146
4136
  if (!error)
4147
4137
  return "";
@@ -4184,9 +4174,6 @@ function detectErrorType(error) {
4184
4174
  if (message.includes("thinking is disabled") && message.includes("cannot contain")) {
4185
4175
  return "thinking_disabled_violation";
4186
4176
  }
4187
- 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")) {
4188
- return "empty_content_message";
4189
- }
4190
4177
  return null;
4191
4178
  }
4192
4179
  function extractToolUseIds(parts) {
@@ -4255,55 +4242,9 @@ async function recoverThinkingDisabledViolation(_client, sessionID, _failedAssis
4255
4242
  }
4256
4243
  return anySuccess;
4257
4244
  }
4258
- var PLACEHOLDER_TEXT = "[user interrupted]";
4259
- async function recoverEmptyContentMessage(_client, sessionID, failedAssistantMsg, _directory, error) {
4260
- const targetIndex = extractMessageIndex(error);
4261
- const failedID = failedAssistantMsg.info?.id;
4262
- let anySuccess = false;
4263
- const messagesWithEmptyText = findMessagesWithEmptyTextParts(sessionID);
4264
- for (const messageID of messagesWithEmptyText) {
4265
- if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
4266
- anySuccess = true;
4267
- }
4268
- }
4269
- const thinkingOnlyIDs = findMessagesWithThinkingOnly(sessionID);
4270
- for (const messageID of thinkingOnlyIDs) {
4271
- if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
4272
- anySuccess = true;
4273
- }
4274
- }
4275
- if (targetIndex !== null) {
4276
- const targetMessageID = findEmptyMessageByIndex(sessionID, targetIndex);
4277
- if (targetMessageID) {
4278
- if (replaceEmptyTextParts(targetMessageID, PLACEHOLDER_TEXT)) {
4279
- return true;
4280
- }
4281
- if (injectTextPart(sessionID, targetMessageID, PLACEHOLDER_TEXT)) {
4282
- return true;
4283
- }
4284
- }
4285
- }
4286
- if (failedID) {
4287
- if (replaceEmptyTextParts(failedID, PLACEHOLDER_TEXT)) {
4288
- return true;
4289
- }
4290
- if (injectTextPart(sessionID, failedID, PLACEHOLDER_TEXT)) {
4291
- return true;
4292
- }
4293
- }
4294
- const emptyMessageIDs = findEmptyMessages(sessionID);
4295
- for (const messageID of emptyMessageIDs) {
4296
- if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
4297
- anySuccess = true;
4298
- }
4299
- if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
4300
- anySuccess = true;
4301
- }
4302
- }
4303
- return anySuccess;
4304
- }
4305
- function createSessionRecoveryHook(ctx) {
4245
+ function createSessionRecoveryHook(ctx, options) {
4306
4246
  const processingErrors = new Set;
4247
+ const experimental = options?.experimental;
4307
4248
  let onAbortCallback = null;
4308
4249
  let onRecoveryCompleteCallback = null;
4309
4250
  const setOnAbortCallback = (callback) => {
@@ -4345,14 +4286,12 @@ function createSessionRecoveryHook(ctx) {
4345
4286
  const toastTitles = {
4346
4287
  tool_result_missing: "Tool Crash Recovery",
4347
4288
  thinking_block_order: "Thinking Block Recovery",
4348
- thinking_disabled_violation: "Thinking Strip Recovery",
4349
- empty_content_message: "Empty Message Recovery"
4289
+ thinking_disabled_violation: "Thinking Strip Recovery"
4350
4290
  };
4351
4291
  const toastMessages = {
4352
4292
  tool_result_missing: "Injecting cancelled tool results...",
4353
4293
  thinking_block_order: "Fixing message structure...",
4354
- thinking_disabled_violation: "Stripping thinking blocks...",
4355
- empty_content_message: "Fixing empty message..."
4294
+ thinking_disabled_violation: "Stripping thinking blocks..."
4356
4295
  };
4357
4296
  await ctx.client.tui.showToast({
4358
4297
  body: {
@@ -4367,10 +4306,18 @@ function createSessionRecoveryHook(ctx) {
4367
4306
  success = await recoverToolResultMissing(ctx.client, sessionID, failedMsg);
4368
4307
  } else if (errorType === "thinking_block_order") {
4369
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
+ }
4370
4314
  } else if (errorType === "thinking_disabled_violation") {
4371
4315
  success = await recoverThinkingDisabledViolation(ctx.client, sessionID, failedMsg);
4372
- } else if (errorType === "empty_content_message") {
4373
- 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
+ }
4374
4321
  }
4375
4322
  return success;
4376
4323
  } catch (err) {
@@ -4393,7 +4340,7 @@ function createSessionRecoveryHook(ctx) {
4393
4340
  // src/hooks/comment-checker/cli.ts
4394
4341
  var {spawn: spawn3 } = globalThis.Bun;
4395
4342
  import { createRequire as createRequire2 } from "module";
4396
- import { dirname, join as join9 } from "path";
4343
+ import { dirname, join as join10 } from "path";
4397
4344
  import { existsSync as existsSync7 } from "fs";
4398
4345
  import * as fs2 from "fs";
4399
4346
  import { tmpdir as tmpdir3 } from "os";
@@ -4401,11 +4348,11 @@ import { tmpdir as tmpdir3 } from "os";
4401
4348
  // src/hooks/comment-checker/downloader.ts
4402
4349
  var {spawn: spawn2 } = globalThis.Bun;
4403
4350
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, chmodSync, unlinkSync as unlinkSync2, appendFileSync as appendFileSync2 } from "fs";
4404
- import { join as join8 } from "path";
4405
- 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";
4406
4353
  import { createRequire } from "module";
4407
4354
  var DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1";
4408
- var DEBUG_FILE = join8(tmpdir2(), "comment-checker-debug.log");
4355
+ var DEBUG_FILE = join9(tmpdir2(), "comment-checker-debug.log");
4409
4356
  function debugLog(...args) {
4410
4357
  if (DEBUG) {
4411
4358
  const msg = `[${new Date().toISOString()}] [comment-checker:downloader] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
@@ -4423,14 +4370,14 @@ var PLATFORM_MAP = {
4423
4370
  };
4424
4371
  function getCacheDir() {
4425
4372
  const xdgCache2 = process.env.XDG_CACHE_HOME;
4426
- const base = xdgCache2 || join8(homedir3(), ".cache");
4427
- return join8(base, "oh-my-opencode", "bin");
4373
+ const base = xdgCache2 || join9(homedir4(), ".cache");
4374
+ return join9(base, "oh-my-opencode", "bin");
4428
4375
  }
4429
4376
  function getBinaryName() {
4430
4377
  return process.platform === "win32" ? "comment-checker.exe" : "comment-checker";
4431
4378
  }
4432
4379
  function getCachedBinaryPath() {
4433
- const binaryPath = join8(getCacheDir(), getBinaryName());
4380
+ const binaryPath = join9(getCacheDir(), getBinaryName());
4434
4381
  return existsSync6(binaryPath) ? binaryPath : null;
4435
4382
  }
4436
4383
  function getPackageVersion() {
@@ -4478,14 +4425,14 @@ async function downloadCommentChecker() {
4478
4425
  }
4479
4426
  const cacheDir = getCacheDir();
4480
4427
  const binaryName = getBinaryName();
4481
- const binaryPath = join8(cacheDir, binaryName);
4428
+ const binaryPath = join9(cacheDir, binaryName);
4482
4429
  if (existsSync6(binaryPath)) {
4483
4430
  debugLog("Binary already cached at:", binaryPath);
4484
4431
  return binaryPath;
4485
4432
  }
4486
4433
  const version = getPackageVersion();
4487
- const { os: os3, arch, ext } = platformInfo;
4488
- 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}`;
4489
4436
  const downloadUrl = `https://github.com/${REPO}/releases/download/v${version}/${assetName}`;
4490
4437
  debugLog(`Downloading from: ${downloadUrl}`);
4491
4438
  console.log(`[oh-my-opencode] Downloading comment-checker binary...`);
@@ -4497,7 +4444,7 @@ async function downloadCommentChecker() {
4497
4444
  if (!response.ok) {
4498
4445
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
4499
4446
  }
4500
- const archivePath = join8(cacheDir, assetName);
4447
+ const archivePath = join9(cacheDir, assetName);
4501
4448
  const arrayBuffer = await response.arrayBuffer();
4502
4449
  await Bun.write(archivePath, arrayBuffer);
4503
4450
  debugLog(`Downloaded archive to: ${archivePath}`);
@@ -4533,7 +4480,7 @@ async function ensureCommentCheckerBinary() {
4533
4480
 
4534
4481
  // src/hooks/comment-checker/cli.ts
4535
4482
  var DEBUG2 = process.env.COMMENT_CHECKER_DEBUG === "1";
4536
- var DEBUG_FILE2 = join9(tmpdir3(), "comment-checker-debug.log");
4483
+ var DEBUG_FILE2 = join10(tmpdir3(), "comment-checker-debug.log");
4537
4484
  function debugLog2(...args) {
4538
4485
  if (DEBUG2) {
4539
4486
  const msg = `[${new Date().toISOString()}] [comment-checker:cli] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
@@ -4550,7 +4497,7 @@ function findCommentCheckerPathSync() {
4550
4497
  const require2 = createRequire2(import.meta.url);
4551
4498
  const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
4552
4499
  const cliDir = dirname(cliPkgPath);
4553
- const binaryPath = join9(cliDir, "bin", binaryName);
4500
+ const binaryPath = join10(cliDir, "bin", binaryName);
4554
4501
  if (existsSync7(binaryPath)) {
4555
4502
  debugLog2("found binary in main package:", binaryPath);
4556
4503
  return binaryPath;
@@ -4597,8 +4544,8 @@ async function getCommentCheckerPath() {
4597
4544
  function startBackgroundInit() {
4598
4545
  if (!initPromise) {
4599
4546
  initPromise = getCommentCheckerPath();
4600
- initPromise.then((path3) => {
4601
- debugLog2("background init complete:", path3 || "no binary");
4547
+ initPromise.then((path4) => {
4548
+ debugLog2("background init complete:", path4 || "no binary");
4602
4549
  }).catch((err) => {
4603
4550
  debugLog2("background init error:", err);
4604
4551
  });
@@ -4647,9 +4594,9 @@ async function runCommentChecker(input, cliPath) {
4647
4594
  import * as fs3 from "fs";
4648
4595
  import { existsSync as existsSync8 } from "fs";
4649
4596
  import { tmpdir as tmpdir4 } from "os";
4650
- import { join as join10 } from "path";
4597
+ import { join as join11 } from "path";
4651
4598
  var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
4652
- var DEBUG_FILE3 = join10(tmpdir4(), "comment-checker-debug.log");
4599
+ var DEBUG_FILE3 = join11(tmpdir4(), "comment-checker-debug.log");
4653
4600
  function debugLog3(...args) {
4654
4601
  if (DEBUG3) {
4655
4602
  const msg = `[${new Date().toISOString()}] [comment-checker:hook] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
@@ -4673,8 +4620,8 @@ function createCommentCheckerHooks() {
4673
4620
  debugLog3("createCommentCheckerHooks called");
4674
4621
  startBackgroundInit();
4675
4622
  cliPathPromise = getCommentCheckerPath();
4676
- cliPathPromise.then((path3) => {
4677
- debugLog3("CLI path resolved:", path3 || "disabled (no binary)");
4623
+ cliPathPromise.then((path4) => {
4624
+ debugLog3("CLI path resolved:", path4 || "disabled (no binary)");
4678
4625
  }).catch((err) => {
4679
4626
  debugLog3("CLI path resolution error:", err);
4680
4627
  });
@@ -4795,7 +4742,7 @@ function createToolOutputTruncatorHook(ctx) {
4795
4742
  }
4796
4743
  // src/hooks/directory-agents-injector/index.ts
4797
4744
  import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
4798
- 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";
4799
4746
 
4800
4747
  // src/hooks/directory-agents-injector/storage.ts
4801
4748
  import {
@@ -4805,17 +4752,17 @@ import {
4805
4752
  writeFileSync as writeFileSync3,
4806
4753
  unlinkSync as unlinkSync3
4807
4754
  } from "fs";
4808
- import { join as join12 } from "path";
4755
+ import { join as join13 } from "path";
4809
4756
 
4810
4757
  // src/hooks/directory-agents-injector/constants.ts
4811
- import { join as join11 } from "path";
4812
- var OPENCODE_STORAGE3 = join11(xdgData2 ?? "", "opencode", "storage");
4813
- 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");
4814
4761
  var AGENTS_FILENAME = "AGENTS.md";
4815
4762
 
4816
4763
  // src/hooks/directory-agents-injector/storage.ts
4817
4764
  function getStoragePath(sessionID) {
4818
- return join12(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
4765
+ return join13(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
4819
4766
  }
4820
4767
  function loadInjectedPaths(sessionID) {
4821
4768
  const filePath = getStoragePath(sessionID);
@@ -4867,7 +4814,7 @@ function createDirectoryAgentsInjectorHook(ctx) {
4867
4814
  const found = [];
4868
4815
  let current = startDir;
4869
4816
  while (true) {
4870
- const agentsPath = join13(current, AGENTS_FILENAME);
4817
+ const agentsPath = join14(current, AGENTS_FILENAME);
4871
4818
  if (existsSync10(agentsPath)) {
4872
4819
  found.push(agentsPath);
4873
4820
  }
@@ -4904,10 +4851,10 @@ function createDirectoryAgentsInjectorHook(ctx) {
4904
4851
  }
4905
4852
  if (toInject.length === 0)
4906
4853
  return;
4907
- for (const { path: path3, content } of toInject) {
4854
+ for (const { path: path4, content } of toInject) {
4908
4855
  output.output += `
4909
4856
 
4910
- [Directory Context: ${path3}]
4857
+ [Directory Context: ${path4}]
4911
4858
  ${content}`;
4912
4859
  }
4913
4860
  saveInjectedPaths(input.sessionID, cache);
@@ -4936,7 +4883,7 @@ ${content}`;
4936
4883
  }
4937
4884
  // src/hooks/directory-readme-injector/index.ts
4938
4885
  import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
4939
- 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";
4940
4887
 
4941
4888
  // src/hooks/directory-readme-injector/storage.ts
4942
4889
  import {
@@ -4946,17 +4893,17 @@ import {
4946
4893
  writeFileSync as writeFileSync4,
4947
4894
  unlinkSync as unlinkSync4
4948
4895
  } from "fs";
4949
- import { join as join15 } from "path";
4896
+ import { join as join16 } from "path";
4950
4897
 
4951
4898
  // src/hooks/directory-readme-injector/constants.ts
4952
- import { join as join14 } from "path";
4953
- var OPENCODE_STORAGE4 = join14(xdgData2 ?? "", "opencode", "storage");
4954
- 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");
4955
4902
  var README_FILENAME = "README.md";
4956
4903
 
4957
4904
  // src/hooks/directory-readme-injector/storage.ts
4958
4905
  function getStoragePath2(sessionID) {
4959
- return join15(README_INJECTOR_STORAGE, `${sessionID}.json`);
4906
+ return join16(README_INJECTOR_STORAGE, `${sessionID}.json`);
4960
4907
  }
4961
4908
  function loadInjectedPaths2(sessionID) {
4962
4909
  const filePath = getStoragePath2(sessionID);
@@ -5008,7 +4955,7 @@ function createDirectoryReadmeInjectorHook(ctx) {
5008
4955
  const found = [];
5009
4956
  let current = startDir;
5010
4957
  while (true) {
5011
- const readmePath = join16(current, README_FILENAME);
4958
+ const readmePath = join17(current, README_FILENAME);
5012
4959
  if (existsSync12(readmePath)) {
5013
4960
  found.push(readmePath);
5014
4961
  }
@@ -5045,10 +4992,10 @@ function createDirectoryReadmeInjectorHook(ctx) {
5045
4992
  }
5046
4993
  if (toInject.length === 0)
5047
4994
  return;
5048
- for (const { path: path3, content } of toInject) {
4995
+ for (const { path: path4, content } of toInject) {
5049
4996
  output.output += `
5050
4997
 
5051
- [Project README: ${path3}]
4998
+ [Project README: ${path4}]
5052
4999
  ${content}`;
5053
5000
  }
5054
5001
  saveInjectedPaths2(input.sessionID, cache);
@@ -5111,7 +5058,8 @@ var TOKEN_LIMIT_KEYWORDS = [
5111
5058
  "max_tokens",
5112
5059
  "token limit",
5113
5060
  "context length",
5114
- "too many tokens"
5061
+ "too many tokens",
5062
+ "non-empty content"
5115
5063
  ];
5116
5064
  function extractTokensFromMessage(message) {
5117
5065
  for (const pattern of TOKEN_LIMIT_PATTERNS) {
@@ -5130,6 +5078,13 @@ function isTokenLimitError(text) {
5130
5078
  }
5131
5079
  function parseAnthropicTokenLimitError(err) {
5132
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
+ }
5133
5088
  if (isTokenLimitError(err)) {
5134
5089
  const tokens = extractTokensFromMessage(err);
5135
5090
  return {
@@ -5225,6 +5180,13 @@ function parseAnthropicTokenLimitError(err) {
5225
5180
  };
5226
5181
  }
5227
5182
  }
5183
+ if (combinedText.toLowerCase().includes("non-empty content")) {
5184
+ return {
5185
+ currentTokens: 0,
5186
+ maxTokens: 0,
5187
+ errorType: "non-empty content"
5188
+ };
5189
+ }
5228
5190
  if (isTokenLimitError(combinedText)) {
5229
5191
  return {
5230
5192
  currentTokens: 0,
@@ -5247,26 +5209,35 @@ var FALLBACK_CONFIG = {
5247
5209
  minMessagesRequired: 2
5248
5210
  };
5249
5211
  var TRUNCATE_CONFIG = {
5250
- maxTruncateAttempts: 10,
5251
- minOutputSizeToTruncate: 1000
5212
+ maxTruncateAttempts: 20,
5213
+ minOutputSizeToTruncate: 500,
5214
+ targetTokenRatio: 0.5,
5215
+ charsPerToken: 4
5252
5216
  };
5253
5217
 
5254
5218
  // src/hooks/anthropic-auto-compact/storage.ts
5255
5219
  import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
5256
- import { join as join17 } from "path";
5257
- var OPENCODE_STORAGE5 = join17(xdgData2 ?? "", "opencode", "storage");
5258
- var MESSAGE_STORAGE3 = join17(OPENCODE_STORAGE5, "message");
5259
- 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");
5260
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.]";
5261
5232
  function getMessageDir3(sessionID) {
5262
5233
  if (!existsSync13(MESSAGE_STORAGE3))
5263
5234
  return "";
5264
- const directPath = join17(MESSAGE_STORAGE3, sessionID);
5235
+ const directPath = join18(MESSAGE_STORAGE3, sessionID);
5265
5236
  if (existsSync13(directPath)) {
5266
5237
  return directPath;
5267
5238
  }
5268
5239
  for (const dir of readdirSync4(MESSAGE_STORAGE3)) {
5269
- const sessionPath = join17(MESSAGE_STORAGE3, dir, sessionID);
5240
+ const sessionPath = join18(MESSAGE_STORAGE3, dir, sessionID);
5270
5241
  if (existsSync13(sessionPath)) {
5271
5242
  return sessionPath;
5272
5243
  }
@@ -5290,14 +5261,14 @@ function findToolResultsBySize(sessionID) {
5290
5261
  const messageIds = getMessageIds(sessionID);
5291
5262
  const results = [];
5292
5263
  for (const messageID of messageIds) {
5293
- const partDir = join17(PART_STORAGE3, messageID);
5264
+ const partDir = join18(PART_STORAGE3, messageID);
5294
5265
  if (!existsSync13(partDir))
5295
5266
  continue;
5296
5267
  for (const file of readdirSync4(partDir)) {
5297
5268
  if (!file.endsWith(".json"))
5298
5269
  continue;
5299
5270
  try {
5300
- const partPath = join17(partDir, file);
5271
+ const partPath = join18(partDir, file);
5301
5272
  const content = readFileSync8(partPath, "utf-8");
5302
5273
  const part = JSON.parse(content);
5303
5274
  if (part.type === "tool" && part.state?.output && !part.truncated) {
@@ -5342,6 +5313,56 @@ function truncateToolResult(partPath) {
5342
5313
  return { success: false };
5343
5314
  }
5344
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
+ }
5345
5366
 
5346
5367
  // src/hooks/anthropic-auto-compact/executor.ts
5347
5368
  function getOrCreateRetryState(autoCompactState, sessionID) {
@@ -5440,14 +5461,97 @@ function clearSessionState(autoCompactState, sessionID) {
5440
5461
  autoCompactState.retryStateBySession.delete(sessionID);
5441
5462
  autoCompactState.fallbackStateBySession.delete(sessionID);
5442
5463
  autoCompactState.truncateStateBySession.delete(sessionID);
5464
+ autoCompactState.emptyContentAttemptBySession.delete(sessionID);
5443
5465
  autoCompactState.compactionInProgress.delete(sessionID);
5444
5466
  }
5445
- 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) {
5446
5504
  if (autoCompactState.compactionInProgress.has(sessionID)) {
5447
5505
  return;
5448
5506
  }
5449
5507
  autoCompactState.compactionInProgress.add(sessionID);
5508
+ const errorData = autoCompactState.errorDataBySession.get(sessionID);
5450
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;
5451
5555
  if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
5452
5556
  const largest = findLargestToolResult(sessionID);
5453
5557
  if (largest && largest.outputSize >= TRUNCATE_CONFIG.minOutputSizeToTruncate) {
@@ -5475,10 +5579,58 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
5475
5579
  }, 500);
5476
5580
  return;
5477
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(() => {});
5478
5601
  }
5479
5602
  }
5480
5603
  const retryState = getOrCreateRetryState(autoCompactState, sessionID);
5481
- 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) {
5482
5634
  retryState.attempt++;
5483
5635
  retryState.lastAttemptTime = Date.now();
5484
5636
  const providerID = msg.providerID;
@@ -5498,7 +5650,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
5498
5650
  body: { providerID, modelID },
5499
5651
  query: { directory }
5500
5652
  });
5501
- clearSessionState(autoCompactState, sessionID);
5653
+ autoCompactState.compactionInProgress.delete(sessionID);
5502
5654
  setTimeout(async () => {
5503
5655
  try {
5504
5656
  await client.session.prompt_async({
@@ -5514,10 +5666,19 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
5514
5666
  const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
5515
5667
  const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
5516
5668
  setTimeout(() => {
5517
- executeCompact(sessionID, msg, autoCompactState, client, directory);
5669
+ executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
5518
5670
  }, cappedDelay);
5519
5671
  return;
5520
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(() => {});
5521
5682
  }
5522
5683
  }
5523
5684
  const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
@@ -5551,10 +5712,19 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
5551
5712
  truncateState.truncateAttempt = 0;
5552
5713
  autoCompactState.compactionInProgress.delete(sessionID);
5553
5714
  setTimeout(() => {
5554
- executeCompact(sessionID, msg, autoCompactState, client, directory);
5715
+ executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
5555
5716
  }, 1000);
5556
5717
  return;
5557
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(() => {});
5558
5728
  }
5559
5729
  }
5560
5730
  clearSessionState(autoCompactState, sessionID);
@@ -5576,11 +5746,13 @@ function createAutoCompactState() {
5576
5746
  retryStateBySession: new Map,
5577
5747
  fallbackStateBySession: new Map,
5578
5748
  truncateStateBySession: new Map,
5749
+ emptyContentAttemptBySession: new Map,
5579
5750
  compactionInProgress: new Set
5580
5751
  };
5581
5752
  }
5582
- function createAnthropicAutoCompactHook(ctx) {
5753
+ function createAnthropicAutoCompactHook(ctx, options) {
5583
5754
  const autoCompactState = createAutoCompactState();
5755
+ const experimental = options?.experimental;
5584
5756
  const eventHandler = async ({ event }) => {
5585
5757
  const props = event.properties;
5586
5758
  if (event.type === "session.deleted") {
@@ -5591,15 +5763,18 @@ function createAnthropicAutoCompactHook(ctx) {
5591
5763
  autoCompactState.retryStateBySession.delete(sessionInfo.id);
5592
5764
  autoCompactState.fallbackStateBySession.delete(sessionInfo.id);
5593
5765
  autoCompactState.truncateStateBySession.delete(sessionInfo.id);
5766
+ autoCompactState.emptyContentAttemptBySession.delete(sessionInfo.id);
5594
5767
  autoCompactState.compactionInProgress.delete(sessionInfo.id);
5595
5768
  }
5596
5769
  return;
5597
5770
  }
5598
5771
  if (event.type === "session.error") {
5599
5772
  const sessionID = props?.sessionID;
5773
+ log("[auto-compact] session.error received", { sessionID, error: props?.error });
5600
5774
  if (!sessionID)
5601
5775
  return;
5602
5776
  const parsed = parseAnthropicTokenLimitError(props?.error);
5777
+ log("[auto-compact] parsed result", { parsed, hasError: !!props?.error });
5603
5778
  if (parsed) {
5604
5779
  autoCompactState.pendingCompact.add(sessionID);
5605
5780
  autoCompactState.errorDataBySession.set(sessionID, parsed);
@@ -5618,7 +5793,7 @@ function createAnthropicAutoCompactHook(ctx) {
5618
5793
  }
5619
5794
  }).catch(() => {});
5620
5795
  setTimeout(() => {
5621
- executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory);
5796
+ executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
5622
5797
  }, 300);
5623
5798
  }
5624
5799
  return;
@@ -5627,7 +5802,9 @@ function createAnthropicAutoCompactHook(ctx) {
5627
5802
  const info = props?.info;
5628
5803
  const sessionID = info?.sessionID;
5629
5804
  if (sessionID && info?.role === "assistant" && info.error) {
5805
+ log("[auto-compact] message.updated with error", { sessionID, error: info.error });
5630
5806
  const parsed = parseAnthropicTokenLimitError(info.error);
5807
+ log("[auto-compact] message.updated parsed result", { parsed });
5631
5808
  if (parsed) {
5632
5809
  parsed.providerID = info.providerID;
5633
5810
  parsed.modelID = info.modelID;
@@ -5659,7 +5836,7 @@ function createAnthropicAutoCompactHook(ctx) {
5659
5836
  duration: 3000
5660
5837
  }
5661
5838
  }).catch(() => {});
5662
- await executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory);
5839
+ await executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
5663
5840
  }
5664
5841
  };
5665
5842
  return {
@@ -5933,8 +6110,8 @@ function createThinkModeHook() {
5933
6110
  };
5934
6111
  }
5935
6112
  // src/hooks/claude-code-hooks/config.ts
5936
- import { homedir as homedir4 } from "os";
5937
- import { join as join18 } from "path";
6113
+ import { homedir as homedir6 } from "os";
6114
+ import { join as join19 } from "path";
5938
6115
  import { existsSync as existsSync14 } from "fs";
5939
6116
  function normalizeHookMatcher(raw) {
5940
6117
  return {
@@ -5958,11 +6135,11 @@ function normalizeHooksConfig(raw) {
5958
6135
  return result;
5959
6136
  }
5960
6137
  function getClaudeSettingsPaths(customPath) {
5961
- const home = homedir4();
6138
+ const home = homedir6();
5962
6139
  const paths = [
5963
- join18(home, ".claude", "settings.json"),
5964
- join18(process.cwd(), ".claude", "settings.json"),
5965
- 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")
5966
6143
  ];
5967
6144
  if (customPath && existsSync14(customPath)) {
5968
6145
  paths.unshift(customPath);
@@ -6006,21 +6183,21 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6006
6183
 
6007
6184
  // src/hooks/claude-code-hooks/config-loader.ts
6008
6185
  import { existsSync as existsSync15 } from "fs";
6009
- import { homedir as homedir5 } from "os";
6010
- import { join as join19 } from "path";
6011
- 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");
6012
6189
  function getProjectConfigPath() {
6013
- return join19(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6190
+ return join20(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6014
6191
  }
6015
- async function loadConfigFromPath(path3) {
6016
- if (!existsSync15(path3)) {
6192
+ async function loadConfigFromPath(path4) {
6193
+ if (!existsSync15(path4)) {
6017
6194
  return null;
6018
6195
  }
6019
6196
  try {
6020
- const content = await Bun.file(path3).text();
6197
+ const content = await Bun.file(path4).text();
6021
6198
  return JSON.parse(content);
6022
6199
  } catch (error) {
6023
- log("Failed to load config", { path: path3, error });
6200
+ log("Failed to load config", { path: path4, error });
6024
6201
  return null;
6025
6202
  }
6026
6203
  }
@@ -6192,13 +6369,13 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
6192
6369
  }
6193
6370
 
6194
6371
  // src/hooks/claude-code-hooks/transcript.ts
6195
- import { join as join20 } from "path";
6372
+ import { join as join21 } from "path";
6196
6373
  import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync16, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "fs";
6197
- import { homedir as homedir6, tmpdir as tmpdir5 } from "os";
6374
+ import { homedir as homedir8, tmpdir as tmpdir5 } from "os";
6198
6375
  import { randomUUID } from "crypto";
6199
- var TRANSCRIPT_DIR = join20(homedir6(), ".claude", "transcripts");
6376
+ var TRANSCRIPT_DIR = join21(homedir8(), ".claude", "transcripts");
6200
6377
  function getTranscriptPath(sessionId) {
6201
- return join20(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6378
+ return join21(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6202
6379
  }
6203
6380
  function ensureTranscriptDir() {
6204
6381
  if (!existsSync16(TRANSCRIPT_DIR)) {
@@ -6207,10 +6384,10 @@ function ensureTranscriptDir() {
6207
6384
  }
6208
6385
  function appendTranscriptEntry(sessionId, entry) {
6209
6386
  ensureTranscriptDir();
6210
- const path3 = getTranscriptPath(sessionId);
6387
+ const path4 = getTranscriptPath(sessionId);
6211
6388
  const line = JSON.stringify(entry) + `
6212
6389
  `;
6213
- appendFileSync5(path3, line);
6390
+ appendFileSync5(path4, line);
6214
6391
  }
6215
6392
  function recordToolUse(sessionId, toolName, toolInput) {
6216
6393
  appendTranscriptEntry(sessionId, {
@@ -6288,7 +6465,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
6288
6465
  }
6289
6466
  };
6290
6467
  entries.push(JSON.stringify(currentEntry));
6291
- const tempPath = join20(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6468
+ const tempPath = join21(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6292
6469
  writeFileSync6(tempPath, entries.join(`
6293
6470
  `) + `
6294
6471
  `);
@@ -6308,7 +6485,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
6308
6485
  ]
6309
6486
  }
6310
6487
  };
6311
- const tempPath = join20(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6488
+ const tempPath = join21(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6312
6489
  writeFileSync6(tempPath, JSON.stringify(currentEntry) + `
6313
6490
  `);
6314
6491
  return tempPath;
@@ -6317,11 +6494,11 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
6317
6494
  }
6318
6495
  }
6319
6496
  }
6320
- function deleteTempTranscript(path3) {
6321
- if (!path3)
6497
+ function deleteTempTranscript(path4) {
6498
+ if (!path4)
6322
6499
  return;
6323
6500
  try {
6324
- unlinkSync5(path3);
6501
+ unlinkSync5(path4);
6325
6502
  } catch {}
6326
6503
  }
6327
6504
 
@@ -6520,11 +6697,11 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
6520
6697
  }
6521
6698
 
6522
6699
  // src/hooks/claude-code-hooks/todo.ts
6523
- import { join as join21 } from "path";
6524
- import { homedir as homedir7 } from "os";
6525
- 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");
6526
6703
  function getTodoPath(sessionId) {
6527
- return join21(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
6704
+ return join22(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
6528
6705
  }
6529
6706
 
6530
6707
  // src/hooks/claude-code-hooks/stop.ts
@@ -6857,7 +7034,7 @@ ${result.message}`;
6857
7034
  }
6858
7035
  // src/hooks/rules-injector/index.ts
6859
7036
  import { readFileSync as readFileSync10 } from "fs";
6860
- import { homedir as homedir8 } from "os";
7037
+ import { homedir as homedir10 } from "os";
6861
7038
  import { relative as relative3, resolve as resolve4 } from "path";
6862
7039
 
6863
7040
  // src/hooks/rules-injector/finder.ts
@@ -6867,12 +7044,12 @@ import {
6867
7044
  realpathSync,
6868
7045
  statSync as statSync2
6869
7046
  } from "fs";
6870
- import { dirname as dirname4, join as join23, relative } from "path";
7047
+ import { dirname as dirname4, join as join24, relative } from "path";
6871
7048
 
6872
7049
  // src/hooks/rules-injector/constants.ts
6873
- import { join as join22 } from "path";
6874
- var OPENCODE_STORAGE6 = join22(xdgData2 ?? "", "opencode", "storage");
6875
- 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");
6876
7053
  var PROJECT_MARKERS = [
6877
7054
  ".git",
6878
7055
  "pyproject.toml",
@@ -6899,7 +7076,7 @@ function findProjectRoot(startPath) {
6899
7076
  }
6900
7077
  while (true) {
6901
7078
  for (const marker of PROJECT_MARKERS) {
6902
- const markerPath = join23(current, marker);
7079
+ const markerPath = join24(current, marker);
6903
7080
  if (existsSync17(markerPath)) {
6904
7081
  return current;
6905
7082
  }
@@ -6917,7 +7094,7 @@ function findRuleFilesRecursive(dir, results) {
6917
7094
  try {
6918
7095
  const entries = readdirSync5(dir, { withFileTypes: true });
6919
7096
  for (const entry of entries) {
6920
- const fullPath = join23(dir, entry.name);
7097
+ const fullPath = join24(dir, entry.name);
6921
7098
  if (entry.isDirectory()) {
6922
7099
  findRuleFilesRecursive(fullPath, results);
6923
7100
  } else if (entry.isFile()) {
@@ -6943,7 +7120,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
6943
7120
  let distance = 0;
6944
7121
  while (true) {
6945
7122
  for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
6946
- const ruleDir = join23(currentDir, parent, subdir);
7123
+ const ruleDir = join24(currentDir, parent, subdir);
6947
7124
  const files = [];
6948
7125
  findRuleFilesRecursive(ruleDir, files);
6949
7126
  for (const filePath of files) {
@@ -6967,7 +7144,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
6967
7144
  currentDir = parentDir;
6968
7145
  distance++;
6969
7146
  }
6970
- const userRuleDir = join23(homeDir, USER_RULE_DIR);
7147
+ const userRuleDir = join24(homeDir, USER_RULE_DIR);
6971
7148
  const userFiles = [];
6972
7149
  findRuleFilesRecursive(userRuleDir, userFiles);
6973
7150
  for (const filePath of userFiles) {
@@ -7162,9 +7339,9 @@ import {
7162
7339
  writeFileSync as writeFileSync7,
7163
7340
  unlinkSync as unlinkSync6
7164
7341
  } from "fs";
7165
- import { join as join24 } from "path";
7342
+ import { join as join25 } from "path";
7166
7343
  function getStoragePath3(sessionID) {
7167
- return join24(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
7344
+ return join25(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
7168
7345
  }
7169
7346
  function loadInjectedRules(sessionID) {
7170
7347
  const filePath = getStoragePath3(sessionID);
@@ -7225,7 +7402,7 @@ function createRulesInjectorHook(ctx) {
7225
7402
  return;
7226
7403
  const projectRoot = findProjectRoot(filePath);
7227
7404
  const cache2 = getSessionCache(input.sessionID);
7228
- const home = homedir8();
7405
+ const home = homedir10();
7229
7406
  const ruleFileCandidates = findRuleFiles(projectRoot, home, filePath);
7230
7407
  const toInject = [];
7231
7408
  for (const candidate of ruleFileCandidates) {
@@ -7296,33 +7473,33 @@ function createBackgroundNotificationHook(manager) {
7296
7473
  }
7297
7474
  // src/hooks/auto-update-checker/checker.ts
7298
7475
  import * as fs4 from "fs";
7299
- import * as path4 from "path";
7476
+ import * as path5 from "path";
7300
7477
  import { fileURLToPath } from "url";
7301
7478
 
7302
7479
  // src/hooks/auto-update-checker/constants.ts
7303
- import * as path3 from "path";
7304
- import * as os3 from "os";
7480
+ import * as path4 from "path";
7481
+ import * as os4 from "os";
7305
7482
  var PACKAGE_NAME = "oh-my-opencode";
7306
7483
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
7307
7484
  var NPM_FETCH_TIMEOUT = 5000;
7308
7485
  function getCacheDir2() {
7309
7486
  if (process.platform === "win32") {
7310
- return path3.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
7487
+ return path4.join(process.env.LOCALAPPDATA ?? os4.homedir(), "opencode");
7311
7488
  }
7312
- return path3.join(os3.homedir(), ".cache", "opencode");
7489
+ return path4.join(os4.homedir(), ".cache", "opencode");
7313
7490
  }
7314
7491
  var CACHE_DIR = getCacheDir2();
7315
- var VERSION_FILE = path3.join(CACHE_DIR, "version");
7316
- var INSTALLED_PACKAGE_JSON = path3.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
7317
- 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() {
7318
7495
  if (process.platform === "win32") {
7319
- return process.env.APPDATA ?? path3.join(os3.homedir(), "AppData", "Roaming");
7496
+ return process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
7320
7497
  }
7321
- return process.env.XDG_CONFIG_HOME ?? path3.join(os3.homedir(), ".config");
7498
+ return process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
7322
7499
  }
7323
- var USER_CONFIG_DIR = getUserConfigDir();
7324
- var USER_OPENCODE_CONFIG = path3.join(USER_CONFIG_DIR, "opencode", "opencode.json");
7325
- 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");
7326
7503
 
7327
7504
  // src/hooks/auto-update-checker/checker.ts
7328
7505
  function isLocalDevMode(directory) {
@@ -7333,8 +7510,8 @@ function stripJsonComments(json) {
7333
7510
  }
7334
7511
  function getConfigPaths(directory) {
7335
7512
  return [
7336
- path4.join(directory, ".opencode", "opencode.json"),
7337
- path4.join(directory, ".opencode", "opencode.jsonc"),
7513
+ path5.join(directory, ".opencode", "opencode.json"),
7514
+ path5.join(directory, ".opencode", "opencode.jsonc"),
7338
7515
  USER_OPENCODE_CONFIG,
7339
7516
  USER_OPENCODE_CONFIG_JSONC
7340
7517
  ];
@@ -7365,9 +7542,9 @@ function getLocalDevPath(directory) {
7365
7542
  function findPackageJsonUp(startPath) {
7366
7543
  try {
7367
7544
  const stat = fs4.statSync(startPath);
7368
- let dir = stat.isDirectory() ? startPath : path4.dirname(startPath);
7545
+ let dir = stat.isDirectory() ? startPath : path5.dirname(startPath);
7369
7546
  for (let i = 0;i < 10; i++) {
7370
- const pkgPath = path4.join(dir, "package.json");
7547
+ const pkgPath = path5.join(dir, "package.json");
7371
7548
  if (fs4.existsSync(pkgPath)) {
7372
7549
  try {
7373
7550
  const content = fs4.readFileSync(pkgPath, "utf-8");
@@ -7376,7 +7553,7 @@ function findPackageJsonUp(startPath) {
7376
7553
  return pkgPath;
7377
7554
  } catch {}
7378
7555
  }
7379
- const parent = path4.dirname(dir);
7556
+ const parent = path5.dirname(dir);
7380
7557
  if (parent === dir)
7381
7558
  break;
7382
7559
  dir = parent;
@@ -7433,7 +7610,7 @@ function getCachedVersion() {
7433
7610
  }
7434
7611
  } catch {}
7435
7612
  try {
7436
- const currentDir = path4.dirname(fileURLToPath(import.meta.url));
7613
+ const currentDir = path5.dirname(fileURLToPath(import.meta.url));
7437
7614
  const pkgPath = findPackageJsonUp(currentDir);
7438
7615
  if (pkgPath) {
7439
7616
  const content = fs4.readFileSync(pkgPath, "utf-8");
@@ -7495,11 +7672,11 @@ async function checkForUpdate(directory) {
7495
7672
 
7496
7673
  // src/hooks/auto-update-checker/cache.ts
7497
7674
  import * as fs5 from "fs";
7498
- import * as path5 from "path";
7675
+ import * as path6 from "path";
7499
7676
  function invalidatePackage(packageName = PACKAGE_NAME) {
7500
7677
  try {
7501
- const pkgDir = path5.join(CACHE_DIR, "node_modules", packageName);
7502
- 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");
7503
7680
  let packageRemoved = false;
7504
7681
  let dependencyRemoved = false;
7505
7682
  if (fs5.existsSync(pkgDir)) {
@@ -7530,7 +7707,27 @@ function invalidatePackage(packageName = PACKAGE_NAME) {
7530
7707
 
7531
7708
  // src/hooks/auto-update-checker/index.ts
7532
7709
  function createAutoUpdateCheckerHook(ctx, options = {}) {
7533
- 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
+ };
7534
7731
  let hasChecked = false;
7535
7732
  return {
7536
7733
  event: async ({ event }) => {
@@ -7548,21 +7745,21 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
7548
7745
  log("[auto-update-checker] Skipped: local development mode");
7549
7746
  if (showStartupToast) {
7550
7747
  const version = getLocalDevVersion(ctx.directory) ?? getCachedVersion();
7551
- await showVersionToast(ctx, version);
7748
+ await showVersionToast(version);
7552
7749
  }
7553
7750
  return;
7554
7751
  }
7555
7752
  if (result.isPinned) {
7556
7753
  log(`[auto-update-checker] Skipped: version pinned to ${result.currentVersion}`);
7557
7754
  if (showStartupToast) {
7558
- await showVersionToast(ctx, result.currentVersion);
7755
+ await showVersionToast(result.currentVersion);
7559
7756
  }
7560
7757
  return;
7561
7758
  }
7562
7759
  if (!result.needsUpdate) {
7563
7760
  log("[auto-update-checker] No update needed");
7564
7761
  if (showStartupToast) {
7565
- await showVersionToast(ctx, result.currentVersion);
7762
+ await showVersionToast(result.currentVersion);
7566
7763
  }
7567
7764
  return;
7568
7765
  }
@@ -7570,8 +7767,7 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
7570
7767
  await ctx.client.tui.showToast({
7571
7768
  body: {
7572
7769
  title: `OhMyOpenCode ${result.latestVersion}`,
7573
- message: `OpenCode is now on Steroids. oMoMoMoMo...
7574
- v${result.latestVersion} available. Restart OpenCode to apply.`,
7770
+ message: getToastMessage(true, result.latestVersion ?? undefined),
7575
7771
  variant: "info",
7576
7772
  duration: 8000
7577
7773
  }
@@ -7580,20 +7776,27 @@ v${result.latestVersion} available. Restart OpenCode to apply.`,
7580
7776
  } catch (err) {
7581
7777
  log("[auto-update-checker] Error during update check:", err);
7582
7778
  }
7779
+ await showConfigErrorsIfAny(ctx);
7583
7780
  }
7584
7781
  };
7585
7782
  }
7586
- async function showVersionToast(ctx, version) {
7587
- 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
+ `);
7588
7789
  await ctx.client.tui.showToast({
7589
7790
  body: {
7590
- title: `OhMyOpenCode ${displayVersion}`,
7591
- message: "OpenCode is now on Steroids. oMoMoMoMo...",
7592
- variant: "info",
7593
- duration: 5000
7791
+ title: "Config Load Error",
7792
+ message: `Failed to load config:
7793
+ ${errorMessages}`,
7794
+ variant: "error",
7795
+ duration: 1e4
7594
7796
  }
7595
7797
  }).catch(() => {});
7596
- log(`[auto-update-checker] Startup toast shown: v${displayVersion}`);
7798
+ log(`[auto-update-checker] Config load errors shown: ${errors.length} error(s)`);
7799
+ clearConfigLoadErrors();
7597
7800
  }
7598
7801
  // src/hooks/agent-usage-reminder/storage.ts
7599
7802
  import {
@@ -7603,12 +7806,12 @@ import {
7603
7806
  writeFileSync as writeFileSync9,
7604
7807
  unlinkSync as unlinkSync7
7605
7808
  } from "fs";
7606
- import { join as join29 } from "path";
7809
+ import { join as join30 } from "path";
7607
7810
 
7608
7811
  // src/hooks/agent-usage-reminder/constants.ts
7609
- import { join as join28 } from "path";
7610
- var OPENCODE_STORAGE7 = join28(xdgData2 ?? "", "opencode", "storage");
7611
- 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");
7612
7815
  var TARGET_TOOLS = new Set([
7613
7816
  "grep",
7614
7817
  "safe_grep",
@@ -7653,7 +7856,7 @@ ALWAYS prefer: Multiple parallel background_task calls > Direct tool calls
7653
7856
 
7654
7857
  // src/hooks/agent-usage-reminder/storage.ts
7655
7858
  function getStoragePath4(sessionID) {
7656
- return join29(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
7859
+ return join30(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
7657
7860
  }
7658
7861
  function loadAgentUsageState(sessionID) {
7659
7862
  const filePath = getStoragePath4(sessionID);
@@ -7888,12 +8091,12 @@ import {
7888
8091
  writeFileSync as writeFileSync10,
7889
8092
  unlinkSync as unlinkSync8
7890
8093
  } from "fs";
7891
- import { join as join31 } from "path";
8094
+ import { join as join32 } from "path";
7892
8095
 
7893
8096
  // src/hooks/interactive-bash-session/constants.ts
7894
- import { join as join30 } from "path";
7895
- var OPENCODE_STORAGE8 = join30(xdgData2 ?? "", "opencode", "storage");
7896
- 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");
7897
8100
  var OMO_SESSION_PREFIX = "omo-";
7898
8101
  function buildSessionReminderMessage(sessions) {
7899
8102
  if (sessions.length === 0)
@@ -7905,7 +8108,7 @@ function buildSessionReminderMessage(sessions) {
7905
8108
 
7906
8109
  // src/hooks/interactive-bash-session/storage.ts
7907
8110
  function getStoragePath5(sessionID) {
7908
- return join31(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
8111
+ return join32(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
7909
8112
  }
7910
8113
  function loadInteractiveBashSessionState(sessionID) {
7911
8114
  const filePath = getStoragePath5(sessionID);
@@ -8115,7 +8318,7 @@ function createInteractiveBashSessionHook(_ctx) {
8115
8318
  };
8116
8319
  }
8117
8320
  // src/hooks/empty-message-sanitizer/index.ts
8118
- var PLACEHOLDER_TEXT2 = "[user interrupted]";
8321
+ var PLACEHOLDER_TEXT = "[user interrupted]";
8119
8322
  function hasTextContent(part) {
8120
8323
  if (part.type === "text") {
8121
8324
  const text = part.text;
@@ -8144,7 +8347,7 @@ function createEmptyMessageSanitizerHook() {
8144
8347
  if (part.type === "text") {
8145
8348
  const textPart = part;
8146
8349
  if (!textPart.text || !textPart.text.trim()) {
8147
- textPart.text = PLACEHOLDER_TEXT2;
8350
+ textPart.text = PLACEHOLDER_TEXT;
8148
8351
  textPart.synthetic = true;
8149
8352
  injected = true;
8150
8353
  break;
@@ -8158,7 +8361,7 @@ function createEmptyMessageSanitizerHook() {
8158
8361
  messageID: message.info.id,
8159
8362
  sessionID: message.info.sessionID ?? "",
8160
8363
  type: "text",
8161
- text: PLACEHOLDER_TEXT2,
8364
+ text: PLACEHOLDER_TEXT,
8162
8365
  synthetic: true
8163
8366
  };
8164
8367
  if (insertIndex === -1) {
@@ -8172,7 +8375,7 @@ function createEmptyMessageSanitizerHook() {
8172
8375
  if (part.type === "text") {
8173
8376
  const textPart = part;
8174
8377
  if (textPart.text !== undefined && textPart.text.trim() === "") {
8175
- textPart.text = PLACEHOLDER_TEXT2;
8378
+ textPart.text = PLACEHOLDER_TEXT;
8176
8379
  textPart.synthetic = true;
8177
8380
  }
8178
8381
  }
@@ -9882,8 +10085,8 @@ async function createGoogleAntigravityAuthPlugin({
9882
10085
  }
9883
10086
  // src/features/claude-code-command-loader/loader.ts
9884
10087
  import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
9885
- import { homedir as homedir10 } from "os";
9886
- import { join as join32, basename } from "path";
10088
+ import { homedir as homedir12 } from "os";
10089
+ import { join as join33, basename } from "path";
9887
10090
  function loadCommandsFromDir(commandsDir, scope) {
9888
10091
  if (!existsSync23(commandsDir)) {
9889
10092
  return [];
@@ -9893,7 +10096,7 @@ function loadCommandsFromDir(commandsDir, scope) {
9893
10096
  for (const entry of entries) {
9894
10097
  if (!isMarkdownFile(entry))
9895
10098
  continue;
9896
- const commandPath = join32(commandsDir, entry.name);
10099
+ const commandPath = join33(commandsDir, entry.name);
9897
10100
  const commandName = basename(entry.name, ".md");
9898
10101
  try {
9899
10102
  const content = readFileSync15(commandPath, "utf-8");
@@ -9936,29 +10139,29 @@ function commandsToRecord(commands) {
9936
10139
  return result;
9937
10140
  }
9938
10141
  function loadUserCommands() {
9939
- const userCommandsDir = join32(homedir10(), ".claude", "commands");
10142
+ const userCommandsDir = join33(homedir12(), ".claude", "commands");
9940
10143
  const commands = loadCommandsFromDir(userCommandsDir, "user");
9941
10144
  return commandsToRecord(commands);
9942
10145
  }
9943
10146
  function loadProjectCommands() {
9944
- const projectCommandsDir = join32(process.cwd(), ".claude", "commands");
10147
+ const projectCommandsDir = join33(process.cwd(), ".claude", "commands");
9945
10148
  const commands = loadCommandsFromDir(projectCommandsDir, "project");
9946
10149
  return commandsToRecord(commands);
9947
10150
  }
9948
10151
  function loadOpencodeGlobalCommands() {
9949
- const opencodeCommandsDir = join32(homedir10(), ".config", "opencode", "command");
10152
+ const opencodeCommandsDir = join33(homedir12(), ".config", "opencode", "command");
9950
10153
  const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
9951
10154
  return commandsToRecord(commands);
9952
10155
  }
9953
10156
  function loadOpencodeProjectCommands() {
9954
- const opencodeProjectDir = join32(process.cwd(), ".opencode", "command");
10157
+ const opencodeProjectDir = join33(process.cwd(), ".opencode", "command");
9955
10158
  const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
9956
10159
  return commandsToRecord(commands);
9957
10160
  }
9958
10161
  // src/features/claude-code-skill-loader/loader.ts
9959
10162
  import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
9960
- import { homedir as homedir11 } from "os";
9961
- import { join as join33 } from "path";
10163
+ import { homedir as homedir13 } from "os";
10164
+ import { join as join34 } from "path";
9962
10165
  function loadSkillsFromDir(skillsDir, scope) {
9963
10166
  if (!existsSync24(skillsDir)) {
9964
10167
  return [];
@@ -9968,11 +10171,11 @@ function loadSkillsFromDir(skillsDir, scope) {
9968
10171
  for (const entry of entries) {
9969
10172
  if (entry.name.startsWith("."))
9970
10173
  continue;
9971
- const skillPath = join33(skillsDir, entry.name);
10174
+ const skillPath = join34(skillsDir, entry.name);
9972
10175
  if (!entry.isDirectory() && !entry.isSymbolicLink())
9973
10176
  continue;
9974
10177
  const resolvedPath = resolveSymlink(skillPath);
9975
- const skillMdPath = join33(resolvedPath, "SKILL.md");
10178
+ const skillMdPath = join34(resolvedPath, "SKILL.md");
9976
10179
  if (!existsSync24(skillMdPath))
9977
10180
  continue;
9978
10181
  try {
@@ -10010,7 +10213,7 @@ $ARGUMENTS
10010
10213
  return skills;
10011
10214
  }
10012
10215
  function loadUserSkillsAsCommands() {
10013
- const userSkillsDir = join33(homedir11(), ".claude", "skills");
10216
+ const userSkillsDir = join34(homedir13(), ".claude", "skills");
10014
10217
  const skills = loadSkillsFromDir(userSkillsDir, "user");
10015
10218
  return skills.reduce((acc, skill) => {
10016
10219
  acc[skill.name] = skill.definition;
@@ -10018,7 +10221,7 @@ function loadUserSkillsAsCommands() {
10018
10221
  }, {});
10019
10222
  }
10020
10223
  function loadProjectSkillsAsCommands() {
10021
- const projectSkillsDir = join33(process.cwd(), ".claude", "skills");
10224
+ const projectSkillsDir = join34(process.cwd(), ".claude", "skills");
10022
10225
  const skills = loadSkillsFromDir(projectSkillsDir, "project");
10023
10226
  return skills.reduce((acc, skill) => {
10024
10227
  acc[skill.name] = skill.definition;
@@ -10027,8 +10230,8 @@ function loadProjectSkillsAsCommands() {
10027
10230
  }
10028
10231
  // src/features/claude-code-agent-loader/loader.ts
10029
10232
  import { existsSync as existsSync25, readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
10030
- import { homedir as homedir12 } from "os";
10031
- 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";
10032
10235
  function parseToolsConfig(toolsStr) {
10033
10236
  if (!toolsStr)
10034
10237
  return;
@@ -10050,7 +10253,7 @@ function loadAgentsFromDir(agentsDir, scope) {
10050
10253
  for (const entry of entries) {
10051
10254
  if (!isMarkdownFile(entry))
10052
10255
  continue;
10053
- const agentPath = join34(agentsDir, entry.name);
10256
+ const agentPath = join35(agentsDir, entry.name);
10054
10257
  const agentName = basename2(entry.name, ".md");
10055
10258
  try {
10056
10259
  const content = readFileSync17(agentPath, "utf-8");
@@ -10080,7 +10283,7 @@ function loadAgentsFromDir(agentsDir, scope) {
10080
10283
  return agents;
10081
10284
  }
10082
10285
  function loadUserAgents() {
10083
- const userAgentsDir = join34(homedir12(), ".claude", "agents");
10286
+ const userAgentsDir = join35(homedir14(), ".claude", "agents");
10084
10287
  const agents = loadAgentsFromDir(userAgentsDir, "user");
10085
10288
  const result = {};
10086
10289
  for (const agent of agents) {
@@ -10089,7 +10292,7 @@ function loadUserAgents() {
10089
10292
  return result;
10090
10293
  }
10091
10294
  function loadProjectAgents() {
10092
- const projectAgentsDir = join34(process.cwd(), ".claude", "agents");
10295
+ const projectAgentsDir = join35(process.cwd(), ".claude", "agents");
10093
10296
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
10094
10297
  const result = {};
10095
10298
  for (const agent of agents) {
@@ -10099,8 +10302,8 @@ function loadProjectAgents() {
10099
10302
  }
10100
10303
  // src/features/claude-code-mcp-loader/loader.ts
10101
10304
  import { existsSync as existsSync26 } from "fs";
10102
- import { homedir as homedir13 } from "os";
10103
- import { join as join35 } from "path";
10305
+ import { homedir as homedir15 } from "os";
10306
+ import { join as join36 } from "path";
10104
10307
 
10105
10308
  // src/features/claude-code-mcp-loader/env-expander.ts
10106
10309
  function expandEnvVars(value) {
@@ -10166,12 +10369,12 @@ function transformMcpServer(name, server) {
10166
10369
 
10167
10370
  // src/features/claude-code-mcp-loader/loader.ts
10168
10371
  function getMcpConfigPaths() {
10169
- const home = homedir13();
10372
+ const home = homedir15();
10170
10373
  const cwd = process.cwd();
10171
10374
  return [
10172
- { path: join35(home, ".claude", ".mcp.json"), scope: "user" },
10173
- { path: join35(cwd, ".mcp.json"), scope: "project" },
10174
- { 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" }
10175
10378
  ];
10176
10379
  }
10177
10380
  async function loadMcpConfigFile(filePath) {
@@ -10190,13 +10393,13 @@ async function loadMcpConfigs() {
10190
10393
  const servers = {};
10191
10394
  const loadedServers = [];
10192
10395
  const paths = getMcpConfigPaths();
10193
- for (const { path: path6, scope } of paths) {
10194
- const config = await loadMcpConfigFile(path6);
10396
+ for (const { path: path7, scope } of paths) {
10397
+ const config = await loadMcpConfigFile(path7);
10195
10398
  if (!config?.mcpServers)
10196
10399
  continue;
10197
10400
  for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
10198
10401
  if (serverConfig.disabled) {
10199
- log(`Skipping disabled MCP server "${name}"`, { path: path6 });
10402
+ log(`Skipping disabled MCP server "${name}"`, { path: path7 });
10200
10403
  continue;
10201
10404
  }
10202
10405
  try {
@@ -10207,7 +10410,7 @@ async function loadMcpConfigs() {
10207
10410
  loadedServers.splice(existingIndex, 1);
10208
10411
  }
10209
10412
  loadedServers.push({ name, scope, config: transformed });
10210
- log(`Loaded MCP server "${name}" from ${scope}`, { path: path6 });
10413
+ log(`Loaded MCP server "${name}" from ${scope}`, { path: path7 });
10211
10414
  } catch (error) {
10212
10415
  log(`Failed to transform MCP server "${name}"`, error);
10213
10416
  }
@@ -10404,13 +10607,13 @@ var EXT_TO_LANG = {
10404
10607
  };
10405
10608
  // src/tools/lsp/config.ts
10406
10609
  import { existsSync as existsSync27, readFileSync as readFileSync18 } from "fs";
10407
- import { join as join36 } from "path";
10408
- import { homedir as homedir14 } from "os";
10409
- function loadJsonFile(path6) {
10410
- 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))
10411
10614
  return null;
10412
10615
  try {
10413
- return JSON.parse(readFileSync18(path6, "utf-8"));
10616
+ return JSON.parse(readFileSync18(path7, "utf-8"));
10414
10617
  } catch {
10415
10618
  return null;
10416
10619
  }
@@ -10418,9 +10621,9 @@ function loadJsonFile(path6) {
10418
10621
  function getConfigPaths2() {
10419
10622
  const cwd = process.cwd();
10420
10623
  return {
10421
- project: join36(cwd, ".opencode", "oh-my-opencode.json"),
10422
- user: join36(homedir14(), ".config", "opencode", "oh-my-opencode.json"),
10423
- 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")
10424
10627
  };
10425
10628
  }
10426
10629
  function loadAllConfigs() {
@@ -10513,7 +10716,7 @@ function isServerInstalled(command) {
10513
10716
  const pathEnv = process.env.PATH || "";
10514
10717
  const paths = pathEnv.split(":");
10515
10718
  for (const p of paths) {
10516
- if (existsSync27(join36(p, cmd))) {
10719
+ if (existsSync27(join37(p, cmd))) {
10517
10720
  return true;
10518
10721
  }
10519
10722
  }
@@ -12122,10 +12325,10 @@ function mergeDefs(...defs) {
12122
12325
  function cloneDef(schema) {
12123
12326
  return mergeDefs(schema._zod.def);
12124
12327
  }
12125
- function getElementAtPath(obj, path6) {
12126
- if (!path6)
12328
+ function getElementAtPath(obj, path7) {
12329
+ if (!path7)
12127
12330
  return obj;
12128
- return path6.reduce((acc, key) => acc?.[key], obj);
12331
+ return path7.reduce((acc, key) => acc?.[key], obj);
12129
12332
  }
12130
12333
  function promiseAllObject(promisesObj) {
12131
12334
  const keys = Object.keys(promisesObj);
@@ -12484,11 +12687,11 @@ function aborted(x, startIndex = 0) {
12484
12687
  }
12485
12688
  return false;
12486
12689
  }
12487
- function prefixIssues(path6, issues) {
12690
+ function prefixIssues(path7, issues) {
12488
12691
  return issues.map((iss) => {
12489
12692
  var _a;
12490
12693
  (_a = iss).path ?? (_a.path = []);
12491
- iss.path.unshift(path6);
12694
+ iss.path.unshift(path7);
12492
12695
  return iss;
12493
12696
  });
12494
12697
  }
@@ -12656,7 +12859,7 @@ function treeifyError(error, _mapper) {
12656
12859
  return issue2.message;
12657
12860
  };
12658
12861
  const result = { errors: [] };
12659
- const processError = (error2, path6 = []) => {
12862
+ const processError = (error2, path7 = []) => {
12660
12863
  var _a, _b;
12661
12864
  for (const issue2 of error2.issues) {
12662
12865
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -12666,7 +12869,7 @@ function treeifyError(error, _mapper) {
12666
12869
  } else if (issue2.code === "invalid_element") {
12667
12870
  processError({ issues: issue2.issues }, issue2.path);
12668
12871
  } else {
12669
- const fullpath = [...path6, ...issue2.path];
12872
+ const fullpath = [...path7, ...issue2.path];
12670
12873
  if (fullpath.length === 0) {
12671
12874
  result.errors.push(mapper(issue2));
12672
12875
  continue;
@@ -12698,8 +12901,8 @@ function treeifyError(error, _mapper) {
12698
12901
  }
12699
12902
  function toDotPath(_path) {
12700
12903
  const segs = [];
12701
- const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12702
- for (const seg of path6) {
12904
+ const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12905
+ for (const seg of path7) {
12703
12906
  if (typeof seg === "number")
12704
12907
  segs.push(`[${seg}]`);
12705
12908
  else if (typeof seg === "symbol")
@@ -24042,14 +24245,14 @@ var lsp_code_action_resolve = tool({
24042
24245
  });
24043
24246
  // src/tools/ast-grep/constants.ts
24044
24247
  import { createRequire as createRequire4 } from "module";
24045
- import { dirname as dirname6, join as join38 } from "path";
24248
+ import { dirname as dirname6, join as join39 } from "path";
24046
24249
  import { existsSync as existsSync30, statSync as statSync4 } from "fs";
24047
24250
 
24048
24251
  // src/tools/ast-grep/downloader.ts
24049
24252
  var {spawn: spawn5 } = globalThis.Bun;
24050
24253
  import { existsSync as existsSync29, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
24051
- import { join as join37 } from "path";
24052
- import { homedir as homedir15 } from "os";
24254
+ import { join as join38 } from "path";
24255
+ import { homedir as homedir17 } from "os";
24053
24256
  import { createRequire as createRequire3 } from "module";
24054
24257
  var REPO2 = "ast-grep/ast-grep";
24055
24258
  var DEFAULT_VERSION = "0.40.0";
@@ -24074,18 +24277,18 @@ var PLATFORM_MAP2 = {
24074
24277
  function getCacheDir3() {
24075
24278
  if (process.platform === "win32") {
24076
24279
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
24077
- const base2 = localAppData || join37(homedir15(), "AppData", "Local");
24078
- return join37(base2, "oh-my-opencode", "bin");
24280
+ const base2 = localAppData || join38(homedir17(), "AppData", "Local");
24281
+ return join38(base2, "oh-my-opencode", "bin");
24079
24282
  }
24080
24283
  const xdgCache2 = process.env.XDG_CACHE_HOME;
24081
- const base = xdgCache2 || join37(homedir15(), ".cache");
24082
- return join37(base, "oh-my-opencode", "bin");
24284
+ const base = xdgCache2 || join38(homedir17(), ".cache");
24285
+ return join38(base, "oh-my-opencode", "bin");
24083
24286
  }
24084
24287
  function getBinaryName3() {
24085
24288
  return process.platform === "win32" ? "sg.exe" : "sg";
24086
24289
  }
24087
24290
  function getCachedBinaryPath2() {
24088
- const binaryPath = join37(getCacheDir3(), getBinaryName3());
24291
+ const binaryPath = join38(getCacheDir3(), getBinaryName3());
24089
24292
  return existsSync29(binaryPath) ? binaryPath : null;
24090
24293
  }
24091
24294
  async function extractZip2(archivePath, destDir) {
@@ -24112,12 +24315,12 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24112
24315
  }
24113
24316
  const cacheDir = getCacheDir3();
24114
24317
  const binaryName = getBinaryName3();
24115
- const binaryPath = join37(cacheDir, binaryName);
24318
+ const binaryPath = join38(cacheDir, binaryName);
24116
24319
  if (existsSync29(binaryPath)) {
24117
24320
  return binaryPath;
24118
24321
  }
24119
- const { arch, os: os4 } = platformInfo;
24120
- const assetName = `app-${arch}-${os4}.zip`;
24322
+ const { arch, os: os5 } = platformInfo;
24323
+ const assetName = `app-${arch}-${os5}.zip`;
24121
24324
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
24122
24325
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
24123
24326
  try {
@@ -24128,7 +24331,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24128
24331
  if (!response2.ok) {
24129
24332
  throw new Error(`HTTP ${response2.status}: ${response2.statusText}`);
24130
24333
  }
24131
- const archivePath = join37(cacheDir, assetName);
24334
+ const archivePath = join38(cacheDir, assetName);
24132
24335
  const arrayBuffer = await response2.arrayBuffer();
24133
24336
  await Bun.write(archivePath, arrayBuffer);
24134
24337
  await extractZip2(archivePath, cacheDir);
@@ -24186,7 +24389,7 @@ function findSgCliPathSync() {
24186
24389
  const require2 = createRequire4(import.meta.url);
24187
24390
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
24188
24391
  const cliDir = dirname6(cliPkgPath);
24189
- const sgPath = join38(cliDir, binaryName);
24392
+ const sgPath = join39(cliDir, binaryName);
24190
24393
  if (existsSync30(sgPath) && isValidBinary(sgPath)) {
24191
24394
  return sgPath;
24192
24395
  }
@@ -24198,7 +24401,7 @@ function findSgCliPathSync() {
24198
24401
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
24199
24402
  const pkgDir = dirname6(pkgPath);
24200
24403
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
24201
- const binaryPath = join38(pkgDir, astGrepName);
24404
+ const binaryPath = join39(pkgDir, astGrepName);
24202
24405
  if (existsSync30(binaryPath) && isValidBinary(binaryPath)) {
24203
24406
  return binaryPath;
24204
24407
  }
@@ -24206,9 +24409,9 @@ function findSgCliPathSync() {
24206
24409
  }
24207
24410
  if (process.platform === "darwin") {
24208
24411
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
24209
- for (const path6 of homebrewPaths) {
24210
- if (existsSync30(path6) && isValidBinary(path6)) {
24211
- return path6;
24412
+ for (const path7 of homebrewPaths) {
24413
+ if (existsSync30(path7) && isValidBinary(path7)) {
24414
+ return path7;
24212
24415
  }
24213
24416
  }
24214
24417
  }
@@ -24226,8 +24429,8 @@ function getSgCliPath() {
24226
24429
  }
24227
24430
  return "sg";
24228
24431
  }
24229
- function setSgCliPath(path6) {
24230
- resolvedCliPath2 = path6;
24432
+ function setSgCliPath(path7) {
24433
+ resolvedCliPath2 = path7;
24231
24434
  }
24232
24435
  var SG_CLI_PATH = getSgCliPath();
24233
24436
  var CLI_LANGUAGES = [
@@ -24574,19 +24777,19 @@ var {spawn: spawn7 } = globalThis.Bun;
24574
24777
 
24575
24778
  // src/tools/grep/constants.ts
24576
24779
  import { existsSync as existsSync33 } from "fs";
24577
- import { join as join40, dirname as dirname7 } from "path";
24780
+ import { join as join41, dirname as dirname7 } from "path";
24578
24781
  import { spawnSync } from "child_process";
24579
24782
 
24580
24783
  // src/tools/grep/downloader.ts
24581
24784
  import { existsSync as existsSync32, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync9 } from "fs";
24582
- import { join as join39 } from "path";
24785
+ import { join as join40 } from "path";
24583
24786
  function getInstallDir() {
24584
24787
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
24585
- return join39(homeDir, ".cache", "oh-my-opencode", "bin");
24788
+ return join40(homeDir, ".cache", "oh-my-opencode", "bin");
24586
24789
  }
24587
24790
  function getRgPath() {
24588
24791
  const isWindows2 = process.platform === "win32";
24589
- return join39(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
24792
+ return join40(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
24590
24793
  }
24591
24794
  function getInstalledRipgrepPath() {
24592
24795
  const rgPath = getRgPath();
@@ -24613,10 +24816,10 @@ function getOpenCodeBundledRg() {
24613
24816
  const isWindows2 = process.platform === "win32";
24614
24817
  const rgName = isWindows2 ? "rg.exe" : "rg";
24615
24818
  const candidates = [
24616
- join40(execDir, rgName),
24617
- join40(execDir, "bin", rgName),
24618
- join40(execDir, "..", "bin", rgName),
24619
- join40(execDir, "..", "libexec", rgName)
24819
+ join41(execDir, rgName),
24820
+ join41(execDir, "bin", rgName),
24821
+ join41(execDir, "..", "bin", rgName),
24822
+ join41(execDir, "..", "libexec", rgName)
24620
24823
  ];
24621
24824
  for (const candidate of candidates) {
24622
24825
  if (existsSync33(candidate)) {
@@ -25023,8 +25226,8 @@ var glob = tool({
25023
25226
  });
25024
25227
  // src/tools/slashcommand/tools.ts
25025
25228
  import { existsSync as existsSync34, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
25026
- import { homedir as homedir16 } from "os";
25027
- 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";
25028
25231
  function discoverCommandsFromDir(commandsDir, scope) {
25029
25232
  if (!existsSync34(commandsDir)) {
25030
25233
  return [];
@@ -25034,7 +25237,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
25034
25237
  for (const entry of entries) {
25035
25238
  if (!isMarkdownFile(entry))
25036
25239
  continue;
25037
- const commandPath = join41(commandsDir, entry.name);
25240
+ const commandPath = join42(commandsDir, entry.name);
25038
25241
  const commandName = basename3(entry.name, ".md");
25039
25242
  try {
25040
25243
  const content = readFileSync21(commandPath, "utf-8");
@@ -25062,10 +25265,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
25062
25265
  return commands;
25063
25266
  }
25064
25267
  function discoverCommandsSync() {
25065
- const userCommandsDir = join41(homedir16(), ".claude", "commands");
25066
- const projectCommandsDir = join41(process.cwd(), ".claude", "commands");
25067
- const opencodeGlobalDir = join41(homedir16(), ".config", "opencode", "command");
25068
- 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");
25069
25272
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
25070
25273
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
25071
25274
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
@@ -25198,8 +25401,8 @@ var SkillFrontmatterSchema = exports_external.object({
25198
25401
  });
25199
25402
  // src/tools/skill/tools.ts
25200
25403
  import { existsSync as existsSync35, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
25201
- import { homedir as homedir17 } from "os";
25202
- 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";
25203
25406
  function parseSkillFrontmatter(data) {
25204
25407
  return {
25205
25408
  name: typeof data.name === "string" ? data.name : "",
@@ -25218,10 +25421,10 @@ function discoverSkillsFromDir(skillsDir, scope) {
25218
25421
  for (const entry of entries) {
25219
25422
  if (entry.name.startsWith("."))
25220
25423
  continue;
25221
- const skillPath = join42(skillsDir, entry.name);
25424
+ const skillPath = join43(skillsDir, entry.name);
25222
25425
  if (entry.isDirectory() || entry.isSymbolicLink()) {
25223
25426
  const resolvedPath = resolveSymlink(skillPath);
25224
- const skillMdPath = join42(resolvedPath, "SKILL.md");
25427
+ const skillMdPath = join43(resolvedPath, "SKILL.md");
25225
25428
  if (!existsSync35(skillMdPath))
25226
25429
  continue;
25227
25430
  try {
@@ -25240,8 +25443,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
25240
25443
  return skills;
25241
25444
  }
25242
25445
  function discoverSkillsSync() {
25243
- const userSkillsDir = join42(homedir17(), ".claude", "skills");
25244
- const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
25446
+ const userSkillsDir = join43(homedir19(), ".claude", "skills");
25447
+ const projectSkillsDir = join43(process.cwd(), ".claude", "skills");
25245
25448
  const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
25246
25449
  const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
25247
25450
  return [...projectSkills, ...userSkills];
@@ -25251,7 +25454,7 @@ var skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.descr
25251
25454
  `);
25252
25455
  async function parseSkillMd(skillPath) {
25253
25456
  const resolvedPath = resolveSymlink(skillPath);
25254
- const skillMdPath = join42(resolvedPath, "SKILL.md");
25457
+ const skillMdPath = join43(resolvedPath, "SKILL.md");
25255
25458
  if (!existsSync35(skillMdPath)) {
25256
25459
  return null;
25257
25460
  }
@@ -25267,9 +25470,9 @@ async function parseSkillMd(skillPath) {
25267
25470
  allowedTools: frontmatter2["allowed-tools"],
25268
25471
  metadata: frontmatter2.metadata
25269
25472
  };
25270
- const referencesDir = join42(resolvedPath, "references");
25271
- const scriptsDir = join42(resolvedPath, "scripts");
25272
- const assetsDir = join42(resolvedPath, "assets");
25473
+ const referencesDir = join43(resolvedPath, "references");
25474
+ const scriptsDir = join43(resolvedPath, "scripts");
25475
+ const assetsDir = join43(resolvedPath, "assets");
25273
25476
  const references = existsSync35(referencesDir) ? readdirSync11(referencesDir).filter((f) => !f.startsWith(".")) : [];
25274
25477
  const scripts = existsSync35(scriptsDir) ? readdirSync11(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
25275
25478
  const assets = existsSync35(assetsDir) ? readdirSync11(assetsDir).filter((f) => !f.startsWith(".")) : [];
@@ -25296,7 +25499,7 @@ async function discoverSkillsFromDirAsync(skillsDir) {
25296
25499
  for (const entry of entries) {
25297
25500
  if (entry.name.startsWith("."))
25298
25501
  continue;
25299
- const skillPath = join42(skillsDir, entry.name);
25502
+ const skillPath = join43(skillsDir, entry.name);
25300
25503
  if (entry.isDirectory() || entry.isSymbolicLink()) {
25301
25504
  const skillInfo = await parseSkillMd(skillPath);
25302
25505
  if (skillInfo) {
@@ -25307,8 +25510,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
25307
25510
  return skills;
25308
25511
  }
25309
25512
  async function discoverSkills() {
25310
- const userSkillsDir = join42(homedir17(), ".claude", "skills");
25311
- const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
25513
+ const userSkillsDir = join43(homedir19(), ".claude", "skills");
25514
+ const projectSkillsDir = join43(process.cwd(), ".claude", "skills");
25312
25515
  const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
25313
25516
  const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
25314
25517
  return [...projectSkills, ...userSkills];
@@ -25337,7 +25540,7 @@ async function loadSkillWithReferences(skill, includeRefs) {
25337
25540
  const referencesLoaded = [];
25338
25541
  if (includeRefs && skill.references.length > 0) {
25339
25542
  for (const ref of skill.references) {
25340
- const refPath = join42(skill.path, "references", ref);
25543
+ const refPath = join43(skill.path, "references", ref);
25341
25544
  try {
25342
25545
  let content = readFileSync22(refPath, "utf-8");
25343
25546
  content = await resolveCommandsInText(content);
@@ -25461,12 +25664,12 @@ async function findTmuxPath() {
25461
25664
  return null;
25462
25665
  }
25463
25666
  const stdout = await new Response(proc.stdout).text();
25464
- const path6 = stdout.trim().split(`
25667
+ const path7 = stdout.trim().split(`
25465
25668
  `)[0];
25466
- if (!path6) {
25669
+ if (!path7) {
25467
25670
  return null;
25468
25671
  }
25469
- const verifyProc = spawn9([path6, "-V"], {
25672
+ const verifyProc = spawn9([path7, "-V"], {
25470
25673
  stdout: "pipe",
25471
25674
  stderr: "pipe"
25472
25675
  });
@@ -25474,7 +25677,7 @@ async function findTmuxPath() {
25474
25677
  if (verifyExitCode !== 0) {
25475
25678
  return null;
25476
25679
  }
25477
- return path6;
25680
+ return path7;
25478
25681
  } catch {
25479
25682
  return null;
25480
25683
  }
@@ -25487,9 +25690,9 @@ async function getTmuxPath() {
25487
25690
  return initPromise3;
25488
25691
  }
25489
25692
  initPromise3 = (async () => {
25490
- const path6 = await findTmuxPath();
25491
- tmuxPath = path6;
25492
- return path6;
25693
+ const path7 = await findTmuxPath();
25694
+ tmuxPath = path7;
25695
+ return path7;
25493
25696
  })();
25494
25697
  return initPromise3;
25495
25698
  }
@@ -25809,7 +26012,7 @@ function createBackgroundCancel(manager, client2) {
25809
26012
  return `\u274C Invalid arguments: Either provide a taskId or set all=true to cancel all running tasks.`;
25810
26013
  }
25811
26014
  if (cancelAll) {
25812
- const tasks = manager.getTasksByParentSession(toolContext.sessionID);
26015
+ const tasks = manager.getAllDescendantTasks(toolContext.sessionID);
25813
26016
  const runningTasks = tasks.filter((t) => t.status === "running");
25814
26017
  if (runningTasks.length === 0) {
25815
26018
  return `\u2705 No running background tasks to cancel.`;
@@ -26104,15 +26307,15 @@ var builtinTools = {
26104
26307
  };
26105
26308
  // src/features/background-agent/manager.ts
26106
26309
  import { existsSync as existsSync36, readdirSync as readdirSync12 } from "fs";
26107
- import { join as join43 } from "path";
26310
+ import { join as join44 } from "path";
26108
26311
  function getMessageDir4(sessionID) {
26109
26312
  if (!existsSync36(MESSAGE_STORAGE))
26110
26313
  return null;
26111
- const directPath = join43(MESSAGE_STORAGE, sessionID);
26314
+ const directPath = join44(MESSAGE_STORAGE, sessionID);
26112
26315
  if (existsSync36(directPath))
26113
26316
  return directPath;
26114
26317
  for (const dir of readdirSync12(MESSAGE_STORAGE)) {
26115
- const sessionPath = join43(MESSAGE_STORAGE, dir, sessionID);
26318
+ const sessionPath = join44(MESSAGE_STORAGE, dir, sessionID);
26116
26319
  if (existsSync36(sessionPath))
26117
26320
  return sessionPath;
26118
26321
  }
@@ -26204,6 +26407,16 @@ class BackgroundManager {
26204
26407
  }
26205
26408
  return result;
26206
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
+ }
26207
26420
  findBySession(sessionID) {
26208
26421
  for (const task of this.tasks.values()) {
26209
26422
  if (task.sessionID === sessionID) {
@@ -26495,7 +26708,7 @@ var AgentPermissionSchema = exports_external.object({
26495
26708
  external_directory: PermissionValue.optional()
26496
26709
  });
26497
26710
  var BuiltinAgentNameSchema = exports_external.enum([
26498
- "OmO",
26711
+ "Sisyphus",
26499
26712
  "oracle",
26500
26713
  "librarian",
26501
26714
  "explore",
@@ -26506,8 +26719,8 @@ var BuiltinAgentNameSchema = exports_external.enum([
26506
26719
  var OverridableAgentNameSchema = exports_external.enum([
26507
26720
  "build",
26508
26721
  "plan",
26509
- "OmO",
26510
- "OmO-Plan",
26722
+ "Sisyphus",
26723
+ "Planner-Sisyphus",
26511
26724
  "oracle",
26512
26725
  "librarian",
26513
26726
  "explore",
@@ -26553,8 +26766,8 @@ var AgentOverrideConfigSchema = exports_external.object({
26553
26766
  var AgentOverridesSchema = exports_external.object({
26554
26767
  build: AgentOverrideConfigSchema.optional(),
26555
26768
  plan: AgentOverrideConfigSchema.optional(),
26556
- OmO: AgentOverrideConfigSchema.optional(),
26557
- "OmO-Plan": AgentOverrideConfigSchema.optional(),
26769
+ Sisyphus: AgentOverrideConfigSchema.optional(),
26770
+ "Planner-Sisyphus": AgentOverrideConfigSchema.optional(),
26558
26771
  oracle: AgentOverrideConfigSchema.optional(),
26559
26772
  librarian: AgentOverrideConfigSchema.optional(),
26560
26773
  explore: AgentOverrideConfigSchema.optional(),
@@ -26569,9 +26782,14 @@ var ClaudeCodeConfigSchema = exports_external.object({
26569
26782
  agents: exports_external.boolean().optional(),
26570
26783
  hooks: exports_external.boolean().optional()
26571
26784
  });
26572
- var OmoAgentConfigSchema = exports_external.object({
26785
+ var SisyphusAgentConfigSchema = exports_external.object({
26573
26786
  disabled: exports_external.boolean().optional()
26574
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
+ });
26575
26793
  var OhMyOpenCodeConfigSchema = exports_external.object({
26576
26794
  $schema: exports_external.string().optional(),
26577
26795
  disabled_mcps: exports_external.array(McpNameSchema).optional(),
@@ -26580,7 +26798,8 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
26580
26798
  agents: AgentOverridesSchema.optional(),
26581
26799
  claude_code: ClaudeCodeConfigSchema.optional(),
26582
26800
  google_auth: exports_external.boolean().optional(),
26583
- omo_agent: OmoAgentConfigSchema.optional()
26801
+ sisyphus_agent: SisyphusAgentConfigSchema.optional(),
26802
+ experimental: ExperimentalConfigSchema.optional()
26584
26803
  });
26585
26804
  // src/agents/plan-prompt.ts
26586
26805
  var PLAN_SYSTEM_PROMPT = `<system-reminder>
@@ -26655,16 +26874,14 @@ var PLAN_PERMISSION = {
26655
26874
 
26656
26875
  // src/index.ts
26657
26876
  import * as fs6 from "fs";
26658
- import * as path6 from "path";
26659
- import * as os4 from "os";
26660
- function getUserConfigDir2() {
26661
- if (process.platform === "win32") {
26662
- return process.env.APPDATA || path6.join(os4.homedir(), "AppData", "Roaming");
26663
- }
26664
- return process.env.XDG_CONFIG_HOME || path6.join(os4.homedir(), ".config");
26665
- }
26877
+ import * as path7 from "path";
26666
26878
  var AGENT_NAME_MAP = {
26667
- 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",
26668
26885
  build: "build",
26669
26886
  oracle: "oracle",
26670
26887
  librarian: "librarian",
@@ -26673,32 +26890,63 @@ var AGENT_NAME_MAP = {
26673
26890
  "document-writer": "document-writer",
26674
26891
  "multimodal-looker": "multimodal-looker"
26675
26892
  };
26676
- function normalizeAgentNames(agents) {
26677
- const normalized = {};
26893
+ function migrateAgentNames(agents) {
26894
+ const migrated = {};
26895
+ let changed = false;
26678
26896
  for (const [key, value] of Object.entries(agents)) {
26679
- const normalizedKey = AGENT_NAME_MAP[key.toLowerCase()] ?? key;
26680
- 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;
26681
26902
  }
26682
- return normalized;
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;
26918
+ }
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;
26683
26929
  }
26684
26930
  function loadConfigFromPath2(configPath) {
26685
26931
  try {
26686
26932
  if (fs6.existsSync(configPath)) {
26687
26933
  const content = fs6.readFileSync(configPath, "utf-8");
26688
26934
  const rawConfig = JSON.parse(content);
26689
- if (rawConfig.agents && typeof rawConfig.agents === "object") {
26690
- rawConfig.agents = normalizeAgentNames(rawConfig.agents);
26691
- }
26935
+ migrateConfigFile(configPath, rawConfig);
26692
26936
  const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
26693
26937
  if (!result.success) {
26938
+ const errorMsg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
26694
26939
  log(`Config validation error in ${configPath}:`, result.error.issues);
26940
+ addConfigLoadError({ path: configPath, error: `Validation error: ${errorMsg}` });
26695
26941
  return null;
26696
26942
  }
26697
26943
  log(`Config loaded from ${configPath}`, { agents: result.data.agents });
26698
26944
  return result.data;
26699
26945
  }
26700
26946
  } catch (err) {
26947
+ const errorMsg = err instanceof Error ? err.message : String(err);
26701
26948
  log(`Error loading config from ${configPath}:`, err);
26949
+ addConfigLoadError({ path: configPath, error: errorMsg });
26702
26950
  }
26703
26951
  return null;
26704
26952
  }
@@ -26729,8 +26977,8 @@ function mergeConfigs(base, override) {
26729
26977
  };
26730
26978
  }
26731
26979
  function loadPluginConfig(directory) {
26732
- const userConfigPath = path6.join(getUserConfigDir2(), "opencode", "oh-my-opencode.json");
26733
- 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");
26734
26982
  let config3 = loadConfigFromPath2(userConfigPath) ?? {};
26735
26983
  const projectConfig = loadConfigFromPath2(projectConfigPath);
26736
26984
  if (projectConfig) {
@@ -26751,7 +26999,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
26751
26999
  const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
26752
27000
  const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx) : null;
26753
27001
  const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
26754
- const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx) : null;
27002
+ const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
26755
27003
  const sessionNotification = isHookEnabled("session-notification") ? createSessionNotification(ctx) : null;
26756
27004
  if (sessionRecovery && todoContinuationEnforcer) {
26757
27005
  sessionRecovery.setOnAbortCallback(todoContinuationEnforcer.markRecovering);
@@ -26766,10 +27014,11 @@ var OhMyOpenCodePlugin = async (ctx) => {
26766
27014
  const claudeCodeHooks = createClaudeCodeHooksHook(ctx, {
26767
27015
  disabledHooks: pluginConfig.claude_code?.hooks ?? true ? undefined : true
26768
27016
  });
26769
- const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx) : null;
27017
+ const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx, { experimental: pluginConfig.experimental }) : null;
26770
27018
  const rulesInjector = isHookEnabled("rules-injector") ? createRulesInjectorHook(ctx) : null;
26771
27019
  const autoUpdateChecker = isHookEnabled("auto-update-checker") ? createAutoUpdateCheckerHook(ctx, {
26772
- showStartupToast: isHookEnabled("startup-toast")
27020
+ showStartupToast: isHookEnabled("startup-toast"),
27021
+ isSisyphusEnabled: pluginConfig.sisyphus_agent?.disabled !== true
26773
27022
  }) : null;
26774
27023
  const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook() : null;
26775
27024
  const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
@@ -26803,22 +27052,22 @@ var OhMyOpenCodePlugin = async (ctx) => {
26803
27052
  const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents, ctx.directory, config3.model);
26804
27053
  const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
26805
27054
  const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
26806
- const isOmoEnabled = pluginConfig.omo_agent?.disabled !== true;
26807
- if (isOmoEnabled && builtinAgents.OmO) {
27055
+ const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
27056
+ if (isSisyphusEnabled && builtinAgents.Sisyphus) {
26808
27057
  const { name: _planName, ...planConfigWithoutName } = config3.agent?.plan ?? {};
26809
- const omoPlanOverride = pluginConfig.agents?.["OmO-Plan"];
26810
- const omoPlanBase = {
27058
+ const plannerSisyphusOverride = pluginConfig.agents?.["Planner-Sisyphus"];
27059
+ const plannerSisyphusBase = {
26811
27060
  ...planConfigWithoutName,
26812
27061
  prompt: PLAN_SYSTEM_PROMPT,
26813
27062
  permission: PLAN_PERMISSION,
26814
27063
  description: `${config3.agent?.plan?.description ?? "Plan agent"} (OhMyOpenCode version)`,
26815
27064
  color: config3.agent?.plan?.color ?? "#6495ED"
26816
27065
  };
26817
- const omoPlanConfig = omoPlanOverride ? { ...omoPlanBase, ...omoPlanOverride } : omoPlanBase;
27066
+ const plannerSisyphusConfig = plannerSisyphusOverride ? { ...plannerSisyphusBase, ...plannerSisyphusOverride } : plannerSisyphusBase;
26818
27067
  config3.agent = {
26819
- OmO: builtinAgents.OmO,
26820
- "OmO-Plan": omoPlanConfig,
26821
- ...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")),
26822
27071
  ...userAgents,
26823
27072
  ...projectAgents,
26824
27073
  ...config3.agent,