evil-omo 3.11.7 → 3.12.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
@@ -982,10 +982,10 @@ function detectManagedConfigFile(directory) {
982
982
  baseName: CONFIG_BASENAME
983
983
  };
984
984
  }
985
- var PLUGIN_NAME = "evil-omo", ALL_PLUGIN_NAMES, CONFIG_BASENAME = "evil-omo", ALL_CONFIG_BASENAMES, LOG_FILENAME = "evil-omo.log", CACHE_DIR_NAME = "evil-omo", SCHEMA_FILENAME = "evil-omo.schema.json";
985
+ var PLUGIN_NAME = "evil-omo", LEGACY_PLUGIN_NAME = "oh-my-opencode", ALL_PLUGIN_NAMES, CONFIG_BASENAME = "evil-omo", ALL_CONFIG_BASENAMES, LOG_FILENAME = "evil-omo.log", CACHE_DIR_NAME = "evil-omo", SCHEMA_FILENAME = "evil-omo.schema.json";
986
986
  var init_plugin_identity = __esm(() => {
987
987
  init_jsonc_parser();
988
- ALL_PLUGIN_NAMES = [PLUGIN_NAME];
988
+ ALL_PLUGIN_NAMES = [PLUGIN_NAME, LEGACY_PLUGIN_NAME];
989
989
  ALL_CONFIG_BASENAMES = [CONFIG_BASENAME];
990
990
  });
991
991
 
@@ -17575,6 +17575,9 @@ function buildEnvPrefix(env, shellType) {
17575
17575
  return "";
17576
17576
  }
17577
17577
  }
