oh-my-opencode 2.12.2 → 2.12.3

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.
Files changed (37) hide show
  1. package/README.ja.md +32 -7
  2. package/README.ko.md +32 -7
  3. package/README.md +17 -8
  4. package/README.zh-cn.md +32 -7
  5. package/dist/agents/sisyphus-prompt-builder.d.ts +1 -0
  6. package/dist/cli/config-manager.d.ts +9 -0
  7. package/dist/cli/index.js +185 -81
  8. package/dist/features/claude-code-command-loader/loader.d.ts +5 -0
  9. package/dist/features/context-injector/collector.d.ts +11 -0
  10. package/dist/features/context-injector/collector.test.d.ts +1 -0
  11. package/dist/features/context-injector/index.d.ts +3 -0
  12. package/dist/features/context-injector/injector.d.ts +39 -0
  13. package/dist/features/context-injector/injector.test.d.ts +1 -0
  14. package/dist/features/context-injector/types.d.ts +83 -0
  15. package/dist/features/opencode-skill-loader/async-loader.d.ts +6 -0
  16. package/dist/features/opencode-skill-loader/async-loader.test.d.ts +1 -0
  17. package/dist/features/opencode-skill-loader/blocking.d.ts +2 -0
  18. package/dist/features/opencode-skill-loader/blocking.test.d.ts +1 -0
  19. package/dist/features/opencode-skill-loader/discover-worker.d.ts +1 -0
  20. package/dist/features/opencode-skill-loader/loader.d.ts +5 -0
  21. package/dist/features/opencode-skill-loader/types.d.ts +6 -0
  22. package/dist/features/skill-mcp-manager/env-cleaner.d.ts +2 -0
  23. package/dist/features/skill-mcp-manager/env-cleaner.test.d.ts +1 -0
  24. package/dist/features/skill-mcp-manager/manager.d.ts +8 -0
  25. package/dist/hooks/ralph-loop/types.d.ts +1 -0
  26. package/dist/hooks/tool-output-truncator.test.d.ts +1 -0
  27. package/dist/index.js +1143 -444
  28. package/dist/shared/frontmatter.d.ts +2 -0
  29. package/dist/shared/index.d.ts +3 -0
  30. package/dist/shared/opencode-config-dir.d.ts +19 -0
  31. package/dist/shared/opencode-config-dir.test.d.ts +1 -0
  32. package/dist/shared/opencode-version.d.ts +10 -0
  33. package/dist/shared/opencode-version.test.d.ts +1 -0
  34. package/dist/shared/permission-compat.d.ts +12 -0
  35. package/dist/shared/permission-compat.test.d.ts +1 -0
  36. package/dist/tools/index.d.ts +1 -0
  37. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -549,7 +549,7 @@ var require_scan = __commonJS((exports, module) => {
549
549
 
550
550
  // node_modules/picomatch/lib/parse.js
551
551
  var require_parse = __commonJS((exports, module) => {
552
- var constants = require_constants();
552
+ var constants2 = require_constants();
553
553
  var utils = require_utils();
554
554
  var {
555
555
  MAX_LENGTH,
@@ -557,7 +557,7 @@ var require_parse = __commonJS((exports, module) => {
557
557
  REGEX_NON_SPECIAL_CHARS,
558
558
  REGEX_SPECIAL_CHARS_BACKREF,
559
559
  REPLACEMENTS
560
- } = constants;
560
+ } = constants2;
561
561
  var expandRange = (args, options) => {
562
562
  if (typeof options.expandRange === "function") {
563
563
  return options.expandRange(...args, options);
@@ -588,8 +588,8 @@ var require_parse = __commonJS((exports, module) => {
588
588
  const bos = { type: "bos", value: "", output: opts.prepend || "" };
589
589
  const tokens = [bos];
590
590
  const capture = opts.capture ? "" : "?:";
591
- const PLATFORM_CHARS = constants.globChars(opts.windows);
592
- const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
591
+ const PLATFORM_CHARS = constants2.globChars(opts.windows);
592
+ const EXTGLOB_CHARS = constants2.extglobChars(PLATFORM_CHARS);
593
593
  const {
594
594
  DOT_LITERAL,
595
595
  PLUS_LITERAL,
@@ -1267,7 +1267,7 @@ var require_parse = __commonJS((exports, module) => {
1267
1267
  NO_DOTS_SLASH,
1268
1268
  STAR,
1269
1269
  START_ANCHOR
1270
- } = constants.globChars(opts.windows);
1270
+ } = constants2.globChars(opts.windows);
1271
1271
  const nodot = opts.dot ? NO_DOTS : NO_DOT;
1272
1272
  const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
1273
1273
  const capture = opts.capture ? "" : "?:";
@@ -1325,7 +1325,7 @@ var require_picomatch = __commonJS((exports, module) => {
1325
1325
  var scan = require_scan();
1326
1326
  var parse3 = require_parse();
1327
1327
  var utils = require_utils();
1328
- var constants = require_constants();
1328
+ var constants2 = require_constants();
1329
1329
  var isObject2 = (val) => val && typeof val === "object" && !Array.isArray(val);
1330
1330
  var picomatch = (glob, options, returnState = false) => {
1331
1331
  if (Array.isArray(glob)) {
@@ -1456,7 +1456,7 @@ var require_picomatch = __commonJS((exports, module) => {
1456
1456
  return /$^/;
1457
1457
  }
1458
1458
  };
1459
- picomatch.constants = constants;
1459
+ picomatch.constants = constants2;
1460
1460
  module.exports = picomatch;
1461
1461
  });
1462
1462
 
@@ -7925,12 +7925,12 @@ var require_dist = __commonJS((exports, module) => {
7925
7925
  throw new Error(`Unknown format "${name}"`);
7926
7926
  return f;
7927
7927
  };
7928
- function addFormats(ajv, list, fs9, exportName) {
7928
+ function addFormats(ajv, list, fs10, exportName) {
7929
7929
  var _a;
7930
7930
  var _b;
7931
7931
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== undefined || (_b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`);
7932
7932
  for (const f of list)
7933
- ajv.addFormat(f, fs9[f]);
7933
+ ajv.addFormat(f, fs10[f]);
7934
7934
  }
7935
7935
  module.exports = exports = formatsPlugin;
7936
7936
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -7941,7 +7941,7 @@ var require_dist = __commonJS((exports, module) => {
7941
7941
  var require_windows = __commonJS((exports, module) => {
7942
7942
  module.exports = isexe;
7943
7943
  isexe.sync = sync;
7944
- var fs9 = __require("fs");
7944
+ var fs10 = __require("fs");
7945
7945
  function checkPathExt(path7, options) {
7946
7946
  var pathext = options.pathExt !== undefined ? options.pathExt : process.env.PATHEXT;
7947
7947
  if (!pathext) {
@@ -7966,12 +7966,12 @@ var require_windows = __commonJS((exports, module) => {
7966
7966
  return checkPathExt(path7, options);
7967
7967
  }
7968
7968
  function isexe(path7, options, cb) {
7969
- fs9.stat(path7, function(er, stat2) {
7969
+ fs10.stat(path7, function(er, stat2) {
7970
7970
  cb(er, er ? false : checkStat(stat2, path7, options));
7971
7971
  });
7972
7972
  }
7973
7973
  function sync(path7, options) {
7974
- return checkStat(fs9.statSync(path7), path7, options);
7974
+ return checkStat(fs10.statSync(path7), path7, options);
7975
7975
  }
7976
7976
  });
7977
7977
 
@@ -7979,14 +7979,14 @@ var require_windows = __commonJS((exports, module) => {
7979
7979
  var require_mode = __commonJS((exports, module) => {
7980
7980
  module.exports = isexe;
7981
7981
  isexe.sync = sync;
7982
- var fs9 = __require("fs");
7982
+ var fs10 = __require("fs");
7983
7983
  function isexe(path7, options, cb) {
7984
- fs9.stat(path7, function(er, stat2) {
7984
+ fs10.stat(path7, function(er, stat2) {
7985
7985
  cb(er, er ? false : checkStat(stat2, options));
7986
7986
  });
7987
7987
  }
7988
7988
  function sync(path7, options) {
7989
- return checkStat(fs9.statSync(path7), options);
7989
+ return checkStat(fs10.statSync(path7), options);
7990
7990
  }
7991
7991
  function checkStat(stat2, options) {
7992
7992
  return stat2.isFile() && checkMode(stat2, options);
@@ -8008,7 +8008,7 @@ var require_mode = __commonJS((exports, module) => {
8008
8008
 
8009
8009
  // node_modules/isexe/index.js
8010
8010
  var require_isexe = __commonJS((exports, module) => {
8011
- var fs9 = __require("fs");
8011
+ var fs10 = __require("fs");
8012
8012
  var core3;
8013
8013
  if (process.platform === "win32" || global.TESTING_WINDOWS) {
8014
8014
  core3 = require_windows();
@@ -8247,16 +8247,16 @@ var require_shebang_command = __commonJS((exports, module) => {
8247
8247
 
8248
8248
  // node_modules/cross-spawn/lib/util/readShebang.js
8249
8249
  var require_readShebang = __commonJS((exports, module) => {
8250
- var fs9 = __require("fs");
8250
+ var fs10 = __require("fs");
8251
8251
  var shebangCommand = require_shebang_command();
8252
8252
  function readShebang(command) {
8253
8253
  const size = 150;
8254
8254
  const buffer = Buffer.alloc(size);
8255
8255
  let fd;
8256
8256
  try {
8257
- fd = fs9.openSync(command, "r");
8258
- fs9.readSync(fd, buffer, 0, size, 0);
8259
- fs9.closeSync(fd);
8257
+ fd = fs10.openSync(command, "r");
8258
+ fs10.readSync(fd, buffer, 0, size, 0);
8259
+ fs10.closeSync(fd);
8260
8260
  } catch (e) {}
8261
8261
  return shebangCommand(buffer.toString());
8262
8262
  }
@@ -8571,29 +8571,21 @@ function getMessageDir(sessionID) {
8571
8571
  }
8572
8572
  return null;
8573
8573
  }
8574
- function isAbortError(error) {
8575
- if (!error)
8576
- return false;
8577
- if (typeof error === "object") {
8578
- const errObj = error;
8579
- const name = errObj.name;
8580
- const message = errObj.message?.toLowerCase() ?? "";
8581
- if (name === "MessageAbortedError" || name === "AbortError")
8582
- return true;
8583
- if (name === "DOMException" && message.includes("abort"))
8584
- return true;
8585
- if (message.includes("aborted") || message.includes("cancelled") || message.includes("interrupted"))
8586
- return true;
8587
- }
8588
- if (typeof error === "string") {
8589
- const lower = error.toLowerCase();
8590
- return lower.includes("abort") || lower.includes("cancel") || lower.includes("interrupt");
8591
- }
8592
- return false;
8593
- }
8594
8574
  function getIncompleteCount(todos) {
8595
8575
  return todos.filter((t) => t.status !== "completed" && t.status !== "cancelled").length;
8596
8576
  }
8577
+ function isLastAssistantMessageAborted(messages) {
8578
+ if (!messages || messages.length === 0)
8579
+ return false;
8580
+ const assistantMessages = messages.filter((m) => m.info?.role === "assistant");
8581
+ if (assistantMessages.length === 0)
8582
+ return false;
8583
+ const lastAssistant = assistantMessages[assistantMessages.length - 1];
8584
+ const errorName = lastAssistant.info?.error?.name;
8585
+ if (!errorName)
8586
+ return false;
8587
+ return errorName === "MessageAbortedError" || errorName === "AbortError";
8588
+ }
8597
8589
  function createTodoContinuationEnforcer(ctx, options = {}) {
8598
8590
  const { backgroundManager } = options;
8599
8591
  const sessions = new Map;
@@ -8726,11 +8718,8 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
8726
8718
  const sessionID = props?.sessionID;
8727
8719
  if (!sessionID)
8728
8720
  return;
8729
- const state2 = getState(sessionID);
8730
- const isAbort = isAbortError(props?.error);
8731
- state2.lastEventWasAbortError = isAbort;
8732
8721
  cancelCountdown(sessionID);
8733
- log(`[${HOOK_NAME}] session.error`, { sessionID, isAbort });
8722
+ log(`[${HOOK_NAME}] session.error`, { sessionID });
8734
8723
  return;
8735
8724
  }
8736
8725
  if (event.type === "session.idle") {
@@ -8750,16 +8739,24 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
8750
8739
  log(`[${HOOK_NAME}] Skipped: in recovery`, { sessionID });
8751
8740
  return;
8752
8741
  }
8753
- if (state2.lastEventWasAbortError) {
8754
- state2.lastEventWasAbortError = false;
8755
- log(`[${HOOK_NAME}] Skipped: abort error immediately before idle`, { sessionID });
8756
- return;
8757
- }
8758
8742
  const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
8759
8743
  if (hasRunningBgTasks) {
8760
8744
  log(`[${HOOK_NAME}] Skipped: background tasks running`, { sessionID });
8761
8745
  return;
8762
8746
  }
8747
+ try {
8748
+ const messagesResp = await ctx.client.session.messages({
8749
+ path: { id: sessionID },
8750
+ query: { directory: ctx.directory }
8751
+ });
8752
+ const messages = messagesResp.data ?? [];
8753
+ if (isLastAssistantMessageAborted(messages)) {
8754
+ log(`[${HOOK_NAME}] Skipped: last assistant message was aborted`, { sessionID });
8755
+ return;
8756
+ }
8757
+ } catch (err) {
8758
+ log(`[${HOOK_NAME}] Messages fetch failed, continuing`, { sessionID, error: String(err) });
8759
+ }
8763
8760
  let todos = [];
8764
8761
  try {
8765
8762
  const response = await ctx.client.session.todo({ path: { id: sessionID } });
@@ -8786,11 +8783,8 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
8786
8783
  const role = info?.role;
8787
8784
  if (!sessionID)
8788
8785
  return;
8789
- const state2 = sessions.get(sessionID);
8790
- if (state2) {
8791
- state2.lastEventWasAbortError = false;
8792
- }
8793
8786
  if (role === "user") {
8787
+ const state2 = sessions.get(sessionID);
8794
8788
  if (state2?.countdownStartedAt) {
8795
8789
  const elapsed = Date.now() - state2.countdownStartedAt;
8796
8790
  if (elapsed < COUNTDOWN_GRACE_PERIOD_MS) {
@@ -8799,7 +8793,6 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
8799
8793
  }
8800
8794
  }
8801
8795
  cancelCountdown(sessionID);
8802
- log(`[${HOOK_NAME}] User message: cleared abort state`, { sessionID });
8803
8796
  }
8804
8797
  if (role === "assistant") {
8805
8798
  cancelCountdown(sessionID);
@@ -8811,10 +8804,6 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
8811
8804
  const sessionID = info?.sessionID;
8812
8805
  const role = info?.role;
8813
8806
  if (sessionID && role === "assistant") {
8814
- const state2 = sessions.get(sessionID);
8815
- if (state2) {
8816
- state2.lastEventWasAbortError = false;
8817
- }
8818
8807
  cancelCountdown(sessionID);
8819
8808
  }
8820
8809
  return;
@@ -8822,10 +8811,6 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
8822
8811
  if (event.type === "tool.execute.before" || event.type === "tool.execute.after") {
8823
8812
  const sessionID = props?.sessionID;
8824
8813
  if (sessionID) {
8825
- const state2 = sessions.get(sessionID);
8826
- if (state2) {
8827
- state2.lastEventWasAbortError = false;
8828
- }
8829
8814
  cancelCountdown(sessionID);
8830
8815
  }
8831
8816
  return;
@@ -8847,7 +8832,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
8847
8832
  }
8848
8833
  // src/hooks/context-window-monitor.ts
8849
8834
  var ANTHROPIC_DISPLAY_LIMIT = 1e6;
8850
- var ANTHROPIC_ACTUAL_LIMIT = 200000;
8835
+ var ANTHROPIC_ACTUAL_LIMIT = process.env.ANTHROPIC_1M_CONTEXT === "true" || process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" ? 1e6 : 200000;
8851
8836
  var CONTEXT_WARNING_THRESHOLD = 0.7;
8852
8837
  var CONTEXT_REMINDER = `[SYSTEM REMINDER - 1M Context Window]
8853
8838
 
@@ -10210,7 +10195,7 @@ ${result.message}`;
10210
10195
  }
10211
10196
  }
10212
10197
  // src/shared/dynamic-truncator.ts
10213
- var ANTHROPIC_ACTUAL_LIMIT2 = 200000;
10198
+ var ANTHROPIC_ACTUAL_LIMIT2 = process.env.ANTHROPIC_1M_CONTEXT === "true" || process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" ? 1e6 : 200000;
10214
10199
  var CHARS_PER_TOKEN_ESTIMATE = 4;
10215
10200
  var DEFAULT_TARGET_MAX_TOKENS = 50000;
10216
10201
  function estimateTokens(text) {
@@ -10319,6 +10304,8 @@ function createDynamicTruncator(ctx) {
10319
10304
  }
10320
10305
 
10321
10306
  // src/hooks/tool-output-truncator.ts
10307
+ var DEFAULT_MAX_TOKENS = 50000;
10308
+ var WEBFETCH_MAX_TOKENS = 1e4;
10322
10309
  var TRUNCATABLE_TOOLS = [
10323
10310
  "grep",
10324
10311
  "Grep",
@@ -10337,6 +10324,10 @@ var TRUNCATABLE_TOOLS = [
10337
10324
  "webfetch",
10338
10325
  "WebFetch"
10339
10326
  ];
10327
+ var TOOL_SPECIFIC_MAX_TOKENS = {
10328
+ webfetch: WEBFETCH_MAX_TOKENS,
10329
+ WebFetch: WEBFETCH_MAX_TOKENS
10330
+ };
10340
10331
  function createToolOutputTruncatorHook(ctx, options) {
10341
10332
  const truncator = createDynamicTruncator(ctx);
10342
10333
  const truncateAll = options?.experimental?.truncate_all_tool_outputs ?? false;
@@ -10344,7 +10335,8 @@ function createToolOutputTruncatorHook(ctx, options) {
10344
10335
  if (!truncateAll && !TRUNCATABLE_TOOLS.includes(input.tool))
10345
10336
  return;
10346
10337
  try {
10347
- const { result, truncated } = await truncator.truncate(input.sessionID, output.output);
10338
+ const targetMaxTokens = TOOL_SPECIFIC_MAX_TOKENS[input.tool] ?? DEFAULT_MAX_TOKENS;
10339
+ const { result, truncated } = await truncator.truncate(input.sessionID, output.output, { targetMaxTokens });
10348
10340
  if (truncated) {
10349
10341
  output.output = result;
10350
10342
  }
@@ -12077,7 +12069,7 @@ var COMPACTION_COOLDOWN_MS = 60000;
12077
12069
 
12078
12070
  // src/hooks/preemptive-compaction/index.ts
12079
12071
  var CLAUDE_MODEL_PATTERN = /claude-(opus|sonnet|haiku)/i;
12080
- var CLAUDE_DEFAULT_CONTEXT_LIMIT = 200000;
12072
+ var CLAUDE_DEFAULT_CONTEXT_LIMIT = process.env.ANTHROPIC_1M_CONTEXT === "true" || process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" ? 1e6 : 200000;
12081
12073
  function isSupportedModel(modelID) {
12082
12074
  return CLAUDE_MODEL_PATTERN.test(modelID);
12083
12075
  }
@@ -12410,6 +12402,16 @@ function extractPromptText(parts) {
12410
12402
  }
12411
12403
 
12412
12404
  // src/hooks/think-mode/switcher.ts
12405
+ function extractModelPrefix(modelID) {
12406
+ const slashIndex = modelID.indexOf("/");
12407
+ if (slashIndex === -1) {
12408
+ return { prefix: "", base: modelID };
12409
+ }
12410
+ return {
12411
+ prefix: modelID.slice(0, slashIndex + 1),
12412
+ base: modelID.slice(slashIndex + 1)
12413
+ };
12414
+ }
12413
12415
  function normalizeModelID(modelID) {
12414
12416
  return modelID.replace(/\.(\d+)/g, "-$1");
12415
12417
  }
@@ -12495,20 +12497,27 @@ var THINKING_CAPABLE_MODELS = {
12495
12497
  };
12496
12498
  function getHighVariant(modelID) {
12497
12499
  const normalized = normalizeModelID(modelID);
12498
- if (ALREADY_HIGH.has(normalized)) {
12500
+ const { prefix, base } = extractModelPrefix(normalized);
12501
+ if (ALREADY_HIGH.has(base) || base.endsWith("-high")) {
12502
+ return null;
12503
+ }
12504
+ const highBase = HIGH_VARIANT_MAP[base];
12505
+ if (!highBase) {
12499
12506
  return null;
12500
12507
  }
12501
- return HIGH_VARIANT_MAP[normalized] ?? null;
12508
+ return prefix + highBase;
12502
12509
  }
12503
12510
  function isAlreadyHighVariant(modelID) {
12504
12511
  const normalized = normalizeModelID(modelID);
12505
- return ALREADY_HIGH.has(normalized) || normalized.endsWith("-high");
12512
+ const { base } = extractModelPrefix(normalized);
12513
+ return ALREADY_HIGH.has(base) || base.endsWith("-high");
12506
12514
  }
12507
12515
  function isThinkingProvider(provider) {
12508
12516
  return provider in THINKING_CONFIGS;
12509
12517
  }
12510
12518
  function getThinkingConfig(providerID, modelID) {
12511
12519
  const normalized = normalizeModelID(modelID);
12520
+ const { base } = extractModelPrefix(normalized);
12512
12521
  if (isAlreadyHighVariant(normalized)) {
12513
12522
  return null;
12514
12523
  }
@@ -12518,8 +12527,8 @@ function getThinkingConfig(providerID, modelID) {
12518
12527
  }
12519
12528
  const config = THINKING_CONFIGS[resolvedProvider];
12520
12529
  const capablePatterns = THINKING_CAPABLE_MODELS[resolvedProvider];
12521
- const modelLower = normalized.toLowerCase();
12522
- const isCapable = capablePatterns.some((pattern) => modelLower.includes(pattern.toLowerCase()));
12530
+ const baseLower = base.toLowerCase();
12531
+ const isCapable = capablePatterns.some((pattern) => baseLower.includes(pattern.toLowerCase()));
12523
12532
  return isCapable ? config : null;
12524
12533
  }
12525
12534
 
@@ -15213,16 +15222,16 @@ function parseFrontmatter(content) {
15213
15222
  const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
15214
15223
  const match = content.match(frontmatterRegex);
15215
15224
  if (!match) {
15216
- return { data: {}, body: content };
15225
+ return { data: {}, body: content, hadFrontmatter: false, parseError: false };
15217
15226
  }
15218
15227
  const yamlContent = match[1];
15219
15228
  const body = match[2];
15220
15229
  try {
15221
15230
  const parsed = jsYaml.load(yamlContent, { schema: jsYaml.JSON_SCHEMA });
15222
15231
  const data = parsed ?? {};
15223
- return { data, body };
15232
+ return { data, body, hadFrontmatter: true, parseError: false };
15224
15233
  } catch {
15225
- return { data: {}, body };
15234
+ return { data: {}, body, hadFrontmatter: true, parseError: true };
15226
15235
  }
15227
15236
  }
15228
15237
  // src/shared/command-executor.ts
@@ -16535,6 +16544,167 @@ function migrateConfigFile(configPath, rawConfig) {
16535
16544
  }
16536
16545
  return needsWrite;
16537
16546
  }
16547
+ // src/shared/opencode-config-dir.ts
16548
+ import { existsSync as existsSync21 } from "fs";
16549
+ import { homedir as homedir6 } from "os";
16550
+ import { join as join26 } from "path";
16551
+ var TAURI_APP_IDENTIFIER = "ai.opencode.desktop";
16552
+ var TAURI_APP_IDENTIFIER_DEV = "ai.opencode.desktop.dev";
16553
+ function isDevBuild(version) {
16554
+ if (!version)
16555
+ return false;
16556
+ return version.includes("-dev") || version.includes(".dev");
16557
+ }
16558
+ function getTauriConfigDir(identifier) {
16559
+ const platform2 = process.platform;
16560
+ switch (platform2) {
16561
+ case "darwin":
16562
+ return join26(homedir6(), "Library", "Application Support", identifier);
16563
+ case "win32": {
16564
+ const appData = process.env.APPDATA || join26(homedir6(), "AppData", "Roaming");
16565
+ return join26(appData, identifier);
16566
+ }
16567
+ case "linux":
16568
+ default: {
16569
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join26(homedir6(), ".config");
16570
+ return join26(xdgConfig, identifier);
16571
+ }
16572
+ }
16573
+ }
16574
+ function getCliConfigDir() {
16575
+ if (process.platform === "win32") {
16576
+ const crossPlatformDir = join26(homedir6(), ".config", "opencode");
16577
+ const crossPlatformConfig = join26(crossPlatformDir, "opencode.json");
16578
+ if (existsSync21(crossPlatformConfig)) {
16579
+ return crossPlatformDir;
16580
+ }
16581
+ const appData = process.env.APPDATA || join26(homedir6(), "AppData", "Roaming");
16582
+ const appdataDir = join26(appData, "opencode");
16583
+ const appdataConfig = join26(appdataDir, "opencode.json");
16584
+ if (existsSync21(appdataConfig)) {
16585
+ return appdataDir;
16586
+ }
16587
+ return crossPlatformDir;
16588
+ }
16589
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join26(homedir6(), ".config");
16590
+ return join26(xdgConfig, "opencode");
16591
+ }
16592
+ function getOpenCodeConfigDir(options) {
16593
+ const { binary: binary2, version, checkExisting = true } = options;
16594
+ if (binary2 === "opencode") {
16595
+ return getCliConfigDir();
16596
+ }
16597
+ const identifier = isDevBuild(version) ? TAURI_APP_IDENTIFIER_DEV : TAURI_APP_IDENTIFIER;
16598
+ const tauriDir = getTauriConfigDir(identifier);
16599
+ if (checkExisting) {
16600
+ const legacyDir = getCliConfigDir();
16601
+ const legacyConfig = join26(legacyDir, "opencode.json");
16602
+ const legacyConfigC = join26(legacyDir, "opencode.jsonc");
16603
+ if (existsSync21(legacyConfig) || existsSync21(legacyConfigC)) {
16604
+ return legacyDir;
16605
+ }
16606
+ }
16607
+ return tauriDir;
16608
+ }
16609
+ function getOpenCodeConfigPaths(options) {
16610
+ const configDir = getOpenCodeConfigDir(options);
16611
+ return {
16612
+ configDir,
16613
+ configJson: join26(configDir, "opencode.json"),
16614
+ configJsonc: join26(configDir, "opencode.jsonc"),
16615
+ packageJson: join26(configDir, "package.json"),
16616
+ omoConfig: join26(configDir, "oh-my-opencode.json")
16617
+ };
16618
+ }
16619
+ // src/shared/opencode-version.ts
16620
+ import { execSync } from "child_process";
16621
+ var PERMISSION_BREAKING_VERSION = "1.1.1";
16622
+ var NOT_CACHED = Symbol("NOT_CACHED");
16623
+ var cachedVersion = NOT_CACHED;
16624
+ function parseVersion(version) {
16625
+ const cleaned = version.replace(/^v/, "").split("-")[0];
16626
+ return cleaned.split(".").map((n) => parseInt(n, 10) || 0);
16627
+ }
16628
+ function compareVersions(a, b) {
16629
+ const partsA = parseVersion(a);
16630
+ const partsB = parseVersion(b);
16631
+ const maxLen = Math.max(partsA.length, partsB.length);
16632
+ for (let i2 = 0;i2 < maxLen; i2++) {
16633
+ const numA = partsA[i2] ?? 0;
16634
+ const numB = partsB[i2] ?? 0;
16635
+ if (numA < numB)
16636
+ return -1;
16637
+ if (numA > numB)
16638
+ return 1;
16639
+ }
16640
+ return 0;
16641
+ }
16642
+ function isVersionGte(a, b) {
16643
+ return compareVersions(a, b) >= 0;
16644
+ }
16645
+ function getOpenCodeVersion() {
16646
+ if (cachedVersion !== NOT_CACHED) {
16647
+ return cachedVersion;
16648
+ }
16649
+ try {
16650
+ const result = execSync("opencode --version", {
16651
+ encoding: "utf-8",
16652
+ timeout: 5000,
16653
+ stdio: ["pipe", "pipe", "pipe"]
16654
+ }).trim();
16655
+ const versionMatch = result.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
16656
+ cachedVersion = versionMatch?.[1] ?? null;
16657
+ return cachedVersion;
16658
+ } catch {
16659
+ cachedVersion = null;
16660
+ return null;
16661
+ }
16662
+ }
16663
+ function supportsNewPermissionSystem() {
16664
+ const version = getOpenCodeVersion();
16665
+ if (!version)
16666
+ return true;
16667
+ return isVersionGte(version, PERMISSION_BREAKING_VERSION);
16668
+ }
16669
+ // src/shared/permission-compat.ts
16670
+ function createAgentToolRestrictions(denyTools) {
16671
+ if (supportsNewPermissionSystem()) {
16672
+ return {
16673
+ permission: Object.fromEntries(denyTools.map((tool) => [tool, "deny"]))
16674
+ };
16675
+ }
16676
+ return {
16677
+ tools: Object.fromEntries(denyTools.map((tool) => [tool, false]))
16678
+ };
16679
+ }
16680
+ function migrateToolsToPermission(tools) {
16681
+ return Object.fromEntries(Object.entries(tools).map(([key, value]) => [
16682
+ key,
16683
+ value ? "allow" : "deny"
16684
+ ]));
16685
+ }
16686
+ function migratePermissionToTools(permission) {
16687
+ return Object.fromEntries(Object.entries(permission).filter(([, value]) => value !== "ask").map(([key, value]) => [key, value === "allow"]));
16688
+ }
16689
+ function migrateAgentConfig(config) {
16690
+ const result = { ...config };
16691
+ if (supportsNewPermissionSystem()) {
16692
+ if (result.tools && typeof result.tools === "object") {
16693
+ const existingPermission = result.permission || {};
16694
+ const migratedPermission = migrateToolsToPermission(result.tools);
16695
+ result.permission = { ...migratedPermission, ...existingPermission };
16696
+ delete result.tools;
16697
+ }
16698
+ } else {
16699
+ if (result.permission && typeof result.permission === "object") {
16700
+ const existingTools = result.tools || {};
16701
+ const migratedTools = migratePermissionToTools(result.permission);
16702
+ result.tools = { ...migratedTools, ...existingTools };
16703
+ delete result.permission;
16704
+ }
16705
+ }
16706
+ return result;
16707
+ }
16538
16708
  // src/hooks/think-mode/index.ts
16539
16709
  var thinkModeState = new Map;
16540
16710
  function createThinkModeHook() {
@@ -16598,8 +16768,8 @@ function createThinkModeHook() {
16598
16768
  };
16599
16769
  }
16600
16770
  // src/hooks/claude-code-hooks/config.ts
16601
- import { join as join26 } from "path";
16602
- import { existsSync as existsSync21 } from "fs";
16771
+ import { join as join27 } from "path";
16772
+ import { existsSync as existsSync22 } from "fs";
16603
16773
  function normalizeHookMatcher(raw) {
16604
16774
  return {
16605
16775
  matcher: raw.matcher ?? raw.pattern ?? "*",
@@ -16625,11 +16795,11 @@ function normalizeHooksConfig(raw) {
16625
16795
  function getClaudeSettingsPaths(customPath) {
16626
16796
  const claudeConfigDir = getClaudeConfigDir();
16627
16797
  const paths = [
16628
- join26(claudeConfigDir, "settings.json"),
16629
- join26(process.cwd(), ".claude", "settings.json"),
16630
- join26(process.cwd(), ".claude", "settings.local.json")
16798
+ join27(claudeConfigDir, "settings.json"),
16799
+ join27(process.cwd(), ".claude", "settings.json"),
16800
+ join27(process.cwd(), ".claude", "settings.local.json")
16631
16801
  ];
16632
- if (customPath && existsSync21(customPath)) {
16802
+ if (customPath && existsSync22(customPath)) {
16633
16803
  paths.unshift(customPath);
16634
16804
  }
16635
16805
  return paths;
@@ -16654,7 +16824,7 @@ async function loadClaudeHooksConfig(customSettingsPath) {
16654
16824
  const paths = getClaudeSettingsPaths(customSettingsPath);
16655
16825
  let mergedConfig = {};
16656
16826
  for (const settingsPath of paths) {
16657
- if (existsSync21(settingsPath)) {
16827
+ if (existsSync22(settingsPath)) {
16658
16828
  try {
16659
16829
  const content = await Bun.file(settingsPath).text();
16660
16830
  const settings = JSON.parse(content);
@@ -16671,15 +16841,15 @@ async function loadClaudeHooksConfig(customSettingsPath) {
16671
16841
  }
16672
16842
 
16673
16843
  // src/hooks/claude-code-hooks/config-loader.ts
16674
- import { existsSync as existsSync22 } from "fs";
16675
- import { homedir as homedir6 } from "os";
16676
- import { join as join27 } from "path";
16677
- var USER_CONFIG_PATH = join27(homedir6(), ".config", "opencode", "opencode-cc-plugin.json");
16844
+ import { existsSync as existsSync23 } from "fs";
16845
+ import { homedir as homedir7 } from "os";
16846
+ import { join as join28 } from "path";
16847
+ var USER_CONFIG_PATH = join28(homedir7(), ".config", "opencode", "opencode-cc-plugin.json");
16678
16848
  function getProjectConfigPath() {
16679
- return join27(process.cwd(), ".opencode", "opencode-cc-plugin.json");
16849
+ return join28(process.cwd(), ".opencode", "opencode-cc-plugin.json");
16680
16850
  }
16681
16851
  async function loadConfigFromPath(path4) {
16682
- if (!existsSync22(path4)) {
16852
+ if (!existsSync23(path4)) {
16683
16853
  return null;
16684
16854
  }
16685
16855
  try {
@@ -16859,16 +17029,16 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
16859
17029
  }
16860
17030
 
16861
17031
  // src/hooks/claude-code-hooks/transcript.ts
16862
- import { join as join28 } from "path";
16863
- import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync23, writeFileSync as writeFileSync8, unlinkSync as unlinkSync5 } from "fs";
17032
+ import { join as join29 } from "path";
17033
+ import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync24, writeFileSync as writeFileSync8, unlinkSync as unlinkSync5 } from "fs";
16864
17034
  import { tmpdir as tmpdir5 } from "os";
16865
17035
  import { randomUUID } from "crypto";
16866
- var TRANSCRIPT_DIR = join28(getClaudeConfigDir(), "transcripts");
17036
+ var TRANSCRIPT_DIR = join29(getClaudeConfigDir(), "transcripts");
16867
17037
  function getTranscriptPath(sessionId) {
16868
- return join28(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
17038
+ return join29(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
16869
17039
  }
16870
17040
  function ensureTranscriptDir() {
16871
- if (!existsSync23(TRANSCRIPT_DIR)) {
17041
+ if (!existsSync24(TRANSCRIPT_DIR)) {
16872
17042
  mkdirSync6(TRANSCRIPT_DIR, { recursive: true });
16873
17043
  }
16874
17044
  }
@@ -16955,7 +17125,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
16955
17125
  }
16956
17126
  };
16957
17127
  entries.push(JSON.stringify(currentEntry));
16958
- const tempPath = join28(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
17128
+ const tempPath = join29(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
16959
17129
  writeFileSync8(tempPath, entries.join(`
16960
17130
  `) + `
16961
17131
  `);
@@ -16975,7 +17145,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
16975
17145
  ]
16976
17146
  }
16977
17147
  };
16978
- const tempPath = join28(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
17148
+ const tempPath = join29(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
16979
17149
  writeFileSync8(tempPath, JSON.stringify(currentEntry) + `
16980
17150
  `);
16981
17151
  return tempPath;
@@ -17187,10 +17357,10 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
17187
17357
  }
17188
17358
 
17189
17359
  // src/hooks/claude-code-hooks/todo.ts
17190
- import { join as join29 } from "path";
17191
- var TODO_DIR = join29(getClaudeConfigDir(), "todos");
17360
+ import { join as join30 } from "path";
17361
+ var TODO_DIR = join30(getClaudeConfigDir(), "todos");
17192
17362
  function getTodoPath(sessionId) {
17193
- return join29(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
17363
+ return join30(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
17194
17364
  }
17195
17365
 
17196
17366
  // src/hooks/claude-code-hooks/stop.ts
@@ -17349,6 +17519,172 @@ setInterval(() => {
17349
17519
  }
17350
17520
  }, CACHE_TTL);
17351
17521
 
17522
+ // src/hooks/keyword-detector/constants.ts
17523
+ var CODE_BLOCK_PATTERN2 = /```[\s\S]*?```/g;
17524
+ var INLINE_CODE_PATTERN2 = /`[^`]+`/g;
17525
+ var KEYWORD_DETECTORS = [
17526
+ {
17527
+ pattern: /(ultrawork|ulw)/i,
17528
+ message: `<ultrawork-mode>
17529
+
17530
+ ## TODO IS YOUR LIFELINE (NON-NEGOTIABLE)
17531
+
17532
+ **USE TodoWrite OBSESSIVELY. This is the #1 most important tool.**
17533
+
17534
+ ### TODO Rules
17535
+ 1. **BEFORE any action**: Create TODOs FIRST. Break down into atomic, granular steps.
17536
+ 2. **Be excessively detailed**: 10 small TODOs > 3 vague TODOs. Err on the side of too many.
17537
+ 3. **Real-time updates**: Mark \`in_progress\` before starting, \`completed\` IMMEDIATELY after. NEVER batch.
17538
+ 4. **One at a time**: Only ONE TODO should be \`in_progress\` at any moment.
17539
+ 5. **Sub-tasks**: Complex TODO? Break it into sub-TODOs. Keep granularity high.
17540
+ 6. **Questions too**: User asks a question? TODO: "Answer with evidence: [question]"
17541
+
17542
+ ### Example TODO Granularity
17543
+ BAD: "Implement user auth"
17544
+ GOOD:
17545
+ - "Read existing auth patterns in codebase"
17546
+ - "Create auth schema types"
17547
+ - "Implement login endpoint"
17548
+ - "Implement token validation middleware"
17549
+ - "Add auth tests - login success case"
17550
+ - "Add auth tests - login failure case"
17551
+ - "Verify LSP diagnostics clean"
17552
+
17553
+ **YOUR WORK IS INVISIBLE WITHOUT TODOs. USE THEM.**
17554
+
17555
+ ## TDD WORKFLOW (MANDATORY when tests exist)
17556
+
17557
+ Check for test infrastructure FIRST. If exists, follow strictly:
17558
+
17559
+ 1. **RED**: Write failing test FIRST \u2192 \`bun test\` must FAIL
17560
+ 2. **GREEN**: Write MINIMAL code to pass \u2192 \`bun test\` must PASS
17561
+ 3. **REFACTOR**: Clean up, tests stay green \u2192 \`bun test\` still PASS
17562
+ 4. **REPEAT**: Next test case, loop until complete
17563
+
17564
+ **NEVER write implementation before test. NEVER delete failing tests.**
17565
+
17566
+ ## AGENT DEPLOYMENT
17567
+
17568
+ Fire available agents in PARALLEL via background tasks. Use explore/librarian agents liberally (multiple concurrent if needed).
17569
+
17570
+ ## EVIDENCE-BASED ANSWERS
17571
+
17572
+ - Every claim: code snippet + file path + line number
17573
+ - No "I think..." - find and SHOW actual code
17574
+ - Local search fails? \u2192 librarian for external sources
17575
+ - **NEVER acceptable**: "I couldn't find it"
17576
+
17577
+ ## ZERO TOLERANCE FOR SHORTCUTS (RIGOROUS & HONEST EXECUTION)
17578
+
17579
+ **CORE PRINCIPLE**: Execute user's ORIGINAL INTENT with maximum rigor. No shortcuts. No compromises. No matter how large the task.
17580
+
17581
+ ### ABSOLUTE PROHIBITIONS
17582
+ | Violation | Why It's Forbidden |
17583
+ |-----------|-------------------|
17584
+ | **Mocking/Stubbing** | Never use mocks, stubs, or fake implementations unless explicitly requested. Real implementation only. |
17585
+ | **Scope Reduction** | Never make "demo", "skeleton", "simplified", "basic", "minimal" versions. Deliver FULL implementation. |
17586
+ | **Partial Completion** | Never stop at 60-80% saying "you can extend this...", "as an exercise...", "you can add...". Finish 100%. |
17587
+ | **Lazy Placeholders** | Never use "// TODO", "...", "etc.", "and so on" in actual code. Complete everything. |
17588
+ | **Assumed Shortcuts** | Never skip requirements deemed "optional" or "can be added later". All requirements are mandatory. |
17589
+ | **Test Deletion** | Never delete or skip failing tests. Fix the code, not the tests. |
17590
+ | **Evidence-Free Claims** | Never say "I think...", "probably...", "should work...". Show actual code/output. |
17591
+
17592
+ ### RIGOROUS EXECUTION MANDATE
17593
+ 1. **Parse Original Intent**: What did the user ACTUALLY want? Not what's convenient. The REAL, COMPLETE request.
17594
+ 2. **No Task Too Large**: If the task requires 100 files, modify 100 files. If it needs 1000 lines, write 1000 lines. Size is irrelevant.
17595
+ 3. **Honest Assessment**: If you cannot complete something, say so BEFORE starting. Don't fake completion.
17596
+ 4. **Evidence-Based Verification**: Every claim backed by code snippets, file paths, line numbers, and actual outputs.
17597
+ 5. **Complete Verification**: Re-read original request after completion. Check EVERY requirement was met.
17598
+
17599
+ ### FAILURE RECOVERY
17600
+ If you realize you've taken shortcuts:
17601
+ 1. STOP immediately
17602
+ 2. Identify what you skipped/faked
17603
+ 3. Create TODOs for ALL remaining work
17604
+ 4. Execute to TRUE completion - not "good enough"
17605
+
17606
+ **THE USER ASKED FOR X. DELIVER EXACTLY X. COMPLETELY. HONESTLY. NO MATTER THE SIZE.**
17607
+
17608
+ ## SUCCESS = All TODOs Done + All Requirements Met + Evidence Provided
17609
+
17610
+ </ultrawork-mode>
17611
+
17612
+ ---
17613
+
17614
+ `
17615
+ },
17616
+ {
17617
+ pattern: /\b(search|find|locate|lookup|look\s*up|explore|discover|scan|grep|query|browse|detect|trace|seek|track|pinpoint|hunt)\b|where\s+is|show\s+me|list\s+all|\uAC80\uC0C9|\uCC3E\uC544|\uD0D0\uC0C9|\uC870\uD68C|\uC2A4\uCE94|\uC11C\uCE58|\uB4A4\uC838|\uCC3E\uAE30|\uC5B4\uB514|\uCD94\uC801|\uD0D0\uC9C0|\uCC3E\uC544\uBD10|\uCC3E\uC544\uB0B4|\uBCF4\uC5EC\uC918|\uBAA9\uB85D|\u691C\u7D22|\u63A2\u3057\u3066|\u898B\u3064\u3051\u3066|\u30B5\u30FC\u30C1|\u63A2\u7D22|\u30B9\u30AD\u30E3\u30F3|\u3069\u3053|\u767A\u898B|\u635C\u7D22|\u898B\u3064\u3051\u51FA\u3059|\u4E00\u89A7|\u641C\u7D22|\u67E5\u627E|\u5BFB\u627E|\u67E5\u8BE2|\u68C0\u7D22|\u5B9A\u4F4D|\u626B\u63CF|\u53D1\u73B0|\u5728\u54EA\u91CC|\u627E\u51FA\u6765|\u5217\u51FA|t\u00ECm ki\u1EBFm|tra c\u1EE9u|\u0111\u1ECBnh v\u1ECB|qu\u00E9t|ph\u00E1t hi\u1EC7n|truy t\u00ECm|t\u00ECm ra|\u1EDF \u0111\u00E2u|li\u1EC7t k\u00EA/i,
17618
+ message: `[search-mode]
17619
+ MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
17620
+ - explore agents (codebase patterns, file structures, ast-grep)
17621
+ - librarian agents (remote repos, official docs, GitHub examples)
17622
+ Plus direct tools: Grep, ripgrep (rg), ast-grep (sg)
17623
+ NEVER stop at first result - be exhaustive.`
17624
+ },
17625
+ {
17626
+ pattern: /\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|\uBD84\uC11D|\uC870\uC0AC|\uD30C\uC545|\uC5F0\uAD6C|\uAC80\uD1A0|\uC9C4\uB2E8|\uC774\uD574|\uC124\uBA85|\uC6D0\uC778|\uC774\uC720|\uB72F\uC5B4\uBD10|\uB530\uC838\uBD10|\uD3C9\uAC00|\uD574\uC11D|\uB514\uBC84\uAE45|\uB514\uBC84\uADF8|\uC5B4\uB5BB\uAC8C|\uC65C|\uC0B4\uD3B4|\u5206\u6790|\u8ABF\u67FB|\u89E3\u6790|\u691C\u8A0E|\u7814\u7A76|\u8A3A\u65AD|\u7406\u89E3|\u8AAC\u660E|\u691C\u8A3C|\u7CBE\u67FB|\u7A76\u660E|\u30C7\u30D0\u30C3\u30B0|\u306A\u305C|\u3069\u3046|\u4ED5\u7D44\u307F|\u8C03\u67E5|\u68C0\u67E5|\u5256\u6790|\u6DF1\u5165|\u8BCA\u65AD|\u89E3\u91CA|\u8C03\u8BD5|\u4E3A\u4EC0\u4E48|\u539F\u7406|\u641E\u6E05\u695A|\u5F04\u660E\u767D|ph\u00E2n t\u00EDch|\u0111i\u1EC1u tra|nghi\u00EAn c\u1EE9u|ki\u1EC3m tra|xem x\u00E9t|ch\u1EA9n \u0111o\u00E1n|gi\u1EA3i th\u00EDch|t\u00ECm hi\u1EC3u|g\u1EE1 l\u1ED7i|t\u1EA1i sao/i,
17627
+ message: `[analyze-mode]
17628
+ ANALYSIS MODE. Gather context before diving deep:
17629
+
17630
+ CONTEXT GATHERING (parallel):
17631
+ - 1-2 explore agents (codebase patterns, implementations)
17632
+ - 1-2 librarian agents (if external library involved)
17633
+ - Direct tools: Grep, AST-grep, LSP for targeted searches
17634
+
17635
+ IF COMPLEX (architecture, multi-system, debugging after 2+ failures):
17636
+ - Consult oracle for strategic guidance
17637
+
17638
+ SYNTHESIZE findings before proceeding.`
17639
+ }
17640
+ ];
17641
+
17642
+ // src/hooks/keyword-detector/detector.ts
17643
+ function removeCodeBlocks2(text) {
17644
+ return text.replace(CODE_BLOCK_PATTERN2, "").replace(INLINE_CODE_PATTERN2, "");
17645
+ }
17646
+ function detectKeywordsWithType(text) {
17647
+ const textWithoutCode = removeCodeBlocks2(text);
17648
+ const types3 = ["ultrawork", "search", "analyze"];
17649
+ return KEYWORD_DETECTORS.map(({ pattern, message }, index) => ({
17650
+ matches: pattern.test(textWithoutCode),
17651
+ type: types3[index],
17652
+ message
17653
+ })).filter((result) => result.matches).map(({ type: type2, message }) => ({ type: type2, message }));
17654
+ }
17655
+ function extractPromptText2(parts) {
17656
+ return parts.filter((p) => p.type === "text").map((p) => p.text || "").join(" ");
17657
+ }
17658
+
17659
+ // src/hooks/keyword-detector/index.ts
17660
+ function createKeywordDetectorHook(ctx) {
17661
+ return {
17662
+ "chat.message": async (input, output) => {
17663
+ const promptText = extractPromptText2(output.parts);
17664
+ const detectedKeywords = detectKeywordsWithType(removeCodeBlocks2(promptText));
17665
+ if (detectedKeywords.length === 0) {
17666
+ return;
17667
+ }
17668
+ const hasUltrawork = detectedKeywords.some((k) => k.type === "ultrawork");
17669
+ if (hasUltrawork) {
17670
+ log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID });
17671
+ ctx.client.tui.showToast({
17672
+ body: {
17673
+ title: "Ultrawork Mode Activated",
17674
+ message: "Maximum precision engaged. All agents at your disposal.",
17675
+ variant: "success",
17676
+ duration: 3000
17677
+ }
17678
+ }).catch((err) => log(`[keyword-detector] Failed to show toast`, { error: err, sessionID: input.sessionID }));
17679
+ }
17680
+ log(`[keyword-detector] Detected ${detectedKeywords.length} keywords`, {
17681
+ sessionID: input.sessionID,
17682
+ types: detectedKeywords.map((k) => k.type)
17683
+ });
17684
+ }
17685
+ };
17686
+ }
17687
+
17352
17688
  // src/hooks/claude-code-hooks/index.ts
17353
17689
  var sessionFirstMessageProcessed = new Set;
17354
17690
  var sessionErrorState = new Map;
@@ -17423,11 +17759,20 @@ function createClaudeCodeHooksHook(ctx, config = {}) {
17423
17759
  log("chat.message injection skipped - interrupted during hooks", { sessionID: input.sessionID });
17424
17760
  return;
17425
17761
  }
17426
- if (result.messages.length > 0) {
17427
- const hookContent = result.messages.join(`
17762
+ const detectedKeywords = detectKeywordsWithType(removeCodeBlocks2(prompt));
17763
+ const keywordMessages = detectedKeywords.map((k) => k.message);
17764
+ if (keywordMessages.length > 0) {
17765
+ log("[claude-code-hooks] Detected keywords", {
17766
+ sessionID: input.sessionID,
17767
+ types: detectedKeywords.map((k) => k.type)
17768
+ });
17769
+ }
17770
+ const allMessages = [...keywordMessages, ...result.messages];
17771
+ if (allMessages.length > 0) {
17772
+ const hookContent = allMessages.join(`
17428
17773
 
17429
17774
  `);
17430
- log(`[claude-code-hooks] Injecting ${result.messages.length} hook messages`, { sessionID: input.sessionID, contentLength: hookContent.length, isFirstMessage });
17775
+ log(`[claude-code-hooks] Injecting ${allMessages.length} messages (${keywordMessages.length} keyword + ${result.messages.length} hook)`, { sessionID: input.sessionID, contentLength: hookContent.length, isFirstMessage });
17431
17776
  if (isFirstMessage) {
17432
17777
  const idx = output.parts.findIndex((p) => p.type === "text" && p.text);
17433
17778
  if (idx >= 0) {
@@ -17621,22 +17966,22 @@ ${result.message}`;
17621
17966
  }
17622
17967
  // src/hooks/rules-injector/index.ts
17623
17968
  import { readFileSync as readFileSync15 } from "fs";
17624
- import { homedir as homedir7 } from "os";
17969
+ import { homedir as homedir8 } from "os";
17625
17970
  import { relative as relative3, resolve as resolve4 } from "path";
17626
17971
 
17627
17972
  // src/hooks/rules-injector/finder.ts
17628
17973
  import {
17629
- existsSync as existsSync24,
17974
+ existsSync as existsSync25,
17630
17975
  readdirSync as readdirSync10,
17631
17976
  realpathSync,
17632
17977
  statSync as statSync2
17633
17978
  } from "fs";
17634
- import { dirname as dirname4, join as join31, relative } from "path";
17979
+ import { dirname as dirname4, join as join32, relative } from "path";
17635
17980
 
17636
17981
  // src/hooks/rules-injector/constants.ts
17637
- import { join as join30 } from "path";
17982
+ import { join as join31 } from "path";
17638
17983
  var OPENCODE_STORAGE6 = getOpenCodeStorageDir();
17639
- var RULES_INJECTOR_STORAGE = join30(OPENCODE_STORAGE6, "rules-injector");
17984
+ var RULES_INJECTOR_STORAGE = join31(OPENCODE_STORAGE6, "rules-injector");
17640
17985
  var PROJECT_MARKERS = [
17641
17986
  ".git",
17642
17987
  "pyproject.toml",
@@ -17677,8 +18022,8 @@ function findProjectRoot(startPath) {
17677
18022
  }
17678
18023
  while (true) {
17679
18024
  for (const marker of PROJECT_MARKERS) {
17680
- const markerPath = join31(current, marker);
17681
- if (existsSync24(markerPath)) {
18025
+ const markerPath = join32(current, marker);
18026
+ if (existsSync25(markerPath)) {
17682
18027
  return current;
17683
18028
  }
17684
18029
  }
@@ -17690,12 +18035,12 @@ function findProjectRoot(startPath) {
17690
18035
  }
17691
18036
  }
17692
18037
  function findRuleFilesRecursive(dir, results) {
17693
- if (!existsSync24(dir))
18038
+ if (!existsSync25(dir))
17694
18039
  return;
17695
18040
  try {
17696
18041
  const entries = readdirSync10(dir, { withFileTypes: true });
17697
18042
  for (const entry of entries) {
17698
- const fullPath = join31(dir, entry.name);
18043
+ const fullPath = join32(dir, entry.name);
17699
18044
  if (entry.isDirectory()) {
17700
18045
  findRuleFilesRecursive(fullPath, results);
17701
18046
  } else if (entry.isFile()) {
@@ -17720,7 +18065,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
17720
18065
  let distance = 0;
17721
18066
  while (true) {
17722
18067
  for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
17723
- const ruleDir = join31(currentDir, parent, subdir);
18068
+ const ruleDir = join32(currentDir, parent, subdir);
17724
18069
  const files = [];
17725
18070
  findRuleFilesRecursive(ruleDir, files);
17726
18071
  for (const filePath of files) {
@@ -17746,8 +18091,8 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
17746
18091
  }
17747
18092
  if (projectRoot) {
17748
18093
  for (const ruleFile of PROJECT_RULE_FILES) {
17749
- const filePath = join31(projectRoot, ruleFile);
17750
- if (existsSync24(filePath)) {
18094
+ const filePath = join32(projectRoot, ruleFile);
18095
+ if (existsSync25(filePath)) {
17751
18096
  try {
17752
18097
  const stat = statSync2(filePath);
17753
18098
  if (stat.isFile()) {
@@ -17767,7 +18112,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
17767
18112
  }
17768
18113
  }
17769
18114
  }
17770
- const userRuleDir = join31(homeDir, USER_RULE_DIR);
18115
+ const userRuleDir = join32(homeDir, USER_RULE_DIR);
17771
18116
  const userFiles = [];
17772
18117
  findRuleFilesRecursive(userRuleDir, userFiles);
17773
18118
  for (const filePath of userFiles) {
@@ -17956,19 +18301,19 @@ function mergeGlobs(existing, newValue) {
17956
18301
 
17957
18302
  // src/hooks/rules-injector/storage.ts
17958
18303
  import {
17959
- existsSync as existsSync25,
18304
+ existsSync as existsSync26,
17960
18305
  mkdirSync as mkdirSync7,
17961
18306
  readFileSync as readFileSync14,
17962
18307
  writeFileSync as writeFileSync9,
17963
18308
  unlinkSync as unlinkSync6
17964
18309
  } from "fs";
17965
- import { join as join32 } from "path";
18310
+ import { join as join33 } from "path";
17966
18311
  function getStoragePath3(sessionID) {
17967
- return join32(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
18312
+ return join33(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
17968
18313
  }
17969
18314
  function loadInjectedRules(sessionID) {
17970
18315
  const filePath = getStoragePath3(sessionID);
17971
- if (!existsSync25(filePath))
18316
+ if (!existsSync26(filePath))
17972
18317
  return { contentHashes: new Set, realPaths: new Set };
17973
18318
  try {
17974
18319
  const content = readFileSync14(filePath, "utf-8");
@@ -17982,7 +18327,7 @@ function loadInjectedRules(sessionID) {
17982
18327
  }
17983
18328
  }
17984
18329
  function saveInjectedRules(sessionID, data) {
17985
- if (!existsSync25(RULES_INJECTOR_STORAGE)) {
18330
+ if (!existsSync26(RULES_INJECTOR_STORAGE)) {
17986
18331
  mkdirSync7(RULES_INJECTOR_STORAGE, { recursive: true });
17987
18332
  }
17988
18333
  const storageData = {
@@ -17995,7 +18340,7 @@ function saveInjectedRules(sessionID, data) {
17995
18340
  }
17996
18341
  function clearInjectedRules(sessionID) {
17997
18342
  const filePath = getStoragePath3(sessionID);
17998
- if (existsSync25(filePath)) {
18343
+ if (existsSync26(filePath)) {
17999
18344
  unlinkSync6(filePath);
18000
18345
  }
18001
18346
  }
@@ -18025,7 +18370,7 @@ function createRulesInjectorHook(ctx) {
18025
18370
  return;
18026
18371
  const projectRoot = findProjectRoot(resolved);
18027
18372
  const cache2 = getSessionCache(sessionID);
18028
- const home = homedir7();
18373
+ const home = homedir8();
18029
18374
  const ruleFileCandidates = findRuleFiles(projectRoot, home, resolved);
18030
18375
  const toInject = [];
18031
18376
  for (const candidate of ruleFileCandidates) {
@@ -18450,13 +18795,17 @@ function invalidatePackage(packageName = PACKAGE_NAME) {
18450
18795
  }
18451
18796
 
18452
18797
  // src/cli/config-manager.ts
18453
- import { homedir as homedir10 } from "os";
18454
- import { join as join36 } from "path";
18455
- var OPENCODE_CONFIG_DIR = join36(homedir10(), ".config", "opencode");
18456
- var OPENCODE_JSON = join36(OPENCODE_CONFIG_DIR, "opencode.json");
18457
- var OPENCODE_JSONC = join36(OPENCODE_CONFIG_DIR, "opencode.jsonc");
18458
- var OPENCODE_PACKAGE_JSON = join36(OPENCODE_CONFIG_DIR, "package.json");
18459
- var OMO_CONFIG = join36(OPENCODE_CONFIG_DIR, "oh-my-opencode.json");
18798
+ var configContext = null;
18799
+ function getConfigContext() {
18800
+ if (!configContext) {
18801
+ const paths = getOpenCodeConfigPaths({ binary: "opencode", version: null });
18802
+ configContext = { binary: "opencode", version: null, paths };
18803
+ }
18804
+ return configContext;
18805
+ }
18806
+ function getConfigDir() {
18807
+ return getConfigContext().paths.configDir;
18808
+ }
18460
18809
  var BUN_INSTALL_TIMEOUT_SECONDS = 60;
18461
18810
  var BUN_INSTALL_TIMEOUT_MS = BUN_INSTALL_TIMEOUT_SECONDS * 1000;
18462
18811
  async function runBunInstall() {
@@ -18466,7 +18815,7 @@ async function runBunInstall() {
18466
18815
  async function runBunInstallWithDetails() {
18467
18816
  try {
18468
18817
  const proc = Bun.spawn(["bun", "install"], {
18469
- cwd: OPENCODE_CONFIG_DIR,
18818
+ cwd: getConfigDir(),
18470
18819
  stdout: "pipe",
18471
18820
  stderr: "pipe"
18472
18821
  });
@@ -18524,9 +18873,9 @@ v${latestVersion} available. Restart OpenCode to apply.` : `OpenCode is now on S
18524
18873
  return;
18525
18874
  hasChecked = true;
18526
18875
  setTimeout(async () => {
18527
- const cachedVersion = getCachedVersion();
18876
+ const cachedVersion2 = getCachedVersion();
18528
18877
  const localDevVersion = getLocalDevVersion(ctx.directory);
18529
- const displayVersion = localDevVersion ?? cachedVersion;
18878
+ const displayVersion = localDevVersion ?? cachedVersion2;
18530
18879
  await showConfigErrorsIfAny(ctx);
18531
18880
  if (localDevVersion) {
18532
18881
  if (showStartupToast) {
@@ -18551,8 +18900,8 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage) {
18551
18900
  log("[auto-update-checker] Plugin not found in config");
18552
18901
  return;
18553
18902
  }
18554
- const cachedVersion = getCachedVersion();
18555
- const currentVersion = cachedVersion ?? pluginInfo.pinnedVersion;
18903
+ const cachedVersion2 = getCachedVersion();
18904
+ const currentVersion = cachedVersion2 ?? pluginInfo.pinnedVersion;
18556
18905
  if (!currentVersion) {
18557
18906
  log("[auto-update-checker] No version found (cached or pinned)");
18558
18907
  return;
@@ -18671,7 +19020,7 @@ async function showLocalDevToast(ctx, version, isSisyphusEnabled) {
18671
19020
  }
18672
19021
  // src/hooks/agent-usage-reminder/storage.ts
18673
19022
  import {
18674
- existsSync as existsSync29,
19023
+ existsSync as existsSync30,
18675
19024
  mkdirSync as mkdirSync8,
18676
19025
  readFileSync as readFileSync18,
18677
19026
  writeFileSync as writeFileSync12,
@@ -18731,7 +19080,7 @@ function getStoragePath4(sessionID) {
18731
19080
  }
18732
19081
  function loadAgentUsageState(sessionID) {
18733
19082
  const filePath = getStoragePath4(sessionID);
18734
- if (!existsSync29(filePath))
19083
+ if (!existsSync30(filePath))
18735
19084
  return null;
18736
19085
  try {
18737
19086
  const content = readFileSync18(filePath, "utf-8");
@@ -18741,7 +19090,7 @@ function loadAgentUsageState(sessionID) {
18741
19090
  }
18742
19091
  }
18743
19092
  function saveAgentUsageState(state2) {
18744
- if (!existsSync29(AGENT_USAGE_REMINDER_STORAGE)) {
19093
+ if (!existsSync30(AGENT_USAGE_REMINDER_STORAGE)) {
18745
19094
  mkdirSync8(AGENT_USAGE_REMINDER_STORAGE, { recursive: true });
18746
19095
  }
18747
19096
  const filePath = getStoragePath4(state2.sessionID);
@@ -18749,7 +19098,7 @@ function saveAgentUsageState(state2) {
18749
19098
  }
18750
19099
  function clearAgentUsageState(sessionID) {
18751
19100
  const filePath = getStoragePath4(sessionID);
18752
- if (existsSync29(filePath)) {
19101
+ if (existsSync30(filePath)) {
18753
19102
  unlinkSync7(filePath);
18754
19103
  }
18755
19104
  }
@@ -18819,163 +19168,6 @@ function createAgentUsageReminderHook(_ctx) {
18819
19168
  event: eventHandler
18820
19169
  };
18821
19170
  }
18822
- // src/hooks/keyword-detector/constants.ts
18823
- var CODE_BLOCK_PATTERN2 = /```[\s\S]*?```/g;
18824
- var INLINE_CODE_PATTERN2 = /`[^`]+`/g;
18825
- var KEYWORD_DETECTORS = [
18826
- {
18827
- pattern: /(ultrawork|ulw)/i,
18828
- message: `<ultrawork-mode>
18829
- [CODE RED] Maximum precision required. Ultrathink before acting.
18830
-
18831
- YOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.
18832
- TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
18833
-
18834
- ## AGENT UTILIZATION PRINCIPLES (by capability, not by name)
18835
- - **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure
18836
- - **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs
18837
- - **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn a dedicated planning agent for work breakdown
18838
- - **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning
18839
- - **Frontend/UI Tasks**: Delegate to UI-specialized agents for design and implementation
18840
-
18841
- ## EXECUTION RULES
18842
- - **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.
18843
- - **PARALLEL**: Fire independent agent calls simultaneously via background_task - NEVER wait sequentially.
18844
- - **BACKGROUND FIRST**: Use background_task for exploration/research agents (10+ concurrent if needed).
18845
- - **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.
18846
- - **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.
18847
-
18848
- ## WORKFLOW
18849
- 1. Analyze the request and identify required capabilities
18850
- 2. Spawn exploration/librarian agents via background_task in PARALLEL (10+ if needed)
18851
- 3. Always Use Plan agent with gathered context to create detailed work breakdown
18852
- 4. Execute with continuous verification against original requirements
18853
-
18854
- ## TDD (if test infrastructure exists)
18855
-
18856
- 1. Write spec (requirements)
18857
- 2. Write tests (failing)
18858
- 3. RED: tests fail
18859
- 4. Implement minimal code
18860
- 5. GREEN: tests pass
18861
- 6. Refactor if needed (must stay green)
18862
- 7. Next feature, repeat
18863
-
18864
- ## ZERO TOLERANCE FAILURES
18865
- - **NO Scope Reduction**: Never make "demo", "skeleton", "simplified", "basic" versions - deliver FULL implementation
18866
- - **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.
18867
- - **NO Partial Completion**: Never stop at 60-80% saying "you can extend this..." - finish 100%
18868
- - **NO Assumed Shortcuts**: Never skip requirements you deem "optional" or "can be added later"
18869
- - **NO Premature Stopping**: Never declare done until ALL TODOs are completed and verified
18870
- - **NO TEST DELETION**: Never delete or skip failing tests to make the build pass. Fix the code, not the tests.
18871
-
18872
- THE USER ASKED FOR X. DELIVER EXACTLY X. NOT A SUBSET. NOT A DEMO. NOT A STARTING POINT.
18873
-
18874
- </ultrawork-mode>
18875
-
18876
- ---
18877
-
18878
- `
18879
- },
18880
- {
18881
- pattern: /\b(search|find|locate|lookup|look\s*up|explore|discover|scan|grep|query|browse|detect|trace|seek|track|pinpoint|hunt)\b|where\s+is|show\s+me|list\s+all|\uAC80\uC0C9|\uCC3E\uC544|\uD0D0\uC0C9|\uC870\uD68C|\uC2A4\uCE94|\uC11C\uCE58|\uB4A4\uC838|\uCC3E\uAE30|\uC5B4\uB514|\uCD94\uC801|\uD0D0\uC9C0|\uCC3E\uC544\uBD10|\uCC3E\uC544\uB0B4|\uBCF4\uC5EC\uC918|\uBAA9\uB85D|\u691C\u7D22|\u63A2\u3057\u3066|\u898B\u3064\u3051\u3066|\u30B5\u30FC\u30C1|\u63A2\u7D22|\u30B9\u30AD\u30E3\u30F3|\u3069\u3053|\u767A\u898B|\u635C\u7D22|\u898B\u3064\u3051\u51FA\u3059|\u4E00\u89A7|\u641C\u7D22|\u67E5\u627E|\u5BFB\u627E|\u67E5\u8BE2|\u68C0\u7D22|\u5B9A\u4F4D|\u626B\u63CF|\u53D1\u73B0|\u5728\u54EA\u91CC|\u627E\u51FA\u6765|\u5217\u51FA|t\u00ECm ki\u1EBFm|tra c\u1EE9u|\u0111\u1ECBnh v\u1ECB|qu\u00E9t|ph\u00E1t hi\u1EC7n|truy t\u00ECm|t\u00ECm ra|\u1EDF \u0111\u00E2u|li\u1EC7t k\u00EA/i,
18882
- message: `[search-mode]
18883
- MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
18884
- - explore agents (codebase patterns, file structures, ast-grep)
18885
- - librarian agents (remote repos, official docs, GitHub examples)
18886
- Plus direct tools: Grep, ripgrep (rg), ast-grep (sg)
18887
- NEVER stop at first result - be exhaustive.`
18888
- },
18889
- {
18890
- pattern: /\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|\uBD84\uC11D|\uC870\uC0AC|\uD30C\uC545|\uC5F0\uAD6C|\uAC80\uD1A0|\uC9C4\uB2E8|\uC774\uD574|\uC124\uBA85|\uC6D0\uC778|\uC774\uC720|\uB72F\uC5B4\uBD10|\uB530\uC838\uBD10|\uD3C9\uAC00|\uD574\uC11D|\uB514\uBC84\uAE45|\uB514\uBC84\uADF8|\uC5B4\uB5BB\uAC8C|\uC65C|\uC0B4\uD3B4|\u5206\u6790|\u8ABF\u67FB|\u89E3\u6790|\u691C\u8A0E|\u7814\u7A76|\u8A3A\u65AD|\u7406\u89E3|\u8AAC\u660E|\u691C\u8A3C|\u7CBE\u67FB|\u7A76\u660E|\u30C7\u30D0\u30C3\u30B0|\u306A\u305C|\u3069\u3046|\u4ED5\u7D44\u307F|\u8C03\u67E5|\u68C0\u67E5|\u5256\u6790|\u6DF1\u5165|\u8BCA\u65AD|\u89E3\u91CA|\u8C03\u8BD5|\u4E3A\u4EC0\u4E48|\u539F\u7406|\u641E\u6E05\u695A|\u5F04\u660E\u767D|ph\u00E2n t\u00EDch|\u0111i\u1EC1u tra|nghi\u00EAn c\u1EE9u|ki\u1EC3m tra|xem x\u00E9t|ch\u1EA9n \u0111o\u00E1n|gi\u1EA3i th\u00EDch|t\u00ECm hi\u1EC3u|g\u1EE1 l\u1ED7i|t\u1EA1i sao/i,
18891
- message: `[analyze-mode]
18892
- ANALYSIS MODE. Gather context before diving deep:
18893
-
18894
- CONTEXT GATHERING (parallel):
18895
- - 1-2 explore agents (codebase patterns, implementations)
18896
- - 1-2 librarian agents (if external library involved)
18897
- - Direct tools: Grep, AST-grep, LSP for targeted searches
18898
-
18899
- IF COMPLEX (architecture, multi-system, debugging after 2+ failures):
18900
- - Consult oracle for strategic guidance
18901
-
18902
- SYNTHESIZE findings before proceeding.`
18903
- }
18904
- ];
18905
-
18906
- // src/hooks/keyword-detector/detector.ts
18907
- function removeCodeBlocks2(text) {
18908
- return text.replace(CODE_BLOCK_PATTERN2, "").replace(INLINE_CODE_PATTERN2, "");
18909
- }
18910
- function detectKeywordsWithType(text) {
18911
- const textWithoutCode = removeCodeBlocks2(text);
18912
- const types3 = ["ultrawork", "search", "analyze"];
18913
- return KEYWORD_DETECTORS.map(({ pattern, message }, index) => ({
18914
- matches: pattern.test(textWithoutCode),
18915
- type: types3[index],
18916
- message
18917
- })).filter((result) => result.matches).map(({ type: type2, message }) => ({ type: type2, message }));
18918
- }
18919
- function extractPromptText2(parts) {
18920
- return parts.filter((p) => p.type === "text").map((p) => p.text || "").join(" ");
18921
- }
18922
-
18923
- // src/hooks/keyword-detector/index.ts
18924
- var sessionFirstMessageProcessed2 = new Set;
18925
- var sessionUltraworkNotified = new Set;
18926
- function createKeywordDetectorHook(ctx) {
18927
- return {
18928
- "chat.message": async (input, output) => {
18929
- const isFirstMessage = !sessionFirstMessageProcessed2.has(input.sessionID);
18930
- sessionFirstMessageProcessed2.add(input.sessionID);
18931
- const promptText = extractPromptText2(output.parts);
18932
- const detectedKeywords = detectKeywordsWithType(promptText);
18933
- const messages = detectedKeywords.map((k) => k.message);
18934
- if (messages.length === 0) {
18935
- return;
18936
- }
18937
- const hasUltrawork = detectedKeywords.some((k) => k.type === "ultrawork");
18938
- if (hasUltrawork && !sessionUltraworkNotified.has(input.sessionID)) {
18939
- sessionUltraworkNotified.add(input.sessionID);
18940
- log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID });
18941
- ctx.client.tui.showToast({
18942
- body: {
18943
- title: "Ultrawork Mode Activated",
18944
- message: "Maximum precision engaged. All agents at your disposal.",
18945
- variant: "success",
18946
- duration: 3000
18947
- }
18948
- }).catch((err) => log(`[keyword-detector] Failed to show toast`, { error: err, sessionID: input.sessionID }));
18949
- }
18950
- const context = messages.join(`
18951
- `);
18952
- if (isFirstMessage) {
18953
- log(`Keywords detected on first message, transforming parts directly`, { sessionID: input.sessionID, keywordCount: messages.length });
18954
- const idx = output.parts.findIndex((p) => p.type === "text" && p.text);
18955
- if (idx >= 0) {
18956
- output.parts[idx].text = `${context}
18957
-
18958
- ---
18959
-
18960
- ${output.parts[idx].text ?? ""}`;
18961
- }
18962
- return;
18963
- }
18964
- log(`Keywords detected: ${messages.length}`, { sessionID: input.sessionID });
18965
- const message = output.message;
18966
- log(`[keyword-detector] Injecting context for ${messages.length} keywords`, { sessionID: input.sessionID, contextLength: context.length });
18967
- const success = injectHookMessage(input.sessionID, context, {
18968
- agent: message.agent,
18969
- model: message.model,
18970
- path: message.path,
18971
- tools: message.tools
18972
- });
18973
- if (success) {
18974
- log("Keyword context injected", { sessionID: input.sessionID });
18975
- }
18976
- }
18977
- };
18978
- }
18979
19171
  // src/hooks/non-interactive-env/constants.ts
18980
19172
  var HOOK_NAME2 = "non-interactive-env";
18981
19173
  var NON_INTERACTIVE_ENV = {
@@ -19058,7 +19250,8 @@ function shellEscape(value) {
19058
19250
  return value;
19059
19251
  }
19060
19252
  function buildEnvPrefix(env) {
19061
- return Object.entries(env).map(([key, value]) => `${key}=${shellEscape(value)}`).join(" \\\n");
19253
+ const exports = Object.entries(env).map(([key, value]) => `${key}=${shellEscape(value)}`).join(" ");
19254
+ return `export ${exports};`;
19062
19255
  }
19063
19256
  function createNonInteractiveEnvHook(_ctx) {
19064
19257
  return {
@@ -19089,7 +19282,7 @@ function createNonInteractiveEnvHook(_ctx) {
19089
19282
  }
19090
19283
  // src/hooks/interactive-bash-session/storage.ts
19091
19284
  import {
19092
- existsSync as existsSync30,
19285
+ existsSync as existsSync31,
19093
19286
  mkdirSync as mkdirSync9,
19094
19287
  readFileSync as readFileSync19,
19095
19288
  writeFileSync as writeFileSync13,
@@ -19116,7 +19309,7 @@ function getStoragePath5(sessionID) {
19116
19309
  }
19117
19310
  function loadInteractiveBashSessionState(sessionID) {
19118
19311
  const filePath = getStoragePath5(sessionID);
19119
- if (!existsSync30(filePath))
19312
+ if (!existsSync31(filePath))
19120
19313
  return null;
19121
19314
  try {
19122
19315
  const content = readFileSync19(filePath, "utf-8");
@@ -19131,7 +19324,7 @@ function loadInteractiveBashSessionState(sessionID) {
19131
19324
  }
19132
19325
  }
19133
19326
  function saveInteractiveBashSessionState(state2) {
19134
- if (!existsSync30(INTERACTIVE_BASH_SESSION_STORAGE)) {
19327
+ if (!existsSync31(INTERACTIVE_BASH_SESSION_STORAGE)) {
19135
19328
  mkdirSync9(INTERACTIVE_BASH_SESSION_STORAGE, { recursive: true });
19136
19329
  }
19137
19330
  const filePath = getStoragePath5(state2.sessionID);
@@ -19144,7 +19337,7 @@ function saveInteractiveBashSessionState(state2) {
19144
19337
  }
19145
19338
  function clearInteractiveBashSessionState(sessionID) {
19146
19339
  const filePath = getStoragePath5(sessionID);
19147
- if (existsSync30(filePath)) {
19340
+ if (existsSync31(filePath)) {
19148
19341
  unlinkSync8(filePath);
19149
19342
  }
19150
19343
  }
@@ -19475,10 +19668,10 @@ function createThinkingBlockValidatorHook() {
19475
19668
  };
19476
19669
  }
19477
19670
  // src/hooks/ralph-loop/index.ts
19478
- import { existsSync as existsSync32, readFileSync as readFileSync21 } from "fs";
19671
+ import { existsSync as existsSync33, readFileSync as readFileSync21 } from "fs";
19479
19672
 
19480
19673
  // src/hooks/ralph-loop/storage.ts
19481
- import { existsSync as existsSync31, readFileSync as readFileSync20, writeFileSync as writeFileSync14, unlinkSync as unlinkSync9, mkdirSync as mkdirSync10 } from "fs";
19674
+ import { existsSync as existsSync32, readFileSync as readFileSync20, writeFileSync as writeFileSync14, unlinkSync as unlinkSync9, mkdirSync as mkdirSync10 } from "fs";
19482
19675
  import { dirname as dirname6, join as join41 } from "path";
19483
19676
 
19484
19677
  // src/hooks/ralph-loop/constants.ts
@@ -19493,7 +19686,7 @@ function getStateFilePath(directory, customPath) {
19493
19686
  }
19494
19687
  function readState(directory, customPath) {
19495
19688
  const filePath = getStateFilePath(directory, customPath);
19496
- if (!existsSync31(filePath)) {
19689
+ if (!existsSync32(filePath)) {
19497
19690
  return null;
19498
19691
  }
19499
19692
  try {
@@ -19530,7 +19723,7 @@ function writeState(directory, state2, customPath) {
19530
19723
  const filePath = getStateFilePath(directory, customPath);
19531
19724
  try {
19532
19725
  const dir = dirname6(filePath);
19533
- if (!existsSync31(dir)) {
19726
+ if (!existsSync32(dir)) {
19534
19727
  mkdirSync10(dir, { recursive: true });
19535
19728
  }
19536
19729
  const sessionIdLine = state2.session_id ? `session_id: "${state2.session_id}"
@@ -19553,7 +19746,7 @@ ${state2.prompt}
19553
19746
  function clearState(directory, customPath) {
19554
19747
  const filePath = getStateFilePath(directory, customPath);
19555
19748
  try {
19556
- if (existsSync31(filePath)) {
19749
+ if (existsSync32(filePath)) {
19557
19750
  unlinkSync9(filePath);
19558
19751
  }
19559
19752
  return true;
@@ -19592,6 +19785,7 @@ function createRalphLoopHook(ctx, options) {
19592
19785
  const stateDir = config?.state_dir;
19593
19786
  const getTranscriptPath2 = options?.getTranscriptPath ?? getTranscriptPath;
19594
19787
  const apiTimeout = options?.apiTimeout ?? DEFAULT_API_TIMEOUT;
19788
+ const checkSessionExists = options?.checkSessionExists;
19595
19789
  function getSessionState(sessionID) {
19596
19790
  let state2 = sessions.get(sessionID);
19597
19791
  if (!state2) {
@@ -19604,7 +19798,7 @@ function createRalphLoopHook(ctx, options) {
19604
19798
  if (!transcriptPath)
19605
19799
  return false;
19606
19800
  try {
19607
- if (!existsSync32(transcriptPath))
19801
+ if (!existsSync33(transcriptPath))
19608
19802
  return false;
19609
19803
  const content = readFileSync21(transcriptPath, "utf-8");
19610
19804
  const pattern = new RegExp(`<promise>\\s*${escapeRegex(promise)}\\s*</promise>`, "is");
@@ -19693,6 +19887,24 @@ function createRalphLoopHook(ctx, options) {
19693
19887
  return;
19694
19888
  }
19695
19889
  if (state2.session_id && state2.session_id !== sessionID) {
19890
+ if (checkSessionExists) {
19891
+ try {
19892
+ const originalSessionExists = await checkSessionExists(state2.session_id);
19893
+ if (!originalSessionExists) {
19894
+ clearState(ctx.directory, stateDir);
19895
+ log(`[${HOOK_NAME3}] Cleared orphaned state from deleted session`, {
19896
+ orphanedSessionId: state2.session_id,
19897
+ currentSessionId: sessionID
19898
+ });
19899
+ return;
19900
+ }
19901
+ } catch (err) {
19902
+ log(`[${HOOK_NAME3}] Failed to check session existence`, {
19903
+ sessionId: state2.session_id,
19904
+ error: String(err)
19905
+ });
19906
+ }
19907
+ }
19696
19908
  return;
19697
19909
  }
19698
19910
  const transcriptPath = getTranscriptPath2(sessionID);
@@ -19861,11 +20073,12 @@ function extractPromptText3(parts) {
19861
20073
  }
19862
20074
 
19863
20075
  // src/hooks/auto-slash-command/executor.ts
19864
- import { existsSync as existsSync35, readdirSync as readdirSync12, readFileSync as readFileSync24 } from "fs";
20076
+ import { existsSync as existsSync36, readdirSync as readdirSync12, readFileSync as readFileSync24 } from "fs";
19865
20077
  import { join as join43, basename as basename2, dirname as dirname8 } from "path";
19866
20078
  import { homedir as homedir13 } from "os";
19867
20079
  // src/features/opencode-skill-loader/loader.ts
19868
- import { existsSync as existsSync33, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
20080
+ import { existsSync as existsSync34, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
20081
+ import { promises as fs9 } from "fs";
19869
20082
  import { join as join42, basename } from "path";
19870
20083
  import { homedir as homedir11 } from "os";
19871
20084
  function parseSkillMcpConfigFromFrontmatter(content) {
@@ -19884,7 +20097,7 @@ function parseSkillMcpConfigFromFrontmatter(content) {
19884
20097
  }
19885
20098
  function loadMcpJsonFromDir(skillDir) {
19886
20099
  const mcpJsonPath = join42(skillDir, "mcp.json");
19887
- if (!existsSync33(mcpJsonPath))
20100
+ if (!existsSync34(mcpJsonPath))
19888
20101
  return;
19889
20102
  try {
19890
20103
  const content = readFileSync22(mcpJsonPath, "utf-8");
@@ -19911,7 +20124,66 @@ function parseAllowedTools(allowedTools) {
19911
20124
  function loadSkillFromPath(skillPath, resolvedPath, defaultName, scope) {
19912
20125
  try {
19913
20126
  const content = readFileSync22(skillPath, "utf-8");
19914
- const { data, body } = parseFrontmatter(content);
20127
+ const { data } = parseFrontmatter(content);
20128
+ const frontmatterMcp = parseSkillMcpConfigFromFrontmatter(content);
20129
+ const mcpJsonMcp = loadMcpJsonFromDir(resolvedPath);
20130
+ const mcpConfig = mcpJsonMcp || frontmatterMcp;
20131
+ const skillName = data.name || defaultName;
20132
+ const originalDescription = data.description || "";
20133
+ const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
20134
+ const formattedDescription = `(${scope} - Skill) ${originalDescription}`;
20135
+ const lazyContent = {
20136
+ loaded: false,
20137
+ content: undefined,
20138
+ load: async () => {
20139
+ if (!lazyContent.loaded) {
20140
+ const fileContent = await fs9.readFile(skillPath, "utf-8");
20141
+ const { body } = parseFrontmatter(fileContent);
20142
+ lazyContent.content = `<skill-instruction>
20143
+ Base directory for this skill: ${resolvedPath}/
20144
+ File references (@path) in this skill are relative to this directory.
20145
+
20146
+ ${body.trim()}
20147
+ </skill-instruction>
20148
+
20149
+ <user-request>
20150
+ $ARGUMENTS
20151
+ </user-request>`;
20152
+ lazyContent.loaded = true;
20153
+ }
20154
+ return lazyContent.content;
20155
+ }
20156
+ };
20157
+ const definition = {
20158
+ name: skillName,
20159
+ description: formattedDescription,
20160
+ template: "",
20161
+ model: sanitizeModelField(data.model, isOpencodeSource ? "opencode" : "claude-code"),
20162
+ agent: data.agent,
20163
+ subtask: data.subtask,
20164
+ argumentHint: data["argument-hint"]
20165
+ };
20166
+ return {
20167
+ name: skillName,
20168
+ path: skillPath,
20169
+ resolvedPath,
20170
+ definition,
20171
+ scope,
20172
+ license: data.license,
20173
+ compatibility: data.compatibility,
20174
+ metadata: data.metadata,
20175
+ allowedTools: parseAllowedTools(data["allowed-tools"]),
20176
+ mcpConfig,
20177
+ lazyContent
20178
+ };
20179
+ } catch {
20180
+ return null;
20181
+ }
20182
+ }
20183
+ async function loadSkillFromPathAsync(skillPath, resolvedPath, defaultName, scope) {
20184
+ try {
20185
+ const content = await fs9.readFile(skillPath, "utf-8");
20186
+ const { data } = parseFrontmatter(content);
19915
20187
  const frontmatterMcp = parseSkillMcpConfigFromFrontmatter(content);
19916
20188
  const mcpJsonMcp = loadMcpJsonFromDir(resolvedPath);
19917
20189
  const mcpConfig = mcpJsonMcp || frontmatterMcp;
@@ -19919,7 +20191,14 @@ function loadSkillFromPath(skillPath, resolvedPath, defaultName, scope) {
19919
20191
  const originalDescription = data.description || "";
19920
20192
  const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
19921
20193
  const formattedDescription = `(${scope} - Skill) ${originalDescription}`;
19922
- const wrappedTemplate = `<skill-instruction>
20194
+ const lazyContent = {
20195
+ loaded: false,
20196
+ content: undefined,
20197
+ load: async () => {
20198
+ if (!lazyContent.loaded) {
20199
+ const fileContent = await fs9.readFile(skillPath, "utf-8");
20200
+ const { body } = parseFrontmatter(fileContent);
20201
+ lazyContent.content = `<skill-instruction>
19923
20202
  Base directory for this skill: ${resolvedPath}/
19924
20203
  File references (@path) in this skill are relative to this directory.
19925
20204
 
@@ -19929,10 +20208,15 @@ ${body.trim()}
19929
20208
  <user-request>
19930
20209
  $ARGUMENTS
19931
20210
  </user-request>`;
20211
+ lazyContent.loaded = true;
20212
+ }
20213
+ return lazyContent.content;
20214
+ }
20215
+ };
19932
20216
  const definition = {
19933
20217
  name: skillName,
19934
20218
  description: formattedDescription,
19935
- template: wrappedTemplate,
20219
+ template: "",
19936
20220
  model: sanitizeModelField(data.model, isOpencodeSource ? "opencode" : "claude-code"),
19937
20221
  agent: data.agent,
19938
20222
  subtask: data.subtask,
@@ -19948,14 +20232,15 @@ $ARGUMENTS
19948
20232
  compatibility: data.compatibility,
19949
20233
  metadata: data.metadata,
19950
20234
  allowedTools: parseAllowedTools(data["allowed-tools"]),
19951
- mcpConfig
20235
+ mcpConfig,
20236
+ lazyContent
19952
20237
  };
19953
20238
  } catch {
19954
20239
  return null;
19955
20240
  }
19956
20241
  }
19957
20242
  function loadSkillsFromDir(skillsDir, scope) {
19958
- if (!existsSync33(skillsDir)) {
20243
+ if (!existsSync34(skillsDir)) {
19959
20244
  return [];
19960
20245
  }
19961
20246
  const entries = readdirSync11(skillsDir, { withFileTypes: true });
@@ -19968,14 +20253,14 @@ function loadSkillsFromDir(skillsDir, scope) {
19968
20253
  const resolvedPath = resolveSymlink(entryPath);
19969
20254
  const dirName = entry.name;
19970
20255
  const skillMdPath = join42(resolvedPath, "SKILL.md");
19971
- if (existsSync33(skillMdPath)) {
20256
+ if (existsSync34(skillMdPath)) {
19972
20257
  const skill = loadSkillFromPath(skillMdPath, resolvedPath, dirName, scope);
19973
20258
  if (skill)
19974
20259
  skills.push(skill);
19975
20260
  continue;
19976
20261
  }
19977
20262
  const namedSkillMdPath = join42(resolvedPath, `${dirName}.md`);
19978
- if (existsSync33(namedSkillMdPath)) {
20263
+ if (existsSync34(namedSkillMdPath)) {
19979
20264
  const skill = loadSkillFromPath(namedSkillMdPath, resolvedPath, dirName, scope);
19980
20265
  if (skill)
19981
20266
  skills.push(skill);
@@ -19992,6 +20277,43 @@ function loadSkillsFromDir(skillsDir, scope) {
19992
20277
  }
19993
20278
  return skills;
19994
20279
  }
20280
+ async function loadSkillsFromDirAsync(skillsDir, scope) {
20281
+ const entries = await fs9.readdir(skillsDir, { withFileTypes: true }).catch(() => []);
20282
+ const skills = [];
20283
+ for (const entry of entries) {
20284
+ if (entry.name.startsWith("."))
20285
+ continue;
20286
+ const entryPath = join42(skillsDir, entry.name);
20287
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
20288
+ const resolvedPath = resolveSymlink(entryPath);
20289
+ const dirName = entry.name;
20290
+ const skillMdPath = join42(resolvedPath, "SKILL.md");
20291
+ try {
20292
+ await fs9.access(skillMdPath);
20293
+ const skill = await loadSkillFromPathAsync(skillMdPath, resolvedPath, dirName, scope);
20294
+ if (skill)
20295
+ skills.push(skill);
20296
+ continue;
20297
+ } catch {}
20298
+ const namedSkillMdPath = join42(resolvedPath, `${dirName}.md`);
20299
+ try {
20300
+ await fs9.access(namedSkillMdPath);
20301
+ const skill = await loadSkillFromPathAsync(namedSkillMdPath, resolvedPath, dirName, scope);
20302
+ if (skill)
20303
+ skills.push(skill);
20304
+ continue;
20305
+ } catch {}
20306
+ continue;
20307
+ }
20308
+ if (isMarkdownFile(entry)) {
20309
+ const skillName = basename(entry.name, ".md");
20310
+ const skill = await loadSkillFromPathAsync(entryPath, skillsDir, skillName, scope);
20311
+ if (skill)
20312
+ skills.push(skill);
20313
+ }
20314
+ }
20315
+ return skills;
20316
+ }
19995
20317
  function skillsToRecord(skills) {
19996
20318
  const result = {};
19997
20319
  for (const skill of skills) {
@@ -20046,24 +20368,24 @@ function discoverSkills(options = {}) {
20046
20368
  const userSkills = loadSkillsFromDir(userDir, "user");
20047
20369
  return [...opencodeProjectSkills, ...projectSkills, ...opencodeGlobalSkills, ...userSkills];
20048
20370
  }
20049
- function discoverUserClaudeSkills() {
20371
+ async function discoverUserClaudeSkillsAsync() {
20050
20372
  const userSkillsDir = join42(getClaudeConfigDir(), "skills");
20051
- return loadSkillsFromDir(userSkillsDir, "user");
20373
+ return loadSkillsFromDirAsync(userSkillsDir, "user");
20052
20374
  }
20053
- function discoverProjectClaudeSkills() {
20375
+ async function discoverProjectClaudeSkillsAsync() {
20054
20376
  const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
20055
- return loadSkillsFromDir(projectSkillsDir, "project");
20377
+ return loadSkillsFromDirAsync(projectSkillsDir, "project");
20056
20378
  }
20057
- function discoverOpencodeGlobalSkills() {
20379
+ async function discoverOpencodeGlobalSkillsAsync() {
20058
20380
  const opencodeSkillsDir = join42(homedir11(), ".config", "opencode", "skill");
20059
- return loadSkillsFromDir(opencodeSkillsDir, "opencode");
20381
+ return loadSkillsFromDirAsync(opencodeSkillsDir, "opencode");
20060
20382
  }
20061
- function discoverOpencodeProjectSkills() {
20383
+ async function discoverOpencodeProjectSkillsAsync() {
20062
20384
  const opencodeProjectDir = join42(process.cwd(), ".opencode", "skill");
20063
- return loadSkillsFromDir(opencodeProjectDir, "opencode-project");
20385
+ return loadSkillsFromDirAsync(opencodeProjectDir, "opencode-project");
20064
20386
  }
20065
20387
  // src/features/opencode-skill-loader/merger.ts
20066
- import { readFileSync as readFileSync23, existsSync as existsSync34 } from "fs";
20388
+ import { readFileSync as readFileSync23, existsSync as existsSync35 } from "fs";
20067
20389
  import { dirname as dirname7, resolve as resolve5, isAbsolute as isAbsolute2 } from "path";
20068
20390
  import { homedir as homedir12 } from "os";
20069
20391
  var SCOPE_PRIORITY = {
@@ -20111,7 +20433,7 @@ function resolveFilePath2(from, configDir) {
20111
20433
  }
20112
20434
  function loadSkillFromFile(filePath) {
20113
20435
  try {
20114
- if (!existsSync34(filePath))
20436
+ if (!existsSync35(filePath))
20115
20437
  return null;
20116
20438
  const content = readFileSync23(filePath, "utf-8");
20117
20439
  const { data, body } = parseFrontmatter(content);
@@ -20267,7 +20589,7 @@ function mergeSkills(builtinSkills, config, userClaudeSkills, userOpencodeSkills
20267
20589
  }
20268
20590
  // src/hooks/auto-slash-command/executor.ts
20269
20591
  function discoverCommandsFromDir(commandsDir, scope) {
20270
- if (!existsSync35(commandsDir)) {
20592
+ if (!existsSync36(commandsDir)) {
20271
20593
  return [];
20272
20594
  }
20273
20595
  const entries = readdirSync12(commandsDir, { withFileTypes: true });
@@ -20490,6 +20812,147 @@ ${EDIT_ERROR_REMINDER}`;
20490
20812
  }
20491
20813
  };
20492
20814
  }
20815
+ // src/features/context-injector/collector.ts
20816
+ var PRIORITY_ORDER = {
20817
+ critical: 0,
20818
+ high: 1,
20819
+ normal: 2,
20820
+ low: 3
20821
+ };
20822
+ var CONTEXT_SEPARATOR = `
20823
+
20824
+ ---
20825
+
20826
+ `;
20827
+
20828
+ class ContextCollector {
20829
+ sessions = new Map;
20830
+ register(sessionID, options) {
20831
+ if (!this.sessions.has(sessionID)) {
20832
+ this.sessions.set(sessionID, new Map);
20833
+ }
20834
+ const sessionMap = this.sessions.get(sessionID);
20835
+ const key = `${options.source}:${options.id}`;
20836
+ const entry = {
20837
+ id: options.id,
20838
+ source: options.source,
20839
+ content: options.content,
20840
+ priority: options.priority ?? "normal",
20841
+ timestamp: Date.now(),
20842
+ metadata: options.metadata
20843
+ };
20844
+ sessionMap.set(key, entry);
20845
+ }
20846
+ getPending(sessionID) {
20847
+ const sessionMap = this.sessions.get(sessionID);
20848
+ if (!sessionMap || sessionMap.size === 0) {
20849
+ return {
20850
+ merged: "",
20851
+ entries: [],
20852
+ hasContent: false
20853
+ };
20854
+ }
20855
+ const entries = this.sortEntries([...sessionMap.values()]);
20856
+ const merged = entries.map((e) => e.content).join(CONTEXT_SEPARATOR);
20857
+ return {
20858
+ merged,
20859
+ entries,
20860
+ hasContent: entries.length > 0
20861
+ };
20862
+ }
20863
+ consume(sessionID) {
20864
+ const pending = this.getPending(sessionID);
20865
+ this.clear(sessionID);
20866
+ return pending;
20867
+ }
20868
+ clear(sessionID) {
20869
+ this.sessions.delete(sessionID);
20870
+ }
20871
+ hasPending(sessionID) {
20872
+ const sessionMap = this.sessions.get(sessionID);
20873
+ return sessionMap !== undefined && sessionMap.size > 0;
20874
+ }
20875
+ sortEntries(entries) {
20876
+ return entries.sort((a, b) => {
20877
+ const priorityDiff = PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority];
20878
+ if (priorityDiff !== 0)
20879
+ return priorityDiff;
20880
+ return a.timestamp - b.timestamp;
20881
+ });
20882
+ }
20883
+ }
20884
+ var contextCollector = new ContextCollector;
20885
+ // src/features/context-injector/injector.ts
20886
+ function createContextInjectorHook(collector) {
20887
+ return {
20888
+ "chat.message": async (_input, _output) => {}
20889
+ };
20890
+ }
20891
+ function createContextInjectorMessagesTransformHook(collector) {
20892
+ return {
20893
+ "experimental.chat.messages.transform": async (_input, output) => {
20894
+ const { messages } = output;
20895
+ if (messages.length === 0) {
20896
+ return;
20897
+ }
20898
+ let lastUserMessageIndex = -1;
20899
+ for (let i2 = messages.length - 1;i2 >= 0; i2--) {
20900
+ if (messages[i2].info.role === "user") {
20901
+ lastUserMessageIndex = i2;
20902
+ break;
20903
+ }
20904
+ }
20905
+ if (lastUserMessageIndex === -1) {
20906
+ return;
20907
+ }
20908
+ const lastUserMessage = messages[lastUserMessageIndex];
20909
+ const sessionID = lastUserMessage.info.sessionID;
20910
+ if (!sessionID) {
20911
+ return;
20912
+ }
20913
+ if (!collector.hasPending(sessionID)) {
20914
+ return;
20915
+ }
20916
+ const pending = collector.consume(sessionID);
20917
+ if (!pending.hasContent) {
20918
+ return;
20919
+ }
20920
+ const refInfo = lastUserMessage.info;
20921
+ const syntheticMessageId = `synthetic_ctx_${Date.now()}`;
20922
+ const syntheticPartId = `synthetic_ctx_part_${Date.now()}`;
20923
+ const now = Date.now();
20924
+ const syntheticMessage = {
20925
+ info: {
20926
+ id: syntheticMessageId,
20927
+ sessionID,
20928
+ role: "user",
20929
+ time: { created: now },
20930
+ agent: refInfo.agent ?? "Sisyphus",
20931
+ model: refInfo.model ?? { providerID: "unknown", modelID: "unknown" },
20932
+ path: refInfo.path ?? { cwd: "/", root: "/" }
20933
+ },
20934
+ parts: [
20935
+ {
20936
+ id: syntheticPartId,
20937
+ sessionID,
20938
+ messageID: syntheticMessageId,
20939
+ type: "text",
20940
+ text: pending.merged,
20941
+ synthetic: true,
20942
+ time: { start: now, end: now }
20943
+ }
20944
+ ]
20945
+ };
20946
+ messages.splice(lastUserMessageIndex, 0, syntheticMessage);
20947
+ log("[context-injector] Injected synthetic message from collector", {
20948
+ sessionID,
20949
+ insertIndex: lastUserMessageIndex,
20950
+ contextLength: pending.merged.length,
20951
+ newMessageCount: messages.length
20952
+ });
20953
+ }
20954
+ };
20955
+ }
20493
20956
  // src/auth/antigravity/constants.ts
20494
20957
  var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
20495
20958
  var ANTIGRAVITY_CLIENT_SECRET = "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";
@@ -22337,7 +22800,7 @@ function createBuiltinSkills() {
22337
22800
  return [playwrightSkill];
22338
22801
  }
22339
22802
  // src/features/claude-code-mcp-loader/loader.ts
22340
- import { existsSync as existsSync36, readFileSync as readFileSync25 } from "fs";
22803
+ import { existsSync as existsSync37, readFileSync as readFileSync25 } from "fs";
22341
22804
  import { join as join44 } from "path";
22342
22805
 
22343
22806
  // src/features/claude-code-mcp-loader/env-expander.ts
@@ -22413,7 +22876,7 @@ function getMcpConfigPaths() {
22413
22876
  ];
22414
22877
  }
22415
22878
  async function loadMcpConfigFile(filePath) {
22416
- if (!existsSync36(filePath)) {
22879
+ if (!existsSync37(filePath)) {
22417
22880
  return null;
22418
22881
  }
22419
22882
  try {
@@ -22428,7 +22891,7 @@ function getSystemMcpServerNames() {
22428
22891
  const names = new Set;
22429
22892
  const paths = getMcpConfigPaths();
22430
22893
  for (const { path: path7 } of paths) {
22431
- if (!existsSync36(path7))
22894
+ if (!existsSync37(path7))
22432
22895
  continue;
22433
22896
  try {
22434
22897
  const content = readFileSync25(path7, "utf-8");
@@ -22541,7 +23004,18 @@ var LSP_INSTALL_HINTS = {
22541
23004
  "lua-ls": "See https://github.com/LuaLS/lua-language-server",
22542
23005
  php: "npm install -g intelephense",
22543
23006
  dart: "Included with Dart SDK",
22544
- "terraform-ls": "See https://github.com/hashicorp/terraform-ls"
23007
+ "terraform-ls": "See https://github.com/hashicorp/terraform-ls",
23008
+ terraform: "See https://github.com/hashicorp/terraform-ls",
23009
+ prisma: "npm install -g prisma",
23010
+ "ocaml-lsp": "opam install ocaml-lsp-server",
23011
+ texlab: "See https://github.com/latex-lsp/texlab",
23012
+ dockerfile: "npm install -g dockerfile-language-server-nodejs",
23013
+ gleam: "See https://gleam.run/getting-started/installing/",
23014
+ "clojure-lsp": "See https://clojure-lsp.io/installation/",
23015
+ nixd: "nix profile install nixpkgs#nixd",
23016
+ tinymist: "See https://github.com/Myriad-Dreamin/tinymist",
23017
+ "haskell-language-server": "ghcup install hls",
23018
+ bash: "npm install -g bash-language-server"
22545
23019
  };
22546
23020
  var BUILTIN_SERVERS = {
22547
23021
  typescript: {
@@ -22646,6 +23120,10 @@ var BUILTIN_SERVERS = {
22646
23120
  command: ["astro-ls", "--stdio"],
22647
23121
  extensions: [".astro"]
22648
23122
  },
23123
+ bash: {
23124
+ command: ["bash-language-server", "start"],
23125
+ extensions: [".sh", ".bash", ".zsh", ".ksh"]
23126
+ },
22649
23127
  "bash-ls": {
22650
23128
  command: ["bash-language-server", "start"],
22651
23129
  extensions: [".sh", ".bash", ".zsh", ".ksh"]
@@ -22670,9 +23148,49 @@ var BUILTIN_SERVERS = {
22670
23148
  command: ["dart", "language-server", "--lsp"],
22671
23149
  extensions: [".dart"]
22672
23150
  },
23151
+ terraform: {
23152
+ command: ["terraform-ls", "serve"],
23153
+ extensions: [".tf", ".tfvars"]
23154
+ },
22673
23155
  "terraform-ls": {
22674
23156
  command: ["terraform-ls", "serve"],
22675
23157
  extensions: [".tf", ".tfvars"]
23158
+ },
23159
+ prisma: {
23160
+ command: ["prisma", "language-server"],
23161
+ extensions: [".prisma"]
23162
+ },
23163
+ "ocaml-lsp": {
23164
+ command: ["ocamllsp"],
23165
+ extensions: [".ml", ".mli"]
23166
+ },
23167
+ texlab: {
23168
+ command: ["texlab"],
23169
+ extensions: [".tex", ".bib"]
23170
+ },
23171
+ dockerfile: {
23172
+ command: ["docker-langserver", "--stdio"],
23173
+ extensions: [".dockerfile"]
23174
+ },
23175
+ gleam: {
23176
+ command: ["gleam", "lsp"],
23177
+ extensions: [".gleam"]
23178
+ },
23179
+ "clojure-lsp": {
23180
+ command: ["clojure-lsp", "listen"],
23181
+ extensions: [".clj", ".cljs", ".cljc", ".edn"]
23182
+ },
23183
+ nixd: {
23184
+ command: ["nixd"],
23185
+ extensions: [".nix"]
23186
+ },
23187
+ tinymist: {
23188
+ command: ["tinymist"],
23189
+ extensions: [".typ", ".typc"]
23190
+ },
23191
+ "haskell-language-server": {
23192
+ command: ["haskell-language-server-wrapper", "--lsp"],
23193
+ extensions: [".hs", ".lhs"]
22676
23194
  }
22677
23195
  };
22678
23196
  var EXT_TO_LANG = {
@@ -22788,6 +23306,14 @@ var EXT_TO_LANG = {
22788
23306
  ".tf": "terraform",
22789
23307
  ".tfvars": "terraform-vars",
22790
23308
  ".hcl": "hcl",
23309
+ ".nix": "nix",
23310
+ ".typ": "typst",
23311
+ ".typc": "typst",
23312
+ ".ets": "typescript",
23313
+ ".lhs": "haskell",
23314
+ ".kt": "kotlin",
23315
+ ".kts": "kotlin",
23316
+ ".prisma": "prisma",
22791
23317
  ".h": "c",
22792
23318
  ".hpp": "cpp",
22793
23319
  ".hh": "cpp",
@@ -22800,11 +23326,11 @@ var EXT_TO_LANG = {
22800
23326
  ".gql": "graphql"
22801
23327
  };
22802
23328
  // src/tools/lsp/config.ts
22803
- import { existsSync as existsSync37, readFileSync as readFileSync26 } from "fs";
23329
+ import { existsSync as existsSync38, readFileSync as readFileSync26 } from "fs";
22804
23330
  import { join as join45 } from "path";
22805
23331
  import { homedir as homedir14 } from "os";
22806
23332
  function loadJsonFile(path7) {
22807
- if (!existsSync37(path7))
23333
+ if (!existsSync38(path7))
22808
23334
  return null;
22809
23335
  try {
22810
23336
  return JSON.parse(readFileSync26(path7, "utf-8"));
@@ -22930,7 +23456,7 @@ function isServerInstalled(command) {
22930
23456
  return false;
22931
23457
  const cmd = command[0];
22932
23458
  if (cmd.includes("/") || cmd.includes("\\")) {
22933
- if (existsSync37(cmd))
23459
+ if (existsSync38(cmd))
22934
23460
  return true;
22935
23461
  }
22936
23462
  const isWindows2 = process.platform === "win32";
@@ -22939,7 +23465,7 @@ function isServerInstalled(command) {
22939
23465
  const pathSeparator = isWindows2 ? ";" : ":";
22940
23466
  const paths = pathEnv.split(pathSeparator);
22941
23467
  for (const p of paths) {
22942
- if (existsSync37(join45(p, cmd)) || existsSync37(join45(p, cmd + ext))) {
23468
+ if (existsSync38(join45(p, cmd)) || existsSync38(join45(p, cmd + ext))) {
22943
23469
  return true;
22944
23470
  }
22945
23471
  }
@@ -22953,7 +23479,7 @@ function isServerInstalled(command) {
22953
23479
  join45(homedir14(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
22954
23480
  ];
22955
23481
  for (const p of additionalPaths) {
22956
- if (existsSync37(p)) {
23482
+ if (existsSync38(p)) {
22957
23483
  return true;
22958
23484
  }
22959
23485
  }
@@ -23552,16 +24078,16 @@ ${msg}`);
23552
24078
  // src/tools/lsp/utils.ts
23553
24079
  import { extname as extname2, resolve as resolve7 } from "path";
23554
24080
  import { fileURLToPath as fileURLToPath2 } from "url";
23555
- import { existsSync as existsSync38, readFileSync as readFileSync28, writeFileSync as writeFileSync15 } from "fs";
24081
+ import { existsSync as existsSync39, readFileSync as readFileSync28, writeFileSync as writeFileSync15 } from "fs";
23556
24082
  function findWorkspaceRoot(filePath) {
23557
24083
  let dir = resolve7(filePath);
23558
- if (!existsSync38(dir) || !__require("fs").statSync(dir).isDirectory()) {
24084
+ if (!existsSync39(dir) || !__require("fs").statSync(dir).isDirectory()) {
23559
24085
  dir = __require("path").dirname(dir);
23560
24086
  }
23561
24087
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
23562
24088
  while (dir !== "/") {
23563
24089
  for (const marker of markers) {
23564
- if (existsSync38(__require("path").join(dir, marker))) {
24090
+ if (existsSync39(__require("path").join(dir, marker))) {
23565
24091
  return dir;
23566
24092
  }
23567
24093
  }
@@ -36525,11 +37051,11 @@ var lsp_code_action_resolve = tool({
36525
37051
  // src/tools/ast-grep/constants.ts
36526
37052
  import { createRequire as createRequire4 } from "module";
36527
37053
  import { dirname as dirname9, join as join47 } from "path";
36528
- import { existsSync as existsSync40, statSync as statSync4 } from "fs";
37054
+ import { existsSync as existsSync41, statSync as statSync4 } from "fs";
36529
37055
 
36530
37056
  // src/tools/ast-grep/downloader.ts
36531
37057
  var {spawn: spawn6 } = globalThis.Bun;
36532
- import { existsSync as existsSync39, mkdirSync as mkdirSync11, chmodSync as chmodSync2, unlinkSync as unlinkSync10 } from "fs";
37058
+ import { existsSync as existsSync40, mkdirSync as mkdirSync11, chmodSync as chmodSync2, unlinkSync as unlinkSync10 } from "fs";
36533
37059
  import { join as join46 } from "path";
36534
37060
  import { homedir as homedir15 } from "os";
36535
37061
  import { createRequire as createRequire3 } from "module";
@@ -36568,7 +37094,7 @@ function getBinaryName3() {
36568
37094
  }
36569
37095
  function getCachedBinaryPath2() {
36570
37096
  const binaryPath = join46(getCacheDir3(), getBinaryName3());
36571
- return existsSync39(binaryPath) ? binaryPath : null;
37097
+ return existsSync40(binaryPath) ? binaryPath : null;
36572
37098
  }
36573
37099
  async function extractZip2(archivePath, destDir) {
36574
37100
  const proc = process.platform === "win32" ? spawn6([
@@ -36595,7 +37121,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
36595
37121
  const cacheDir = getCacheDir3();
36596
37122
  const binaryName = getBinaryName3();
36597
37123
  const binaryPath = join46(cacheDir, binaryName);
36598
- if (existsSync39(binaryPath)) {
37124
+ if (existsSync40(binaryPath)) {
36599
37125
  return binaryPath;
36600
37126
  }
36601
37127
  const { arch, os: os6 } = platformInfo;
@@ -36603,7 +37129,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
36603
37129
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
36604
37130
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
36605
37131
  try {
36606
- if (!existsSync39(cacheDir)) {
37132
+ if (!existsSync40(cacheDir)) {
36607
37133
  mkdirSync11(cacheDir, { recursive: true });
36608
37134
  }
36609
37135
  const response2 = await fetch(downloadUrl, { redirect: "follow" });
@@ -36614,10 +37140,10 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
36614
37140
  const arrayBuffer = await response2.arrayBuffer();
36615
37141
  await Bun.write(archivePath, arrayBuffer);
36616
37142
  await extractZip2(archivePath, cacheDir);
36617
- if (existsSync39(archivePath)) {
37143
+ if (existsSync40(archivePath)) {
36618
37144
  unlinkSync10(archivePath);
36619
37145
  }
36620
- if (process.platform !== "win32" && existsSync39(binaryPath)) {
37146
+ if (process.platform !== "win32" && existsSync40(binaryPath)) {
36621
37147
  chmodSync2(binaryPath, 493);
36622
37148
  }
36623
37149
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -36669,7 +37195,7 @@ function findSgCliPathSync() {
36669
37195
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
36670
37196
  const cliDir = dirname9(cliPkgPath);
36671
37197
  const sgPath = join47(cliDir, binaryName);
36672
- if (existsSync40(sgPath) && isValidBinary(sgPath)) {
37198
+ if (existsSync41(sgPath) && isValidBinary(sgPath)) {
36673
37199
  return sgPath;
36674
37200
  }
36675
37201
  } catch {}
@@ -36681,7 +37207,7 @@ function findSgCliPathSync() {
36681
37207
  const pkgDir = dirname9(pkgPath);
36682
37208
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
36683
37209
  const binaryPath = join47(pkgDir, astGrepName);
36684
- if (existsSync40(binaryPath) && isValidBinary(binaryPath)) {
37210
+ if (existsSync41(binaryPath) && isValidBinary(binaryPath)) {
36685
37211
  return binaryPath;
36686
37212
  }
36687
37213
  } catch {}
@@ -36689,7 +37215,7 @@ function findSgCliPathSync() {
36689
37215
  if (process.platform === "darwin") {
36690
37216
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
36691
37217
  for (const path7 of homebrewPaths) {
36692
- if (existsSync40(path7) && isValidBinary(path7)) {
37218
+ if (existsSync41(path7) && isValidBinary(path7)) {
36693
37219
  return path7;
36694
37220
  }
36695
37221
  }
@@ -36744,11 +37270,11 @@ var DEFAULT_MAX_MATCHES = 500;
36744
37270
 
36745
37271
  // src/tools/ast-grep/cli.ts
36746
37272
  var {spawn: spawn7 } = globalThis.Bun;
36747
- import { existsSync as existsSync41 } from "fs";
37273
+ import { existsSync as existsSync42 } from "fs";
36748
37274
  var resolvedCliPath3 = null;
36749
37275
  var initPromise2 = null;
36750
37276
  async function getAstGrepPath() {
36751
- if (resolvedCliPath3 !== null && existsSync41(resolvedCliPath3)) {
37277
+ if (resolvedCliPath3 !== null && existsSync42(resolvedCliPath3)) {
36752
37278
  return resolvedCliPath3;
36753
37279
  }
36754
37280
  if (initPromise2) {
@@ -36756,7 +37282,7 @@ async function getAstGrepPath() {
36756
37282
  }
36757
37283
  initPromise2 = (async () => {
36758
37284
  const syncPath = findSgCliPathSync();
36759
- if (syncPath && existsSync41(syncPath)) {
37285
+ if (syncPath && existsSync42(syncPath)) {
36760
37286
  resolvedCliPath3 = syncPath;
36761
37287
  setSgCliPath(syncPath);
36762
37288
  return syncPath;
@@ -36790,7 +37316,7 @@ async function runSg(options) {
36790
37316
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
36791
37317
  args.push(...paths);
36792
37318
  let cliPath = getSgCliPath();
36793
- if (!existsSync41(cliPath) && cliPath !== "sg") {
37319
+ if (!existsSync42(cliPath) && cliPath !== "sg") {
36794
37320
  const downloadedPath = await getAstGrepPath();
36795
37321
  if (downloadedPath) {
36796
37322
  cliPath = downloadedPath;
@@ -37054,12 +37580,12 @@ var ast_grep_replace = tool({
37054
37580
  var {spawn: spawn9 } = globalThis.Bun;
37055
37581
 
37056
37582
  // src/tools/grep/constants.ts
37057
- import { existsSync as existsSync43 } from "fs";
37583
+ import { existsSync as existsSync44 } from "fs";
37058
37584
  import { join as join49, dirname as dirname10 } from "path";
37059
37585
  import { spawnSync } from "child_process";
37060
37586
 
37061
37587
  // src/tools/grep/downloader.ts
37062
- import { existsSync as existsSync42, mkdirSync as mkdirSync12, chmodSync as chmodSync3, unlinkSync as unlinkSync11, readdirSync as readdirSync13 } from "fs";
37588
+ import { existsSync as existsSync43, mkdirSync as mkdirSync12, chmodSync as chmodSync3, unlinkSync as unlinkSync11, readdirSync as readdirSync13 } from "fs";
37063
37589
  import { join as join48 } from "path";
37064
37590
  var {spawn: spawn8 } = globalThis.Bun;
37065
37591
  function findFileRecursive(dir, filename) {
@@ -37169,7 +37695,7 @@ async function downloadAndInstallRipgrep() {
37169
37695
  }
37170
37696
  const installDir = getInstallDir();
37171
37697
  const rgPath = getRgPath();
37172
- if (existsSync42(rgPath)) {
37698
+ if (existsSync43(rgPath)) {
37173
37699
  return rgPath;
37174
37700
  }
37175
37701
  mkdirSync12(installDir, { recursive: true });
@@ -37186,12 +37712,12 @@ async function downloadAndInstallRipgrep() {
37186
37712
  if (process.platform !== "win32") {
37187
37713
  chmodSync3(rgPath, 493);
37188
37714
  }
37189
- if (!existsSync42(rgPath)) {
37715
+ if (!existsSync43(rgPath)) {
37190
37716
  throw new Error("ripgrep binary not found after extraction");
37191
37717
  }
37192
37718
  return rgPath;
37193
37719
  } finally {
37194
- if (existsSync42(archivePath)) {
37720
+ if (existsSync43(archivePath)) {
37195
37721
  try {
37196
37722
  unlinkSync11(archivePath);
37197
37723
  } catch {}
@@ -37200,7 +37726,7 @@ async function downloadAndInstallRipgrep() {
37200
37726
  }
37201
37727
  function getInstalledRipgrepPath() {
37202
37728
  const rgPath = getRgPath();
37203
- return existsSync42(rgPath) ? rgPath : null;
37729
+ return existsSync43(rgPath) ? rgPath : null;
37204
37730
  }
37205
37731
 
37206
37732
  // src/tools/grep/constants.ts
@@ -37231,7 +37757,7 @@ function getOpenCodeBundledRg() {
37231
37757
  join49(execDir, "..", "libexec", rgName)
37232
37758
  ];
37233
37759
  for (const candidate of candidates) {
37234
- if (existsSync43(candidate)) {
37760
+ if (existsSync44(candidate)) {
37235
37761
  return candidate;
37236
37762
  }
37237
37763
  }
@@ -37684,10 +38210,10 @@ var glob = tool({
37684
38210
  }
37685
38211
  });
37686
38212
  // src/tools/slashcommand/tools.ts
37687
- import { existsSync as existsSync44, readdirSync as readdirSync14, readFileSync as readFileSync29 } from "fs";
38213
+ import { existsSync as existsSync45, readdirSync as readdirSync14, readFileSync as readFileSync29 } from "fs";
37688
38214
  import { join as join50, basename as basename3, dirname as dirname11 } from "path";
37689
38215
  function discoverCommandsFromDir2(commandsDir, scope) {
37690
- if (!existsSync44(commandsDir)) {
38216
+ if (!existsSync45(commandsDir)) {
37691
38217
  return [];
37692
38218
  }
37693
38219
  const entries = readdirSync14(commandsDir, { withFileTypes: true });
@@ -37936,11 +38462,11 @@ Has Todos: Yes (12 items, 8 completed)
37936
38462
  Has Transcript: Yes (234 entries)`;
37937
38463
 
37938
38464
  // src/tools/session-manager/storage.ts
37939
- import { existsSync as existsSync45, readdirSync as readdirSync15 } from "fs";
38465
+ import { existsSync as existsSync46, readdirSync as readdirSync15 } from "fs";
37940
38466
  import { readdir, readFile } from "fs/promises";
37941
38467
  import { join as join52 } from "path";
37942
38468
  async function getMainSessions(options) {
37943
- if (!existsSync45(SESSION_STORAGE))
38469
+ if (!existsSync46(SESSION_STORAGE))
37944
38470
  return [];
37945
38471
  const sessions = [];
37946
38472
  try {
@@ -37972,7 +38498,7 @@ async function getMainSessions(options) {
37972
38498
  return sessions.sort((a, b) => b.time.updated - a.time.updated);
37973
38499
  }
37974
38500
  async function getAllSessions() {
37975
- if (!existsSync45(MESSAGE_STORAGE4))
38501
+ if (!existsSync46(MESSAGE_STORAGE4))
37976
38502
  return [];
37977
38503
  const sessions = [];
37978
38504
  async function scanDirectory(dir) {
@@ -37997,16 +38523,16 @@ async function getAllSessions() {
37997
38523
  return [...new Set(sessions)];
37998
38524
  }
37999
38525
  function getMessageDir9(sessionID) {
38000
- if (!existsSync45(MESSAGE_STORAGE4))
38526
+ if (!existsSync46(MESSAGE_STORAGE4))
38001
38527
  return "";
38002
38528
  const directPath = join52(MESSAGE_STORAGE4, sessionID);
38003
- if (existsSync45(directPath)) {
38529
+ if (existsSync46(directPath)) {
38004
38530
  return directPath;
38005
38531
  }
38006
38532
  try {
38007
38533
  for (const dir of readdirSync15(MESSAGE_STORAGE4)) {
38008
38534
  const sessionPath = join52(MESSAGE_STORAGE4, dir, sessionID);
38009
- if (existsSync45(sessionPath)) {
38535
+ if (existsSync46(sessionPath)) {
38010
38536
  return sessionPath;
38011
38537
  }
38012
38538
  }
@@ -38020,7 +38546,7 @@ function sessionExists(sessionID) {
38020
38546
  }
38021
38547
  async function readSessionMessages(sessionID) {
38022
38548
  const messageDir = getMessageDir9(sessionID);
38023
- if (!messageDir || !existsSync45(messageDir))
38549
+ if (!messageDir || !existsSync46(messageDir))
38024
38550
  return [];
38025
38551
  const messages = [];
38026
38552
  try {
@@ -38056,7 +38582,7 @@ async function readSessionMessages(sessionID) {
38056
38582
  }
38057
38583
  async function readParts2(messageID) {
38058
38584
  const partDir = join52(PART_STORAGE4, messageID);
38059
- if (!existsSync45(partDir))
38585
+ if (!existsSync46(partDir))
38060
38586
  return [];
38061
38587
  const parts = [];
38062
38588
  try {
@@ -38077,7 +38603,7 @@ async function readParts2(messageID) {
38077
38603
  return parts.sort((a, b) => a.id.localeCompare(b.id));
38078
38604
  }
38079
38605
  async function readSessionTodos(sessionID) {
38080
- if (!existsSync45(TODO_DIR2))
38606
+ if (!existsSync46(TODO_DIR2))
38081
38607
  return [];
38082
38608
  try {
38083
38609
  const allFiles = await readdir(TODO_DIR2);
@@ -38104,10 +38630,10 @@ async function readSessionTodos(sessionID) {
38104
38630
  return [];
38105
38631
  }
38106
38632
  async function readSessionTranscript(sessionID) {
38107
- if (!existsSync45(TRANSCRIPT_DIR2))
38633
+ if (!existsSync46(TRANSCRIPT_DIR2))
38108
38634
  return 0;
38109
38635
  const transcriptFile = join52(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
38110
- if (!existsSync45(transcriptFile))
38636
+ if (!existsSync46(transcriptFile))
38111
38637
  return 0;
38112
38638
  try {
38113
38639
  const content = await readFile(transcriptFile, "utf-8");
@@ -38487,6 +39013,12 @@ async function getTmuxPath() {
38487
39013
  function getCachedTmuxPath() {
38488
39014
  return tmuxPath;
38489
39015
  }
39016
+ function startBackgroundCheck2() {
39017
+ if (!initPromise3) {
39018
+ initPromise3 = getTmuxPath();
39019
+ initPromise3.catch(() => {});
39020
+ }
39021
+ }
38490
39022
 
38491
39023
  // src/tools/interactive-bash/tools.ts
38492
39024
  function tokenizeCommand2(cmd) {
@@ -38614,7 +39146,12 @@ function formatSkillsXml(skills) {
38614
39146
  ${skillsXml}
38615
39147
  </available_skills>`;
38616
39148
  }
38617
- function extractSkillBody(skill) {
39149
+ async function extractSkillBody(skill) {
39150
+ if (skill.lazyContent) {
39151
+ const fullTemplate = await skill.lazyContent.load();
39152
+ const templateMatch2 = fullTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
39153
+ return templateMatch2 ? templateMatch2[1].trim() : fullTemplate;
39154
+ }
38618
39155
  if (skill.path) {
38619
39156
  const content = readFileSync30(skill.path, "utf-8");
38620
39157
  const { body } = parseFrontmatter(content);
@@ -38698,7 +39235,7 @@ function createSkillTool(options = {}) {
38698
39235
  const available = skills.map((s) => s.name).join(", ");
38699
39236
  throw new Error(`Skill "${args.name}" not found. Available skills: ${available || "none"}`);
38700
39237
  }
38701
- const body = extractSkillBody(skill);
39238
+ const body = await extractSkillBody(skill);
38702
39239
  const dir = skill.path ? dirname12(skill.path) : skill.resolvedPath || process.cwd();
38703
39240
  const output = [
38704
39241
  `## Skill: ${skill.name}`,
@@ -38864,7 +39401,7 @@ function createSkillMcpTool(options) {
38864
39401
  });
38865
39402
  }
38866
39403
  // src/tools/background-task/tools.ts
38867
- import { existsSync as existsSync46, readdirSync as readdirSync16 } from "fs";
39404
+ import { existsSync as existsSync47, readdirSync as readdirSync16 } from "fs";
38868
39405
  import { join as join53 } from "path";
38869
39406
 
38870
39407
  // src/tools/background-task/constants.ts
@@ -38876,14 +39413,14 @@ var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=
38876
39413
 
38877
39414
  // src/tools/background-task/tools.ts
38878
39415
  function getMessageDir10(sessionID) {
38879
- if (!existsSync46(MESSAGE_STORAGE))
39416
+ if (!existsSync47(MESSAGE_STORAGE))
38880
39417
  return null;
38881
39418
  const directPath = join53(MESSAGE_STORAGE, sessionID);
38882
- if (existsSync46(directPath))
39419
+ if (existsSync47(directPath))
38883
39420
  return directPath;
38884
39421
  for (const dir of readdirSync16(MESSAGE_STORAGE)) {
38885
39422
  const sessionPath = join53(MESSAGE_STORAGE, dir, sessionID);
38886
- if (existsSync46(sessionPath))
39423
+ if (existsSync47(sessionPath))
38887
39424
  return sessionPath;
38888
39425
  }
38889
39426
  return null;
@@ -39469,18 +40006,18 @@ var builtinTools = {
39469
40006
  session_info
39470
40007
  };
39471
40008
  // src/features/background-agent/manager.ts
39472
- import { existsSync as existsSync47, readdirSync as readdirSync17 } from "fs";
40009
+ import { existsSync as existsSync48, readdirSync as readdirSync17 } from "fs";
39473
40010
  import { join as join54 } from "path";
39474
40011
  var TASK_TTL_MS = 30 * 60 * 1000;
39475
40012
  function getMessageDir11(sessionID) {
39476
- if (!existsSync47(MESSAGE_STORAGE))
40013
+ if (!existsSync48(MESSAGE_STORAGE))
39477
40014
  return null;
39478
40015
  const directPath = join54(MESSAGE_STORAGE, sessionID);
39479
- if (existsSync47(directPath))
40016
+ if (existsSync48(directPath))
39480
40017
  return directPath;
39481
40018
  for (const dir of readdirSync17(MESSAGE_STORAGE)) {
39482
40019
  const sessionPath = join54(MESSAGE_STORAGE, dir, sessionID);
39483
- if (existsSync47(sessionPath))
40020
+ if (existsSync48(sessionPath))
39484
40021
  return sessionPath;
39485
40022
  }
39486
40023
  return null;
@@ -42399,21 +42936,89 @@ function isElectron() {
42399
42936
  return "type" in process2;
42400
42937
  }
42401
42938
 
42939
+ // src/features/skill-mcp-manager/env-cleaner.ts
42940
+ var EXCLUDED_ENV_PATTERNS = [
42941
+ /^NPM_CONFIG_/i,
42942
+ /^npm_config_/,
42943
+ /^YARN_/,
42944
+ /^PNPM_/,
42945
+ /^NO_UPDATE_NOTIFIER$/
42946
+ ];
42947
+ function createCleanMcpEnvironment(customEnv = {}) {
42948
+ const cleanEnv = {};
42949
+ for (const [key, value] of Object.entries(process.env)) {
42950
+ if (value === undefined)
42951
+ continue;
42952
+ const shouldExclude = EXCLUDED_ENV_PATTERNS.some((pattern) => pattern.test(key));
42953
+ if (!shouldExclude) {
42954
+ cleanEnv[key] = value;
42955
+ }
42956
+ }
42957
+ Object.assign(cleanEnv, customEnv);
42958
+ return cleanEnv;
42959
+ }
42960
+
42402
42961
  // src/features/skill-mcp-manager/manager.ts
42403
42962
  class SkillMcpManager {
42404
42963
  clients = new Map;
42964
+ pendingConnections = new Map;
42965
+ cleanupRegistered = false;
42966
+ cleanupInterval = null;
42967
+ IDLE_TIMEOUT = 5 * 60 * 1000;
42405
42968
  getClientKey(info) {
42406
42969
  return `${info.sessionID}:${info.skillName}:${info.serverName}`;
42407
42970
  }
42971
+ registerProcessCleanup() {
42972
+ if (this.cleanupRegistered)
42973
+ return;
42974
+ this.cleanupRegistered = true;
42975
+ const cleanup = async () => {
42976
+ for (const [, managed] of this.clients) {
42977
+ try {
42978
+ await managed.client.close();
42979
+ } catch {}
42980
+ try {
42981
+ await managed.transport.close();
42982
+ } catch {}
42983
+ }
42984
+ this.clients.clear();
42985
+ this.pendingConnections.clear();
42986
+ };
42987
+ process.on("SIGINT", async () => {
42988
+ await cleanup();
42989
+ process.exit(0);
42990
+ });
42991
+ process.on("SIGTERM", async () => {
42992
+ await cleanup();
42993
+ process.exit(0);
42994
+ });
42995
+ if (process.platform === "win32") {
42996
+ process.on("SIGBREAK", async () => {
42997
+ await cleanup();
42998
+ process.exit(0);
42999
+ });
43000
+ }
43001
+ }
42408
43002
  async getOrCreateClient(info, config3) {
42409
43003
  const key = this.getClientKey(info);
42410
43004
  const existing = this.clients.get(key);
42411
43005
  if (existing) {
43006
+ existing.lastUsedAt = Date.now();
42412
43007
  return existing.client;
42413
43008
  }
43009
+ const pending = this.pendingConnections.get(key);
43010
+ if (pending) {
43011
+ return pending;
43012
+ }
42414
43013
  const expandedConfig = expandEnvVarsInObject(config3);
42415
- const client2 = await this.createClient(info, expandedConfig);
42416
- return client2;
43014
+ const connectionPromise = this.createClient(info, expandedConfig);
43015
+ this.pendingConnections.set(key, connectionPromise);
43016
+ try {
43017
+ const client2 = await connectionPromise;
43018
+ return client2;
43019
+ } finally {
43020
+ this.pendingConnections.delete(key);
43021
+ }
42417
43022
  }
42418
43023
  async createClient(info, config3) {
42419
43024
  const key = this.getClientKey(info);
@@ -42430,14 +43035,8 @@ class SkillMcpManager {
42430
43035
  }
42431
43036
  const command = config3.command;
42432
43037
  const args = config3.args || [];
42433
- const mergedEnv = {};
42434
- for (const [key2, value] of Object.entries(process.env)) {
42435
- if (value !== undefined)
42436
- mergedEnv[key2] = value;
42437
- }
42438
- if (config3.env) {
42439
- Object.assign(mergedEnv, config3.env);
42440
- }
43038
+ const mergedEnv = createCleanMcpEnvironment(config3.env);
43039
+ this.registerProcessCleanup();
42441
43040
  const transport = new StdioClientTransport({
42442
43041
  command,
42443
43042
  args,
@@ -42448,6 +43047,9 @@ class SkillMcpManager {
42448
43047
  try {
42449
43048
  await client2.connect(transport);
42450
43049
  } catch (error45) {
43050
+ try {
43051
+ await transport.close();
43052
+ } catch {}
42451
43053
  const errorMessage = error45 instanceof Error ? error45.message : String(error45);
42452
43054
  throw new Error(`Failed to connect to MCP server "${info.serverName}".
42453
43055
 
@@ -42459,7 +43061,8 @@ class SkillMcpManager {
42459
43061
  ` + ` - Check if the MCP server package exists
42460
43062
  ` + ` - Verify the args are correct for this server`);
42461
43063
  }
42462
- this.clients.set(key, { client: client2, transport, skillName: info.skillName });
43064
+ this.clients.set(key, { client: client2, transport, skillName: info.skillName, lastUsedAt: Date.now() });
43065
+ this.startCleanupTimer();
42463
43066
  return client2;
42464
43067
  }
42465
43068
  async disconnectSession(sessionID) {
@@ -42467,22 +43070,56 @@ class SkillMcpManager {
42467
43070
  for (const [key, managed] of this.clients.entries()) {
42468
43071
  if (key.startsWith(`${sessionID}:`)) {
42469
43072
  keysToRemove.push(key);
43073
+ this.clients.delete(key);
42470
43074
  try {
42471
43075
  await managed.client.close();
42472
43076
  } catch {}
43077
+ try {
43078
+ await managed.transport.close();
43079
+ } catch {}
42473
43080
  }
42474
43081
  }
42475
- for (const key of keysToRemove) {
42476
- this.clients.delete(key);
42477
- }
42478
43082
  }
42479
43083
  async disconnectAll() {
42480
- for (const [, managed] of this.clients.entries()) {
43084
+ this.stopCleanupTimer();
43085
+ const clients = Array.from(this.clients.values());
43086
+ this.clients.clear();
43087
+ for (const managed of clients) {
42481
43088
  try {
42482
43089
  await managed.client.close();
42483
43090
  } catch {}
43091
+ try {
43092
+ await managed.transport.close();
43093
+ } catch {}
43094
+ }
43095
+ }
43096
+ startCleanupTimer() {
43097
+ if (this.cleanupInterval)
43098
+ return;
43099
+ this.cleanupInterval = setInterval(() => {
43100
+ this.cleanupIdleClients();
43101
+ }, 60000);
43102
+ this.cleanupInterval.unref();
43103
+ }
43104
+ stopCleanupTimer() {
43105
+ if (this.cleanupInterval) {
43106
+ clearInterval(this.cleanupInterval);
43107
+ this.cleanupInterval = null;
43108
+ }
43109
+ }
43110
+ async cleanupIdleClients() {
43111
+ const now = Date.now();
43112
+ for (const [key, managed] of this.clients) {
43113
+ if (now - managed.lastUsedAt > this.IDLE_TIMEOUT) {
43114
+ this.clients.delete(key);
43115
+ try {
43116
+ await managed.client.close();
43117
+ } catch {}
43118
+ try {
43119
+ await managed.transport.close();
43120
+ } catch {}
43121
+ }
42484
43122
  }
42485
- this.clients.clear();
42486
43123
  }
42487
43124
  async listTools(info, context) {
42488
43125
  const client2 = await this.getOrCreateClientWithRetry(info, context.config);
@@ -42521,10 +43158,13 @@ class SkillMcpManager {
42521
43158
  const key = this.getClientKey(info);
42522
43159
  const existing = this.clients.get(key);
42523
43160
  if (existing) {
43161
+ this.clients.delete(key);
42524
43162
  try {
42525
43163
  await existing.client.close();
42526
43164
  } catch {}
42527
- this.clients.delete(key);
43165
+ try {
43166
+ await existing.transport.close();
43167
+ } catch {}
42528
43168
  return await this.getOrCreateClient(info, config3);
42529
43169
  }
42530
43170
  throw error45;
@@ -42538,7 +43178,7 @@ class SkillMcpManager {
42538
43178
  }
42539
43179
  }
42540
43180
  // src/plugin-config.ts
42541
- import * as fs9 from "fs";
43181
+ import * as fs10 from "fs";
42542
43182
  import * as path7 from "path";
42543
43183
 
42544
43184
  // src/mcp/types.ts
@@ -42758,8 +43398,8 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
42758
43398
  // src/plugin-config.ts
42759
43399
  function loadConfigFromPath2(configPath, ctx) {
42760
43400
  try {
42761
- if (fs9.existsSync(configPath)) {
42762
- const content = fs9.readFileSync(configPath, "utf-8");
43401
+ if (fs10.existsSync(configPath)) {
43402
+ const content = fs10.readFileSync(configPath, "utf-8");
42763
43403
  const rawConfig = parseJsonc(content);
42764
43404
  migrateConfigFile(configPath, rawConfig);
42765
43405
  const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
@@ -43663,12 +44303,18 @@ Organize your final answer in three tiers:
43663
44303
 
43664
44304
  Your response goes directly to the user with no intermediate processing. Make your final message self-contained: a clear recommendation they can act on immediately, covering both what to do and why.`;
43665
44305
  function createOracleAgent(model = DEFAULT_MODEL2) {
44306
+ const restrictions = createAgentToolRestrictions([
44307
+ "write",
44308
+ "edit",
44309
+ "task",
44310
+ "background_task"
44311
+ ]);
43666
44312
  const base = {
43667
44313
  description: "Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.",
43668
44314
  mode: "subagent",
43669
44315
  model,
43670
44316
  temperature: 0.1,
43671
- tools: { write: false, edit: false, task: false, background_task: false },
44317
+ ...restrictions,
43672
44318
  prompt: ORACLE_SYSTEM_PROMPT
43673
44319
  };
43674
44320
  if (isGptModel(model)) {
@@ -43697,12 +44343,17 @@ var LIBRARIAN_PROMPT_METADATA = {
43697
44343
  ]
43698
44344
  };
43699
44345
  function createLibrarianAgent(model = DEFAULT_MODEL3) {
44346
+ const restrictions = createAgentToolRestrictions([
44347
+ "write",
44348
+ "edit",
44349
+ "background_task"
44350
+ ]);
43700
44351
  return {
43701
44352
  description: "Specialized codebase understanding agent for multi-repository analysis, searching remote codebases, retrieving official documentation, and finding implementation examples using GitHub CLI, Context7, and Web Search. MUST BE USED when users ask to look up code in remote repositories, explain library internals, or find usage examples in open source.",
43702
44353
  mode: "subagent",
43703
44354
  model,
43704
44355
  temperature: 0.1,
43705
- tools: { write: false, edit: false, background_task: false },
44356
+ ...restrictions,
43706
44357
  prompt: `# THE LIBRARIAN
43707
44358
 
43708
44359
  You are **THE LIBRARIAN**, a specialized open-source codebase understanding agent.
@@ -43959,12 +44610,17 @@ var EXPLORE_PROMPT_METADATA = {
43959
44610
  ]
43960
44611
  };
43961
44612
  function createExploreAgent(model = DEFAULT_MODEL4) {
44613
+ const restrictions = createAgentToolRestrictions([
44614
+ "write",
44615
+ "edit",
44616
+ "background_task"
44617
+ ]);
43962
44618
  return {
43963
44619
  description: 'Contextual grep for codebases. Answers "Where is X?", "Which file has Y?", "Find the code that does Z". Fire multiple in parallel for broad searches. Specify thoroughness: "quick" for basic, "medium" for moderate, "very thorough" for comprehensive analysis.',
43964
44620
  mode: "subagent",
43965
44621
  model,
43966
44622
  temperature: 0.1,
43967
- tools: { write: false, edit: false, background_task: false },
44623
+ ...restrictions,
43968
44624
  prompt: `You are a codebase search specialist. Your job: find files and code, return actionable results.
43969
44625
 
43970
44626
  ## Your Mission
@@ -44065,11 +44721,12 @@ var FRONTEND_PROMPT_METADATA = {
44065
44721
  ]
44066
44722
  };
44067
44723
  function createFrontendUiUxEngineerAgent(model = DEFAULT_MODEL5) {
44724
+ const restrictions = createAgentToolRestrictions(["background_task"]);
44068
44725
  return {
44069
44726
  description: "A designer-turned-developer who crafts stunning UI/UX even without design mockups. Code may be a bit messy, but the visual output is always fire.",
44070
44727
  mode: "subagent",
44071
44728
  model,
44072
- tools: { background_task: false },
44729
+ ...restrictions,
44073
44730
  prompt: `# Role: Designer-Turned-Developer
44074
44731
 
44075
44732
  You are a designer who learned to code. You see what pure developers miss\u2014spacing, color harmony, micro-interactions, that indefinable "feel" that makes interfaces memorable. Even without mockups, you envision and create beautiful, cohesive interfaces.
@@ -44158,11 +44815,12 @@ var DOCUMENT_WRITER_PROMPT_METADATA = {
44158
44815
  ]
44159
44816
  };
44160
44817
  function createDocumentWriterAgent(model = DEFAULT_MODEL6) {
44818
+ const restrictions = createAgentToolRestrictions(["background_task"]);
44161
44819
  return {
44162
44820
  description: "A technical writer who crafts clear, comprehensive documentation. Specializes in README files, API docs, architecture docs, and user guides. MUST BE USED when executing documentation tasks from ai-todo list plans.",
44163
44821
  mode: "subagent",
44164
44822
  model,
44165
- tools: { background_task: false },
44823
+ ...restrictions,
44166
44824
  prompt: `<role>
44167
44825
  You are a TECHNICAL WRITER with deep engineering background who transforms complex codebases into crystal-clear documentation. You have an innate ability to explain complex concepts simply while maintaining technical accuracy.
44168
44826
 
@@ -44370,12 +45028,18 @@ var MULTIMODAL_LOOKER_PROMPT_METADATA = {
44370
45028
  triggers: []
44371
45029
  };
44372
45030
  function createMultimodalLookerAgent(model = DEFAULT_MODEL7) {
45031
+ const restrictions = createAgentToolRestrictions([
45032
+ "write",
45033
+ "edit",
45034
+ "bash",
45035
+ "background_task"
45036
+ ]);
44373
45037
  return {
44374
45038
  description: "Analyze media files (PDFs, images, diagrams) that require interpretation beyond raw text. Extracts specific information or summaries from documents, describes visual content. Use when you need analyzed/extracted data rather than literal file contents.",
44375
45039
  mode: "subagent",
44376
45040
  model,
44377
45041
  temperature: 0.1,
44378
- tools: { write: false, edit: false, bash: false, background_task: false },
45042
+ ...restrictions,
44379
45043
  prompt: `You interpret media files that cannot be read as plain text.
44380
45044
 
44381
45045
  Your job: examine the attached file and extract ONLY what was requested.
@@ -44506,10 +45170,10 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
44506
45170
  return result;
44507
45171
  }
44508
45172
  // src/features/claude-code-command-loader/loader.ts
44509
- import { existsSync as existsSync49, readdirSync as readdirSync18, readFileSync as readFileSync32, realpathSync as realpathSync2 } from "fs";
45173
+ import { existsSync as existsSync50, readdirSync as readdirSync18, readFileSync as readFileSync32, realpathSync as realpathSync2 } from "fs";
44510
45174
  import { join as join56, basename as basename5 } from "path";
44511
45175
  function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix = "") {
44512
- if (!existsSync49(commandsDir)) {
45176
+ if (!existsSync50(commandsDir)) {
44513
45177
  return [];
44514
45178
  }
44515
45179
  let realPath;
@@ -44993,7 +45657,7 @@ function loadBuiltinCommands(disabledCommands) {
44993
45657
  return commands;
44994
45658
  }
44995
45659
  // src/features/claude-code-agent-loader/loader.ts
44996
- import { existsSync as existsSync50, readdirSync as readdirSync19, readFileSync as readFileSync33 } from "fs";
45660
+ import { existsSync as existsSync51, readdirSync as readdirSync19, readFileSync as readFileSync33 } from "fs";
44997
45661
  import { join as join57, basename as basename6 } from "path";
44998
45662
  function parseToolsConfig(toolsStr) {
44999
45663
  if (!toolsStr)
@@ -45008,7 +45672,7 @@ function parseToolsConfig(toolsStr) {
45008
45672
  return result;
45009
45673
  }
45010
45674
  function loadAgentsFromDir(agentsDir, scope) {
45011
- if (!existsSync50(agentsDir)) {
45675
+ if (!existsSync51(agentsDir)) {
45012
45676
  return [];
45013
45677
  }
45014
45678
  const entries = readdirSync19(agentsDir, { withFileTypes: true });
@@ -45064,7 +45728,7 @@ function loadProjectAgents() {
45064
45728
  return result;
45065
45729
  }
45066
45730
  // src/features/claude-code-plugin-loader/loader.ts
45067
- import { existsSync as existsSync51, readdirSync as readdirSync20, readFileSync as readFileSync34 } from "fs";
45731
+ import { existsSync as existsSync52, readdirSync as readdirSync20, readFileSync as readFileSync34 } from "fs";
45068
45732
  import { homedir as homedir16 } from "os";
45069
45733
  import { join as join58, basename as basename7 } from "path";
45070
45734
  var CLAUDE_PLUGIN_ROOT_VAR = "${CLAUDE_PLUGIN_ROOT}";
@@ -45100,7 +45764,7 @@ function resolvePluginPaths(obj, pluginRoot) {
45100
45764
  }
45101
45765
  function loadInstalledPlugins() {
45102
45766
  const dbPath = getInstalledPluginsPath();
45103
- if (!existsSync51(dbPath)) {
45767
+ if (!existsSync52(dbPath)) {
45104
45768
  return null;
45105
45769
  }
45106
45770
  try {
@@ -45119,7 +45783,7 @@ function getClaudeSettingsPath() {
45119
45783
  }
45120
45784
  function loadClaudeSettings() {
45121
45785
  const settingsPath = getClaudeSettingsPath();
45122
- if (!existsSync51(settingsPath)) {
45786
+ if (!existsSync52(settingsPath)) {
45123
45787
  return null;
45124
45788
  }
45125
45789
  try {
@@ -45132,7 +45796,7 @@ function loadClaudeSettings() {
45132
45796
  }
45133
45797
  function loadPluginManifest(installPath) {
45134
45798
  const manifestPath = join58(installPath, ".claude-plugin", "plugin.json");
45135
- if (!existsSync51(manifestPath)) {
45799
+ if (!existsSync52(manifestPath)) {
45136
45800
  return null;
45137
45801
  }
45138
45802
  try {
@@ -45183,7 +45847,7 @@ function discoverInstalledPlugins(options) {
45183
45847
  continue;
45184
45848
  }
45185
45849
  const { installPath, scope, version: version2 } = installation;
45186
- if (!existsSync51(installPath)) {
45850
+ if (!existsSync52(installPath)) {
45187
45851
  errors3.push({
45188
45852
  pluginKey,
45189
45853
  installPath,
@@ -45201,21 +45865,21 @@ function discoverInstalledPlugins(options) {
45201
45865
  pluginKey,
45202
45866
  manifest: manifest ?? undefined
45203
45867
  };
45204
- if (existsSync51(join58(installPath, "commands"))) {
45868
+ if (existsSync52(join58(installPath, "commands"))) {
45205
45869
  loadedPlugin.commandsDir = join58(installPath, "commands");
45206
45870
  }
45207
- if (existsSync51(join58(installPath, "agents"))) {
45871
+ if (existsSync52(join58(installPath, "agents"))) {
45208
45872
  loadedPlugin.agentsDir = join58(installPath, "agents");
45209
45873
  }
45210
- if (existsSync51(join58(installPath, "skills"))) {
45874
+ if (existsSync52(join58(installPath, "skills"))) {
45211
45875
  loadedPlugin.skillsDir = join58(installPath, "skills");
45212
45876
  }
45213
45877
  const hooksPath = join58(installPath, "hooks", "hooks.json");
45214
- if (existsSync51(hooksPath)) {
45878
+ if (existsSync52(hooksPath)) {
45215
45879
  loadedPlugin.hooksPath = hooksPath;
45216
45880
  }
45217
45881
  const mcpPath = join58(installPath, ".mcp.json");
45218
- if (existsSync51(mcpPath)) {
45882
+ if (existsSync52(mcpPath)) {
45219
45883
  loadedPlugin.mcpPath = mcpPath;
45220
45884
  }
45221
45885
  plugins.push(loadedPlugin);
@@ -45226,7 +45890,7 @@ function discoverInstalledPlugins(options) {
45226
45890
  function loadPluginCommands(plugins) {
45227
45891
  const commands2 = {};
45228
45892
  for (const plugin2 of plugins) {
45229
- if (!plugin2.commandsDir || !existsSync51(plugin2.commandsDir))
45893
+ if (!plugin2.commandsDir || !existsSync52(plugin2.commandsDir))
45230
45894
  continue;
45231
45895
  const entries = readdirSync20(plugin2.commandsDir, { withFileTypes: true });
45232
45896
  for (const entry of entries) {
@@ -45268,7 +45932,7 @@ $ARGUMENTS
45268
45932
  function loadPluginSkillsAsCommands(plugins) {
45269
45933
  const skills = {};
45270
45934
  for (const plugin2 of plugins) {
45271
- if (!plugin2.skillsDir || !existsSync51(plugin2.skillsDir))
45935
+ if (!plugin2.skillsDir || !existsSync52(plugin2.skillsDir))
45272
45936
  continue;
45273
45937
  const entries = readdirSync20(plugin2.skillsDir, { withFileTypes: true });
45274
45938
  for (const entry of entries) {
@@ -45279,7 +45943,7 @@ function loadPluginSkillsAsCommands(plugins) {
45279
45943
  continue;
45280
45944
  const resolvedPath = resolveSymlink(skillPath);
45281
45945
  const skillMdPath = join58(resolvedPath, "SKILL.md");
45282
- if (!existsSync51(skillMdPath))
45946
+ if (!existsSync52(skillMdPath))
45283
45947
  continue;
45284
45948
  try {
45285
45949
  const content = readFileSync34(skillMdPath, "utf-8");
@@ -45329,7 +45993,7 @@ function parseToolsConfig2(toolsStr) {
45329
45993
  function loadPluginAgents(plugins) {
45330
45994
  const agents = {};
45331
45995
  for (const plugin2 of plugins) {
45332
- if (!plugin2.agentsDir || !existsSync51(plugin2.agentsDir))
45996
+ if (!plugin2.agentsDir || !existsSync52(plugin2.agentsDir))
45333
45997
  continue;
45334
45998
  const entries = readdirSync20(plugin2.agentsDir, { withFileTypes: true });
45335
45999
  for (const entry of entries) {
@@ -45365,7 +46029,7 @@ function loadPluginAgents(plugins) {
45365
46029
  async function loadPluginMcpServers(plugins) {
45366
46030
  const servers = {};
45367
46031
  for (const plugin2 of plugins) {
45368
- if (!plugin2.mcpPath || !existsSync51(plugin2.mcpPath))
46032
+ if (!plugin2.mcpPath || !existsSync52(plugin2.mcpPath))
45369
46033
  continue;
45370
46034
  try {
45371
46035
  const content = await Bun.file(plugin2.mcpPath).text();
@@ -45397,7 +46061,7 @@ async function loadPluginMcpServers(plugins) {
45397
46061
  function loadPluginHooksConfigs(plugins) {
45398
46062
  const configs = [];
45399
46063
  for (const plugin2 of plugins) {
45400
- if (!plugin2.hooksPath || !existsSync51(plugin2.hooksPath))
46064
+ if (!plugin2.hooksPath || !existsSync52(plugin2.hooksPath))
45401
46065
  continue;
45402
46066
  try {
45403
46067
  const content = readFileSync34(plugin2.hooksPath, "utf-8");
@@ -45413,11 +46077,13 @@ function loadPluginHooksConfigs(plugins) {
45413
46077
  }
45414
46078
  async function loadAllPluginComponents(options) {
45415
46079
  const { plugins, errors: errors3 } = discoverInstalledPlugins(options);
45416
- const commands2 = loadPluginCommands(plugins);
45417
- const skills = loadPluginSkillsAsCommands(plugins);
45418
- const agents = loadPluginAgents(plugins);
45419
- const mcpServers = await loadPluginMcpServers(plugins);
45420
- const hooksConfigs = loadPluginHooksConfigs(plugins);
46080
+ const [commands2, skills, agents, mcpServers, hooksConfigs] = await Promise.all([
46081
+ Promise.resolve(loadPluginCommands(plugins)),
46082
+ Promise.resolve(loadPluginSkillsAsCommands(plugins)),
46083
+ Promise.resolve(loadPluginAgents(plugins)),
46084
+ loadPluginMcpServers(plugins),
46085
+ Promise.resolve(loadPluginHooksConfigs(plugins))
46086
+ ]);
45421
46087
  log(`Loaded ${plugins.length} plugins with ${Object.keys(commands2).length} commands, ${Object.keys(skills).length} skills, ${Object.keys(agents).length} agents, ${Object.keys(mcpServers).length} MCP servers`);
45422
46088
  return {
45423
46089
  commands: commands2,
@@ -45577,9 +46243,21 @@ function createConfigHandler(deps) {
45577
46243
  log(`Plugin load errors`, { errors: pluginComponents.errors });
45578
46244
  }
45579
46245
  const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents, ctx.directory, config3.model);
45580
- const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
45581
- const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
45582
- const pluginAgents = pluginComponents.agents;
46246
+ const rawUserAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
46247
+ const rawProjectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
46248
+ const rawPluginAgents = pluginComponents.agents;
46249
+ const userAgents = Object.fromEntries(Object.entries(rawUserAgents).map(([k, v]) => [
46250
+ k,
46251
+ v ? migrateAgentConfig(v) : v
46252
+ ]));
46253
+ const projectAgents = Object.fromEntries(Object.entries(rawProjectAgents).map(([k, v]) => [
46254
+ k,
46255
+ v ? migrateAgentConfig(v) : v
46256
+ ]));
46257
+ const pluginAgents = Object.fromEntries(Object.entries(rawPluginAgents).map(([k, v]) => [
46258
+ k,
46259
+ v ? migrateAgentConfig(v) : v
46260
+ ]));
45583
46261
  const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
45584
46262
  const builderEnabled = pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
45585
46263
  const plannerEnabled = pluginConfig.sisyphus_agent?.planner_enabled ?? true;
@@ -45592,18 +46270,21 @@ function createConfigHandler(deps) {
45592
46270
  };
45593
46271
  if (builderEnabled) {
45594
46272
  const { name: _buildName, ...buildConfigWithoutName } = configAgent?.build ?? {};
46273
+ const migratedBuildConfig = migrateAgentConfig(buildConfigWithoutName);
45595
46274
  const openCodeBuilderOverride = pluginConfig.agents?.["OpenCode-Builder"];
45596
46275
  const openCodeBuilderBase = {
45597
- ...buildConfigWithoutName,
46276
+ ...migratedBuildConfig,
45598
46277
  description: `${configAgent?.build?.description ?? "Build agent"} (OpenCode default)`
45599
46278
  };
45600
46279
  agentConfig["OpenCode-Builder"] = openCodeBuilderOverride ? { ...openCodeBuilderBase, ...openCodeBuilderOverride } : openCodeBuilderBase;
45601
46280
  }
45602
46281
  if (plannerEnabled) {
45603
46282
  const { name: _planName, ...planConfigWithoutName } = configAgent?.plan ?? {};
46283
+ const migratedPlanConfig = migrateAgentConfig(planConfigWithoutName);
45604
46284
  const plannerSisyphusOverride = pluginConfig.agents?.["Planner-Sisyphus"];
45605
46285
  const plannerSisyphusBase = {
45606
- ...planConfigWithoutName,
46286
+ ...migratedPlanConfig,
46287
+ mode: "primary",
45607
46288
  prompt: PLAN_SYSTEM_PROMPT,
45608
46289
  permission: PLAN_PERMISSION,
45609
46290
  description: `${configAgent?.plan?.description ?? "Plan agent"} (OhMyOpenCode version)`,
@@ -45617,7 +46298,12 @@ function createConfigHandler(deps) {
45617
46298
  if (key === "plan" && replacePlan)
45618
46299
  return false;
45619
46300
  return true;
45620
- })) : {};
46301
+ }).map(([key, value]) => [
46302
+ key,
46303
+ value ? migrateAgentConfig(value) : value
46304
+ ])) : {};
46305
+ const migratedBuild = configAgent?.build ? migrateAgentConfig(configAgent.build) : {};
46306
+ const planDemoteConfig = replacePlan ? { disable: true } : undefined;
45621
46307
  config3.agent = {
45622
46308
  ...agentConfig,
45623
46309
  ...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "Sisyphus")),
@@ -45625,8 +46311,8 @@ function createConfigHandler(deps) {
45625
46311
  ...projectAgents,
45626
46312
  ...pluginAgents,
45627
46313
  ...filteredConfigAgents,
45628
- build: { ...configAgent?.build, mode: "subagent" },
45629
- ...replacePlan ? { plan: { ...configAgent?.plan, mode: "subagent" } } : {}
46314
+ build: { ...migratedBuild, mode: "subagent", hidden: true },
46315
+ ...planDemoteConfig ? { plan: planDemoteConfig } : {}
45630
46316
  };
45631
46317
  } else {
45632
46318
  config3.agent = {
@@ -45703,6 +46389,7 @@ function createConfigHandler(deps) {
45703
46389
  }
45704
46390
  // src/index.ts
45705
46391
  var OhMyOpenCodePlugin = async (ctx) => {
46392
+ startBackgroundCheck2();
45706
46393
  const pluginConfig = loadPluginConfig(ctx.directory, ctx);
45707
46394
  const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
45708
46395
  const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
@@ -45738,12 +46425,17 @@ var OhMyOpenCodePlugin = async (ctx) => {
45738
46425
  autoUpdate: pluginConfig.auto_update ?? true
45739
46426
  }) : null;
45740
46427
  const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook(ctx) : null;
46428
+ const contextInjector = createContextInjectorHook(contextCollector);
46429
+ const contextInjectorMessagesTransform = createContextInjectorMessagesTransformHook(contextCollector);
45741
46430
  const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
45742
46431
  const nonInteractiveEnv = isHookEnabled("non-interactive-env") ? createNonInteractiveEnvHook(ctx) : null;
45743
46432
  const interactiveBashSession = isHookEnabled("interactive-bash-session") ? createInteractiveBashSessionHook(ctx) : null;
45744
46433
  const emptyMessageSanitizer = isHookEnabled("empty-message-sanitizer") ? createEmptyMessageSanitizerHook() : null;
45745
46434
  const thinkingBlockValidator = isHookEnabled("thinking-block-validator") ? createThinkingBlockValidatorHook() : null;
45746
- const ralphLoop = isHookEnabled("ralph-loop") ? createRalphLoopHook(ctx, { config: pluginConfig.ralph_loop }) : null;
46435
+ const ralphLoop = isHookEnabled("ralph-loop") ? createRalphLoopHook(ctx, {
46436
+ config: pluginConfig.ralph_loop,
46437
+ checkSessionExists: async (sessionId) => sessionExists(sessionId)
46438
+ }) : null;
45747
46439
  const autoSlashCommand = isHookEnabled("auto-slash-command") ? createAutoSlashCommandHook() : null;
45748
46440
  const editErrorRecovery = isHookEnabled("edit-error-recovery") ? createEditErrorRecoveryHook(ctx) : null;
45749
46441
  const backgroundManager = new BackgroundManager(ctx);
@@ -45770,7 +46462,13 @@ var OhMyOpenCodePlugin = async (ctx) => {
45770
46462
  return true;
45771
46463
  });
45772
46464
  const includeClaudeSkills = pluginConfig.claude_code?.skills !== false;
45773
- const mergedSkills = mergeSkills(builtinSkills, pluginConfig.skills, includeClaudeSkills ? discoverUserClaudeSkills() : [], discoverOpencodeGlobalSkills(), includeClaudeSkills ? discoverProjectClaudeSkills() : [], discoverOpencodeProjectSkills());
46465
+ const [userSkills, globalSkills, projectSkills, opencodeProjectSkills] = await Promise.all([
46466
+ includeClaudeSkills ? discoverUserClaudeSkillsAsync() : Promise.resolve([]),
46467
+ discoverOpencodeGlobalSkillsAsync(),
46468
+ includeClaudeSkills ? discoverProjectClaudeSkillsAsync() : Promise.resolve([]),
46469
+ discoverOpencodeProjectSkillsAsync()
46470
+ ]);
46471
+ const mergedSkills = mergeSkills(builtinSkills, pluginConfig.skills, userSkills, globalSkills, projectSkills, opencodeProjectSkills);
45774
46472
  const skillMcpManager = new SkillMcpManager;
45775
46473
  const getSessionIDForMcp = () => getMainSessionID() || "";
45776
46474
  const skillTool = createSkillTool({
@@ -45784,7 +46482,6 @@ var OhMyOpenCodePlugin = async (ctx) => {
45784
46482
  getSessionID: getSessionIDForMcp
45785
46483
  });
45786
46484
  const googleAuthHooks = pluginConfig.google_auth !== false ? await createGoogleAntigravityAuthPlugin(ctx) : null;
45787
- const tmuxAvailable = await getTmuxPath();
45788
46485
  const configHandler = createConfigHandler({
45789
46486
  ctx,
45790
46487
  pluginConfig,
@@ -45799,11 +46496,12 @@ var OhMyOpenCodePlugin = async (ctx) => {
45799
46496
  look_at: lookAt,
45800
46497
  skill: skillTool,
45801
46498
  skill_mcp: skillMcpTool,
45802
- ...tmuxAvailable ? { interactive_bash } : {}
46499
+ interactive_bash
45803
46500
  },
45804
46501
  "chat.message": async (input, output) => {
45805
46502
  await claudeCodeHooks["chat.message"]?.(input, output);
45806
46503
  await keywordDetector?.["chat.message"]?.(input, output);
46504
+ await contextInjector["chat.message"]?.(input, output);
45807
46505
  await autoSlashCommand?.["chat.message"]?.(input, output);
45808
46506
  if (ralphLoop) {
45809
46507
  const parts = output.parts;
@@ -45835,6 +46533,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
45835
46533
  }
45836
46534
  },
45837
46535
  "experimental.chat.messages.transform": async (input, output) => {
46536
+ await contextInjectorMessagesTransform?.["experimental.chat.messages.transform"]?.(input, output);
45838
46537
  await thinkingBlockValidator?.["experimental.chat.messages.transform"]?.(input, output);
45839
46538
  await emptyMessageSanitizer?.["experimental.chat.messages.transform"]?.(input, output);
45840
46539
  },