oh-my-opencode 3.5.0 → 3.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -8326,10 +8326,9 @@ var init_cached_version = __esm(() => {
8326
8326
 
8327
8327
  // src/hooks/auto-update-checker/checker/pinned-version-updater.ts
8328
8328
  import * as fs9 from "fs";
8329
- function updatePinnedVersion(configPath, oldEntry, newVersion) {
8329
+ function replacePluginEntry(configPath, oldEntry, newEntry) {
8330
8330
  try {
8331
8331
  const content = fs9.readFileSync(configPath, "utf-8");
8332
- const newEntry = `${PACKAGE_NAME3}@${newVersion}`;
8333
8332
  const pluginMatch = content.match(/"plugin"\s*:\s*\[/);
8334
8333
  if (!pluginMatch || pluginMatch.index === undefined) {
8335
8334
  log(`[auto-update-checker] No "plugin" array found in ${configPath}`);
@@ -8368,6 +8367,10 @@ function updatePinnedVersion(configPath, oldEntry, newVersion) {
8368
8367
  return false;
8369
8368
  }
8370
8369
  }
8370
+ function revertPinnedVersion(configPath, failedVersion, originalEntry) {
8371
+ const failedEntry = `${PACKAGE_NAME3}@${failedVersion}`;
8372
+ return replacePluginEntry(configPath, failedEntry, originalEntry);
8373
+ }
8371
8374
  var init_pinned_version_updater = __esm(() => {
8372
8375
  init_logger();
8373
8376
  init_constants3();
@@ -8642,23 +8645,23 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage) {
8642
8645
  return;
8643
8646
  }
8644
8647
  if (pluginInfo.isPinned) {
8645
- const updated = updatePinnedVersion(pluginInfo.configPath, pluginInfo.entry, latestVersion);
8646
- if (!updated) {
8647
- await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
8648
- log("[auto-update-checker] Failed to update pinned version in config");
8649
- return;
8650
- }
8651
- log(`[auto-update-checker] Config updated: ${pluginInfo.entry} \u2192 ${PACKAGE_NAME3}@${latestVersion}`);
8648
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
8649
+ log(`[auto-update-checker] User-pinned version detected (${pluginInfo.entry}), skipping auto-update. Notification only.`);
8650
+ return;
8652
8651
  }
8653
8652
  invalidatePackage(PACKAGE_NAME3);
8654
8653
  const installSuccess = await runBunInstallSafe();
8655
8654
  if (installSuccess) {
8656
8655
  await showAutoUpdatedToast(ctx, currentVersion, latestVersion);
8657
8656
  log(`[auto-update-checker] Update installed: ${currentVersion} \u2192 ${latestVersion}`);
8658
- } else {
8659
- await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
8660
- log("[auto-update-checker] bun install failed; update not installed (falling back to notification-only)");
8657
+ return;
8661
8658
  }
8659
+ if (pluginInfo.isPinned) {
8660
+ revertPinnedVersion(pluginInfo.configPath, latestVersion, pluginInfo.entry);
8661
+ log("[auto-update-checker] Config reverted due to install failure");
8662
+ }
8663
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
8664
+ log("[auto-update-checker] bun install failed; update not installed (falling back to notification-only)");
8662
8665
  }
8663
8666
  var init_background_update_check = __esm(() => {
8664
8667
  init_config_manager();
@@ -8867,7 +8870,7 @@ var {
8867
8870
  // package.json
8868
8871
  var package_default = {
8869
8872
  name: "oh-my-opencode",
8870
- version: "3.5.0",
8873
+ version: "3.5.2",
8871
8874
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
8872
8875
  main: "dist/index.js",
8873
8876
  types: "dist/index.d.ts",
@@ -8941,13 +8944,13 @@ var package_default = {
8941
8944
  typescript: "^5.7.3"
8942
8945
  },
8943
8946
  optionalDependencies: {
8944
- "oh-my-opencode-darwin-arm64": "3.5.0",
8945
- "oh-my-opencode-darwin-x64": "3.5.0",
8946
- "oh-my-opencode-linux-arm64": "3.5.0",
8947
- "oh-my-opencode-linux-arm64-musl": "3.5.0",
8948
- "oh-my-opencode-linux-x64": "3.5.0",
8949
- "oh-my-opencode-linux-x64-musl": "3.5.0",
8950
- "oh-my-opencode-windows-x64": "3.5.0"
8947
+ "oh-my-opencode-darwin-arm64": "3.5.2",
8948
+ "oh-my-opencode-darwin-x64": "3.5.2",
8949
+ "oh-my-opencode-linux-arm64": "3.5.2",
8950
+ "oh-my-opencode-linux-arm64-musl": "3.5.2",
8951
+ "oh-my-opencode-linux-x64": "3.5.2",
8952
+ "oh-my-opencode-linux-x64-musl": "3.5.2",
8953
+ "oh-my-opencode-windows-x64": "3.5.2"
8951
8954
  },
8952
8955
  trustedDependencies: [
8953
8956
  "@ast-grep/cli",
@@ -22716,7 +22719,8 @@ var CategoryConfigSchema = exports_external.object({
22716
22719
  textVerbosity: exports_external.enum(["low", "medium", "high"]).optional(),
22717
22720
  tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
22718
22721
  prompt_append: exports_external.string().optional(),
22719
- is_unstable_agent: exports_external.boolean().optional()
22722
+ is_unstable_agent: exports_external.boolean().optional(),
22723
+ disable: exports_external.boolean().optional()
22720
22724
  });
22721
22725
  var BuiltinCategoryNameSchema = exports_external.enum([
22722
22726
  "visual-engineering",
@@ -27,6 +27,7 @@ export declare const CategoryConfigSchema: z.ZodObject<{
27
27
  tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
28
28
  prompt_append: z.ZodOptional<z.ZodString>;
29
29
  is_unstable_agent: z.ZodOptional<z.ZodBoolean>;
30
+ disable: z.ZodOptional<z.ZodBoolean>;
30
31
  }, z.core.$strip>;
31
32
  export declare const BuiltinCategoryNameSchema: z.ZodEnum<{
32
33
  "visual-engineering": "visual-engineering";
@@ -66,6 +67,7 @@ export declare const CategoriesConfigSchema: z.ZodRecord<z.ZodString, z.ZodObjec
66
67
  tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
67
68
  prompt_append: z.ZodOptional<z.ZodString>;
68
69
  is_unstable_agent: z.ZodOptional<z.ZodBoolean>;
70
+ disable: z.ZodOptional<z.ZodBoolean>;
69
71
  }, z.core.$strip>>;
70
72
  export type CategoryConfig = z.infer<typeof CategoryConfigSchema>;
71
73
  export type CategoriesConfig = z.infer<typeof CategoriesConfigSchema>;
@@ -1158,6 +1158,7 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
1158
1158
  tools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
1159
1159
  prompt_append: z.ZodOptional<z.ZodString>;
1160
1160
  is_unstable_agent: z.ZodOptional<z.ZodBoolean>;
1161
+ disable: z.ZodOptional<z.ZodBoolean>;
1161
1162
  }, z.core.$strip>>>;
1162
1163
  claude_code: z.ZodOptional<z.ZodObject<{
1163
1164
  mcp: z.ZodOptional<z.ZodBoolean>;
@@ -1 +1,2 @@
1
1
  export declare function updatePinnedVersion(configPath: string, oldEntry: string, newVersion: string): boolean;
2
+ export declare function revertPinnedVersion(configPath: string, failedVersion: string, originalEntry: string): boolean;
@@ -3,6 +3,6 @@ export { getLocalDevVersion } from "./checker/local-dev-version";
3
3
  export { findPluginEntry } from "./checker/plugin-entry";
4
4
  export type { PluginEntryInfo } from "./checker/plugin-entry";
5
5
  export { getCachedVersion } from "./checker/cached-version";
6
- export { updatePinnedVersion } from "./checker/pinned-version-updater";
6
+ export { updatePinnedVersion, revertPinnedVersion } from "./checker/pinned-version-updater";
7
7
  export { getLatestVersion } from "./checker/latest-version";
8
8
  export { checkForUpdate } from "./checker/check-for-update";
package/dist/index.js CHANGED
@@ -12252,7 +12252,14 @@ function readBoulderState(directory) {
12252
12252
  }
12253
12253
  try {
12254
12254
  const content = readFileSync(filePath, "utf-8");
12255
- return JSON.parse(content);
12255
+ const parsed = JSON.parse(content);
12256
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
12257
+ return null;
12258
+ }
12259
+ if (!Array.isArray(parsed.session_ids)) {
12260
+ parsed.session_ids = [];
12261
+ }
12262
+ return parsed;
12256
12263
  } catch {
12257
12264
  return null;
12258
12265
  }
@@ -12274,7 +12281,10 @@ function appendSessionId(directory, sessionId) {
12274
12281
  const state = readBoulderState(directory);
12275
12282
  if (!state)
12276
12283
  return null;
12277
- if (!state.session_ids.includes(sessionId)) {
12284
+ if (!state.session_ids?.includes(sessionId)) {
12285
+ if (!Array.isArray(state.session_ids)) {
12286
+ state.session_ids = [];
12287
+ }
12278
12288
  state.session_ids.push(sessionId);
12279
12289
  if (writeBoulderState(directory, state)) {
12280
12290
  return state;
@@ -26785,7 +26795,7 @@ function createCommentCheckerHooks(config2) {
26785
26795
  "tool.execute.after": async (input, output) => {
26786
26796
  debugLog3("tool.execute.after:", { tool: input.tool, callID: input.callID });
26787
26797
  const toolLower = input.tool.toLowerCase();
26788
- const outputLower = output.output.toLowerCase();
26798
+ const outputLower = (output.output ?? "").toLowerCase();
26789
26799
  const isToolFailure = outputLower.includes("error:") || outputLower.includes("failed to") || outputLower.includes("could not") || outputLower.startsWith("error");
26790
26800
  if (isToolFailure) {
26791
26801
  debugLog3("skipping due to tool failure in output");
@@ -34207,6 +34217,63 @@ async function enforceMainPaneWidth(mainPaneId, windowWidth) {
34207
34217
  }
34208
34218
  // src/shared/model-suggestion-retry.ts
34209
34219
  init_logger();
34220
+ function extractMessage(error45) {
34221
+ if (typeof error45 === "string")
34222
+ return error45;
34223
+ if (error45 instanceof Error)
34224
+ return error45.message;
34225
+ if (typeof error45 === "object" && error45 !== null) {
34226
+ const obj = error45;
34227
+ if (typeof obj.message === "string")
34228
+ return obj.message;
34229
+ try {
34230
+ return JSON.stringify(error45);
34231
+ } catch {
34232
+ return "";
34233
+ }
34234
+ }
34235
+ return String(error45);
34236
+ }
34237
+ function parseModelSuggestion(error45) {
34238
+ if (!error45)
34239
+ return null;
34240
+ if (typeof error45 === "object") {
34241
+ const errObj = error45;
34242
+ if (errObj.name === "ProviderModelNotFoundError" && typeof errObj.data === "object" && errObj.data !== null) {
34243
+ const data = errObj.data;
34244
+ const suggestions = data.suggestions;
34245
+ if (Array.isArray(suggestions) && suggestions.length > 0 && typeof suggestions[0] === "string") {
34246
+ return {
34247
+ providerID: String(data.providerID ?? ""),
34248
+ modelID: String(data.modelID ?? ""),
34249
+ suggestion: suggestions[0]
34250
+ };
34251
+ }
34252
+ return null;
34253
+ }
34254
+ for (const key of ["data", "error", "cause"]) {
34255
+ const nested = errObj[key];
34256
+ if (nested && typeof nested === "object") {
34257
+ const result = parseModelSuggestion(nested);
34258
+ if (result)
34259
+ return result;
34260
+ }
34261
+ }
34262
+ }
34263
+ const message = extractMessage(error45);
34264
+ if (!message)
34265
+ return null;
34266
+ const modelMatch = message.match(/model not found:\s*([^/\s]+)\s*\/\s*([^.\s]+)/i);
34267
+ const suggestionMatch = message.match(/did you mean:\s*([^,?]+)/i);
34268
+ if (modelMatch && suggestionMatch) {
34269
+ return {
34270
+ providerID: modelMatch[1].trim(),
34271
+ modelID: modelMatch[2].trim(),
34272
+ suggestion: suggestionMatch[1].trim()
34273
+ };
34274
+ }
34275
+ return null;
34276
+ }
34210
34277
  async function promptWithModelSuggestionRetry(client, args) {
34211
34278
  const promptPromise = client.session.promptAsync(args);
34212
34279
  let timeoutID = null;
@@ -34222,6 +34289,30 @@ async function promptWithModelSuggestionRetry(client, args) {
34222
34289
  clearTimeout(timeoutID);
34223
34290
  }
34224
34291
  }
34292
+ async function promptSyncWithModelSuggestionRetry(client, args) {
34293
+ try {
34294
+ await client.session.prompt(args);
34295
+ } catch (error45) {
34296
+ const suggestion = parseModelSuggestion(error45);
34297
+ if (!suggestion || !args.body.model) {
34298
+ throw error45;
34299
+ }
34300
+ log("[model-suggestion-retry] Model not found, retrying with suggestion", {
34301
+ original: `${suggestion.providerID}/${suggestion.modelID}`,
34302
+ suggested: suggestion.suggestion
34303
+ });
34304
+ await client.session.prompt({
34305
+ ...args,
34306
+ body: {
34307
+ ...args.body,
34308
+ model: {
34309
+ providerID: suggestion.providerID,
34310
+ modelID: suggestion.suggestion
34311
+ }
34312
+ }
34313
+ });
34314
+ }
34315
+ }
34225
34316
  // src/shared/opencode-server-auth.ts
34226
34317
  function getServerBasicAuthHeader() {
34227
34318
  const password = process.env.OPENCODE_SERVER_PASSWORD;
@@ -36308,10 +36399,9 @@ function getCachedVersion() {
36308
36399
  // src/hooks/auto-update-checker/checker/pinned-version-updater.ts
36309
36400
  init_logger();
36310
36401
  import * as fs12 from "fs";
36311
- function updatePinnedVersion(configPath, oldEntry, newVersion) {
36402
+ function replacePluginEntry(configPath, oldEntry, newEntry) {
36312
36403
  try {
36313
36404
  const content = fs12.readFileSync(configPath, "utf-8");
36314
- const newEntry = `${PACKAGE_NAME}@${newVersion}`;
36315
36405
  const pluginMatch = content.match(/"plugin"\s*:\s*\[/);
36316
36406
  if (!pluginMatch || pluginMatch.index === undefined) {
36317
36407
  log(`[auto-update-checker] No "plugin" array found in ${configPath}`);
@@ -36350,6 +36440,10 @@ function updatePinnedVersion(configPath, oldEntry, newVersion) {
36350
36440
  return false;
36351
36441
  }
36352
36442
  }
36443
+ function revertPinnedVersion(configPath, failedVersion, originalEntry) {
36444
+ const failedEntry = `${PACKAGE_NAME}@${failedVersion}`;
36445
+ return replacePluginEntry(configPath, failedEntry, originalEntry);
36446
+ }
36353
36447
  // src/hooks/auto-update-checker/checker/latest-version.ts
36354
36448
  async function getLatestVersion(channel = "latest") {
36355
36449
  const controller = new AbortController;
@@ -36592,23 +36686,23 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage) {
36592
36686
  return;
36593
36687
  }
36594
36688
  if (pluginInfo.isPinned) {
36595
- const updated = updatePinnedVersion(pluginInfo.configPath, pluginInfo.entry, latestVersion);
36596
- if (!updated) {
36597
- await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
36598
- log("[auto-update-checker] Failed to update pinned version in config");
36599
- return;
36600
- }
36601
- log(`[auto-update-checker] Config updated: ${pluginInfo.entry} \u2192 ${PACKAGE_NAME}@${latestVersion}`);
36689
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
36690
+ log(`[auto-update-checker] User-pinned version detected (${pluginInfo.entry}), skipping auto-update. Notification only.`);
36691
+ return;
36602
36692
  }
36603
36693
  invalidatePackage(PACKAGE_NAME);
36604
36694
  const installSuccess = await runBunInstallSafe();
36605
36695
  if (installSuccess) {
36606
36696
  await showAutoUpdatedToast(ctx, currentVersion, latestVersion);
36607
36697
  log(`[auto-update-checker] Update installed: ${currentVersion} \u2192 ${latestVersion}`);
36608
- } else {
36609
- await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
36610
- log("[auto-update-checker] bun install failed; update not installed (falling back to notification-only)");
36698
+ return;
36611
36699
  }
36700
+ if (pluginInfo.isPinned) {
36701
+ revertPinnedVersion(pluginInfo.configPath, latestVersion, pluginInfo.entry);
36702
+ log("[auto-update-checker] Config reverted due to install failure");
36703
+ }
36704
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
36705
+ log("[auto-update-checker] bun install failed; update not installed (falling back to notification-only)");
36612
36706
  }
36613
36707
 
36614
36708
  // src/hooks/auto-update-checker/hook/config-errors-toast.ts
@@ -42751,7 +42845,7 @@ function createEditErrorRecoveryHook(_ctx) {
42751
42845
  "tool.execute.after": async (input, output) => {
42752
42846
  if (input.tool.toLowerCase() !== "edit")
42753
42847
  return;
42754
- const outputLower = output.output.toLowerCase();
42848
+ const outputLower = (output.output ?? "").toLowerCase();
42755
42849
  const hasEditError = EDIT_ERROR_PATTERNS.some((pattern) => outputLower.includes(pattern.toLowerCase()));
42756
42850
  if (hasEditError) {
42757
42851
  output.output += `
@@ -42884,7 +42978,7 @@ function getAgentFromSession(sessionID, directory) {
42884
42978
  if (memoryAgent)
42885
42979
  return memoryAgent;
42886
42980
  const boulderState = readBoulderState(directory);
42887
- if (boulderState?.session_ids.includes(sessionID) && boulderState.agent) {
42981
+ if (boulderState?.session_ids?.includes(sessionID) && boulderState.agent) {
42888
42982
  return boulderState.agent;
42889
42983
  }
42890
42984
  return getAgentFromMessageFiles(sessionID);
@@ -43039,15 +43133,16 @@ function createTaskResumeInfoHook() {
43039
43133
  const toolExecuteAfter = async (input, output) => {
43040
43134
  if (!TARGET_TOOLS2.includes(input.tool))
43041
43135
  return;
43042
- if (output.output.startsWith("Error:") || output.output.startsWith("Failed"))
43136
+ const outputText = output.output ?? "";
43137
+ if (outputText.startsWith("Error:") || outputText.startsWith("Failed"))
43043
43138
  return;
43044
- if (output.output.includes(`
43139
+ if (outputText.includes(`
43045
43140
  to continue:`))
43046
43141
  return;
43047
- const sessionId = extractSessionId(output.output);
43142
+ const sessionId = extractSessionId(outputText);
43048
43143
  if (!sessionId)
43049
43144
  return;
43050
- output.output = output.output.trimEnd() + `
43145
+ output.output = outputText.trimEnd() + `
43051
43146
 
43052
43147
  to continue: task(session_id="${sessionId}", prompt="...")`;
43053
43148
  };
@@ -43548,7 +43643,7 @@ function createAtlasEventHandler(input) {
43548
43643
  return;
43549
43644
  log(`[${HOOK_NAME7}] session.idle`, { sessionID });
43550
43645
  const boulderState = readBoulderState(ctx.directory);
43551
- const isBoulderSession = boulderState?.session_ids.includes(sessionID) ?? false;
43646
+ const isBoulderSession = boulderState?.session_ids?.includes(sessionID) ?? false;
43552
43647
  const isBackgroundTaskSession = subagentSessions.has(sessionID);
43553
43648
  if (!isBackgroundTaskSession && !isBoulderSession) {
43554
43649
  log(`[${HOOK_NAME7}] Skipped: not boulder or background task session`, { sessionID });
@@ -43717,7 +43812,23 @@ function buildOrchestratorReminder(planName, progress, sessionId) {
43717
43812
 
43718
43813
  ${buildVerificationReminder(sessionId)}
43719
43814
 
43720
- **STEP 5: CHECK BOULDER STATE DIRECTLY (EVERY TIME \u2014 NO EXCEPTIONS)**
43815
+ **STEP 5: READ SUBAGENT NOTEPAD (LEARNINGS, ISSUES, PROBLEMS)**
43816
+
43817
+ The subagent was instructed to record findings in notepad files. Read them NOW:
43818
+ \`\`\`
43819
+ Glob(".sisyphus/notepads/${planName}/*.md")
43820
+ \`\`\`
43821
+ Then \`Read\` each file found \u2014 especially:
43822
+ - **learnings.md**: Patterns, conventions, successful approaches discovered
43823
+ - **issues.md**: Problems, blockers, gotchas encountered during work
43824
+ - **problems.md**: Unresolved issues, technical debt flagged
43825
+
43826
+ **USE this information to:**
43827
+ - Inform your next delegation (avoid known pitfalls)
43828
+ - Adjust your plan if blockers were discovered
43829
+ - Propagate learnings to subsequent subagents
43830
+
43831
+ **STEP 6: CHECK BOULDER STATE DIRECTLY (EVERY TIME \u2014 NO EXCEPTIONS)**
43721
43832
 
43722
43833
  Do NOT rely on cached progress. Read the plan file NOW:
43723
43834
  \`\`\`
@@ -43726,7 +43837,7 @@ Read(".sisyphus/plans/${planName}.md")
43726
43837
  Count exactly: how many \`- [ ]\` remain? How many \`- [x]\` completed?
43727
43838
  This is YOUR ground truth. Use it to decide what comes next.
43728
43839
 
43729
- **STEP 6: MARK COMPLETION IN PLAN FILE (IMMEDIATELY)**
43840
+ **STEP 7: MARK COMPLETION IN PLAN FILE (IMMEDIATELY)**
43730
43841
 
43731
43842
  RIGHT NOW - Do not delay. Verification passed \u2192 Mark IMMEDIATELY.
43732
43843
 
@@ -43736,12 +43847,12 @@ Update the plan file \`.sisyphus/plans/${planName}.md\`:
43736
43847
 
43737
43848
  **DO THIS BEFORE ANYTHING ELSE. Unmarked = Untracked = Lost progress.**
43738
43849
 
43739
- **STEP 7: COMMIT ATOMIC UNIT**
43850
+ **STEP 8: COMMIT ATOMIC UNIT**
43740
43851
 
43741
43852
  - Stage ONLY the verified changes
43742
43853
  - Commit with clear message describing what was done
43743
43854
 
43744
- **STEP 8: PROCEED TO NEXT TASK**
43855
+ **STEP 9: PROCEED TO NEXT TASK**
43745
43856
 
43746
43857
  - Read the plan file AGAIN to identify the next \`- [ ]\` task
43747
43858
  - Start immediately - DO NOT STOP
@@ -43835,7 +43946,7 @@ function createToolExecuteAfterHandler2(input) {
43835
43946
  const boulderState = readBoulderState(ctx.directory);
43836
43947
  if (boulderState) {
43837
43948
  const progress = getPlanProgress(boulderState.active_plan);
43838
- if (toolInput.sessionID && !boulderState.session_ids.includes(toolInput.sessionID)) {
43949
+ if (toolInput.sessionID && !boulderState.session_ids?.includes(toolInput.sessionID)) {
43839
43950
  appendSessionId(ctx.directory, toolInput.sessionID);
43840
43951
  log(`[${HOOK_NAME7}] Appended session to boulder`, {
43841
43952
  sessionID: toolInput.sessionID,
@@ -49445,10 +49556,7 @@ async function createOrGetSession(args, toolContext, ctx) {
49445
49556
  const createResult = await ctx.client.session.create({
49446
49557
  body: {
49447
49558
  parentID: toolContext.sessionID,
49448
- title: `${args.description} (@${args.subagent_type} subagent)`,
49449
- permission: [
49450
- { permission: "question", action: "deny", pattern: "*" }
49451
- ]
49559
+ title: `${args.description} (@${args.subagent_type} subagent)`
49452
49560
  },
49453
49561
  query: {
49454
49562
  directory: parentDirectory
@@ -49471,6 +49579,7 @@ Original error: ${createResult.error}`);
49471
49579
  }
49472
49580
  const sessionID = createResult.data.id;
49473
49581
  log(`[call_omo_agent] Created session: ${sessionID}`);
49582
+ subagentSessions.add(sessionID);
49474
49583
  return { sessionID, isNew: true };
49475
49584
  }
49476
49585
  }
@@ -49656,30 +49765,6 @@ var LOOK_AT_DESCRIPTION = `Analyze media files (PDFs, images, diagrams) that req
49656
49765
  import { basename as basename5 } from "path";
49657
49766
  import { pathToFileURL as pathToFileURL3 } from "url";
49658
49767
 
49659
- // src/tools/look-at/session-poller.ts
49660
- var DEFAULT_POLL_INTERVAL_MS = 1000;
49661
- var DEFAULT_TIMEOUT_MS6 = 120000;
49662
- async function pollSessionUntilIdle(client2, sessionID, options) {
49663
- const pollInterval = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
49664
- const timeout = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS6;
49665
- const startTime = Date.now();
49666
- while (Date.now() - startTime < timeout) {
49667
- const statusResult = await client2.session.status().catch((error45) => {
49668
- log(`[look_at] session.status error (treating as idle):`, error45);
49669
- return { data: undefined, error: error45 };
49670
- });
49671
- if (statusResult.error || !statusResult.data) {
49672
- return;
49673
- }
49674
- const sessionStatus = statusResult.data[sessionID];
49675
- if (!sessionStatus || sessionStatus.type === "idle") {
49676
- return;
49677
- }
49678
- await new Promise((resolve11) => setTimeout(resolve11, pollInterval));
49679
- }
49680
- throw new Error(`[look_at] Polling timed out after ${timeout}ms waiting for session ${sessionID} to become idle`);
49681
- }
49682
-
49683
49768
  // src/tools/look-at/assistant-message-extractor.ts
49684
49769
  function isObject3(value) {
49685
49770
  return typeof value === "object" && value !== null;
@@ -49925,9 +50010,9 @@ Original error: ${createResult.error}`;
49925
50010
  const sessionID = createResult.data.id;
49926
50011
  log(`[look_at] Created session: ${sessionID}`);
49927
50012
  const { agentModel, agentVariant } = await resolveMultimodalLookerAgentMetadata(ctx);
49928
- log(`[look_at] Sending async prompt with ${isBase64Input ? "base64 image" : "file"} to session ${sessionID}`);
50013
+ log(`[look_at] Sending prompt with ${isBase64Input ? "base64 image" : "file"} to session ${sessionID}`);
49929
50014
  try {
49930
- await promptWithModelSuggestionRetry(ctx.client, {
50015
+ await promptSyncWithModelSuggestionRetry(ctx.client, {
49931
50016
  path: { id: sessionID },
49932
50017
  body: {
49933
50018
  agent: MULTIMODAL_LOOKER_AGENT,
@@ -49946,14 +50031,7 @@ Original error: ${createResult.error}`;
49946
50031
  }
49947
50032
  });
49948
50033
  } catch (promptError) {
49949
- log(`[look_at] promptAsync error:`, promptError);
49950
- return `Error: Failed to send prompt to multimodal-looker agent: ${promptError instanceof Error ? promptError.message : String(promptError)}`;
49951
- }
49952
- log(`[look_at] Polling session ${sessionID} until idle...`);
49953
- try {
49954
- await pollSessionUntilIdle(ctx.client, sessionID, { pollIntervalMs: 500, timeoutMs: 120000 });
49955
- } catch (pollError) {
49956
- log(`[look_at] Polling error (will still try to fetch messages):`, pollError);
50034
+ log(`[look_at] Prompt error (ignored, will still fetch messages):`, promptError);
49957
50035
  }
49958
50036
  log(`[look_at] Fetching messages from session ${sessionID}...`);
49959
50037
  const messagesResult = await ctx.client.session.messages({
@@ -49977,6 +50055,15 @@ Original error: ${createResult.error}`;
49977
50055
  }
49978
50056
  // src/tools/delegate-task/tools.ts
49979
50057
  init_constants();
50058
+
50059
+ // src/shared/merge-categories.ts
50060
+ init_constants();
50061
+ function mergeCategories(userCategories) {
50062
+ const merged = userCategories ? { ...DEFAULT_CATEGORIES, ...userCategories } : { ...DEFAULT_CATEGORIES };
50063
+ return Object.fromEntries(Object.entries(merged).filter(([, config3]) => !config3.disable));
50064
+ }
50065
+
50066
+ // src/tools/delegate-task/tools.ts
49980
50067
  init_logger();
49981
50068
 
49982
50069
  // src/tools/delegate-task/prompt-builder.ts
@@ -50798,10 +50885,7 @@ async function createSyncSession(client2, input) {
50798
50885
  const createResult = await client2.session.create({
50799
50886
  body: {
50800
50887
  parentID: input.parentSessionID,
50801
- title: `${input.description} (@${input.agentToUse} subagent)`,
50802
- permission: [
50803
- { permission: "question", action: "deny", pattern: "*" }
50804
- ]
50888
+ title: `${input.description} (@${input.agentToUse} subagent)`
50805
50889
  },
50806
50890
  query: {
50807
50891
  directory: parentDirectory
@@ -50985,9 +51069,6 @@ session_id: ${sessionID}
50985
51069
  }
50986
51070
  }
50987
51071
  }
50988
- // src/tools/delegate-task/category-resolver.ts
50989
- init_constants();
50990
-
50991
51072
  // src/tools/delegate-task/sisyphus-junior-agent.ts
50992
51073
  var SISYPHUS_JUNIOR_AGENT2 = "sisyphus-junior";
50993
51074
 
@@ -50999,6 +51080,9 @@ function resolveCategoryConfig(categoryName, options) {
50999
51080
  const defaultConfig = DEFAULT_CATEGORIES[categoryName];
51000
51081
  const userConfig = userCategories?.[categoryName];
51001
51082
  const hasExplicitUserConfig = userConfig !== undefined;
51083
+ if (userConfig?.disable) {
51084
+ return null;
51085
+ }
51002
51086
  const categoryReq = CATEGORY_MODEL_REQUIREMENTS[categoryName];
51003
51087
  if (categoryReq?.requiresModel && availableModels && !hasExplicitUserConfig) {
51004
51088
  if (!isModelAvailable(categoryReq.requiresModel, availableModels)) {
@@ -51148,7 +51232,8 @@ async function resolveCategoryExecution(args, executorCtx, inheritedModel, syste
51148
51232
  const { client: client2, userCategories, sisyphusJuniorModel } = executorCtx;
51149
51233
  const availableModels = await getAvailableModelsForDelegateTask(client2);
51150
51234
  const categoryName = args.category;
51151
- const categoryExists = DEFAULT_CATEGORIES[categoryName] !== undefined || userCategories?.[categoryName] !== undefined;
51235
+ const enabledCategories = mergeCategories(userCategories);
51236
+ const categoryExists = enabledCategories[categoryName] !== undefined;
51152
51237
  const resolved = resolveCategoryConfig(categoryName, {
51153
51238
  userCategories,
51154
51239
  inheritedModel,
@@ -51157,7 +51242,7 @@ async function resolveCategoryExecution(args, executorCtx, inheritedModel, syste
51157
51242
  });
51158
51243
  if (!resolved) {
51159
51244
  const requirement2 = CATEGORY_MODEL_REQUIREMENTS[categoryName];
51160
- const allCategoryNames = Object.keys({ ...DEFAULT_CATEGORIES, ...userCategories }).join(", ");
51245
+ const allCategoryNames = Object.keys(enabledCategories).join(", ");
51161
51246
  if (categoryExists && requirement2?.requiresModel) {
51162
51247
  return {
51163
51248
  agentToUse: "",
@@ -51232,7 +51317,7 @@ Available categories: ${allCategoryNames}`
51232
51317
  }
51233
51318
  const categoryPromptAppend = resolved.promptAppend || undefined;
51234
51319
  if (!categoryModel && !actualModel) {
51235
- const categoryNames = Object.keys({ ...DEFAULT_CATEGORIES, ...userCategories });
51320
+ const categoryNames = Object.keys(enabledCategories);
51236
51321
  return {
51237
51322
  agentToUse: "",
51238
51323
  categoryModel: undefined,
@@ -51342,7 +51427,7 @@ Create the work plan directly - that's your job as the planning agent.`
51342
51427
  // src/tools/delegate-task/tools.ts
51343
51428
  function createDelegateTask(options) {
51344
51429
  const { userCategories } = options;
51345
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
51430
+ const allCategories = mergeCategories(userCategories);
51346
51431
  const categoryNames = Object.keys(allCategories);
51347
51432
  const categoryExamples = categoryNames.map((k) => `'${k}'`).join(", ");
51348
51433
  const availableCategories = options.availableCategories ?? Object.entries(allCategories).map(([name, categoryConfig]) => {
@@ -52714,14 +52799,10 @@ class BackgroundManager {
52714
52799
  });
52715
52800
  const parentDirectory = parentSession?.data?.directory ?? this.directory;
52716
52801
  log(`[background-agent] Parent dir: ${parentSession?.data?.directory}, using: ${parentDirectory}`);
52717
- const inheritedPermission = parentSession?.data?.permission;
52718
- const permissionRules = Array.isArray(inheritedPermission) ? inheritedPermission.filter((r) => r?.permission !== "question") : [];
52719
- permissionRules.push({ permission: "question", action: "deny", pattern: "*" });
52720
52802
  const createResult = await this.client.session.create({
52721
52803
  body: {
52722
52804
  parentID: input.parentSessionID,
52723
- title: `${input.description} (@${input.agent} subagent)`,
52724
- permission: permissionRules
52805
+ title: `${input.description} (@${input.agent} subagent)`
52725
52806
  },
52726
52807
  query: {
52727
52808
  directory: parentDirectory
@@ -62180,7 +62261,7 @@ ${rows.join(`
62180
62261
  `)}`;
62181
62262
  }
62182
62263
  function buildCategorySection(userCategories) {
62183
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
62264
+ const allCategories = mergeCategories(userCategories);
62184
62265
  const categoryRows = Object.entries(allCategories).map(([name, config3]) => {
62185
62266
  const temp = config3.temperature ?? 0.5;
62186
62267
  return `| \`${name}\` | ${temp} | ${getCategoryDescription(name, userCategories)} |`;
@@ -62256,7 +62337,7 @@ task(category="[category]", load_skills=["skill-1", "skill-2"], run_in_backgroun
62256
62337
  - Missing a relevant skill = suboptimal output quality`;
62257
62338
  }
62258
62339
  function buildDecisionMatrix(agents, userCategories) {
62259
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
62340
+ const allCategories = mergeCategories(userCategories);
62260
62341
  const categoryRows = Object.entries(allCategories).map(([name]) => `| ${getCategoryDescription(name, userCategories)} | \`category="${name}", load_skills=[...]\` |`);
62261
62342
  const agentRows = agents.map((a) => {
62262
62343
  const shortDesc = truncateDescription(a.description);
@@ -62274,7 +62355,6 @@ ${agentRows.join(`
62274
62355
  **NEVER provide both category AND agent - they are mutually exclusive.**`;
62275
62356
  }
62276
62357
  // src/agents/atlas/agent.ts
62277
- init_constants();
62278
62358
  var MODE7 = "primary";
62279
62359
  function getAtlasPromptSource(model) {
62280
62360
  if (model && isGptModel2(model)) {
@@ -62297,7 +62377,7 @@ function buildDynamicOrchestratorPrompt(ctx) {
62297
62377
  const skills = ctx?.availableSkills ?? [];
62298
62378
  const userCategories = ctx?.userCategories;
62299
62379
  const model = ctx?.model;
62300
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
62380
+ const allCategories = mergeCategories(userCategories);
62301
62381
  const availableCategories = Object.entries(allCategories).map(([name]) => ({
62302
62382
  name,
62303
62383
  description: getCategoryDescription(name, userCategories)
@@ -63173,13 +63253,12 @@ function buildAvailableSkills(discoveredSkills, browserProvider, disabledSkills)
63173
63253
  }
63174
63254
 
63175
63255
  // src/agents/agent-builder.ts
63176
- init_constants();
63177
63256
  function isFactory(source) {
63178
63257
  return typeof source === "function";
63179
63258
  }
63180
63259
  function buildAgent(source, model, categories, gitMasterConfig, browserProvider, disabledSkills) {
63181
63260
  const base = isFactory(source) ? source(model) : { ...source };
63182
- const categoryConfigs = categories ? { ...DEFAULT_CATEGORIES, ...categories } : DEFAULT_CATEGORIES;
63261
+ const categoryConfigs = mergeCategories(categories);
63183
63262
  const agentWithCategory = base;
63184
63263
  if (agentWithCategory.category) {
63185
63264
  const categoryConfig = categoryConfigs[agentWithCategory.category];
@@ -63589,7 +63668,7 @@ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, dir
63589
63668
  });
63590
63669
  const isFirstRunNoCache = availableModels.size === 0 && (!connectedProviders || connectedProviders.length === 0);
63591
63670
  const result = {};
63592
- const mergedCategories = categories ? { ...DEFAULT_CATEGORIES, ...categories } : DEFAULT_CATEGORIES;
63671
+ const mergedCategories = mergeCategories(categories);
63593
63672
  const availableCategories = Object.entries(mergedCategories).map(([name]) => ({
63594
63673
  name,
63595
63674
  description: categories?.[name]?.description ?? CATEGORY_DESCRIPTIONS[name] ?? "General tasks"
@@ -66632,8 +66711,8 @@ function createManagers(args) {
66632
66711
  // src/plugin/available-categories.ts
66633
66712
  init_constants();
66634
66713
  function createAvailableCategories(pluginConfig) {
66635
- const mergedCategories = pluginConfig.categories ? { ...DEFAULT_CATEGORIES, ...pluginConfig.categories } : DEFAULT_CATEGORIES;
66636
- return Object.entries(mergedCategories).map(([name, categoryConfig]) => {
66714
+ const categories = mergeCategories(pluginConfig.categories);
66715
+ return Object.entries(categories).map(([name, categoryConfig]) => {
66637
66716
  const model = typeof categoryConfig.model === "string" ? categoryConfig.model : undefined;
66638
66717
  return {
66639
66718
  name,
@@ -67381,7 +67460,8 @@ var CategoryConfigSchema = exports_external.object({
67381
67460
  textVerbosity: exports_external.enum(["low", "medium", "high"]).optional(),
67382
67461
  tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
67383
67462
  prompt_append: exports_external.string().optional(),
67384
- is_unstable_agent: exports_external.boolean().optional()
67463
+ is_unstable_agent: exports_external.boolean().optional(),
67464
+ disable: exports_external.boolean().optional()
67385
67465
  });
67386
67466
  var BuiltinCategoryNameSchema = exports_external.enum([
67387
67467
  "visual-engineering",
@@ -0,0 +1,6 @@
1
+ import type { CategoriesConfig, CategoryConfig } from "../config/schema";
2
+ /**
3
+ * Merge default and user categories, filtering out disabled ones.
4
+ * Single source of truth for category merging across the codebase.
5
+ */
6
+ export declare function mergeCategories(userCategories?: CategoriesConfig): Record<string, CategoryConfig>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode",
3
- "version": "3.5.0",
3
+ "version": "3.5.2",
4
4
  "description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -74,13 +74,13 @@
74
74
  "typescript": "^5.7.3"
75
75
  },
76
76
  "optionalDependencies": {
77
- "oh-my-opencode-darwin-arm64": "3.5.0",
78
- "oh-my-opencode-darwin-x64": "3.5.0",
79
- "oh-my-opencode-linux-arm64": "3.5.0",
80
- "oh-my-opencode-linux-arm64-musl": "3.5.0",
81
- "oh-my-opencode-linux-x64": "3.5.0",
82
- "oh-my-opencode-linux-x64-musl": "3.5.0",
83
- "oh-my-opencode-windows-x64": "3.5.0"
77
+ "oh-my-opencode-darwin-arm64": "3.5.2",
78
+ "oh-my-opencode-darwin-x64": "3.5.2",
79
+ "oh-my-opencode-linux-arm64": "3.5.2",
80
+ "oh-my-opencode-linux-arm64-musl": "3.5.2",
81
+ "oh-my-opencode-linux-x64": "3.5.2",
82
+ "oh-my-opencode-linux-x64-musl": "3.5.2",
83
+ "oh-my-opencode-windows-x64": "3.5.2"
84
84
  },
85
85
  "trustedDependencies": [
86
86
  "@ast-grep/cli",