oh-my-opencode 2.13.1 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8374,7 +8374,7 @@ var require_cross_spawn = __commonJS((exports, module) => {
8374
8374
  var cp = __require("child_process");
8375
8375
  var parse7 = require_parse2();
8376
8376
  var enoent = require_enoent();
8377
- function spawn12(command, args, options) {
8377
+ function spawn13(command, args, options) {
8378
8378
  const parsed = parse7(command, args, options);
8379
8379
  const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
8380
8380
  enoent.hookChildProcess(spawned, parsed);
@@ -8386,8 +8386,8 @@ var require_cross_spawn = __commonJS((exports, module) => {
8386
8386
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
8387
8387
  return result;
8388
8388
  }
8389
- module.exports = spawn12;
8390
- module.exports.spawn = spawn12;
8389
+ module.exports = spawn13;
8390
+ module.exports.spawn = spawn13;
8391
8391
  module.exports.sync = spawnSync2;
8392
8392
  module.exports._parse = parse7;
8393
8393
  module.exports._enoent = enoent;
@@ -8889,6 +8889,7 @@ ${CONTEXT_REMINDER}
8889
8889
  }
8890
8890
  // src/hooks/session-notification.ts
8891
8891
  import { platform } from "os";
8892
+ import { spawn as spawn2 } from "child_process";
8892
8893
 
8893
8894
  // src/hooks/session-notification-utils.ts
8894
8895
  var {spawn } = globalThis.Bun;
@@ -9013,6 +9014,16 @@ function startBackgroundCheck(platform) {
9013
9014
  }
9014
9015
 
9015
9016
  // src/hooks/session-notification.ts
9017
+ function execCommand(command, args) {
9018
+ return new Promise((resolve) => {
9019
+ const proc = spawn2(command, args, {
9020
+ stdio: "ignore",
9021
+ detached: false
9022
+ });
9023
+ proc.on("close", () => resolve());
9024
+ proc.on("error", () => resolve());
9025
+ });
9026
+ }
9016
9027
  function detectPlatform() {
9017
9028
  const p = platform();
9018
9029
  if (p === "darwin" || p === "linux" || p === "win32")
@@ -9039,14 +9050,15 @@ async function sendNotification(ctx, p, title, message) {
9039
9050
  return;
9040
9051
  const esTitle = title.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
9041
9052
  const esMessage = message.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
9042
- await ctx.$`${osascriptPath2} -e ${'display notification "' + esMessage + '" with title "' + esTitle + '"'}`.catch(() => {});
9053
+ const script = `display notification "${esMessage}" with title "${esTitle}"`;
9054
+ await execCommand(osascriptPath2, ["-e", script]).catch(() => {});
9043
9055
  break;
9044
9056
  }
9045
9057
  case "linux": {
9046
9058
  const notifySendPath2 = await getNotifySendPath();
9047
9059
  if (!notifySendPath2)
9048
9060
  return;
9049
- await ctx.$`${notifySendPath2} ${title} ${message} 2>/dev/null`.catch(() => {});
9061
+ await execCommand(notifySendPath2, [title, message]).catch(() => {});
9050
9062
  break;
9051
9063
  }
9052
9064
  case "win32": {
@@ -9067,7 +9079,7 @@ $Toast = [Windows.UI.Notifications.ToastNotification]::new($SerializedXml)
9067
9079
  $Notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('OpenCode')
9068
9080
  $Notifier.Show($Toast)
9069
9081
  `.trim().replace(/\n/g, "; ");
9070
- await ctx.$`${powershellPath2} -Command ${toastScript}`.catch(() => {});
9082
+ await execCommand(powershellPath2, ["-Command", toastScript]).catch(() => {});
9071
9083
  break;
9072
9084
  }
9073
9085
  }
@@ -9078,17 +9090,17 @@ async function playSound(ctx, p, soundPath) {
9078
9090
  const afplayPath2 = await getAfplayPath();
9079
9091
  if (!afplayPath2)
9080
9092
  return;
9081
- ctx.$`${afplayPath2} ${soundPath}`.catch(() => {});
9093
+ execCommand(afplayPath2, [soundPath]).catch(() => {});
9082
9094
  break;
9083
9095
  }
9084
9096
  case "linux": {
9085
9097
  const paplayPath2 = await getPaplayPath();
9086
9098
  if (paplayPath2) {
9087
- ctx.$`${paplayPath2} ${soundPath} 2>/dev/null`.catch(() => {});
9099
+ execCommand(paplayPath2, [soundPath]).catch(() => {});
9088
9100
  } else {
9089
9101
  const aplayPath2 = await getAplayPath();
9090
9102
  if (aplayPath2) {
9091
- ctx.$`${aplayPath2} ${soundPath} 2>/dev/null`.catch(() => {});
9103
+ execCommand(aplayPath2, [soundPath]).catch(() => {});
9092
9104
  }
9093
9105
  }
9094
9106
  break;
@@ -9097,7 +9109,8 @@ async function playSound(ctx, p, soundPath) {
9097
9109
  const powershellPath2 = await getPowershellPath();
9098
9110
  if (!powershellPath2)
9099
9111
  return;
9100
- ctx.$`${powershellPath2} -Command ${"(New-Object Media.SoundPlayer '" + soundPath + "').PlaySync()"}`.catch(() => {});
9112
+ const soundScript = `(New-Object Media.SoundPlayer '${soundPath.replace(/'/g, "''")}').PlaySync()`;
9113
+ execCommand(powershellPath2, ["-Command", soundScript]).catch(() => {});
9101
9114
  break;
9102
9115
  }
9103
9116
  }
@@ -9805,7 +9818,7 @@ function createSessionRecoveryHook(ctx, options) {
9805
9818
  };
9806
9819
  }
9807
9820
  // src/hooks/comment-checker/cli.ts
9808
- var {spawn: spawn3 } = globalThis.Bun;
9821
+ var {spawn: spawn4 } = globalThis.Bun;
9809
9822
  import { createRequire as createRequire2 } from "module";
9810
9823
  import { dirname, join as join9 } from "path";
9811
9824
  import { existsSync as existsSync5 } from "fs";
@@ -9813,7 +9826,7 @@ import * as fs2 from "fs";
9813
9826
  import { tmpdir as tmpdir3 } from "os";
9814
9827
 
9815
9828
  // src/hooks/comment-checker/downloader.ts
9816
- var {spawn: spawn2 } = globalThis.Bun;
9829
+ var {spawn: spawn3 } = globalThis.Bun;
9817
9830
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, chmodSync, unlinkSync as unlinkSync2, appendFileSync as appendFileSync2 } from "fs";
9818
9831
  import { join as join8 } from "path";
9819
9832
  import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
@@ -9863,7 +9876,7 @@ function getPackageVersion() {
9863
9876
  }
9864
9877
  async function extractTarGz(archivePath, destDir) {
9865
9878
  debugLog("Extracting tar.gz:", archivePath, "to", destDir);
9866
- const proc = spawn2(["tar", "-xzf", archivePath, "-C", destDir], {
9879
+ const proc = spawn3(["tar", "-xzf", archivePath, "-C", destDir], {
9867
9880
  stdout: "pipe",
9868
9881
  stderr: "pipe"
9869
9882
  });
@@ -9875,10 +9888,10 @@ async function extractTarGz(archivePath, destDir) {
9875
9888
  }
9876
9889
  async function extractZip(archivePath, destDir) {
9877
9890
  debugLog("Extracting zip:", archivePath, "to", destDir);
9878
- const proc = process.platform === "win32" ? spawn2(["powershell", "-command", `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`], {
9891
+ const proc = process.platform === "win32" ? spawn3(["powershell", "-command", `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`], {
9879
9892
  stdout: "pipe",
9880
9893
  stderr: "pipe"
9881
- }) : spawn2(["unzip", "-o", archivePath, "-d", destDir], {
9894
+ }) : spawn3(["unzip", "-o", archivePath, "-d", destDir], {
9882
9895
  stdout: "pipe",
9883
9896
  stderr: "pipe"
9884
9897
  });
@@ -10045,7 +10058,7 @@ async function runCommentChecker(input, cliPath, customPrompt) {
10045
10058
  if (customPrompt) {
10046
10059
  args.push("--prompt", customPrompt);
10047
10060
  }
10048
- const proc = spawn3(args, {
10061
+ const proc = spawn4(args, {
10049
10062
  stdin: "pipe",
10050
10063
  stdout: "pipe",
10051
10064
  stderr: "pipe"
@@ -15235,40 +15248,52 @@ function parseFrontmatter(content) {
15235
15248
  }
15236
15249
  }
15237
15250
  // src/shared/command-executor.ts
15238
- import { spawn as spawn4 } from "child_process";
15251
+ import { spawn as spawn5 } from "child_process";
15239
15252
  import { exec } from "child_process";
15240
15253
  import { promisify } from "util";
15241
15254
  import { existsSync as existsSync17 } from "fs";
15242
15255
  import { homedir as homedir3 } from "os";
15243
15256
  var DEFAULT_ZSH_PATHS = ["/bin/zsh", "/usr/bin/zsh", "/usr/local/bin/zsh"];
15257
+ var DEFAULT_BASH_PATHS = ["/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash"];
15244
15258
  function getHomeDir() {
15245
15259
  return process.env.HOME || process.env.USERPROFILE || homedir3();
15246
15260
  }
15247
- function findZshPath(customZshPath) {
15248
- if (customZshPath && existsSync17(customZshPath)) {
15249
- return customZshPath;
15261
+ function findShellPath(defaultPaths, customPath) {
15262
+ if (customPath && existsSync17(customPath)) {
15263
+ return customPath;
15250
15264
  }
15251
- for (const path3 of DEFAULT_ZSH_PATHS) {
15265
+ for (const path3 of defaultPaths) {
15252
15266
  if (existsSync17(path3)) {
15253
15267
  return path3;
15254
15268
  }
15255
15269
  }
15256
15270
  return null;
15257
15271
  }
15272
+ function findZshPath(customZshPath) {
15273
+ return findShellPath(DEFAULT_ZSH_PATHS, customZshPath);
15274
+ }
15275
+ function findBashPath() {
15276
+ return findShellPath(DEFAULT_BASH_PATHS);
15277
+ }
15258
15278
  var execAsync = promisify(exec);
15259
15279
  async function executeHookCommand(command, stdin, cwd, options) {
15260
15280
  const home = getHomeDir();
15261
15281
  let expandedCommand = command.replace(/^~(?=\/|$)/g, home).replace(/\s~(?=\/)/g, ` ${home}`).replace(/\$CLAUDE_PROJECT_DIR/g, cwd).replace(/\$\{CLAUDE_PROJECT_DIR\}/g, cwd);
15262
15282
  let finalCommand = expandedCommand;
15263
15283
  if (options?.forceZsh) {
15264
- const zshPath = options.zshPath || findZshPath();
15284
+ const zshPath = findZshPath(options.zshPath);
15285
+ const escapedCommand = expandedCommand.replace(/'/g, "'\\''");
15265
15286
  if (zshPath) {
15266
- const escapedCommand = expandedCommand.replace(/'/g, "'\\''");
15267
15287
  finalCommand = `${zshPath} -lc '${escapedCommand}'`;
15288
+ } else {
15289
+ const bashPath = findBashPath();
15290
+ if (bashPath) {
15291
+ finalCommand = `${bashPath} -lc '${escapedCommand}'`;
15292
+ }
15268
15293
  }
15269
15294
  }
15270
15295
  return new Promise((resolve3) => {
15271
- const proc = spawn4(finalCommand, {
15296
+ const proc = spawn5(finalCommand, {
15272
15297
  cwd,
15273
15298
  shell: true,
15274
15299
  env: { ...process.env, HOME: home, CLAUDE_PROJECT_DIR: cwd }
@@ -17767,10 +17792,22 @@ function createKeywordDetectorHook(ctx) {
17767
17792
  return {
17768
17793
  "chat.message": async (input, output) => {
17769
17794
  const promptText = extractPromptText2(output.parts);
17770
- const detectedKeywords = detectKeywordsWithType(removeCodeBlocks2(promptText), input.agent);
17795
+ let detectedKeywords = detectKeywordsWithType(removeCodeBlocks2(promptText), input.agent);
17771
17796
  if (detectedKeywords.length === 0) {
17772
17797
  return;
17773
17798
  }
17799
+ const mainSessionID2 = getMainSessionID();
17800
+ const isNonMainSession = mainSessionID2 && input.sessionID !== mainSessionID2;
17801
+ if (isNonMainSession) {
17802
+ detectedKeywords = detectedKeywords.filter((k) => k.type === "ultrawork");
17803
+ if (detectedKeywords.length === 0) {
17804
+ log(`[keyword-detector] Skipping non-ultrawork keywords in non-main session`, {
17805
+ sessionID: input.sessionID,
17806
+ mainSessionID: mainSessionID2
17807
+ });
17808
+ return;
17809
+ }
17810
+ }
17774
17811
  const hasUltrawork = detectedKeywords.some((k) => k.type === "ultrawork");
17775
17812
  if (hasUltrawork) {
17776
17813
  log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID });
@@ -20641,10 +20678,11 @@ function skillToCommandInfo(skill) {
20641
20678
  subtask: skill.definition.subtask
20642
20679
  },
20643
20680
  content: skill.definition.template,
20644
- scope: "skill"
20681
+ scope: "skill",
20682
+ lazyContentLoader: skill.lazyContent
20645
20683
  };
20646
20684
  }
20647
- async function discoverAllCommands() {
20685
+ async function discoverAllCommands(options) {
20648
20686
  const userCommandsDir = join43(getClaudeConfigDir(), "commands");
20649
20687
  const projectCommandsDir = join43(process.cwd(), ".claude", "commands");
20650
20688
  const opencodeGlobalDir = join43(homedir13(), ".config", "opencode", "command");
@@ -20653,7 +20691,7 @@ async function discoverAllCommands() {
20653
20691
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
20654
20692
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
20655
20693
  const opencodeProjectCommands = discoverCommandsFromDir(opencodeProjectDir, "opencode-project");
20656
- const skills = await discoverAllSkills();
20694
+ const skills = options?.skills ?? await discoverAllSkills();
20657
20695
  const skillCommands = skills.map(skillToCommandInfo);
20658
20696
  return [
20659
20697
  ...opencodeProjectCommands,
@@ -20663,8 +20701,8 @@ async function discoverAllCommands() {
20663
20701
  ...skillCommands
20664
20702
  ];
20665
20703
  }
20666
- async function findCommand2(commandName) {
20667
- const allCommands = await discoverAllCommands();
20704
+ async function findCommand2(commandName, options) {
20705
+ const allCommands = await discoverAllCommands(options);
20668
20706
  return allCommands.find((cmd) => cmd.name.toLowerCase() === commandName.toLowerCase()) ?? null;
20669
20707
  }
20670
20708
  async function formatCommandTemplate(cmd, args) {
@@ -20693,8 +20731,12 @@ async function formatCommandTemplate(cmd, args) {
20693
20731
  `);
20694
20732
  sections.push(`## Command Instructions
20695
20733
  `);
20734
+ let content = cmd.content || "";
20735
+ if (!content && cmd.lazyContentLoader) {
20736
+ content = await cmd.lazyContentLoader.load();
20737
+ }
20696
20738
  const commandDir = cmd.path ? dirname8(cmd.path) : process.cwd();
20697
- const withFileRefs = await resolveFileReferencesInText(cmd.content || "", commandDir);
20739
+ const withFileRefs = await resolveFileReferencesInText(content, commandDir);
20698
20740
  const resolvedContent = await resolveCommandsInText(withFileRefs);
20699
20741
  sections.push(resolvedContent.trim());
20700
20742
  if (args) {
@@ -20709,8 +20751,8 @@ async function formatCommandTemplate(cmd, args) {
20709
20751
  return sections.join(`
20710
20752
  `);
20711
20753
  }
20712
- async function executeSlashCommand(parsed) {
20713
- const command = await findCommand2(parsed.command);
20754
+ async function executeSlashCommand(parsed, options) {
20755
+ const command = await findCommand2(parsed.command, options);
20714
20756
  if (!command) {
20715
20757
  return {
20716
20758
  success: false,
@@ -20733,7 +20775,10 @@ async function executeSlashCommand(parsed) {
20733
20775
 
20734
20776
  // src/hooks/auto-slash-command/index.ts
20735
20777
  var sessionProcessedCommands = new Set;
20736
- function createAutoSlashCommandHook() {
20778
+ function createAutoSlashCommandHook(options) {
20779
+ const executorOptions = {
20780
+ skills: options?.skills
20781
+ };
20737
20782
  return {
20738
20783
  "chat.message": async (input, output) => {
20739
20784
  const promptText = extractPromptText3(output.parts);
@@ -20753,7 +20798,7 @@ function createAutoSlashCommandHook() {
20753
20798
  sessionID: input.sessionID,
20754
20799
  args: parsed.args
20755
20800
  });
20756
- const result = await executeSlashCommand(parsed);
20801
+ const result = await executeSlashCommand(parsed, executorOptions);
20757
20802
  const idx = output.parts.findIndex((p) => p.type === "text" && p.text);
20758
20803
  if (idx < 0) {
20759
20804
  return;
@@ -22789,7 +22834,7 @@ async function createGoogleAntigravityAuthPlugin({
22789
22834
  // src/features/builtin-skills/skills.ts
22790
22835
  var playwrightSkill = {
22791
22836
  name: "playwright",
22792
- description: "Browser automation with Playwright MCP. Use for web scraping, testing, screenshots, and browser interactions.",
22837
+ description: "MUST USE for any browser-related tasks. Browser automation via Playwright MCP - verification, browsing, information gathering, web scraping, testing, screenshots, and all browser interactions.",
22793
22838
  template: `# Playwright Browser Automation
22794
22839
 
22795
22840
  This skill provides browser automation capabilities via the Playwright MCP server.`,
@@ -23535,7 +23580,7 @@ function getAllServers() {
23535
23580
  return result;
23536
23581
  }
23537
23582
  // src/tools/lsp/client.ts
23538
- var {spawn: spawn5 } = globalThis.Bun;
23583
+ var {spawn: spawn6 } = globalThis.Bun;
23539
23584
  import { readFileSync as readFileSync26 } from "fs";
23540
23585
  import { extname, resolve as resolve6 } from "path";
23541
23586
  class LSPServerManager {
@@ -23702,7 +23747,7 @@ class LSPClient {
23702
23747
  this.server = server;
23703
23748
  }
23704
23749
  async start() {
23705
- this.proc = spawn5(this.server.command, {
23750
+ this.proc = spawn6(this.server.command, {
23706
23751
  stdin: "pipe",
23707
23752
  stdout: "pipe",
23708
23753
  stderr: "pipe",
@@ -37058,7 +37103,7 @@ import { dirname as dirname9, join as join47 } from "path";
37058
37103
  import { existsSync as existsSync40, statSync as statSync4 } from "fs";
37059
37104
 
37060
37105
  // src/tools/ast-grep/downloader.ts
37061
- var {spawn: spawn6 } = globalThis.Bun;
37106
+ var {spawn: spawn7 } = globalThis.Bun;
37062
37107
  import { existsSync as existsSync39, mkdirSync as mkdirSync11, chmodSync as chmodSync2, unlinkSync as unlinkSync10 } from "fs";
37063
37108
  import { join as join46 } from "path";
37064
37109
  import { homedir as homedir15 } from "os";
@@ -37101,11 +37146,11 @@ function getCachedBinaryPath2() {
37101
37146
  return existsSync39(binaryPath) ? binaryPath : null;
37102
37147
  }
37103
37148
  async function extractZip2(archivePath, destDir) {
37104
- const proc = process.platform === "win32" ? spawn6([
37149
+ const proc = process.platform === "win32" ? spawn7([
37105
37150
  "powershell",
37106
37151
  "-command",
37107
37152
  `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`
37108
- ], { stdout: "pipe", stderr: "pipe" }) : spawn6(["unzip", "-o", archivePath, "-d", destDir], { stdout: "pipe", stderr: "pipe" });
37153
+ ], { stdout: "pipe", stderr: "pipe" }) : spawn7(["unzip", "-o", archivePath, "-d", destDir], { stdout: "pipe", stderr: "pipe" });
37109
37154
  const exitCode = await proc.exited;
37110
37155
  if (exitCode !== 0) {
37111
37156
  const stderr = await new Response(proc.stderr).text();
@@ -37273,7 +37318,7 @@ var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
37273
37318
  var DEFAULT_MAX_MATCHES = 500;
37274
37319
 
37275
37320
  // src/tools/ast-grep/cli.ts
37276
- var {spawn: spawn7 } = globalThis.Bun;
37321
+ var {spawn: spawn8 } = globalThis.Bun;
37277
37322
  import { existsSync as existsSync41 } from "fs";
37278
37323
  var resolvedCliPath3 = null;
37279
37324
  var initPromise2 = null;
@@ -37327,7 +37372,7 @@ async function runSg(options) {
37327
37372
  }
37328
37373
  }
37329
37374
  const timeout = DEFAULT_TIMEOUT_MS;
37330
- const proc = spawn7([cliPath, ...args], {
37375
+ const proc = spawn8([cliPath, ...args], {
37331
37376
  stdout: "pipe",
37332
37377
  stderr: "pipe"
37333
37378
  });
@@ -37581,7 +37626,7 @@ var ast_grep_replace = tool({
37581
37626
  }
37582
37627
  });
37583
37628
  // src/tools/grep/cli.ts
37584
- var {spawn: spawn9 } = globalThis.Bun;
37629
+ var {spawn: spawn10 } = globalThis.Bun;
37585
37630
 
37586
37631
  // src/tools/grep/constants.ts
37587
37632
  import { existsSync as existsSync43 } from "fs";
@@ -37591,7 +37636,7 @@ import { spawnSync } from "child_process";
37591
37636
  // src/tools/grep/downloader.ts
37592
37637
  import { existsSync as existsSync42, mkdirSync as mkdirSync12, chmodSync as chmodSync3, unlinkSync as unlinkSync11, readdirSync as readdirSync12 } from "fs";
37593
37638
  import { join as join48 } from "path";
37594
- var {spawn: spawn8 } = globalThis.Bun;
37639
+ var {spawn: spawn9 } = globalThis.Bun;
37595
37640
  function findFileRecursive(dir, filename) {
37596
37641
  try {
37597
37642
  const entries = readdirSync12(dir, { withFileTypes: true, recursive: true });
@@ -37640,7 +37685,7 @@ async function extractTarGz2(archivePath, destDir) {
37640
37685
  } else if (platformKey.endsWith("-linux")) {
37641
37686
  args.push("--wildcards", "*/rg");
37642
37687
  }
37643
- const proc = spawn8(args, {
37688
+ const proc = spawn9(args, {
37644
37689
  cwd: destDir,
37645
37690
  stdout: "pipe",
37646
37691
  stderr: "pipe"
@@ -37652,7 +37697,7 @@ async function extractTarGz2(archivePath, destDir) {
37652
37697
  }
37653
37698
  }
37654
37699
  async function extractZipWindows(archivePath, destDir) {
37655
- const proc = spawn8(["powershell", "-Command", `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`], { stdout: "pipe", stderr: "pipe" });
37700
+ const proc = spawn9(["powershell", "-Command", `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`], { stdout: "pipe", stderr: "pipe" });
37656
37701
  const exitCode = await proc.exited;
37657
37702
  if (exitCode !== 0) {
37658
37703
  throw new Error("Failed to extract zip with PowerShell");
@@ -37667,7 +37712,7 @@ async function extractZipWindows(archivePath, destDir) {
37667
37712
  }
37668
37713
  }
37669
37714
  async function extractZipUnix(archivePath, destDir) {
37670
- const proc = spawn8(["unzip", "-o", archivePath, "-d", destDir], {
37715
+ const proc = spawn9(["unzip", "-o", archivePath, "-d", destDir], {
37671
37716
  stdout: "pipe",
37672
37717
  stderr: "pipe"
37673
37718
  });
@@ -37924,7 +37969,7 @@ async function runRg(options) {
37924
37969
  }
37925
37970
  const paths = options.paths?.length ? options.paths : ["."];
37926
37971
  args.push(...paths);
37927
- const proc = spawn9([cli.path, ...args], {
37972
+ const proc = spawn10([cli.path, ...args], {
37928
37973
  stdout: "pipe",
37929
37974
  stderr: "pipe"
37930
37975
  });
@@ -38026,7 +38071,7 @@ var grep = tool({
38026
38071
  });
38027
38072
 
38028
38073
  // src/tools/glob/cli.ts
38029
- var {spawn: spawn10 } = globalThis.Bun;
38074
+ var {spawn: spawn11 } = globalThis.Bun;
38030
38075
 
38031
38076
  // src/tools/glob/constants.ts
38032
38077
  var DEFAULT_TIMEOUT_MS3 = 60000;
@@ -38108,7 +38153,7 @@ async function runRgFiles(options, resolvedCli) {
38108
38153
  cwd = paths[0] || ".";
38109
38154
  command = [cli.path, ...args];
38110
38155
  }
38111
- const proc = spawn10(command, {
38156
+ const proc = spawn11(command, {
38112
38157
  stdout: "pipe",
38113
38158
  stderr: "pipe",
38114
38159
  cwd
@@ -38277,7 +38322,8 @@ function skillToCommandInfo2(skill) {
38277
38322
  subtask: skill.definition.subtask
38278
38323
  },
38279
38324
  content: skill.definition.template,
38280
- scope: skill.scope
38325
+ scope: skill.scope,
38326
+ lazyContentLoader: skill.lazyContent
38281
38327
  };
38282
38328
  }
38283
38329
  async function formatLoadedCommand(cmd) {
@@ -38310,8 +38356,12 @@ async function formatLoadedCommand(cmd) {
38310
38356
  `);
38311
38357
  sections.push(`## Command Instructions
38312
38358
  `);
38359
+ let content = cmd.content || "";
38360
+ if (!content && cmd.lazyContentLoader) {
38361
+ content = await cmd.lazyContentLoader.load();
38362
+ }
38313
38363
  const commandDir = cmd.path ? dirname11(cmd.path) : process.cwd();
38314
- const withFileRefs = await resolveFileReferencesInText(cmd.content || "", commandDir);
38364
+ const withFileRefs = await resolveFileReferencesInText(content, commandDir);
38315
38365
  const resolvedContent = await resolveCommandsInText(withFileRefs);
38316
38366
  sections.push(resolvedContent.trim());
38317
38367
  return sections.join(`
@@ -38992,14 +39042,14 @@ For: server processes, long-running tasks, background jobs, interactive CLI tool
38992
39042
  Blocked (use bash instead): capture-pane, save-buffer, show-buffer, pipe-pane.`;
38993
39043
 
38994
39044
  // src/tools/interactive-bash/utils.ts
38995
- var {spawn: spawn11 } = globalThis.Bun;
39045
+ var {spawn: spawn12 } = globalThis.Bun;
38996
39046
  var tmuxPath = null;
38997
39047
  var initPromise3 = null;
38998
39048
  async function findTmuxPath() {
38999
39049
  const isWindows2 = process.platform === "win32";
39000
39050
  const cmd = isWindows2 ? "where" : "which";
39001
39051
  try {
39002
- const proc = spawn11([cmd, "tmux"], {
39052
+ const proc = spawn12([cmd, "tmux"], {
39003
39053
  stdout: "pipe",
39004
39054
  stderr: "pipe"
39005
39055
  });
@@ -39013,7 +39063,7 @@ async function findTmuxPath() {
39013
39063
  if (!path7) {
39014
39064
  return null;
39015
39065
  }
39016
- const verifyProc = spawn11([path7, "-V"], {
39066
+ const verifyProc = spawn12([path7, "-V"], {
39017
39067
  stdout: "pipe",
39018
39068
  stderr: "pipe"
39019
39069
  });
@@ -40056,6 +40106,67 @@ var builtinTools = {
40056
40106
  // src/features/background-agent/manager.ts
40057
40107
  import { existsSync as existsSync47, readdirSync as readdirSync16 } from "fs";
40058
40108
  import { join as join54 } from "path";
40109
+
40110
+ // src/features/background-agent/concurrency.ts
40111
+ class ConcurrencyManager {
40112
+ config;
40113
+ counts = new Map;
40114
+ queues = new Map;
40115
+ constructor(config3) {
40116
+ this.config = config3;
40117
+ }
40118
+ getConcurrencyLimit(model) {
40119
+ const modelLimit = this.config?.modelConcurrency?.[model];
40120
+ if (modelLimit !== undefined) {
40121
+ return modelLimit === 0 ? Infinity : modelLimit;
40122
+ }
40123
+ const provider = model.split("/")[0];
40124
+ const providerLimit = this.config?.providerConcurrency?.[provider];
40125
+ if (providerLimit !== undefined) {
40126
+ return providerLimit === 0 ? Infinity : providerLimit;
40127
+ }
40128
+ const defaultLimit = this.config?.defaultConcurrency;
40129
+ if (defaultLimit !== undefined) {
40130
+ return defaultLimit === 0 ? Infinity : defaultLimit;
40131
+ }
40132
+ return 5;
40133
+ }
40134
+ async acquire(model) {
40135
+ const limit = this.getConcurrencyLimit(model);
40136
+ if (limit === Infinity) {
40137
+ return;
40138
+ }
40139
+ const current = this.counts.get(model) ?? 0;
40140
+ if (current < limit) {
40141
+ this.counts.set(model, current + 1);
40142
+ return;
40143
+ }
40144
+ return new Promise((resolve8) => {
40145
+ const queue = this.queues.get(model) ?? [];
40146
+ queue.push(resolve8);
40147
+ this.queues.set(model, queue);
40148
+ });
40149
+ }
40150
+ release(model) {
40151
+ const limit = this.getConcurrencyLimit(model);
40152
+ if (limit === Infinity) {
40153
+ return;
40154
+ }
40155
+ const queue = this.queues.get(model);
40156
+ if (queue && queue.length > 0) {
40157
+ const next = queue.shift();
40158
+ this.counts.set(model, this.counts.get(model) ?? 0);
40159
+ next();
40160
+ } else {
40161
+ const current = this.counts.get(model) ?? 0;
40162
+ if (current > 0) {
40163
+ this.counts.set(model, current - 1);
40164
+ }
40165
+ }
40166
+ }
40167
+ }
40168
+
40169
+ // src/features/background-agent/manager.ts
40059
40170
  var TASK_TTL_MS = 30 * 60 * 1000;
40060
40171
  function getMessageDir11(sessionID) {
40061
40172
  if (!existsSync47(MESSAGE_STORAGE))
@@ -40077,23 +40188,31 @@ class BackgroundManager {
40077
40188
  client;
40078
40189
  directory;
40079
40190
  pollingInterval;
40080
- constructor(ctx) {
40191
+ concurrencyManager;
40192
+ constructor(ctx, config3) {
40081
40193
  this.tasks = new Map;
40082
40194
  this.notifications = new Map;
40083
40195
  this.client = ctx.client;
40084
40196
  this.directory = ctx.directory;
40197
+ this.concurrencyManager = new ConcurrencyManager(config3);
40085
40198
  }
40086
40199
  async launch(input) {
40087
40200
  if (!input.agent || input.agent.trim() === "") {
40088
40201
  throw new Error("Agent parameter is required");
40089
40202
  }
40203
+ const model = input.agent;
40204
+ await this.concurrencyManager.acquire(model);
40090
40205
  const createResult = await this.client.session.create({
40091
40206
  body: {
40092
40207
  parentID: input.parentSessionID,
40093
40208
  title: `Background: ${input.description}`
40094
40209
  }
40210
+ }).catch((error45) => {
40211
+ this.concurrencyManager.release(model);
40212
+ throw error45;
40095
40213
  });
40096
40214
  if (createResult.error) {
40215
+ this.concurrencyManager.release(model);
40097
40216
  throw new Error(`Failed to create background session: ${createResult.error}`);
40098
40217
  }
40099
40218
  const sessionID = createResult.data.id;
@@ -40112,7 +40231,8 @@ class BackgroundManager {
40112
40231
  toolCalls: 0,
40113
40232
  lastUpdate: new Date
40114
40233
  },
40115
- parentModel: input.parentModel
40234
+ parentModel: input.parentModel,
40235
+ model
40116
40236
  };
40117
40237
  this.tasks.set(task.id, task);
40118
40238
  this.startPolling();
@@ -40123,7 +40243,8 @@ class BackgroundManager {
40123
40243
  agent: input.agent,
40124
40244
  tools: {
40125
40245
  task: false,
40126
- background_task: false
40246
+ background_task: false,
40247
+ call_omo_agent: false
40127
40248
  },
40128
40249
  parts: [{ type: "text", text: input.prompt }]
40129
40250
  }
@@ -40139,6 +40260,9 @@ class BackgroundManager {
40139
40260
  existingTask.error = errorMessage;
40140
40261
  }
40141
40262
  existingTask.completedAt = new Date;
40263
+ if (existingTask.model) {
40264
+ this.concurrencyManager.release(existingTask.model);
40265
+ }
40142
40266
  this.markForNotification(existingTask);
40143
40267
  this.notifyParentSession(existingTask);
40144
40268
  }
@@ -40245,6 +40369,9 @@ class BackgroundManager {
40245
40369
  task.completedAt = new Date;
40246
40370
  task.error = "Session deleted";
40247
40371
  }
40372
+ if (task.model) {
40373
+ this.concurrencyManager.release(task.model);
40374
+ }
40248
40375
  this.tasks.delete(task.id);
40249
40376
  this.clearNotificationsForTask(task.id);
40250
40377
  subagentSessions.delete(sessionID);
@@ -40308,6 +40435,9 @@ class BackgroundManager {
40308
40435
  log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
40309
40436
  const taskId = task.id;
40310
40437
  setTimeout(async () => {
40438
+ if (task.model) {
40439
+ this.concurrencyManager.release(task.model);
40440
+ }
40311
40441
  try {
40312
40442
  const messageDir = getMessageDir11(task.parentSessionID);
40313
40443
  const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
@@ -40360,6 +40490,9 @@ class BackgroundManager {
40360
40490
  task.status = "error";
40361
40491
  task.error = "Task timed out after 30 minutes";
40362
40492
  task.completedAt = new Date;
40493
+ if (task.model) {
40494
+ this.concurrencyManager.release(task.model);
40495
+ }
40363
40496
  this.clearNotificationsForTask(taskId);
40364
40497
  this.tasks.delete(taskId);
40365
40498
  subagentSessions.delete(task.sessionID);
@@ -43230,7 +43363,7 @@ import * as fs11 from "fs";
43230
43363
  import * as path7 from "path";
43231
43364
 
43232
43365
  // src/mcp/types.ts
43233
- var McpNameSchema = exports_external.enum(["context7", "grep_app"]);
43366
+ var McpNameSchema = exports_external.enum(["websearch", "context7", "grep_app"]);
43234
43367
  var AnyMcpNameSchema = exports_external.string().min(1);
43235
43368
 
43236
43369
  // src/config/schema.ts
@@ -43427,6 +43560,11 @@ var RalphLoopConfigSchema = exports_external.object({
43427
43560
  default_max_iterations: exports_external.number().min(1).max(1000).default(100),
43428
43561
  state_dir: exports_external.string().optional()
43429
43562
  });
43563
+ var BackgroundTaskConfigSchema = exports_external.object({
43564
+ defaultConcurrency: exports_external.number().min(1).optional(),
43565
+ providerConcurrency: exports_external.record(exports_external.string(), exports_external.number().min(1)).optional(),
43566
+ modelConcurrency: exports_external.record(exports_external.string(), exports_external.number().min(1)).optional()
43567
+ });
43430
43568
  var OhMyOpenCodeConfigSchema = exports_external.object({
43431
43569
  $schema: exports_external.string().optional(),
43432
43570
  disabled_mcps: exports_external.array(AnyMcpNameSchema).optional(),
@@ -43442,7 +43580,8 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
43442
43580
  experimental: ExperimentalConfigSchema.optional(),
43443
43581
  auto_update: exports_external.boolean().optional(),
43444
43582
  skills: SkillsConfigSchema.optional(),
43445
- ralph_loop: RalphLoopConfigSchema.optional()
43583
+ ralph_loop: RalphLoopConfigSchema.optional(),
43584
+ background_task: BackgroundTaskConfigSchema.optional()
43446
43585
  });
43447
43586
  // src/plugin-config.ts
43448
43587
  function loadConfigFromPath2(configPath, ctx) {
@@ -44355,8 +44494,7 @@ function createOracleAgent(model = DEFAULT_MODEL2) {
44355
44494
  const restrictions = createAgentToolRestrictions([
44356
44495
  "write",
44357
44496
  "edit",
44358
- "task",
44359
- "background_task"
44497
+ "task"
44360
44498
  ]);
44361
44499
  const base = {
44362
44500
  description: "Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.",
@@ -44374,7 +44512,7 @@ function createOracleAgent(model = DEFAULT_MODEL2) {
44374
44512
  var oracleAgent = createOracleAgent();
44375
44513
 
44376
44514
  // src/agents/librarian.ts
44377
- var DEFAULT_MODEL3 = "anthropic/claude-sonnet-4-5";
44515
+ var DEFAULT_MODEL3 = "opencode/glm-4.7-free";
44378
44516
  var LIBRARIAN_PROMPT_METADATA = {
44379
44517
  category: "exploration",
44380
44518
  cost: "CHEAP",
@@ -44394,8 +44532,7 @@ var LIBRARIAN_PROMPT_METADATA = {
44394
44532
  function createLibrarianAgent(model = DEFAULT_MODEL3) {
44395
44533
  const restrictions = createAgentToolRestrictions([
44396
44534
  "write",
44397
- "edit",
44398
- "background_task"
44535
+ "edit"
44399
44536
  ]);
44400
44537
  return {
44401
44538
  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.",
@@ -44407,7 +44544,7 @@ function createLibrarianAgent(model = DEFAULT_MODEL3) {
44407
44544
 
44408
44545
  You are **THE LIBRARIAN**, a specialized open-source codebase understanding agent.
44409
44546
 
44410
- Your job: Answer questions about open-source libraries by finding **EVIDENCE** with **GitHub permalinks**.
44547
+ Your job: Answer questions about open-source libraries. Provide **EVIDENCE** with **GitHub permalinks** when the question requires verification, implementation details, or current/version-specific information. For well-known APIs and stable concepts, answer directly from knowledge.
44411
44548
 
44412
44549
  ## CRITICAL: DATE AWARENESS
44413
44550
 
@@ -44419,9 +44556,13 @@ Your job: Answer questions about open-source libraries by finding **EVIDENCE** w
44419
44556
 
44420
44557
  ---
44421
44558
 
44422
- ## PHASE 0: REQUEST CLASSIFICATION (MANDATORY FIRST STEP)
44559
+ ## PHASE 0: ASSESS BEFORE SEARCHING
44423
44560
 
44424
- Classify EVERY request into one of these categories before taking action:
44561
+ **First**: Can you answer confidently from training knowledge? If yes, answer directly.
44562
+
44563
+ **Search when**: version-specific info, implementation internals, recent changes, unfamiliar libraries, user explicitly requests source/examples.
44564
+
44565
+ **If search needed**, classify into:
44425
44566
 
44426
44567
  | Type | Trigger Examples | Tools |
44427
44568
  |------|------------------|-------|
@@ -44437,7 +44578,7 @@ Classify EVERY request into one of these categories before taking action:
44437
44578
  ### TYPE A: CONCEPTUAL QUESTION
44438
44579
  **Trigger**: "How do I...", "What is...", "Best practice for...", rough/general questions
44439
44580
 
44440
- **Execute in parallel (2+ calls)**:
44581
+ **If searching**, use tools as needed:
44441
44582
  \`\`\`
44442
44583
  Tool 1: context7_resolve-library-id("library-name")
44443
44584
  \u2192 then context7_get-library-docs(id, topic: "specific-topic")
@@ -44469,7 +44610,7 @@ Step 4: Construct permalink
44469
44610
  https://github.com/owner/repo/blob/<sha>/path/to/file#L10-L20
44470
44611
  \`\`\`
44471
44612
 
44472
- **Parallel acceleration (4+ calls)**:
44613
+ **For faster results, parallelize**:
44473
44614
  \`\`\`
44474
44615
  Tool 1: gh repo clone owner/repo \${TMPDIR:-/tmp}/repo -- --depth 1
44475
44616
  Tool 2: grep_app_searchGitHub(query: "function_name", repo: "owner/repo")
@@ -44482,7 +44623,7 @@ Tool 4: context7_get-library-docs(id, topic: "relevant-api")
44482
44623
  ### TYPE C: CONTEXT & HISTORY
44483
44624
  **Trigger**: "Why was this changed?", "What's the history?", "Related issues/PRs?"
44484
44625
 
44485
- **Execute in parallel (4+ calls)**:
44626
+ **Tools to use**:
44486
44627
  \`\`\`
44487
44628
  Tool 1: gh search issues "keyword" --repo owner/repo --state all --limit 10
44488
44629
  Tool 2: gh search prs "keyword" --repo owner/repo --state merged --limit 10
@@ -44504,7 +44645,7 @@ gh api repos/owner/repo/pulls/<number>/files
44504
44645
  ### TYPE D: COMPREHENSIVE RESEARCH
44505
44646
  **Trigger**: Complex questions, ambiguous requests, "deep dive into..."
44506
44647
 
44507
- **Execute ALL available tools in parallel (5+ calls)**:
44648
+ **Use multiple tools as needed**:
44508
44649
  \`\`\`
44509
44650
  // Documentation
44510
44651
  Tool 1: context7_resolve-library-id \u2192 context7_get-library-docs
@@ -44590,14 +44731,16 @@ Use OS-appropriate temp directory:
44590
44731
 
44591
44732
  ---
44592
44733
 
44593
- ## PARALLEL EXECUTION REQUIREMENTS
44734
+ ## PARALLEL EXECUTION GUIDANCE
44594
44735
 
44595
- | Request Type | Minimum Parallel Calls |
44596
- |--------------|----------------------|
44597
- | TYPE A (Conceptual) | 3+ |
44598
- | TYPE B (Implementation) | 4+ |
44599
- | TYPE C (Context) | 4+ |
44600
- | TYPE D (Comprehensive) | 6+ |
44736
+ When searching is needed, scale effort to question complexity:
44737
+
44738
+ | Request Type | Suggested Calls |
44739
+ |--------------|----------------|
44740
+ | TYPE A (Conceptual) | 1-2 |
44741
+ | TYPE B (Implementation) | 2-3 |
44742
+ | TYPE C (Context) | 2-3 |
44743
+ | TYPE D (Comprehensive) | 3-5 |
44601
44744
 
44602
44745
  **Always vary queries** when using grep_app:
44603
44746
  \`\`\`
@@ -44662,8 +44805,7 @@ var EXPLORE_PROMPT_METADATA = {
44662
44805
  function createExploreAgent(model = DEFAULT_MODEL4) {
44663
44806
  const restrictions = createAgentToolRestrictions([
44664
44807
  "write",
44665
- "edit",
44666
- "background_task"
44808
+ "edit"
44667
44809
  ]);
44668
44810
  return {
44669
44811
  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.',
@@ -44771,7 +44913,7 @@ var FRONTEND_PROMPT_METADATA = {
44771
44913
  ]
44772
44914
  };
44773
44915
  function createFrontendUiUxEngineerAgent(model = DEFAULT_MODEL5) {
44774
- const restrictions = createAgentToolRestrictions(["background_task"]);
44916
+ const restrictions = createAgentToolRestrictions([]);
44775
44917
  return {
44776
44918
  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.",
44777
44919
  mode: "subagent",
@@ -44865,7 +45007,7 @@ var DOCUMENT_WRITER_PROMPT_METADATA = {
44865
45007
  ]
44866
45008
  };
44867
45009
  function createDocumentWriterAgent(model = DEFAULT_MODEL6) {
44868
- const restrictions = createAgentToolRestrictions(["background_task"]);
45010
+ const restrictions = createAgentToolRestrictions([]);
44869
45011
  return {
44870
45012
  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.",
44871
45013
  mode: "subagent",
@@ -45081,8 +45223,7 @@ function createMultimodalLookerAgent(model = DEFAULT_MODEL7) {
45081
45223
  const restrictions = createAgentToolRestrictions([
45082
45224
  "write",
45083
45225
  "edit",
45084
- "bash",
45085
- "background_task"
45226
+ "bash"
45086
45227
  ]);
45087
45228
  return {
45088
45229
  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.",
@@ -46781,6 +46922,13 @@ async function loadAllPluginComponents(options) {
46781
46922
  errors: errors3
46782
46923
  };
46783
46924
  }
46925
+ // src/mcp/websearch.ts
46926
+ var websearch = {
46927
+ type: "remote",
46928
+ url: "https://mcp.exa.ai/mcp?tools=web_search_exa",
46929
+ enabled: true
46930
+ };
46931
+
46784
46932
  // src/mcp/context7.ts
46785
46933
  var context7 = {
46786
46934
  type: "remote",
@@ -46797,6 +46945,7 @@ var grep_app = {
46797
46945
 
46798
46946
  // src/mcp/index.ts
46799
46947
  var allBuiltinMcps = {
46948
+ websearch,
46800
46949
  context7,
46801
46950
  grep_app
46802
46951
  };
@@ -47120,7 +47269,6 @@ var OhMyOpenCodePlugin = async (ctx) => {
47120
47269
  config: pluginConfig.ralph_loop,
47121
47270
  checkSessionExists: async (sessionId) => sessionExists(sessionId)
47122
47271
  }) : null;
47123
- const autoSlashCommand = isHookEnabled("auto-slash-command") ? createAutoSlashCommandHook() : null;
47124
47272
  const editErrorRecovery = isHookEnabled("edit-error-recovery") ? createEditErrorRecoveryHook(ctx) : null;
47125
47273
  const backgroundManager = new BackgroundManager(ctx);
47126
47274
  const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx, { backgroundManager }) : null;
@@ -47170,6 +47318,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
47170
47318
  commands: commands2,
47171
47319
  skills: mergedSkills
47172
47320
  });
47321
+ const autoSlashCommand = isHookEnabled("auto-slash-command") ? createAutoSlashCommandHook({ skills: mergedSkills }) : null;
47173
47322
  const googleAuthHooks = pluginConfig.google_auth !== false ? await createGoogleAntigravityAuthPlugin(ctx) : null;
47174
47323
  const configHandler = createConfigHandler({
47175
47324
  ctx,