oh-my-opencode 2.5.2 → 2.5.4

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 = (path4, { windows } = {}) => {
226
- const segs = path4.split(windows ? /[\\/]/ : "/");
225
+ exports.basename = (path5, { windows } = {}) => {
226
+ const segs = path5.split(windows ? /[\\/]/ : "/");
227
227
  const last = segs[segs.length - 1];
228
228
  if (last === "") {
229
229
  return segs[segs.length - 2];
@@ -3216,7 +3216,7 @@ async function getContextWindowUsage(ctx, sessionID) {
3216
3216
  return null;
3217
3217
  const lastAssistant = assistantMessages[assistantMessages.length - 1];
3218
3218
  const lastTokens = lastAssistant.tokens;
3219
- const usedTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0);
3219
+ const usedTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0) + (lastTokens?.output ?? 0);
3220
3220
  const remainingTokens = ANTHROPIC_ACTUAL_LIMIT - usedTokens;
3221
3221
  return {
3222
3222
  usedTokens,
@@ -3269,6 +3269,18 @@ function getUserConfigDir() {
3269
3269
  }
3270
3270
  return process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
3271
3271
  }
3272
+ // src/shared/data-path.ts
3273
+ import * as path3 from "path";
3274
+ import * as os3 from "os";
3275
+ function getDataDir() {
3276
+ if (process.platform === "win32") {
3277
+ return process.env.LOCALAPPDATA ?? path3.join(os3.homedir(), "AppData", "Local");
3278
+ }
3279
+ return process.env.XDG_DATA_HOME ?? path3.join(os3.homedir(), ".local", "share");
3280
+ }
3281
+ function getOpenCodeStorageDir() {
3282
+ return path3.join(getDataDir(), "opencode", "storage");
3283
+ }
3272
3284
  // src/shared/config-errors.ts
3273
3285
  var configLoadErrors = [];
3274
3286
  function getConfigLoadErrors() {
@@ -3356,7 +3368,7 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
3356
3368
  }
3357
3369
  // src/hooks/todo-continuation-enforcer.ts
3358
3370
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
3359
- import { join as join6 } from "path";
3371
+ import { join as join7 } from "path";
3360
3372
 
3361
3373
  // src/features/claude-code-session-state/state.ts
3362
3374
  var subagentSessions = new Set;
@@ -3369,15 +3381,13 @@ function getMainSessionID() {
3369
3381
  }
3370
3382
  // src/features/hook-message-injector/injector.ts
3371
3383
  import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
3372
- import { join as join5 } from "path";
3384
+ import { join as join6 } from "path";
3373
3385
 
3374
3386
  // src/features/hook-message-injector/constants.ts
3375
- import { join as join4 } from "path";
3376
- import { homedir as homedir3 } from "os";
3377
- var xdgData = process.env.XDG_DATA_HOME || join4(homedir3(), ".local", "share");
3378
- var OPENCODE_STORAGE = join4(xdgData, "opencode", "storage");
3379
- var MESSAGE_STORAGE = join4(OPENCODE_STORAGE, "message");
3380
- var PART_STORAGE = join4(OPENCODE_STORAGE, "part");
3387
+ import { join as join5 } from "path";
3388
+ var OPENCODE_STORAGE = getOpenCodeStorageDir();
3389
+ var MESSAGE_STORAGE = join5(OPENCODE_STORAGE, "message");
3390
+ var PART_STORAGE = join5(OPENCODE_STORAGE, "part");
3381
3391
 
3382
3392
  // src/features/hook-message-injector/injector.ts
3383
3393
  function findNearestMessageWithFields(messageDir) {
@@ -3385,7 +3395,7 @@ function findNearestMessageWithFields(messageDir) {
3385
3395
  const files = readdirSync(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
3386
3396
  for (const file of files) {
3387
3397
  try {
3388
- const content = readFileSync2(join5(messageDir, file), "utf-8");
3398
+ const content = readFileSync2(join6(messageDir, file), "utf-8");
3389
3399
  const msg = JSON.parse(content);
3390
3400
  if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
3391
3401
  return msg;
@@ -3413,12 +3423,12 @@ function getOrCreateMessageDir(sessionID) {
3413
3423
  if (!existsSync4(MESSAGE_STORAGE)) {
3414
3424
  mkdirSync(MESSAGE_STORAGE, { recursive: true });
3415
3425
  }
3416
- const directPath = join5(MESSAGE_STORAGE, sessionID);
3426
+ const directPath = join6(MESSAGE_STORAGE, sessionID);
3417
3427
  if (existsSync4(directPath)) {
3418
3428
  return directPath;
3419
3429
  }
3420
3430
  for (const dir of readdirSync(MESSAGE_STORAGE)) {
3421
- const sessionPath = join5(MESSAGE_STORAGE, dir, sessionID);
3431
+ const sessionPath = join6(MESSAGE_STORAGE, dir, sessionID);
3422
3432
  if (existsSync4(sessionPath)) {
3423
3433
  return sessionPath;
3424
3434
  }
@@ -3472,12 +3482,12 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
3472
3482
  sessionID
3473
3483
  };
3474
3484
  try {
3475
- writeFileSync(join5(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3476
- const partDir = join5(PART_STORAGE, messageID);
3485
+ writeFileSync(join6(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3486
+ const partDir = join6(PART_STORAGE, messageID);
3477
3487
  if (!existsSync4(partDir)) {
3478
3488
  mkdirSync(partDir, { recursive: true });
3479
3489
  }
3480
- writeFileSync(join5(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3490
+ writeFileSync(join6(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3481
3491
  return true;
3482
3492
  } catch {
3483
3493
  return false;
@@ -3512,11 +3522,11 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
3512
3522
  function getMessageDir(sessionID) {
3513
3523
  if (!existsSync5(MESSAGE_STORAGE))
3514
3524
  return null;
3515
- const directPath = join6(MESSAGE_STORAGE, sessionID);
3525
+ const directPath = join7(MESSAGE_STORAGE, sessionID);
3516
3526
  if (existsSync5(directPath))
3517
3527
  return directPath;
3518
3528
  for (const dir of readdirSync2(MESSAGE_STORAGE)) {
3519
- const sessionPath = join6(MESSAGE_STORAGE, dir, sessionID);
3529
+ const sessionPath = join7(MESSAGE_STORAGE, dir, sessionID);
3520
3530
  if (existsSync5(sessionPath))
3521
3531
  return sessionPath;
3522
3532
  }
@@ -3653,6 +3663,24 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
3653
3663
  errorSessions.delete(sessionID);
3654
3664
  return;
3655
3665
  }
3666
+ let freshTodos = [];
3667
+ try {
3668
+ log(`[${HOOK_NAME}] Re-verifying todos after countdown`, { sessionID });
3669
+ const response = await ctx.client.session.todo({
3670
+ path: { id: sessionID }
3671
+ });
3672
+ freshTodos = response.data ?? response;
3673
+ log(`[${HOOK_NAME}] Fresh todo count`, { sessionID, todosCount: freshTodos?.length ?? 0 });
3674
+ } catch (err) {
3675
+ log(`[${HOOK_NAME}] Failed to re-verify todos`, { sessionID, error: String(err) });
3676
+ return;
3677
+ }
3678
+ const freshIncomplete = freshTodos.filter((t) => t.status !== "completed" && t.status !== "cancelled");
3679
+ if (freshIncomplete.length === 0) {
3680
+ log(`[${HOOK_NAME}] Abort: no incomplete todos after countdown`, { sessionID, total: freshTodos.length });
3681
+ return;
3682
+ }
3683
+ log(`[${HOOK_NAME}] Confirmed incomplete todos, proceeding with injection`, { sessionID, incomplete: freshIncomplete.length, total: freshTodos.length });
3656
3684
  remindedSessions.add(sessionID);
3657
3685
  try {
3658
3686
  const messageDir = getMessageDir(sessionID);
@@ -3673,7 +3701,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
3673
3701
  type: "text",
3674
3702
  text: `${CONTINUATION_PROMPT}
3675
3703
 
3676
- [Status: ${todos.length - incomplete.length}/${todos.length} completed, ${incomplete.length} remaining]`
3704
+ [Status: ${freshTodos.length - freshIncomplete.length}/${freshTodos.length} completed, ${freshIncomplete.length} remaining]`
3677
3705
  }
3678
3706
  ]
3679
3707
  },
@@ -3723,7 +3751,8 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
3723
3751
  }
3724
3752
  if (sessionID && role === "assistant" && finish) {
3725
3753
  remindedSessions.delete(sessionID);
3726
- log(`[${HOOK_NAME}] Cleared reminded state on assistant finish`, { sessionID });
3754
+ preemptivelyInjectedSessions.delete(sessionID);
3755
+ log(`[${HOOK_NAME}] Cleared reminded/preemptive state on assistant finish`, { sessionID });
3727
3756
  const isTerminalFinish = finish && !["tool-calls", "unknown"].includes(finish);
3728
3757
  if (isTerminalFinish && isNonInteractive()) {
3729
3758
  log(`[${HOOK_NAME}] Terminal finish in non-interactive mode`, { sessionID, finish });
@@ -4090,24 +4119,24 @@ function createSessionNotification(ctx, config = {}) {
4090
4119
  }
4091
4120
  // src/hooks/session-recovery/storage.ts
4092
4121
  import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
4093
- import { join as join8 } from "path";
4122
+ import { join as join9 } from "path";
4094
4123
 
4095
4124
  // src/hooks/session-recovery/constants.ts
4096
- import { join as join7 } from "path";
4125
+ import { join as join8 } from "path";
4097
4126
 
4098
4127
  // node_modules/xdg-basedir/index.js
4099
- import os3 from "os";
4100
- import path3 from "path";
4101
- var homeDirectory = os3.homedir();
4128
+ import os4 from "os";
4129
+ import path4 from "path";
4130
+ var homeDirectory = os4.homedir();
4102
4131
  var { env } = process;
4103
- var xdgData2 = env.XDG_DATA_HOME || (homeDirectory ? path3.join(homeDirectory, ".local", "share") : undefined);
4104
- var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path3.join(homeDirectory, ".config") : undefined);
4105
- var xdgState = env.XDG_STATE_HOME || (homeDirectory ? path3.join(homeDirectory, ".local", "state") : undefined);
4106
- var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ? path3.join(homeDirectory, ".cache") : undefined);
4132
+ var xdgData = env.XDG_DATA_HOME || (homeDirectory ? path4.join(homeDirectory, ".local", "share") : undefined);
4133
+ var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path4.join(homeDirectory, ".config") : undefined);
4134
+ var xdgState = env.XDG_STATE_HOME || (homeDirectory ? path4.join(homeDirectory, ".local", "state") : undefined);
4135
+ var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ? path4.join(homeDirectory, ".cache") : undefined);
4107
4136
  var xdgRuntime = env.XDG_RUNTIME_DIR || undefined;
4108
4137
  var xdgDataDirectories = (env.XDG_DATA_DIRS || "/usr/local/share/:/usr/share/").split(":");
4109
- if (xdgData2) {
4110
- xdgDataDirectories.unshift(xdgData2);
4138
+ if (xdgData) {
4139
+ xdgDataDirectories.unshift(xdgData);
4111
4140
  }
4112
4141
  var xdgConfigDirectories = (env.XDG_CONFIG_DIRS || "/etc/xdg").split(":");
4113
4142
  if (xdgConfig) {
@@ -4115,9 +4144,9 @@ if (xdgConfig) {
4115
4144
  }
4116
4145
 
4117
4146
  // src/hooks/session-recovery/constants.ts
4118
- var OPENCODE_STORAGE2 = join7(xdgData2 ?? "", "opencode", "storage");
4119
- var MESSAGE_STORAGE2 = join7(OPENCODE_STORAGE2, "message");
4120
- var PART_STORAGE2 = join7(OPENCODE_STORAGE2, "part");
4147
+ var OPENCODE_STORAGE2 = join8(xdgData ?? "", "opencode", "storage");
4148
+ var MESSAGE_STORAGE2 = join8(OPENCODE_STORAGE2, "message");
4149
+ var PART_STORAGE2 = join8(OPENCODE_STORAGE2, "part");
4121
4150
  var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
4122
4151
  var META_TYPES = new Set(["step-start", "step-finish"]);
4123
4152
  var CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"]);
@@ -4131,12 +4160,12 @@ function generatePartId2() {
4131
4160
  function getMessageDir2(sessionID) {
4132
4161
  if (!existsSync6(MESSAGE_STORAGE2))
4133
4162
  return "";
4134
- const directPath = join8(MESSAGE_STORAGE2, sessionID);
4163
+ const directPath = join9(MESSAGE_STORAGE2, sessionID);
4135
4164
  if (existsSync6(directPath)) {
4136
4165
  return directPath;
4137
4166
  }
4138
4167
  for (const dir of readdirSync3(MESSAGE_STORAGE2)) {
4139
- const sessionPath = join8(MESSAGE_STORAGE2, dir, sessionID);
4168
+ const sessionPath = join9(MESSAGE_STORAGE2, dir, sessionID);
4140
4169
  if (existsSync6(sessionPath)) {
4141
4170
  return sessionPath;
4142
4171
  }
@@ -4152,7 +4181,7 @@ function readMessages(sessionID) {
4152
4181
  if (!file.endsWith(".json"))
4153
4182
  continue;
4154
4183
  try {
4155
- const content = readFileSync3(join8(messageDir, file), "utf-8");
4184
+ const content = readFileSync3(join9(messageDir, file), "utf-8");
4156
4185
  messages.push(JSON.parse(content));
4157
4186
  } catch {
4158
4187
  continue;
@@ -4167,7 +4196,7 @@ function readMessages(sessionID) {
4167
4196
  });
4168
4197
  }
4169
4198
  function readParts(messageID) {
4170
- const partDir = join8(PART_STORAGE2, messageID);
4199
+ const partDir = join9(PART_STORAGE2, messageID);
4171
4200
  if (!existsSync6(partDir))
4172
4201
  return [];
4173
4202
  const parts = [];
@@ -4175,7 +4204,7 @@ function readParts(messageID) {
4175
4204
  if (!file.endsWith(".json"))
4176
4205
  continue;
4177
4206
  try {
4178
- const content = readFileSync3(join8(partDir, file), "utf-8");
4207
+ const content = readFileSync3(join9(partDir, file), "utf-8");
4179
4208
  parts.push(JSON.parse(content));
4180
4209
  } catch {
4181
4210
  continue;
@@ -4205,7 +4234,7 @@ function messageHasContent(messageID) {
4205
4234
  return parts.some(hasContent);
4206
4235
  }
4207
4236
  function injectTextPart(sessionID, messageID, text) {
4208
- const partDir = join8(PART_STORAGE2, messageID);
4237
+ const partDir = join9(PART_STORAGE2, messageID);
4209
4238
  if (!existsSync6(partDir)) {
4210
4239
  mkdirSync2(partDir, { recursive: true });
4211
4240
  }
@@ -4219,7 +4248,7 @@ function injectTextPart(sessionID, messageID, text) {
4219
4248
  synthetic: true
4220
4249
  };
4221
4250
  try {
4222
- writeFileSync2(join8(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
4251
+ writeFileSync2(join9(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
4223
4252
  return true;
4224
4253
  } catch {
4225
4254
  return false;
@@ -4282,7 +4311,7 @@ function findMessagesWithOrphanThinking(sessionID) {
4282
4311
  return result;
4283
4312
  }
4284
4313
  function prependThinkingPart(sessionID, messageID) {
4285
- const partDir = join8(PART_STORAGE2, messageID);
4314
+ const partDir = join9(PART_STORAGE2, messageID);
4286
4315
  if (!existsSync6(partDir)) {
4287
4316
  mkdirSync2(partDir, { recursive: true });
4288
4317
  }
@@ -4296,14 +4325,14 @@ function prependThinkingPart(sessionID, messageID) {
4296
4325
  synthetic: true
4297
4326
  };
4298
4327
  try {
4299
- writeFileSync2(join8(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
4328
+ writeFileSync2(join9(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
4300
4329
  return true;
4301
4330
  } catch {
4302
4331
  return false;
4303
4332
  }
4304
4333
  }
4305
4334
  function stripThinkingParts(messageID) {
4306
- const partDir = join8(PART_STORAGE2, messageID);
4335
+ const partDir = join9(PART_STORAGE2, messageID);
4307
4336
  if (!existsSync6(partDir))
4308
4337
  return false;
4309
4338
  let anyRemoved = false;
@@ -4311,7 +4340,7 @@ function stripThinkingParts(messageID) {
4311
4340
  if (!file.endsWith(".json"))
4312
4341
  continue;
4313
4342
  try {
4314
- const filePath = join8(partDir, file);
4343
+ const filePath = join9(partDir, file);
4315
4344
  const content = readFileSync3(filePath, "utf-8");
4316
4345
  const part = JSON.parse(content);
4317
4346
  if (THINKING_TYPES.has(part.type)) {
@@ -4325,7 +4354,7 @@ function stripThinkingParts(messageID) {
4325
4354
  return anyRemoved;
4326
4355
  }
4327
4356
  function replaceEmptyTextParts(messageID, replacementText) {
4328
- const partDir = join8(PART_STORAGE2, messageID);
4357
+ const partDir = join9(PART_STORAGE2, messageID);
4329
4358
  if (!existsSync6(partDir))
4330
4359
  return false;
4331
4360
  let anyReplaced = false;
@@ -4333,7 +4362,7 @@ function replaceEmptyTextParts(messageID, replacementText) {
4333
4362
  if (!file.endsWith(".json"))
4334
4363
  continue;
4335
4364
  try {
4336
- const filePath = join8(partDir, file);
4365
+ const filePath = join9(partDir, file);
4337
4366
  const content = readFileSync3(filePath, "utf-8");
4338
4367
  const part = JSON.parse(content);
4339
4368
  if (part.type === "text") {
@@ -4610,7 +4639,7 @@ function createSessionRecoveryHook(ctx, options) {
4610
4639
  // src/hooks/comment-checker/cli.ts
4611
4640
  var {spawn: spawn3 } = globalThis.Bun;
4612
4641
  import { createRequire as createRequire2 } from "module";
4613
- import { dirname, join as join10 } from "path";
4642
+ import { dirname, join as join11 } from "path";
4614
4643
  import { existsSync as existsSync8 } from "fs";
4615
4644
  import * as fs3 from "fs";
4616
4645
  import { tmpdir as tmpdir3 } from "os";
@@ -4618,11 +4647,11 @@ import { tmpdir as tmpdir3 } from "os";
4618
4647
  // src/hooks/comment-checker/downloader.ts
4619
4648
  var {spawn: spawn2 } = globalThis.Bun;
4620
4649
  import { existsSync as existsSync7, mkdirSync as mkdirSync3, chmodSync, unlinkSync as unlinkSync2, appendFileSync as appendFileSync2 } from "fs";
4621
- import { join as join9 } from "path";
4650
+ import { join as join10 } from "path";
4622
4651
  import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
4623
4652
  import { createRequire } from "module";
4624
4653
  var DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1";
4625
- var DEBUG_FILE = join9(tmpdir2(), "comment-checker-debug.log");
4654
+ var DEBUG_FILE = join10(tmpdir2(), "comment-checker-debug.log");
4626
4655
  function debugLog(...args) {
4627
4656
  if (DEBUG) {
4628
4657
  const msg = `[${new Date().toISOString()}] [comment-checker:downloader] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
@@ -4640,14 +4669,14 @@ var PLATFORM_MAP = {
4640
4669
  };
4641
4670
  function getCacheDir() {
4642
4671
  const xdgCache2 = process.env.XDG_CACHE_HOME;
4643
- const base = xdgCache2 || join9(homedir4(), ".cache");
4644
- return join9(base, "oh-my-opencode", "bin");
4672
+ const base = xdgCache2 || join10(homedir4(), ".cache");
4673
+ return join10(base, "oh-my-opencode", "bin");
4645
4674
  }
4646
4675
  function getBinaryName() {
4647
4676
  return process.platform === "win32" ? "comment-checker.exe" : "comment-checker";
4648
4677
  }
4649
4678
  function getCachedBinaryPath() {
4650
- const binaryPath = join9(getCacheDir(), getBinaryName());
4679
+ const binaryPath = join10(getCacheDir(), getBinaryName());
4651
4680
  return existsSync7(binaryPath) ? binaryPath : null;
4652
4681
  }
4653
4682
  function getPackageVersion() {
@@ -4695,14 +4724,14 @@ async function downloadCommentChecker() {
4695
4724
  }
4696
4725
  const cacheDir = getCacheDir();
4697
4726
  const binaryName = getBinaryName();
4698
- const binaryPath = join9(cacheDir, binaryName);
4727
+ const binaryPath = join10(cacheDir, binaryName);
4699
4728
  if (existsSync7(binaryPath)) {
4700
4729
  debugLog("Binary already cached at:", binaryPath);
4701
4730
  return binaryPath;
4702
4731
  }
4703
4732
  const version = getPackageVersion();
4704
- const { os: os4, arch, ext } = platformInfo;
4705
- const assetName = `comment-checker_v${version}_${os4}_${arch}.${ext}`;
4733
+ const { os: os5, arch, ext } = platformInfo;
4734
+ const assetName = `comment-checker_v${version}_${os5}_${arch}.${ext}`;
4706
4735
  const downloadUrl = `https://github.com/${REPO}/releases/download/v${version}/${assetName}`;
4707
4736
  debugLog(`Downloading from: ${downloadUrl}`);
4708
4737
  console.log(`[oh-my-opencode] Downloading comment-checker binary...`);
@@ -4714,7 +4743,7 @@ async function downloadCommentChecker() {
4714
4743
  if (!response.ok) {
4715
4744
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
4716
4745
  }
4717
- const archivePath = join9(cacheDir, assetName);
4746
+ const archivePath = join10(cacheDir, assetName);
4718
4747
  const arrayBuffer = await response.arrayBuffer();
4719
4748
  await Bun.write(archivePath, arrayBuffer);
4720
4749
  debugLog(`Downloaded archive to: ${archivePath}`);
@@ -4750,7 +4779,7 @@ async function ensureCommentCheckerBinary() {
4750
4779
 
4751
4780
  // src/hooks/comment-checker/cli.ts
4752
4781
  var DEBUG2 = process.env.COMMENT_CHECKER_DEBUG === "1";
4753
- var DEBUG_FILE2 = join10(tmpdir3(), "comment-checker-debug.log");
4782
+ var DEBUG_FILE2 = join11(tmpdir3(), "comment-checker-debug.log");
4754
4783
  function debugLog2(...args) {
4755
4784
  if (DEBUG2) {
4756
4785
  const msg = `[${new Date().toISOString()}] [comment-checker:cli] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
@@ -4767,7 +4796,7 @@ function findCommentCheckerPathSync() {
4767
4796
  const require2 = createRequire2(import.meta.url);
4768
4797
  const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
4769
4798
  const cliDir = dirname(cliPkgPath);
4770
- const binaryPath = join10(cliDir, "bin", binaryName);
4799
+ const binaryPath = join11(cliDir, "bin", binaryName);
4771
4800
  if (existsSync8(binaryPath)) {
4772
4801
  debugLog2("found binary in main package:", binaryPath);
4773
4802
  return binaryPath;
@@ -4814,8 +4843,8 @@ async function getCommentCheckerPath() {
4814
4843
  function startBackgroundInit() {
4815
4844
  if (!initPromise) {
4816
4845
  initPromise = getCommentCheckerPath();
4817
- initPromise.then((path4) => {
4818
- debugLog2("background init complete:", path4 || "no binary");
4846
+ initPromise.then((path5) => {
4847
+ debugLog2("background init complete:", path5 || "no binary");
4819
4848
  }).catch((err) => {
4820
4849
  debugLog2("background init error:", err);
4821
4850
  });
@@ -4864,9 +4893,9 @@ async function runCommentChecker(input, cliPath) {
4864
4893
  import * as fs4 from "fs";
4865
4894
  import { existsSync as existsSync9 } from "fs";
4866
4895
  import { tmpdir as tmpdir4 } from "os";
4867
- import { join as join11 } from "path";
4896
+ import { join as join12 } from "path";
4868
4897
  var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
4869
- var DEBUG_FILE3 = join11(tmpdir4(), "comment-checker-debug.log");
4898
+ var DEBUG_FILE3 = join12(tmpdir4(), "comment-checker-debug.log");
4870
4899
  function debugLog3(...args) {
4871
4900
  if (DEBUG3) {
4872
4901
  const msg = `[${new Date().toISOString()}] [comment-checker:hook] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
@@ -4890,8 +4919,8 @@ function createCommentCheckerHooks() {
4890
4919
  debugLog3("createCommentCheckerHooks called");
4891
4920
  startBackgroundInit();
4892
4921
  cliPathPromise = getCommentCheckerPath();
4893
- cliPathPromise.then((path4) => {
4894
- debugLog3("CLI path resolved:", path4 || "disabled (no binary)");
4922
+ cliPathPromise.then((path5) => {
4923
+ debugLog3("CLI path resolved:", path5 || "disabled (no binary)");
4895
4924
  }).catch((err) => {
4896
4925
  debugLog3("CLI path resolution error:", err);
4897
4926
  });
@@ -4998,7 +5027,7 @@ var TRUNCATABLE_TOOLS = [
4998
5027
  ];
4999
5028
  function createToolOutputTruncatorHook(ctx, options) {
5000
5029
  const truncator = createDynamicTruncator(ctx);
5001
- const truncateAll = options?.experimental?.truncate_all_tool_outputs ?? false;
5030
+ const truncateAll = options?.experimental?.truncate_all_tool_outputs ?? true;
5002
5031
  const toolExecuteAfter = async (input, output) => {
5003
5032
  if (!truncateAll && !TRUNCATABLE_TOOLS.includes(input.tool))
5004
5033
  return;
@@ -5015,7 +5044,7 @@ function createToolOutputTruncatorHook(ctx, options) {
5015
5044
  }
5016
5045
  // src/hooks/directory-agents-injector/index.ts
5017
5046
  import { existsSync as existsSync11, readFileSync as readFileSync5 } from "fs";
5018
- import { dirname as dirname2, join as join14, resolve as resolve2 } from "path";
5047
+ import { dirname as dirname2, join as join15, resolve as resolve2 } from "path";
5019
5048
 
5020
5049
  // src/hooks/directory-agents-injector/storage.ts
5021
5050
  import {
@@ -5025,17 +5054,17 @@ import {
5025
5054
  writeFileSync as writeFileSync3,
5026
5055
  unlinkSync as unlinkSync3
5027
5056
  } from "fs";
5028
- import { join as join13 } from "path";
5057
+ import { join as join14 } from "path";
5029
5058
 
5030
5059
  // src/hooks/directory-agents-injector/constants.ts
5031
- import { join as join12 } from "path";
5032
- var OPENCODE_STORAGE3 = join12(xdgData2 ?? "", "opencode", "storage");
5033
- var AGENTS_INJECTOR_STORAGE = join12(OPENCODE_STORAGE3, "directory-agents");
5060
+ import { join as join13 } from "path";
5061
+ var OPENCODE_STORAGE3 = join13(xdgData ?? "", "opencode", "storage");
5062
+ var AGENTS_INJECTOR_STORAGE = join13(OPENCODE_STORAGE3, "directory-agents");
5034
5063
  var AGENTS_FILENAME = "AGENTS.md";
5035
5064
 
5036
5065
  // src/hooks/directory-agents-injector/storage.ts
5037
5066
  function getStoragePath(sessionID) {
5038
- return join13(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
5067
+ return join14(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
5039
5068
  }
5040
5069
  function loadInjectedPaths(sessionID) {
5041
5070
  const filePath = getStoragePath(sessionID);
@@ -5071,24 +5100,25 @@ function clearInjectedPaths(sessionID) {
5071
5100
  function createDirectoryAgentsInjectorHook(ctx) {
5072
5101
  const sessionCaches = new Map;
5073
5102
  const pendingBatchReads = new Map;
5103
+ const truncator = createDynamicTruncator(ctx);
5074
5104
  function getSessionCache(sessionID) {
5075
5105
  if (!sessionCaches.has(sessionID)) {
5076
5106
  sessionCaches.set(sessionID, loadInjectedPaths(sessionID));
5077
5107
  }
5078
5108
  return sessionCaches.get(sessionID);
5079
5109
  }
5080
- function resolveFilePath2(path4) {
5081
- if (!path4)
5110
+ function resolveFilePath2(path5) {
5111
+ if (!path5)
5082
5112
  return null;
5083
- if (path4.startsWith("/"))
5084
- return path4;
5085
- return resolve2(ctx.directory, path4);
5113
+ if (path5.startsWith("/"))
5114
+ return path5;
5115
+ return resolve2(ctx.directory, path5);
5086
5116
  }
5087
5117
  function findAgentsMdUp(startDir) {
5088
5118
  const found = [];
5089
5119
  let current = startDir;
5090
5120
  while (true) {
5091
- const agentsPath = join14(current, AGENTS_FILENAME);
5121
+ const agentsPath = join15(current, AGENTS_FILENAME);
5092
5122
  if (existsSync11(agentsPath)) {
5093
5123
  found.push(agentsPath);
5094
5124
  }
@@ -5103,7 +5133,7 @@ function createDirectoryAgentsInjectorHook(ctx) {
5103
5133
  }
5104
5134
  return found.reverse();
5105
5135
  }
5106
- function processFilePathForInjection(filePath, sessionID, output) {
5136
+ async function processFilePathForInjection(filePath, sessionID, output) {
5107
5137
  const resolved = resolveFilePath2(filePath);
5108
5138
  if (!resolved)
5109
5139
  return;
@@ -5116,10 +5146,14 @@ function createDirectoryAgentsInjectorHook(ctx) {
5116
5146
  continue;
5117
5147
  try {
5118
5148
  const content = readFileSync5(agentsPath, "utf-8");
5149
+ const { result, truncated } = await truncator.truncate(sessionID, content);
5150
+ const truncationNotice = truncated ? `
5151
+
5152
+ [Note: Content was truncated to save context window space. For full context, please read the file directly: ${agentsPath}]` : "";
5119
5153
  output.output += `
5120
5154
 
5121
5155
  [Directory Context: ${agentsPath}]
5122
- ${content}`;
5156
+ ${result}${truncationNotice}`;
5123
5157
  cache.add(agentsDir);
5124
5158
  } catch {}
5125
5159
  }
@@ -5144,14 +5178,14 @@ ${content}`;
5144
5178
  const toolExecuteAfter = async (input, output) => {
5145
5179
  const toolName = input.tool.toLowerCase();
5146
5180
  if (toolName === "read") {
5147
- processFilePathForInjection(output.title, input.sessionID, output);
5181
+ await processFilePathForInjection(output.title, input.sessionID, output);
5148
5182
  return;
5149
5183
  }
5150
5184
  if (toolName === "batch") {
5151
5185
  const filePaths = pendingBatchReads.get(input.callID);
5152
5186
  if (filePaths) {
5153
5187
  for (const filePath of filePaths) {
5154
- processFilePathForInjection(filePath, input.sessionID, output);
5188
+ await processFilePathForInjection(filePath, input.sessionID, output);
5155
5189
  }
5156
5190
  pendingBatchReads.delete(input.callID);
5157
5191
  }
@@ -5182,7 +5216,7 @@ ${content}`;
5182
5216
  }
5183
5217
  // src/hooks/directory-readme-injector/index.ts
5184
5218
  import { existsSync as existsSync13, readFileSync as readFileSync7 } from "fs";
5185
- import { dirname as dirname3, join as join17, resolve as resolve3 } from "path";
5219
+ import { dirname as dirname3, join as join18, resolve as resolve3 } from "path";
5186
5220
 
5187
5221
  // src/hooks/directory-readme-injector/storage.ts
5188
5222
  import {
@@ -5192,17 +5226,17 @@ import {
5192
5226
  writeFileSync as writeFileSync4,
5193
5227
  unlinkSync as unlinkSync4
5194
5228
  } from "fs";
5195
- import { join as join16 } from "path";
5229
+ import { join as join17 } from "path";
5196
5230
 
5197
5231
  // src/hooks/directory-readme-injector/constants.ts
5198
- import { join as join15 } from "path";
5199
- var OPENCODE_STORAGE4 = join15(xdgData2 ?? "", "opencode", "storage");
5200
- var README_INJECTOR_STORAGE = join15(OPENCODE_STORAGE4, "directory-readme");
5232
+ import { join as join16 } from "path";
5233
+ var OPENCODE_STORAGE4 = join16(xdgData ?? "", "opencode", "storage");
5234
+ var README_INJECTOR_STORAGE = join16(OPENCODE_STORAGE4, "directory-readme");
5201
5235
  var README_FILENAME = "README.md";
5202
5236
 
5203
5237
  // src/hooks/directory-readme-injector/storage.ts
5204
5238
  function getStoragePath2(sessionID) {
5205
- return join16(README_INJECTOR_STORAGE, `${sessionID}.json`);
5239
+ return join17(README_INJECTOR_STORAGE, `${sessionID}.json`);
5206
5240
  }
5207
5241
  function loadInjectedPaths2(sessionID) {
5208
5242
  const filePath = getStoragePath2(sessionID);
@@ -5238,24 +5272,25 @@ function clearInjectedPaths2(sessionID) {
5238
5272
  function createDirectoryReadmeInjectorHook(ctx) {
5239
5273
  const sessionCaches = new Map;
5240
5274
  const pendingBatchReads = new Map;
5275
+ const truncator = createDynamicTruncator(ctx);
5241
5276
  function getSessionCache(sessionID) {
5242
5277
  if (!sessionCaches.has(sessionID)) {
5243
5278
  sessionCaches.set(sessionID, loadInjectedPaths2(sessionID));
5244
5279
  }
5245
5280
  return sessionCaches.get(sessionID);
5246
5281
  }
5247
- function resolveFilePath2(path4) {
5248
- if (!path4)
5282
+ function resolveFilePath2(path5) {
5283
+ if (!path5)
5249
5284
  return null;
5250
- if (path4.startsWith("/"))
5251
- return path4;
5252
- return resolve3(ctx.directory, path4);
5285
+ if (path5.startsWith("/"))
5286
+ return path5;
5287
+ return resolve3(ctx.directory, path5);
5253
5288
  }
5254
5289
  function findReadmeMdUp(startDir) {
5255
5290
  const found = [];
5256
5291
  let current = startDir;
5257
5292
  while (true) {
5258
- const readmePath = join17(current, README_FILENAME);
5293
+ const readmePath = join18(current, README_FILENAME);
5259
5294
  if (existsSync13(readmePath)) {
5260
5295
  found.push(readmePath);
5261
5296
  }
@@ -5270,7 +5305,7 @@ function createDirectoryReadmeInjectorHook(ctx) {
5270
5305
  }
5271
5306
  return found.reverse();
5272
5307
  }
5273
- function processFilePathForInjection(filePath, sessionID, output) {
5308
+ async function processFilePathForInjection(filePath, sessionID, output) {
5274
5309
  const resolved = resolveFilePath2(filePath);
5275
5310
  if (!resolved)
5276
5311
  return;
@@ -5283,10 +5318,14 @@ function createDirectoryReadmeInjectorHook(ctx) {
5283
5318
  continue;
5284
5319
  try {
5285
5320
  const content = readFileSync7(readmePath, "utf-8");
5321
+ const { result, truncated } = await truncator.truncate(sessionID, content);
5322
+ const truncationNotice = truncated ? `
5323
+
5324
+ [Note: Content was truncated to save context window space. For full context, please read the file directly: ${readmePath}]` : "";
5286
5325
  output.output += `
5287
5326
 
5288
5327
  [Project README: ${readmePath}]
5289
- ${content}`;
5328
+ ${result}${truncationNotice}`;
5290
5329
  cache.add(readmeDir);
5291
5330
  } catch {}
5292
5331
  }
@@ -5311,14 +5350,14 @@ ${content}`;
5311
5350
  const toolExecuteAfter = async (input, output) => {
5312
5351
  const toolName = input.tool.toLowerCase();
5313
5352
  if (toolName === "read") {
5314
- processFilePathForInjection(output.title, input.sessionID, output);
5353
+ await processFilePathForInjection(output.title, input.sessionID, output);
5315
5354
  return;
5316
5355
  }
5317
5356
  if (toolName === "batch") {
5318
5357
  const filePaths = pendingBatchReads.get(input.callID);
5319
5358
  if (filePaths) {
5320
5359
  for (const filePath of filePaths) {
5321
- processFilePathForInjection(filePath, input.sessionID, output);
5360
+ await processFilePathForInjection(filePath, input.sessionID, output);
5322
5361
  }
5323
5362
  pendingBatchReads.delete(input.callID);
5324
5363
  }
@@ -5553,26 +5592,26 @@ var TRUNCATE_CONFIG = {
5553
5592
  // src/hooks/anthropic-auto-compact/storage.ts
5554
5593
  import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
5555
5594
  import { homedir as homedir5 } from "os";
5556
- import { join as join18 } from "path";
5557
- var OPENCODE_STORAGE5 = join18(xdgData2 ?? "", "opencode", "storage");
5595
+ import { join as join19 } from "path";
5596
+ var OPENCODE_STORAGE5 = join19(xdgData ?? "", "opencode", "storage");
5558
5597
  if (process.platform === "darwin" && !existsSync14(OPENCODE_STORAGE5)) {
5559
- const localShare = join18(homedir5(), ".local", "share", "opencode", "storage");
5598
+ const localShare = join19(homedir5(), ".local", "share", "opencode", "storage");
5560
5599
  if (existsSync14(localShare)) {
5561
5600
  OPENCODE_STORAGE5 = localShare;
5562
5601
  }
5563
5602
  }
5564
- var MESSAGE_STORAGE3 = join18(OPENCODE_STORAGE5, "message");
5565
- var PART_STORAGE3 = join18(OPENCODE_STORAGE5, "part");
5603
+ var MESSAGE_STORAGE3 = join19(OPENCODE_STORAGE5, "message");
5604
+ var PART_STORAGE3 = join19(OPENCODE_STORAGE5, "part");
5566
5605
  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.]";
5567
5606
  function getMessageDir3(sessionID) {
5568
5607
  if (!existsSync14(MESSAGE_STORAGE3))
5569
5608
  return "";
5570
- const directPath = join18(MESSAGE_STORAGE3, sessionID);
5609
+ const directPath = join19(MESSAGE_STORAGE3, sessionID);
5571
5610
  if (existsSync14(directPath)) {
5572
5611
  return directPath;
5573
5612
  }
5574
5613
  for (const dir of readdirSync4(MESSAGE_STORAGE3)) {
5575
- const sessionPath = join18(MESSAGE_STORAGE3, dir, sessionID);
5614
+ const sessionPath = join19(MESSAGE_STORAGE3, dir, sessionID);
5576
5615
  if (existsSync14(sessionPath)) {
5577
5616
  return sessionPath;
5578
5617
  }
@@ -5596,14 +5635,14 @@ function findToolResultsBySize(sessionID) {
5596
5635
  const messageIds = getMessageIds(sessionID);
5597
5636
  const results = [];
5598
5637
  for (const messageID of messageIds) {
5599
- const partDir = join18(PART_STORAGE3, messageID);
5638
+ const partDir = join19(PART_STORAGE3, messageID);
5600
5639
  if (!existsSync14(partDir))
5601
5640
  continue;
5602
5641
  for (const file of readdirSync4(partDir)) {
5603
5642
  if (!file.endsWith(".json"))
5604
5643
  continue;
5605
5644
  try {
5606
- const partPath = join18(partDir, file);
5645
+ const partPath = join19(partDir, file);
5607
5646
  const content = readFileSync8(partPath, "utf-8");
5608
5647
  const part = JSON.parse(content);
5609
5648
  if (part.type === "tool" && part.state?.output && !part.truncated) {
@@ -5864,244 +5903,250 @@ async function fixEmptyMessages(sessionID, autoCompactState, client, messageInde
5864
5903
  }
5865
5904
  async function executeCompact(sessionID, msg, autoCompactState, client, directory, experimental) {
5866
5905
  if (autoCompactState.compactionInProgress.has(sessionID)) {
5906
+ await client.tui.showToast({
5907
+ body: {
5908
+ title: "Compact In Progress",
5909
+ message: "Recovery already running. Please wait or start new session if stuck.",
5910
+ variant: "warning",
5911
+ duration: 5000
5912
+ }
5913
+ }).catch(() => {});
5867
5914
  return;
5868
5915
  }
5869
5916
  autoCompactState.compactionInProgress.add(sessionID);
5870
- const errorData = autoCompactState.errorDataBySession.get(sessionID);
5871
- const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
5872
- if (experimental?.aggressive_truncation && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens && truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
5873
- log("[auto-compact] aggressive truncation triggered (experimental)", {
5874
- currentTokens: errorData.currentTokens,
5875
- maxTokens: errorData.maxTokens,
5876
- targetRatio: TRUNCATE_CONFIG.targetTokenRatio
5877
- });
5878
- const aggressiveResult = truncateUntilTargetTokens(sessionID, errorData.currentTokens, errorData.maxTokens, TRUNCATE_CONFIG.targetTokenRatio, TRUNCATE_CONFIG.charsPerToken);
5879
- if (aggressiveResult.truncatedCount > 0) {
5880
- truncateState.truncateAttempt += aggressiveResult.truncatedCount;
5881
- const toolNames = aggressiveResult.truncatedTools.map((t) => t.toolName).join(", ");
5882
- 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...`;
5883
- await client.tui.showToast({
5884
- body: {
5885
- title: aggressiveResult.sufficient ? "Aggressive Truncation" : "Partial Truncation",
5886
- message: `${statusMsg}: ${toolNames}`,
5887
- variant: "warning",
5888
- duration: 4000
5889
- }
5890
- }).catch(() => {});
5891
- log("[auto-compact] aggressive truncation completed", aggressiveResult);
5892
- if (aggressiveResult.sufficient) {
5893
- autoCompactState.compactionInProgress.delete(sessionID);
5894
- setTimeout(async () => {
5895
- try {
5896
- await client.session.prompt_async({
5897
- path: { sessionID },
5898
- body: { parts: [{ type: "text", text: "Continue" }] },
5899
- query: { directory }
5900
- });
5901
- } catch {}
5902
- }, 500);
5903
- return;
5904
- }
5905
- } else {
5906
- await client.tui.showToast({
5907
- body: {
5908
- title: "Truncation Skipped",
5909
- message: "No tool outputs found to truncate.",
5910
- variant: "warning",
5911
- duration: 3000
5917
+ try {
5918
+ const errorData = autoCompactState.errorDataBySession.get(sessionID);
5919
+ const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
5920
+ if (experimental?.aggressive_truncation && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens && truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
5921
+ log("[auto-compact] aggressive truncation triggered (experimental)", {
5922
+ currentTokens: errorData.currentTokens,
5923
+ maxTokens: errorData.maxTokens,
5924
+ targetRatio: TRUNCATE_CONFIG.targetTokenRatio
5925
+ });
5926
+ const aggressiveResult = truncateUntilTargetTokens(sessionID, errorData.currentTokens, errorData.maxTokens, TRUNCATE_CONFIG.targetTokenRatio, TRUNCATE_CONFIG.charsPerToken);
5927
+ if (aggressiveResult.truncatedCount > 0) {
5928
+ truncateState.truncateAttempt += aggressiveResult.truncatedCount;
5929
+ const toolNames = aggressiveResult.truncatedTools.map((t) => t.toolName).join(", ");
5930
+ 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...`;
5931
+ await client.tui.showToast({
5932
+ body: {
5933
+ title: aggressiveResult.sufficient ? "Aggressive Truncation" : "Partial Truncation",
5934
+ message: `${statusMsg}: ${toolNames}`,
5935
+ variant: "warning",
5936
+ duration: 4000
5937
+ }
5938
+ }).catch(() => {});
5939
+ log("[auto-compact] aggressive truncation completed", aggressiveResult);
5940
+ if (aggressiveResult.sufficient) {
5941
+ setTimeout(async () => {
5942
+ try {
5943
+ await client.session.prompt_async({
5944
+ path: { sessionID },
5945
+ body: { parts: [{ type: "text", text: "Continue" }] },
5946
+ query: { directory }
5947
+ });
5948
+ } catch {}
5949
+ }, 500);
5950
+ return;
5912
5951
  }
5913
- }).catch(() => {});
5914
- }
5915
- }
5916
- let skipSummarize = false;
5917
- if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
5918
- const largest = findLargestToolResult(sessionID);
5919
- if (largest && largest.outputSize >= TRUNCATE_CONFIG.minOutputSizeToTruncate) {
5920
- const result = truncateToolResult(largest.partPath);
5921
- if (result.success) {
5922
- truncateState.truncateAttempt++;
5923
- truncateState.lastTruncatedPartId = largest.partId;
5952
+ } else {
5924
5953
  await client.tui.showToast({
5925
5954
  body: {
5926
- title: "Truncating Large Output",
5927
- message: `Truncated ${result.toolName} (${formatBytes(result.originalSize ?? 0)}). Retrying...`,
5955
+ title: "Truncation Skipped",
5956
+ message: "No tool outputs found to truncate.",
5928
5957
  variant: "warning",
5929
5958
  duration: 3000
5930
5959
  }
5931
5960
  }).catch(() => {});
5932
- autoCompactState.compactionInProgress.delete(sessionID);
5933
- setTimeout(async () => {
5934
- try {
5935
- await client.session.prompt_async({
5936
- path: { sessionID },
5937
- body: { parts: [{ type: "text", text: "Continue" }] },
5938
- query: { directory }
5939
- });
5940
- } catch {}
5941
- }, 500);
5942
- return;
5943
5961
  }
5944
- } else if (errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
5945
- skipSummarize = true;
5946
- await client.tui.showToast({
5947
- body: {
5948
- title: "Summarize Skipped",
5949
- message: `Over token limit (${errorData.currentTokens}/${errorData.maxTokens}) with nothing to truncate. Going to revert...`,
5950
- variant: "warning",
5951
- duration: 3000
5952
- }
5953
- }).catch(() => {});
5954
- } else if (!errorData?.currentTokens) {
5955
- await client.tui.showToast({
5956
- body: {
5957
- title: "Truncation Skipped",
5958
- message: "No large tool outputs found.",
5959
- variant: "warning",
5960
- duration: 3000
5961
- }
5962
- }).catch(() => {});
5963
5962
  }
5964
- }
5965
- const retryState = getOrCreateRetryState(autoCompactState, sessionID);
5966
- if (errorData?.errorType?.includes("non-empty content")) {
5967
- const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
5968
- if (attempt < 3) {
5969
- const fixed = await fixEmptyMessages(sessionID, autoCompactState, client, errorData.messageIndex);
5970
- if (fixed) {
5971
- autoCompactState.compactionInProgress.delete(sessionID);
5972
- setTimeout(() => {
5973
- executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
5974
- }, 500);
5975
- return;
5976
- }
5977
- } else {
5978
- await client.tui.showToast({
5979
- body: {
5980
- title: "Recovery Failed",
5981
- message: "Max recovery attempts (3) reached for empty content error. Please start a new session.",
5982
- variant: "error",
5983
- duration: 1e4
5963
+ let skipSummarize = false;
5964
+ if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
5965
+ const largest = findLargestToolResult(sessionID);
5966
+ if (largest && largest.outputSize >= TRUNCATE_CONFIG.minOutputSizeToTruncate) {
5967
+ const result = truncateToolResult(largest.partPath);
5968
+ if (result.success) {
5969
+ truncateState.truncateAttempt++;
5970
+ truncateState.lastTruncatedPartId = largest.partId;
5971
+ await client.tui.showToast({
5972
+ body: {
5973
+ title: "Truncating Large Output",
5974
+ message: `Truncated ${result.toolName} (${formatBytes(result.originalSize ?? 0)}). Retrying...`,
5975
+ variant: "warning",
5976
+ duration: 3000
5977
+ }
5978
+ }).catch(() => {});
5979
+ setTimeout(async () => {
5980
+ try {
5981
+ await client.session.prompt_async({
5982
+ path: { sessionID },
5983
+ body: { parts: [{ type: "text", text: "Continue" }] },
5984
+ query: { directory }
5985
+ });
5986
+ } catch {}
5987
+ }, 500);
5988
+ return;
5984
5989
  }
5985
- }).catch(() => {});
5986
- autoCompactState.compactionInProgress.delete(sessionID);
5987
- return;
5988
- }
5989
- }
5990
- if (Date.now() - retryState.lastAttemptTime > 300000) {
5991
- retryState.attempt = 0;
5992
- autoCompactState.fallbackStateBySession.delete(sessionID);
5993
- autoCompactState.truncateStateBySession.delete(sessionID);
5994
- }
5995
- if (!skipSummarize && retryState.attempt < RETRY_CONFIG.maxAttempts) {
5996
- retryState.attempt++;
5997
- retryState.lastAttemptTime = Date.now();
5998
- const providerID = msg.providerID;
5999
- const modelID = msg.modelID;
6000
- if (providerID && modelID) {
6001
- try {
5990
+ } else if (errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
5991
+ skipSummarize = true;
6002
5992
  await client.tui.showToast({
6003
5993
  body: {
6004
- title: "Auto Compact",
6005
- message: `Summarizing session (attempt ${retryState.attempt}/${RETRY_CONFIG.maxAttempts})...`,
5994
+ title: "Summarize Skipped",
5995
+ message: `Over token limit (${errorData.currentTokens}/${errorData.maxTokens}) with nothing to truncate. Going to revert...`,
5996
+ variant: "warning",
5997
+ duration: 3000
5998
+ }
5999
+ }).catch(() => {});
6000
+ } else if (!errorData?.currentTokens) {
6001
+ await client.tui.showToast({
6002
+ body: {
6003
+ title: "Truncation Skipped",
6004
+ message: "No large tool outputs found.",
6006
6005
  variant: "warning",
6007
6006
  duration: 3000
6008
6007
  }
6009
6008
  }).catch(() => {});
6010
- await client.session.summarize({
6011
- path: { id: sessionID },
6012
- body: { providerID, modelID },
6013
- query: { directory }
6014
- });
6015
- autoCompactState.compactionInProgress.delete(sessionID);
6016
- setTimeout(async () => {
6017
- try {
6018
- await client.session.prompt_async({
6019
- path: { sessionID },
6020
- body: { parts: [{ type: "text", text: "Continue" }] },
6021
- query: { directory }
6022
- });
6023
- } catch {}
6024
- }, 500);
6025
- return;
6026
- } catch {
6027
- autoCompactState.compactionInProgress.delete(sessionID);
6028
- const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
6029
- const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
6030
- setTimeout(() => {
6031
- executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
6032
- }, cappedDelay);
6033
- return;
6034
6009
  }
6035
- } else {
6036
- await client.tui.showToast({
6037
- body: {
6038
- title: "Summarize Skipped",
6039
- message: "Missing providerID or modelID. Skipping to revert...",
6040
- variant: "warning",
6041
- duration: 3000
6010
+ }
6011
+ const retryState = getOrCreateRetryState(autoCompactState, sessionID);
6012
+ if (errorData?.errorType?.includes("non-empty content")) {
6013
+ const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
6014
+ if (attempt < 3) {
6015
+ const fixed = await fixEmptyMessages(sessionID, autoCompactState, client, errorData.messageIndex);
6016
+ if (fixed) {
6017
+ setTimeout(() => {
6018
+ executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
6019
+ }, 500);
6020
+ return;
6042
6021
  }
6043
- }).catch(() => {});
6022
+ } else {
6023
+ await client.tui.showToast({
6024
+ body: {
6025
+ title: "Recovery Failed",
6026
+ message: "Max recovery attempts (3) reached for empty content error. Please start a new session.",
6027
+ variant: "error",
6028
+ duration: 1e4
6029
+ }
6030
+ }).catch(() => {});
6031
+ return;
6032
+ }
6044
6033
  }
6045
- }
6046
- const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
6047
- if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) {
6048
- const pair = await getLastMessagePair(sessionID, client, directory);
6049
- if (pair) {
6050
- try {
6034
+ if (Date.now() - retryState.lastAttemptTime > 300000) {
6035
+ retryState.attempt = 0;
6036
+ autoCompactState.fallbackStateBySession.delete(sessionID);
6037
+ autoCompactState.truncateStateBySession.delete(sessionID);
6038
+ }
6039
+ if (!skipSummarize && retryState.attempt < RETRY_CONFIG.maxAttempts) {
6040
+ retryState.attempt++;
6041
+ retryState.lastAttemptTime = Date.now();
6042
+ const providerID = msg.providerID;
6043
+ const modelID = msg.modelID;
6044
+ if (providerID && modelID) {
6045
+ try {
6046
+ await client.tui.showToast({
6047
+ body: {
6048
+ title: "Auto Compact",
6049
+ message: `Summarizing session (attempt ${retryState.attempt}/${RETRY_CONFIG.maxAttempts})...`,
6050
+ variant: "warning",
6051
+ duration: 3000
6052
+ }
6053
+ }).catch(() => {});
6054
+ await client.session.summarize({
6055
+ path: { id: sessionID },
6056
+ body: { providerID, modelID },
6057
+ query: { directory }
6058
+ });
6059
+ setTimeout(async () => {
6060
+ try {
6061
+ await client.session.prompt_async({
6062
+ path: { sessionID },
6063
+ body: { parts: [{ type: "text", text: "Continue" }] },
6064
+ query: { directory }
6065
+ });
6066
+ } catch {}
6067
+ }, 500);
6068
+ return;
6069
+ } catch {
6070
+ const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
6071
+ const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
6072
+ setTimeout(() => {
6073
+ executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
6074
+ }, cappedDelay);
6075
+ return;
6076
+ }
6077
+ } else {
6051
6078
  await client.tui.showToast({
6052
6079
  body: {
6053
- title: "Emergency Recovery",
6054
- message: "Removing last message pair...",
6080
+ title: "Summarize Skipped",
6081
+ message: "Missing providerID or modelID. Skipping to revert...",
6055
6082
  variant: "warning",
6056
6083
  duration: 3000
6057
6084
  }
6058
6085
  }).catch(() => {});
6059
- if (pair.assistantMessageID) {
6086
+ }
6087
+ }
6088
+ const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
6089
+ if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) {
6090
+ const pair = await getLastMessagePair(sessionID, client, directory);
6091
+ if (pair) {
6092
+ try {
6093
+ await client.tui.showToast({
6094
+ body: {
6095
+ title: "Emergency Recovery",
6096
+ message: "Removing last message pair...",
6097
+ variant: "warning",
6098
+ duration: 3000
6099
+ }
6100
+ }).catch(() => {});
6101
+ if (pair.assistantMessageID) {
6102
+ await client.session.revert({
6103
+ path: { id: sessionID },
6104
+ body: { messageID: pair.assistantMessageID },
6105
+ query: { directory }
6106
+ });
6107
+ }
6060
6108
  await client.session.revert({
6061
6109
  path: { id: sessionID },
6062
- body: { messageID: pair.assistantMessageID },
6110
+ body: { messageID: pair.userMessageID },
6063
6111
  query: { directory }
6064
6112
  });
6065
- }
6066
- await client.session.revert({
6067
- path: { id: sessionID },
6068
- body: { messageID: pair.userMessageID },
6069
- query: { directory }
6070
- });
6071
- fallbackState.revertAttempt++;
6072
- fallbackState.lastRevertedMessageID = pair.userMessageID;
6073
- clearSessionState(autoCompactState, sessionID);
6074
- setTimeout(async () => {
6075
- try {
6076
- await client.session.prompt_async({
6077
- path: { sessionID },
6078
- body: { parts: [{ type: "text", text: "Continue" }] },
6079
- query: { directory }
6080
- });
6081
- } catch {}
6082
- }, 500);
6083
- return;
6084
- } catch {}
6085
- } else {
6086
- await client.tui.showToast({
6087
- body: {
6088
- title: "Revert Skipped",
6089
- message: "Could not find last message pair to revert.",
6090
- variant: "warning",
6091
- duration: 3000
6092
- }
6093
- }).catch(() => {});
6113
+ fallbackState.revertAttempt++;
6114
+ fallbackState.lastRevertedMessageID = pair.userMessageID;
6115
+ clearSessionState(autoCompactState, sessionID);
6116
+ setTimeout(async () => {
6117
+ try {
6118
+ await client.session.prompt_async({
6119
+ path: { sessionID },
6120
+ body: { parts: [{ type: "text", text: "Continue" }] },
6121
+ query: { directory }
6122
+ });
6123
+ } catch {}
6124
+ }, 500);
6125
+ return;
6126
+ } catch {}
6127
+ } else {
6128
+ await client.tui.showToast({
6129
+ body: {
6130
+ title: "Revert Skipped",
6131
+ message: "Could not find last message pair to revert.",
6132
+ variant: "warning",
6133
+ duration: 3000
6134
+ }
6135
+ }).catch(() => {});
6136
+ }
6094
6137
  }
6138
+ clearSessionState(autoCompactState, sessionID);
6139
+ await client.tui.showToast({
6140
+ body: {
6141
+ title: "Auto Compact Failed",
6142
+ message: "All recovery attempts failed. Please start a new session.",
6143
+ variant: "error",
6144
+ duration: 5000
6145
+ }
6146
+ }).catch(() => {});
6147
+ } finally {
6148
+ autoCompactState.compactionInProgress.delete(sessionID);
6095
6149
  }
6096
- clearSessionState(autoCompactState, sessionID);
6097
- await client.tui.showToast({
6098
- body: {
6099
- title: "Auto Compact Failed",
6100
- message: "All recovery attempts failed. Please start a new session.",
6101
- variant: "error",
6102
- duration: 5000
6103
- }
6104
- }).catch(() => {});
6105
6150
  }
6106
6151
 
6107
6152
  // src/hooks/anthropic-auto-compact/index.ts
@@ -6211,7 +6256,7 @@ function createAnthropicAutoCompactHook(ctx, options) {
6211
6256
  }
6212
6257
  // src/hooks/preemptive-compaction/index.ts
6213
6258
  import { existsSync as existsSync15, readdirSync as readdirSync5 } from "fs";
6214
- import { join as join19 } from "path";
6259
+ import { join as join20 } from "path";
6215
6260
 
6216
6261
  // src/hooks/preemptive-compaction/constants.ts
6217
6262
  var DEFAULT_THRESHOLD = 0.85;
@@ -6227,11 +6272,11 @@ function isSupportedModel(modelID) {
6227
6272
  function getMessageDir4(sessionID) {
6228
6273
  if (!existsSync15(MESSAGE_STORAGE))
6229
6274
  return null;
6230
- const directPath = join19(MESSAGE_STORAGE, sessionID);
6275
+ const directPath = join20(MESSAGE_STORAGE, sessionID);
6231
6276
  if (existsSync15(directPath))
6232
6277
  return directPath;
6233
6278
  for (const dir of readdirSync5(MESSAGE_STORAGE)) {
6234
- const sessionPath = join19(MESSAGE_STORAGE, dir, sessionID);
6279
+ const sessionPath = join20(MESSAGE_STORAGE, dir, sessionID);
6235
6280
  if (existsSync15(sessionPath))
6236
6281
  return sessionPath;
6237
6282
  }
@@ -6717,7 +6762,7 @@ function createThinkModeHook() {
6717
6762
  }
6718
6763
  // src/hooks/claude-code-hooks/config.ts
6719
6764
  import { homedir as homedir6 } from "os";
6720
- import { join as join20 } from "path";
6765
+ import { join as join21 } from "path";
6721
6766
  import { existsSync as existsSync16 } from "fs";
6722
6767
  function normalizeHookMatcher(raw) {
6723
6768
  return {
@@ -6744,9 +6789,9 @@ function normalizeHooksConfig(raw) {
6744
6789
  function getClaudeSettingsPaths(customPath) {
6745
6790
  const home = homedir6();
6746
6791
  const paths = [
6747
- join20(home, ".claude", "settings.json"),
6748
- join20(process.cwd(), ".claude", "settings.json"),
6749
- join20(process.cwd(), ".claude", "settings.local.json")
6792
+ join21(home, ".claude", "settings.json"),
6793
+ join21(process.cwd(), ".claude", "settings.json"),
6794
+ join21(process.cwd(), ".claude", "settings.local.json")
6750
6795
  ];
6751
6796
  if (customPath && existsSync16(customPath)) {
6752
6797
  paths.unshift(customPath);
@@ -6792,20 +6837,20 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6792
6837
  // src/hooks/claude-code-hooks/config-loader.ts
6793
6838
  import { existsSync as existsSync17 } from "fs";
6794
6839
  import { homedir as homedir7 } from "os";
6795
- import { join as join21 } from "path";
6796
- var USER_CONFIG_PATH = join21(homedir7(), ".config", "opencode", "opencode-cc-plugin.json");
6840
+ import { join as join22 } from "path";
6841
+ var USER_CONFIG_PATH = join22(homedir7(), ".config", "opencode", "opencode-cc-plugin.json");
6797
6842
  function getProjectConfigPath() {
6798
- return join21(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6843
+ return join22(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6799
6844
  }
6800
- async function loadConfigFromPath(path4) {
6801
- if (!existsSync17(path4)) {
6845
+ async function loadConfigFromPath(path5) {
6846
+ if (!existsSync17(path5)) {
6802
6847
  return null;
6803
6848
  }
6804
6849
  try {
6805
- const content = await Bun.file(path4).text();
6850
+ const content = await Bun.file(path5).text();
6806
6851
  return JSON.parse(content);
6807
6852
  } catch (error) {
6808
- log("Failed to load config", { path: path4, error });
6853
+ log("Failed to load config", { path: path5, error });
6809
6854
  return null;
6810
6855
  }
6811
6856
  }
@@ -6978,13 +7023,13 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
6978
7023
  }
6979
7024
 
6980
7025
  // src/hooks/claude-code-hooks/transcript.ts
6981
- import { join as join22 } from "path";
7026
+ import { join as join23 } from "path";
6982
7027
  import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync18, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "fs";
6983
7028
  import { homedir as homedir8, tmpdir as tmpdir5 } from "os";
6984
7029
  import { randomUUID } from "crypto";
6985
- var TRANSCRIPT_DIR = join22(homedir8(), ".claude", "transcripts");
7030
+ var TRANSCRIPT_DIR = join23(homedir8(), ".claude", "transcripts");
6986
7031
  function getTranscriptPath(sessionId) {
6987
- return join22(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
7032
+ return join23(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6988
7033
  }
6989
7034
  function ensureTranscriptDir() {
6990
7035
  if (!existsSync18(TRANSCRIPT_DIR)) {
@@ -6993,10 +7038,10 @@ function ensureTranscriptDir() {
6993
7038
  }
6994
7039
  function appendTranscriptEntry(sessionId, entry) {
6995
7040
  ensureTranscriptDir();
6996
- const path4 = getTranscriptPath(sessionId);
7041
+ const path5 = getTranscriptPath(sessionId);
6997
7042
  const line = JSON.stringify(entry) + `
6998
7043
  `;
6999
- appendFileSync5(path4, line);
7044
+ appendFileSync5(path5, line);
7000
7045
  }
7001
7046
  function recordToolUse(sessionId, toolName, toolInput) {
7002
7047
  appendTranscriptEntry(sessionId, {
@@ -7074,7 +7119,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
7074
7119
  }
7075
7120
  };
7076
7121
  entries.push(JSON.stringify(currentEntry));
7077
- const tempPath = join22(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
7122
+ const tempPath = join23(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
7078
7123
  writeFileSync6(tempPath, entries.join(`
7079
7124
  `) + `
7080
7125
  `);
@@ -7094,7 +7139,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
7094
7139
  ]
7095
7140
  }
7096
7141
  };
7097
- const tempPath = join22(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
7142
+ const tempPath = join23(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
7098
7143
  writeFileSync6(tempPath, JSON.stringify(currentEntry) + `
7099
7144
  `);
7100
7145
  return tempPath;
@@ -7103,11 +7148,11 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
7103
7148
  }
7104
7149
  }
7105
7150
  }
7106
- function deleteTempTranscript(path4) {
7107
- if (!path4)
7151
+ function deleteTempTranscript(path5) {
7152
+ if (!path5)
7108
7153
  return;
7109
7154
  try {
7110
- unlinkSync5(path4);
7155
+ unlinkSync5(path5);
7111
7156
  } catch {}
7112
7157
  }
7113
7158
 
@@ -7306,11 +7351,11 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
7306
7351
  }
7307
7352
 
7308
7353
  // src/hooks/claude-code-hooks/todo.ts
7309
- import { join as join23 } from "path";
7354
+ import { join as join24 } from "path";
7310
7355
  import { homedir as homedir9 } from "os";
7311
- var TODO_DIR = join23(homedir9(), ".claude", "todos");
7356
+ var TODO_DIR = join24(homedir9(), ".claude", "todos");
7312
7357
  function getTodoPath(sessionId) {
7313
- return join23(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
7358
+ return join24(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
7314
7359
  }
7315
7360
 
7316
7361
  // src/hooks/claude-code-hooks/stop.ts
@@ -7745,12 +7790,12 @@ import {
7745
7790
  realpathSync,
7746
7791
  statSync as statSync2
7747
7792
  } from "fs";
7748
- import { dirname as dirname4, join as join25, relative } from "path";
7793
+ import { dirname as dirname4, join as join26, relative } from "path";
7749
7794
 
7750
7795
  // src/hooks/rules-injector/constants.ts
7751
- import { join as join24 } from "path";
7752
- var OPENCODE_STORAGE6 = join24(xdgData2 ?? "", "opencode", "storage");
7753
- var RULES_INJECTOR_STORAGE = join24(OPENCODE_STORAGE6, "rules-injector");
7796
+ import { join as join25 } from "path";
7797
+ var OPENCODE_STORAGE6 = join25(xdgData ?? "", "opencode", "storage");
7798
+ var RULES_INJECTOR_STORAGE = join25(OPENCODE_STORAGE6, "rules-injector");
7754
7799
  var PROJECT_MARKERS = [
7755
7800
  ".git",
7756
7801
  "pyproject.toml",
@@ -7777,7 +7822,7 @@ function findProjectRoot(startPath) {
7777
7822
  }
7778
7823
  while (true) {
7779
7824
  for (const marker of PROJECT_MARKERS) {
7780
- const markerPath = join25(current, marker);
7825
+ const markerPath = join26(current, marker);
7781
7826
  if (existsSync19(markerPath)) {
7782
7827
  return current;
7783
7828
  }
@@ -7795,7 +7840,7 @@ function findRuleFilesRecursive(dir, results) {
7795
7840
  try {
7796
7841
  const entries = readdirSync6(dir, { withFileTypes: true });
7797
7842
  for (const entry of entries) {
7798
- const fullPath = join25(dir, entry.name);
7843
+ const fullPath = join26(dir, entry.name);
7799
7844
  if (entry.isDirectory()) {
7800
7845
  findRuleFilesRecursive(fullPath, results);
7801
7846
  } else if (entry.isFile()) {
@@ -7821,7 +7866,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
7821
7866
  let distance = 0;
7822
7867
  while (true) {
7823
7868
  for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
7824
- const ruleDir = join25(currentDir, parent, subdir);
7869
+ const ruleDir = join26(currentDir, parent, subdir);
7825
7870
  const files = [];
7826
7871
  findRuleFilesRecursive(ruleDir, files);
7827
7872
  for (const filePath of files) {
@@ -7845,7 +7890,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
7845
7890
  currentDir = parentDir;
7846
7891
  distance++;
7847
7892
  }
7848
- const userRuleDir = join25(homeDir, USER_RULE_DIR);
7893
+ const userRuleDir = join26(homeDir, USER_RULE_DIR);
7849
7894
  const userFiles = [];
7850
7895
  findRuleFilesRecursive(userRuleDir, userFiles);
7851
7896
  for (const filePath of userFiles) {
@@ -8040,9 +8085,9 @@ import {
8040
8085
  writeFileSync as writeFileSync7,
8041
8086
  unlinkSync as unlinkSync6
8042
8087
  } from "fs";
8043
- import { join as join26 } from "path";
8088
+ import { join as join27 } from "path";
8044
8089
  function getStoragePath3(sessionID) {
8045
- return join26(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
8090
+ return join27(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
8046
8091
  }
8047
8092
  function loadInjectedRules(sessionID) {
8048
8093
  const filePath = getStoragePath3(sessionID);
@@ -8083,20 +8128,21 @@ var TRACKED_TOOLS = ["read", "write", "edit", "multiedit"];
8083
8128
  function createRulesInjectorHook(ctx) {
8084
8129
  const sessionCaches = new Map;
8085
8130
  const pendingBatchFiles = new Map;
8131
+ const truncator = createDynamicTruncator(ctx);
8086
8132
  function getSessionCache(sessionID) {
8087
8133
  if (!sessionCaches.has(sessionID)) {
8088
8134
  sessionCaches.set(sessionID, loadInjectedRules(sessionID));
8089
8135
  }
8090
8136
  return sessionCaches.get(sessionID);
8091
8137
  }
8092
- function resolveFilePath2(path4) {
8093
- if (!path4)
8138
+ function resolveFilePath2(path5) {
8139
+ if (!path5)
8094
8140
  return null;
8095
- if (path4.startsWith("/"))
8096
- return path4;
8097
- return resolve4(ctx.directory, path4);
8141
+ if (path5.startsWith("/"))
8142
+ return path5;
8143
+ return resolve4(ctx.directory, path5);
8098
8144
  }
8099
- function processFilePathForInjection(filePath, sessionID, output) {
8145
+ async function processFilePathForInjection(filePath, sessionID, output) {
8100
8146
  const resolved = resolveFilePath2(filePath);
8101
8147
  if (!resolved)
8102
8148
  return;
@@ -8132,11 +8178,15 @@ function createRulesInjectorHook(ctx) {
8132
8178
  return;
8133
8179
  toInject.sort((a, b) => a.distance - b.distance);
8134
8180
  for (const rule of toInject) {
8181
+ const { result, truncated } = await truncator.truncate(sessionID, rule.content);
8182
+ const truncationNotice = truncated ? `
8183
+
8184
+ [Note: Content was truncated to save context window space. For full context, please read the file directly: ${rule.relativePath}]` : "";
8135
8185
  output.output += `
8136
8186
 
8137
8187
  [Rule: ${rule.relativePath}]
8138
8188
  [Match: ${rule.matchReason}]
8139
- ${rule.content}`;
8189
+ ${result}${truncationNotice}`;
8140
8190
  }
8141
8191
  saveInjectedRules(sessionID, cache2);
8142
8192
  }
@@ -8166,14 +8216,14 @@ ${rule.content}`;
8166
8216
  const toolExecuteAfter = async (input, output) => {
8167
8217
  const toolName = input.tool.toLowerCase();
8168
8218
  if (TRACKED_TOOLS.includes(toolName)) {
8169
- processFilePathForInjection(output.title, input.sessionID, output);
8219
+ await processFilePathForInjection(output.title, input.sessionID, output);
8170
8220
  return;
8171
8221
  }
8172
8222
  if (toolName === "batch") {
8173
8223
  const filePaths = pendingBatchFiles.get(input.callID);
8174
8224
  if (filePaths) {
8175
8225
  for (const filePath of filePaths) {
8176
- processFilePathForInjection(filePath, input.sessionID, output);
8226
+ await processFilePathForInjection(filePath, input.sessionID, output);
8177
8227
  }
8178
8228
  pendingBatchFiles.delete(input.callID);
8179
8229
  }
@@ -8213,33 +8263,33 @@ function createBackgroundNotificationHook(manager) {
8213
8263
  }
8214
8264
  // src/hooks/auto-update-checker/checker.ts
8215
8265
  import * as fs5 from "fs";
8216
- import * as path5 from "path";
8266
+ import * as path6 from "path";
8217
8267
  import { fileURLToPath } from "url";
8218
8268
 
8219
8269
  // src/hooks/auto-update-checker/constants.ts
8220
- import * as path4 from "path";
8221
- import * as os4 from "os";
8270
+ import * as path5 from "path";
8271
+ import * as os5 from "os";
8222
8272
  var PACKAGE_NAME = "oh-my-opencode";
8223
8273
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
8224
8274
  var NPM_FETCH_TIMEOUT = 5000;
8225
8275
  function getCacheDir2() {
8226
8276
  if (process.platform === "win32") {
8227
- return path4.join(process.env.LOCALAPPDATA ?? os4.homedir(), "opencode");
8277
+ return path5.join(process.env.LOCALAPPDATA ?? os5.homedir(), "opencode");
8228
8278
  }
8229
- return path4.join(os4.homedir(), ".cache", "opencode");
8279
+ return path5.join(os5.homedir(), ".cache", "opencode");
8230
8280
  }
8231
8281
  var CACHE_DIR = getCacheDir2();
8232
- var VERSION_FILE = path4.join(CACHE_DIR, "version");
8233
- var INSTALLED_PACKAGE_JSON = path4.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
8282
+ var VERSION_FILE = path5.join(CACHE_DIR, "version");
8283
+ var INSTALLED_PACKAGE_JSON = path5.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
8234
8284
  function getUserConfigDir2() {
8235
8285
  if (process.platform === "win32") {
8236
- return process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
8286
+ return process.env.APPDATA ?? path5.join(os5.homedir(), "AppData", "Roaming");
8237
8287
  }
8238
- return process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
8288
+ return process.env.XDG_CONFIG_HOME ?? path5.join(os5.homedir(), ".config");
8239
8289
  }
8240
8290
  var USER_CONFIG_DIR = getUserConfigDir2();
8241
- var USER_OPENCODE_CONFIG = path4.join(USER_CONFIG_DIR, "opencode", "opencode.json");
8242
- var USER_OPENCODE_CONFIG_JSONC = path4.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc");
8291
+ var USER_OPENCODE_CONFIG = path5.join(USER_CONFIG_DIR, "opencode", "opencode.json");
8292
+ var USER_OPENCODE_CONFIG_JSONC = path5.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc");
8243
8293
 
8244
8294
  // src/hooks/auto-update-checker/checker.ts
8245
8295
  function stripJsonComments(json) {
@@ -8247,8 +8297,8 @@ function stripJsonComments(json) {
8247
8297
  }
8248
8298
  function getConfigPaths(directory) {
8249
8299
  return [
8250
- path5.join(directory, ".opencode", "opencode.json"),
8251
- path5.join(directory, ".opencode", "opencode.jsonc"),
8300
+ path6.join(directory, ".opencode", "opencode.json"),
8301
+ path6.join(directory, ".opencode", "opencode.jsonc"),
8252
8302
  USER_OPENCODE_CONFIG,
8253
8303
  USER_OPENCODE_CONFIG_JSONC
8254
8304
  ];
@@ -8279,9 +8329,9 @@ function getLocalDevPath(directory) {
8279
8329
  function findPackageJsonUp(startPath) {
8280
8330
  try {
8281
8331
  const stat = fs5.statSync(startPath);
8282
- let dir = stat.isDirectory() ? startPath : path5.dirname(startPath);
8332
+ let dir = stat.isDirectory() ? startPath : path6.dirname(startPath);
8283
8333
  for (let i = 0;i < 10; i++) {
8284
- const pkgPath = path5.join(dir, "package.json");
8334
+ const pkgPath = path6.join(dir, "package.json");
8285
8335
  if (fs5.existsSync(pkgPath)) {
8286
8336
  try {
8287
8337
  const content = fs5.readFileSync(pkgPath, "utf-8");
@@ -8290,7 +8340,7 @@ function findPackageJsonUp(startPath) {
8290
8340
  return pkgPath;
8291
8341
  } catch {}
8292
8342
  }
8293
- const parent = path5.dirname(dir);
8343
+ const parent = path6.dirname(dir);
8294
8344
  if (parent === dir)
8295
8345
  break;
8296
8346
  dir = parent;
@@ -8347,7 +8397,7 @@ function getCachedVersion() {
8347
8397
  }
8348
8398
  } catch {}
8349
8399
  try {
8350
- const currentDir = path5.dirname(fileURLToPath(import.meta.url));
8400
+ const currentDir = path6.dirname(fileURLToPath(import.meta.url));
8351
8401
  const pkgPath = findPackageJsonUp(currentDir);
8352
8402
  if (pkgPath) {
8353
8403
  const content = fs5.readFileSync(pkgPath, "utf-8");
@@ -8423,12 +8473,12 @@ async function getLatestVersion() {
8423
8473
 
8424
8474
  // src/hooks/auto-update-checker/cache.ts
8425
8475
  import * as fs6 from "fs";
8426
- import * as path6 from "path";
8476
+ import * as path7 from "path";
8427
8477
  function stripTrailingCommas(json) {
8428
8478
  return json.replace(/,(\s*[}\]])/g, "$1");
8429
8479
  }
8430
8480
  function removeFromBunLock(packageName) {
8431
- const lockPath = path6.join(CACHE_DIR, "bun.lock");
8481
+ const lockPath = path7.join(CACHE_DIR, "bun.lock");
8432
8482
  if (!fs6.existsSync(lockPath))
8433
8483
  return false;
8434
8484
  try {
@@ -8454,8 +8504,8 @@ function removeFromBunLock(packageName) {
8454
8504
  }
8455
8505
  function invalidatePackage(packageName = PACKAGE_NAME) {
8456
8506
  try {
8457
- const pkgDir = path6.join(CACHE_DIR, "node_modules", packageName);
8458
- const pkgJsonPath = path6.join(CACHE_DIR, "package.json");
8507
+ const pkgDir = path7.join(CACHE_DIR, "node_modules", packageName);
8508
+ const pkgJsonPath = path7.join(CACHE_DIR, "package.json");
8459
8509
  let packageRemoved = false;
8460
8510
  let dependencyRemoved = false;
8461
8511
  let lockRemoved = false;
@@ -8649,12 +8699,12 @@ import {
8649
8699
  writeFileSync as writeFileSync10,
8650
8700
  unlinkSync as unlinkSync7
8651
8701
  } from "fs";
8652
- import { join as join31 } from "path";
8702
+ import { join as join32 } from "path";
8653
8703
 
8654
8704
  // src/hooks/agent-usage-reminder/constants.ts
8655
- import { join as join30 } from "path";
8656
- var OPENCODE_STORAGE7 = join30(xdgData2 ?? "", "opencode", "storage");
8657
- var AGENT_USAGE_REMINDER_STORAGE = join30(OPENCODE_STORAGE7, "agent-usage-reminder");
8705
+ import { join as join31 } from "path";
8706
+ var OPENCODE_STORAGE7 = join31(xdgData ?? "", "opencode", "storage");
8707
+ var AGENT_USAGE_REMINDER_STORAGE = join31(OPENCODE_STORAGE7, "agent-usage-reminder");
8658
8708
  var TARGET_TOOLS = new Set([
8659
8709
  "grep",
8660
8710
  "safe_grep",
@@ -8699,7 +8749,7 @@ ALWAYS prefer: Multiple parallel background_task calls > Direct tool calls
8699
8749
 
8700
8750
  // src/hooks/agent-usage-reminder/storage.ts
8701
8751
  function getStoragePath4(sessionID) {
8702
- return join31(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
8752
+ return join32(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
8703
8753
  }
8704
8754
  function loadAgentUsageState(sessionID) {
8705
8755
  const filePath = getStoragePath4(sessionID);
@@ -8825,6 +8875,7 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
8825
8875
 
8826
8876
  ## ZERO TOLERANCE FAILURES
8827
8877
  - **NO Scope Reduction**: Never make "demo", "skeleton", "simplified", "basic" versions - deliver FULL implementation
8878
+ - **NO MockUp Work**: When user asked you to do "port A", you must "port A", fully, 100%. No Extra feature, No reduced feature, no mock data, fully working 100% port.
8828
8879
  - **NO Partial Completion**: Never stop at 60-80% saying "you can extend this..." - finish 100%
8829
8880
  - **NO Assumed Shortcuts**: Never skip requirements you deem "optional" or "can be added later"
8830
8881
  - **NO Premature Stopping**: Never declare done until ALL TODOs are completed and verified
@@ -9021,12 +9072,12 @@ import {
9021
9072
  writeFileSync as writeFileSync11,
9022
9073
  unlinkSync as unlinkSync8
9023
9074
  } from "fs";
9024
- import { join as join33 } from "path";
9075
+ import { join as join34 } from "path";
9025
9076
 
9026
9077
  // src/hooks/interactive-bash-session/constants.ts
9027
- import { join as join32 } from "path";
9028
- var OPENCODE_STORAGE8 = join32(xdgData2 ?? "", "opencode", "storage");
9029
- var INTERACTIVE_BASH_SESSION_STORAGE = join32(OPENCODE_STORAGE8, "interactive-bash-session");
9078
+ import { join as join33 } from "path";
9079
+ var OPENCODE_STORAGE8 = join33(xdgData ?? "", "opencode", "storage");
9080
+ var INTERACTIVE_BASH_SESSION_STORAGE = join33(OPENCODE_STORAGE8, "interactive-bash-session");
9030
9081
  var OMO_SESSION_PREFIX = "omo-";
9031
9082
  function buildSessionReminderMessage(sessions) {
9032
9083
  if (sessions.length === 0)
@@ -9038,7 +9089,7 @@ function buildSessionReminderMessage(sessions) {
9038
9089
 
9039
9090
  // src/hooks/interactive-bash-session/storage.ts
9040
9091
  function getStoragePath5(sessionID) {
9041
- return join33(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
9092
+ return join34(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
9042
9093
  }
9043
9094
  function loadInteractiveBashSessionState(sessionID) {
9044
9095
  const filePath = getStoragePath5(sessionID);
@@ -11016,7 +11067,7 @@ async function createGoogleAntigravityAuthPlugin({
11016
11067
  // src/features/claude-code-command-loader/loader.ts
11017
11068
  import { existsSync as existsSync25, readdirSync as readdirSync7, readFileSync as readFileSync15 } from "fs";
11018
11069
  import { homedir as homedir12 } from "os";
11019
- import { join as join34, basename } from "path";
11070
+ import { join as join35, basename } from "path";
11020
11071
  function loadCommandsFromDir(commandsDir, scope) {
11021
11072
  if (!existsSync25(commandsDir)) {
11022
11073
  return [];
@@ -11026,7 +11077,7 @@ function loadCommandsFromDir(commandsDir, scope) {
11026
11077
  for (const entry of entries) {
11027
11078
  if (!isMarkdownFile(entry))
11028
11079
  continue;
11029
- const commandPath = join34(commandsDir, entry.name);
11080
+ const commandPath = join35(commandsDir, entry.name);
11030
11081
  const commandName = basename(entry.name, ".md");
11031
11082
  try {
11032
11083
  const content = readFileSync15(commandPath, "utf-8");
@@ -11069,29 +11120,29 @@ function commandsToRecord(commands) {
11069
11120
  return result;
11070
11121
  }
11071
11122
  function loadUserCommands() {
11072
- const userCommandsDir = join34(homedir12(), ".claude", "commands");
11123
+ const userCommandsDir = join35(homedir12(), ".claude", "commands");
11073
11124
  const commands = loadCommandsFromDir(userCommandsDir, "user");
11074
11125
  return commandsToRecord(commands);
11075
11126
  }
11076
11127
  function loadProjectCommands() {
11077
- const projectCommandsDir = join34(process.cwd(), ".claude", "commands");
11128
+ const projectCommandsDir = join35(process.cwd(), ".claude", "commands");
11078
11129
  const commands = loadCommandsFromDir(projectCommandsDir, "project");
11079
11130
  return commandsToRecord(commands);
11080
11131
  }
11081
11132
  function loadOpencodeGlobalCommands() {
11082
- const opencodeCommandsDir = join34(homedir12(), ".config", "opencode", "command");
11133
+ const opencodeCommandsDir = join35(homedir12(), ".config", "opencode", "command");
11083
11134
  const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
11084
11135
  return commandsToRecord(commands);
11085
11136
  }
11086
11137
  function loadOpencodeProjectCommands() {
11087
- const opencodeProjectDir = join34(process.cwd(), ".opencode", "command");
11138
+ const opencodeProjectDir = join35(process.cwd(), ".opencode", "command");
11088
11139
  const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
11089
11140
  return commandsToRecord(commands);
11090
11141
  }
11091
11142
  // src/features/claude-code-skill-loader/loader.ts
11092
11143
  import { existsSync as existsSync26, readdirSync as readdirSync8, readFileSync as readFileSync16 } from "fs";
11093
11144
  import { homedir as homedir13 } from "os";
11094
- import { join as join35 } from "path";
11145
+ import { join as join36 } from "path";
11095
11146
  function loadSkillsFromDir(skillsDir, scope) {
11096
11147
  if (!existsSync26(skillsDir)) {
11097
11148
  return [];
@@ -11101,11 +11152,11 @@ function loadSkillsFromDir(skillsDir, scope) {
11101
11152
  for (const entry of entries) {
11102
11153
  if (entry.name.startsWith("."))
11103
11154
  continue;
11104
- const skillPath = join35(skillsDir, entry.name);
11155
+ const skillPath = join36(skillsDir, entry.name);
11105
11156
  if (!entry.isDirectory() && !entry.isSymbolicLink())
11106
11157
  continue;
11107
11158
  const resolvedPath = resolveSymlink(skillPath);
11108
- const skillMdPath = join35(resolvedPath, "SKILL.md");
11159
+ const skillMdPath = join36(resolvedPath, "SKILL.md");
11109
11160
  if (!existsSync26(skillMdPath))
11110
11161
  continue;
11111
11162
  try {
@@ -11143,7 +11194,7 @@ $ARGUMENTS
11143
11194
  return skills;
11144
11195
  }
11145
11196
  function loadUserSkillsAsCommands() {
11146
- const userSkillsDir = join35(homedir13(), ".claude", "skills");
11197
+ const userSkillsDir = join36(homedir13(), ".claude", "skills");
11147
11198
  const skills = loadSkillsFromDir(userSkillsDir, "user");
11148
11199
  return skills.reduce((acc, skill) => {
11149
11200
  acc[skill.name] = skill.definition;
@@ -11151,7 +11202,7 @@ function loadUserSkillsAsCommands() {
11151
11202
  }, {});
11152
11203
  }
11153
11204
  function loadProjectSkillsAsCommands() {
11154
- const projectSkillsDir = join35(process.cwd(), ".claude", "skills");
11205
+ const projectSkillsDir = join36(process.cwd(), ".claude", "skills");
11155
11206
  const skills = loadSkillsFromDir(projectSkillsDir, "project");
11156
11207
  return skills.reduce((acc, skill) => {
11157
11208
  acc[skill.name] = skill.definition;
@@ -11161,7 +11212,7 @@ function loadProjectSkillsAsCommands() {
11161
11212
  // src/features/claude-code-agent-loader/loader.ts
11162
11213
  import { existsSync as existsSync27, readdirSync as readdirSync9, readFileSync as readFileSync17 } from "fs";
11163
11214
  import { homedir as homedir14 } from "os";
11164
- import { join as join36, basename as basename2 } from "path";
11215
+ import { join as join37, basename as basename2 } from "path";
11165
11216
  function parseToolsConfig(toolsStr) {
11166
11217
  if (!toolsStr)
11167
11218
  return;
@@ -11183,7 +11234,7 @@ function loadAgentsFromDir(agentsDir, scope) {
11183
11234
  for (const entry of entries) {
11184
11235
  if (!isMarkdownFile(entry))
11185
11236
  continue;
11186
- const agentPath = join36(agentsDir, entry.name);
11237
+ const agentPath = join37(agentsDir, entry.name);
11187
11238
  const agentName = basename2(entry.name, ".md");
11188
11239
  try {
11189
11240
  const content = readFileSync17(agentPath, "utf-8");
@@ -11213,7 +11264,7 @@ function loadAgentsFromDir(agentsDir, scope) {
11213
11264
  return agents;
11214
11265
  }
11215
11266
  function loadUserAgents() {
11216
- const userAgentsDir = join36(homedir14(), ".claude", "agents");
11267
+ const userAgentsDir = join37(homedir14(), ".claude", "agents");
11217
11268
  const agents = loadAgentsFromDir(userAgentsDir, "user");
11218
11269
  const result = {};
11219
11270
  for (const agent of agents) {
@@ -11222,7 +11273,7 @@ function loadUserAgents() {
11222
11273
  return result;
11223
11274
  }
11224
11275
  function loadProjectAgents() {
11225
- const projectAgentsDir = join36(process.cwd(), ".claude", "agents");
11276
+ const projectAgentsDir = join37(process.cwd(), ".claude", "agents");
11226
11277
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
11227
11278
  const result = {};
11228
11279
  for (const agent of agents) {
@@ -11233,7 +11284,7 @@ function loadProjectAgents() {
11233
11284
  // src/features/claude-code-mcp-loader/loader.ts
11234
11285
  import { existsSync as existsSync28 } from "fs";
11235
11286
  import { homedir as homedir15 } from "os";
11236
- import { join as join37 } from "path";
11287
+ import { join as join38 } from "path";
11237
11288
 
11238
11289
  // src/features/claude-code-mcp-loader/env-expander.ts
11239
11290
  function expandEnvVars(value) {
@@ -11302,9 +11353,9 @@ function getMcpConfigPaths() {
11302
11353
  const home = homedir15();
11303
11354
  const cwd = process.cwd();
11304
11355
  return [
11305
- { path: join37(home, ".claude", ".mcp.json"), scope: "user" },
11306
- { path: join37(cwd, ".mcp.json"), scope: "project" },
11307
- { path: join37(cwd, ".claude", ".mcp.json"), scope: "local" }
11356
+ { path: join38(home, ".claude", ".mcp.json"), scope: "user" },
11357
+ { path: join38(cwd, ".mcp.json"), scope: "project" },
11358
+ { path: join38(cwd, ".claude", ".mcp.json"), scope: "local" }
11308
11359
  ];
11309
11360
  }
11310
11361
  async function loadMcpConfigFile(filePath) {
@@ -11323,13 +11374,13 @@ async function loadMcpConfigs() {
11323
11374
  const servers = {};
11324
11375
  const loadedServers = [];
11325
11376
  const paths = getMcpConfigPaths();
11326
- for (const { path: path7, scope } of paths) {
11327
- const config = await loadMcpConfigFile(path7);
11377
+ for (const { path: path8, scope } of paths) {
11378
+ const config = await loadMcpConfigFile(path8);
11328
11379
  if (!config?.mcpServers)
11329
11380
  continue;
11330
11381
  for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
11331
11382
  if (serverConfig.disabled) {
11332
- log(`Skipping disabled MCP server "${name}"`, { path: path7 });
11383
+ log(`Skipping disabled MCP server "${name}"`, { path: path8 });
11333
11384
  continue;
11334
11385
  }
11335
11386
  try {
@@ -11340,7 +11391,7 @@ async function loadMcpConfigs() {
11340
11391
  loadedServers.splice(existingIndex, 1);
11341
11392
  }
11342
11393
  loadedServers.push({ name, scope, config: transformed });
11343
- log(`Loaded MCP server "${name}" from ${scope}`, { path: path7 });
11394
+ log(`Loaded MCP server "${name}" from ${scope}`, { path: path8 });
11344
11395
  } catch (error) {
11345
11396
  log(`Failed to transform MCP server "${name}"`, error);
11346
11397
  }
@@ -11644,13 +11695,13 @@ var EXT_TO_LANG = {
11644
11695
  };
11645
11696
  // src/tools/lsp/config.ts
11646
11697
  import { existsSync as existsSync29, readFileSync as readFileSync18 } from "fs";
11647
- import { join as join38 } from "path";
11698
+ import { join as join39 } from "path";
11648
11699
  import { homedir as homedir16 } from "os";
11649
- function loadJsonFile(path7) {
11650
- if (!existsSync29(path7))
11700
+ function loadJsonFile(path8) {
11701
+ if (!existsSync29(path8))
11651
11702
  return null;
11652
11703
  try {
11653
- return JSON.parse(readFileSync18(path7, "utf-8"));
11704
+ return JSON.parse(readFileSync18(path8, "utf-8"));
11654
11705
  } catch {
11655
11706
  return null;
11656
11707
  }
@@ -11658,9 +11709,9 @@ function loadJsonFile(path7) {
11658
11709
  function getConfigPaths2() {
11659
11710
  const cwd = process.cwd();
11660
11711
  return {
11661
- project: join38(cwd, ".opencode", "oh-my-opencode.json"),
11662
- user: join38(homedir16(), ".config", "opencode", "oh-my-opencode.json"),
11663
- opencode: join38(homedir16(), ".config", "opencode", "opencode.json")
11712
+ project: join39(cwd, ".opencode", "oh-my-opencode.json"),
11713
+ user: join39(homedir16(), ".config", "opencode", "oh-my-opencode.json"),
11714
+ opencode: join39(homedir16(), ".config", "opencode", "opencode.json")
11664
11715
  };
11665
11716
  }
11666
11717
  function loadAllConfigs() {
@@ -11756,18 +11807,18 @@ function isServerInstalled(command) {
11756
11807
  const pathSeparator = isWindows2 ? ";" : ":";
11757
11808
  const paths = pathEnv.split(pathSeparator);
11758
11809
  for (const p of paths) {
11759
- if (existsSync29(join38(p, cmd)) || existsSync29(join38(p, cmd + ext))) {
11810
+ if (existsSync29(join39(p, cmd)) || existsSync29(join39(p, cmd + ext))) {
11760
11811
  return true;
11761
11812
  }
11762
11813
  }
11763
11814
  const cwd = process.cwd();
11764
11815
  const additionalPaths = [
11765
- join38(cwd, "node_modules", ".bin", cmd),
11766
- join38(cwd, "node_modules", ".bin", cmd + ext),
11767
- join38(homedir16(), ".config", "opencode", "bin", cmd),
11768
- join38(homedir16(), ".config", "opencode", "bin", cmd + ext),
11769
- join38(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd),
11770
- join38(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
11816
+ join39(cwd, "node_modules", ".bin", cmd),
11817
+ join39(cwd, "node_modules", ".bin", cmd + ext),
11818
+ join39(homedir16(), ".config", "opencode", "bin", cmd),
11819
+ join39(homedir16(), ".config", "opencode", "bin", cmd + ext),
11820
+ join39(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd),
11821
+ join39(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
11771
11822
  ];
11772
11823
  for (const p of additionalPaths) {
11773
11824
  if (existsSync29(p)) {
@@ -13379,10 +13430,10 @@ function mergeDefs(...defs) {
13379
13430
  function cloneDef(schema) {
13380
13431
  return mergeDefs(schema._zod.def);
13381
13432
  }
13382
- function getElementAtPath(obj, path7) {
13383
- if (!path7)
13433
+ function getElementAtPath(obj, path8) {
13434
+ if (!path8)
13384
13435
  return obj;
13385
- return path7.reduce((acc, key) => acc?.[key], obj);
13436
+ return path8.reduce((acc, key) => acc?.[key], obj);
13386
13437
  }
13387
13438
  function promiseAllObject(promisesObj) {
13388
13439
  const keys = Object.keys(promisesObj);
@@ -13741,11 +13792,11 @@ function aborted(x, startIndex = 0) {
13741
13792
  }
13742
13793
  return false;
13743
13794
  }
13744
- function prefixIssues(path7, issues) {
13795
+ function prefixIssues(path8, issues) {
13745
13796
  return issues.map((iss) => {
13746
13797
  var _a;
13747
13798
  (_a = iss).path ?? (_a.path = []);
13748
- iss.path.unshift(path7);
13799
+ iss.path.unshift(path8);
13749
13800
  return iss;
13750
13801
  });
13751
13802
  }
@@ -13913,7 +13964,7 @@ function treeifyError(error, _mapper) {
13913
13964
  return issue2.message;
13914
13965
  };
13915
13966
  const result = { errors: [] };
13916
- const processError = (error2, path7 = []) => {
13967
+ const processError = (error2, path8 = []) => {
13917
13968
  var _a, _b;
13918
13969
  for (const issue2 of error2.issues) {
13919
13970
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -13923,7 +13974,7 @@ function treeifyError(error, _mapper) {
13923
13974
  } else if (issue2.code === "invalid_element") {
13924
13975
  processError({ issues: issue2.issues }, issue2.path);
13925
13976
  } else {
13926
- const fullpath = [...path7, ...issue2.path];
13977
+ const fullpath = [...path8, ...issue2.path];
13927
13978
  if (fullpath.length === 0) {
13928
13979
  result.errors.push(mapper(issue2));
13929
13980
  continue;
@@ -13955,8 +14006,8 @@ function treeifyError(error, _mapper) {
13955
14006
  }
13956
14007
  function toDotPath(_path) {
13957
14008
  const segs = [];
13958
- const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
13959
- for (const seg of path7) {
14009
+ const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
14010
+ for (const seg of path8) {
13960
14011
  if (typeof seg === "number")
13961
14012
  segs.push(`[${seg}]`);
13962
14013
  else if (typeof seg === "symbol")
@@ -25299,13 +25350,13 @@ var lsp_code_action_resolve = tool({
25299
25350
  });
25300
25351
  // src/tools/ast-grep/constants.ts
25301
25352
  import { createRequire as createRequire4 } from "module";
25302
- import { dirname as dirname6, join as join40 } from "path";
25353
+ import { dirname as dirname6, join as join41 } from "path";
25303
25354
  import { existsSync as existsSync32, statSync as statSync4 } from "fs";
25304
25355
 
25305
25356
  // src/tools/ast-grep/downloader.ts
25306
25357
  var {spawn: spawn5 } = globalThis.Bun;
25307
25358
  import { existsSync as existsSync31, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
25308
- import { join as join39 } from "path";
25359
+ import { join as join40 } from "path";
25309
25360
  import { homedir as homedir17 } from "os";
25310
25361
  import { createRequire as createRequire3 } from "module";
25311
25362
  var REPO2 = "ast-grep/ast-grep";
@@ -25331,18 +25382,18 @@ var PLATFORM_MAP2 = {
25331
25382
  function getCacheDir3() {
25332
25383
  if (process.platform === "win32") {
25333
25384
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
25334
- const base2 = localAppData || join39(homedir17(), "AppData", "Local");
25335
- return join39(base2, "oh-my-opencode", "bin");
25385
+ const base2 = localAppData || join40(homedir17(), "AppData", "Local");
25386
+ return join40(base2, "oh-my-opencode", "bin");
25336
25387
  }
25337
25388
  const xdgCache2 = process.env.XDG_CACHE_HOME;
25338
- const base = xdgCache2 || join39(homedir17(), ".cache");
25339
- return join39(base, "oh-my-opencode", "bin");
25389
+ const base = xdgCache2 || join40(homedir17(), ".cache");
25390
+ return join40(base, "oh-my-opencode", "bin");
25340
25391
  }
25341
25392
  function getBinaryName3() {
25342
25393
  return process.platform === "win32" ? "sg.exe" : "sg";
25343
25394
  }
25344
25395
  function getCachedBinaryPath2() {
25345
- const binaryPath = join39(getCacheDir3(), getBinaryName3());
25396
+ const binaryPath = join40(getCacheDir3(), getBinaryName3());
25346
25397
  return existsSync31(binaryPath) ? binaryPath : null;
25347
25398
  }
25348
25399
  async function extractZip2(archivePath, destDir) {
@@ -25369,12 +25420,12 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
25369
25420
  }
25370
25421
  const cacheDir = getCacheDir3();
25371
25422
  const binaryName = getBinaryName3();
25372
- const binaryPath = join39(cacheDir, binaryName);
25423
+ const binaryPath = join40(cacheDir, binaryName);
25373
25424
  if (existsSync31(binaryPath)) {
25374
25425
  return binaryPath;
25375
25426
  }
25376
- const { arch, os: os5 } = platformInfo;
25377
- const assetName = `app-${arch}-${os5}.zip`;
25427
+ const { arch, os: os6 } = platformInfo;
25428
+ const assetName = `app-${arch}-${os6}.zip`;
25378
25429
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
25379
25430
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
25380
25431
  try {
@@ -25385,7 +25436,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
25385
25436
  if (!response2.ok) {
25386
25437
  throw new Error(`HTTP ${response2.status}: ${response2.statusText}`);
25387
25438
  }
25388
- const archivePath = join39(cacheDir, assetName);
25439
+ const archivePath = join40(cacheDir, assetName);
25389
25440
  const arrayBuffer = await response2.arrayBuffer();
25390
25441
  await Bun.write(archivePath, arrayBuffer);
25391
25442
  await extractZip2(archivePath, cacheDir);
@@ -25443,7 +25494,7 @@ function findSgCliPathSync() {
25443
25494
  const require2 = createRequire4(import.meta.url);
25444
25495
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
25445
25496
  const cliDir = dirname6(cliPkgPath);
25446
- const sgPath = join40(cliDir, binaryName);
25497
+ const sgPath = join41(cliDir, binaryName);
25447
25498
  if (existsSync32(sgPath) && isValidBinary(sgPath)) {
25448
25499
  return sgPath;
25449
25500
  }
@@ -25455,7 +25506,7 @@ function findSgCliPathSync() {
25455
25506
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
25456
25507
  const pkgDir = dirname6(pkgPath);
25457
25508
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
25458
- const binaryPath = join40(pkgDir, astGrepName);
25509
+ const binaryPath = join41(pkgDir, astGrepName);
25459
25510
  if (existsSync32(binaryPath) && isValidBinary(binaryPath)) {
25460
25511
  return binaryPath;
25461
25512
  }
@@ -25463,9 +25514,9 @@ function findSgCliPathSync() {
25463
25514
  }
25464
25515
  if (process.platform === "darwin") {
25465
25516
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
25466
- for (const path7 of homebrewPaths) {
25467
- if (existsSync32(path7) && isValidBinary(path7)) {
25468
- return path7;
25517
+ for (const path8 of homebrewPaths) {
25518
+ if (existsSync32(path8) && isValidBinary(path8)) {
25519
+ return path8;
25469
25520
  }
25470
25521
  }
25471
25522
  }
@@ -25483,8 +25534,8 @@ function getSgCliPath() {
25483
25534
  }
25484
25535
  return "sg";
25485
25536
  }
25486
- function setSgCliPath(path7) {
25487
- resolvedCliPath2 = path7;
25537
+ function setSgCliPath(path8) {
25538
+ resolvedCliPath2 = path8;
25488
25539
  }
25489
25540
  var SG_CLI_PATH = getSgCliPath();
25490
25541
  var CLI_LANGUAGES = [
@@ -25831,19 +25882,19 @@ var {spawn: spawn7 } = globalThis.Bun;
25831
25882
 
25832
25883
  // src/tools/grep/constants.ts
25833
25884
  import { existsSync as existsSync35 } from "fs";
25834
- import { join as join42, dirname as dirname7 } from "path";
25885
+ import { join as join43, dirname as dirname7 } from "path";
25835
25886
  import { spawnSync } from "child_process";
25836
25887
 
25837
25888
  // src/tools/grep/downloader.ts
25838
25889
  import { existsSync as existsSync34, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync10 } from "fs";
25839
- import { join as join41 } from "path";
25890
+ import { join as join42 } from "path";
25840
25891
  function getInstallDir() {
25841
25892
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
25842
- return join41(homeDir, ".cache", "oh-my-opencode", "bin");
25893
+ return join42(homeDir, ".cache", "oh-my-opencode", "bin");
25843
25894
  }
25844
25895
  function getRgPath() {
25845
25896
  const isWindows2 = process.platform === "win32";
25846
- return join41(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
25897
+ return join42(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
25847
25898
  }
25848
25899
  function getInstalledRipgrepPath() {
25849
25900
  const rgPath = getRgPath();
@@ -25870,10 +25921,10 @@ function getOpenCodeBundledRg() {
25870
25921
  const isWindows2 = process.platform === "win32";
25871
25922
  const rgName = isWindows2 ? "rg.exe" : "rg";
25872
25923
  const candidates = [
25873
- join42(execDir, rgName),
25874
- join42(execDir, "bin", rgName),
25875
- join42(execDir, "..", "bin", rgName),
25876
- join42(execDir, "..", "libexec", rgName)
25924
+ join43(execDir, rgName),
25925
+ join43(execDir, "bin", rgName),
25926
+ join43(execDir, "..", "bin", rgName),
25927
+ join43(execDir, "..", "libexec", rgName)
25877
25928
  ];
25878
25929
  for (const candidate of candidates) {
25879
25930
  if (existsSync35(candidate)) {
@@ -26281,7 +26332,7 @@ var glob = tool({
26281
26332
  // src/tools/slashcommand/tools.ts
26282
26333
  import { existsSync as existsSync36, readdirSync as readdirSync11, readFileSync as readFileSync21 } from "fs";
26283
26334
  import { homedir as homedir18 } from "os";
26284
- import { join as join43, basename as basename3, dirname as dirname8 } from "path";
26335
+ import { join as join44, basename as basename3, dirname as dirname8 } from "path";
26285
26336
  function discoverCommandsFromDir(commandsDir, scope) {
26286
26337
  if (!existsSync36(commandsDir)) {
26287
26338
  return [];
@@ -26291,7 +26342,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
26291
26342
  for (const entry of entries) {
26292
26343
  if (!isMarkdownFile(entry))
26293
26344
  continue;
26294
- const commandPath = join43(commandsDir, entry.name);
26345
+ const commandPath = join44(commandsDir, entry.name);
26295
26346
  const commandName = basename3(entry.name, ".md");
26296
26347
  try {
26297
26348
  const content = readFileSync21(commandPath, "utf-8");
@@ -26319,10 +26370,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
26319
26370
  return commands;
26320
26371
  }
26321
26372
  function discoverCommandsSync() {
26322
- const userCommandsDir = join43(homedir18(), ".claude", "commands");
26323
- const projectCommandsDir = join43(process.cwd(), ".claude", "commands");
26324
- const opencodeGlobalDir = join43(homedir18(), ".config", "opencode", "command");
26325
- const opencodeProjectDir = join43(process.cwd(), ".opencode", "command");
26373
+ const userCommandsDir = join44(homedir18(), ".claude", "commands");
26374
+ const projectCommandsDir = join44(process.cwd(), ".claude", "commands");
26375
+ const opencodeGlobalDir = join44(homedir18(), ".config", "opencode", "command");
26376
+ const opencodeProjectDir = join44(process.cwd(), ".opencode", "command");
26326
26377
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
26327
26378
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
26328
26379
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
@@ -26445,6 +26496,484 @@ Provide a command name to execute.`;
26445
26496
  Try a different command name.`;
26446
26497
  }
26447
26498
  });
26499
+ // src/tools/session-manager/constants.ts
26500
+ import { join as join45 } from "path";
26501
+ import { homedir as homedir19 } from "os";
26502
+ var OPENCODE_STORAGE9 = getOpenCodeStorageDir();
26503
+ var MESSAGE_STORAGE4 = join45(OPENCODE_STORAGE9, "message");
26504
+ var PART_STORAGE4 = join45(OPENCODE_STORAGE9, "part");
26505
+ var TODO_DIR2 = join45(homedir19(), ".claude", "todos");
26506
+ var TRANSCRIPT_DIR2 = join45(homedir19(), ".claude", "transcripts");
26507
+ var SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.
26508
+
26509
+ Returns a list of available session IDs with metadata including message count, date range, and agents used.
26510
+
26511
+ Arguments:
26512
+ - limit (optional): Maximum number of sessions to return
26513
+ - from_date (optional): Filter sessions from this date (ISO 8601 format)
26514
+ - to_date (optional): Filter sessions until this date (ISO 8601 format)
26515
+
26516
+ Example output:
26517
+ | Session ID | Messages | First | Last | Agents |
26518
+ |------------|----------|-------|------|--------|
26519
+ | ses_abc123 | 45 | 2025-12-20 | 2025-12-24 | build, oracle |
26520
+ | ses_def456 | 12 | 2025-12-19 | 2025-12-19 | build |`;
26521
+ var SESSION_READ_DESCRIPTION = `Read messages and history from an OpenCode session.
26522
+
26523
+ Returns a formatted view of session messages with role, timestamp, and content. Optionally includes todos and transcript data.
26524
+
26525
+ Arguments:
26526
+ - session_id (required): Session ID to read
26527
+ - include_todos (optional): Include todo list if available (default: false)
26528
+ - include_transcript (optional): Include transcript log if available (default: false)
26529
+ - limit (optional): Maximum number of messages to return (default: all)
26530
+
26531
+ Example output:
26532
+ Session: ses_abc123
26533
+ Messages: 45
26534
+ Date Range: 2025-12-20 to 2025-12-24
26535
+
26536
+ [Message 1] user (2025-12-20 10:30:00)
26537
+ Hello, can you help me with...
26538
+
26539
+ [Message 2] assistant (2025-12-20 10:30:15)
26540
+ Of course! Let me help you with...`;
26541
+ var SESSION_SEARCH_DESCRIPTION = `Search for content within OpenCode session messages.
26542
+
26543
+ Performs full-text search across session messages and returns matching excerpts with context.
26544
+
26545
+ Arguments:
26546
+ - query (required): Search query string
26547
+ - session_id (optional): Search within specific session only (default: all sessions)
26548
+ - case_sensitive (optional): Case-sensitive search (default: false)
26549
+ - limit (optional): Maximum number of results to return (default: 20)
26550
+
26551
+ Example output:
26552
+ Found 3 matches across 2 sessions:
26553
+
26554
+ [ses_abc123] Message msg_001 (user)
26555
+ ...implement the **session manager** tool...
26556
+
26557
+ [ses_abc123] Message msg_005 (assistant)
26558
+ ...I'll create a **session manager** with full search...
26559
+
26560
+ [ses_def456] Message msg_012 (user)
26561
+ ...use the **session manager** to find...`;
26562
+ var SESSION_INFO_DESCRIPTION = `Get metadata and statistics about an OpenCode session.
26563
+
26564
+ Returns detailed information about a session including message count, date range, agents used, and available data sources.
26565
+
26566
+ Arguments:
26567
+ - session_id (required): Session ID to inspect
26568
+
26569
+ Example output:
26570
+ Session ID: ses_abc123
26571
+ Messages: 45
26572
+ Date Range: 2025-12-20 10:30:00 to 2025-12-24 15:45:30
26573
+ Duration: 4 days, 5 hours
26574
+ Agents Used: build, oracle, librarian
26575
+ Has Todos: Yes (12 items, 8 completed)
26576
+ Has Transcript: Yes (234 entries)`;
26577
+
26578
+ // src/tools/session-manager/storage.ts
26579
+ import { existsSync as existsSync37, readdirSync as readdirSync12, readFileSync as readFileSync22 } from "fs";
26580
+ import { join as join46 } from "path";
26581
+ function getAllSessions() {
26582
+ if (!existsSync37(MESSAGE_STORAGE4))
26583
+ return [];
26584
+ const sessions = [];
26585
+ function scanDirectory(dir) {
26586
+ try {
26587
+ for (const entry of readdirSync12(dir, { withFileTypes: true })) {
26588
+ if (entry.isDirectory()) {
26589
+ const sessionPath = join46(dir, entry.name);
26590
+ const files = readdirSync12(sessionPath);
26591
+ if (files.some((f) => f.endsWith(".json"))) {
26592
+ sessions.push(entry.name);
26593
+ } else {
26594
+ scanDirectory(sessionPath);
26595
+ }
26596
+ }
26597
+ }
26598
+ } catch {
26599
+ return;
26600
+ }
26601
+ }
26602
+ scanDirectory(MESSAGE_STORAGE4);
26603
+ return [...new Set(sessions)];
26604
+ }
26605
+ function getMessageDir5(sessionID) {
26606
+ if (!existsSync37(MESSAGE_STORAGE4))
26607
+ return "";
26608
+ const directPath = join46(MESSAGE_STORAGE4, sessionID);
26609
+ if (existsSync37(directPath)) {
26610
+ return directPath;
26611
+ }
26612
+ for (const dir of readdirSync12(MESSAGE_STORAGE4)) {
26613
+ const sessionPath = join46(MESSAGE_STORAGE4, dir, sessionID);
26614
+ if (existsSync37(sessionPath)) {
26615
+ return sessionPath;
26616
+ }
26617
+ }
26618
+ return "";
26619
+ }
26620
+ function sessionExists(sessionID) {
26621
+ return getMessageDir5(sessionID) !== "";
26622
+ }
26623
+ function readSessionMessages(sessionID) {
26624
+ const messageDir = getMessageDir5(sessionID);
26625
+ if (!messageDir || !existsSync37(messageDir))
26626
+ return [];
26627
+ const messages = [];
26628
+ for (const file2 of readdirSync12(messageDir)) {
26629
+ if (!file2.endsWith(".json"))
26630
+ continue;
26631
+ try {
26632
+ const content = readFileSync22(join46(messageDir, file2), "utf-8");
26633
+ const meta = JSON.parse(content);
26634
+ const parts = readParts2(meta.id);
26635
+ messages.push({
26636
+ id: meta.id,
26637
+ role: meta.role,
26638
+ agent: meta.agent,
26639
+ time: meta.time,
26640
+ parts
26641
+ });
26642
+ } catch {
26643
+ continue;
26644
+ }
26645
+ }
26646
+ return messages.sort((a, b) => {
26647
+ const aTime = a.time?.created ?? 0;
26648
+ const bTime = b.time?.created ?? 0;
26649
+ if (aTime !== bTime)
26650
+ return aTime - bTime;
26651
+ return a.id.localeCompare(b.id);
26652
+ });
26653
+ }
26654
+ function readParts2(messageID) {
26655
+ const partDir = join46(PART_STORAGE4, messageID);
26656
+ if (!existsSync37(partDir))
26657
+ return [];
26658
+ const parts = [];
26659
+ for (const file2 of readdirSync12(partDir)) {
26660
+ if (!file2.endsWith(".json"))
26661
+ continue;
26662
+ try {
26663
+ const content = readFileSync22(join46(partDir, file2), "utf-8");
26664
+ parts.push(JSON.parse(content));
26665
+ } catch {
26666
+ continue;
26667
+ }
26668
+ }
26669
+ return parts.sort((a, b) => a.id.localeCompare(b.id));
26670
+ }
26671
+ function readSessionTodos(sessionID) {
26672
+ if (!existsSync37(TODO_DIR2))
26673
+ return [];
26674
+ const todoFiles = readdirSync12(TODO_DIR2).filter((f) => f.includes(sessionID) && f.endsWith(".json"));
26675
+ for (const file2 of todoFiles) {
26676
+ try {
26677
+ const content = readFileSync22(join46(TODO_DIR2, file2), "utf-8");
26678
+ const data = JSON.parse(content);
26679
+ if (Array.isArray(data)) {
26680
+ return data.map((item) => ({
26681
+ id: item.id || "",
26682
+ content: item.content || "",
26683
+ status: item.status || "pending",
26684
+ priority: item.priority
26685
+ }));
26686
+ }
26687
+ } catch {
26688
+ continue;
26689
+ }
26690
+ }
26691
+ return [];
26692
+ }
26693
+ function readSessionTranscript(sessionID) {
26694
+ if (!existsSync37(TRANSCRIPT_DIR2))
26695
+ return 0;
26696
+ const transcriptFile = join46(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
26697
+ if (!existsSync37(transcriptFile))
26698
+ return 0;
26699
+ try {
26700
+ const content = readFileSync22(transcriptFile, "utf-8");
26701
+ return content.trim().split(`
26702
+ `).filter(Boolean).length;
26703
+ } catch {
26704
+ return 0;
26705
+ }
26706
+ }
26707
+ function getSessionInfo(sessionID) {
26708
+ const messages = readSessionMessages(sessionID);
26709
+ if (messages.length === 0)
26710
+ return null;
26711
+ const agentsUsed = new Set;
26712
+ let firstMessage;
26713
+ let lastMessage;
26714
+ for (const msg of messages) {
26715
+ if (msg.agent)
26716
+ agentsUsed.add(msg.agent);
26717
+ if (msg.time?.created) {
26718
+ const date5 = new Date(msg.time.created);
26719
+ if (!firstMessage || date5 < firstMessage)
26720
+ firstMessage = date5;
26721
+ if (!lastMessage || date5 > lastMessage)
26722
+ lastMessage = date5;
26723
+ }
26724
+ }
26725
+ const todos = readSessionTodos(sessionID);
26726
+ const transcriptEntries = readSessionTranscript(sessionID);
26727
+ return {
26728
+ id: sessionID,
26729
+ message_count: messages.length,
26730
+ first_message: firstMessage,
26731
+ last_message: lastMessage,
26732
+ agents_used: Array.from(agentsUsed),
26733
+ has_todos: todos.length > 0,
26734
+ has_transcript: transcriptEntries > 0,
26735
+ todos,
26736
+ transcript_entries: transcriptEntries
26737
+ };
26738
+ }
26739
+
26740
+ // src/tools/session-manager/utils.ts
26741
+ function formatSessionList(sessionIDs) {
26742
+ if (sessionIDs.length === 0) {
26743
+ return "No sessions found.";
26744
+ }
26745
+ const infos = sessionIDs.map((id) => getSessionInfo(id)).filter((info) => info !== null);
26746
+ if (infos.length === 0) {
26747
+ return "No valid sessions found.";
26748
+ }
26749
+ const headers = ["Session ID", "Messages", "First", "Last", "Agents"];
26750
+ const rows = infos.map((info) => [
26751
+ info.id,
26752
+ info.message_count.toString(),
26753
+ info.first_message?.toISOString().split("T")[0] ?? "N/A",
26754
+ info.last_message?.toISOString().split("T")[0] ?? "N/A",
26755
+ info.agents_used.join(", ") || "none"
26756
+ ]);
26757
+ const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
26758
+ const formatRow = (cells) => {
26759
+ return "| " + cells.map((cell, i) => cell.padEnd(colWidths[i])).join(" | ").trim() + " |";
26760
+ };
26761
+ const separator = "|" + colWidths.map((w) => "-".repeat(w + 2)).join("|") + "|";
26762
+ return [formatRow(headers), separator, ...rows.map(formatRow)].join(`
26763
+ `);
26764
+ }
26765
+ function formatSessionMessages(messages, includeTodos, todos) {
26766
+ if (messages.length === 0) {
26767
+ return "No messages found in this session.";
26768
+ }
26769
+ const lines = [];
26770
+ for (const msg of messages) {
26771
+ const timestamp = msg.time?.created ? new Date(msg.time.created).toISOString() : "Unknown time";
26772
+ const agent = msg.agent ? ` (${msg.agent})` : "";
26773
+ lines.push(`
26774
+ [${msg.role}${agent}] ${timestamp}`);
26775
+ for (const part of msg.parts) {
26776
+ if (part.type === "text" && part.text) {
26777
+ lines.push(part.text.trim());
26778
+ } else if (part.type === "thinking" && part.thinking) {
26779
+ lines.push(`[thinking] ${part.thinking.substring(0, 200)}...`);
26780
+ } else if ((part.type === "tool_use" || part.type === "tool") && part.tool) {
26781
+ const input = part.input ? JSON.stringify(part.input).substring(0, 100) : "";
26782
+ lines.push(`[tool: ${part.tool}] ${input}`);
26783
+ } else if (part.type === "tool_result") {
26784
+ const output = part.output ? part.output.substring(0, 200) : "";
26785
+ lines.push(`[tool result] ${output}...`);
26786
+ }
26787
+ }
26788
+ }
26789
+ if (includeTodos && todos && todos.length > 0) {
26790
+ lines.push(`
26791
+
26792
+ === Todos ===`);
26793
+ for (const todo of todos) {
26794
+ const status = todo.status === "completed" ? "\u2713" : todo.status === "in_progress" ? "\u2192" : "\u25CB";
26795
+ lines.push(`${status} [${todo.status}] ${todo.content}`);
26796
+ }
26797
+ }
26798
+ return lines.join(`
26799
+ `);
26800
+ }
26801
+ function formatSessionInfo(info) {
26802
+ const lines = [
26803
+ `Session ID: ${info.id}`,
26804
+ `Messages: ${info.message_count}`,
26805
+ `Date Range: ${info.first_message?.toISOString() ?? "N/A"} to ${info.last_message?.toISOString() ?? "N/A"}`,
26806
+ `Agents Used: ${info.agents_used.join(", ") || "none"}`,
26807
+ `Has Todos: ${info.has_todos ? `Yes (${info.todos?.length ?? 0} items)` : "No"}`,
26808
+ `Has Transcript: ${info.has_transcript ? `Yes (${info.transcript_entries} entries)` : "No"}`
26809
+ ];
26810
+ if (info.first_message && info.last_message) {
26811
+ const duration3 = info.last_message.getTime() - info.first_message.getTime();
26812
+ const days = Math.floor(duration3 / (1000 * 60 * 60 * 24));
26813
+ const hours = Math.floor(duration3 % (1000 * 60 * 60 * 24) / (1000 * 60 * 60));
26814
+ if (days > 0 || hours > 0) {
26815
+ lines.push(`Duration: ${days} days, ${hours} hours`);
26816
+ }
26817
+ }
26818
+ return lines.join(`
26819
+ `);
26820
+ }
26821
+ function formatSearchResults(results) {
26822
+ if (results.length === 0) {
26823
+ return "No matches found.";
26824
+ }
26825
+ const lines = [`Found ${results.length} matches:
26826
+ `];
26827
+ for (const result of results) {
26828
+ const timestamp = result.timestamp ? new Date(result.timestamp).toISOString() : "";
26829
+ lines.push(`[${result.session_id}] ${result.message_id} (${result.role}) ${timestamp}`);
26830
+ lines.push(` ${result.excerpt}`);
26831
+ lines.push(` Matches: ${result.match_count}
26832
+ `);
26833
+ }
26834
+ return lines.join(`
26835
+ `);
26836
+ }
26837
+ function filterSessionsByDate(sessionIDs, fromDate, toDate) {
26838
+ if (!fromDate && !toDate)
26839
+ return sessionIDs;
26840
+ const from = fromDate ? new Date(fromDate) : null;
26841
+ const to = toDate ? new Date(toDate) : null;
26842
+ return sessionIDs.filter((id) => {
26843
+ const info = getSessionInfo(id);
26844
+ if (!info || !info.last_message)
26845
+ return false;
26846
+ if (from && info.last_message < from)
26847
+ return false;
26848
+ if (to && info.last_message > to)
26849
+ return false;
26850
+ return true;
26851
+ });
26852
+ }
26853
+ function searchInSession(sessionID, query, caseSensitive = false) {
26854
+ const messages = readSessionMessages(sessionID);
26855
+ const results = [];
26856
+ const searchQuery = caseSensitive ? query : query.toLowerCase();
26857
+ for (const msg of messages) {
26858
+ let matchCount = 0;
26859
+ let excerpts = [];
26860
+ for (const part of msg.parts) {
26861
+ if (part.type === "text" && part.text) {
26862
+ const text = caseSensitive ? part.text : part.text.toLowerCase();
26863
+ const matches = text.split(searchQuery).length - 1;
26864
+ if (matches > 0) {
26865
+ matchCount += matches;
26866
+ const index = text.indexOf(searchQuery);
26867
+ if (index !== -1) {
26868
+ const start = Math.max(0, index - 50);
26869
+ const end = Math.min(text.length, index + searchQuery.length + 50);
26870
+ let excerpt = part.text.substring(start, end);
26871
+ if (start > 0)
26872
+ excerpt = "..." + excerpt;
26873
+ if (end < text.length)
26874
+ excerpt = excerpt + "...";
26875
+ excerpts.push(excerpt);
26876
+ }
26877
+ }
26878
+ }
26879
+ }
26880
+ if (matchCount > 0) {
26881
+ results.push({
26882
+ session_id: sessionID,
26883
+ message_id: msg.id,
26884
+ role: msg.role,
26885
+ excerpt: excerpts[0] || "",
26886
+ match_count: matchCount,
26887
+ timestamp: msg.time?.created
26888
+ });
26889
+ }
26890
+ }
26891
+ return results;
26892
+ }
26893
+
26894
+ // src/tools/session-manager/tools.ts
26895
+ var session_list = tool({
26896
+ description: SESSION_LIST_DESCRIPTION,
26897
+ args: {
26898
+ limit: tool.schema.number().optional().describe("Maximum number of sessions to return"),
26899
+ from_date: tool.schema.string().optional().describe("Filter sessions from this date (ISO 8601 format)"),
26900
+ to_date: tool.schema.string().optional().describe("Filter sessions until this date (ISO 8601 format)")
26901
+ },
26902
+ execute: async (args, _context) => {
26903
+ try {
26904
+ let sessions = getAllSessions();
26905
+ if (args.from_date || args.to_date) {
26906
+ sessions = filterSessionsByDate(sessions, args.from_date, args.to_date);
26907
+ }
26908
+ if (args.limit && args.limit > 0) {
26909
+ sessions = sessions.slice(0, args.limit);
26910
+ }
26911
+ return formatSessionList(sessions);
26912
+ } catch (e) {
26913
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
26914
+ }
26915
+ }
26916
+ });
26917
+ var session_read = tool({
26918
+ description: SESSION_READ_DESCRIPTION,
26919
+ args: {
26920
+ session_id: tool.schema.string().describe("Session ID to read"),
26921
+ include_todos: tool.schema.boolean().optional().describe("Include todo list if available (default: false)"),
26922
+ include_transcript: tool.schema.boolean().optional().describe("Include transcript log if available (default: false)"),
26923
+ limit: tool.schema.number().optional().describe("Maximum number of messages to return (default: all)")
26924
+ },
26925
+ execute: async (args, _context) => {
26926
+ try {
26927
+ if (!sessionExists(args.session_id)) {
26928
+ return `Session not found: ${args.session_id}`;
26929
+ }
26930
+ let messages = readSessionMessages(args.session_id);
26931
+ if (args.limit && args.limit > 0) {
26932
+ messages = messages.slice(0, args.limit);
26933
+ }
26934
+ const todos = args.include_todos ? readSessionTodos(args.session_id) : undefined;
26935
+ return formatSessionMessages(messages, args.include_todos, todos);
26936
+ } catch (e) {
26937
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
26938
+ }
26939
+ }
26940
+ });
26941
+ var session_search = tool({
26942
+ description: SESSION_SEARCH_DESCRIPTION,
26943
+ args: {
26944
+ query: tool.schema.string().describe("Search query string"),
26945
+ session_id: tool.schema.string().optional().describe("Search within specific session only (default: all sessions)"),
26946
+ case_sensitive: tool.schema.boolean().optional().describe("Case-sensitive search (default: false)"),
26947
+ limit: tool.schema.number().optional().describe("Maximum number of results to return (default: 20)")
26948
+ },
26949
+ execute: async (args, _context) => {
26950
+ try {
26951
+ const sessions = args.session_id ? [args.session_id] : getAllSessions();
26952
+ const allResults = sessions.flatMap((sid) => searchInSession(sid, args.query, args.case_sensitive));
26953
+ const limited = args.limit && args.limit > 0 ? allResults.slice(0, args.limit) : allResults.slice(0, 20);
26954
+ return formatSearchResults(limited);
26955
+ } catch (e) {
26956
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
26957
+ }
26958
+ }
26959
+ });
26960
+ var session_info = tool({
26961
+ description: SESSION_INFO_DESCRIPTION,
26962
+ args: {
26963
+ session_id: tool.schema.string().describe("Session ID to inspect")
26964
+ },
26965
+ execute: async (args, _context) => {
26966
+ try {
26967
+ const info = getSessionInfo(args.session_id);
26968
+ if (!info) {
26969
+ return `Session not found: ${args.session_id}`;
26970
+ }
26971
+ return formatSessionInfo(info);
26972
+ } catch (e) {
26973
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
26974
+ }
26975
+ }
26976
+ });
26448
26977
  // src/tools/interactive-bash/constants.ts
26449
26978
  var DEFAULT_TIMEOUT_MS4 = 60000;
26450
26979
  var BLOCKED_TMUX_SUBCOMMANDS = [
@@ -26478,12 +27007,12 @@ async function findTmuxPath() {
26478
27007
  return null;
26479
27008
  }
26480
27009
  const stdout = await new Response(proc.stdout).text();
26481
- const path7 = stdout.trim().split(`
27010
+ const path8 = stdout.trim().split(`
26482
27011
  `)[0];
26483
- if (!path7) {
27012
+ if (!path8) {
26484
27013
  return null;
26485
27014
  }
26486
- const verifyProc = spawn9([path7, "-V"], {
27015
+ const verifyProc = spawn9([path8, "-V"], {
26487
27016
  stdout: "pipe",
26488
27017
  stderr: "pipe"
26489
27018
  });
@@ -26491,7 +27020,7 @@ async function findTmuxPath() {
26491
27020
  if (verifyExitCode !== 0) {
26492
27021
  return null;
26493
27022
  }
26494
- return path7;
27023
+ return path8;
26495
27024
  } catch {
26496
27025
  return null;
26497
27026
  }
@@ -26504,9 +27033,9 @@ async function getTmuxPath() {
26504
27033
  return initPromise3;
26505
27034
  }
26506
27035
  initPromise3 = (async () => {
26507
- const path7 = await findTmuxPath();
26508
- tmuxPath = path7;
26509
- return path7;
27036
+ const path8 = await findTmuxPath();
27037
+ tmuxPath = path8;
27038
+ return path8;
26510
27039
  })();
26511
27040
  return initPromise3;
26512
27041
  }
@@ -26596,6 +27125,10 @@ var interactive_bash = tool({
26596
27125
  }
26597
27126
  }
26598
27127
  });
27128
+ // src/tools/background-task/tools.ts
27129
+ import { existsSync as existsSync38, readdirSync as readdirSync13 } from "fs";
27130
+ import { join as join47 } from "path";
27131
+
26599
27132
  // src/tools/background-task/constants.ts
26600
27133
  var BACKGROUND_TASK_DESCRIPTION = `Run agent task in background. Returns task_id immediately; notifies on completion.
26601
27134
 
@@ -26604,6 +27137,19 @@ var BACKGROUND_OUTPUT_DESCRIPTION = `Get output from background task. System not
26604
27137
  var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=true to cancel ALL before final answer.`;
26605
27138
 
26606
27139
  // src/tools/background-task/tools.ts
27140
+ function getMessageDir6(sessionID) {
27141
+ if (!existsSync38(MESSAGE_STORAGE))
27142
+ return null;
27143
+ const directPath = join47(MESSAGE_STORAGE, sessionID);
27144
+ if (existsSync38(directPath))
27145
+ return directPath;
27146
+ for (const dir of readdirSync13(MESSAGE_STORAGE)) {
27147
+ const sessionPath = join47(MESSAGE_STORAGE, dir, sessionID);
27148
+ if (existsSync38(sessionPath))
27149
+ return sessionPath;
27150
+ }
27151
+ return null;
27152
+ }
26607
27153
  function formatDuration(start, end) {
26608
27154
  const duration3 = (end ?? new Date).getTime() - start.getTime();
26609
27155
  const seconds = Math.floor(duration3 / 1000);
@@ -26630,12 +27176,16 @@ function createBackgroundTask(manager) {
26630
27176
  return `\u274C Agent parameter is required. Please specify which agent to use (e.g., "explore", "librarian", "build", etc.)`;
26631
27177
  }
26632
27178
  try {
27179
+ const messageDir = getMessageDir6(toolContext.sessionID);
27180
+ const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
27181
+ const parentModel = prevMessage?.model?.providerID && prevMessage?.model?.modelID ? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID } : undefined;
26633
27182
  const task = await manager.launch({
26634
27183
  description: args.description,
26635
27184
  prompt: args.prompt,
26636
27185
  agent: args.agent.trim(),
26637
27186
  parentSessionID: toolContext.sessionID,
26638
- parentMessageID: toolContext.messageID
27187
+ parentMessageID: toolContext.messageID,
27188
+ parentModel
26639
27189
  });
26640
27190
  return `Background task launched successfully.
26641
27191
 
@@ -27145,20 +27695,24 @@ var builtinTools = {
27145
27695
  ast_grep_replace,
27146
27696
  grep,
27147
27697
  glob,
27148
- slashcommand
27698
+ slashcommand,
27699
+ session_list,
27700
+ session_read,
27701
+ session_search,
27702
+ session_info
27149
27703
  };
27150
27704
  // src/features/background-agent/manager.ts
27151
- import { existsSync as existsSync37, readdirSync as readdirSync12 } from "fs";
27152
- import { join as join44 } from "path";
27153
- function getMessageDir5(sessionID) {
27154
- if (!existsSync37(MESSAGE_STORAGE))
27705
+ import { existsSync as existsSync39, readdirSync as readdirSync14 } from "fs";
27706
+ import { join as join48 } from "path";
27707
+ function getMessageDir7(sessionID) {
27708
+ if (!existsSync39(MESSAGE_STORAGE))
27155
27709
  return null;
27156
- const directPath = join44(MESSAGE_STORAGE, sessionID);
27157
- if (existsSync37(directPath))
27710
+ const directPath = join48(MESSAGE_STORAGE, sessionID);
27711
+ if (existsSync39(directPath))
27158
27712
  return directPath;
27159
- for (const dir of readdirSync12(MESSAGE_STORAGE)) {
27160
- const sessionPath = join44(MESSAGE_STORAGE, dir, sessionID);
27161
- if (existsSync37(sessionPath))
27713
+ for (const dir of readdirSync14(MESSAGE_STORAGE)) {
27714
+ const sessionPath = join48(MESSAGE_STORAGE, dir, sessionID);
27715
+ if (existsSync39(sessionPath))
27162
27716
  return sessionPath;
27163
27717
  }
27164
27718
  return null;
@@ -27204,7 +27758,8 @@ class BackgroundManager {
27204
27758
  progress: {
27205
27759
  toolCalls: 0,
27206
27760
  lastUpdate: new Date
27207
- }
27761
+ },
27762
+ parentModel: input.parentModel
27208
27763
  };
27209
27764
  this.tasks.set(task.id, task);
27210
27765
  this.startPolling();
@@ -27394,12 +27949,15 @@ class BackgroundManager {
27394
27949
  log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
27395
27950
  setTimeout(async () => {
27396
27951
  try {
27397
- const messageDir = getMessageDir5(task.parentSessionID);
27952
+ const messageDir = getMessageDir7(task.parentSessionID);
27398
27953
  const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
27954
+ const modelContext = task.parentModel ?? prevMessage?.model;
27955
+ const modelField = modelContext?.providerID && modelContext?.modelID ? { providerID: modelContext.providerID, modelID: modelContext.modelID } : undefined;
27399
27956
  await this.client.session.prompt({
27400
27957
  path: { id: task.parentSessionID },
27401
27958
  body: {
27402
27959
  agent: prevMessage?.agent,
27960
+ model: modelField,
27403
27961
  parts: [{ type: "text", text: message }]
27404
27962
  },
27405
27963
  query: { directory: this.directory }
@@ -27562,6 +28120,7 @@ var OverridableAgentNameSchema = exports_external.enum([
27562
28120
  "build",
27563
28121
  "plan",
27564
28122
  "Sisyphus",
28123
+ "OpenCode-Builder",
27565
28124
  "Planner-Sisyphus",
27566
28125
  "oracle",
27567
28126
  "librarian",
@@ -27610,6 +28169,7 @@ var AgentOverridesSchema = exports_external.object({
27610
28169
  build: AgentOverrideConfigSchema.optional(),
27611
28170
  plan: AgentOverrideConfigSchema.optional(),
27612
28171
  Sisyphus: AgentOverrideConfigSchema.optional(),
28172
+ "OpenCode-Builder": AgentOverrideConfigSchema.optional(),
27613
28173
  "Planner-Sisyphus": AgentOverrideConfigSchema.optional(),
27614
28174
  oracle: AgentOverrideConfigSchema.optional(),
27615
28175
  librarian: AgentOverrideConfigSchema.optional(),
@@ -27626,14 +28186,17 @@ var ClaudeCodeConfigSchema = exports_external.object({
27626
28186
  hooks: exports_external.boolean().optional()
27627
28187
  });
27628
28188
  var SisyphusAgentConfigSchema = exports_external.object({
27629
- disabled: exports_external.boolean().optional()
28189
+ disabled: exports_external.boolean().optional(),
28190
+ default_builder_enabled: exports_external.boolean().optional(),
28191
+ planner_enabled: exports_external.boolean().optional(),
28192
+ replace_plan: exports_external.boolean().optional()
27630
28193
  });
27631
28194
  var ExperimentalConfigSchema = exports_external.object({
27632
28195
  aggressive_truncation: exports_external.boolean().optional(),
27633
28196
  auto_resume: exports_external.boolean().optional(),
27634
28197
  preemptive_compaction: exports_external.boolean().optional(),
27635
28198
  preemptive_compaction_threshold: exports_external.number().min(0.5).max(0.95).optional(),
27636
- truncate_all_tool_outputs: exports_external.boolean().optional()
28199
+ truncate_all_tool_outputs: exports_external.boolean().default(true)
27637
28200
  });
27638
28201
  var OhMyOpenCodeConfigSchema = exports_external.object({
27639
28202
  $schema: exports_external.string().optional(),
@@ -27720,7 +28283,7 @@ var PLAN_PERMISSION = {
27720
28283
 
27721
28284
  // src/index.ts
27722
28285
  import * as fs7 from "fs";
27723
- import * as path7 from "path";
28286
+ import * as path8 from "path";
27724
28287
  var AGENT_NAME_MAP = {
27725
28288
  omo: "Sisyphus",
27726
28289
  OmO: "Sisyphus",
@@ -27773,7 +28336,7 @@ function migrateConfigFile(configPath, rawConfig) {
27773
28336
  }
27774
28337
  return needsWrite;
27775
28338
  }
27776
- function loadConfigFromPath2(configPath) {
28339
+ function loadConfigFromPath2(configPath, ctx) {
27777
28340
  try {
27778
28341
  if (fs7.existsSync(configPath)) {
27779
28342
  const content = fs7.readFileSync(configPath, "utf-8");
@@ -27784,6 +28347,21 @@ function loadConfigFromPath2(configPath) {
27784
28347
  const errorMsg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
27785
28348
  log(`Config validation error in ${configPath}:`, result.error.issues);
27786
28349
  addConfigLoadError({ path: configPath, error: `Validation error: ${errorMsg}` });
28350
+ const errorList = result.error.issues.map((issue2) => `\u2022 ${issue2.path.join(".")}: ${issue2.message}`).join(`
28351
+ `);
28352
+ ctx.client.tui.showToast({
28353
+ body: {
28354
+ title: "\u274C OhMyOpenCode: Config Validation Failed",
28355
+ message: `Failed to load ${configPath}
28356
+
28357
+ Validation errors:
28358
+ ${errorList}
28359
+
28360
+ Config will be ignored. Please fix the errors above.`,
28361
+ variant: "error",
28362
+ duration: 1e4
28363
+ }
28364
+ }).catch(() => {});
27787
28365
  return null;
27788
28366
  }
27789
28367
  log(`Config loaded from ${configPath}`, { agents: result.data.agents });
@@ -27793,6 +28371,21 @@ function loadConfigFromPath2(configPath) {
27793
28371
  const errorMsg = err instanceof Error ? err.message : String(err);
27794
28372
  log(`Error loading config from ${configPath}:`, err);
27795
28373
  addConfigLoadError({ path: configPath, error: errorMsg });
28374
+ const hint = err instanceof SyntaxError ? `
28375
+
28376
+ Hint: Check for syntax errors in your JSON file (missing commas, quotes, brackets, etc.)` : "";
28377
+ ctx.client.tui.showToast({
28378
+ body: {
28379
+ title: "\u274C OhMyOpenCode: Config Load Failed",
28380
+ message: `Failed to load ${configPath}
28381
+
28382
+ Error: ${errorMsg}${hint}
28383
+
28384
+ Config will be ignored. Please fix the error above.`,
28385
+ variant: "error",
28386
+ duration: 1e4
28387
+ }
28388
+ }).catch(() => {});
27796
28389
  }
27797
28390
  return null;
27798
28391
  }
@@ -27822,11 +28415,11 @@ function mergeConfigs(base, override) {
27822
28415
  claude_code: deepMerge(base.claude_code, override.claude_code)
27823
28416
  };
27824
28417
  }
27825
- function loadPluginConfig(directory) {
27826
- const userConfigPath = path7.join(getUserConfigDir(), "opencode", "oh-my-opencode.json");
27827
- const projectConfigPath = path7.join(directory, ".opencode", "oh-my-opencode.json");
27828
- let config3 = loadConfigFromPath2(userConfigPath) ?? {};
27829
- const projectConfig = loadConfigFromPath2(projectConfigPath);
28418
+ function loadPluginConfig(directory, ctx) {
28419
+ const userConfigPath = path8.join(getUserConfigDir(), "opencode", "oh-my-opencode.json");
28420
+ const projectConfigPath = path8.join(directory, ".opencode", "oh-my-opencode.json");
28421
+ let config3 = loadConfigFromPath2(userConfigPath, ctx) ?? {};
28422
+ const projectConfig = loadConfigFromPath2(projectConfigPath, ctx);
27830
28423
  if (projectConfig) {
27831
28424
  config3 = mergeConfigs(config3, projectConfig);
27832
28425
  }
@@ -27840,7 +28433,7 @@ function loadPluginConfig(directory) {
27840
28433
  return config3;
27841
28434
  }
27842
28435
  var OhMyOpenCodePlugin = async (ctx) => {
27843
- const pluginConfig = loadPluginConfig(ctx.directory);
28436
+ const pluginConfig = loadPluginConfig(ctx.directory, ctx);
27844
28437
  const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
27845
28438
  const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
27846
28439
  const modelContextLimitsCache = new Map;
@@ -27934,26 +28527,49 @@ var OhMyOpenCodePlugin = async (ctx) => {
27934
28527
  const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
27935
28528
  const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
27936
28529
  const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
28530
+ const builderEnabled = pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
28531
+ const plannerEnabled = pluginConfig.sisyphus_agent?.planner_enabled ?? true;
28532
+ const replacePlan = pluginConfig.sisyphus_agent?.replace_plan ?? true;
27937
28533
  if (isSisyphusEnabled && builtinAgents.Sisyphus) {
27938
- const { name: _planName, ...planConfigWithoutName } = config3.agent?.plan ?? {};
27939
- const plannerSisyphusOverride = pluginConfig.agents?.["Planner-Sisyphus"];
27940
- const plannerSisyphusBase = {
27941
- ...planConfigWithoutName,
27942
- prompt: PLAN_SYSTEM_PROMPT,
27943
- permission: PLAN_PERMISSION,
27944
- description: `${config3.agent?.plan?.description ?? "Plan agent"} (OhMyOpenCode version)`,
27945
- color: config3.agent?.plan?.color ?? "#6495ED"
28534
+ const agentConfig = {
28535
+ Sisyphus: builtinAgents.Sisyphus
27946
28536
  };
27947
- const plannerSisyphusConfig = plannerSisyphusOverride ? { ...plannerSisyphusBase, ...plannerSisyphusOverride } : plannerSisyphusBase;
28537
+ if (builderEnabled) {
28538
+ const { name: _buildName, ...buildConfigWithoutName } = config3.agent?.build ?? {};
28539
+ const openCodeBuilderOverride = pluginConfig.agents?.["OpenCode-Builder"];
28540
+ const openCodeBuilderBase = {
28541
+ ...buildConfigWithoutName,
28542
+ description: `${config3.agent?.build?.description ?? "Build agent"} (OpenCode default)`
28543
+ };
28544
+ agentConfig["OpenCode-Builder"] = openCodeBuilderOverride ? { ...openCodeBuilderBase, ...openCodeBuilderOverride } : openCodeBuilderBase;
28545
+ }
28546
+ if (plannerEnabled) {
28547
+ const { name: _planName, ...planConfigWithoutName } = config3.agent?.plan ?? {};
28548
+ const plannerSisyphusOverride = pluginConfig.agents?.["Planner-Sisyphus"];
28549
+ const plannerSisyphusBase = {
28550
+ ...planConfigWithoutName,
28551
+ prompt: PLAN_SYSTEM_PROMPT,
28552
+ permission: PLAN_PERMISSION,
28553
+ description: `${config3.agent?.plan?.description ?? "Plan agent"} (OhMyOpenCode version)`,
28554
+ color: config3.agent?.plan?.color ?? "#6495ED"
28555
+ };
28556
+ agentConfig["Planner-Sisyphus"] = plannerSisyphusOverride ? { ...plannerSisyphusBase, ...plannerSisyphusOverride } : plannerSisyphusBase;
28557
+ }
28558
+ const filteredConfigAgents = config3.agent ? Object.fromEntries(Object.entries(config3.agent).filter(([key]) => {
28559
+ if (key === "build")
28560
+ return false;
28561
+ if (key === "plan" && replacePlan)
28562
+ return false;
28563
+ return true;
28564
+ })) : {};
27948
28565
  config3.agent = {
27949
- Sisyphus: builtinAgents.Sisyphus,
27950
- "Planner-Sisyphus": plannerSisyphusConfig,
28566
+ ...agentConfig,
27951
28567
  ...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "Sisyphus")),
27952
28568
  ...userAgents,
27953
28569
  ...projectAgents,
27954
- ...config3.agent,
28570
+ ...filteredConfigAgents,
27955
28571
  build: { ...config3.agent?.build, mode: "subagent" },
27956
- plan: { ...config3.agent?.plan, mode: "subagent" }
28572
+ ...replacePlan ? { plan: { ...config3.agent?.plan, mode: "subagent" } } : {}
27957
28573
  };
27958
28574
  } else {
27959
28575
  config3.agent = {