oh-my-opencode 3.5.1 → 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.1",
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.1",
8945
- "oh-my-opencode-darwin-x64": "3.5.1",
8946
- "oh-my-opencode-linux-arm64": "3.5.1",
8947
- "oh-my-opencode-linux-arm64-musl": "3.5.1",
8948
- "oh-my-opencode-linux-x64": "3.5.1",
8949
- "oh-my-opencode-linux-x64-musl": "3.5.1",
8950
- "oh-my-opencode-windows-x64": "3.5.1"
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");
@@ -36389,10 +36399,9 @@ function getCachedVersion() {
36389
36399
  // src/hooks/auto-update-checker/checker/pinned-version-updater.ts
36390
36400
  init_logger();
36391
36401
  import * as fs12 from "fs";
36392
- function updatePinnedVersion(configPath, oldEntry, newVersion) {
36402
+ function replacePluginEntry(configPath, oldEntry, newEntry) {
36393
36403
  try {
36394
36404
  const content = fs12.readFileSync(configPath, "utf-8");
36395
- const newEntry = `${PACKAGE_NAME}@${newVersion}`;
36396
36405
  const pluginMatch = content.match(/"plugin"\s*:\s*\[/);
36397
36406
  if (!pluginMatch || pluginMatch.index === undefined) {
36398
36407
  log(`[auto-update-checker] No "plugin" array found in ${configPath}`);
@@ -36431,6 +36440,10 @@ function updatePinnedVersion(configPath, oldEntry, newVersion) {
36431
36440
  return false;
36432
36441
  }
36433
36442
  }
36443
+ function revertPinnedVersion(configPath, failedVersion, originalEntry) {
36444
+ const failedEntry = `${PACKAGE_NAME}@${failedVersion}`;
36445
+ return replacePluginEntry(configPath, failedEntry, originalEntry);
36446
+ }
36434
36447
  // src/hooks/auto-update-checker/checker/latest-version.ts
36435
36448
  async function getLatestVersion(channel = "latest") {
36436
36449
  const controller = new AbortController;
@@ -36673,23 +36686,23 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage) {
36673
36686
  return;
36674
36687
  }
36675
36688
  if (pluginInfo.isPinned) {
36676
- const updated = updatePinnedVersion(pluginInfo.configPath, pluginInfo.entry, latestVersion);
36677
- if (!updated) {
36678
- await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
36679
- log("[auto-update-checker] Failed to update pinned version in config");
36680
- return;
36681
- }
36682
- 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;
36683
36692
  }
36684
36693
  invalidatePackage(PACKAGE_NAME);
36685
36694
  const installSuccess = await runBunInstallSafe();
36686
36695
  if (installSuccess) {
36687
36696
  await showAutoUpdatedToast(ctx, currentVersion, latestVersion);
36688
36697
  log(`[auto-update-checker] Update installed: ${currentVersion} \u2192 ${latestVersion}`);
36689
- } else {
36690
- await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
36691
- log("[auto-update-checker] bun install failed; update not installed (falling back to notification-only)");
36698
+ return;
36692
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)");
36693
36706
  }
36694
36707
 
36695
36708
  // src/hooks/auto-update-checker/hook/config-errors-toast.ts
@@ -42832,7 +42845,7 @@ function createEditErrorRecoveryHook(_ctx) {
42832
42845
  "tool.execute.after": async (input, output) => {
42833
42846
  if (input.tool.toLowerCase() !== "edit")
42834
42847
  return;
42835
- const outputLower = output.output.toLowerCase();
42848
+ const outputLower = (output.output ?? "").toLowerCase();
42836
42849
  const hasEditError = EDIT_ERROR_PATTERNS.some((pattern) => outputLower.includes(pattern.toLowerCase()));
42837
42850
  if (hasEditError) {
42838
42851
  output.output += `
@@ -42965,7 +42978,7 @@ function getAgentFromSession(sessionID, directory) {
42965
42978
  if (memoryAgent)
42966
42979
  return memoryAgent;
42967
42980
  const boulderState = readBoulderState(directory);
42968
- if (boulderState?.session_ids.includes(sessionID) && boulderState.agent) {
42981
+ if (boulderState?.session_ids?.includes(sessionID) && boulderState.agent) {
42969
42982
  return boulderState.agent;
42970
42983
  }
42971
42984
  return getAgentFromMessageFiles(sessionID);
@@ -43120,15 +43133,16 @@ function createTaskResumeInfoHook() {
43120
43133
  const toolExecuteAfter = async (input, output) => {
43121
43134
  if (!TARGET_TOOLS2.includes(input.tool))
43122
43135
  return;
43123
- if (output.output.startsWith("Error:") || output.output.startsWith("Failed"))
43136
+ const outputText = output.output ?? "";
43137
+ if (outputText.startsWith("Error:") || outputText.startsWith("Failed"))
43124
43138
  return;
43125
- if (output.output.includes(`
43139
+ if (outputText.includes(`
43126
43140
  to continue:`))
43127
43141
  return;
43128
- const sessionId = extractSessionId(output.output);
43142
+ const sessionId = extractSessionId(outputText);
43129
43143
  if (!sessionId)
43130
43144
  return;
43131
- output.output = output.output.trimEnd() + `
43145
+ output.output = outputText.trimEnd() + `
43132
43146
 
43133
43147
  to continue: task(session_id="${sessionId}", prompt="...")`;
43134
43148
  };
@@ -43629,7 +43643,7 @@ function createAtlasEventHandler(input) {
43629
43643
  return;
43630
43644
  log(`[${HOOK_NAME7}] session.idle`, { sessionID });
43631
43645
  const boulderState = readBoulderState(ctx.directory);
43632
- const isBoulderSession = boulderState?.session_ids.includes(sessionID) ?? false;
43646
+ const isBoulderSession = boulderState?.session_ids?.includes(sessionID) ?? false;
43633
43647
  const isBackgroundTaskSession = subagentSessions.has(sessionID);
43634
43648
  if (!isBackgroundTaskSession && !isBoulderSession) {
43635
43649
  log(`[${HOOK_NAME7}] Skipped: not boulder or background task session`, { sessionID });
@@ -43798,7 +43812,23 @@ function buildOrchestratorReminder(planName, progress, sessionId) {
43798
43812
 
43799
43813
  ${buildVerificationReminder(sessionId)}
43800
43814
 
43801
- **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)**
43802
43832
 
43803
43833
  Do NOT rely on cached progress. Read the plan file NOW:
43804
43834
  \`\`\`
@@ -43807,7 +43837,7 @@ Read(".sisyphus/plans/${planName}.md")
43807
43837
  Count exactly: how many \`- [ ]\` remain? How many \`- [x]\` completed?
43808
43838
  This is YOUR ground truth. Use it to decide what comes next.
43809
43839
 
43810
- **STEP 6: MARK COMPLETION IN PLAN FILE (IMMEDIATELY)**
43840
+ **STEP 7: MARK COMPLETION IN PLAN FILE (IMMEDIATELY)**
43811
43841
 
43812
43842
  RIGHT NOW - Do not delay. Verification passed \u2192 Mark IMMEDIATELY.
43813
43843
 
@@ -43817,12 +43847,12 @@ Update the plan file \`.sisyphus/plans/${planName}.md\`:
43817
43847
 
43818
43848
  **DO THIS BEFORE ANYTHING ELSE. Unmarked = Untracked = Lost progress.**
43819
43849
 
43820
- **STEP 7: COMMIT ATOMIC UNIT**
43850
+ **STEP 8: COMMIT ATOMIC UNIT**
43821
43851
 
43822
43852
  - Stage ONLY the verified changes
43823
43853
  - Commit with clear message describing what was done
43824
43854
 
43825
- **STEP 8: PROCEED TO NEXT TASK**
43855
+ **STEP 9: PROCEED TO NEXT TASK**
43826
43856
 
43827
43857
  - Read the plan file AGAIN to identify the next \`- [ ]\` task
43828
43858
  - Start immediately - DO NOT STOP
@@ -43916,7 +43946,7 @@ function createToolExecuteAfterHandler2(input) {
43916
43946
  const boulderState = readBoulderState(ctx.directory);
43917
43947
  if (boulderState) {
43918
43948
  const progress = getPlanProgress(boulderState.active_plan);
43919
- if (toolInput.sessionID && !boulderState.session_ids.includes(toolInput.sessionID)) {
43949
+ if (toolInput.sessionID && !boulderState.session_ids?.includes(toolInput.sessionID)) {
43920
43950
  appendSessionId(ctx.directory, toolInput.sessionID);
43921
43951
  log(`[${HOOK_NAME7}] Appended session to boulder`, {
43922
43952
  sessionID: toolInput.sessionID,
@@ -49526,10 +49556,7 @@ async function createOrGetSession(args, toolContext, ctx) {
49526
49556
  const createResult = await ctx.client.session.create({
49527
49557
  body: {
49528
49558
  parentID: toolContext.sessionID,
49529
- title: `${args.description} (@${args.subagent_type} subagent)`,
49530
- permission: [
49531
- { permission: "question", action: "deny", pattern: "*" }
49532
- ]
49559
+ title: `${args.description} (@${args.subagent_type} subagent)`
49533
49560
  },
49534
49561
  query: {
49535
49562
  directory: parentDirectory
@@ -49552,6 +49579,7 @@ Original error: ${createResult.error}`);
49552
49579
  }
49553
49580
  const sessionID = createResult.data.id;
49554
49581
  log(`[call_omo_agent] Created session: ${sessionID}`);
49582
+ subagentSessions.add(sessionID);
49555
49583
  return { sessionID, isNew: true };
49556
49584
  }
49557
49585
  }
@@ -50027,6 +50055,15 @@ Original error: ${createResult.error}`;
50027
50055
  }
50028
50056
  // src/tools/delegate-task/tools.ts
50029
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
50030
50067
  init_logger();
50031
50068
 
50032
50069
  // src/tools/delegate-task/prompt-builder.ts
@@ -50848,10 +50885,7 @@ async function createSyncSession(client2, input) {
50848
50885
  const createResult = await client2.session.create({
50849
50886
  body: {
50850
50887
  parentID: input.parentSessionID,
50851
- title: `${input.description} (@${input.agentToUse} subagent)`,
50852
- permission: [
50853
- { permission: "question", action: "deny", pattern: "*" }
50854
- ]
50888
+ title: `${input.description} (@${input.agentToUse} subagent)`
50855
50889
  },
50856
50890
  query: {
50857
50891
  directory: parentDirectory
@@ -51035,9 +51069,6 @@ session_id: ${sessionID}
51035
51069
  }
51036
51070
  }
51037
51071
  }
51038
- // src/tools/delegate-task/category-resolver.ts
51039
- init_constants();
51040
-
51041
51072
  // src/tools/delegate-task/sisyphus-junior-agent.ts
51042
51073
  var SISYPHUS_JUNIOR_AGENT2 = "sisyphus-junior";
51043
51074
 
@@ -51049,6 +51080,9 @@ function resolveCategoryConfig(categoryName, options) {
51049
51080
  const defaultConfig = DEFAULT_CATEGORIES[categoryName];
51050
51081
  const userConfig = userCategories?.[categoryName];
51051
51082
  const hasExplicitUserConfig = userConfig !== undefined;
51083
+ if (userConfig?.disable) {
51084
+ return null;
51085
+ }
51052
51086
  const categoryReq = CATEGORY_MODEL_REQUIREMENTS[categoryName];
51053
51087
  if (categoryReq?.requiresModel && availableModels && !hasExplicitUserConfig) {
51054
51088
  if (!isModelAvailable(categoryReq.requiresModel, availableModels)) {
@@ -51198,7 +51232,8 @@ async function resolveCategoryExecution(args, executorCtx, inheritedModel, syste
51198
51232
  const { client: client2, userCategories, sisyphusJuniorModel } = executorCtx;
51199
51233
  const availableModels = await getAvailableModelsForDelegateTask(client2);
51200
51234
  const categoryName = args.category;
51201
- const categoryExists = DEFAULT_CATEGORIES[categoryName] !== undefined || userCategories?.[categoryName] !== undefined;
51235
+ const enabledCategories = mergeCategories(userCategories);
51236
+ const categoryExists = enabledCategories[categoryName] !== undefined;
51202
51237
  const resolved = resolveCategoryConfig(categoryName, {
51203
51238
  userCategories,
51204
51239
  inheritedModel,
@@ -51207,7 +51242,7 @@ async function resolveCategoryExecution(args, executorCtx, inheritedModel, syste
51207
51242
  });
51208
51243
  if (!resolved) {
51209
51244
  const requirement2 = CATEGORY_MODEL_REQUIREMENTS[categoryName];
51210
- const allCategoryNames = Object.keys({ ...DEFAULT_CATEGORIES, ...userCategories }).join(", ");
51245
+ const allCategoryNames = Object.keys(enabledCategories).join(", ");
51211
51246
  if (categoryExists && requirement2?.requiresModel) {
51212
51247
  return {
51213
51248
  agentToUse: "",
@@ -51282,7 +51317,7 @@ Available categories: ${allCategoryNames}`
51282
51317
  }
51283
51318
  const categoryPromptAppend = resolved.promptAppend || undefined;
51284
51319
  if (!categoryModel && !actualModel) {
51285
- const categoryNames = Object.keys({ ...DEFAULT_CATEGORIES, ...userCategories });
51320
+ const categoryNames = Object.keys(enabledCategories);
51286
51321
  return {
51287
51322
  agentToUse: "",
51288
51323
  categoryModel: undefined,
@@ -51392,7 +51427,7 @@ Create the work plan directly - that's your job as the planning agent.`
51392
51427
  // src/tools/delegate-task/tools.ts
51393
51428
  function createDelegateTask(options) {
51394
51429
  const { userCategories } = options;
51395
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
51430
+ const allCategories = mergeCategories(userCategories);
51396
51431
  const categoryNames = Object.keys(allCategories);
51397
51432
  const categoryExamples = categoryNames.map((k) => `'${k}'`).join(", ");
51398
51433
  const availableCategories = options.availableCategories ?? Object.entries(allCategories).map(([name, categoryConfig]) => {
@@ -52764,14 +52799,10 @@ class BackgroundManager {
52764
52799
  });
52765
52800
  const parentDirectory = parentSession?.data?.directory ?? this.directory;
52766
52801
  log(`[background-agent] Parent dir: ${parentSession?.data?.directory}, using: ${parentDirectory}`);
52767
- const inheritedPermission = parentSession?.data?.permission;
52768
- const permissionRules = Array.isArray(inheritedPermission) ? inheritedPermission.filter((r) => r?.permission !== "question") : [];
52769
- permissionRules.push({ permission: "question", action: "deny", pattern: "*" });
52770
52802
  const createResult = await this.client.session.create({
52771
52803
  body: {
52772
52804
  parentID: input.parentSessionID,
52773
- title: `${input.description} (@${input.agent} subagent)`,
52774
- permission: permissionRules
52805
+ title: `${input.description} (@${input.agent} subagent)`
52775
52806
  },
52776
52807
  query: {
52777
52808
  directory: parentDirectory
@@ -62230,7 +62261,7 @@ ${rows.join(`
62230
62261
  `)}`;
62231
62262
  }
62232
62263
  function buildCategorySection(userCategories) {
62233
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
62264
+ const allCategories = mergeCategories(userCategories);
62234
62265
  const categoryRows = Object.entries(allCategories).map(([name, config3]) => {
62235
62266
  const temp = config3.temperature ?? 0.5;
62236
62267
  return `| \`${name}\` | ${temp} | ${getCategoryDescription(name, userCategories)} |`;
@@ -62306,7 +62337,7 @@ task(category="[category]", load_skills=["skill-1", "skill-2"], run_in_backgroun
62306
62337
  - Missing a relevant skill = suboptimal output quality`;
62307
62338
  }
62308
62339
  function buildDecisionMatrix(agents, userCategories) {
62309
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
62340
+ const allCategories = mergeCategories(userCategories);
62310
62341
  const categoryRows = Object.entries(allCategories).map(([name]) => `| ${getCategoryDescription(name, userCategories)} | \`category="${name}", load_skills=[...]\` |`);
62311
62342
  const agentRows = agents.map((a) => {
62312
62343
  const shortDesc = truncateDescription(a.description);
@@ -62324,7 +62355,6 @@ ${agentRows.join(`
62324
62355
  **NEVER provide both category AND agent - they are mutually exclusive.**`;
62325
62356
  }
62326
62357
  // src/agents/atlas/agent.ts
62327
- init_constants();
62328
62358
  var MODE7 = "primary";
62329
62359
  function getAtlasPromptSource(model) {
62330
62360
  if (model && isGptModel2(model)) {
@@ -62347,7 +62377,7 @@ function buildDynamicOrchestratorPrompt(ctx) {
62347
62377
  const skills = ctx?.availableSkills ?? [];
62348
62378
  const userCategories = ctx?.userCategories;
62349
62379
  const model = ctx?.model;
62350
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
62380
+ const allCategories = mergeCategories(userCategories);
62351
62381
  const availableCategories = Object.entries(allCategories).map(([name]) => ({
62352
62382
  name,
62353
62383
  description: getCategoryDescription(name, userCategories)
@@ -63223,13 +63253,12 @@ function buildAvailableSkills(discoveredSkills, browserProvider, disabledSkills)
63223
63253
  }
63224
63254
 
63225
63255
  // src/agents/agent-builder.ts
63226
- init_constants();
63227
63256
  function isFactory(source) {
63228
63257
  return typeof source === "function";
63229
63258
  }
63230
63259
  function buildAgent(source, model, categories, gitMasterConfig, browserProvider, disabledSkills) {
63231
63260
  const base = isFactory(source) ? source(model) : { ...source };
63232
- const categoryConfigs = categories ? { ...DEFAULT_CATEGORIES, ...categories } : DEFAULT_CATEGORIES;
63261
+ const categoryConfigs = mergeCategories(categories);
63233
63262
  const agentWithCategory = base;
63234
63263
  if (agentWithCategory.category) {
63235
63264
  const categoryConfig = categoryConfigs[agentWithCategory.category];
@@ -63639,7 +63668,7 @@ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, dir
63639
63668
  });
63640
63669
  const isFirstRunNoCache = availableModels.size === 0 && (!connectedProviders || connectedProviders.length === 0);
63641
63670
  const result = {};
63642
- const mergedCategories = categories ? { ...DEFAULT_CATEGORIES, ...categories } : DEFAULT_CATEGORIES;
63671
+ const mergedCategories = mergeCategories(categories);
63643
63672
  const availableCategories = Object.entries(mergedCategories).map(([name]) => ({
63644
63673
  name,
63645
63674
  description: categories?.[name]?.description ?? CATEGORY_DESCRIPTIONS[name] ?? "General tasks"
@@ -66682,8 +66711,8 @@ function createManagers(args) {
66682
66711
  // src/plugin/available-categories.ts
66683
66712
  init_constants();
66684
66713
  function createAvailableCategories(pluginConfig) {
66685
- const mergedCategories = pluginConfig.categories ? { ...DEFAULT_CATEGORIES, ...pluginConfig.categories } : DEFAULT_CATEGORIES;
66686
- return Object.entries(mergedCategories).map(([name, categoryConfig]) => {
66714
+ const categories = mergeCategories(pluginConfig.categories);
66715
+ return Object.entries(categories).map(([name, categoryConfig]) => {
66687
66716
  const model = typeof categoryConfig.model === "string" ? categoryConfig.model : undefined;
66688
66717
  return {
66689
66718
  name,
@@ -67431,7 +67460,8 @@ var CategoryConfigSchema = exports_external.object({
67431
67460
  textVerbosity: exports_external.enum(["low", "medium", "high"]).optional(),
67432
67461
  tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
67433
67462
  prompt_append: exports_external.string().optional(),
67434
- is_unstable_agent: exports_external.boolean().optional()
67463
+ is_unstable_agent: exports_external.boolean().optional(),
67464
+ disable: exports_external.boolean().optional()
67435
67465
  });
67436
67466
  var BuiltinCategoryNameSchema = exports_external.enum([
67437
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.1",
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.1",
78
- "oh-my-opencode-darwin-x64": "3.5.1",
79
- "oh-my-opencode-linux-arm64": "3.5.1",
80
- "oh-my-opencode-linux-arm64-musl": "3.5.1",
81
- "oh-my-opencode-linux-x64": "3.5.1",
82
- "oh-my-opencode-linux-x64-musl": "3.5.1",
83
- "oh-my-opencode-windows-x64": "3.5.1"
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",