17578
+ function shellEscapeForDoubleQuotedCommand(value) {
17579
+ return value.replace(/\\/g, "\\\\").replace(/\$/g, "\\$").replace(/`/g, "\\`").replace(/"/g, "\\\"").replace(/;/g, "\\;").replace(/\|/g, "\\|").replace(/&/g, "\\&").replace(/#/g, "\\#").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
17580
+ }
17578
17581
  // src/shared/system-directive.ts
17579
17582
  var SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: OMO-PLUGIN";
17580
17583
  function createSystemDirective(type2) {
@@ -18593,7 +18596,8 @@ async function spawnTmuxPane(sessionId, description, config, serverUrl, targetPa
18593
18596
  }
18594
18597
  log2("[spawnTmuxPane] all checks passed, spawning...");
18595
18598
  const shell = process.env.SHELL || "/bin/sh";
18596
- const opencodeCmd = `${shell} -c 'opencode attach ${serverUrl} --session ${sessionId}'`;
18599
+ const escapedUrl = shellEscapeForDoubleQuotedCommand(serverUrl);
18600
+ const opencodeCmd = `${shell} -c "opencode attach ${escapedUrl} --session ${sessionId}"`;
18597
18601
  const args = [
18598
18602
  "split-window",
18599
18603
  splitDirection,
@@ -18688,7 +18692,8 @@ async function replaceTmuxPane(paneId, sessionId, description, config, serverUrl
18688
18692
  });
18689
18693
  await ctrlCProc.exited;
18690
18694
  const shell = process.env.SHELL || "/bin/sh";
18691
- const opencodeCmd = `${shell} -c 'opencode attach ${serverUrl} --session ${sessionId}'`;
18695
+ const escapedUrl = shellEscapeForDoubleQuotedCommand(serverUrl);
18696
+ const opencodeCmd = `${shell} -c "opencode attach ${escapedUrl} --session ${sessionId}"`;
18692
18697
  const proc = spawn7([tmux, "respawn-pane", "-k", "-t", paneId, opencodeCmd], {
18693
18698
  stdout: "pipe",
18694
18699
  stderr: "pipe"
@@ -18710,6 +18715,7 @@ async function replaceTmuxPane(paneId, sessionId, description, config, serverUrl
18710
18715
  const titleStderr = await stderrPromise;
18711
18716
  log2("[replaceTmuxPane] WARNING: failed to set pane title", {
18712
18717
  paneId,
18718
+ title,
18713
18719
  exitCode: titleExitCode,
18714
18720
  stderr: titleStderr.trim()
18715
18721
  });
@@ -20754,17 +20760,8 @@ function createSessionStateStore() {
20754
20760
  };
20755
20761
  const trackedSession = {
20756
20762
  state: rawState,
20757
- lastAccessedAt: Date.now(),
20758
- activitySignalCount: 0
20763
+ lastAccessedAt: Date.now()
20759
20764
  };
20760
- trackedSession.state = new Proxy(rawState, {
20761
- set(target, property, value, receiver) {
20762
- if (property === "abortDetectedAt" && value === undefined) {
20763
- trackedSession.activitySignalCount += 1;
20764
- }
20765
- return Reflect.set(target, property, value, receiver);
20766
- }
20767
- });
20768
20765
  sessions.set(sessionID, trackedSession);
20769
20766
  return trackedSession;
20770
20767
  }
@@ -20786,10 +20783,8 @@ function createSessionStateStore() {
20786
20783
  const previousStagnationCount = state2.stagnationCount;
20787
20784
  const currentCompletedCount = todos?.filter((todo) => todo.status === "completed").length;
20788
20785
  const currentTodoSnapshot = todos ? getTodoSnapshot(todos) : undefined;
20789
- const currentActivitySignalCount = trackedSession.activitySignalCount;
20790
20786
  const hasCompletedMoreTodos = currentCompletedCount !== undefined && trackedSession.lastCompletedCount !== undefined && currentCompletedCount > trackedSession.lastCompletedCount;
20791
20787
  const hasTodoSnapshotChanged = currentTodoSnapshot !== undefined && trackedSession.lastTodoSnapshot !== undefined && currentTodoSnapshot !== trackedSession.lastTodoSnapshot;
20792
- const hasObservedExternalActivity = trackedSession.lastObservedActivitySignalCount !== undefined && currentActivitySignalCount > trackedSession.lastObservedActivitySignalCount;
20793
20788
  const hadSuccessfulInjectionAwaitingProgressCheck = state2.awaitingPostInjectionProgressCheck === true;
20794
20789
  state2.lastIncompleteCount = incompleteCount;
20795
20790
  if (currentCompletedCount !== undefined) {
@@ -20798,7 +20793,6 @@ function createSessionStateStore() {
20798
20793
  if (currentTodoSnapshot !== undefined) {
20799
20794
  trackedSession.lastTodoSnapshot = currentTodoSnapshot;
20800
20795
  }
20801
- trackedSession.lastObservedActivitySignalCount = currentActivitySignalCount;
20802
20796
  if (previousIncompleteCount === undefined) {
20803
20797
  state2.stagnationCount = 0;
20804
20798
  return {
@@ -20809,7 +20803,7 @@ function createSessionStateStore() {
20809
20803
  progressSource: "none"
20810
20804
  };
20811
20805
  }
20812
- const progressSource = incompleteCount < previousIncompleteCount || hasCompletedMoreTodos || hasTodoSnapshotChanged ? "todo" : hasObservedExternalActivity ? "activity" : "none";
20806
+ const progressSource = incompleteCount < previousIncompleteCount || hasCompletedMoreTodos || hasTodoSnapshotChanged ? "todo" : "none";
20813
20807
  if (progressSource !== "none") {
20814
20808
  state2.stagnationCount = 0;
20815
20809
  state2.awaitingPostInjectionProgressCheck = false;
@@ -20851,8 +20845,6 @@ function createSessionStateStore() {
20851
20845
  state2.awaitingPostInjectionProgressCheck = false;
20852
20846
  trackedSession.lastCompletedCount = undefined;
20853
20847
  trackedSession.lastTodoSnapshot = undefined;
20854
- trackedSession.activitySignalCount = 0;
20855
- trackedSession.lastObservedActivitySignalCount = undefined;
20856
20848
  }
20857
20849
  function cancelCountdown(sessionID) {
20858
20850
  const tracked = sessions.get(sessionID);
@@ -38723,6 +38715,16 @@ class TaskToastManager {
38723
38715
  const running = this.getRunningTasks();
38724
38716
  const queued = this.getQueuedTasks();
38725
38717
  const concurrencyInfo = this.getConcurrencyInfo();
38718
+ const formatTaskIdentifier = (task) => {
38719
+ const modelName = task.modelInfo?.model?.split("/").pop();
38720
+ if (modelName && task.category)
38721
+ return `${modelName}: ${task.category}`;
38722
+ if (modelName)
38723
+ return modelName;
38724
+ if (task.category)
38725
+ return `${task.agent}/${task.category}`;
38726
+ return task.agent;
38727
+ };
38726
38728
  const lines = [];
38727
38729
  const isFallback = newTask.modelInfo && (newTask.modelInfo.type === "inherited" || newTask.modelInfo.type === "system-default" || newTask.modelInfo.type === "runtime-fallback");
38728
38730
  if (isFallback) {
@@ -38741,9 +38743,9 @@ class TaskToastManager {
38741
38743
  const duration3 = this.formatDuration(task.startedAt);
38742
38744
  const bgIcon = task.isBackground ? "[BG]" : "[RUN]";
38743
38745
  const isNew = task.id === newTask.id ? " \u2190 NEW" : "";
38744
- const categoryInfo = task.category ? `/${task.category}` : "";
38746
+ const taskId = formatTaskIdentifier(task);
38745
38747
  const skillsInfo = task.skills?.length ? ` [${task.skills.join(", ")}]` : "";
38746
- lines.push(`${bgIcon} ${task.description} (${task.agent}${categoryInfo})${skillsInfo} - ${duration3}${isNew}`);
38748
+ lines.push(`${bgIcon} ${task.description} (${taskId})${skillsInfo} - ${duration3}${isNew}`);
38747
38749
  }
38748
38750
  }
38749
38751
  if (queued.length > 0) {
@@ -38752,10 +38754,10 @@ class TaskToastManager {
38752
38754
  lines.push(`Queued (${queued.length}):`);
38753
38755
  for (const task of queued) {
38754
38756
  const bgIcon = task.isBackground ? "[Q]" : "[W]";
38755
- const categoryInfo = task.category ? `/${task.category}` : "";
38757
+ const taskId = formatTaskIdentifier(task);
38756
38758
  const skillsInfo = task.skills?.length ? ` [${task.skills.join(", ")}]` : "";
38757
38759
  const isNew = task.id === newTask.id ? " \u2190 NEW" : "";
38758
- lines.push(`${bgIcon} ${task.description} (${task.agent}${categoryInfo})${skillsInfo} - Queued${isNew}`);
38760
+ lines.push(`${bgIcon} ${task.description} (${taskId})${skillsInfo} - Queued${isNew}`);
38759
38761
  }
38760
38762
  }
38761
38763
  return lines.join(`
@@ -41073,6 +41075,9 @@ function syncCachePackageJsonToIntent(pluginInfo) {
41073
41075
  return { synced: false, error: "write_error", message: "Failed to write cache package.json" };
41074
41076
  }
41075
41077
  }
41078
+ // src/hooks/auto-update-checker/hook/background-update-check.ts
41079
+ import { existsSync as existsSync48 } from "fs";
41080
+ import { join as join54 } from "path";
41076
41081
  // src/cli/config-manager/plugin-name-with-version.ts
41077
41082
  init_plugin_identity();
41078
41083
  // src/cli/config-manager/add-plugin-to-opencode-config.ts
@@ -41167,7 +41172,7 @@ function logCapturedOutputOnFailure(outputMode, output) {
41167
41172
  }
41168
41173
  async function runBunInstallWithDetails(options) {
41169
41174
  const outputMode = options?.outputMode ?? "pipe";
41170
- const cacheDir = getOpenCodeCacheDir();
41175
+ const cacheDir = options?.workspaceDir ?? getOpenCodeCacheDir();
41171
41176
  const packageJsonPath = `${cacheDir}/package.json`;
41172
41177
  if (!existsSync46(packageJsonPath)) {
41173
41178
  return {
@@ -41329,9 +41334,25 @@ Restart OpenCode to apply.`,
41329
41334
  function getPinnedVersionToastMessage(latestVersion) {
41330
41335
  return `Update available: ${latestVersion} (version pinned, update manually)`;
41331
41336
  }
41332
- async function runBunInstallSafe() {
41337
+ function resolveActiveInstallWorkspace() {
41338
+ const configPaths = getOpenCodeConfigPaths({ binary: "opencode" });
41339
+ const cacheDir = getOpenCodeCacheDir();
41340
+ const configInstallPath = join54(configPaths.configDir, "node_modules", PACKAGE_NAME, "package.json");
41341
+ const cacheInstallPath = join54(cacheDir, "node_modules", PACKAGE_NAME, "package.json");
41342
+ if (existsSync48(configInstallPath)) {
41343
+ log(`[auto-update-checker] Active workspace: config-dir (${configPaths.configDir})`);
41344
+ return configPaths.configDir;
41345
+ }
41346
+ if (existsSync48(cacheInstallPath)) {
41347
+ log(`[auto-update-checker] Active workspace: cache-dir (${cacheDir})`);
41348
+ return cacheDir;
41349
+ }
41350
+ log(`[auto-update-checker] Active workspace: config-dir (default, no install detected)`);
41351
+ return configPaths.configDir;
41352
+ }
41353
+ async function runBunInstallSafe(workspaceDir) {
41333
41354
  try {
41334
- const result = await runBunInstallWithDetails({ outputMode: "pipe" });
41355
+ const result = await runBunInstallWithDetails({ outputMode: "pipe", workspaceDir });
41335
41356
  if (!result.success && result.error) {
41336
41357
  log("[auto-update-checker] bun install error:", result.error);
41337
41358
  }
@@ -41382,7 +41403,8 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage) {
41382
41403
  return;
41383
41404
  }
41384
41405
  invalidatePackage(PACKAGE_NAME);
41385
- const installSuccess = await runBunInstallSafe();
41406
+ const activeWorkspace = resolveActiveInstallWorkspace();
41407
+ const installSuccess = await runBunInstallSafe(activeWorkspace);
41386
41408
  if (installSuccess) {
41387
41409
  await showAutoUpdatedToast(ctx, currentVersion, latestVersion);
41388
41410
  log(`[auto-update-checker] Update installed: ${currentVersion} \u2192 ${latestVersion}`);
@@ -41557,17 +41579,17 @@ v${latestVersion} available. Restart OpenCode to apply.` : "OpenCode is now on S
41557
41579
  }
41558
41580
  // src/hooks/agent-usage-reminder/storage.ts
41559
41581
  import {
41560
- existsSync as existsSync48,
41582
+ existsSync as existsSync49,
41561
41583
  mkdirSync as mkdirSync10,
41562
41584
  readFileSync as readFileSync33,
41563
41585
  writeFileSync as writeFileSync14,
41564
41586
  unlinkSync as unlinkSync8
41565
41587
  } from "fs";
41566
- import { join as join55 } from "path";
41588
+ import { join as join56 } from "path";
41567
41589
 
41568
41590
  // src/hooks/agent-usage-reminder/constants.ts
41569
- import { join as join54 } from "path";
41570
- var AGENT_USAGE_REMINDER_STORAGE = join54(OPENCODE_STORAGE, "agent-usage-reminder");
41591
+ import { join as join55 } from "path";
41592
+ var AGENT_USAGE_REMINDER_STORAGE = join55(OPENCODE_STORAGE, "agent-usage-reminder");
41571
41593
  var TARGET_TOOLS = new Set([
41572
41594
  "grep",
41573
41595
  "safe_grep",
@@ -41613,11 +41635,11 @@ ALWAYS prefer: Multiple parallel task calls > Direct tool calls
41613
41635
 
41614
41636
  // src/hooks/agent-usage-reminder/storage.ts
41615
41637
  function getStoragePath2(sessionID) {
41616
- return join55(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
41638
+ return join56(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
41617
41639
  }
41618
41640
  function loadAgentUsageState(sessionID) {
41619
41641
  const filePath = getStoragePath2(sessionID);
41620
- if (!existsSync48(filePath))
41642
+ if (!existsSync49(filePath))
41621
41643
  return null;
41622
41644
  try {
41623
41645
  const content = readFileSync33(filePath, "utf-8");
@@ -41627,7 +41649,7 @@ function loadAgentUsageState(sessionID) {
41627
41649
  }
41628
41650
  }
41629
41651
  function saveAgentUsageState(state3) {
41630
- if (!existsSync48(AGENT_USAGE_REMINDER_STORAGE)) {
41652
+ if (!existsSync49(AGENT_USAGE_REMINDER_STORAGE)) {
41631
41653
  mkdirSync10(AGENT_USAGE_REMINDER_STORAGE, { recursive: true });
41632
41654
  }
41633
41655
  const filePath = getStoragePath2(state3.sessionID);
@@ -41635,7 +41657,7 @@ function saveAgentUsageState(state3) {
41635
41657
  }
41636
41658
  function clearAgentUsageState(sessionID) {
41637
41659
  const filePath = getStoragePath2(sessionID);
41638
- if (existsSync48(filePath)) {
41660
+ if (existsSync49(filePath)) {
41639
41661
  unlinkSync8(filePath);
41640
41662
  }
41641
41663
  }
@@ -42899,17 +42921,17 @@ function createNonInteractiveEnvHook(_ctx) {
42899
42921
  }
42900
42922
  // src/hooks/interactive-bash-session/storage.ts
42901
42923
  import {
42902
- existsSync as existsSync49,
42924
+ existsSync as existsSync50,
42903
42925
  mkdirSync as mkdirSync11,
42904
42926
  readFileSync as readFileSync34,
42905
42927
  writeFileSync as writeFileSync15,
42906
42928
  unlinkSync as unlinkSync9
42907
42929
  } from "fs";
42908
- import { join as join57 } from "path";
42930
+ import { join as join58 } from "path";
42909
42931
 
42910
42932
  // src/hooks/interactive-bash-session/constants.ts
42911
- import { join as join56 } from "path";
42912
- var INTERACTIVE_BASH_SESSION_STORAGE = join56(OPENCODE_STORAGE, "interactive-bash-session");
42933
+ import { join as join57 } from "path";
42934
+ var INTERACTIVE_BASH_SESSION_STORAGE = join57(OPENCODE_STORAGE, "interactive-bash-session");
42913
42935
  var OMO_SESSION_PREFIX = "omo-";
42914
42936
  function buildSessionReminderMessage(sessions) {
42915
42937
  if (sessions.length === 0)
@@ -42921,11 +42943,11 @@ function buildSessionReminderMessage(sessions) {
42921
42943
 
42922
42944
  // src/hooks/interactive-bash-session/storage.ts
42923
42945
  function getStoragePath3(sessionID) {
42924
- return join57(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
42946
+ return join58(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
42925
42947
  }
42926
42948
  function loadInteractiveBashSessionState(sessionID) {
42927
42949
  const filePath = getStoragePath3(sessionID);
42928
- if (!existsSync49(filePath))
42950
+ if (!existsSync50(filePath))
42929
42951
  return null;
42930
42952
  try {
42931
42953
  const content = readFileSync34(filePath, "utf-8");
@@ -42940,7 +42962,7 @@ function loadInteractiveBashSessionState(sessionID) {
42940
42962
  }
42941
42963
  }
42942
42964
  function saveInteractiveBashSessionState(state3) {
42943
- if (!existsSync49(INTERACTIVE_BASH_SESSION_STORAGE)) {
42965
+ if (!existsSync50(INTERACTIVE_BASH_SESSION_STORAGE)) {
42944
42966
  mkdirSync11(INTERACTIVE_BASH_SESSION_STORAGE, { recursive: true });
42945
42967
  }
42946
42968
  const filePath = getStoragePath3(state3.sessionID);
@@ -42953,7 +42975,7 @@ function saveInteractiveBashSessionState(state3) {
42953
42975
  }
42954
42976
  function clearInteractiveBashSessionState(sessionID) {
42955
42977
  const filePath = getStoragePath3(sessionID);
42956
- if (existsSync49(filePath)) {
42978
+ if (existsSync50(filePath)) {
42957
42979
  unlinkSync9(filePath);
42958
42980
  }
42959
42981
  }
@@ -43350,14 +43372,14 @@ var DEFAULT_MAX_ITERATIONS = 100;
43350
43372
  var DEFAULT_COMPLETION_PROMISE = "DONE";
43351
43373
  var ULTRAWORK_VERIFICATION_PROMISE = "VERIFIED";
43352
43374
  // src/hooks/ralph-loop/storage.ts
43353
- import { existsSync as existsSync50, readFileSync as readFileSync35, writeFileSync as writeFileSync16, unlinkSync as unlinkSync10, mkdirSync as mkdirSync12 } from "fs";
43354
- import { dirname as dirname11, join as join58 } from "path";
43375
+ import { existsSync as existsSync51, readFileSync as readFileSync35, writeFileSync as writeFileSync16, unlinkSync as unlinkSync10, mkdirSync as mkdirSync12 } from "fs";
43376
+ import { dirname as dirname11, join as join59 } from "path";
43355
43377
  function getStateFilePath(directory, customPath) {
43356
- return customPath ? join58(directory, customPath) : join58(directory, DEFAULT_STATE_FILE);
43378
+ return customPath ? join59(directory, customPath) : join59(directory, DEFAULT_STATE_FILE);
43357
43379
  }
43358
43380
  function readState(directory, customPath) {
43359
43381
  const filePath = getStateFilePath(directory, customPath);
43360
- if (!existsSync50(filePath)) {
43382
+ if (!existsSync51(filePath)) {
43361
43383
  return null;
43362
43384
  }
43363
43385
  try {
@@ -43403,7 +43425,7 @@ function writeState(directory, state3, customPath) {
43403
43425
  const filePath = getStateFilePath(directory, customPath);
43404
43426
  try {
43405
43427
  const dir = dirname11(filePath);
43406
- if (!existsSync50(dir)) {
43428
+ if (!existsSync51(dir)) {
43407
43429
  mkdirSync12(dir, { recursive: true });
43408
43430
  }
43409
43431
  const sessionIdLine = state3.session_id ? `session_id: "${state3.session_id}"
@@ -43441,7 +43463,7 @@ ${state3.prompt}
43441
43463
  function clearState(directory, customPath) {
43442
43464
  const filePath = getStateFilePath(directory, customPath);
43443
43465
  try {
43444
- if (existsSync50(filePath)) {
43466
+ if (existsSync51(filePath)) {
43445
43467
  unlinkSync10(filePath);
43446
43468
  }
43447
43469
  return true;
@@ -43770,7 +43792,7 @@ async function handleDetectedCompletion(ctx, input) {
43770
43792
 
43771
43793
  // src/hooks/ralph-loop/completion-promise-detector.ts
43772
43794
  init_logger();
43773
- import { existsSync as existsSync51, readFileSync as readFileSync36 } from "fs";
43795
+ import { existsSync as existsSync52, readFileSync as readFileSync36 } from "fs";
43774
43796
  function escapeRegex2(str2) {
43775
43797
  return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
43776
43798
  }
@@ -43781,7 +43803,7 @@ function detectCompletionInTranscript(transcriptPath, promise2, startedAt) {
43781
43803
  if (!transcriptPath)
43782
43804
  return false;
43783
43805
  try {
43784
- if (!existsSync51(transcriptPath))
43806
+ if (!existsSync52(transcriptPath))
43785
43807
  return false;
43786
43808
  const content = readFileSync36(transcriptPath, "utf-8");
43787
43809
  const pattern = buildPromisePattern(promise2);
@@ -43998,6 +44020,56 @@ async function handleFailedVerification(ctx, input) {
43998
44020
  }
43999
44021
 
44000
44022
  // src/hooks/ralph-loop/pending-verification-handler.ts
44023
+ var ORACLE_AGENT_PATTERN = /Agent:\s*oracle/i;
44024
+ var TASK_METADATA_SESSION_PATTERN = /<task_metadata>[\s\S]*?session_id:\s*([^\s<]+)[\s\S]*?<\/task_metadata>/i;
44025
+ var VERIFIED_PROMISE_PATTERN = new RegExp(`<promise>\\s*${ULTRAWORK_VERIFICATION_PROMISE}\\s*<\\/promise>`, "i");
44026
+ function collectAssistantText(message) {
44027
+ if (!Array.isArray(message.parts)) {
44028
+ return "";
44029
+ }
44030
+ let text = "";
44031
+ for (const part of message.parts) {
44032
+ if (part.type !== "text") {
44033
+ continue;
44034
+ }
44035
+ text += `${text ? `
44036
+ ` : ""}${part.text ?? ""}`;
44037
+ }
44038
+ return text;
44039
+ }
44040
+ async function detectOracleVerificationFromParentSession(ctx, parentSessionID, directory, apiTimeoutMs) {
44041
+ try {
44042
+ const response = await withTimeout(ctx.client.session.messages({
44043
+ path: { id: parentSessionID },
44044
+ query: { directory }
44045
+ }), apiTimeoutMs);
44046
+ const messagesResponse = response;
44047
+ const responseData = typeof messagesResponse === "object" && messagesResponse !== null && "data" in messagesResponse ? messagesResponse.data : undefined;
44048
+ const messageArray = Array.isArray(messagesResponse) ? messagesResponse : Array.isArray(responseData) ? responseData : [];
44049
+ for (let index = messageArray.length - 1;index >= 0; index -= 1) {
44050
+ const message = messageArray[index];
44051
+ if (message.info?.role !== "assistant") {
44052
+ continue;
44053
+ }
44054
+ const assistantText = collectAssistantText(message);
44055
+ if (!VERIFIED_PROMISE_PATTERN.test(assistantText) || !ORACLE_AGENT_PATTERN.test(assistantText)) {
44056
+ continue;
44057
+ }
44058
+ const sessionMatch = assistantText.match(TASK_METADATA_SESSION_PATTERN);
44059
+ const detectedOracleSessionID = sessionMatch?.[1]?.trim();
44060
+ if (detectedOracleSessionID) {
44061
+ return detectedOracleSessionID;
44062
+ }
44063
+ }
44064
+ return;
44065
+ } catch (error48) {
44066
+ log(`[${HOOK_NAME3}] Failed to scan parent session for oracle verification evidence`, {
44067
+ parentSessionID,
44068
+ error: String(error48)
44069
+ });
44070
+ return;
44071
+ }
44072
+ }
44001
44073
  async function handlePendingVerification(ctx, input) {
44002
44074
  const {
44003
44075
  sessionID,
@@ -44010,6 +44082,19 @@ async function handlePendingVerification(ctx, input) {
44010
44082
  apiTimeoutMs
44011
44083
  } = input;
44012
44084
  if (matchesParentSession || verificationSessionID && matchesVerificationSession) {
44085
+ if (!verificationSessionID && state3.session_id) {
44086
+ const recoveredVerificationSessionID = await detectOracleVerificationFromParentSession(ctx, state3.session_id, directory, apiTimeoutMs);
44087
+ if (recoveredVerificationSessionID) {
44088
+ const updatedState = loopState.setVerificationSessionID(state3.session_id, recoveredVerificationSessionID);
44089
+ if (updatedState) {
44090
+ log(`[${HOOK_NAME3}] Recovered missing verification session from parent evidence`, {
44091
+ parentSessionID: state3.session_id,
44092
+ recoveredVerificationSessionID
44093
+ });
44094
+ return;
44095
+ }
44096
+ }
44097
+ }
44013
44098
  const restarted = await handleFailedVerification(ctx, {
44014
44099
  state: state3,
44015
44100
  loopState,
@@ -44141,6 +44226,12 @@ function createRalphLoopEventHandler(ctx, options) {
44141
44226
  return;
44142
44227
  }
44143
44228
  if (state3.verification_pending) {
44229
+ if (!verificationSessionID && matchesParentSession) {
44230
+ log(`[${HOOK_NAME3}] Verification pending without tracked oracle session, running recovery check`, {
44231
+ sessionID,
44232
+ iteration: state3.iteration
44233
+ });
44234
+ }
44144
44235
  await handlePendingVerification(ctx, {
44145
44236
  sessionID,
44146
44237
  state: state3,
@@ -44664,7 +44755,7 @@ function findSlashCommandPartIndex(parts) {
44664
44755
  // src/hooks/auto-slash-command/executor.ts
44665
44756
  import { dirname as dirname14 } from "path";
44666
44757
  // src/features/opencode-skill-loader/loader.ts
44667
- import { join as join61 } from "path";
44758
+ import { join as join62 } from "path";
44668
44759
  import { homedir as homedir11 } from "os";
44669
44760
 
44670
44761
  // src/features/opencode-skill-loader/skill-definition-record.ts
@@ -44692,7 +44783,7 @@ function deduplicateSkillsByName(skills) {
44692
44783
 
44693
44784
  // src/features/opencode-skill-loader/skill-directory-loader.ts
44694
44785
  import { promises as fs16 } from "fs";
44695
- import { join as join60 } from "path";
44786
+ import { join as join61 } from "path";
44696
44787
 
44697
44788
  // src/features/opencode-skill-loader/loaded-skill-from-path.ts
44698
44789
  import { promises as fs15 } from "fs";
@@ -44710,7 +44801,7 @@ function parseAllowedTools(allowedTools) {
44710
44801
 
44711
44802
  // src/features/opencode-skill-loader/skill-mcp-config.ts
44712
44803
  import { promises as fs14 } from "fs";
44713
- import { join as join59 } from "path";
44804
+ import { join as join60 } from "path";
44714
44805
  function parseSkillMcpConfigFromFrontmatter(content) {
44715
44806
  const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
44716
44807
  if (!frontmatterMatch)
@@ -44726,7 +44817,7 @@ function parseSkillMcpConfigFromFrontmatter(content) {
44726
44817
  return;
44727
44818
  }
44728
44819
  async function loadMcpJsonFromDir(skillDir) {
44729
- const mcpJsonPath = join59(skillDir, "mcp.json");
44820
+ const mcpJsonPath = join60(skillDir, "mcp.json");
44730
44821
  try {
44731
44822
  const content = await fs14.readFile(mcpJsonPath, "utf-8");
44732
44823
  const parsed = JSON.parse(content);
@@ -44815,10 +44906,10 @@ async function loadSkillsFromDir(options) {
44815
44906
  const directories = entries.filter((entry) => !entry.name.startsWith(".") && (entry.isDirectory() || entry.isSymbolicLink()));
44816
44907
  const files = entries.filter((entry) => !entry.name.startsWith(".") && !entry.isDirectory() && !entry.isSymbolicLink() && isMarkdownFile(entry));
44817
44908
  for (const entry of directories) {
44818
- const entryPath = join60(options.skillsDir, entry.name);
44909
+ const entryPath = join61(options.skillsDir, entry.name);
44819
44910
  const resolvedPath = await resolveSymlinkAsync(entryPath);
44820
44911
  const dirName = entry.name;
44821
- const skillMdPath = join60(resolvedPath, "SKILL.md");
44912
+ const skillMdPath = join61(resolvedPath, "SKILL.md");
44822
44913
  try {
44823
44914
  await fs16.access(skillMdPath);
44824
44915
  const skill = await loadSkillFromPath({
@@ -44833,7 +44924,7 @@ async function loadSkillsFromDir(options) {
44833
44924
  }
44834
44925
  continue;
44835
44926
  } catch {}
44836
- const namedSkillMdPath = join60(resolvedPath, `${dirName}.md`);
44927
+ const namedSkillMdPath = join61(resolvedPath, `${dirName}.md`);
44837
44928
  try {
44838
44929
  await fs16.access(namedSkillMdPath);
44839
44930
  const skill = await loadSkillFromPath({
@@ -44865,7 +44956,7 @@ async function loadSkillsFromDir(options) {
44865
44956
  }
44866
44957
  }
44867
44958
  for (const entry of files) {
44868
- const entryPath = join60(options.skillsDir, entry.name);
44959
+ const entryPath = join61(options.skillsDir, entry.name);
44869
44960
  const baseName = inferSkillNameFromFileName(entryPath);
44870
44961
  const skill = await loadSkillFromPath({
44871
44962
  skillPath: entryPath,
@@ -44883,12 +44974,12 @@ async function loadSkillsFromDir(options) {
44883
44974
 
44884
44975
  // src/features/opencode-skill-loader/loader.ts
44885
44976
  async function loadUserSkills() {
44886
- const userSkillsDir = join61(getClaudeConfigDir(), "skills");
44977
+ const userSkillsDir = join62(getClaudeConfigDir(), "skills");
44887
44978
  const skills = await loadSkillsFromDir({ skillsDir: userSkillsDir, scope: "user" });
44888
44979
  return skillsToCommandDefinitionRecord(skills);
44889
44980
  }
44890
44981
  async function loadProjectSkills(directory) {
44891
- const projectSkillsDir = join61(directory ?? process.cwd(), ".claude", "skills");
44982
+ const projectSkillsDir = join62(directory ?? process.cwd(), ".claude", "skills");
44892
44983
  const skills = await loadSkillsFromDir({ skillsDir: projectSkillsDir, scope: "project" });
44893
44984
  return skillsToCommandDefinitionRecord(skills);
44894
44985
  }
@@ -44898,7 +44989,7 @@ async function loadOpencodeGlobalSkills() {
44898
44989
  return skillsToCommandDefinitionRecord(deduplicateSkillsByName(allSkills.flat()));
44899
44990
  }
44900
44991
  async function loadOpencodeProjectSkills(directory) {
44901
- const opencodeProjectDir = join61(directory ?? process.cwd(), ".opencode", "skills");
44992
+ const opencodeProjectDir = join62(directory ?? process.cwd(), ".opencode", "skills");
44902
44993
  const skills = await loadSkillsFromDir({ skillsDir: opencodeProjectDir, scope: "opencode-project" });
44903
44994
  return skillsToCommandDefinitionRecord(skills);
44904
44995
  }
@@ -44945,11 +45036,11 @@ async function discoverSkills(options = {}) {
44945
45036
  ]);
44946
45037
  }
44947
45038
  async function discoverUserClaudeSkills() {
44948
- const userSkillsDir = join61(getClaudeConfigDir(), "skills");
45039
+ const userSkillsDir = join62(getClaudeConfigDir(), "skills");
44949
45040
  return loadSkillsFromDir({ skillsDir: userSkillsDir, scope: "user" });
44950
45041
  }
44951
45042
  async function discoverProjectClaudeSkills(directory) {
44952
- const projectSkillsDir = join61(directory ?? process.cwd(), ".claude", "skills");
45043
+ const projectSkillsDir = join62(directory ?? process.cwd(), ".claude", "skills");
44953
45044
  return loadSkillsFromDir({ skillsDir: projectSkillsDir, scope: "project" });
44954
45045
  }
44955
45046
  async function discoverOpencodeGlobalSkills() {
@@ -44958,15 +45049,15 @@ async function discoverOpencodeGlobalSkills() {
44958
45049
  return deduplicateSkillsByName(allSkills.flat());
44959
45050
  }
44960
45051
  async function discoverOpencodeProjectSkills(directory) {
44961
- const opencodeProjectDir = join61(directory ?? process.cwd(), ".opencode", "skills");
45052
+ const opencodeProjectDir = join62(directory ?? process.cwd(), ".opencode", "skills");
44962
45053
  return loadSkillsFromDir({ skillsDir: opencodeProjectDir, scope: "opencode-project" });
44963
45054
  }
44964
45055
  async function discoverProjectAgentsSkills(directory) {
44965
- const agentsProjectDir = join61(directory ?? process.cwd(), ".agents", "skills");
45056
+ const agentsProjectDir = join62(directory ?? process.cwd(), ".agents", "skills");
44966
45057
  return loadSkillsFromDir({ skillsDir: agentsProjectDir, scope: "project" });
44967
45058
  }
44968
45059
  async function discoverGlobalAgentsSkills() {
44969
- const agentsGlobalDir = join61(homedir11(), ".agents", "skills");
45060
+ const agentsGlobalDir = join62(homedir11(), ".agents", "skills");
44970
45061
  return loadSkillsFromDir({ skillsDir: agentsGlobalDir, scope: "user" });
44971
45062
  }
44972
45063
  // src/features/opencode-skill-loader/merger/builtin-skill-converter.ts
@@ -44993,7 +45084,7 @@ function builtinToLoadedSkill(builtin) {
44993
45084
  }
44994
45085
 
44995
45086
  // src/features/opencode-skill-loader/merger/config-skill-entry-loader.ts
44996
- import { existsSync as existsSync52, readFileSync as readFileSync37 } from "fs";
45087
+ import { existsSync as existsSync53, readFileSync as readFileSync37 } from "fs";
44997
45088
  import { dirname as dirname12, isAbsolute as isAbsolute4, resolve as resolve5 } from "path";
44998
45089
  import { homedir as homedir12 } from "os";
44999
45090
  function resolveFilePath5(from, configDir) {
@@ -45012,7 +45103,7 @@ function resolveFilePath5(from, configDir) {
45012
45103
  }
45013
45104
  function loadSkillFromFile(filePath) {
45014
45105
  try {
45015
- if (!existsSync52(filePath))
45106
+ if (!existsSync53(filePath))
45016
45107
  return null;
45017
45108
  const content = readFileSync37(filePath, "utf-8");
45018
45109
  const { data, body } = parseFrontmatter(content);
@@ -47508,6 +47599,11 @@ var BabysittingConfigSchema = exports_external.object({
47508
47599
  timeout_ms: exports_external.number().default(120000)
47509
47600
  });
47510
47601
  // src/config/schema/background-task.ts
47602
+ var CircuitBreakerConfigSchema = exports_external.object({
47603
+ maxToolCalls: exports_external.number().int().min(10).optional(),
47604
+ windowSize: exports_external.number().int().min(5).optional(),
47605
+ repetitionThresholdPercent: exports_external.number().gt(0).max(100).optional()
47606
+ });
47511
47607
  var BackgroundTaskConfigSchema = exports_external.object({
47512
47608
  defaultConcurrency: exports_external.number().min(1).optional(),
47513
47609
  providerConcurrency: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
@@ -47516,7 +47612,9 @@ var BackgroundTaskConfigSchema = exports_external.object({
47516
47612
  maxDescendants: exports_external.number().int().min(1).optional(),
47517
47613
  staleTimeoutMs: exports_external.number().min(60000).optional(),
47518
47614
  messageStalenessTimeoutMs: exports_external.number().min(60000).optional(),
47519
- syncPollTimeoutMs: exports_external.number().min(60000).optional()
47615
+ syncPollTimeoutMs: exports_external.number().min(60000).optional(),
47616
+ maxToolCalls: exports_external.number().int().min(10).optional(),
47617
+ circuitBreaker: CircuitBreakerConfigSchema.optional()
47520
47618
  });
47521
47619
  // src/config/schema/browser-automation.ts
47522
47620
  var BrowserAutomationProviderSchema = exports_external.enum([
@@ -47902,7 +48000,14 @@ function prefixGitCommandsInBashCodeBlocks(template, prefix) {
47902
48000
  });
47903
48001
  }
47904
48002
  function prefixGitCommandsInCodeBlock(codeBlock, prefix) {
47905
- return codeBlock.replace(LEADING_GIT_COMMAND_PATTERN, `$1${prefix} git`).replace(INLINE_GIT_COMMAND_PATTERN, `$1${prefix} git`);
48003
+ return codeBlock.split(`
48004
+ `).map((line) => {
48005
+ if (line.includes(prefix)) {
48006
+ return line;
48007
+ }
48008
+ return line.replace(LEADING_GIT_COMMAND_PATTERN, `$1${prefix} git`).replace(INLINE_GIT_COMMAND_PATTERN, `$1${prefix} git`);
48009
+ }).join(`
48010
+ `);
47906
48011
  }
47907
48012
  function buildCommitFooterInjection(commitFooter, includeCoAuthoredBy, gitEnvPrefix) {
47908
48013
  const sections = [];
@@ -47996,7 +48101,7 @@ async function resolveMultipleSkillsAsync(skillNames, options) {
47996
48101
  // src/features/opencode-skill-loader/config-source-discovery.ts
47997
48102
  var import_picomatch2 = __toESM(require_picomatch2(), 1);
47998
48103
  import { promises as fs17 } from "fs";
47999
- import { dirname as dirname13, extname, isAbsolute as isAbsolute5, join as join62, relative as relative3 } from "path";
48104
+ import { dirname as dirname13, extname, isAbsolute as isAbsolute5, join as join63, relative as relative3 } from "path";
48000
48105
  var MAX_RECURSIVE_DEPTH = 10;
48001
48106
  function isHttpUrl(path11) {
48002
48107
  return path11.startsWith("http://") || path11.startsWith("https://");
@@ -48005,7 +48110,7 @@ function toAbsolutePath(path11, configDir) {
48005
48110
  if (isAbsolute5(path11)) {
48006
48111
  return path11;
48007
48112
  }
48008
- return join62(configDir, path11);
48113
+ return join63(configDir, path11);
48009
48114
  }
48010
48115
  function isMarkdownPath(path11) {
48011
48116
  return extname(path11).toLowerCase() === ".md";
@@ -48075,8 +48180,8 @@ async function discoverConfigSourceSkills(options) {
48075
48180
  return deduplicateSkillsByName(loadedBySource.flat());
48076
48181
  }
48077
48182
  // src/tools/slashcommand/command-discovery.ts
48078
- import { existsSync as existsSync53, readdirSync as readdirSync15, readFileSync as readFileSync39 } from "fs";
48079
- import { basename as basename5, join as join63 } from "path";
48183
+ import { existsSync as existsSync54, readdirSync as readdirSync15, readFileSync as readFileSync39 } from "fs";
48184
+ import { basename as basename5, join as join64 } from "path";
48080
48185
  // src/features/builtin-commands/templates/init-deep.ts
48081
48186
  var INIT_DEEP_TEMPLATE = `# /init-deep
48082
48187
 
@@ -49459,14 +49564,14 @@ function loadBuiltinCommands(disabledCommands) {
49459
49564
  }
49460
49565
  // src/tools/slashcommand/command-discovery.ts
49461
49566
  function discoverCommandsFromDir(commandsDir, scope) {
49462
- if (!existsSync53(commandsDir))
49567
+ if (!existsSync54(commandsDir))
49463
49568
  return [];
49464
49569
  const entries = readdirSync15(commandsDir, { withFileTypes: true });
49465
49570
  const commands3 = [];
49466
49571
  for (const entry of entries) {
49467
49572
  if (!isMarkdownFile(entry))
49468
49573
  continue;
49469
- const commandPath = join63(commandsDir, entry.name);
49574
+ const commandPath = join64(commandsDir, entry.name);
49470
49575
  const commandName = basename5(entry.name, ".md");
49471
49576
  try {
49472
49577
  const content = readFileSync39(commandPath, "utf-8");
@@ -49509,10 +49614,10 @@ function discoverPluginCommands(options) {
49509
49614
  }));
49510
49615
  }
49511
49616
  function discoverCommandsSync(directory, options) {
49512
- const userCommandsDir = join63(getClaudeConfigDir(), "commands");
49513
- const projectCommandsDir = join63(directory ?? process.cwd(), ".claude", "commands");
49617
+ const userCommandsDir = join64(getClaudeConfigDir(), "commands");
49618
+ const projectCommandsDir = join64(directory ?? process.cwd(), ".claude", "commands");
49514
49619
  const opencodeGlobalDirs = getOpenCodeCommandDirs({ binary: "opencode" });
49515
- const opencodeProjectDir = join63(directory ?? process.cwd(), ".opencode", "command");
49620
+ const opencodeProjectDir = join64(directory ?? process.cwd(), ".opencode", "command");
49516
49621
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
49517
49622
  const opencodeGlobalCommands = opencodeGlobalDirs.flatMap((commandsDir) => discoverCommandsFromDir(commandsDir, "opencode"));
49518
49623
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
@@ -49974,14 +50079,14 @@ var NOTEPAD_DIR = "notepads";
49974
50079
  var NOTEPAD_BASE_PATH = `${BOULDER_DIR}/${NOTEPAD_DIR}`;
49975
50080
  var PROMETHEUS_PLANS_DIR = ".sisyphus/plans";
49976
50081
  // src/features/boulder-state/storage.ts
49977
- import { existsSync as existsSync54, readFileSync as readFileSync40, writeFileSync as writeFileSync17, mkdirSync as mkdirSync13, readdirSync as readdirSync16 } from "fs";
49978
- import { dirname as dirname15, join as join64, basename as basename6 } from "path";
50082
+ import { existsSync as existsSync55, readFileSync as readFileSync40, writeFileSync as writeFileSync17, mkdirSync as mkdirSync13, readdirSync as readdirSync16 } from "fs";
50083
+ import { dirname as dirname15, join as join65, basename as basename6 } from "path";
49979
50084
  function getBoulderFilePath(directory) {
49980
- return join64(directory, BOULDER_DIR, BOULDER_FILE);
50085
+ return join65(directory, BOULDER_DIR, BOULDER_FILE);
49981
50086
  }
49982
50087
  function readBoulderState(directory) {
49983
50088
  const filePath = getBoulderFilePath(directory);
49984
- if (!existsSync54(filePath)) {
50089
+ if (!existsSync55(filePath)) {
49985
50090
  return null;
49986
50091
  }
49987
50092
  try {
@@ -50002,7 +50107,7 @@ function writeBoulderState(directory, state3) {
50002
50107
  const filePath = getBoulderFilePath(directory);
50003
50108
  try {
50004
50109
  const dir = dirname15(filePath);
50005
- if (!existsSync54(dir)) {
50110
+ if (!existsSync55(dir)) {
50006
50111
  mkdirSync13(dir, { recursive: true });
50007
50112
  }
50008
50113
  writeFileSync17(filePath, JSON.stringify(state3, null, 2), "utf-8");
@@ -50019,17 +50124,20 @@ function appendSessionId(directory, sessionId) {
50019
50124
  if (!Array.isArray(state3.session_ids)) {
50020
50125
  state3.session_ids = [];
50021
50126
  }
50127
+ const originalSessionIds = [...state3.session_ids];
50022
50128
  state3.session_ids.push(sessionId);
50023
50129
  if (writeBoulderState(directory, state3)) {
50024
50130
  return state3;
50025
50131
  }
50132
+ state3.session_ids = originalSessionIds;
50133
+ return null;
50026
50134
  }
50027
50135
  return state3;
50028
50136
  }
50029
50137
  function clearBoulderState(directory) {
50030
50138
  const filePath = getBoulderFilePath(directory);
50031
50139
  try {
50032
- if (existsSync54(filePath)) {
50140
+ if (existsSync55(filePath)) {
50033
50141
  const { unlinkSync: unlinkSync11 } = __require("fs");
50034
50142
  unlinkSync11(filePath);
50035
50143
  }
@@ -50039,13 +50147,13 @@ function clearBoulderState(directory) {
50039
50147
  }
50040
50148
  }
50041
50149
  function findPrometheusPlans(directory) {
50042
- const plansDir = join64(directory, PROMETHEUS_PLANS_DIR);
50043
- if (!existsSync54(plansDir)) {
50150
+ const plansDir = join65(directory, PROMETHEUS_PLANS_DIR);
50151
+ if (!existsSync55(plansDir)) {
50044
50152
  return [];
50045
50153
  }
50046
50154
  try {
50047
50155
  const files = readdirSync16(plansDir);
50048
- return files.filter((f) => f.endsWith(".md")).map((f) => join64(plansDir, f)).sort((a, b) => {
50156
+ return files.filter((f) => f.endsWith(".md")).map((f) => join65(plansDir, f)).sort((a, b) => {
50049
50157
  const aStat = __require("fs").statSync(a);
50050
50158
  const bStat = __require("fs").statSync(b);
50051
50159
  return bStat.mtimeMs - aStat.mtimeMs;
@@ -50055,7 +50163,7 @@ function findPrometheusPlans(directory) {
50055
50163
  }
50056
50164
  }
50057
50165
  function getPlanProgress(planPath) {
50058
- if (!existsSync54(planPath)) {
50166
+ if (!existsSync55(planPath)) {
50059
50167
  return { total: 0, completed: 0, isComplete: true };
50060
50168
  }
50061
50169
  try {
@@ -51135,7 +51243,7 @@ function createAtlasEventHandler(input) {
51135
51243
  init_logger();
51136
51244
 
51137
51245
  // src/hooks/atlas/final-wave-plan-state.ts
51138
- import { existsSync as existsSync55, readFileSync as readFileSync41 } from "fs";
51246
+ import { existsSync as existsSync56, readFileSync as readFileSync41 } from "fs";
51139
51247
  var TODO_HEADING_PATTERN = /^##\s+TODOs\b/i;
51140
51248
  var FINAL_VERIFICATION_HEADING_PATTERN = /^##\s+Final Verification Wave\b/i;
51141
51249
  var SECOND_LEVEL_HEADING_PATTERN = /^##\s+/;
@@ -51143,7 +51251,7 @@ var UNCHECKED_CHECKBOX_PATTERN = /^\s*[-*]\s*\[\s*\]\s*(.+)$/;
51143
51251
  var TODO_TASK_PATTERN = /^\d+\./;
51144
51252
  var FINAL_WAVE_TASK_PATTERN = /^F\d+\./i;
51145
51253
  function readFinalWavePlanState(planPath) {
51146
- if (!existsSync55(planPath)) {
51254
+ if (!existsSync56(planPath)) {
51147
51255
  return null;
51148
51256
  }
51149
51257
  try {
@@ -53336,8 +53444,14 @@ function extractStatusCode(error48, retryOnErrors) {
53336
53444
  if (!error48)
53337
53445
  return;
53338
53446
  const errorObj = error48;
53339
- const statusCode = errorObj.statusCode ?? errorObj.status ?? errorObj.data?.statusCode;
53340
- if (typeof statusCode === "number") {
53447
+ const statusCode = [
53448
+ errorObj.statusCode,
53449
+ errorObj.status,
53450
+ errorObj.data?.statusCode,
53451
+ errorObj.error?.statusCode,
53452
+ errorObj.cause?.statusCode
53453
+ ].find((code) => typeof code === "number");
53454
+ if (statusCode !== undefined) {
53341
53455
  return statusCode;
53342
53456
  }
53343
53457
  const codes = retryOnErrors ?? DEFAULT_CONFIG2.retry_on_errors;
@@ -54097,8 +54211,8 @@ function createRuntimeFallbackHook(ctx, options) {
54097
54211
  };
54098
54212
  }
54099
54213
  // src/hooks/write-existing-file-guard/hook.ts
54100
- import { existsSync as existsSync57, realpathSync as realpathSync4 } from "fs";
54101
- import { basename as basename7, dirname as dirname16, isAbsolute as isAbsolute7, join as join66, normalize, relative as relative5, resolve as resolve7 } from "path";
54214
+ import { existsSync as existsSync58, realpathSync as realpathSync4 } from "fs";
54215
+ import { basename as basename7, dirname as dirname16, isAbsolute as isAbsolute7, join as join67, normalize, relative as relative5, resolve as resolve7 } from "path";
54102
54216
  var MAX_TRACKED_SESSIONS = 256;
54103
54217
  var MAX_TRACKED_PATHS_PER_SESSION = 1024;
54104
54218
  function asRecord(value) {
@@ -54119,7 +54233,7 @@ function isPathInsideDirectory(pathToCheck, directory) {
54119
54233
  }
54120
54234
  function toCanonicalPath(absolutePath) {
54121
54235
  let canonicalPath = absolutePath;
54122
- if (existsSync57(absolutePath)) {
54236
+ if (existsSync58(absolutePath)) {
54123
54237
  try {
54124
54238
  canonicalPath = realpathSync4.native(absolutePath);
54125
54239
  } catch {
@@ -54127,8 +54241,8 @@ function toCanonicalPath(absolutePath) {
54127
54241
  }
54128
54242
  } else {
54129
54243
  const absoluteDir = dirname16(absolutePath);
54130
- const resolvedDir = existsSync57(absoluteDir) ? realpathSync4.native(absoluteDir) : absoluteDir;
54131
- canonicalPath = join66(resolvedDir, basename7(absolutePath));
54244
+ const resolvedDir = existsSync58(absoluteDir) ? realpathSync4.native(absoluteDir) : absoluteDir;
54245
+ canonicalPath = join67(resolvedDir, basename7(absolutePath));
54132
54246
  }
54133
54247
  return normalize(canonicalPath);
54134
54248
  }
@@ -54228,7 +54342,7 @@ function createWriteExistingFileGuardHook(ctx) {
54228
54342
  return;
54229
54343
  }
54230
54344
  if (toolName === "read") {
54231
- if (!existsSync57(resolvedPath) || !input.sessionID) {
54345
+ if (!existsSync58(resolvedPath) || !input.sessionID) {
54232
54346
  return;
54233
54347
  }
54234
54348
  registerReadPermission(input.sessionID, canonicalPath);
@@ -54238,7 +54352,7 @@ function createWriteExistingFileGuardHook(ctx) {
54238
54352
  if (argsRecord && "overwrite" in argsRecord) {
54239
54353
  delete argsRecord.overwrite;
54240
54354
  }
54241
- if (!existsSync57(resolvedPath)) {
54355
+ if (!existsSync58(resolvedPath)) {
54242
54356
  return;
54243
54357
  }
54244
54358
  const isSisyphusPath2 = canonicalPath.includes("/.sisyphus/");
@@ -55327,12 +55441,12 @@ var DEFAULT_MAX_SYMBOLS = 200;
55327
55441
  var DEFAULT_MAX_DIAGNOSTICS = 200;
55328
55442
  var DEFAULT_MAX_DIRECTORY_FILES = 50;
55329
55443
  // src/tools/lsp/server-config-loader.ts
55330
- import { existsSync as existsSync58, readFileSync as readFileSync43 } from "fs";
55331
- import { join as join67 } from "path";
55444
+ import { existsSync as existsSync59, readFileSync as readFileSync43 } from "fs";
55445
+ import { join as join68 } from "path";
55332
55446
  init_jsonc_parser();
55333
55447
  init_plugin_identity();
55334
55448
  function loadJsonFile(path12) {
55335
- if (!existsSync58(path12))
55449
+ if (!existsSync59(path12))
55336
55450
  return null;
55337
55451
  try {
55338
55452
  return parseJsonc(readFileSync43(path12, "utf-8"));
@@ -55344,9 +55458,9 @@ function getConfigPaths3() {
55344
55458
  const cwd = process.cwd();
55345
55459
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
55346
55460
  return {
55347
- project: detectManagedConfigFile(join67(cwd, ".opencode")).path,
55461
+ project: detectManagedConfigFile(join68(cwd, ".opencode")).path,
55348
55462
  user: detectManagedConfigFile(configDir).path,
55349
- opencode: detectConfigFile(join67(configDir, "opencode")).path
55463
+ opencode: detectConfigFile(join68(configDir, "opencode")).path
55350
55464
  };
55351
55465
  }
55352
55466
  function loadAllConfigs() {
@@ -55415,20 +55529,20 @@ function getMergedServers() {
55415
55529
  }
55416
55530
 
55417
55531
  // src/tools/lsp/server-installation.ts
55418
- import { existsSync as existsSync59 } from "fs";
55419
- import { delimiter, join as join69 } from "path";
55532
+ import { existsSync as existsSync60 } from "fs";
55533
+ import { delimiter, join as join70 } from "path";
55420
55534
 
55421
55535
  // src/tools/lsp/server-path-bases.ts
55422
- import { join as join68 } from "path";
55536
+ import { join as join69 } from "path";
55423
55537
  function getLspServerAdditionalPathBases(workingDirectory) {
55424
55538
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
55425
- const dataDir = join68(getDataDir(), "opencode");
55539
+ const dataDir = join69(getDataDir(), "opencode");
55426
55540
  return [
55427
- join68(workingDirectory, "node_modules", ".bin"),
55428
- join68(configDir, "bin"),
55429
- join68(configDir, "node_modules", ".bin"),
55430
- join68(dataDir, "bin"),
55431
- join68(dataDir, "bin", "node_modules", ".bin")
55541
+ join69(workingDirectory, "node_modules", ".bin"),
55542
+ join69(configDir, "bin"),
55543
+ join69(configDir, "node_modules", ".bin"),
55544
+ join69(dataDir, "bin"),
55545
+ join69(dataDir, "bin", "node_modules", ".bin")
55432
55546
  ];
55433
55547
  }
55434
55548
 
@@ -55438,7 +55552,7 @@ function isServerInstalled(command) {
55438
55552
  return false;
55439
55553
  const cmd = command[0];
55440
55554
  if (cmd.includes("/") || cmd.includes("\\")) {
55441
- if (existsSync59(cmd))
55555
+ if (existsSync60(cmd))
55442
55556
  return true;
55443
55557
  }
55444
55558
  const isWindows2 = process.platform === "win32";
@@ -55459,14 +55573,14 @@ function isServerInstalled(command) {
55459
55573
  const paths = pathEnv.split(delimiter);
55460
55574
  for (const p of paths) {
55461
55575
  for (const suffix of exts) {
55462
- if (existsSync59(join69(p, cmd + suffix))) {
55576
+ if (existsSync60(join70(p, cmd + suffix))) {
55463
55577
  return true;
55464
55578
  }
55465
55579
  }
55466
55580
  }
55467
55581
  for (const base of getLspServerAdditionalPathBases(process.cwd())) {
55468
55582
  for (const suffix of exts) {
55469
- if (existsSync59(join69(base, cmd + suffix))) {
55583
+ if (existsSync60(join70(base, cmd + suffix))) {
55470
55584
  return true;
55471
55585
  }
55472
55586
  }
@@ -55524,13 +55638,13 @@ function getLanguageId(ext) {
55524
55638
  init_logger();
55525
55639
  var {spawn: bunSpawn2 } = globalThis.Bun;
55526
55640
  import { spawn as nodeSpawn2 } from "child_process";
55527
- import { existsSync as existsSync60, statSync as statSync7 } from "fs";
55641
+ import { existsSync as existsSync61, statSync as statSync7 } from "fs";
55528
55642
  function shouldUseNodeSpawn() {
55529
55643
  return process.platform === "win32";
55530
55644
  }
55531
55645
  function validateCwd(cwd) {
55532
55646
  try {
55533
- if (!existsSync60(cwd)) {
55647
+ if (!existsSync61(cwd)) {
55534
55648
  return { valid: false, error: `Working directory does not exist: ${cwd}` };
55535
55649
  }
55536
55650
  const stats = statSync7(cwd);
@@ -56275,9 +56389,9 @@ var lspManager = LSPServerManager.getInstance();
56275
56389
  // src/tools/lsp/lsp-client-wrapper.ts
56276
56390
  import { extname as extname4, resolve as resolve9 } from "path";
56277
56391
  import { fileURLToPath as fileURLToPath3 } from "url";
56278
- import { existsSync as existsSync61, statSync as statSync8 } from "fs";
56392
+ import { existsSync as existsSync62, statSync as statSync8 } from "fs";
56279
56393
  function isDirectoryPath(filePath) {
56280
- if (!existsSync61(filePath)) {
56394
+ if (!existsSync62(filePath)) {
56281
56395
  return false;
56282
56396
  }
56283
56397
  return statSync8(filePath).isDirectory();
@@ -56287,14 +56401,14 @@ function uriToPath(uri) {
56287
56401
  }
56288
56402
  function findWorkspaceRoot(filePath) {
56289
56403
  let dir = resolve9(filePath);
56290
- if (!existsSync61(dir) || !isDirectoryPath(dir)) {
56404
+ if (!existsSync62(dir) || !isDirectoryPath(dir)) {
56291
56405
  dir = __require("path").dirname(dir);
56292
56406
  }
56293
56407
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
56294
56408
  let prevDir = "";
56295
56409
  while (dir !== prevDir) {
56296
56410
  for (const marker of markers) {
56297
- if (existsSync61(__require("path").join(dir, marker))) {
56411
+ if (existsSync62(__require("path").join(dir, marker))) {
56298
56412
  return dir;
56299
56413
  }
56300
56414
  }
@@ -69021,8 +69135,8 @@ var lsp_symbols = tool({
69021
69135
  import { resolve as resolve11 } from "path";
69022
69136
 
69023
69137
  // src/tools/lsp/directory-diagnostics.ts
69024
- import { existsSync as existsSync62, lstatSync as lstatSync2, readdirSync as readdirSync17 } from "fs";
69025
- import { extname as extname5, join as join70, resolve as resolve10 } from "path";
69138
+ import { existsSync as existsSync63, lstatSync as lstatSync2, readdirSync as readdirSync17 } from "fs";
69139
+ import { extname as extname5, join as join71, resolve as resolve10 } from "path";
69026
69140
  var SKIP_DIRECTORIES = new Set(["node_modules", ".git", "dist", "build", ".next", "out"]);
69027
69141
  function collectFilesWithExtension(dir, extension, maxFiles) {
69028
69142
  const files = [];
@@ -69038,7 +69152,7 @@ function collectFilesWithExtension(dir, extension, maxFiles) {
69038
69152
  for (const entry of entries) {
69039
69153
  if (files.length >= maxFiles)
69040
69154
  return;
69041
- const fullPath = join70(currentDir, entry);
69155
+ const fullPath = join71(currentDir, entry);
69042
69156
  let stat;
69043
69157
  try {
69044
69158
  stat = lstatSync2(fullPath);
@@ -69067,7 +69181,7 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
69067
69181
  throw new Error(`Extension must start with a dot (e.g., ".ts", not "${extension}"). ` + `Use ".${extension}" instead.`);
69068
69182
  }
69069
69183
  const absDir = resolve10(directory);
69070
- if (!existsSync62(absDir)) {
69184
+ if (!existsSync63(absDir)) {
69071
69185
  throw new Error(`Directory does not exist: ${absDir}`);
69072
69186
  }
69073
69187
  const serverResult = findServerForExtension(extension);
@@ -69269,12 +69383,12 @@ var DEFAULT_MAX_MATCHES = 500;
69269
69383
 
69270
69384
  // src/tools/ast-grep/sg-cli-path.ts
69271
69385
  import { createRequire as createRequire4 } from "module";
69272
- import { dirname as dirname17, join as join72 } from "path";
69273
- import { existsSync as existsSync64, statSync as statSync9 } from "fs";
69386
+ import { dirname as dirname17, join as join73 } from "path";
69387
+ import { existsSync as existsSync65, statSync as statSync9 } from "fs";
69274
69388
 
69275
69389
  // src/tools/ast-grep/downloader.ts
69276
- import { existsSync as existsSync63 } from "fs";
69277
- import { join as join71 } from "path";
69390
+ import { existsSync as existsSync64 } from "fs";
69391
+ import { join as join72 } from "path";
69278
69392
  import { homedir as homedir13 } from "os";
69279
69393
  import { createRequire as createRequire3 } from "module";
69280
69394
  init_logger();
@@ -69302,12 +69416,12 @@ var PLATFORM_MAP2 = {
69302
69416
  function getCacheDir3() {
69303
69417
  if (process.platform === "win32") {
69304
69418
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
69305
- const base2 = localAppData || join71(homedir13(), "AppData", "Local");
69306
- return join71(base2, CACHE_DIR_NAME, "bin");
69419
+ const base2 = localAppData || join72(homedir13(), "AppData", "Local");
69420
+ return join72(base2, CACHE_DIR_NAME, "bin");
69307
69421
  }
69308
69422
  const xdgCache = process.env.XDG_CACHE_HOME;
69309
- const base = xdgCache || join71(homedir13(), ".cache");
69310
- return join71(base, CACHE_DIR_NAME, "bin");
69423
+ const base = xdgCache || join72(homedir13(), ".cache");
69424
+ return join72(base, CACHE_DIR_NAME, "bin");
69311
69425
  }
69312
69426
  function getBinaryName3() {
69313
69427
  return process.platform === "win32" ? "sg.exe" : "sg";
@@ -69324,8 +69438,8 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
69324
69438
  }
69325
69439
  const cacheDir = getCacheDir3();
69326
69440
  const binaryName = getBinaryName3();
69327
- const binaryPath = join71(cacheDir, binaryName);
69328
- if (existsSync63(binaryPath)) {
69441
+ const binaryPath = join72(cacheDir, binaryName);
69442
+ if (existsSync64(binaryPath)) {
69329
69443
  return binaryPath;
69330
69444
  }
69331
69445
  const { arch, os: os6 } = platformInfo;
@@ -69333,7 +69447,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
69333
69447
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version3}/${assetName}`;
69334
69448
  log(`[${PLUGIN_NAME}] Downloading ast-grep binary...`);
69335
69449
  try {
69336
- const archivePath = join71(cacheDir, assetName);
69450
+ const archivePath = join72(cacheDir, assetName);
69337
69451
  ensureCacheDir(cacheDir);
69338
69452
  await downloadArchive(downloadUrl, archivePath);
69339
69453
  await extractZipArchive(archivePath, cacheDir);
@@ -69387,8 +69501,8 @@ function findSgCliPathSync() {
69387
69501
  const require2 = createRequire4(import.meta.url);
69388
69502
  const cliPackageJsonPath = require2.resolve("@ast-grep/cli/package.json");
69389
69503
  const cliDirectory = dirname17(cliPackageJsonPath);
69390
- const sgPath = join72(cliDirectory, binaryName);
69391
- if (existsSync64(sgPath) && isValidBinary(sgPath)) {
69504
+ const sgPath = join73(cliDirectory, binaryName);
69505
+ if (existsSync65(sgPath) && isValidBinary(sgPath)) {
69392
69506
  return sgPath;
69393
69507
  }
69394
69508
  } catch {}
@@ -69399,8 +69513,8 @@ function findSgCliPathSync() {
69399
69513
  const packageJsonPath = require2.resolve(`${platformPackage}/package.json`);
69400
69514
  const packageDirectory = dirname17(packageJsonPath);
69401
69515
  const astGrepBinaryName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
69402
- const binaryPath = join72(packageDirectory, astGrepBinaryName);
69403
- if (existsSync64(binaryPath) && isValidBinary(binaryPath)) {
69516
+ const binaryPath = join73(packageDirectory, astGrepBinaryName);
69517
+ if (existsSync65(binaryPath) && isValidBinary(binaryPath)) {
69404
69518
  return binaryPath;
69405
69519
  }
69406
69520
  } catch {}
@@ -69408,7 +69522,7 @@ function findSgCliPathSync() {
69408
69522
  if (process.platform === "darwin") {
69409
69523
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
69410
69524
  for (const path12 of homebrewPaths) {
69411
- if (existsSync64(path12) && isValidBinary(path12)) {
69525
+ if (existsSync65(path12) && isValidBinary(path12)) {
69412
69526
  return path12;
69413
69527
  }
69414
69528
  }
@@ -69432,14 +69546,14 @@ function setSgCliPath(path12) {
69432
69546
  }
69433
69547
  // src/tools/ast-grep/cli.ts
69434
69548
  var {spawn: spawn10 } = globalThis.Bun;
69435
- import { existsSync as existsSync66 } from "fs";
69549
+ import { existsSync as existsSync67 } from "fs";
69436
69550
 
69437
69551
  // src/tools/ast-grep/cli-binary-path-resolution.ts
69438
- import { existsSync as existsSync65 } from "fs";
69552
+ import { existsSync as existsSync66 } from "fs";
69439
69553
  var resolvedCliPath3 = null;
69440
69554
  var initPromise3 = null;
69441
69555
  async function getAstGrepPath() {
69442
- if (resolvedCliPath3 !== null && existsSync65(resolvedCliPath3)) {
69556
+ if (resolvedCliPath3 !== null && existsSync66(resolvedCliPath3)) {
69443
69557
  return resolvedCliPath3;
69444
69558
  }
69445
69559
  if (initPromise3) {
@@ -69447,7 +69561,7 @@ async function getAstGrepPath() {
69447
69561
  }
69448
69562
  initPromise3 = (async () => {
69449
69563
  const syncPath = findSgCliPathSync();
69450
- if (syncPath && existsSync65(syncPath)) {
69564
+ if (syncPath && existsSync66(syncPath)) {
69451
69565
  resolvedCliPath3 = syncPath;
69452
69566
  setSgCliPath(syncPath);
69453
69567
  return syncPath;
@@ -69546,7 +69660,7 @@ async function runSg(options) {
69546
69660
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
69547
69661
  args.push(...paths);
69548
69662
  let cliPath = getSgCliPath();
69549
- if (!cliPath || !existsSync66(cliPath)) {
69663
+ if (!cliPath || !existsSync67(cliPath)) {
69550
69664
  const downloadedPath = await getAstGrepPath();
69551
69665
  if (downloadedPath) {
69552
69666
  cliPath = downloadedPath;
@@ -69800,20 +69914,20 @@ import { resolve as resolve12 } from "path";
69800
69914
  var {spawn: spawn11 } = globalThis.Bun;
69801
69915
 
69802
69916
  // src/tools/grep/constants.ts
69803
- import { existsSync as existsSync68 } from "fs";
69804
- import { join as join74, dirname as dirname18 } from "path";
69917
+ import { existsSync as existsSync69 } from "fs";
69918
+ import { join as join75, dirname as dirname18 } from "path";
69805
69919
  import { spawnSync as spawnSync2 } from "child_process";
69806
69920
 
69807
69921
  // src/tools/grep/downloader.ts
69808
- import { existsSync as existsSync67, readdirSync as readdirSync18 } from "fs";
69809
- import { join as join73 } from "path";
69922
+ import { existsSync as existsSync68, readdirSync as readdirSync18 } from "fs";
69923
+ import { join as join74 } from "path";
69810
69924
  init_plugin_identity();
69811
69925
  function findFileRecursive(dir, filename) {
69812
69926
  try {
69813
69927
  const entries = readdirSync18(dir, { withFileTypes: true, recursive: true });
69814
69928
  for (const entry of entries) {
69815
69929
  if (entry.isFile() && entry.name === filename) {
69816
- return join73(entry.parentPath ?? dir, entry.name);
69930
+ return join74(entry.parentPath ?? dir, entry.name);
69817
69931
  }
69818
69932
  }
69819
69933
  } catch {
@@ -69834,11 +69948,11 @@ function getPlatformKey() {
69834
69948
  }
69835
69949
  function getInstallDir() {
69836
69950
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
69837
- return join73(homeDir, ".cache", CACHE_DIR_NAME, "bin");
69951
+ return join74(homeDir, ".cache", CACHE_DIR_NAME, "bin");
69838
69952
  }
69839
69953
  function getRgPath() {
69840
69954
  const isWindows2 = process.platform === "win32";
69841
- return join73(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
69955
+ return join74(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
69842
69956
  }
69843
69957
  async function extractTarGz2(archivePath, destDir) {
69844
69958
  const platformKey = getPlatformKey();
@@ -69855,7 +69969,7 @@ async function extractZip2(archivePath, destDir) {
69855
69969
  const binaryName = process.platform === "win32" ? "rg.exe" : "rg";
69856
69970
  const foundPath = findFileRecursive(destDir, binaryName);
69857
69971
  if (foundPath) {
69858
- const destPath = join73(destDir, binaryName);
69972
+ const destPath = join74(destDir, binaryName);
69859
69973
  if (foundPath !== destPath) {
69860
69974
  const { renameSync: renameSync2 } = await import("fs");
69861
69975
  renameSync2(foundPath, destPath);
@@ -69870,13 +69984,13 @@ async function downloadAndInstallRipgrep() {
69870
69984
  }
69871
69985
  const installDir = getInstallDir();
69872
69986
  const rgPath = getRgPath();
69873
- if (existsSync67(rgPath)) {
69987
+ if (existsSync68(rgPath)) {
69874
69988
  return rgPath;
69875
69989
  }
69876
69990
  ensureCacheDir(installDir);
69877
69991
  const filename = `ripgrep-${RG_VERSION}-${config4.platform}.${config4.extension}`;
69878
69992
  const url3 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
69879
- const archivePath = join73(installDir, filename);
69993
+ const archivePath = join74(installDir, filename);
69880
69994
  try {
69881
69995
  await downloadArchive(url3, archivePath);
69882
69996
  if (config4.extension === "tar.gz") {
@@ -69885,7 +69999,7 @@ async function downloadAndInstallRipgrep() {
69885
69999
  await extractZip2(archivePath, installDir);
69886
70000
  }
69887
70001
  ensureExecutable(rgPath);
69888
- if (!existsSync67(rgPath)) {
70002
+ if (!existsSync68(rgPath)) {
69889
70003
  throw new Error("ripgrep binary not found after extraction");
69890
70004
  }
69891
70005
  return rgPath;
@@ -69897,7 +70011,7 @@ async function downloadAndInstallRipgrep() {
69897
70011
  }
69898
70012
  function getInstalledRipgrepPath() {
69899
70013
  const rgPath = getRgPath();
69900
- return existsSync67(rgPath) ? rgPath : null;
70014
+ return existsSync68(rgPath) ? rgPath : null;
69901
70015
  }
69902
70016
 
69903
70017
  // src/tools/grep/constants.ts
@@ -69921,14 +70035,14 @@ function getOpenCodeBundledRg() {
69921
70035
  const isWindows2 = process.platform === "win32";
69922
70036
  const rgName = isWindows2 ? "rg.exe" : "rg";
69923
70037
  const candidates = [
69924
- join74(getDataDir(), "opencode", "bin", rgName),
69925
- join74(execDir, rgName),
69926
- join74(execDir, "bin", rgName),
69927
- join74(execDir, "..", "bin", rgName),
69928
- join74(execDir, "..", "libexec", rgName)
70038
+ join75(getDataDir(), "opencode", "bin", rgName),
70039
+ join75(execDir, rgName),
70040
+ join75(execDir, "bin", rgName),
70041
+ join75(execDir, "..", "bin", rgName),
70042
+ join75(execDir, "..", "libexec", rgName)
69929
70043
  ];
69930
70044
  for (const candidate of candidates) {
69931
- if (existsSync68(candidate)) {
70045
+ if (existsSync69(candidate)) {
69932
70046
  return candidate;
69933
70047
  }
69934
70048
  }
@@ -70817,6 +70931,7 @@ function createSkillTool(options = {}) {
70817
70931
  },
70818
70932
  async execute(args, ctx) {
70819
70933
  const skills2 = await getSkills();
70934
+ cachedDescription = null;
70820
70935
  const commands3 = getCommands();
70821
70936
  const requestedName = args.name.replace(/^\//, "");
70822
70937
  const matchedSkill = skills2.find((s) => s.name.toLowerCase() === requestedName.toLowerCase());
@@ -70869,9 +70984,9 @@ function createSkillTool(options = {}) {
70869
70984
  }
70870
70985
  var skill = createSkillTool();
70871
70986
  // src/tools/session-manager/constants.ts
70872
- import { join as join75 } from "path";
70873
- var TODO_DIR2 = join75(getClaudeConfigDir(), "todos");
70874
- var TRANSCRIPT_DIR2 = join75(getClaudeConfigDir(), "transcripts");
70987
+ import { join as join76 } from "path";
70988
+ var TODO_DIR2 = join76(getClaudeConfigDir(), "todos");
70989
+ var TRANSCRIPT_DIR2 = join76(getClaudeConfigDir(), "transcripts");
70875
70990
  var SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.
70876
70991
 
70877
70992
  Returns a list of available session IDs with metadata including message count, date range, and agents used.
@@ -70944,9 +71059,9 @@ Has Todos: Yes (12 items, 8 completed)
70944
71059
  Has Transcript: Yes (234 entries)`;
70945
71060
 
70946
71061
  // src/tools/session-manager/storage.ts
70947
- import { existsSync as existsSync69 } from "fs";
71062
+ import { existsSync as existsSync70 } from "fs";
70948
71063
  import { readdir, readFile } from "fs/promises";
70949
- import { join as join76 } from "path";
71064
+ import { join as join77 } from "path";
70950
71065
  var sdkClient = null;
70951
71066
  function setStorageClient(client2) {
70952
71067
  sdkClient = client2;
@@ -70965,7 +71080,7 @@ async function getMainSessions(options) {
70965
71080
  return [];
70966
71081
  }
70967
71082
  }
70968
- if (!existsSync69(SESSION_STORAGE))
71083
+ if (!existsSync70(SESSION_STORAGE))
70969
71084
  return [];
70970
71085
  const sessions = [];
70971
71086
  try {
@@ -70973,13 +71088,13 @@ async function getMainSessions(options) {
70973
71088
  for (const projectDir of projectDirs) {
70974
71089
  if (!projectDir.isDirectory())
70975
71090
  continue;
70976
- const projectPath = join76(SESSION_STORAGE, projectDir.name);
71091
+ const projectPath = join77(SESSION_STORAGE, projectDir.name);
70977
71092
  const sessionFiles = await readdir(projectPath);
70978
71093
  for (const file3 of sessionFiles) {
70979
71094
  if (!file3.endsWith(".json"))
70980
71095
  continue;
70981
71096
  try {
70982
- const content = await readFile(join76(projectPath, file3), "utf-8");
71097
+ const content = await readFile(join77(projectPath, file3), "utf-8");
70983
71098
  const meta3 = JSON.parse(content);
70984
71099
  if (meta3.parentID)
70985
71100
  continue;
@@ -71006,7 +71121,7 @@ async function getAllSessions() {
71006
71121
  return [];
71007
71122
  }
71008
71123
  }
71009
- if (!existsSync69(MESSAGE_STORAGE))
71124
+ if (!existsSync70(MESSAGE_STORAGE))
71010
71125
  return [];
71011
71126
  const sessions = [];
71012
71127
  async function scanDirectory(dir) {
@@ -71014,7 +71129,7 @@ async function getAllSessions() {
71014
71129
  const entries = await readdir(dir, { withFileTypes: true });
71015
71130
  for (const entry of entries) {
71016
71131
  if (entry.isDirectory()) {
71017
- const sessionPath = join76(dir, entry.name);
71132
+ const sessionPath = join77(dir, entry.name);
71018
71133
  const files = await readdir(sessionPath);
71019
71134
  if (files.some((f) => f.endsWith(".json"))) {
71020
71135
  sessions.push(entry.name);
@@ -71075,7 +71190,7 @@ async function readSessionMessages2(sessionID) {
71075
71190
  }
71076
71191
  }
71077
71192
  const messageDir = getMessageDir(sessionID);
71078
- if (!messageDir || !existsSync69(messageDir))
71193
+ if (!messageDir || !existsSync70(messageDir))
71079
71194
  return [];
71080
71195
  const messages = [];
71081
71196
  try {
@@ -71084,7 +71199,7 @@ async function readSessionMessages2(sessionID) {
71084
71199
  if (!file3.endsWith(".json"))
71085
71200
  continue;
71086
71201
  try {
71087
- const content = await readFile(join76(messageDir, file3), "utf-8");
71202
+ const content = await readFile(join77(messageDir, file3), "utf-8");
71088
71203
  const meta3 = JSON.parse(content);
71089
71204
  const parts = await readParts2(meta3.id);
71090
71205
  messages.push({
@@ -71110,8 +71225,8 @@ async function readSessionMessages2(sessionID) {
71110
71225
  });
71111
71226
  }
71112
71227
  async function readParts2(messageID) {
71113
- const partDir = join76(PART_STORAGE, messageID);
71114
- if (!existsSync69(partDir))
71228
+ const partDir = join77(PART_STORAGE, messageID);
71229
+ if (!existsSync70(partDir))
71115
71230
  return [];
71116
71231
  const parts = [];
71117
71232
  try {
@@ -71120,7 +71235,7 @@ async function readParts2(messageID) {
71120
71235
  if (!file3.endsWith(".json"))
71121
71236
  continue;
71122
71237
  try {
71123
- const content = await readFile(join76(partDir, file3), "utf-8");
71238
+ const content = await readFile(join77(partDir, file3), "utf-8");
71124
71239
  parts.push(JSON.parse(content));
71125
71240
  } catch {
71126
71241
  continue;
@@ -71146,14 +71261,14 @@ async function readSessionTodos(sessionID) {
71146
71261
  return [];
71147
71262
  }
71148
71263
  }
71149
- if (!existsSync69(TODO_DIR2))
71264
+ if (!existsSync70(TODO_DIR2))
71150
71265
  return [];
71151
71266
  try {
71152
71267
  const allFiles = await readdir(TODO_DIR2);
71153
71268
  const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
71154
71269
  for (const file3 of todoFiles) {
71155
71270
  try {
71156
- const content = await readFile(join76(TODO_DIR2, file3), "utf-8");
71271
+ const content = await readFile(join77(TODO_DIR2, file3), "utf-8");
71157
71272
  const data = JSON.parse(content);
71158
71273
  if (Array.isArray(data)) {
71159
71274
  return data.map((item) => ({
@@ -71173,10 +71288,10 @@ async function readSessionTodos(sessionID) {
71173
71288
  return [];
71174
71289
  }
71175
71290
  async function readSessionTranscript(sessionID) {
71176
- if (!existsSync69(TRANSCRIPT_DIR2))
71291
+ if (!existsSync70(TRANSCRIPT_DIR2))
71177
71292
  return 0;
71178
- const transcriptFile = join76(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
71179
- if (!existsSync69(transcriptFile))
71293
+ const transcriptFile = join77(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
71294
+ if (!existsSync70(transcriptFile))
71180
71295
  return 0;
71181
71296
  try {
71182
71297
  const content = await readFile(transcriptFile, "utf-8");
@@ -72806,7 +72921,7 @@ function createCallOmoAgent(ctx, backgroundManager, disabledAgents = [], agentOv
72806
72921
  }
72807
72922
  // src/tools/look-at/constants.ts
72808
72923
  var MULTIMODAL_LOOKER_AGENT = "multimodal-looker";
72809
- var LOOK_AT_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.`;
72924
+ var LOOK_AT_DESCRIPTION = `Extract basic information from media files (PDFs, images, diagrams) when a quick summary suffices over precise reading. Good for simple text-based content extraction without using the Read tool. NEVER use for visual precision, aesthetic evaluation, or exact accuracy \u2014 use Read tool instead for those cases.`;
72810
72925
  // src/tools/look-at/tools.ts
72811
72926
  import { basename as basename8 } from "path";
72812
72927
  import { pathToFileURL as pathToFileURL3 } from "url";
@@ -73025,9 +73140,9 @@ async function resolveMultimodalLookerAgentMetadata(ctx) {
73025
73140
 
73026
73141
  // src/tools/look-at/image-converter.ts
73027
73142
  import { execFileSync as execFileSync3 } from "child_process";
73028
- import { existsSync as existsSync70, mkdtempSync, readFileSync as readFileSync46, rmSync as rmSync3, unlinkSync as unlinkSync11, writeFileSync as writeFileSync19 } from "fs";
73143
+ import { existsSync as existsSync71, mkdtempSync, readFileSync as readFileSync46, rmSync as rmSync3, unlinkSync as unlinkSync11, writeFileSync as writeFileSync19 } from "fs";
73029
73144
  import { tmpdir as tmpdir6 } from "os";
73030
- import { dirname as dirname21, join as join77 } from "path";
73145
+ import { dirname as dirname21, join as join78 } from "path";
73031
73146
  var SUPPORTED_FORMATS = new Set([
73032
73147
  "image/jpeg",
73033
73148
  "image/png",
@@ -73065,11 +73180,11 @@ function needsConversion(mimeType) {
73065
73180
  return mimeType.startsWith("image/");
73066
73181
  }
73067
73182
  function convertImageToJpeg(inputPath, mimeType) {
73068
- if (!existsSync70(inputPath)) {
73183
+ if (!existsSync71(inputPath)) {
73069
73184
  throw new Error(`File not found: ${inputPath}`);
73070
73185
  }
73071
- const tempDir = mkdtempSync(join77(tmpdir6(), "opencode-img-"));
73072
- const outputPath = join77(tempDir, "converted.jpg");
73186
+ const tempDir = mkdtempSync(join78(tmpdir6(), "opencode-img-"));
73187
+ const outputPath = join78(tempDir, "converted.jpg");
73073
73188
  log(`[image-converter] Converting ${mimeType} to JPEG: ${inputPath}`);
73074
73189
  try {
73075
73190
  if (process.platform === "darwin") {
@@ -73079,7 +73194,7 @@ function convertImageToJpeg(inputPath, mimeType) {
73079
73194
  encoding: "utf-8",
73080
73195
  timeout: CONVERSION_TIMEOUT_MS
73081
73196
  });
73082
- if (existsSync70(outputPath)) {
73197
+ if (existsSync71(outputPath)) {
73083
73198
  log(`[image-converter] Converted using sips: ${outputPath}`);
73084
73199
  return outputPath;
73085
73200
  }
@@ -73094,7 +73209,7 @@ function convertImageToJpeg(inputPath, mimeType) {
73094
73209
  encoding: "utf-8",
73095
73210
  timeout: CONVERSION_TIMEOUT_MS
73096
73211
  });
73097
- if (existsSync70(outputPath)) {
73212
+ if (existsSync71(outputPath)) {
73098
73213
  log(`[image-converter] Converted using ImageMagick: ${outputPath}`);
73099
73214
  return outputPath;
73100
73215
  }
@@ -73107,7 +73222,7 @@ function convertImageToJpeg(inputPath, mimeType) {
73107
73222
  ` + ` RHEL/CentOS: sudo yum install ImageMagick`);
73108
73223
  } catch (error92) {
73109
73224
  try {
73110
- if (existsSync70(outputPath)) {
73225
+ if (existsSync71(outputPath)) {
73111
73226
  unlinkSync11(outputPath);
73112
73227
  }
73113
73228
  } catch {}
@@ -73121,11 +73236,11 @@ function convertImageToJpeg(inputPath, mimeType) {
73121
73236
  function cleanupConvertedImage(filePath) {
73122
73237
  try {
73123
73238
  const tempDirectory = dirname21(filePath);
73124
- if (existsSync70(filePath)) {
73239
+ if (existsSync71(filePath)) {
73125
73240
  unlinkSync11(filePath);
73126
73241
  log(`[image-converter] Cleaned up temporary file: ${filePath}`);
73127
73242
  }
73128
- if (existsSync70(tempDirectory)) {
73243
+ if (existsSync71(tempDirectory)) {
73129
73244
  rmSync3(tempDirectory, { recursive: true, force: true });
73130
73245
  log(`[image-converter] Cleaned up temporary directory: ${tempDirectory}`);
73131
73246
  }
@@ -73134,9 +73249,9 @@ function cleanupConvertedImage(filePath) {
73134
73249
  }
73135
73250
  }
73136
73251
  function convertBase64ImageToJpeg(base64Data, mimeType) {
73137
- const tempDir = mkdtempSync(join77(tmpdir6(), "opencode-b64-"));
73252
+ const tempDir = mkdtempSync(join78(tmpdir6(), "opencode-b64-"));
73138
73253
  const inputExt = mimeType.split("/")[1] || "bin";
73139
- const inputPath = join77(tempDir, `input.${inputExt}`);
73254
+ const inputPath = join78(tempDir, `input.${inputExt}`);
73140
73255
  const tempFiles = [inputPath];
73141
73256
  try {
73142
73257
  const cleanBase64 = base64Data.replace(/^data:[^;]+;base64,/, "");
@@ -73152,7 +73267,7 @@ function convertBase64ImageToJpeg(base64Data, mimeType) {
73152
73267
  } catch (error92) {
73153
73268
  tempFiles.forEach((file3) => {
73154
73269
  try {
73155
- if (existsSync70(file3))
73270
+ if (existsSync71(file3))
73156
73271
  unlinkSync11(file3);
73157
73272
  } catch {}
73158
73273
  });
@@ -73671,7 +73786,7 @@ var POLL_INTERVAL_MS = 1000;
73671
73786
  var MIN_STABILITY_TIME_MS = 1e4;
73672
73787
  var STABILITY_POLLS_REQUIRED = 3;
73673
73788
  var WAIT_FOR_SESSION_INTERVAL_MS = 100;
73674
- var WAIT_FOR_SESSION_TIMEOUT_MS = 30000;
73789
+ var WAIT_FOR_SESSION_TIMEOUT_MS = 60000;
73675
73790
  var DEFAULT_POLL_TIMEOUT_MS = 30 * 60 * 1000;
73676
73791
  var MAX_POLL_TIME_MS = DEFAULT_POLL_TIMEOUT_MS;
73677
73792
  var SESSION_CONTINUATION_STABILITY_MS = 5000;
@@ -74663,7 +74778,7 @@ function resolveModelForDelegateTask(input) {
74663
74778
  return { model: userModel };
74664
74779
  }
74665
74780
  if (input.availableModels.size === 0 && !hasProviderModelsCache() && !hasConnectedProvidersCache()) {
74666
- return;
74781
+ return { skipped: true };
74667
74782
  }
74668
74783
  const categoryDefault = normalizeModel(input.categoryDefaultModel);
74669
74784
  const explicitHighBaseModel = categoryDefault ? getExplicitHighBaseModel(categoryDefault) : null;
@@ -74790,6 +74905,7 @@ Available categories: ${allCategoryNames}`
74790
74905
  let actualModel;
74791
74906
  let modelInfo;
74792
74907
  let categoryModel;
74908
+ let isModelResolutionSkipped = false;
74793
74909
  const overrideModel = sisyphusJuniorModel;
74794
74910
  const explicitCategoryModel = userCategories?.[args.category]?.model;
74795
74911
  if (!requirement) {
@@ -74806,7 +74922,9 @@ Available categories: ${allCategoryNames}`
74806
74922
  availableModels,
74807
74923
  systemDefaultModel
74808
74924
  });
74809
- if (resolution) {
74925
+ if (resolution && "skipped" in resolution) {
74926
+ isModelResolutionSkipped = true;
74927
+ } else if (resolution) {
74810
74928
  const { model: resolvedModel2, variant: resolvedVariant } = resolution;
74811
74929
  actualModel = resolvedModel2;
74812
74930
  if (!parseModelString(actualModel)) {
@@ -74941,7 +75059,7 @@ Create the work plan directly - that's your job as the planning agent.`
74941
75059
  availableModels,
74942
75060
  systemDefaultModel: undefined
74943
75061
  });
74944
- if (resolution) {
75062
+ if (resolution && !("skipped" in resolution)) {
74945
75063
  const normalized = normalizeModelFormat(resolution.model);
74946
75064
  if (normalized) {
74947
75065
  const variantToUse = agentOverride?.variant ?? resolution.variant;
@@ -75027,7 +75145,7 @@ function createDelegateTask(options) {
75027
75145
  Available categories:
75028
75146
  ${categoryList}
75029
75147
  - subagent_type: Use specific agent directly (explore, librarian, oracle, metis, momus)
75030
- - run_in_background: true=async (returns task_id), false=sync (waits). Default: false. Use background=true ONLY for parallel exploration with 5+ independent queries.
75148
+ - run_in_background: REQUIRED. true=async (returns task_id), false=sync (waits). Use background=true ONLY for parallel exploration with 5+ independent queries.
75031
75149
  - session_id: Existing Task session to continue (from previous task output). Continues agent with FULL CONTEXT PRESERVED - saves tokens, maintains continuity.
75032
75150
  - command: The command that triggered this task (optional, for slash command tracking).
75033
75151
 
@@ -75043,7 +75161,7 @@ function createDelegateTask(options) {
75043
75161
  load_skills: tool.schema.array(tool.schema.string()).describe("Skill names to inject. REQUIRED - pass [] if no skills needed."),
75044
75162
  description: tool.schema.string().describe("Short task description (3-5 words)"),
75045
75163
  prompt: tool.schema.string().describe("Full detailed prompt for the agent"),
75046
- run_in_background: tool.schema.boolean().describe("true=async (returns task_id), false=sync (waits). Default: false"),
75164
+ run_in_background: tool.schema.boolean().describe("REQUIRED. true=async (returns task_id), false=sync (waits). Use false for task delegation, true ONLY for parallel exploration."),
75047
75165
  category: tool.schema.string().optional().describe(`REQUIRED if subagent_type not provided. Do NOT provide both category and subagent_type.`),
75048
75166
  subagent_type: tool.schema.string().optional().describe("REQUIRED if category not provided. Do NOT provide both category and subagent_type."),
75049
75167
  session_id: tool.schema.string().optional().describe("Existing Task session to continue"),
@@ -75064,11 +75182,7 @@ function createDelegateTask(options) {
75064
75182
  title: args.description
75065
75183
  });
75066
75184
  if (args.run_in_background === undefined) {
75067
- if (args.category || args.subagent_type || args.session_id) {
75068
- args.run_in_background = false;
75069
- } else {
75070
- throw new Error(`Invalid arguments: 'run_in_background' parameter is REQUIRED. Use run_in_background=false for task delegation, run_in_background=true only for parallel exploration.`);
75071
- }
75185
+ throw new Error(`Invalid arguments: 'run_in_background' parameter is REQUIRED. Specify run_in_background=false for task delegation, or run_in_background=true for parallel exploration.`);
75072
75186
  }
75073
75187
  if (typeof args.load_skills === "string") {
75074
75188
  try {
@@ -75186,7 +75300,7 @@ function createDelegateTask(options) {
75186
75300
  // src/tools/delegate-task/index.ts
75187
75301
  init_constants();
75188
75302
  // src/tools/task/task-create.ts
75189
- import { join as join79 } from "path";
75303
+ import { join as join80 } from "path";
75190
75304
 
75191
75305
  // src/tools/task/types.ts
75192
75306
  var TaskStatusSchema = exports_external.enum(["pending", "in_progress", "completed", "deleted"]);
@@ -75240,18 +75354,18 @@ var TaskDeleteInputSchema = exports_external.object({
75240
75354
  });
75241
75355
 
75242
75356
  // src/features/claude-tasks/storage.ts
75243
- import { join as join78, dirname as dirname22, basename as basename9, isAbsolute as isAbsolute8 } from "path";
75244
- import { existsSync as existsSync71, mkdirSync as mkdirSync14, readFileSync as readFileSync47, writeFileSync as writeFileSync20, renameSync as renameSync2, unlinkSync as unlinkSync12, readdirSync as readdirSync19 } from "fs";
75357
+ import { join as join79, dirname as dirname22, basename as basename9, isAbsolute as isAbsolute8 } from "path";
75358
+ import { existsSync as existsSync72, mkdirSync as mkdirSync14, readFileSync as readFileSync47, writeFileSync as writeFileSync20, renameSync as renameSync2, unlinkSync as unlinkSync12, readdirSync as readdirSync19 } from "fs";
75245
75359
  import { randomUUID as randomUUID3 } from "crypto";
75246
75360
  function getTaskDir(config4 = {}) {
75247
75361
  const tasksConfig = config4.sisyphus?.tasks;
75248
75362
  const storagePath = tasksConfig?.storage_path;
75249
75363
  if (storagePath) {
75250
- return isAbsolute8(storagePath) ? storagePath : join78(process.cwd(), storagePath);
75364
+ return isAbsolute8(storagePath) ? storagePath : join79(process.cwd(), storagePath);
75251
75365
  }
75252
75366
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
75253
75367
  const listId = resolveTaskListId(config4);
75254
- return join78(configDir, "tasks", listId);
75368
+ return join79(configDir, "tasks", listId);
75255
75369
  }
75256
75370
  function sanitizePathSegment(value) {
75257
75371
  return value.replace(/[^a-zA-Z0-9_-]/g, "-") || "default";
@@ -75269,13 +75383,13 @@ function resolveTaskListId(config4 = {}) {
75269
75383
  return sanitizePathSegment(basename9(process.cwd()));
75270
75384
  }
75271
75385
  function ensureDir(dirPath) {
75272
- if (!existsSync71(dirPath)) {
75386
+ if (!existsSync72(dirPath)) {
75273
75387
  mkdirSync14(dirPath, { recursive: true });
75274
75388
  }
75275
75389
  }
75276
75390
  function readJsonSafe(filePath, schema2) {
75277
75391
  try {
75278
- if (!existsSync71(filePath)) {
75392
+ if (!existsSync72(filePath)) {
75279
75393
  return null;
75280
75394
  }
75281
75395
  const content = readFileSync47(filePath, "utf-8");
@@ -75298,7 +75412,7 @@ function writeJsonAtomic(filePath, data) {
75298
75412
  renameSync2(tempPath, filePath);
75299
75413
  } catch (error92) {
75300
75414
  try {
75301
- if (existsSync71(tempPath)) {
75415
+ if (existsSync72(tempPath)) {
75302
75416
  unlinkSync12(tempPath);
75303
75417
  }
75304
75418
  } catch {}
@@ -75310,7 +75424,7 @@ function generateTaskId() {
75310
75424
  return `T-${randomUUID3()}`;
75311
75425
  }
75312
75426
  function acquireLock(dirPath) {
75313
- const lockPath = join78(dirPath, ".lock");
75427
+ const lockPath = join79(dirPath, ".lock");
75314
75428
  const lockId = randomUUID3();
75315
75429
  const createLock = (timestamp2) => {
75316
75430
  writeFileSync20(lockPath, JSON.stringify({ id: lockId, timestamp: timestamp2 }), {
@@ -75358,7 +75472,7 @@ function acquireLock(dirPath) {
75358
75472
  acquired: true,
75359
75473
  release: () => {
75360
75474
  try {
75361
- if (!existsSync71(lockPath))
75475
+ if (!existsSync72(lockPath))
75362
75476
  return;
75363
75477
  const lockContent = readFileSync47(lockPath, "utf-8");
75364
75478
  const lockData = JSON.parse(lockContent);
@@ -75523,7 +75637,7 @@ async function handleCreate(args, config4, ctx, context) {
75523
75637
  threadID: context.sessionID
75524
75638
  };
75525
75639
  const validatedTask = TaskObjectSchema.parse(task);
75526
- writeJsonAtomic(join79(taskDir, `${taskId}.json`), validatedTask);
75640
+ writeJsonAtomic(join80(taskDir, `${taskId}.json`), validatedTask);
75527
75641
  await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
75528
75642
  return JSON.stringify({
75529
75643
  task: {
@@ -75545,7 +75659,7 @@ async function handleCreate(args, config4, ctx, context) {
75545
75659
  }
75546
75660
  }
75547
75661
  // src/tools/task/task-get.ts
75548
- import { join as join80 } from "path";
75662
+ import { join as join81 } from "path";
75549
75663
  var TASK_ID_PATTERN = /^T-[A-Za-z0-9-]+$/;
75550
75664
  function parseTaskId(id) {
75551
75665
  if (!TASK_ID_PATTERN.test(id))
@@ -75570,7 +75684,7 @@ Returns null if the task does not exist or the file is invalid.`,
75570
75684
  return JSON.stringify({ error: "invalid_task_id" });
75571
75685
  }
75572
75686
  const taskDir = getTaskDir(config4);
75573
- const taskPath = join80(taskDir, `${taskId}.json`);
75687
+ const taskPath = join81(taskDir, `${taskId}.json`);
75574
75688
  const task = readJsonSafe(taskPath, TaskObjectSchema);
75575
75689
  return JSON.stringify({ task: task ?? null });
75576
75690
  } catch (error92) {
@@ -75583,8 +75697,8 @@ Returns null if the task does not exist or the file is invalid.`,
75583
75697
  });
75584
75698
  }
75585
75699
  // src/tools/task/task-list.ts
75586
- import { join as join81 } from "path";
75587
- import { existsSync as existsSync72, readdirSync as readdirSync20 } from "fs";
75700
+ import { join as join82 } from "path";
75701
+ import { existsSync as existsSync73, readdirSync as readdirSync20 } from "fs";
75588
75702
  function createTaskList(config4) {
75589
75703
  return tool({
75590
75704
  description: `List all active tasks with summary information.
@@ -75595,7 +75709,7 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript
75595
75709
  args: {},
75596
75710
  execute: async () => {
75597
75711
  const taskDir = getTaskDir(config4);
75598
- if (!existsSync72(taskDir)) {
75712
+ if (!existsSync73(taskDir)) {
75599
75713
  return JSON.stringify({ tasks: [] });
75600
75714
  }
75601
75715
  const files = readdirSync20(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-")).map((f) => f.replace(".json", ""));
@@ -75604,7 +75718,7 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript
75604
75718
  }
75605
75719
  const allTasks = [];
75606
75720
  for (const fileId of files) {
75607
- const task = readJsonSafe(join81(taskDir, `${fileId}.json`), TaskObjectSchema);
75721
+ const task = readJsonSafe(join82(taskDir, `${fileId}.json`), TaskObjectSchema);
75608
75722
  if (task) {
75609
75723
  allTasks.push(task);
75610
75724
  }
@@ -75631,7 +75745,7 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript
75631
75745
  });
75632
75746
  }
75633
75747
  // src/tools/task/task-update.ts
75634
- import { join as join82 } from "path";
75748
+ import { join as join83 } from "path";
75635
75749
  var TASK_ID_PATTERN2 = /^T-[A-Za-z0-9-]+$/;
75636
75750
  function parseTaskId2(id) {
75637
75751
  if (!TASK_ID_PATTERN2.test(id))
@@ -75679,7 +75793,7 @@ async function handleUpdate(args, config4, ctx, context) {
75679
75793
  return JSON.stringify({ error: "task_lock_unavailable" });
75680
75794
  }
75681
75795
  try {
75682
- const taskPath = join82(taskDir, `${taskId}.json`);
75796
+ const taskPath = join83(taskDir, `${taskId}.json`);
75683
75797
  const task = readJsonSafe(taskPath, TaskObjectSchema);
75684
75798
  if (!task) {
75685
75799
  return JSON.stringify({ error: "task_not_found" });
@@ -77098,66 +77212,92 @@ WORKFLOW:
77098
77212
  4. If same file needs another call, re-read first.
77099
77213
  5. Use anchors as "LINE#ID" only (never include trailing "|content").
77100
77214
 
77101
- VALIDATION:
77102
- Payload shape: { "filePath": string, "edits": [...], "delete"?: boolean, "rename"?: string }
77103
- Each edit must be one of: replace, append, prepend
77104
- Edit shape: { "op": "replace"|"append"|"prepend", "pos"?: "LINE#ID", "end"?: "LINE#ID", "lines": string|string[]|null }
77105
- lines must contain plain replacement text only (no LINE#ID prefixes, no diff + markers)
77106
- CRITICAL: all operations validate against the same pre-edit file snapshot and apply bottom-up. Refs/tags are interpreted against the last-read version of the file.
77215
+ <must>
77216
+ - SNAPSHOT: All edits in one call reference the ORIGINAL file state. Do NOT adjust line numbers for prior edits in the same call \u2014 the system applies them bottom-up automatically.
77217
+ - replace removes lines pos..end (inclusive) and inserts lines in their place. Lines BEFORE pos and AFTER end are UNTOUCHED \u2014 do NOT include them in lines. If you do, they will appear twice.
77218
+ - lines must contain ONLY the content that belongs inside the consumed range. Content after end survives unchanged.
77219
+ - Tags MUST be copied exactly from read output or >>> mismatch output. NEVER guess tags.
77220
+ - Batch = multiple operations in edits[], NOT one big replace covering everything. Each operation targets the smallest possible change.
77221
+ - lines must contain plain replacement text only (no LINE#ID prefixes, no diff + markers).
77222
+ </must>
77223
+
77224
+ <operations>
77225
+ LINE#ID FORMAT:
77226
+ Each line reference must be in "{line_number}#{hash_id}" format where:
77227
+ {line_number}: 1-based line number
77228
+ {hash_id}: Two CID letters from the set ZPMQVRWSNKTXJBYH
77107
77229
 
77108
- LINE#ID FORMAT (CRITICAL):
77109
- Each line reference must be in "{line_number}#{hash_id}" format where:
77110
- {line_number}: 1-based line number
77111
- {hash_id}: Two CID letters from the set ZPMQVRWSNKTXJBYH
77112
-
77113
- FILE MODES:
77114
- delete=true deletes file and requires edits=[] with no rename
77115
- rename moves final content to a new path and removes old path
77230
+ OPERATION CHOICE:
77231
+ replace with pos only -> replace one line at pos
77232
+ replace with pos+end -> replace range pos..end inclusive as a block (ranges MUST NOT overlap across edits)
77233
+ append with pos/end anchor -> insert after that anchor
77234
+ prepend with pos/end anchor -> insert before that anchor
77235
+ append/prepend without anchors -> EOF/BOF insertion (also creates missing files)
77116
77236
 
77117
77237
  CONTENT FORMAT:
77118
77238
  lines can be a string (single line) or string[] (multi-line, preferred).
77119
77239
  If you pass a multi-line string, it is split by real newline characters.
77120
- Literal "\\n" is preserved as text.
77240
+ lines: null or lines: [] with replace -> delete those lines.
77121
77241
 
77122
- FILE CREATION:
77123
- append without anchors adds content at EOF. If file does not exist, creates it.
77124
- prepend without anchors adds content at BOF. If file does not exist, creates it.
77125
- CRITICAL: only unanchored append/prepend can create a missing file.
77242
+ FILE MODES:
77243
+ delete=true deletes file and requires edits=[] with no rename
77244
+ rename moves final content to a new path and removes old path
77126
77245
 
77127
- OPERATION CHOICE:
77128
- replace with pos only -> replace one line at pos
77129
- replace with pos+end -> replace ENTIRE range pos..end as a block (ranges MUST NOT overlap across edits)
77130
- append with pos/end anchor -> insert after that anchor
77131
- prepend with pos/end anchor -> insert before that anchor
77132
- append/prepend without anchors -> EOF/BOF insertion
77133
-
77134
- RULES (CRITICAL):
77135
- 1. Minimize scope: one logical mutation site per operation.
77136
- 2. Preserve formatting: keep indentation, punctuation, line breaks, trailing commas, brace style.
77137
- 3. Prefer insertion over neighbor rewrites: anchor to structural boundaries (}, ], },), not interior property lines.
77138
- 4. No no-ops: replacement content must differ from current content.
77139
- 5. Touch only requested code: avoid incidental edits.
77140
- 6. Use exact current tokens: NEVER rewrite approximately.
77141
- 7. For swaps/moves: prefer one range operation over multiple single-line operations.
77142
- 8. Output tool calls only; no prose or commentary between them.
77143
-
77144
- TAG CHOICE (ALWAYS):
77145
- - Copy tags exactly from read output or >>> mismatch output.
77146
- - NEVER guess tags.
77147
- - Anchor to structural lines (function/class/brace), NEVER blank lines.
77148
- - Anti-pattern warning: blank/whitespace anchors are fragile.
77149
- - Re-read after each successful edit call before issuing another on the same file.
77150
-
77151
- AUTOCORRECT (built-in - you do NOT need to handle these):
77152
- Merged lines are auto-expanded back to original line count.
77153
- Indentation is auto-restored from original lines.
77154
- BOM and CRLF line endings are preserved automatically.
77155
- Hashline prefixes and diff markers in text are auto-stripped.
77246
+ RULES:
77247
+ 1. Minimize scope: one logical mutation site per operation.
77248
+ 2. Preserve formatting: keep indentation, punctuation, line breaks, trailing commas, brace style.
77249
+ 3. Prefer insertion over neighbor rewrites: anchor to structural boundaries (}, ], },), not interior property lines.
77250
+ 4. No no-ops: replacement content must differ from current content.
77251
+ 5. Touch only requested code: avoid incidental edits.
77252
+ 6. Use exact current tokens: NEVER rewrite approximately.
77253
+ 7. For swaps/moves: prefer one range operation over multiple single-line operations.
77254
+ 8. Anchor to structural lines (function/class/brace), NEVER blank lines.
77255
+ 9. Re-read after each successful edit call before issuing another on the same file.
77256
+ </operations>
77257
+
77258
+ <examples>
77259
+ Given this file content after read:
77260
+ 10#VK|function hello() {
77261
+ 11#XJ| console.log("hi");
77262
+ 12#MB| console.log("bye");
77263
+ 13#QR|}
77264
+ 14#TN|
77265
+ 15#WS|function world() {
77266
+
77267
+ Single-line replace (change line 11):
77268
+ { op: "replace", pos: "11#XJ", lines: [" console.log(\\"hello\\");"] }
77269
+ Result: line 11 replaced. Lines 10, 12-15 unchanged.
77270
+
77271
+ Range replace (rewrite function body, lines 11-12):
77272
+ { op: "replace", pos: "11#XJ", end: "12#MB", lines: [" return \\"hello world\\";"] }
77273
+ Result: lines 11-12 removed, replaced by 1 new line. Lines 10, 13-15 unchanged.
77274
+
77275
+ Delete a line:
77276
+ { op: "replace", pos: "12#MB", lines: null }
77277
+ Result: line 12 removed. Lines 10-11, 13-15 unchanged.
77278
+
77279
+ Insert after line 13 (between functions):
77280
+ { op: "append", pos: "13#QR", lines: ["", "function added() {", " return true;", "}"] }
77281
+ Result: 4 new lines inserted after line 13. All existing lines unchanged.
77282
+
77283
+ BAD \u2014 lines extend past end (DUPLICATES line 13):
77284
+ { op: "replace", pos: "11#XJ", end: "12#MB", lines: [" return \\"hi\\";", "}"] }
77285
+ Line 13 is "}" which already exists after end. Including "}" in lines duplicates it.
77286
+ CORRECT: { op: "replace", pos: "11#XJ", end: "12#MB", lines: [" return \\"hi\\";"] }
77287
+ </examples>
77288
+
77289
+ <auto>
77290
+ Built-in autocorrect (you do NOT need to handle these):
77291
+ Merged lines are auto-expanded back to original line count.
77292
+ Indentation is auto-restored from original lines.
77293
+ BOM and CRLF line endings are preserved automatically.
77294
+ Hashline prefixes and diff markers in text are auto-stripped.
77295
+ Boundary echo lines (duplicating adjacent surviving lines) are auto-stripped.
77296
+ </auto>
77156
77297
 
77157
77298
  RECOVERY (when >>> mismatch error appears):
77158
- Copy the updated LINE#ID tags shown in the error output directly.
77159
- Re-read only if the needed tags are missing from the error snippet.
77160
- ALWAYS batch all edits for one file in a single call.`;
77299
+ Copy the updated LINE#ID tags shown in the error output directly.
77300
+ Re-read only if the needed tags are missing from the error snippet.`;
77161
77301
 
77162
77302
  // src/tools/hashline-edit/tools.ts
77163
77303
  function createHashlineEditTool() {
@@ -77175,7 +77315,7 @@ function createHashlineEditTool() {
77175
77315
  ]).describe("Hashline edit operation mode"),
77176
77316
  pos: tool.schema.string().optional().describe("Primary anchor in LINE#ID format"),
77177
77317
  end: tool.schema.string().optional().describe("Range end anchor in LINE#ID format"),
77178
- lines: tool.schema.union([tool.schema.string(), tool.schema.null()]).describe("Replacement or inserted lines as newline-delimited string. null deletes with replace")
77318
+ lines: tool.schema.union([tool.schema.array(tool.schema.string()), tool.schema.string(), tool.schema.null()]).describe("Replacement or inserted lines as newline-delimited string. null deletes with replace")
77179
77319
  })).describe("Array of edit operations to apply (empty when delete=true)")
77180
77320
  },
77181
77321
  execute: async (args, context) => executeHashlineEditTool(args, context)
@@ -77886,9 +78026,13 @@ class ConcurrencyManager {
77886
78026
 
77887
78027
  // src/features/background-agent/constants.ts
77888
78028
  var TASK_TTL_MS = 30 * 60 * 1000;
78029
+ var TERMINAL_TASK_TTL_MS = 30 * 60 * 1000;
77889
78030
  var MIN_STABILITY_TIME_MS2 = 10 * 1000;
77890
- var DEFAULT_STALE_TIMEOUT_MS = 180000;
78031
+ var DEFAULT_STALE_TIMEOUT_MS = 1200000;
77891
78032
  var DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 1800000;
78033
+ var DEFAULT_MAX_TOOL_CALLS = 200;
78034
+ var DEFAULT_CIRCUIT_BREAKER_WINDOW_SIZE = 20;
78035
+ var DEFAULT_CIRCUIT_BREAKER_REPETITION_THRESHOLD_PERCENT = 80;
77892
78036
  var MIN_RUNTIME_BEFORE_STALE_MS = 30000;
77893
78037
  var MIN_IDLE_TIME_MS = 5000;
77894
78038
  var POLLING_INTERVAL_MS = 3000;
@@ -78134,7 +78278,7 @@ function unregisterManagerForCleanup(manager) {
78134
78278
 
78135
78279
  // src/features/background-agent/compaction-aware-message-resolver.ts
78136
78280
  import { readdirSync as readdirSync21, readFileSync as readFileSync48 } from "fs";
78137
- import { join as join83 } from "path";
78281
+ import { join as join84 } from "path";
78138
78282
  function isCompactionAgent4(agent) {
78139
78283
  return agent?.trim().toLowerCase() === "compaction";
78140
78284
  }
@@ -78213,7 +78357,7 @@ function findNearestMessageExcludingCompaction(messageDir, sessionID) {
78213
78357
  const messages = [];
78214
78358
  for (const file3 of files) {
78215
78359
  try {
78216
- const content = readFileSync48(join83(messageDir, file3), "utf-8");
78360
+ const content = readFileSync48(join84(messageDir, file3), "utf-8");
78217
78361
  messages.push(JSON.parse(content));
78218
78362
  } catch {
78219
78363
  continue;
@@ -78299,7 +78443,7 @@ function handleSessionIdleBackgroundEvent(args) {
78299
78443
  }
78300
78444
 
78301
78445
  // src/features/background-agent/manager.ts
78302
- import { join as join84 } from "path";
78446
+ import { join as join85 } from "path";
78303
78447
 
78304
78448
  // src/features/background-agent/remove-task-toast-tracking.ts
78305
78449
  function removeTaskToastTracking(taskId) {
@@ -78310,7 +78454,6 @@ function removeTaskToastTracking(taskId) {
78310
78454
  }
78311
78455
 
78312
78456
  // src/features/background-agent/task-poller.ts
78313
- var TERMINAL_TASK_TTL_MS = 30 * 60 * 1000;
78314
78457
  var TERMINAL_TASK_STATUSES = new Set([
78315
78458
  "completed",
78316
78459
  "error",
@@ -78441,6 +78584,57 @@ async function checkAndInterruptStaleTasks(args) {
78441
78584
  }
78442
78585
  }
78443
78586
 
78587
+ // src/features/background-agent/loop-detector.ts
78588
+ function resolveCircuitBreakerSettings(config4) {
78589
+ return {
78590
+ maxToolCalls: config4?.circuitBreaker?.maxToolCalls ?? config4?.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS,
78591
+ windowSize: config4?.circuitBreaker?.windowSize ?? DEFAULT_CIRCUIT_BREAKER_WINDOW_SIZE,
78592
+ repetitionThresholdPercent: config4?.circuitBreaker?.repetitionThresholdPercent ?? DEFAULT_CIRCUIT_BREAKER_REPETITION_THRESHOLD_PERCENT
78593
+ };
78594
+ }
78595
+ function recordToolCall(window, toolName, settings) {
78596
+ const previous = window?.toolNames ?? [];
78597
+ const toolNames = [...previous, toolName].slice(-settings.windowSize);
78598
+ return {
78599
+ toolNames,
78600
+ windowSize: settings.windowSize,
78601
+ thresholdPercent: settings.repetitionThresholdPercent
78602
+ };
78603
+ }
78604
+ function detectRepetitiveToolUse(window) {
78605
+ if (!window || window.toolNames.length === 0) {
78606
+ return { triggered: false };
78607
+ }
78608
+ const counts = new Map;
78609
+ for (const toolName of window.toolNames) {
78610
+ counts.set(toolName, (counts.get(toolName) ?? 0) + 1);
78611
+ }
78612
+ let repeatedTool;
78613
+ let repeatedCount = 0;
78614
+ for (const [toolName, count] of counts.entries()) {
78615
+ if (count > repeatedCount) {
78616
+ repeatedTool = toolName;
78617
+ repeatedCount = count;
78618
+ }
78619
+ }
78620
+ const sampleSize = window.toolNames.length;
78621
+ const minimumSampleSize = Math.min(window.windowSize, Math.ceil(window.windowSize * window.thresholdPercent / 100));
78622
+ if (sampleSize < minimumSampleSize) {
78623
+ return { triggered: false };
78624
+ }
78625
+ const thresholdCount = Math.ceil(sampleSize * window.thresholdPercent / 100);
78626
+ if (!repeatedTool || repeatedCount < thresholdCount) {
78627
+ return { triggered: false };
78628
+ }
78629
+ return {
78630
+ triggered: true,
78631
+ toolName: repeatedTool,
78632
+ repeatedCount,
78633
+ sampleSize,
78634
+ thresholdPercent: window.thresholdPercent
78635
+ };
78636
+ }
78637
+
78444
78638
  // src/features/background-agent/subagent-spawn-limits.ts
78445
78639
  var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
78446
78640
  var DEFAULT_MAX_ROOT_SESSION_SPAWN_BUDGET = 50;
@@ -78499,6 +78693,18 @@ function createSubagentDescendantLimitError(input) {
78499
78693
  }
78500
78694
 
78501
78695
  // src/features/background-agent/manager.ts
78696
+ function resolveMessagePartInfo(properties) {
78697
+ if (!properties || typeof properties !== "object") {
78698
+ return;
78699
+ }
78700
+ const nestedPart = properties.part;
78701
+ if (nestedPart && typeof nestedPart === "object") {
78702
+ return nestedPart;
78703
+ }
78704
+ return properties;
78705
+ }
78706
+ var MAX_TASK_REMOVAL_RESCHEDULES = 6;
78707
+
78502
78708
  class BackgroundManager {
78503
78709
  tasks;
78504
78710
  notifications;
@@ -78987,6 +79193,8 @@ class BackgroundManager {
78987
79193
  existingTask.startedAt = new Date;
78988
79194
  existingTask.progress = {
78989
79195
  toolCalls: existingTask.progress?.toolCalls ?? 0,
79196
+ toolCallWindow: existingTask.progress?.toolCallWindow,
79197
+ countedToolPartIDs: existingTask.progress?.countedToolPartIDs,
78990
79198
  lastUpdate: new Date
78991
79199
  };
78992
79200
  this.startPolling();
@@ -79094,9 +79302,7 @@ class BackgroundManager {
79094
79302
  this.tryFallbackRetry(task, errorInfo, "message.updated");
79095
79303
  }
79096
79304
  if (event.type === "message.part.updated" || event.type === "message.part.delta") {
79097
- if (!props || typeof props !== "object" || !("sessionID" in props))
79098
- return;
79099
- const partInfo = props;
79305
+ const partInfo = resolveMessagePartInfo(props);
79100
79306
  const sessionID = partInfo?.sessionID;
79101
79307
  if (!sessionID)
79102
79308
  return;
@@ -79116,8 +79322,51 @@ class BackgroundManager {
79116
79322
  }
79117
79323
  task.progress.lastUpdate = new Date;
79118
79324
  if (partInfo?.type === "tool" || partInfo?.tool) {
79325
+ const countedToolPartIDs = task.progress.countedToolPartIDs ?? [];
79326
+ const shouldCountToolCall = !partInfo.id || partInfo.state?.status !== "running" || !countedToolPartIDs.includes(partInfo.id);
79327
+ if (!shouldCountToolCall) {
79328
+ return;
79329
+ }
79330
+ if (partInfo.id && partInfo.state?.status === "running") {
79331
+ task.progress.countedToolPartIDs = [...countedToolPartIDs, partInfo.id];
79332
+ }
79119
79333
  task.progress.toolCalls += 1;
79120
79334
  task.progress.lastTool = partInfo.tool;
79335
+ const circuitBreaker = resolveCircuitBreakerSettings(this.config);
79336
+ if (partInfo.tool) {
79337
+ task.progress.toolCallWindow = recordToolCall(task.progress.toolCallWindow, partInfo.tool, circuitBreaker);
79338
+ const loopDetection = detectRepetitiveToolUse(task.progress.toolCallWindow);
79339
+ if (loopDetection.triggered) {
79340
+ log("[background-agent] Circuit breaker: repetitive tool usage detected", {
79341
+ taskId: task.id,
79342
+ agent: task.agent,
79343
+ sessionID,
79344
+ toolName: loopDetection.toolName,
79345
+ repeatedCount: loopDetection.repeatedCount,
79346
+ sampleSize: loopDetection.sampleSize,
79347
+ thresholdPercent: loopDetection.thresholdPercent
79348
+ });
79349
+ this.cancelTask(task.id, {
79350
+ source: "circuit-breaker",
79351
+ reason: `Subagent repeatedly called ${loopDetection.toolName} ${loopDetection.repeatedCount}/${loopDetection.sampleSize} times in the recent tool-call window (${loopDetection.thresholdPercent}% threshold). This usually indicates an infinite loop. The task was automatically cancelled to prevent excessive token usage.`
79352
+ });
79353
+ return;
79354
+ }
79355
+ }
79356
+ const maxToolCalls = circuitBreaker.maxToolCalls;
79357
+ if (task.progress.toolCalls >= maxToolCalls) {
79358
+ log("[background-agent] Circuit breaker: tool call limit reached", {
79359
+ taskId: task.id,
79360
+ toolCalls: task.progress.toolCalls,
79361
+ maxToolCalls,
79362
+ agent: task.agent,
79363
+ sessionID
79364
+ });
79365
+ this.cancelTask(task.id, {
79366
+ source: "circuit-breaker",
79367
+ reason: `Subagent exceeded maximum tool call limit (${maxToolCalls}). This usually indicates an infinite loop. The task was automatically cancelled to prevent excessive token usage.`
79368
+ });
79369
+ }
79121
79370
  }
79122
79371
  }
79123
79372
  if (event.type === "session.idle") {
@@ -79362,7 +79611,7 @@ ${originalText}`;
79362
79611
  this.taskHistory.clearSession(parentSessionID);
79363
79612
  this.completedTaskSummaries.delete(parentSessionID);
79364
79613
  }
79365
- scheduleTaskRemoval(taskId) {
79614
+ scheduleTaskRemoval(taskId, rescheduleCount = 0) {
79366
79615
  const existingTimer = this.completionTimers.get(taskId);
79367
79616
  if (existingTimer) {
79368
79617
  clearTimeout(existingTimer);
@@ -79371,17 +79620,26 @@ ${originalText}`;
79371
79620
  const timer = setTimeout(() => {
79372
79621
  this.completionTimers.delete(taskId);
79373
79622
  const task = this.tasks.get(taskId);
79374
- if (task) {
79375
- this.clearNotificationsForTask(taskId);
79376
- this.tasks.delete(taskId);
79377
- this.clearTaskHistoryWhenParentTasksGone(task.parentSessionID);
79378
- if (task.sessionID) {
79379
- subagentSessions.delete(task.sessionID);
79380
- SessionCategoryRegistry.remove(task.sessionID);
79623
+ if (!task)
79624
+ return;
79625
+ if (task.parentSessionID) {
79626
+ const siblings = this.getTasksByParentSession(task.parentSessionID);
79627
+ const runningOrPendingSiblings = siblings.filter((sibling) => sibling.id !== taskId && (sibling.status === "running" || sibling.status === "pending"));
79628
+ const completedAtTimestamp = task.completedAt?.getTime();
79629
+ const reachedTaskTtl = completedAtTimestamp !== undefined && Date.now() - completedAtTimestamp >= TASK_TTL_MS;
79630
+ if (runningOrPendingSiblings.length > 0 && rescheduleCount < MAX_TASK_REMOVAL_RESCHEDULES && !reachedTaskTtl) {
79631
+ this.scheduleTaskRemoval(taskId, rescheduleCount + 1);
79632
+ return;
79381
79633
  }
79382
- log("[background-agent] Removed completed task from memory:", taskId);
79383
- this.clearTaskHistoryWhenParentTasksGone(task?.parentSessionID);
79384
79634
  }
79635
+ this.clearNotificationsForTask(taskId);
79636
+ this.tasks.delete(taskId);
79637
+ this.clearTaskHistoryWhenParentTasksGone(task.parentSessionID);
79638
+ if (task.sessionID) {
79639
+ subagentSessions.delete(task.sessionID);
79640
+ SessionCategoryRegistry.remove(task.sessionID);
79641
+ }
79642
+ log("[background-agent] Removed completed task from memory:", taskId);
79385
79643
  }, TASK_CLEANUP_DELAY_MS);
79386
79644
  this.completionTimers.set(taskId, timer);
79387
79645
  }
@@ -79602,7 +79860,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
79602
79860
  parentSessionID: task.parentSessionID
79603
79861
  });
79604
79862
  }
79605
- const messageDir = join84(MESSAGE_STORAGE, task.parentSessionID);
79863
+ const messageDir = join85(MESSAGE_STORAGE, task.parentSessionID);
79606
79864
  const currentMessage = messageDir ? findNearestMessageExcludingCompaction(messageDir, task.parentSessionID) : null;
79607
79865
  agent = currentMessage?.agent ?? task.parentAgent;
79608
79866
  model = currentMessage?.model?.providerID && currentMessage?.model?.modelID ? { providerID: currentMessage.model.providerID, modelID: currentMessage.model.modelID } : undefined;
@@ -83708,11 +83966,11 @@ class StreamableHTTPClientTransport {
83708
83966
  }
83709
83967
 
83710
83968
  // src/features/mcp-oauth/storage.ts
83711
- import { chmodSync as chmodSync2, existsSync as existsSync73, mkdirSync as mkdirSync15, readFileSync as readFileSync49, unlinkSync as unlinkSync13, writeFileSync as writeFileSync21 } from "fs";
83712
- import { dirname as dirname23, join as join85 } from "path";
83969
+ import { chmodSync as chmodSync2, existsSync as existsSync74, mkdirSync as mkdirSync15, readFileSync as readFileSync49, unlinkSync as unlinkSync13, writeFileSync as writeFileSync21 } from "fs";
83970
+ import { dirname as dirname23, join as join86 } from "path";
83713
83971
  var STORAGE_FILE_NAME = "mcp-oauth.json";
83714
83972
  function getMcpOauthStoragePath() {
83715
- return join85(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
83973
+ return join86(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
83716
83974
  }
83717
83975
  function normalizeHost(serverHost) {
83718
83976
  let host = serverHost.trim();
@@ -83749,7 +84007,7 @@ function buildKey(serverHost, resource) {
83749
84007
  }
83750
84008
  function readStore() {
83751
84009
  const filePath = getMcpOauthStoragePath();
83752
- if (!existsSync73(filePath)) {
84010
+ if (!existsSync74(filePath)) {
83753
84011
  return null;
83754
84012
  }
83755
84013
  try {
@@ -83763,7 +84021,7 @@ function writeStore(store2) {
83763
84021
  const filePath = getMcpOauthStoragePath();
83764
84022
  try {
83765
84023
  const dir = dirname23(filePath);
83766
- if (!existsSync73(dir)) {
84024
+ if (!existsSync74(dir)) {
83767
84025
  mkdirSync15(dir, { recursive: true });
83768
84026
  }
83769
84027
  writeFileSync21(filePath, JSON.stringify(store2, null, 2), { encoding: "utf-8", mode: 384 });
@@ -84565,7 +84823,19 @@ var EXCLUDED_ENV_PATTERNS = [
84565
84823
  /^npm_config_/,
84566
84824
  /^YARN_/,
84567
84825
  /^PNPM_/,
84568
- /^NO_UPDATE_NOTIFIER$/
84826
+ /^NO_UPDATE_NOTIFIER$/,
84827
+ /^ANTHROPIC_API_KEY$/i,
84828
+ /^AWS_ACCESS_KEY_ID$/i,
84829
+ /^AWS_SECRET_ACCESS_KEY$/i,
84830
+ /^GITHUB_TOKEN$/i,
84831
+ /^DATABASE_URL$/i,
84832
+ /^OPENAI_API_KEY$/i,
84833
+ /_KEY$/i,
84834
+ /_SECRET$/i,
84835
+ /_TOKEN$/i,
84836
+ /_PASSWORD$/i,
84837
+ /_CREDENTIAL$/i,
84838
+ /_API_KEY$/i
84569
84839
  ];
84570
84840
  function createCleanMcpEnvironment(customEnv = {}) {
84571
84841
  const cleanEnv = {};
@@ -91652,7 +91922,7 @@ function createHephaestusAgent2(model, availableAgents, availableToolNames, avai
91652
91922
  }
91653
91923
  createHephaestusAgent2.mode = MODE10;
91654
91924
  // src/agents/builtin-agents/resolve-file-uri.ts
91655
- import { existsSync as existsSync74, readFileSync as readFileSync50 } from "fs";
91925
+ import { existsSync as existsSync75, readFileSync as readFileSync50 } from "fs";
91656
91926
  import { homedir as homedir14 } from "os";
91657
91927
  import { isAbsolute as isAbsolute9, resolve as resolve15 } from "path";
91658
91928
  function resolvePromptAppend(promptAppend, configDir) {
@@ -91667,7 +91937,7 @@ function resolvePromptAppend(promptAppend, configDir) {
91667
91937
  } catch {
91668
91938
  return `[WARNING: Malformed file URI (invalid percent-encoding): ${promptAppend}]`;
91669
91939
  }
91670
- if (!existsSync74(filePath)) {
91940
+ if (!existsSync75(filePath)) {
91671
91941
  return `[WARNING: Could not resolve file URI: ${promptAppend}]`;
91672
91942
  }
91673
91943
  try {
@@ -92954,8 +93224,8 @@ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, dir
92954
93224
  return result;
92955
93225
  }
92956
93226
  // src/features/claude-code-agent-loader/loader.ts
92957
- import { existsSync as existsSync75, readdirSync as readdirSync22, readFileSync as readFileSync51 } from "fs";
92958
- import { join as join86, basename as basename10 } from "path";
93227
+ import { existsSync as existsSync76, readdirSync as readdirSync22, readFileSync as readFileSync51 } from "fs";
93228
+ import { join as join87, basename as basename10 } from "path";
92959
93229
  function parseToolsConfig2(toolsStr) {
92960
93230
  if (!toolsStr)
92961
93231
  return;
@@ -92969,7 +93239,7 @@ function parseToolsConfig2(toolsStr) {
92969
93239
  return result;
92970
93240
  }
92971
93241
  function loadAgentsFromDir(agentsDir, scope) {
92972
- if (!existsSync75(agentsDir)) {
93242
+ if (!existsSync76(agentsDir)) {
92973
93243
  return [];
92974
93244
  }
92975
93245
  const entries = readdirSync22(agentsDir, { withFileTypes: true });
@@ -92977,7 +93247,7 @@ function loadAgentsFromDir(agentsDir, scope) {
92977
93247
  for (const entry of entries) {
92978
93248
  if (!isMarkdownFile(entry))
92979
93249
  continue;
92980
- const agentPath = join86(agentsDir, entry.name);
93250
+ const agentPath = join87(agentsDir, entry.name);
92981
93251
  const agentName = basename10(entry.name, ".md");
92982
93252
  try {
92983
93253
  const content = readFileSync51(agentPath, "utf-8");
@@ -93010,7 +93280,7 @@ function loadAgentsFromDir(agentsDir, scope) {
93010
93280
  return agents;
93011
93281
  }
93012
93282
  function loadUserAgents() {
93013
- const userAgentsDir = join86(getClaudeConfigDir(), "agents");
93283
+ const userAgentsDir = join87(getClaudeConfigDir(), "agents");
93014
93284
  const agents = loadAgentsFromDir(userAgentsDir, "user");
93015
93285
  const result = {};
93016
93286
  for (const agent of agents) {
@@ -93019,7 +93289,7 @@ function loadUserAgents() {
93019
93289
  return result;
93020
93290
  }
93021
93291
  function loadProjectAgents(directory) {
93022
- const projectAgentsDir = join86(directory ?? process.cwd(), ".claude", "agents");
93292
+ const projectAgentsDir = join87(directory ?? process.cwd(), ".claude", "agents");
93023
93293
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
93024
93294
  const result = {};
93025
93295
  for (const agent of agents) {
@@ -95471,7 +95741,7 @@ async function applyAgentConfig(params) {
95471
95741
  }
95472
95742
  // src/features/claude-code-command-loader/loader.ts
95473
95743
  import { promises as fs19 } from "fs";
95474
- import { join as join87, basename as basename11 } from "path";
95744
+ import { join as join88, basename as basename11 } from "path";
95475
95745
  init_logger();
95476
95746
  async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix = "") {
95477
95747
  try {
@@ -95502,7 +95772,7 @@ async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix
95502
95772
  if (entry.isDirectory()) {
95503
95773
  if (entry.name.startsWith("."))
95504
95774
  continue;
95505
- const subDirPath = join87(commandsDir, entry.name);
95775
+ const subDirPath = join88(commandsDir, entry.name);
95506
95776
  const subPrefix = prefix ? `${prefix}:${entry.name}` : entry.name;
95507
95777
  const subCommands = await loadCommandsFromDir(subDirPath, scope, visited, subPrefix);
95508
95778
  commands3.push(...subCommands);
@@ -95510,7 +95780,7 @@ async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix
95510
95780
  }
95511
95781
  if (!isMarkdownFile(entry))
95512
95782
  continue;
95513
- const commandPath = join87(commandsDir, entry.name);
95783
+ const commandPath = join88(commandsDir, entry.name);
95514
95784
  const baseCommandName = basename11(entry.name, ".md");
95515
95785
  const commandName = prefix ? `${prefix}:${baseCommandName}` : baseCommandName;
95516
95786
  try {
@@ -95557,23 +95827,23 @@ function commandsToRecord(commands3) {
95557
95827
  return result;
95558
95828
  }
95559
95829
  async function loadUserCommands() {
95560
- const userCommandsDir = join87(getClaudeConfigDir(), "commands");
95830
+ const userCommandsDir = join88(getClaudeConfigDir(), "commands");
95561
95831
  const commands3 = await loadCommandsFromDir(userCommandsDir, "user");
95562
95832
  return commandsToRecord(commands3);
95563
95833
  }
95564
95834
  async function loadProjectCommands(directory) {
95565
- const projectCommandsDir = join87(directory ?? process.cwd(), ".claude", "commands");
95835
+ const projectCommandsDir = join88(directory ?? process.cwd(), ".claude", "commands");
95566
95836
  const commands3 = await loadCommandsFromDir(projectCommandsDir, "project");
95567
95837
  return commandsToRecord(commands3);
95568
95838
  }
95569
95839
  async function loadOpencodeGlobalCommands() {
95570
95840
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
95571
- const opencodeCommandsDir = join87(configDir, "command");
95841
+ const opencodeCommandsDir = join88(configDir, "command");
95572
95842
  const commands3 = await loadCommandsFromDir(opencodeCommandsDir, "opencode");
95573
95843
  return commandsToRecord(commands3);
95574
95844
  }
95575
95845
  async function loadOpencodeProjectCommands(directory) {
95576
- const opencodeProjectDir = join87(directory ?? process.cwd(), ".opencode", "command");
95846
+ const opencodeProjectDir = join88(directory ?? process.cwd(), ".opencode", "command");
95577
95847
  const commands3 = await loadCommandsFromDir(opencodeProjectDir, "opencode-project");
95578
95848
  return commandsToRecord(commands3);
95579
95849
  }
@@ -95632,22 +95902,22 @@ function remapCommandAgentFields(commands3) {
95632
95902
  }
95633
95903
  }
95634
95904
  // src/features/claude-code-mcp-loader/loader.ts
95635
- import { existsSync as existsSync76, readFileSync as readFileSync52 } from "fs";
95636
- import { join as join88 } from "path";
95905
+ import { existsSync as existsSync77, readFileSync as readFileSync52 } from "fs";
95906
+ import { join as join89 } from "path";
95637
95907
  import { homedir as homedir15 } from "os";
95638
95908
  init_logger();
95639
95909
  function getMcpConfigPaths() {
95640
95910
  const claudeConfigDir = getClaudeConfigDir();
95641
95911
  const cwd = process.cwd();
95642
95912
  return [
95643
- { path: join88(homedir15(), ".claude.json"), scope: "user" },
95644
- { path: join88(claudeConfigDir, ".mcp.json"), scope: "user" },
95645
- { path: join88(cwd, ".mcp.json"), scope: "project" },
95646
- { path: join88(cwd, ".claude", ".mcp.json"), scope: "local" }
95913
+ { path: join89(homedir15(), ".claude.json"), scope: "user" },
95914
+ { path: join89(claudeConfigDir, ".mcp.json"), scope: "user" },
95915
+ { path: join89(cwd, ".mcp.json"), scope: "project" },
95916
+ { path: join89(cwd, ".claude", ".mcp.json"), scope: "local" }
95647
95917
  ];
95648
95918
  }
95649
95919
  async function loadMcpConfigFile(filePath) {
95650
- if (!existsSync76(filePath)) {
95920
+ if (!existsSync77(filePath)) {
95651
95921
  return null;
95652
95922
  }
95653
95923
  try {
@@ -95662,7 +95932,7 @@ function getSystemMcpServerNames() {
95662
95932
  const names = new Set;
95663
95933
  const paths = getMcpConfigPaths();
95664
95934
  for (const { path: path12 } of paths) {
95665
- if (!existsSync76(path12))
95935
+ if (!existsSync77(path12))
95666
95936
  continue;
95667
95937
  try {
95668
95938
  const content = readFileSync52(path12, "utf-8");
@@ -96480,10 +96750,10 @@ function createChatHeadersHandler(args) {
96480
96750
 
96481
96751
  // src/plugin/ultrawork-db-model-override.ts
96482
96752
  import { Database } from "bun:sqlite";
96483
- import { join as join89 } from "path";
96484
- import { existsSync as existsSync77 } from "fs";
96753
+ import { join as join90 } from "path";
96754
+ import { existsSync as existsSync78 } from "fs";
96485
96755
  function getDbPath() {
96486
- return join89(getDataDir(), "opencode", "opencode.db");
96756
+ return join90(getDataDir(), "opencode", "opencode.db");
96487
96757
  }
96488
96758
  var MAX_MICROTASK_RETRIES = 10;
96489
96759
  function tryUpdateMessageModel(db, messageId, targetModel, variant) {
@@ -96560,7 +96830,7 @@ function retryViaMicrotask(db, messageId, targetModel, variant, attempt) {
96560
96830
  function scheduleDeferredModelOverride(messageId, targetModel, variant) {
96561
96831
  queueMicrotask(() => {
96562
96832
  const dbPath = getDbPath();
96563
- if (!existsSync77(dbPath)) {
96833
+ if (!existsSync78(dbPath)) {
96564
96834
  log("[ultrawork-db-override] DB not found, skipping deferred override");
96565
96835
  return;
96566
96836
  }
@@ -97172,6 +97442,9 @@ function createEventHandler2(args) {
97172
97442
  if (event.type === "session.status") {
97173
97443
  const sessionID = props?.sessionID;
97174
97444
  const status = props?.status;
97445
+ if (sessionID && status?.type === "idle") {
97446
+ lastHandledRetryStatusKey.delete(sessionID);
97447
+ }
97175
97448
  if (sessionID && status?.type === "retry" && isModelFallbackEnabled && !isRuntimeFallbackEnabled) {
97176
97449
  try {
97177
97450
  const retryMessage = typeof status.message === "string" ? status.message : "";
@@ -97294,11 +97567,37 @@ function createToolExecuteAfterHandler3(args) {
97294
97567
  const prompt = typeof output.metadata?.prompt === "string" ? output.metadata.prompt : undefined;
97295
97568
  const verificationAttemptId = prompt?.match(VERIFICATION_ATTEMPT_PATTERN)?.[1]?.trim();
97296
97569
  const loopState = directory ? readState(directory) : null;
97297
- if (agent === "oracle" && sessionId && verificationAttemptId && directory && loopState?.active === true && loopState.ultrawork === true && loopState.verification_pending === true && loopState.session_id === input.sessionID && loopState.verification_attempt_id === verificationAttemptId) {
97570
+ const isVerificationContext = agent === "oracle" && !!sessionId && !!directory && loopState?.active === true && loopState.ultrawork === true && loopState.verification_pending === true && loopState.session_id === input.sessionID;
97571
+ log("[tool-execute-after] ULW verification tracking check", {
97572
+ tool: input.tool,
97573
+ agent,
97574
+ parentSessionID: input.sessionID,
97575
+ oracleSessionID: sessionId,
97576
+ hasPromptInMetadata: typeof prompt === "string",
97577
+ extractedVerificationAttemptId: verificationAttemptId
97578
+ });
97579
+ if (isVerificationContext && verificationAttemptId && loopState.verification_attempt_id === verificationAttemptId) {
97580
+ writeState(directory, {
97581
+ ...loopState,
97582
+ verification_session_id: sessionId
97583
+ });
97584
+ log("[tool-execute-after] Stored oracle verification session via attempt match", {
97585
+ parentSessionID: input.sessionID,
97586
+ oracleSessionID: sessionId,
97587
+ verificationAttemptId
97588
+ });
97589
+ } else if (isVerificationContext && !verificationAttemptId) {
97298
97590
  writeState(directory, {
97299
97591
  ...loopState,
97300
97592
  verification_session_id: sessionId
97301
97593
  });
97594
+ log("[tool-execute-after] Fallback: stored oracle verification session without attempt match", {
97595
+ parentSessionID: input.sessionID,
97596
+ oracleSessionID: sessionId,
97597
+ hasPromptInMetadata: typeof prompt === "string",
97598
+ expectedAttemptId: loopState.verification_attempt_id,
97599
+ extractedAttemptId: verificationAttemptId
97600
+ });
97302
97601
  }
97303
97602
  }
97304
97603
  const runToolExecuteAfterHooks = async () => {
@@ -97416,6 +97715,12 @@ function createToolExecuteBeforeHandler3(args) {
97416
97715
  const shouldInjectOracleVerification = normalizedSubagentType === "oracle" && loopState?.active === true && loopState.ultrawork === true && loopState.verification_pending === true && loopState.session_id === input.sessionID;
97417
97716
  if (shouldInjectOracleVerification) {
97418
97717
  const verificationAttemptId = randomUUID4();
97718
+ log("[tool-execute-before] Injecting ULW oracle verification attempt", {
97719
+ sessionID: input.sessionID,
97720
+ callID: input.callID,
97721
+ verificationAttemptId,
97722
+ loopSessionID: loopState.session_id
97723
+ });
97419
97724
  writeState(ctx.directory, {
97420
97725
  ...loopState,
97421
97726
  verification_attempt_id: verificationAttemptId,