oh-my-opencode 3.5.1 → 3.5.3

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
@@ -7052,6 +7052,12 @@ var init_tmux = __esm(() => {
7052
7052
  var init_model_suggestion_retry = __esm(() => {
7053
7053
  init_logger();
7054
7054
  });
7055
+
7056
+ // src/shared/opencode-server-auth.ts
7057
+ var init_opencode_server_auth = __esm(() => {
7058
+ init_logger();
7059
+ });
7060
+
7055
7061
  // src/shared/port-utils.ts
7056
7062
  async function isPortAvailable(port, hostname = "127.0.0.1") {
7057
7063
  try {
@@ -7128,6 +7134,7 @@ var init_shared = __esm(() => {
7128
7134
  init_session_utils();
7129
7135
  init_tmux();
7130
7136
  init_model_suggestion_retry();
7137
+ init_opencode_server_auth();
7131
7138
  init_port_utils();
7132
7139
  init_git_worktree();
7133
7140
  init_safe_create_hook();
@@ -8326,10 +8333,9 @@ var init_cached_version = __esm(() => {
8326
8333
 
8327
8334
  // src/hooks/auto-update-checker/checker/pinned-version-updater.ts
8328
8335
  import * as fs9 from "fs";
8329
- function updatePinnedVersion(configPath, oldEntry, newVersion) {
8336
+ function replacePluginEntry(configPath, oldEntry, newEntry) {
8330
8337
  try {
8331
8338
  const content = fs9.readFileSync(configPath, "utf-8");
8332
- const newEntry = `${PACKAGE_NAME3}@${newVersion}`;
8333
8339
  const pluginMatch = content.match(/"plugin"\s*:\s*\[/);
8334
8340
  if (!pluginMatch || pluginMatch.index === undefined) {
8335
8341
  log(`[auto-update-checker] No "plugin" array found in ${configPath}`);
@@ -8368,6 +8374,10 @@ function updatePinnedVersion(configPath, oldEntry, newVersion) {
8368
8374
  return false;
8369
8375
  }
8370
8376
  }
8377
+ function revertPinnedVersion(configPath, failedVersion, originalEntry) {
8378
+ const failedEntry = `${PACKAGE_NAME3}@${failedVersion}`;
8379
+ return replacePluginEntry(configPath, failedEntry, originalEntry);
8380
+ }
8371
8381
  var init_pinned_version_updater = __esm(() => {
8372
8382
  init_logger();
8373
8383
  init_constants3();
@@ -8642,23 +8652,23 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage) {
8642
8652
  return;
8643
8653
  }
8644
8654
  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}`);
8655
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
8656
+ log(`[auto-update-checker] User-pinned version detected (${pluginInfo.entry}), skipping auto-update. Notification only.`);
8657
+ return;
8652
8658
  }
8653
8659
  invalidatePackage(PACKAGE_NAME3);
8654
8660
  const installSuccess = await runBunInstallSafe();
8655
8661
  if (installSuccess) {
8656
8662
  await showAutoUpdatedToast(ctx, currentVersion, latestVersion);
8657
8663
  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)");
8664
+ return;
8661
8665
  }
8666
+ if (pluginInfo.isPinned) {
8667
+ revertPinnedVersion(pluginInfo.configPath, latestVersion, pluginInfo.entry);
8668
+ log("[auto-update-checker] Config reverted due to install failure");
8669
+ }
8670
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
8671
+ log("[auto-update-checker] bun install failed; update not installed (falling back to notification-only)");
8662
8672
  }
8663
8673
  var init_background_update_check = __esm(() => {
8664
8674
  init_config_manager();
@@ -8867,7 +8877,7 @@ var {
8867
8877
  // package.json
8868
8878
  var package_default = {
8869
8879
  name: "oh-my-opencode",
8870
- version: "3.5.1",
8880
+ version: "3.5.3",
8871
8881
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
8872
8882
  main: "dist/index.js",
8873
8883
  types: "dist/index.d.ts",
@@ -8941,13 +8951,13 @@ var package_default = {
8941
8951
  typescript: "^5.7.3"
8942
8952
  },
8943
8953
  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"
8954
+ "oh-my-opencode-darwin-arm64": "3.5.3",
8955
+ "oh-my-opencode-darwin-x64": "3.5.3",
8956
+ "oh-my-opencode-linux-arm64": "3.5.3",
8957
+ "oh-my-opencode-linux-arm64-musl": "3.5.3",
8958
+ "oh-my-opencode-linux-x64": "3.5.3",
8959
+ "oh-my-opencode-linux-x64-musl": "3.5.3",
8960
+ "oh-my-opencode-windows-x64": "3.5.3"
8951
8961
  },
8952
8962
  trustedDependencies: [
8953
8963
  "@ast-grep/cli",
@@ -22716,7 +22726,8 @@ var CategoryConfigSchema = exports_external.object({
22716
22726
  textVerbosity: exports_external.enum(["low", "medium", "high"]).optional(),
22717
22727
  tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
22718
22728
  prompt_append: exports_external.string().optional(),
22719
- is_unstable_agent: exports_external.boolean().optional()
22729
+ is_unstable_agent: exports_external.boolean().optional(),
22730
+ disable: exports_external.boolean().optional()
22720
22731
  });
22721
22732
  var BuiltinCategoryNameSchema = exports_external.enum([
22722
22733
  "visual-engineering",
@@ -24748,11 +24759,14 @@ async function areAllDescendantsIdle(ctx, sessionID, allStatuses) {
24748
24759
  var DEFAULT_POLL_INTERVAL_MS = 500;
24749
24760
  var DEFAULT_REQUIRED_CONSECUTIVE = 3;
24750
24761
  var ERROR_GRACE_CYCLES = 3;
24762
+ var MIN_STABILIZATION_MS = 1e4;
24751
24763
  async function pollForCompletion(ctx, eventState, abortController, options = {}) {
24752
24764
  const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
24753
24765
  const requiredConsecutive = options.requiredConsecutive ?? DEFAULT_REQUIRED_CONSECUTIVE;
24766
+ const minStabilizationMs = options.minStabilizationMs ?? MIN_STABILIZATION_MS;
24754
24767
  let consecutiveCompleteChecks = 0;
24755
24768
  let errorCycleCount = 0;
24769
+ let firstWorkTimestamp = null;
24756
24770
  while (!abortController.signal.aborted) {
24757
24771
  await new Promise((resolve2) => setTimeout(resolve2, pollIntervalMs));
24758
24772
  if (eventState.mainSessionError) {
@@ -24780,6 +24794,13 @@ Session ended with error: ${eventState.lastError}`));
24780
24794
  consecutiveCompleteChecks = 0;
24781
24795
  continue;
24782
24796
  }
24797
+ if (firstWorkTimestamp === null) {
24798
+ firstWorkTimestamp = Date.now();
24799
+ }
24800
+ if (Date.now() - firstWorkTimestamp < minStabilizationMs) {
24801
+ consecutiveCompleteChecks = 0;
24802
+ continue;
24803
+ }
24783
24804
  const shouldExit = await checkCompletionConditions(ctx);
24784
24805
  if (shouldExit) {
24785
24806
  consecutiveCompleteChecks++;
@@ -24844,7 +24865,7 @@ Interrupted. Shutting down...`));
24844
24865
  });
24845
24866
  console.log(import_picocolors14.default.dim(`Session: ${sessionID}`));
24846
24867
  const ctx = { client: client3, sessionID, directory, abortController };
24847
- const events = await client3.event.subscribe();
24868
+ const events = await client3.event.subscribe({ query: { directory } });
24848
24869
  const eventState = createEventState();
24849
24870
  const eventProcessor = processEvents(ctx, events.stream, eventState).catch(() => {});
24850
24871
  console.log(import_picocolors14.default.dim(`
@@ -3,5 +3,6 @@ import type { EventState } from "./events";
3
3
  export interface PollOptions {
4
4
  pollIntervalMs?: number;
5
5
  requiredConsecutive?: number;
6
+ minStabilizationMs?: number;
6
7
  }
7
8
  export declare function pollForCompletion(ctx: RunContext, eventState: EventState, abortController: AbortController, options?: PollOptions): Promise<number>;
@@ -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>;
@@ -7,6 +7,7 @@ export declare function handleBackgroundEvent(args: {
7
7
  event: Event;
8
8
  findBySession: (sessionID: string) => BackgroundTask | undefined;
9
9
  getAllDescendantTasks: (sessionID: string) => BackgroundTask[];
10
+ releaseConcurrencyKey?: (key: string) => void;
10
11
  cancelTask: (taskId: string, options: {
11
12
  source: string;
12
13
  reason: string;
@@ -114,6 +114,8 @@ export declare class BackgroundManager {
114
114
  private formatDuration;
115
115
  private isAbortedSessionError;
116
116
  private getErrorText;
117
+ private isRecord;
118
+ private getSessionErrorMessage;
117
119
  private hasRunningTasks;
118
120
  private pruneStaleTasksAndNotifications;
119
121
  private checkAndInterruptStaleTasks;
@@ -0,0 +1,10 @@
1
+ import type { BackgroundTask } from "./types";
2
+ export declare function handleSessionIdleBackgroundEvent(args: {
3
+ properties: Record<string, unknown>;
4
+ findBySession: (sessionID: string) => BackgroundTask | undefined;
5
+ idleDeferralTimers: Map<string, ReturnType<typeof setTimeout>>;
6
+ validateSessionHasOutput: (sessionID: string) => Promise<boolean>;
7
+ checkSessionTodos: (sessionID: string) => Promise<boolean>;
8
+ tryCompleteTask: (task: BackgroundTask, source: string) => Promise<boolean>;
9
+ emitIdleEvent: (sessionID: string) => void;
10
+ }): void;
@@ -0,0 +1,10 @@
1
+ import type { BackgroundTask } from "./types";
2
+ export declare function cleanupTaskAfterSessionEnds(args: {
3
+ task: BackgroundTask;
4
+ tasks: Map<string, BackgroundTask>;
5
+ idleDeferralTimers: Map<string, ReturnType<typeof setTimeout>>;
6
+ completionTimers: Map<string, ReturnType<typeof setTimeout>>;
7
+ cleanupPendingByParent: (task: BackgroundTask) => void;
8
+ clearNotificationsForTask: (taskId: string) => void;
9
+ releaseConcurrencyKey?: (key: string) => void;
10
+ }): void;
@@ -1,9 +1,15 @@
1
- import type { BackgroundTask } from "./types";
1
+ import type { BackgroundTask, LaunchInput } from "./types";
2
2
  import type { ConcurrencyManager } from "./concurrency";
3
+ type QueueItem = {
4
+ task: BackgroundTask;
5
+ input: LaunchInput;
6
+ };
3
7
  export declare function pruneStaleState(args: {
4
8
  tasks: Map<string, BackgroundTask>;
5
9
  notifications: Map<string, BackgroundTask[]>;
10
+ queuesByKey: Map<string, QueueItem[]>;
6
11
  concurrencyManager: ConcurrencyManager;
7
12
  cleanupPendingByParent: (task: BackgroundTask) => void;
8
13
  clearNotificationsForTask: (taskId: string) => void;
9
14
  }): void;
15
+ export {};
@@ -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";
@@ -0,0 +1 @@
1
+ export declare function isPrometheusAgent(agentName: string | undefined): boolean;
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");
@@ -34304,6 +34314,7 @@ async function promptSyncWithModelSuggestionRetry(client, args) {
34304
34314
  }
34305
34315
  }
34306
34316
  // src/shared/opencode-server-auth.ts
34317
+ init_logger();
34307
34318
  function getServerBasicAuthHeader() {
34308
34319
  const password = process.env.OPENCODE_SERVER_PASSWORD;
34309
34320
  if (!password) {
@@ -34313,27 +34324,128 @@ function getServerBasicAuthHeader() {
34313
34324
  const token = Buffer.from(`${username}:${password}`, "utf8").toString("base64");
34314
34325
  return `Basic ${token}`;
34315
34326
  }
34327
+ function isRecord(value) {
34328
+ return typeof value === "object" && value !== null;
34329
+ }
34330
+ function isRequestFetch(value) {
34331
+ return typeof value === "function";
34332
+ }
34333
+ function wrapRequestFetch(baseFetch, auth) {
34334
+ return async (request) => {
34335
+ const headers = new Headers(request.headers);
34336
+ headers.set("Authorization", auth);
34337
+ return baseFetch(new Request(request, { headers }));
34338
+ };
34339
+ }
34340
+ function getInternalClient(client) {
34341
+ if (!isRecord(client)) {
34342
+ return null;
34343
+ }
34344
+ const internal = client["_client"];
34345
+ return isRecord(internal) ? internal : null;
34346
+ }
34347
+ function tryInjectViaSetConfigHeaders(internal, auth) {
34348
+ const setConfig = internal["setConfig"];
34349
+ if (typeof setConfig !== "function") {
34350
+ return false;
34351
+ }
34352
+ setConfig({
34353
+ headers: {
34354
+ Authorization: auth
34355
+ }
34356
+ });
34357
+ return true;
34358
+ }
34359
+ function tryInjectViaInterceptors(internal, auth) {
34360
+ const interceptors = internal["interceptors"];
34361
+ if (!isRecord(interceptors)) {
34362
+ return false;
34363
+ }
34364
+ const requestInterceptors = interceptors["request"];
34365
+ if (!isRecord(requestInterceptors)) {
34366
+ return false;
34367
+ }
34368
+ const use = requestInterceptors["use"];
34369
+ if (typeof use !== "function") {
34370
+ return false;
34371
+ }
34372
+ use((request) => {
34373
+ if (!request.headers.get("Authorization")) {
34374
+ request.headers.set("Authorization", auth);
34375
+ }
34376
+ return request;
34377
+ });
34378
+ return true;
34379
+ }
34380
+ function tryInjectViaFetchWrapper(internal, auth) {
34381
+ const getConfig = internal["getConfig"];
34382
+ const setConfig = internal["setConfig"];
34383
+ if (typeof getConfig !== "function" || typeof setConfig !== "function") {
34384
+ return false;
34385
+ }
34386
+ const config2 = getConfig();
34387
+ if (!isRecord(config2)) {
34388
+ return false;
34389
+ }
34390
+ const fetchValue = config2["fetch"];
34391
+ if (!isRequestFetch(fetchValue)) {
34392
+ return false;
34393
+ }
34394
+ setConfig({
34395
+ fetch: wrapRequestFetch(fetchValue, auth)
34396
+ });
34397
+ return true;
34398
+ }
34399
+ function tryInjectViaMutableInternalConfig(internal, auth) {
34400
+ const configValue = internal["_config"];
34401
+ if (!isRecord(configValue)) {
34402
+ return false;
34403
+ }
34404
+ const fetchValue = configValue["fetch"];
34405
+ if (!isRequestFetch(fetchValue)) {
34406
+ return false;
34407
+ }
34408
+ configValue["fetch"] = wrapRequestFetch(fetchValue, auth);
34409
+ return true;
34410
+ }
34411
+ function tryInjectViaTopLevelFetch(client, auth) {
34412
+ if (!isRecord(client)) {
34413
+ return false;
34414
+ }
34415
+ const fetchValue = client["fetch"];
34416
+ if (!isRequestFetch(fetchValue)) {
34417
+ return false;
34418
+ }
34419
+ client["fetch"] = wrapRequestFetch(fetchValue, auth);
34420
+ return true;
34421
+ }
34316
34422
  function injectServerAuthIntoClient(client) {
34317
34423
  const auth = getServerBasicAuthHeader();
34318
34424
  if (!auth) {
34319
34425
  return;
34320
34426
  }
34321
34427
  try {
34322
- if (typeof client !== "object" || client === null || !("_client" in client) || typeof client._client !== "object" || client._client === null) {
34323
- throw new Error("[opencode-server-auth] OPENCODE_SERVER_PASSWORD is set but SDK client structure is incompatible. " + "This may indicate an OpenCode SDK version mismatch.");
34428
+ const internal = getInternalClient(client);
34429
+ if (internal) {
34430
+ const injectedHeaders = tryInjectViaSetConfigHeaders(internal, auth);
34431
+ const injectedInterceptors = tryInjectViaInterceptors(internal, auth);
34432
+ const injectedFetch = tryInjectViaFetchWrapper(internal, auth);
34433
+ const injectedMutable = tryInjectViaMutableInternalConfig(internal, auth);
34434
+ const injected2 = injectedHeaders || injectedInterceptors || injectedFetch || injectedMutable;
34435
+ if (!injected2) {
34436
+ log("[opencode-server-auth] OPENCODE_SERVER_PASSWORD is set but SDK client structure is incompatible", {
34437
+ keys: Object.keys(internal)
34438
+ });
34439
+ }
34440
+ return;
34324
34441
  }
34325
- const internal = client._client;
34326
- if (typeof internal.setConfig !== "function") {
34327
- throw new Error("[opencode-server-auth] OPENCODE_SERVER_PASSWORD is set but SDK client._client.setConfig is not a function. " + "This may indicate an OpenCode SDK version mismatch.");
34442
+ const injected = tryInjectViaTopLevelFetch(client, auth);
34443
+ if (!injected) {
34444
+ log("[opencode-server-auth] OPENCODE_SERVER_PASSWORD is set but no compatible SDK client found");
34328
34445
  }
34329
- internal.setConfig({
34330
- headers: {
34331
- Authorization: auth
34332
- }
34333
- });
34334
34446
  } catch (error45) {
34335
34447
  const message = error45 instanceof Error ? error45.message : String(error45);
34336
- console.warn(`[opencode-server-auth] Failed to inject server auth: ${message}`);
34448
+ log("[opencode-server-auth] Failed to inject server auth", { message });
34337
34449
  }
34338
34450
  }
34339
34451
  // src/shared/git-worktree/parse-status-porcelain.ts
@@ -36389,10 +36501,9 @@ function getCachedVersion() {
36389
36501
  // src/hooks/auto-update-checker/checker/pinned-version-updater.ts
36390
36502
  init_logger();
36391
36503
  import * as fs12 from "fs";
36392
- function updatePinnedVersion(configPath, oldEntry, newVersion) {
36504
+ function replacePluginEntry(configPath, oldEntry, newEntry) {
36393
36505
  try {
36394
36506
  const content = fs12.readFileSync(configPath, "utf-8");
36395
- const newEntry = `${PACKAGE_NAME}@${newVersion}`;
36396
36507
  const pluginMatch = content.match(/"plugin"\s*:\s*\[/);
36397
36508
  if (!pluginMatch || pluginMatch.index === undefined) {
36398
36509
  log(`[auto-update-checker] No "plugin" array found in ${configPath}`);
@@ -36431,6 +36542,10 @@ function updatePinnedVersion(configPath, oldEntry, newVersion) {
36431
36542
  return false;
36432
36543
  }
36433
36544
  }
36545
+ function revertPinnedVersion(configPath, failedVersion, originalEntry) {
36546
+ const failedEntry = `${PACKAGE_NAME}@${failedVersion}`;
36547
+ return replacePluginEntry(configPath, failedEntry, originalEntry);
36548
+ }
36434
36549
  // src/hooks/auto-update-checker/checker/latest-version.ts
36435
36550
  async function getLatestVersion(channel = "latest") {
36436
36551
  const controller = new AbortController;
@@ -36673,23 +36788,23 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage) {
36673
36788
  return;
36674
36789
  }
36675
36790
  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}`);
36791
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
36792
+ log(`[auto-update-checker] User-pinned version detected (${pluginInfo.entry}), skipping auto-update. Notification only.`);
36793
+ return;
36683
36794
  }
36684
36795
  invalidatePackage(PACKAGE_NAME);
36685
36796
  const installSuccess = await runBunInstallSafe();
36686
36797
  if (installSuccess) {
36687
36798
  await showAutoUpdatedToast(ctx, currentVersion, latestVersion);
36688
36799
  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)");
36800
+ return;
36801
+ }
36802
+ if (pluginInfo.isPinned) {
36803
+ revertPinnedVersion(pluginInfo.configPath, latestVersion, pluginInfo.entry);
36804
+ log("[auto-update-checker] Config reverted due to install failure");
36692
36805
  }
36806
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
36807
+ log("[auto-update-checker] bun install failed; update not installed (falling back to notification-only)");
36693
36808
  }
36694
36809
 
36695
36810
  // src/hooks/auto-update-checker/hook/config-errors-toast.ts
@@ -42698,7 +42813,7 @@ async function executeSlashCommand(parsed, options) {
42698
42813
  if (!command) {
42699
42814
  return {
42700
42815
  success: false,
42701
- error: `Command "/${parsed.command}" not found. Use the slashcommand tool to list available commands.`
42816
+ error: parsed.command.includes(":") ? `Marketplace plugin commands like "/${parsed.command}" are not supported. Use .claude/commands/ for custom commands.` : `Command "/${parsed.command}" not found. Use the slashcommand tool to list available commands.`
42702
42817
  };
42703
42818
  }
42704
42819
  try {
@@ -42832,7 +42947,7 @@ function createEditErrorRecoveryHook(_ctx) {
42832
42947
  "tool.execute.after": async (input, output) => {
42833
42948
  if (input.tool.toLowerCase() !== "edit")
42834
42949
  return;
42835
- const outputLower = output.output.toLowerCase();
42950
+ const outputLower = (output.output ?? "").toLowerCase();
42836
42951
  const hasEditError = EDIT_ERROR_PATTERNS.some((pattern) => outputLower.includes(pattern.toLowerCase()));
42837
42952
  if (hasEditError) {
42838
42953
  output.output += `
@@ -42965,12 +43080,17 @@ function getAgentFromSession(sessionID, directory) {
42965
43080
  if (memoryAgent)
42966
43081
  return memoryAgent;
42967
43082
  const boulderState = readBoulderState(directory);
42968
- if (boulderState?.session_ids.includes(sessionID) && boulderState.agent) {
43083
+ if (boulderState?.session_ids?.includes(sessionID) && boulderState.agent) {
42969
43084
  return boulderState.agent;
42970
43085
  }
42971
43086
  return getAgentFromMessageFiles(sessionID);
42972
43087
  }
42973
43088
 
43089
+ // src/hooks/prometheus-md-only/agent-matcher.ts
43090
+ function isPrometheusAgent(agentName) {
43091
+ return agentName?.toLowerCase().includes(PROMETHEUS_AGENT) ?? false;
43092
+ }
43093
+
42974
43094
  // src/hooks/prometheus-md-only/path-policy.ts
42975
43095
  import { relative as relative3, resolve as resolve7, isAbsolute as isAbsolute3 } from "path";
42976
43096
  function isAllowedFile(filePath, workspaceRoot) {
@@ -42995,7 +43115,7 @@ function createPrometheusMdOnlyHook(ctx) {
42995
43115
  return {
42996
43116
  "tool.execute.before": async (input, output) => {
42997
43117
  const agentName = getAgentFromSession(input.sessionID, ctx.directory);
42998
- if (agentName !== PROMETHEUS_AGENT) {
43118
+ if (!isPrometheusAgent(agentName)) {
42999
43119
  return;
43000
43120
  }
43001
43121
  const toolName = input.tool;
@@ -43120,15 +43240,16 @@ function createTaskResumeInfoHook() {
43120
43240
  const toolExecuteAfter = async (input, output) => {
43121
43241
  if (!TARGET_TOOLS2.includes(input.tool))
43122
43242
  return;
43123
- if (output.output.startsWith("Error:") || output.output.startsWith("Failed"))
43243
+ const outputText = output.output ?? "";
43244
+ if (outputText.startsWith("Error:") || outputText.startsWith("Failed"))
43124
43245
  return;
43125
- if (output.output.includes(`
43246
+ if (outputText.includes(`
43126
43247
  to continue:`))
43127
43248
  return;
43128
- const sessionId = extractSessionId(output.output);
43249
+ const sessionId = extractSessionId(outputText);
43129
43250
  if (!sessionId)
43130
43251
  return;
43131
- output.output = output.output.trimEnd() + `
43252
+ output.output = outputText.trimEnd() + `
43132
43253
 
43133
43254
  to continue: task(session_id="${sessionId}", prompt="...")`;
43134
43255
  };
@@ -43629,7 +43750,7 @@ function createAtlasEventHandler(input) {
43629
43750
  return;
43630
43751
  log(`[${HOOK_NAME7}] session.idle`, { sessionID });
43631
43752
  const boulderState = readBoulderState(ctx.directory);
43632
- const isBoulderSession = boulderState?.session_ids.includes(sessionID) ?? false;
43753
+ const isBoulderSession = boulderState?.session_ids?.includes(sessionID) ?? false;
43633
43754
  const isBackgroundTaskSession = subagentSessions.has(sessionID);
43634
43755
  if (!isBackgroundTaskSession && !isBoulderSession) {
43635
43756
  log(`[${HOOK_NAME7}] Skipped: not boulder or background task session`, { sessionID });
@@ -43798,7 +43919,23 @@ function buildOrchestratorReminder(planName, progress, sessionId) {
43798
43919
 
43799
43920
  ${buildVerificationReminder(sessionId)}
43800
43921
 
43801
- **STEP 5: CHECK BOULDER STATE DIRECTLY (EVERY TIME \u2014 NO EXCEPTIONS)**
43922
+ **STEP 5: READ SUBAGENT NOTEPAD (LEARNINGS, ISSUES, PROBLEMS)**
43923
+
43924
+ The subagent was instructed to record findings in notepad files. Read them NOW:
43925
+ \`\`\`
43926
+ Glob(".sisyphus/notepads/${planName}/*.md")
43927
+ \`\`\`
43928
+ Then \`Read\` each file found \u2014 especially:
43929
+ - **learnings.md**: Patterns, conventions, successful approaches discovered
43930
+ - **issues.md**: Problems, blockers, gotchas encountered during work
43931
+ - **problems.md**: Unresolved issues, technical debt flagged
43932
+
43933
+ **USE this information to:**
43934
+ - Inform your next delegation (avoid known pitfalls)
43935
+ - Adjust your plan if blockers were discovered
43936
+ - Propagate learnings to subsequent subagents
43937
+
43938
+ **STEP 6: CHECK BOULDER STATE DIRECTLY (EVERY TIME \u2014 NO EXCEPTIONS)**
43802
43939
 
43803
43940
  Do NOT rely on cached progress. Read the plan file NOW:
43804
43941
  \`\`\`
@@ -43807,7 +43944,7 @@ Read(".sisyphus/plans/${planName}.md")
43807
43944
  Count exactly: how many \`- [ ]\` remain? How many \`- [x]\` completed?
43808
43945
  This is YOUR ground truth. Use it to decide what comes next.
43809
43946
 
43810
- **STEP 6: MARK COMPLETION IN PLAN FILE (IMMEDIATELY)**
43947
+ **STEP 7: MARK COMPLETION IN PLAN FILE (IMMEDIATELY)**
43811
43948
 
43812
43949
  RIGHT NOW - Do not delay. Verification passed \u2192 Mark IMMEDIATELY.
43813
43950
 
@@ -43817,12 +43954,12 @@ Update the plan file \`.sisyphus/plans/${planName}.md\`:
43817
43954
 
43818
43955
  **DO THIS BEFORE ANYTHING ELSE. Unmarked = Untracked = Lost progress.**
43819
43956
 
43820
- **STEP 7: COMMIT ATOMIC UNIT**
43957
+ **STEP 8: COMMIT ATOMIC UNIT**
43821
43958
 
43822
43959
  - Stage ONLY the verified changes
43823
43960
  - Commit with clear message describing what was done
43824
43961
 
43825
- **STEP 8: PROCEED TO NEXT TASK**
43962
+ **STEP 9: PROCEED TO NEXT TASK**
43826
43963
 
43827
43964
  - Read the plan file AGAIN to identify the next \`- [ ]\` task
43828
43965
  - Start immediately - DO NOT STOP
@@ -43916,7 +44053,7 @@ function createToolExecuteAfterHandler2(input) {
43916
44053
  const boulderState = readBoulderState(ctx.directory);
43917
44054
  if (boulderState) {
43918
44055
  const progress = getPlanProgress(boulderState.active_plan);
43919
- if (toolInput.sessionID && !boulderState.session_ids.includes(toolInput.sessionID)) {
44056
+ if (toolInput.sessionID && !boulderState.session_ids?.includes(toolInput.sessionID)) {
43920
44057
  appendSessionId(ctx.directory, toolInput.sessionID);
43921
44058
  log(`[${HOOK_NAME7}] Appended session to boulder`, {
43922
44059
  sessionID: toolInput.sessionID,
@@ -44380,16 +44517,16 @@ var THINKING_SUMMARY_MAX_CHARS = 500;
44380
44517
  function hasData(value) {
44381
44518
  return typeof value === "object" && value !== null && "data" in value;
44382
44519
  }
44383
- function isRecord(value) {
44520
+ function isRecord2(value) {
44384
44521
  return typeof value === "object" && value !== null;
44385
44522
  }
44386
44523
  function getMessageInfo(value) {
44387
- if (!isRecord(value))
44524
+ if (!isRecord2(value))
44388
44525
  return;
44389
- if (!isRecord(value.info))
44526
+ if (!isRecord2(value.info))
44390
44527
  return;
44391
44528
  const info = value.info;
44392
- const modelValue = isRecord(info.model) ? info.model : undefined;
44529
+ const modelValue = isRecord2(info.model) ? info.model : undefined;
44393
44530
  const model = modelValue && typeof modelValue.providerID === "string" && typeof modelValue.modelID === "string" ? { providerID: modelValue.providerID, modelID: modelValue.modelID } : undefined;
44394
44531
  return {
44395
44532
  role: typeof info.role === "string" ? info.role : undefined,
@@ -44400,11 +44537,11 @@ function getMessageInfo(value) {
44400
44537
  };
44401
44538
  }
44402
44539
  function getMessageParts(value) {
44403
- if (!isRecord(value))
44540
+ if (!isRecord2(value))
44404
44541
  return [];
44405
44542
  if (!Array.isArray(value.parts))
44406
44543
  return [];
44407
- return value.parts.filter(isRecord).map((part) => ({
44544
+ return value.parts.filter(isRecord2).map((part) => ({
44408
44545
  type: typeof part.type === "string" ? part.type : undefined,
44409
44546
  text: typeof part.text === "string" ? part.text : undefined,
44410
44547
  thinking: typeof part.thinking === "string" ? part.thinking : undefined
@@ -47804,7 +47941,9 @@ Provide a command or skill name to execute.`;
47804
47941
 
47805
47942
  ${formatCommandList(allItems)}`;
47806
47943
  }
47807
- return `Command or skill "/${commandName}" not found.
47944
+ return commandName.includes(":") ? `Marketplace plugin commands like "/${commandName}" are not supported. Use .claude/commands/ for custom commands.
47945
+
47946
+ ${formatCommandList(allItems)}` : `Command or skill "/${commandName}" not found.
47808
47947
 
47809
47948
  ${formatCommandList(allItems)}
47810
47949
 
@@ -49526,10 +49665,7 @@ async function createOrGetSession(args, toolContext, ctx) {
49526
49665
  const createResult = await ctx.client.session.create({
49527
49666
  body: {
49528
49667
  parentID: toolContext.sessionID,
49529
- title: `${args.description} (@${args.subagent_type} subagent)`,
49530
- permission: [
49531
- { permission: "question", action: "deny", pattern: "*" }
49532
- ]
49668
+ title: `${args.description} (@${args.subagent_type} subagent)`
49533
49669
  },
49534
49670
  query: {
49535
49671
  directory: parentDirectory
@@ -49552,6 +49688,7 @@ Original error: ${createResult.error}`);
49552
49688
  }
49553
49689
  const sessionID = createResult.data.id;
49554
49690
  log(`[call_omo_agent] Created session: ${sessionID}`);
49691
+ subagentSessions.add(sessionID);
49555
49692
  return { sessionID, isNew: true };
49556
49693
  }
49557
49694
  }
@@ -50027,6 +50164,15 @@ Original error: ${createResult.error}`;
50027
50164
  }
50028
50165
  // src/tools/delegate-task/tools.ts
50029
50166
  init_constants();
50167
+
50168
+ // src/shared/merge-categories.ts
50169
+ init_constants();
50170
+ function mergeCategories(userCategories) {
50171
+ const merged = userCategories ? { ...DEFAULT_CATEGORIES, ...userCategories } : { ...DEFAULT_CATEGORIES };
50172
+ return Object.fromEntries(Object.entries(merged).filter(([, config3]) => !config3.disable));
50173
+ }
50174
+
50175
+ // src/tools/delegate-task/tools.ts
50030
50176
  init_logger();
50031
50177
 
50032
50178
  // src/tools/delegate-task/prompt-builder.ts
@@ -50176,7 +50322,8 @@ Use \`background_output\` with task_id="${task.id}" to check progress.
50176
50322
 
50177
50323
  <task_metadata>
50178
50324
  session_id: ${task.sessionID}
50179
- </task_metadata>`;
50325
+ ${task.agent ? `subagent: ${task.agent}
50326
+ ` : ""}</task_metadata>`;
50180
50327
  } catch (error45) {
50181
50328
  return formatDetailedError(error45, {
50182
50329
  operation: "Continue background task",
@@ -50629,7 +50776,8 @@ ${result.textContent || "(No text output)"}
50629
50776
 
50630
50777
  <task_metadata>
50631
50778
  session_id: ${args.session_id}
50632
- </task_metadata>`;
50779
+ ${resumeAgent ? `subagent: ${resumeAgent}
50780
+ ` : ""}</task_metadata>`;
50633
50781
  } finally {
50634
50782
  if (toastManager) {
50635
50783
  toastManager.removeTask(taskId);
@@ -50848,10 +50996,7 @@ async function createSyncSession(client2, input) {
50848
50996
  const createResult = await client2.session.create({
50849
50997
  body: {
50850
50998
  parentID: input.parentSessionID,
50851
- title: `${input.description} (@${input.agentToUse} subagent)`,
50852
- permission: [
50853
- { permission: "question", action: "deny", pattern: "*" }
50854
- ]
50999
+ title: `${input.description} (@${input.agentToUse} subagent)`
50855
51000
  },
50856
51001
  query: {
50857
51002
  directory: parentDirectory
@@ -51035,9 +51180,6 @@ session_id: ${sessionID}
51035
51180
  }
51036
51181
  }
51037
51182
  }
51038
- // src/tools/delegate-task/category-resolver.ts
51039
- init_constants();
51040
-
51041
51183
  // src/tools/delegate-task/sisyphus-junior-agent.ts
51042
51184
  var SISYPHUS_JUNIOR_AGENT2 = "sisyphus-junior";
51043
51185
 
@@ -51049,6 +51191,9 @@ function resolveCategoryConfig(categoryName, options) {
51049
51191
  const defaultConfig = DEFAULT_CATEGORIES[categoryName];
51050
51192
  const userConfig = userCategories?.[categoryName];
51051
51193
  const hasExplicitUserConfig = userConfig !== undefined;
51194
+ if (userConfig?.disable) {
51195
+ return null;
51196
+ }
51052
51197
  const categoryReq = CATEGORY_MODEL_REQUIREMENTS[categoryName];
51053
51198
  if (categoryReq?.requiresModel && availableModels && !hasExplicitUserConfig) {
51054
51199
  if (!isModelAvailable(categoryReq.requiresModel, availableModels)) {
@@ -51198,7 +51343,8 @@ async function resolveCategoryExecution(args, executorCtx, inheritedModel, syste
51198
51343
  const { client: client2, userCategories, sisyphusJuniorModel } = executorCtx;
51199
51344
  const availableModels = await getAvailableModelsForDelegateTask(client2);
51200
51345
  const categoryName = args.category;
51201
- const categoryExists = DEFAULT_CATEGORIES[categoryName] !== undefined || userCategories?.[categoryName] !== undefined;
51346
+ const enabledCategories = mergeCategories(userCategories);
51347
+ const categoryExists = enabledCategories[categoryName] !== undefined;
51202
51348
  const resolved = resolveCategoryConfig(categoryName, {
51203
51349
  userCategories,
51204
51350
  inheritedModel,
@@ -51207,7 +51353,7 @@ async function resolveCategoryExecution(args, executorCtx, inheritedModel, syste
51207
51353
  });
51208
51354
  if (!resolved) {
51209
51355
  const requirement2 = CATEGORY_MODEL_REQUIREMENTS[categoryName];
51210
- const allCategoryNames = Object.keys({ ...DEFAULT_CATEGORIES, ...userCategories }).join(", ");
51356
+ const allCategoryNames = Object.keys(enabledCategories).join(", ");
51211
51357
  if (categoryExists && requirement2?.requiresModel) {
51212
51358
  return {
51213
51359
  agentToUse: "",
@@ -51282,7 +51428,7 @@ Available categories: ${allCategoryNames}`
51282
51428
  }
51283
51429
  const categoryPromptAppend = resolved.promptAppend || undefined;
51284
51430
  if (!categoryModel && !actualModel) {
51285
- const categoryNames = Object.keys({ ...DEFAULT_CATEGORIES, ...userCategories });
51431
+ const categoryNames = Object.keys(enabledCategories);
51286
51432
  return {
51287
51433
  agentToUse: "",
51288
51434
  categoryModel: undefined,
@@ -51392,7 +51538,7 @@ Create the work plan directly - that's your job as the planning agent.`
51392
51538
  // src/tools/delegate-task/tools.ts
51393
51539
  function createDelegateTask(options) {
51394
51540
  const { userCategories } = options;
51395
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
51541
+ const allCategories = mergeCategories(userCategories);
51396
51542
  const categoryNames = Object.keys(allCategories);
51397
51543
  const categoryExamples = categoryNames.map((k) => `'${k}'`).join(", ");
51398
51544
  const availableCategories = options.availableCategories ?? Object.entries(allCategories).map(([name, categoryConfig]) => {
@@ -52729,7 +52875,7 @@ class BackgroundManager {
52729
52875
  while (queue && queue.length > 0) {
52730
52876
  const item = queue[0];
52731
52877
  await this.concurrencyManager.acquire(key);
52732
- if (item.task.status === "cancelled") {
52878
+ if (item.task.status === "cancelled" || item.task.status === "error") {
52733
52879
  this.concurrencyManager.release(key);
52734
52880
  queue.shift();
52735
52881
  continue;
@@ -52764,14 +52910,10 @@ class BackgroundManager {
52764
52910
  });
52765
52911
  const parentDirectory = parentSession?.data?.directory ?? this.directory;
52766
52912
  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
52913
  const createResult = await this.client.session.create({
52771
52914
  body: {
52772
52915
  parentID: input.parentSessionID,
52773
- title: `${input.description} (@${input.agent} subagent)`,
52774
- permission: permissionRules
52916
+ title: `${input.description} (@${input.agent} subagent)`
52775
52917
  },
52776
52918
  query: {
52777
52919
  directory: parentDirectory
@@ -53157,6 +53299,38 @@ class BackgroundManager {
53157
53299
  log("[background-agent] Error in session.idle handler:", err);
53158
53300
  });
53159
53301
  }
53302
+ if (event.type === "session.error") {
53303
+ const sessionID = typeof props?.sessionID === "string" ? props.sessionID : undefined;
53304
+ if (!sessionID)
53305
+ return;
53306
+ const task = this.findBySession(sessionID);
53307
+ if (!task || task.status !== "running")
53308
+ return;
53309
+ const errorMessage = props ? this.getSessionErrorMessage(props) : undefined;
53310
+ task.status = "error";
53311
+ task.error = errorMessage ?? "Session error";
53312
+ task.completedAt = new Date;
53313
+ if (task.concurrencyKey) {
53314
+ this.concurrencyManager.release(task.concurrencyKey);
53315
+ task.concurrencyKey = undefined;
53316
+ }
53317
+ const completionTimer = this.completionTimers.get(task.id);
53318
+ if (completionTimer) {
53319
+ clearTimeout(completionTimer);
53320
+ this.completionTimers.delete(task.id);
53321
+ }
53322
+ const idleTimer = this.idleDeferralTimers.get(task.id);
53323
+ if (idleTimer) {
53324
+ clearTimeout(idleTimer);
53325
+ this.idleDeferralTimers.delete(task.id);
53326
+ }
53327
+ this.cleanupPendingByParent(task);
53328
+ this.tasks.delete(task.id);
53329
+ this.clearNotificationsForTask(task.id);
53330
+ if (task.sessionID) {
53331
+ subagentSessions.delete(task.sessionID);
53332
+ }
53333
+ }
53160
53334
  if (event.type === "session.deleted") {
53161
53335
  const info = props?.info;
53162
53336
  if (!info || typeof info.id !== "string")
@@ -53579,6 +53753,22 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
53579
53753
  }
53580
53754
  return "";
53581
53755
  }
53756
+ isRecord(value) {
53757
+ return typeof value === "object" && value !== null;
53758
+ }
53759
+ getSessionErrorMessage(properties) {
53760
+ const errorRaw = properties["error"];
53761
+ if (!this.isRecord(errorRaw))
53762
+ return;
53763
+ const dataRaw = errorRaw["data"];
53764
+ if (this.isRecord(dataRaw)) {
53765
+ const message2 = dataRaw["message"];
53766
+ if (typeof message2 === "string")
53767
+ return message2;
53768
+ }
53769
+ const message = errorRaw["message"];
53770
+ return typeof message === "string" ? message : undefined;
53771
+ }
53582
53772
  hasRunningTasks() {
53583
53773
  for (const task of this.tasks.values()) {
53584
53774
  if (task.status === "running")
@@ -53589,6 +53779,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
53589
53779
  pruneStaleTasksAndNotifications() {
53590
53780
  const now = Date.now();
53591
53781
  for (const [taskId, task] of this.tasks.entries()) {
53782
+ const wasPending = task.status === "pending";
53592
53783
  const timestamp2 = task.status === "pending" ? task.queuedAt?.getTime() : task.startedAt?.getTime();
53593
53784
  if (!timestamp2) {
53594
53785
  continue;
@@ -53605,6 +53796,19 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
53605
53796
  task.concurrencyKey = undefined;
53606
53797
  }
53607
53798
  this.cleanupPendingByParent(task);
53799
+ if (wasPending) {
53800
+ const key = task.model ? `${task.model.providerID}/${task.model.modelID}` : task.agent;
53801
+ const queue = this.queuesByKey.get(key);
53802
+ if (queue) {
53803
+ const index = queue.findIndex((item) => item.task.id === taskId);
53804
+ if (index !== -1) {
53805
+ queue.splice(index, 1);
53806
+ if (queue.length === 0) {
53807
+ this.queuesByKey.delete(key);
53808
+ }
53809
+ }
53810
+ }
53811
+ }
53608
53812
  this.clearNotificationsForTask(taskId);
53609
53813
  this.tasks.delete(taskId);
53610
53814
  if (task.sessionID) {
@@ -58051,7 +58255,7 @@ async function getOrRegisterClient(options) {
58051
58255
  }
58052
58256
  }
58053
58257
  function parseRegistrationResponse(data) {
58054
- if (!isRecord2(data))
58258
+ if (!isRecord3(data))
58055
58259
  return null;
58056
58260
  const clientId = data.client_id;
58057
58261
  if (typeof clientId !== "string" || clientId.length === 0)
@@ -58062,7 +58266,7 @@ function parseRegistrationResponse(data) {
58062
58266
  }
58063
58267
  return { clientId };
58064
58268
  }
58065
- function isRecord2(value) {
58269
+ function isRecord3(value) {
58066
58270
  return typeof value === "object" && value !== null;
58067
58271
  }
58068
58272
 
@@ -62230,7 +62434,7 @@ ${rows.join(`
62230
62434
  `)}`;
62231
62435
  }
62232
62436
  function buildCategorySection(userCategories) {
62233
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
62437
+ const allCategories = mergeCategories(userCategories);
62234
62438
  const categoryRows = Object.entries(allCategories).map(([name, config3]) => {
62235
62439
  const temp = config3.temperature ?? 0.5;
62236
62440
  return `| \`${name}\` | ${temp} | ${getCategoryDescription(name, userCategories)} |`;
@@ -62306,7 +62510,7 @@ task(category="[category]", load_skills=["skill-1", "skill-2"], run_in_backgroun
62306
62510
  - Missing a relevant skill = suboptimal output quality`;
62307
62511
  }
62308
62512
  function buildDecisionMatrix(agents, userCategories) {
62309
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
62513
+ const allCategories = mergeCategories(userCategories);
62310
62514
  const categoryRows = Object.entries(allCategories).map(([name]) => `| ${getCategoryDescription(name, userCategories)} | \`category="${name}", load_skills=[...]\` |`);
62311
62515
  const agentRows = agents.map((a) => {
62312
62516
  const shortDesc = truncateDescription(a.description);
@@ -62324,7 +62528,6 @@ ${agentRows.join(`
62324
62528
  **NEVER provide both category AND agent - they are mutually exclusive.**`;
62325
62529
  }
62326
62530
  // src/agents/atlas/agent.ts
62327
- init_constants();
62328
62531
  var MODE7 = "primary";
62329
62532
  function getAtlasPromptSource(model) {
62330
62533
  if (model && isGptModel2(model)) {
@@ -62347,7 +62550,7 @@ function buildDynamicOrchestratorPrompt(ctx) {
62347
62550
  const skills = ctx?.availableSkills ?? [];
62348
62551
  const userCategories = ctx?.userCategories;
62349
62552
  const model = ctx?.model;
62350
- const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
62553
+ const allCategories = mergeCategories(userCategories);
62351
62554
  const availableCategories = Object.entries(allCategories).map(([name]) => ({
62352
62555
  name,
62353
62556
  description: getCategoryDescription(name, userCategories)
@@ -63223,13 +63426,12 @@ function buildAvailableSkills(discoveredSkills, browserProvider, disabledSkills)
63223
63426
  }
63224
63427
 
63225
63428
  // src/agents/agent-builder.ts
63226
- init_constants();
63227
63429
  function isFactory(source) {
63228
63430
  return typeof source === "function";
63229
63431
  }
63230
63432
  function buildAgent(source, model, categories, gitMasterConfig, browserProvider, disabledSkills) {
63231
63433
  const base = isFactory(source) ? source(model) : { ...source };
63232
- const categoryConfigs = categories ? { ...DEFAULT_CATEGORIES, ...categories } : DEFAULT_CATEGORIES;
63434
+ const categoryConfigs = mergeCategories(categories);
63233
63435
  const agentWithCategory = base;
63234
63436
  if (agentWithCategory.category) {
63235
63437
  const categoryConfig = categoryConfigs[agentWithCategory.category];
@@ -63569,7 +63771,7 @@ function maybeCreateAtlasConfig(input) {
63569
63771
  function sanitizeMarkdownTableCell(value) {
63570
63772
  return value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
63571
63773
  }
63572
- function isRecord3(value) {
63774
+ function isRecord4(value) {
63573
63775
  return typeof value === "object" && value !== null;
63574
63776
  }
63575
63777
  function parseRegisteredAgentSummaries(input) {
@@ -63577,7 +63779,7 @@ function parseRegisteredAgentSummaries(input) {
63577
63779
  return [];
63578
63780
  const result = [];
63579
63781
  for (const item of input) {
63580
- if (!isRecord3(item))
63782
+ if (!isRecord4(item))
63581
63783
  continue;
63582
63784
  const name = typeof item.name === "string" ? item.name : undefined;
63583
63785
  if (!name)
@@ -63639,7 +63841,7 @@ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, dir
63639
63841
  });
63640
63842
  const isFirstRunNoCache = availableModels.size === 0 && (!connectedProviders || connectedProviders.length === 0);
63641
63843
  const result = {};
63642
- const mergedCategories = categories ? { ...DEFAULT_CATEGORIES, ...categories } : DEFAULT_CATEGORIES;
63844
+ const mergedCategories = mergeCategories(categories);
63643
63845
  const availableCategories = Object.entries(mergedCategories).map(([name]) => ({
63644
63846
  name,
63645
63847
  description: categories?.[name]?.description ?? CATEGORY_DESCRIPTIONS[name] ?? "General tasks"
@@ -66682,8 +66884,8 @@ function createManagers(args) {
66682
66884
  // src/plugin/available-categories.ts
66683
66885
  init_constants();
66684
66886
  function createAvailableCategories(pluginConfig) {
66685
- const mergedCategories = pluginConfig.categories ? { ...DEFAULT_CATEGORIES, ...pluginConfig.categories } : DEFAULT_CATEGORIES;
66686
- return Object.entries(mergedCategories).map(([name, categoryConfig]) => {
66887
+ const categories = mergeCategories(pluginConfig.categories);
66888
+ return Object.entries(categories).map(([name, categoryConfig]) => {
66687
66889
  const model = typeof categoryConfig.model === "string" ? categoryConfig.model : undefined;
66688
66890
  return {
66689
66891
  name,
@@ -66865,11 +67067,11 @@ async function createTools(args) {
66865
67067
  }
66866
67068
 
66867
67069
  // src/plugin/chat-params.ts
66868
- function isRecord4(value) {
67070
+ function isRecord5(value) {
66869
67071
  return typeof value === "object" && value !== null;
66870
67072
  }
66871
67073
  function buildChatParamsInput(raw) {
66872
- if (!isRecord4(raw))
67074
+ if (!isRecord5(raw))
66873
67075
  return null;
66874
67076
  const sessionID = raw.sessionID;
66875
67077
  const agent = raw.agent;
@@ -66880,11 +67082,11 @@ function buildChatParamsInput(raw) {
66880
67082
  return null;
66881
67083
  if (typeof agent !== "string")
66882
67084
  return null;
66883
- if (!isRecord4(model))
67085
+ if (!isRecord5(model))
66884
67086
  return null;
66885
- if (!isRecord4(provider))
67087
+ if (!isRecord5(provider))
66886
67088
  return null;
66887
- if (!isRecord4(message))
67089
+ if (!isRecord5(message))
66888
67090
  return null;
66889
67091
  const providerID = model.providerID;
66890
67092
  const modelID = model.modelID;
@@ -66905,12 +67107,12 @@ function buildChatParamsInput(raw) {
66905
67107
  };
66906
67108
  }
66907
67109
  function isChatParamsOutput(raw) {
66908
- if (!isRecord4(raw))
67110
+ if (!isRecord5(raw))
66909
67111
  return false;
66910
- if (!isRecord4(raw.options)) {
67112
+ if (!isRecord5(raw.options)) {
66911
67113
  raw.options = {};
66912
67114
  }
66913
- return isRecord4(raw.options);
67115
+ return isRecord5(raw.options);
66914
67116
  }
66915
67117
  function createChatParamsHandler(args) {
66916
67118
  return async (input, output) => {
@@ -67431,7 +67633,8 @@ var CategoryConfigSchema = exports_external.object({
67431
67633
  textVerbosity: exports_external.enum(["low", "medium", "high"]).optional(),
67432
67634
  tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
67433
67635
  prompt_append: exports_external.string().optional(),
67434
- is_unstable_agent: exports_external.boolean().optional()
67636
+ is_unstable_agent: exports_external.boolean().optional(),
67637
+ disable: exports_external.boolean().optional()
67435
67638
  });
67436
67639
  var BuiltinCategoryNameSchema = exports_external.enum([
67437
67640
  "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.3",
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.3",
78
+ "oh-my-opencode-darwin-x64": "3.5.3",
79
+ "oh-my-opencode-linux-arm64": "3.5.3",
80
+ "oh-my-opencode-linux-arm64-musl": "3.5.3",
81
+ "oh-my-opencode-linux-x64": "3.5.3",
82
+ "oh-my-opencode-linux-x64-musl": "3.5.3",
83
+ "oh-my-opencode-windows-x64": "3.5.3"
84
84
  },
85
85
  "trustedDependencies": [
86
86
  "@ast-grep/cli",