oh-my-opencode 4.3.0 → 4.3.1

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/cli/index.js CHANGED
@@ -8093,8 +8093,14 @@ var init_external_plugin_detector = __esm(() => {
8093
8093
  });
8094
8094
 
8095
8095
  // src/shared/bun-spawn-shim.ts
8096
- import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "child_process";
8096
+ import {
8097
+ spawn as nodeSpawn,
8098
+ spawnSync as nodeSpawnSync
8099
+ } from "child_process";
8097
8100
  import { Readable, Writable } from "stream";
8101
+ function getBunRuntime() {
8102
+ return typeof Bun === "undefined" ? undefined : runtime.Bun;
8103
+ }
8098
8104
  function emptyReadableStream() {
8099
8105
  return new ReadableStream({
8100
8106
  start(controller) {
@@ -8127,6 +8133,24 @@ function resolveStdio(options) {
8127
8133
  return options.stdio;
8128
8134
  return [options.stdin ?? "ignore", options.stdout ?? "pipe", options.stderr ?? "inherit"];
8129
8135
  }
8136
+ function createNodeSpawnOptions(options, platform = process.platform) {
8137
+ const nodeOptions = {
8138
+ stdio: resolveStdio(options),
8139
+ shell: false
8140
+ };
8141
+ if (options.cwd !== undefined)
8142
+ nodeOptions.cwd = options.cwd;
8143
+ if (options.env !== undefined)
8144
+ nodeOptions.env = options.env;
8145
+ if (options.detached !== undefined)
8146
+ nodeOptions.detached = options.detached;
8147
+ if (options.signal !== undefined)
8148
+ nodeOptions.signal = options.signal;
8149
+ if (platform === "win32") {
8150
+ nodeOptions.windowsHide = true;
8151
+ }
8152
+ return nodeOptions;
8153
+ }
8130
8154
  function wrapNodeProcess(proc) {
8131
8155
  let exitCode = null;
8132
8156
  const exited = new Promise((resolve4, reject) => {
@@ -8169,28 +8193,24 @@ function wrapNodeProcess(proc) {
8169
8193
  };
8170
8194
  }
8171
8195
  function spawn(cmdOrOpts, opts) {
8172
- if (IS_BUN)
8173
- return runtime.Bun.spawn(cmdOrOpts, opts);
8174
8196
  const { cmd, opts: options } = resolveCommand(cmdOrOpts, opts);
8197
+ const bun = getBunRuntime();
8198
+ if (bun)
8199
+ return bun.spawn(cmd, options);
8175
8200
  const [bin, ...args] = cmd;
8176
- const proc = nodeSpawn(bin, args, {
8177
- cwd: options.cwd,
8178
- env: options.env,
8179
- stdio: resolveStdio(options),
8180
- detached: options.detached,
8181
- signal: options.signal
8182
- });
8201
+ if (bin === undefined) {
8202
+ throw new Error("Cannot spawn an empty command");
8203
+ }
8204
+ const proc = nodeSpawn(bin, args, createNodeSpawnOptions(options));
8183
8205
  return wrapNodeProcess(proc);
8184
8206
  }
8185
- var runtime, IS_BUN;
8207
+ var runtime;
8186
8208
  var init_bun_spawn_shim = __esm(() => {
8187
8209
  runtime = globalThis;
8188
- IS_BUN = typeof runtime.Bun !== "undefined";
8189
8210
  });
8190
8211
 
8191
8212
  // src/shared/archive-entry-validator.ts
8192
8213
  var init_archive_entry_validator = () => {};
8193
-
8194
8214
  // src/shared/zip-entry-listing/python-zip-entry-listing.ts
8195
8215
  var init_python_zip_entry_listing = __esm(() => {
8196
8216
  init_bun_spawn_shim();
@@ -8234,10 +8254,10 @@ var init_zip_extractor = __esm(() => {
8234
8254
  });
8235
8255
 
8236
8256
  // src/shared/bun-file-shim.ts
8237
- var runtime2, IS_BUN2;
8257
+ var runtime2, IS_BUN;
8238
8258
  var init_bun_file_shim = __esm(() => {
8239
8259
  runtime2 = globalThis;
8240
- IS_BUN2 = typeof runtime2.Bun !== "undefined";
8260
+ IS_BUN = typeof runtime2.Bun !== "undefined";
8241
8261
  });
8242
8262
 
8243
8263
  // src/shared/binary-downloader.ts
@@ -55420,7 +55440,7 @@ var {
55420
55440
  // package.json
55421
55441
  var package_default = {
55422
55442
  name: "oh-my-opencode",
55423
- version: "4.3.0",
55443
+ version: "4.3.1",
55424
55444
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
55425
55445
  main: "./dist/index.js",
55426
55446
  types: "dist/index.d.ts",
@@ -55530,17 +55550,17 @@ var package_default = {
55530
55550
  zod: "^4.4.3"
55531
55551
  },
55532
55552
  optionalDependencies: {
55533
- "oh-my-opencode-darwin-arm64": "4.3.0",
55534
- "oh-my-opencode-darwin-x64": "4.3.0",
55535
- "oh-my-opencode-darwin-x64-baseline": "4.3.0",
55536
- "oh-my-opencode-linux-arm64": "4.3.0",
55537
- "oh-my-opencode-linux-arm64-musl": "4.3.0",
55538
- "oh-my-opencode-linux-x64": "4.3.0",
55539
- "oh-my-opencode-linux-x64-baseline": "4.3.0",
55540
- "oh-my-opencode-linux-x64-musl": "4.3.0",
55541
- "oh-my-opencode-linux-x64-musl-baseline": "4.3.0",
55542
- "oh-my-opencode-windows-x64": "4.3.0",
55543
- "oh-my-opencode-windows-x64-baseline": "4.3.0"
55553
+ "oh-my-opencode-darwin-arm64": "4.3.1",
55554
+ "oh-my-opencode-darwin-x64": "4.3.1",
55555
+ "oh-my-opencode-darwin-x64-baseline": "4.3.1",
55556
+ "oh-my-opencode-linux-arm64": "4.3.1",
55557
+ "oh-my-opencode-linux-arm64-musl": "4.3.1",
55558
+ "oh-my-opencode-linux-x64": "4.3.1",
55559
+ "oh-my-opencode-linux-x64-baseline": "4.3.1",
55560
+ "oh-my-opencode-linux-x64-musl": "4.3.1",
55561
+ "oh-my-opencode-linux-x64-musl-baseline": "4.3.1",
55562
+ "oh-my-opencode-windows-x64": "4.3.1",
55563
+ "oh-my-opencode-windows-x64-baseline": "4.3.1"
55544
55564
  },
55545
55565
  overrides: {
55546
55566
  hono: "^4.12.18",
@@ -82985,7 +83005,7 @@ import { basename as basename5 } from "path";
82985
83005
  import { accessSync as accessSync3, constants as constants6 } from "fs";
82986
83006
  import { delimiter as delimiter2, join as join39 } from "path";
82987
83007
  var runtime3 = globalThis;
82988
- var IS_BUN3 = typeof runtime3.Bun !== "undefined";
83008
+ var IS_BUN2 = typeof runtime3.Bun !== "undefined";
82989
83009
  function isUnsafeCommandName(commandName) {
82990
83010
  if (commandName.includes("/") || commandName.includes("\\"))
82991
83011
  return true;
@@ -83020,7 +83040,7 @@ function bunWhich(commandName) {
83020
83040
  return null;
83021
83041
  if (isUnsafeCommandName(commandName))
83022
83042
  return null;
83023
- if (IS_BUN3)
83043
+ if (IS_BUN2)
83024
83044
  return runtime3.Bun?.which(commandName) ?? null;
83025
83045
  const pathValue = resolvePathValue();
83026
83046
  if (!pathValue)
@@ -62,6 +62,7 @@ export declare class ParentWakeNotifier {
62
62
  private resolveParentWakePromptContext;
63
63
  private cloneParentWake;
64
64
  private trackDispatchedParentWake;
65
+ private isSameParentWake;
65
66
  private loadParentWakeSessionMessages;
66
67
  private getParentWakeMessageRole;
67
68
  private getParentWakeMessageFinish;
package/dist/index.js CHANGED
@@ -9157,8 +9157,14 @@ var init_external_plugin_detector = __esm(() => {
9157
9157
  });
9158
9158
 
9159
9159
  // src/shared/bun-spawn-shim.ts
9160
- import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "child_process";
9160
+ import {
9161
+ spawn as nodeSpawn,
9162
+ spawnSync as nodeSpawnSync
9163
+ } from "child_process";
9161
9164
  import { Readable, Writable } from "stream";
9165
+ function getBunRuntime() {
9166
+ return typeof Bun === "undefined" ? undefined : runtime.Bun;
9167
+ }
9162
9168
  function emptyReadableStream() {
9163
9169
  return new ReadableStream({
9164
9170
  start(controller) {
@@ -9191,6 +9197,38 @@ function resolveStdio(options) {
9191
9197
  return options.stdio;
9192
9198
  return [options.stdin ?? "ignore", options.stdout ?? "pipe", options.stderr ?? "inherit"];
9193
9199
  }
9200
+ function createNodeSpawnOptions(options, platform = process.platform) {
9201
+ const nodeOptions = {
9202
+ stdio: resolveStdio(options),
9203
+ shell: false
9204
+ };
9205
+ if (options.cwd !== undefined)
9206
+ nodeOptions.cwd = options.cwd;
9207
+ if (options.env !== undefined)
9208
+ nodeOptions.env = options.env;
9209
+ if (options.detached !== undefined)
9210
+ nodeOptions.detached = options.detached;
9211
+ if (options.signal !== undefined)
9212
+ nodeOptions.signal = options.signal;
9213
+ if (platform === "win32") {
9214
+ nodeOptions.windowsHide = true;
9215
+ }
9216
+ return nodeOptions;
9217
+ }
9218
+ function createNodeSpawnSyncOptions(options, platform = process.platform) {
9219
+ const nodeOptions = {
9220
+ stdio: resolveStdio(options),
9221
+ shell: false
9222
+ };
9223
+ if (options.cwd !== undefined)
9224
+ nodeOptions.cwd = options.cwd;
9225
+ if (options.env !== undefined)
9226
+ nodeOptions.env = options.env;
9227
+ if (platform === "win32") {
9228
+ nodeOptions.windowsHide = true;
9229
+ }
9230
+ return nodeOptions;
9231
+ }
9194
9232
  function wrapNodeProcess(proc) {
9195
9233
  let exitCode = null;
9196
9234
  const exited = new Promise((resolve5, reject) => {
@@ -9232,42 +9270,45 @@ function wrapNodeProcess(proc) {
9232
9270
  }
9233
9271
  };
9234
9272
  }
9273
+ function toSpawnSyncBuffer(output) {
9274
+ if (output === null) {
9275
+ return;
9276
+ }
9277
+ return Buffer.isBuffer(output) ? output : Buffer.from(output, "utf8");
9278
+ }
9235
9279
  function spawn2(cmdOrOpts, opts) {
9236
- if (IS_BUN)
9237
- return runtime.Bun.spawn(cmdOrOpts, opts);
9238
9280
  const { cmd, opts: options } = resolveCommand(cmdOrOpts, opts);
9281
+ const bun = getBunRuntime();
9282
+ if (bun)
9283
+ return bun.spawn(cmd, options);
9239
9284
  const [bin, ...args] = cmd;
9240
- const proc = nodeSpawn(bin, args, {
9241
- cwd: options.cwd,
9242
- env: options.env,
9243
- stdio: resolveStdio(options),
9244
- detached: options.detached,
9245
- signal: options.signal
9246
- });
9285
+ if (bin === undefined) {
9286
+ throw new Error("Cannot spawn an empty command");
9287
+ }
9288
+ const proc = nodeSpawn(bin, args, createNodeSpawnOptions(options));
9247
9289
  return wrapNodeProcess(proc);
9248
9290
  }
9249
9291
  function spawnSync(cmdOrOpts, opts) {
9250
- if (IS_BUN)
9251
- return runtime.Bun.spawnSync(cmdOrOpts, opts);
9252
9292
  const { cmd, opts: options } = resolveCommand(cmdOrOpts, opts);
9293
+ const bun = getBunRuntime();
9294
+ if (bun)
9295
+ return bun.spawnSync(cmd, options);
9253
9296
  const [bin, ...args] = cmd;
9254
- const result = nodeSpawnSync(bin, args, {
9255
- cwd: options.cwd,
9256
- env: options.env,
9257
- stdio: resolveStdio(options)
9258
- });
9297
+ if (bin === undefined) {
9298
+ throw new Error("Cannot spawnSync an empty command");
9299
+ }
9300
+ const result = nodeSpawnSync(bin, args, createNodeSpawnSyncOptions(options));
9259
9301
  return {
9260
9302
  exitCode: result.status ?? 1,
9261
- stdout: result.stdout ?? undefined,
9262
- stderr: result.stderr ?? undefined,
9303
+ stdout: toSpawnSyncBuffer(result.stdout),
9304
+ stderr: toSpawnSyncBuffer(result.stderr),
9263
9305
  success: (result.status ?? 1) === 0,
9264
9306
  pid: result.pid ?? -1
9265
9307
  };
9266
9308
  }
9267
- var runtime, IS_BUN;
9309
+ var runtime;
9268
9310
  var init_bun_spawn_shim = __esm(() => {
9269
9311
  runtime = globalThis;
9270
- IS_BUN = typeof runtime.Bun !== "undefined";
9271
9312
  });
9272
9313
 
9273
9314
  // src/shared/archive-entry-validator.ts
@@ -9326,6 +9367,52 @@ function validateArchiveEntries(entries, destDir) {
9326
9367
  }
9327
9368
  var init_archive_entry_validator = () => {};
9328
9369
 
9370
+ // src/shared/process-stream-reader.ts
9371
+ function bufferFromChunk(chunk) {
9372
+ if (Buffer.isBuffer(chunk)) {
9373
+ return chunk;
9374
+ }
9375
+ if (chunk instanceof Uint8Array) {
9376
+ return Buffer.from(chunk);
9377
+ }
9378
+ if (typeof chunk === "string") {
9379
+ return Buffer.from(chunk, "utf8");
9380
+ }
9381
+ throw new TypeError(`Unsupported process stream chunk type: ${typeof chunk}`);
9382
+ }
9383
+ async function readWebStream(stream) {
9384
+ const reader = stream.getReader();
9385
+ const chunks = [];
9386
+ try {
9387
+ while (true) {
9388
+ const result = await reader.read();
9389
+ if (result.done) {
9390
+ return chunks;
9391
+ }
9392
+ chunks.push(Buffer.from(result.value));
9393
+ }
9394
+ } finally {
9395
+ reader.releaseLock();
9396
+ }
9397
+ }
9398
+ async function readNodeStream(stream) {
9399
+ const chunks = [];
9400
+ for await (const chunk of stream) {
9401
+ chunks.push(bufferFromChunk(chunk));
9402
+ }
9403
+ return chunks;
9404
+ }
9405
+ function isWebReadableStream(stream) {
9406
+ return typeof ReadableStream !== "undefined" && stream instanceof ReadableStream;
9407
+ }
9408
+ async function readProcessStream(stream) {
9409
+ if (!stream) {
9410
+ return "";
9411
+ }
9412
+ const chunks = isWebReadableStream(stream) ? await readWebStream(stream) : await readNodeStream(stream);
9413
+ return Buffer.concat(chunks).toString("utf8");
9414
+ }
9415
+
9329
9416
  // src/shared/zip-entry-listing/python-zip-entry-listing.ts
9330
9417
  function isPythonZipListingAvailable() {
9331
9418
  const proc = spawnSync(["python3", "--version"], {
@@ -9363,8 +9450,8 @@ async function listZipEntriesWithPython(archivePath) {
9363
9450
  });
9364
9451
  const [exitCode, stdout, stderr] = await Promise.all([
9365
9452
  proc.exited,
9366
- new Response(proc.stdout).text(),
9367
- new Response(proc.stderr).text()
9453
+ readProcessStream(proc.stdout),
9454
+ readProcessStream(proc.stderr)
9368
9455
  ]);
9369
9456
  if (exitCode !== 0) {
9370
9457
  throw new Error(`zip entry listing failed (exit ${exitCode}): ${stderr}`);
@@ -9431,8 +9518,8 @@ async function listZipEntriesWithPowerShell(archivePath, escapePowerShellPath, e
9431
9518
  });
9432
9519
  const [exitCode, stdout, stderr] = await Promise.all([
9433
9520
  proc.exited,
9434
- new Response(proc.stdout).text(),
9435
- new Response(proc.stderr).text()
9521
+ readProcessStream(proc.stdout),
9522
+ readProcessStream(proc.stderr)
9436
9523
  ]);
9437
9524
  if (exitCode !== 0) {
9438
9525
  throw new Error(`zip entry listing failed (exit ${exitCode}): ${stderr}`);
@@ -9495,8 +9582,8 @@ async function listZipEntriesWithTar(archivePath) {
9495
9582
  });
9496
9583
  const [exitCode, stdout, stderr] = await Promise.all([
9497
9584
  proc.exited,
9498
- new Response(proc.stdout).text(),
9499
- new Response(proc.stderr).text()
9585
+ readProcessStream(proc.stdout),
9586
+ readProcessStream(proc.stderr)
9500
9587
  ]);
9501
9588
  if (exitCode !== 0) {
9502
9589
  throw new Error(`zip entry listing failed (exit ${exitCode}): ${stderr}`);
@@ -9516,8 +9603,8 @@ async function readZipSymlinkTarget(archivePath, entryPath) {
9516
9603
  });
9517
9604
  const [exitCode, stdout, stderr] = await Promise.all([
9518
9605
  proc.exited,
9519
- new Response(proc.stdout).text(),
9520
- new Response(proc.stderr).text()
9606
+ readProcessStream(proc.stdout),
9607
+ readProcessStream(proc.stderr)
9521
9608
  ]);
9522
9609
  if (exitCode !== 0) {
9523
9610
  throw new Error(`zip symlink target read failed (exit ${exitCode}): ${stderr}`);
@@ -9560,8 +9647,8 @@ async function listZipEntriesWithZipInfo(archivePath) {
9560
9647
  });
9561
9648
  const [exitCode, stdout, stderr] = await Promise.all([
9562
9649
  proc.exited,
9563
- new Response(proc.stdout).text(),
9564
- new Response(proc.stderr).text()
9650
+ readProcessStream(proc.stdout),
9651
+ readProcessStream(proc.stderr)
9565
9652
  ]);
9566
9653
  if (exitCode !== 0) {
9567
9654
  throw new Error(`zip entry listing failed (exit ${exitCode}): ${stderr}`);
@@ -9657,7 +9744,7 @@ async function extractZip(archivePath, destDir) {
9657
9744
  }
9658
9745
  const exitCode = await proc.exited;
9659
9746
  if (exitCode !== 0) {
9660
- const stderr = await new Response(proc.stderr).text();
9747
+ const stderr = await readProcessStream(proc.stderr);
9661
9748
  throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
9662
9749
  }
9663
9750
  }
@@ -9716,20 +9803,20 @@ function createNodeFile(path6) {
9716
9803
  };
9717
9804
  }
9718
9805
  function bunFile(path6) {
9719
- if (IS_BUN2)
9806
+ if (IS_BUN)
9720
9807
  return runtime2.Bun.file(path6);
9721
9808
  return createNodeFile(path6);
9722
9809
  }
9723
9810
  async function bunWrite(path6, data) {
9724
- if (IS_BUN2)
9811
+ if (IS_BUN)
9725
9812
  return runtime2.Bun.write(path6, data);
9726
9813
  await writeFile(path6, toWritableData(data));
9727
9814
  return byteLength(data);
9728
9815
  }
9729
- var runtime2, IS_BUN2;
9816
+ var runtime2, IS_BUN;
9730
9817
  var init_bun_file_shim = __esm(() => {
9731
9818
  runtime2 = globalThis;
9732
- IS_BUN2 = typeof runtime2.Bun !== "undefined";
9819
+ IS_BUN = typeof runtime2.Bun !== "undefined";
9733
9820
  });
9734
9821
 
9735
9822
  // src/shared/binary-downloader.ts
@@ -9766,7 +9853,7 @@ async function extractTarGz(archivePath, destDir, options) {
9766
9853
  });
9767
9854
  const exitCode = await proc.exited;
9768
9855
  if (exitCode !== 0) {
9769
- const stderr = await new Response(proc.stderr).text();
9856
+ const stderr = await readProcessStream(proc.stderr);
9770
9857
  if (isTarTraversalErrorOutput(stderr)) {
9771
9858
  throw new Error(`Unsafe archive entry: path contains path traversal (${archivePath})`);
9772
9859
  }
@@ -9816,8 +9903,8 @@ async function listTarEntries(archivePath, cwd) {
9816
9903
  });
9817
9904
  const [exitCode, stdout, stderr] = await Promise.all([
9818
9905
  proc.exited,
9819
- new Response(proc.stdout).text(),
9820
- new Response(proc.stderr).text()
9906
+ readProcessStream(proc.stdout),
9907
+ readProcessStream(proc.stderr)
9821
9908
  ]);
9822
9909
  if (isTarTraversalErrorOutput(stderr)) {
9823
9910
  throw new Error(`Unsafe archive entry: path contains path traversal (${archivePath})`);
@@ -69279,6 +69366,8 @@ async function buildReadyNotificationContent(ctx, input) {
69279
69366
 
69280
69367
  // src/hooks/session-notification-sender.ts
69281
69368
  init_shared();
69369
+ import { execFile } from "child_process";
69370
+ import { promisify as promisify2 } from "util";
69282
69371
  import { platform } from "os";
69283
69372
 
69284
69373
  // src/hooks/session-notification-utils.ts
@@ -69288,7 +69377,7 @@ init_logger();
69288
69377
  import { accessSync as accessSync2, constants as constants4 } from "fs";
69289
69378
  import { delimiter, join as join29 } from "path";
69290
69379
  var runtime3 = globalThis;
69291
- var IS_BUN3 = typeof runtime3.Bun !== "undefined";
69380
+ var IS_BUN2 = typeof runtime3.Bun !== "undefined";
69292
69381
  function isUnsafeCommandName(commandName) {
69293
69382
  if (commandName.includes("/") || commandName.includes("\\"))
69294
69383
  return true;
@@ -69323,7 +69412,7 @@ function bunWhich(commandName) {
69323
69412
  return null;
69324
69413
  if (isUnsafeCommandName(commandName))
69325
69414
  return null;
69326
- if (IS_BUN3)
69415
+ if (IS_BUN2)
69327
69416
  return runtime3.Bun?.which(commandName) ?? null;
69328
69417
  const pathValue = resolvePathValue();
69329
69418
  if (!pathValue)
@@ -69459,14 +69548,33 @@ function getDefaultSoundPath(platform2) {
69459
69548
  }
69460
69549
  }
69461
69550
  var hasLoggedUnavailableShellHelper = false;
69462
- function canRunNotificationCommand(ctx) {
69463
- if (typeof ctx?.$ === "function")
69464
- return true;
69551
+ function getShellRunner(ctx) {
69552
+ if (typeof ctx.$ === "function")
69553
+ return ctx.$;
69465
69554
  if (!hasLoggedUnavailableShellHelper) {
69466
69555
  hasLoggedUnavailableShellHelper = true;
69467
- log("[session-notification] ctx.$ unavailable; skipping notification command execution");
69556
+ log("[session-notification] ctx.$ unavailable; falling back to child_process.execFile");
69468
69557
  }
69469
- return false;
69558
+ return;
69559
+ }
69560
+ function logCommandFailure(commandName, error) {
69561
+ log("[session-notification] notification command failed", {
69562
+ commandName,
69563
+ error: typeof error === "string" ? error : error.message
69564
+ });
69565
+ }
69566
+ function logOperationFailure(operation, error) {
69567
+ log("[session-notification] notification operation failed", {
69568
+ operation,
69569
+ error: typeof error === "string" ? error : error.message
69570
+ });
69571
+ }
69572
+ async function runQuiet(command) {
69573
+ if (typeof command.quiet === "function") {
69574
+ await command.quiet();
69575
+ return;
69576
+ }
69577
+ await command;
69470
69578
  }
69471
69579
  async function runQuietNothrow(command) {
69472
69580
  const safeCommand = typeof command.nothrow === "function" ? command.nothrow() : command;
@@ -69476,85 +69584,124 @@ async function runQuietNothrow(command) {
69476
69584
  }
69477
69585
  await safeCommand;
69478
69586
  }
69479
- async function sendSessionNotification(ctx, platform2, title, message) {
69480
- if (!canRunNotificationCommand(ctx))
69587
+ async function runExecFile(commandPath, args) {
69588
+ const execFileAsync = promisify2(execFile);
69589
+ await execFileAsync(commandPath, [...args], { windowsHide: true });
69590
+ }
69591
+ async function runNotificationCommand(ctx, commandPath, args, shellCommand, shellFailureMode = "nothrow") {
69592
+ const shell = getShellRunner(ctx);
69593
+ if (shell) {
69594
+ if (shellFailureMode === "throw") {
69595
+ await runQuiet(shellCommand(shell));
69596
+ return;
69597
+ }
69598
+ await runQuietNothrow(shellCommand(shell));
69481
69599
  return;
69482
- switch (platform2) {
69483
- case "darwin": {
69484
- const cmuxPath = await getCmuxPath();
69485
- if (cmuxPath) {
69486
- try {
69487
- await ctx.$`${cmuxPath} notify --title ${title} --body ${message}`.quiet();
69488
- break;
69489
- } catch {}
69490
- }
69491
- const terminalNotifierPath = await getTerminalNotifierPath();
69492
- if (terminalNotifierPath) {
69493
- const bundleId = process.env.__CFBundleIdentifier;
69494
- try {
69495
- if (bundleId) {
69496
- await ctx.$`${terminalNotifierPath} -title ${title} -message ${message} -activate ${bundleId}`.quiet();
69497
- } else {
69498
- await ctx.$`${terminalNotifierPath} -title ${title} -message ${message}`.quiet();
69600
+ }
69601
+ await runExecFile(commandPath, args);
69602
+ }
69603
+ async function sendSessionNotification(ctx, platform2, title, message) {
69604
+ try {
69605
+ switch (platform2) {
69606
+ case "darwin": {
69607
+ const cmuxPath = await getCmuxPath();
69608
+ if (cmuxPath) {
69609
+ try {
69610
+ await runNotificationCommand(ctx, cmuxPath, ["notify", "--title", title, "--body", message], (shell) => shell`${cmuxPath} notify --title ${title} --body ${message}`, "throw");
69611
+ break;
69612
+ } catch (error) {
69613
+ if (error instanceof Error) {
69614
+ logCommandFailure("cmux", error);
69615
+ } else {
69616
+ logCommandFailure("cmux", String(error));
69617
+ }
69499
69618
  }
69500
- break;
69501
- } catch {}
69619
+ }
69620
+ const terminalNotifierPath = await getTerminalNotifierPath();
69621
+ if (terminalNotifierPath) {
69622
+ const bundleId = process.env.__CFBundleIdentifier;
69623
+ const args = bundleId ? ["-title", title, "-message", message, "-activate", bundleId] : ["-title", title, "-message", message];
69624
+ try {
69625
+ await runNotificationCommand(ctx, terminalNotifierPath, args, (shell) => bundleId ? shell`${terminalNotifierPath} -title ${title} -message ${message} -activate ${bundleId}` : shell`${terminalNotifierPath} -title ${title} -message ${message}`, "throw");
69626
+ break;
69627
+ } catch (error) {
69628
+ if (error instanceof Error) {
69629
+ logCommandFailure("terminal-notifier", error);
69630
+ } else {
69631
+ logCommandFailure("terminal-notifier", String(error));
69632
+ }
69633
+ }
69634
+ }
69635
+ const osascriptPath = await getOsascriptPath();
69636
+ if (!osascriptPath)
69637
+ return;
69638
+ const escapedTitle = escapeAppleScriptText(title);
69639
+ const escapedMessage = escapeAppleScriptText(message);
69640
+ const appleScript = 'display notification "' + escapedMessage + '" with title "' + escapedTitle + '"';
69641
+ await runNotificationCommand(ctx, osascriptPath, ["-e", appleScript], (shell) => shell`${osascriptPath} -e ${appleScript}`);
69642
+ break;
69643
+ }
69644
+ case "linux": {
69645
+ const notifySendPath = await getNotifySendPath();
69646
+ if (!notifySendPath)
69647
+ return;
69648
+ await runNotificationCommand(ctx, notifySendPath, [title, message], (shell) => shell`${notifySendPath} ${title} ${message} 2>/dev/null`);
69649
+ break;
69650
+ }
69651
+ case "win32": {
69652
+ const powershellPath = await getPowershellPath();
69653
+ if (!powershellPath)
69654
+ return;
69655
+ const toastScript = buildWindowsToastScript(title, message);
69656
+ await runNotificationCommand(ctx, powershellPath, ["-Command", toastScript], (shell) => shell`${powershellPath} -Command ${toastScript}`);
69657
+ break;
69502
69658
  }
69503
- const osascriptPath = await getOsascriptPath();
69504
- if (!osascriptPath)
69505
- return;
69506
- const escapedTitle = escapeAppleScriptText(title);
69507
- const escapedMessage = escapeAppleScriptText(message);
69508
- await runQuietNothrow(ctx.$`${osascriptPath} -e ${'display notification "' + escapedMessage + '" with title "' + escapedTitle + '"'}`);
69509
- break;
69510
- }
69511
- case "linux": {
69512
- const notifySendPath = await getNotifySendPath();
69513
- if (!notifySendPath)
69514
- return;
69515
- await runQuietNothrow(ctx.$`${notifySendPath} ${title} ${message} 2>/dev/null`);
69516
- break;
69517
69659
  }
69518
- case "win32": {
69519
- const powershellPath = await getPowershellPath();
69520
- if (!powershellPath)
69521
- return;
69522
- const toastScript = buildWindowsToastScript(title, message);
69523
- await runQuietNothrow(ctx.$`${powershellPath} -Command ${toastScript}`);
69524
- break;
69660
+ } catch (error) {
69661
+ if (error instanceof Error) {
69662
+ logOperationFailure("send", error);
69663
+ } else {
69664
+ logOperationFailure("send", String(error));
69525
69665
  }
69526
69666
  }
69527
69667
  }
69528
69668
  async function playSessionNotificationSound(ctx, platform2, soundPath) {
69529
- if (!canRunNotificationCommand(ctx))
69530
- return;
69531
- switch (platform2) {
69532
- case "darwin": {
69533
- const afplayPath = await getAfplayPath();
69534
- if (!afplayPath)
69535
- return;
69536
- await runQuietNothrow(ctx.$`${afplayPath} ${soundPath}`);
69537
- break;
69538
- }
69539
- case "linux": {
69540
- const paplayPath = await getPaplayPath();
69541
- if (paplayPath) {
69542
- await runQuietNothrow(ctx.$`${paplayPath} ${soundPath} 2>/dev/null`);
69543
- } else {
69544
- const aplayPath = await getAplayPath();
69545
- if (aplayPath) {
69546
- await runQuietNothrow(ctx.$`${aplayPath} ${soundPath} 2>/dev/null`);
69669
+ try {
69670
+ switch (platform2) {
69671
+ case "darwin": {
69672
+ const afplayPath = await getAfplayPath();
69673
+ if (!afplayPath)
69674
+ return;
69675
+ await runNotificationCommand(ctx, afplayPath, [soundPath], (shell) => shell`${afplayPath} ${soundPath}`);
69676
+ break;
69677
+ }
69678
+ case "linux": {
69679
+ const paplayPath = await getPaplayPath();
69680
+ if (paplayPath) {
69681
+ await runNotificationCommand(ctx, paplayPath, [soundPath], (shell) => shell`${paplayPath} ${soundPath} 2>/dev/null`);
69682
+ } else {
69683
+ const aplayPath = await getAplayPath();
69684
+ if (aplayPath) {
69685
+ await runNotificationCommand(ctx, aplayPath, [soundPath], (shell) => shell`${aplayPath} ${soundPath} 2>/dev/null`);
69686
+ }
69547
69687
  }
69688
+ break;
69689
+ }
69690
+ case "win32": {
69691
+ const powershellPath = await getPowershellPath();
69692
+ if (!powershellPath)
69693
+ return;
69694
+ const escaped = escapePowerShellSingleQuotedText(soundPath);
69695
+ const soundScript = "(New-Object Media.SoundPlayer '" + escaped + "').PlaySync()";
69696
+ await runNotificationCommand(ctx, powershellPath, ["-Command", soundScript], (shell) => shell`${powershellPath} -Command ${soundScript}`);
69697
+ break;
69548
69698
  }
69549
- break;
69550
69699
  }
69551
- case "win32": {
69552
- const powershellPath = await getPowershellPath();
69553
- if (!powershellPath)
69554
- return;
69555
- const escaped = escapePowerShellSingleQuotedText(soundPath);
69556
- await runQuietNothrow(ctx.$`${powershellPath} -Command ${"(New-Object Media.SoundPlayer '" + escaped + "').PlaySync()"}`);
69557
- break;
69700
+ } catch (error) {
69701
+ if (error instanceof Error) {
69702
+ logOperationFailure("sound", error);
69703
+ } else {
69704
+ logOperationFailure("sound", String(error));
69558
69705
  }
69559
69706
  }
69560
69707
  }
@@ -71625,6 +71772,27 @@ async function runCommentChecker2(input, cliPath, customPrompt) {
71625
71772
  // src/hooks/comment-checker/cli-runner.ts
71626
71773
  var cliPathPromise = null;
71627
71774
  var isRunning = false;
71775
+ var sessionLastWarning = new Map;
71776
+ var DEDUP_WINDOW_MS = 30000;
71777
+ function hasCommentSyntax(text) {
71778
+ if (!text)
71779
+ return false;
71780
+ return /^\s*(\/\/|\/\*|#|--|<!--|:\s*)[\s\S]*$/m.test(text) || /<!--[\s\S]*-->/.test(text);
71781
+ }
71782
+ function hasNewCommentsOnly(oldText, newText) {
71783
+ if (!hasCommentSyntax(newText))
71784
+ return false;
71785
+ if (!hasCommentSyntax(oldText))
71786
+ return true;
71787
+ const oldLines = new Set((oldText ?? "").split(`
71788
+ `).map((l) => l.trim()));
71789
+ const newLines = (newText ?? "").split(`
71790
+ `);
71791
+ return newLines.some((l) => {
71792
+ const trimmed = l.trim();
71793
+ return trimmed && hasCommentSyntax(trimmed) && !oldLines.has(trimmed);
71794
+ });
71795
+ }
71628
71796
  async function withCommentCheckerLock(fn, fallback, debugLog3) {
71629
71797
  if (isRunning) {
71630
71798
  debugLog3("comment-checker already running, skipping");
@@ -71666,6 +71834,17 @@ async function processWithCli(input, pendingCall, output, cliPath, customPrompt,
71666
71834
  edits: pendingCall.edits
71667
71835
  }
71668
71836
  };
71837
+ if (!hasNewCommentsOnly(pendingCall.oldString, pendingCall.newString)) {
71838
+ debugLog3("skipping: no net-new comments in edit (oldString/newString)");
71839
+ return;
71840
+ }
71841
+ const lastWarned = sessionLastWarning.get(pendingCall.sessionID) ?? 0;
71842
+ const now = Date.now();
71843
+ if (now - lastWarned < DEDUP_WINDOW_MS) {
71844
+ debugLog3("dedup: skipping comment warning within dedup window for session", pendingCall.sessionID);
71845
+ return;
71846
+ }
71847
+ sessionLastWarning.set(pendingCall.sessionID, now);
71669
71848
  const result = await (deps.runCommentChecker ?? runCommentChecker2)(hookInput, cliPath, customPrompt);
71670
71849
  if (result.hasComments && result.message) {
71671
71850
  debugLog3("CLI detected comments, appending message");
@@ -100037,11 +100216,17 @@ var cachedCli = null;
100037
100216
  var autoInstallAttempted = false;
100038
100217
  function findExecutable(name) {
100039
100218
  const isWindows2 = process.platform === "win32";
100040
- const cmd = isWindows2 ? "where" : "which";
100219
+ const cmd = isWindows2 ? "where.exe" : "which";
100041
100220
  try {
100042
- const result = spawnSync2(cmd, [name], { encoding: "utf-8", timeout: 5000 });
100043
- if (result.status === 0 && result.stdout.trim()) {
100044
- return result.stdout.trim().split(`
100221
+ const result = spawnSync2(cmd, [name], {
100222
+ encoding: "utf-8",
100223
+ timeout: 5000,
100224
+ windowsHide: isWindows2,
100225
+ shell: false
100226
+ });
100227
+ const stdout = result.stdout;
100228
+ if (result.status === 0 && stdout.trim()) {
100229
+ return stdout.trim().split(`
100045
100230
  `)[0];
100046
100231
  }
100047
100232
  } catch {
@@ -100163,6 +100348,29 @@ class Semaphore {
100163
100348
  }
100164
100349
  var rgSemaphore = new Semaphore(2);
100165
100350
 
100351
+ // src/tools/shared/search-process-output.ts
100352
+ function getErrorMessage5(error) {
100353
+ return error instanceof Error ? error.message : String(error);
100354
+ }
100355
+ function createProcessTimeout(proc, timeoutMs, timeoutMessage) {
100356
+ return new Promise((_, reject) => {
100357
+ const id = setTimeout(() => {
100358
+ proc.kill();
100359
+ reject(new Error(timeoutMessage));
100360
+ }, timeoutMs);
100361
+ proc.exited.then(() => clearTimeout(id), () => clearTimeout(id));
100362
+ });
100363
+ }
100364
+ async function collectSearchProcessOutput(proc, timeoutMs, timeoutMessage) {
100365
+ const stderrPromise = readProcessStream(proc.stderr).catch(getErrorMessage5);
100366
+ const stdout = await Promise.race([
100367
+ readProcessStream(proc.stdout),
100368
+ createProcessTimeout(proc, timeoutMs, timeoutMessage)
100369
+ ]);
100370
+ const [exitCode, stderr] = await Promise.all([proc.exited, stderrPromise]);
100371
+ return { stdout, stderr, exitCode };
100372
+ }
100373
+
100166
100374
  // src/tools/grep/cli.ts
100167
100375
  function buildRgArgs(options) {
100168
100376
  const args = [
@@ -100286,15 +100494,15 @@ function parseCountOutput(output) {
100286
100494
  }
100287
100495
  return results;
100288
100496
  }
100289
- async function runRg(options, resolvedCli) {
100497
+ async function runRg(options, resolvedCli, processSpawner = spawn2) {
100290
100498
  await rgSemaphore.acquire();
100291
100499
  try {
100292
- return await runRgInternal(options, resolvedCli);
100500
+ return await runRgInternal(options, resolvedCli, processSpawner);
100293
100501
  } finally {
100294
100502
  rgSemaphore.release();
100295
100503
  }
100296
100504
  }
100297
- async function runRgInternal(options, resolvedCli) {
100505
+ async function runRgInternal(options, resolvedCli, processSpawner = spawn2) {
100298
100506
  const cli = resolvedCli ?? resolveGrepCli();
100299
100507
  const args = buildArgs(options, cli.backend);
100300
100508
  const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS2, DEFAULT_TIMEOUT_MS2);
@@ -100305,21 +100513,12 @@ async function runRgInternal(options, resolvedCli) {
100305
100513
  }
100306
100514
  const paths = options.paths?.length ? options.paths : ["."];
100307
100515
  args.push(...paths);
100308
- const proc = spawn2([cli.path, ...args], {
100309
- stdout: "pipe",
100310
- stderr: "pipe"
100311
- });
100312
- const timeoutPromise = new Promise((_, reject) => {
100313
- const id = setTimeout(() => {
100314
- proc.kill();
100315
- reject(new Error(`Search timeout after ${timeout}ms`));
100316
- }, timeout);
100317
- proc.exited.then(() => clearTimeout(id));
100318
- });
100319
100516
  try {
100320
- const stdout = await Promise.race([new Response(proc.stdout).text(), timeoutPromise]);
100321
- const stderr = await new Response(proc.stderr).text();
100322
- const exitCode = await proc.exited;
100517
+ const proc = processSpawner([cli.path, ...args], {
100518
+ stdout: "pipe",
100519
+ stderr: "pipe"
100520
+ });
100521
+ const { stdout, stderr, exitCode } = await collectSearchProcessOutput(proc, timeout, `Search timeout after ${timeout}ms`);
100323
100522
  const truncated = stdout.length >= DEFAULT_MAX_OUTPUT_BYTES;
100324
100523
  const outputToProcess = truncated ? stdout.substring(0, DEFAULT_MAX_OUTPUT_BYTES) : stdout;
100325
100524
  if (exitCode > 1 && stderr.trim()) {
@@ -100350,15 +100549,15 @@ async function runRgInternal(options, resolvedCli) {
100350
100549
  };
100351
100550
  }
100352
100551
  }
100353
- async function runRgCount(options, resolvedCli) {
100552
+ async function runRgCount(options, resolvedCli, processSpawner = spawn2) {
100354
100553
  await rgSemaphore.acquire();
100355
100554
  try {
100356
- return await runRgCountInternal(options, resolvedCli);
100555
+ return await runRgCountInternal(options, resolvedCli, processSpawner);
100357
100556
  } finally {
100358
100557
  rgSemaphore.release();
100359
100558
  }
100360
100559
  }
100361
- async function runRgCountInternal(options, resolvedCli) {
100560
+ async function runRgCountInternal(options, resolvedCli, processSpawner = spawn2) {
100362
100561
  const cli = resolvedCli ?? resolveGrepCli();
100363
100562
  const args = buildArgs({ ...options, context: 0 }, cli.backend);
100364
100563
  if (cli.backend === "rg") {
@@ -100369,19 +100568,15 @@ async function runRgCountInternal(options, resolvedCli) {
100369
100568
  const paths = options.paths?.length ? options.paths : ["."];
100370
100569
  args.push(...paths);
100371
100570
  const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS2, DEFAULT_TIMEOUT_MS2);
100372
- const proc = spawn2([cli.path, ...args], {
100373
- stdout: "pipe",
100374
- stderr: "pipe"
100375
- });
100376
- const timeoutPromise = new Promise((_, reject) => {
100377
- const id = setTimeout(() => {
100378
- proc.kill();
100379
- reject(new Error(`Search timeout after ${timeout}ms`));
100380
- }, timeout);
100381
- proc.exited.then(() => clearTimeout(id));
100382
- });
100383
100571
  try {
100384
- const stdout = await Promise.race([new Response(proc.stdout).text(), timeoutPromise]);
100572
+ const proc = processSpawner([cli.path, ...args], {
100573
+ stdout: "pipe",
100574
+ stderr: "pipe"
100575
+ });
100576
+ const { stdout, stderr, exitCode } = await collectSearchProcessOutput(proc, timeout, `Search timeout after ${timeout}ms`);
100577
+ if (exitCode > 1 && stderr.trim()) {
100578
+ throw new Error(stderr.trim());
100579
+ }
100385
100580
  return parseCountOutput(stdout);
100386
100581
  } catch (e) {
100387
100582
  throw new Error(`Count search failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -100542,12 +100737,12 @@ function buildPowerShellCommand(options) {
100542
100737
  const searchPath = paths[0] || ".";
100543
100738
  const escapedPath = searchPath.replace(/'/g, "''");
100544
100739
  const escapedPattern = options.pattern.replace(/'/g, "''");
100545
- let psCommand = `Get-ChildItem -Path '${escapedPath}' -File -Recurse -Depth ${maxDepth - 1} -Filter '${escapedPattern}'`;
100740
+ let psCommand = `Get-ChildItem -LiteralPath '${escapedPath}' -File -Recurse -Depth ${maxDepth - 1} -Filter '${escapedPattern}'`;
100546
100741
  if (options.hidden !== false) {
100547
100742
  psCommand += " -Force";
100548
100743
  }
100549
100744
  psCommand += " -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName";
100550
- return ["powershell", "-NoProfile", "-Command", psCommand];
100745
+ return ["powershell.exe", "-NoProfile", "-Command", psCommand];
100551
100746
  }
100552
100747
  async function getFileMtime(filePath) {
100553
100748
  try {
@@ -100557,15 +100752,15 @@ async function getFileMtime(filePath) {
100557
100752
  return 0;
100558
100753
  }
100559
100754
  }
100560
- async function runRgFiles(options, resolvedCli) {
100755
+ async function runRgFiles(options, resolvedCli, processSpawner = spawn2) {
100561
100756
  await rgSemaphore.acquire();
100562
100757
  try {
100563
- return await runRgFilesInternal(options, resolvedCli);
100758
+ return await runRgFilesInternal(options, resolvedCli, processSpawner);
100564
100759
  } finally {
100565
100760
  rgSemaphore.release();
100566
100761
  }
100567
100762
  }
100568
- async function runRgFilesInternal(options, resolvedCli) {
100763
+ async function runRgFilesInternal(options, resolvedCli, processSpawner = spawn2) {
100569
100764
  const cli = resolvedCli ?? resolveGrepCli();
100570
100765
  const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS3, DEFAULT_TIMEOUT_MS3);
100571
100766
  const limit = Math.min(options.limit ?? DEFAULT_LIMIT, DEFAULT_LIMIT);
@@ -100587,22 +100782,13 @@ async function runRgFilesInternal(options, resolvedCli) {
100587
100782
  cwd = paths[0] || ".";
100588
100783
  command = [cli.path, ...args];
100589
100784
  }
100590
- const proc = spawn2(command, {
100591
- stdout: "pipe",
100592
- stderr: "pipe",
100593
- cwd
100594
- });
100595
- const timeoutPromise = new Promise((_, reject) => {
100596
- const id = setTimeout(() => {
100597
- proc.kill();
100598
- reject(new Error(`Glob search timeout after ${timeout}ms`));
100599
- }, timeout);
100600
- proc.exited.then(() => clearTimeout(id));
100601
- });
100602
100785
  try {
100603
- const stdout = await Promise.race([new Response(proc.stdout).text(), timeoutPromise]);
100604
- const stderr = await new Response(proc.stderr).text();
100605
- const exitCode = await proc.exited;
100786
+ const proc = processSpawner(command, {
100787
+ stdout: "pipe",
100788
+ stderr: "pipe",
100789
+ cwd
100790
+ });
100791
+ const { stdout, stderr, exitCode } = await collectSearchProcessOutput(proc, timeout, `Glob search timeout after ${timeout}ms`);
100606
100792
  if (exitCode > 1 && stderr.trim()) {
100607
100793
  return {
100608
100794
  files: [],
@@ -102373,7 +102559,7 @@ init_opencode_message_dir();
102373
102559
  init_logger();
102374
102560
 
102375
102561
  // src/tools/background-task/session-messages.ts
102376
- function getErrorMessage5(value) {
102562
+ function getErrorMessage6(value) {
102377
102563
  if (Array.isArray(value))
102378
102564
  return null;
102379
102565
  if (value.error === undefined || value.error === null)
@@ -102556,7 +102742,7 @@ async function formatFullSession(task, client, options) {
102556
102742
  } catch (error) {
102557
102743
  return `Error fetching messages: ${error instanceof Error ? error.message : String(error)}`;
102558
102744
  }
102559
- const errorMessage = getErrorMessage5(messagesResult);
102745
+ const errorMessage = getErrorMessage6(messagesResult);
102560
102746
  if (errorMessage) {
102561
102747
  return `Error fetching messages: ${errorMessage}`;
102562
102748
  }
@@ -102762,7 +102948,7 @@ async function formatTaskResult(task, client) {
102762
102948
  } catch (error) {
102763
102949
  return `Error fetching messages: ${error instanceof Error ? error.message : String(error)}`;
102764
102950
  }
102765
- const errorMessage = getErrorMessage5(messagesResult);
102951
+ const errorMessage = getErrorMessage6(messagesResult);
102766
102952
  if (errorMessage) {
102767
102953
  return `Error fetching messages: ${errorMessage}`;
102768
102954
  }
@@ -110633,6 +110819,11 @@ class ParentWakeNotifier {
110633
110819
  throw promptResult.error;
110634
110820
  }
110635
110821
  if (promptResult.status === "reserved" && promptResult.reservedBy === "background-agent-parent-wake") {
110822
+ const dispatchedWake = this.dispatchedParentWakes.get(sessionID);
110823
+ if (dispatchedWake && this.isSameParentWake(latestWake, dispatchedWake)) {
110824
+ log("[background-agent] Suppressed duplicate parent wake during promptAsync gate hold:", { sessionID });
110825
+ return;
110826
+ }
110636
110827
  this.requeueWake(sessionID, latestWake);
110637
110828
  this.schedulePendingParentWakeFlush(sessionID, 2000);
110638
110829
  log("[background-agent] Requeued parent wake flush reserved by promptAsync gate hold:", { sessionID });
@@ -110769,6 +110960,9 @@ class ParentWakeNotifier {
110769
110960
  unrefTimerHandle(timer);
110770
110961
  this.dispatchedParentWakeTimers.set(sessionID, timer);
110771
110962
  }
110963
+ isSameParentWake(left, right) {
110964
+ return left.shouldReply === right.shouldReply && JSON.stringify(left.notifications) === JSON.stringify(right.notifications) && JSON.stringify(left.promptContext) === JSON.stringify(right.promptContext);
110965
+ }
110772
110966
  async loadParentWakeSessionMessages(sessionID) {
110773
110967
  try {
110774
110968
  const messagesResp = await messagesInDirectory(this.deps.client, {
@@ -110993,9 +111187,13 @@ function describeProcessCleanupError(error) {
110993
111187
  return { raw: String(error) };
110994
111188
  }
110995
111189
  function registerErrorEvent(signal) {
111190
+ let logging = false;
110996
111191
  const listener = (error) => {
110997
- process.off(signal, listener);
111192
+ if (logging)
111193
+ return;
111194
+ logging = true;
110998
111195
  log(`[background-agent] ${signal} observed; keeping host alive and skipping cleanup (signal handlers run on real shutdown)`, describeProcessCleanupError(error));
111196
+ logging = false;
110999
111197
  };
111000
111198
  process.on(signal, listener);
111001
111199
  return listener;
@@ -139870,7 +140068,7 @@ function createEventHandler2(args) {
139870
140068
  const recentSyntheticIdles = new Map;
139871
140069
  const recentRealIdles = new Map;
139872
140070
  const recentAnyIdles = new Map;
139873
- const DEDUP_WINDOW_MS = 500;
140071
+ const DEDUP_WINDOW_MS2 = 500;
139874
140072
  const teamModeConfig = pluginConfig.team_mode?.enabled ? pluginConfig.team_mode : undefined;
139875
140073
  const teamLeadOrphanHandler = teamModeConfig ? createTeamLeadOrphanHandler(teamModeConfig, managers.tmuxSessionManager, managers.backgroundManager) : undefined;
139876
140074
  const teamMemberErrorHandler = teamModeConfig ? createTeamMemberErrorHandler(teamModeConfig, { client: pluginContext.client }) : undefined;
@@ -139896,7 +140094,7 @@ function createEventHandler2(args) {
139896
140094
  };
139897
140095
  const shouldDispatchIdleEvent = (sessionID, now) => {
139898
140096
  const lastDispatchedAt = recentAnyIdles.get(sessionID);
139899
- if (lastDispatchedAt !== undefined && now - lastDispatchedAt < DEDUP_WINDOW_MS) {
140097
+ if (lastDispatchedAt !== undefined && now - lastDispatchedAt < DEDUP_WINDOW_MS2) {
139900
140098
  return false;
139901
140099
  }
139902
140100
  recentAnyIdles.set(sessionID, now);
@@ -140059,7 +140257,7 @@ function createEventHandler2(args) {
140059
140257
  recentRealIdles,
140060
140258
  recentAnyIdles,
140061
140259
  now: Date.now(),
140062
- dedupWindowMs: DEDUP_WINDOW_MS
140260
+ dedupWindowMs: DEDUP_WINDOW_MS2
140063
140261
  });
140064
140262
  const syntheticIdle = normalizeSessionStatusToIdle(input);
140065
140263
  if (input.event.type === "session.idle") {
@@ -140067,7 +140265,7 @@ function createEventHandler2(args) {
140067
140265
  if (sessionID) {
140068
140266
  const now = Date.now();
140069
140267
  const emittedAt = recentSyntheticIdles.get(sessionID);
140070
- if (emittedAt !== undefined && now - emittedAt < DEDUP_WINDOW_MS) {
140268
+ if (emittedAt !== undefined && now - emittedAt < DEDUP_WINDOW_MS2) {
140071
140269
  recentSyntheticIdles.delete(sessionID);
140072
140270
  const lastAnyIdleAt = recentAnyIdles.get(sessionID);
140073
140271
  if (lastAnyIdleAt === emittedAt) {
@@ -140097,7 +140295,7 @@ function createEventHandler2(args) {
140097
140295
  const sessionID = syntheticIdle.event.properties?.sessionID;
140098
140296
  const now = Date.now();
140099
140297
  const emittedAt = recentRealIdles.get(sessionID);
140100
- if (emittedAt !== undefined && now - emittedAt < DEDUP_WINDOW_MS) {
140298
+ if (emittedAt !== undefined && now - emittedAt < DEDUP_WINDOW_MS2) {
140101
140299
  recentRealIdles.delete(sessionID);
140102
140300
  return;
140103
140301
  }
@@ -1,3 +1,4 @@
1
+ import { type SpawnOptions as NodeSpawnOptions, type SpawnSyncOptions as NodeSpawnSyncOptions } from "node:child_process";
1
2
  type StdioMode = "pipe" | "inherit" | "ignore";
2
3
  type StdioTuple = [StdioMode, StdioMode, StdioMode];
3
4
  export interface SpawnOptions {
@@ -29,6 +30,8 @@ export interface SpawnSyncResult {
29
30
  readonly success: boolean;
30
31
  readonly pid: number;
31
32
  }
33
+ export declare function createNodeSpawnOptions(options: SpawnOptions, platform?: NodeJS.Platform): NodeSpawnOptions;
34
+ export declare function createNodeSpawnSyncOptions(options: SpawnOptions, platform?: NodeJS.Platform): NodeSpawnSyncOptions;
32
35
  export declare function spawn(command: string[], options?: SpawnOptions): SpawnedProcess;
33
36
  export declare function spawn(options: SpawnOptions & {
34
37
  cmd: string[];
@@ -0,0 +1,3 @@
1
+ import { Readable } from "node:stream";
2
+ export type ProcessReadableStream = ReadableStream<Uint8Array> | Readable | null | undefined;
3
+ export declare function readProcessStream(stream: ProcessReadableStream): Promise<string>;
@@ -1,11 +1,13 @@
1
+ import { type SpawnOptions, type SpawnedProcess } from "../../shared/bun-spawn-shim";
1
2
  import { type GrepBackend } from "./constants";
2
3
  import type { GlobOptions, GlobResult } from "./types";
3
4
  export interface ResolvedCli {
4
5
  path: string;
5
6
  backend: GrepBackend;
6
7
  }
8
+ export type SearchProcessSpawner = (command: string[], options?: SpawnOptions) => SpawnedProcess;
7
9
  declare function buildRgArgs(options: GlobOptions): string[];
8
10
  declare function buildFindArgs(options: GlobOptions): string[];
9
11
  declare function buildPowerShellCommand(options: GlobOptions): string[];
10
12
  export { buildRgArgs, buildFindArgs, buildPowerShellCommand };
11
- export declare function runRgFiles(options: GlobOptions, resolvedCli?: ResolvedCli): Promise<GlobResult>;
13
+ export declare function runRgFiles(options: GlobOptions, resolvedCli?: ResolvedCli, processSpawner?: SearchProcessSpawner): Promise<GlobResult>;
@@ -1,4 +1,6 @@
1
+ import { type SpawnOptions, type SpawnedProcess } from "../../shared/bun-spawn-shim";
1
2
  import { type ResolvedCli } from "../../shared/ripgrep-cli";
2
3
  import type { GrepOptions, GrepResult, CountResult } from "./types";
3
- export declare function runRg(options: GrepOptions, resolvedCli?: ResolvedCli): Promise<GrepResult>;
4
- export declare function runRgCount(options: Omit<GrepOptions, "context">, resolvedCli?: ResolvedCli): Promise<CountResult[]>;
4
+ export type SearchProcessSpawner = (command: string[], options?: SpawnOptions) => SpawnedProcess;
5
+ export declare function runRg(options: GrepOptions, resolvedCli?: ResolvedCli, processSpawner?: SearchProcessSpawner): Promise<GrepResult>;
6
+ export declare function runRgCount(options: Omit<GrepOptions, "context">, resolvedCli?: ResolvedCli, processSpawner?: SearchProcessSpawner): Promise<CountResult[]>;
@@ -0,0 +1,7 @@
1
+ import type { SpawnedProcess } from "../../shared/bun-spawn-shim";
2
+ export interface SearchProcessOutput {
3
+ readonly stdout: string;
4
+ readonly stderr: string;
5
+ readonly exitCode: number;
6
+ }
7
+ export declare function collectSearchProcessOutput(proc: SpawnedProcess, timeoutMs: number, timeoutMessage: string): Promise<SearchProcessOutput>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode",
3
- "version": "4.3.0",
3
+ "version": "4.3.1",
4
4
  "description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
5
5
  "main": "./dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -110,17 +110,17 @@
110
110
  "zod": "^4.4.3"
111
111
  },
112
112
  "optionalDependencies": {
113
- "oh-my-opencode-darwin-arm64": "4.3.0",
114
- "oh-my-opencode-darwin-x64": "4.3.0",
115
- "oh-my-opencode-darwin-x64-baseline": "4.3.0",
116
- "oh-my-opencode-linux-arm64": "4.3.0",
117
- "oh-my-opencode-linux-arm64-musl": "4.3.0",
118
- "oh-my-opencode-linux-x64": "4.3.0",
119
- "oh-my-opencode-linux-x64-baseline": "4.3.0",
120
- "oh-my-opencode-linux-x64-musl": "4.3.0",
121
- "oh-my-opencode-linux-x64-musl-baseline": "4.3.0",
122
- "oh-my-opencode-windows-x64": "4.3.0",
123
- "oh-my-opencode-windows-x64-baseline": "4.3.0"
113
+ "oh-my-opencode-darwin-arm64": "4.3.1",
114
+ "oh-my-opencode-darwin-x64": "4.3.1",
115
+ "oh-my-opencode-darwin-x64-baseline": "4.3.1",
116
+ "oh-my-opencode-linux-arm64": "4.3.1",
117
+ "oh-my-opencode-linux-arm64-musl": "4.3.1",
118
+ "oh-my-opencode-linux-x64": "4.3.1",
119
+ "oh-my-opencode-linux-x64-baseline": "4.3.1",
120
+ "oh-my-opencode-linux-x64-musl": "4.3.1",
121
+ "oh-my-opencode-linux-x64-musl-baseline": "4.3.1",
122
+ "oh-my-opencode-windows-x64": "4.3.1",
123
+ "oh-my-opencode-windows-x64-baseline": "4.3.1"
124
124
  },
125
125
  "overrides": {
126
126
  "hono": "^4.12.18",