oh-my-opencode 4.3.1 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -7463,7 +7463,6 @@ var init_model_versions = __esm(() => {
7463
7463
  "anthropic/claude-opus-4-5": "anthropic/claude-opus-4-7",
7464
7464
  "anthropic/claude-opus-4-6": "anthropic/claude-opus-4-7",
7465
7465
  "anthropic/claude-sonnet-4-5": "anthropic/claude-sonnet-4-6",
7466
- "openai/gpt-5.3-codex": "openai/gpt-5.4",
7467
7466
  "openai/gpt-5.4": "openai/gpt-5.5"
7468
7467
  };
7469
7468
  });
@@ -7719,6 +7718,12 @@ function migrateConfigFile(configPath, rawConfig) {
7719
7718
  delete copy.omo_agent;
7720
7719
  needsWrite = true;
7721
7720
  }
7721
+ if (copy.lsp !== undefined) {
7722
+ const droppedServers = copy.lsp && typeof copy.lsp === "object" ? Object.keys(copy.lsp) : [];
7723
+ log("Removed obsolete 'lsp' config key from oh-my-opencode config. Custom LSP servers are now configured in .opencode/lsp.json at the project root (consumed by the 'lsp' MCP server). Move any server definitions there to restore them.", { configPath, droppedServers });
7724
+ delete copy.lsp;
7725
+ needsWrite = true;
7726
+ }
7722
7727
  if (copy.experimental && typeof copy.experimental === "object") {
7723
7728
  const experimental = copy.experimental;
7724
7729
  if ("hashline_edit" in experimental) {
@@ -55440,7 +55445,7 @@ var {
55440
55445
  // package.json
55441
55446
  var package_default = {
55442
55447
  name: "oh-my-opencode",
55443
- version: "4.3.1",
55448
+ version: "4.4.0",
55444
55449
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
55445
55450
  main: "./dist/index.js",
55446
55451
  types: "dist/index.d.ts",
@@ -55550,17 +55555,17 @@ var package_default = {
55550
55555
  zod: "^4.4.3"
55551
55556
  },
55552
55557
  optionalDependencies: {
55553
- "oh-my-opencode-darwin-arm64": "4.3.1",
55554
- "oh-my-opencode-darwin-x64": "4.3.1",
55555
- "oh-my-opencode-darwin-x64-baseline": "4.3.1",
55556
- "oh-my-opencode-linux-arm64": "4.3.1",
55557
- "oh-my-opencode-linux-arm64-musl": "4.3.1",
55558
- "oh-my-opencode-linux-x64": "4.3.1",
55559
- "oh-my-opencode-linux-x64-baseline": "4.3.1",
55560
- "oh-my-opencode-linux-x64-musl": "4.3.1",
55561
- "oh-my-opencode-linux-x64-musl-baseline": "4.3.1",
55562
- "oh-my-opencode-windows-x64": "4.3.1",
55563
- "oh-my-opencode-windows-x64-baseline": "4.3.1"
55558
+ "oh-my-opencode-darwin-arm64": "4.4.0",
55559
+ "oh-my-opencode-darwin-x64": "4.4.0",
55560
+ "oh-my-opencode-darwin-x64-baseline": "4.4.0",
55561
+ "oh-my-opencode-linux-arm64": "4.4.0",
55562
+ "oh-my-opencode-linux-arm64-musl": "4.4.0",
55563
+ "oh-my-opencode-linux-x64": "4.4.0",
55564
+ "oh-my-opencode-linux-x64-baseline": "4.4.0",
55565
+ "oh-my-opencode-linux-x64-musl": "4.4.0",
55566
+ "oh-my-opencode-linux-x64-musl-baseline": "4.4.0",
55567
+ "oh-my-opencode-windows-x64": "4.4.0",
55568
+ "oh-my-opencode-windows-x64-baseline": "4.4.0"
55564
55569
  },
55565
55570
  overrides: {
55566
55571
  hono: "^4.12.18",
@@ -57649,8 +57654,10 @@ var import_picocolors6 = __toESM(require_picocolors(), 1);
57649
57654
  function renderAgentHeader(agent, model, variant, agentColorsByName) {
57650
57655
  if (!agent && !model)
57651
57656
  return;
57652
- const agentLabel = agent ? import_picocolors6.default.bold(colorizeWithProfileColor(agent, agentColorsByName[agent])) : "";
57653
- const modelBase = model ?? "";
57657
+ const normalizedAgent = agent?.normalize("NFC") ?? null;
57658
+ const normalizedModel = model?.normalize("NFC") ?? null;
57659
+ const agentLabel = agent ? import_picocolors6.default.bold(colorizeWithProfileColor(normalizedAgent ?? agent, agentColorsByName[agent])) : "";
57660
+ const modelBase = normalizedModel ?? "";
57654
57661
  const variantSuffix = variant ? ` (${variant})` : "";
57655
57662
  const modelLabel = model ? import_picocolors6.default.dim(`${modelBase}${variantSuffix}`) : "";
57656
57663
  process.stdout.write(`
@@ -0,0 +1,19 @@
1
+ export type ParentWakePromptContext = {
2
+ agent?: string;
3
+ model?: {
4
+ providerID: string;
5
+ modelID: string;
6
+ };
7
+ variant?: string;
8
+ tools?: Record<string, boolean>;
9
+ };
10
+ export type PendingParentWake = {
11
+ promptContext: ParentWakePromptContext;
12
+ notifications: string[];
13
+ shouldReply: boolean;
14
+ dispatchedAt?: number;
15
+ toolCallDeferralStartedAt?: number;
16
+ };
17
+ export declare function resolveParentWakePromptContext(promptContext: ParentWakePromptContext): ParentWakePromptContext;
18
+ export declare function cloneParentWake(wake: PendingParentWake): PendingParentWake;
19
+ export declare function isRedundantParentWake(latestWake: PendingParentWake, dispatchedWake: PendingParentWake): boolean;
@@ -1,21 +1,7 @@
1
1
  import type { PluginInput } from "@opencode-ai/plugin";
2
+ import { type ParentWakePromptContext, type PendingParentWake } from "./parent-wake-dedupe";
2
3
  type OpencodeClient = PluginInput["client"];
3
- export type ParentWakePromptContext = {
4
- agent?: string;
5
- model?: {
6
- providerID: string;
7
- modelID: string;
8
- };
9
- variant?: string;
10
- tools?: Record<string, boolean>;
11
- };
12
- export type PendingParentWake = {
13
- promptContext: ParentWakePromptContext;
14
- notifications: string[];
15
- shouldReply: boolean;
16
- dispatchedAt?: number;
17
- toolCallDeferralStartedAt?: number;
18
- };
4
+ export type { ParentWakePromptContext, PendingParentWake } from "./parent-wake-dedupe";
19
5
  type ParentWakeNotifierDeps = {
20
6
  client: OpencodeClient;
21
7
  directory: string;
@@ -59,10 +45,7 @@ export declare class ParentWakeNotifier {
59
45
  shutdown(): void;
60
46
  private isSessionActive;
61
47
  private hasRecentParentSessionActivity;
62
- private resolveParentWakePromptContext;
63
- private cloneParentWake;
64
48
  private trackDispatchedParentWake;
65
- private isSameParentWake;
66
49
  private loadParentWakeSessionMessages;
67
50
  private getParentWakeMessageRole;
68
51
  private getParentWakeMessageFinish;
@@ -76,4 +59,3 @@ export declare class ParentWakeNotifier {
76
59
  private hasAcceptedMessageAfterDispatchedParentWake;
77
60
  private requeueWake;
78
61
  }
79
- export {};
@@ -15,7 +15,12 @@ type ToolResultPart = {
15
15
  }>;
16
16
  [key: string]: unknown;
17
17
  };
18
- type TransformPart = Part | ToolUsePart | ToolResultPart;
18
+ type TextPart = {
19
+ type: "text";
20
+ text: string;
21
+ synthetic: true;
22
+ };
23
+ type TransformPart = Part | ToolUsePart | ToolResultPart | TextPart;
19
24
  type TransformMessageInfo = Message | {
20
25
  role: "user";
21
26
  sessionID?: string;
package/dist/index.js CHANGED
@@ -7285,7 +7285,6 @@ var init_model_versions = __esm(() => {
7285
7285
  "anthropic/claude-opus-4-5": "anthropic/claude-opus-4-7",
7286
7286
  "anthropic/claude-opus-4-6": "anthropic/claude-opus-4-7",
7287
7287
  "anthropic/claude-sonnet-4-5": "anthropic/claude-sonnet-4-6",
7288
- "openai/gpt-5.3-codex": "openai/gpt-5.4",
7289
7288
  "openai/gpt-5.4": "openai/gpt-5.5"
7290
7289
  };
7291
7290
  });
@@ -8570,6 +8569,12 @@ function migrateConfigFile(configPath, rawConfig) {
8570
8569
  delete copy.omo_agent;
8571
8570
  needsWrite = true;
8572
8571
  }
8572
+ if (copy.lsp !== undefined) {
8573
+ const droppedServers = copy.lsp && typeof copy.lsp === "object" ? Object.keys(copy.lsp) : [];
8574
+ log("Removed obsolete 'lsp' config key from oh-my-opencode config. Custom LSP servers are now configured in .opencode/lsp.json at the project root (consumed by the 'lsp' MCP server). Move any server definitions there to restore them.", { configPath, droppedServers });
8575
+ delete copy.lsp;
8576
+ needsWrite = true;
8577
+ }
8573
8578
  if (copy.experimental && typeof copy.experimental === "object") {
8574
8579
  const experimental = copy.experimental;
8575
8580
  if ("hashline_edit" in experimental) {
@@ -80677,6 +80682,7 @@ function createTeamModeStatusInjector(config, keywordDetectorConfig) {
80677
80682
  // src/hooks/tool-pair-validator/hook.ts
80678
80683
  init_logger();
80679
80684
  var TOOL_RESULT_PLACEHOLDER = "Tool output unavailable (context compacted)";
80685
+ var TOOL_RESULT_RECOVERY_CONTINUATION = "Recovered missing tool results. Continue from the repaired tool output.";
80680
80686
  function getToolUseID(part) {
80681
80687
  const candidate = part;
80682
80688
  if (candidate.type === "tool_use" && typeof candidate.id === "string" && candidate.id.length > 0) {
@@ -80754,7 +80760,14 @@ function createSyntheticUserMessage(assistantMessage, missingToolUseIDs) {
80754
80760
  role: "user",
80755
80761
  ...sessionID ? { sessionID } : {}
80756
80762
  },
80757
- parts: missingToolUseIDs.map((toolUseID) => createToolResultPart(toolUseID))
80763
+ parts: [
80764
+ ...missingToolUseIDs.map((toolUseID) => createToolResultPart(toolUseID)),
80765
+ {
80766
+ type: "text",
80767
+ text: TOOL_RESULT_RECOVERY_CONTINUATION,
80768
+ synthetic: true
80769
+ }
80770
+ ]
80758
80771
  };
80759
80772
  }
80760
80773
  function getMessageID(message) {
@@ -92023,6 +92036,10 @@ async function handleAtlasSessionIdle(input) {
92023
92036
  }
92024
92037
  const { boulderState, progress, appendedSession } = activeBoulderSession;
92025
92038
  if (progress.isComplete) {
92039
+ if (sessionState.pendingRetryTimer) {
92040
+ clearTimeout(sessionState.pendingRetryTimer);
92041
+ sessionState.pendingRetryTimer = undefined;
92042
+ }
92026
92043
  const work = getWorkForSession(ctx.directory, sessionID);
92027
92044
  if (work) {
92028
92045
  completeBoulder(ctx.directory, work.work_id);
@@ -92033,6 +92050,10 @@ async function handleAtlasSessionIdle(input) {
92033
92050
  log(`[${HOOK_NAME7}] Boulder complete`, { sessionID, plan: boulderState.plan_name });
92034
92051
  return;
92035
92052
  }
92053
+ if (options?.isContinuationStopped?.(sessionID)) {
92054
+ log(`[${HOOK_NAME7}] Boulder completion nudge skipped because continuation stopped`, { sessionID, plan: boulderState.plan_name });
92055
+ return;
92056
+ }
92036
92057
  if (sessionState.boulderCompletionNudgedAt?.[work.work_id]) {
92037
92058
  log(`[${HOOK_NAME7}] Boulder complete`, { sessionID, plan: boulderState.plan_name });
92038
92059
  return;
@@ -100240,6 +100261,7 @@ function getOpenCodeBundledRg() {
100240
100261
  const isWindows2 = process.platform === "win32";
100241
100262
  const rgName = isWindows2 ? "rg.exe" : "rg";
100242
100263
  const candidates = [
100264
+ join81(getOpenCodeCacheDir(), "bin", rgName),
100243
100265
  join81(getDataDir(), "opencode", "bin", rgName),
100244
100266
  join81(execDir, rgName),
100245
100267
  join81(execDir, "bin", rgName),
@@ -104826,10 +104848,10 @@ async function waitForLookAtSessionResult(client, sessionID, options) {
104826
104848
  const isActive = supportedButNeverSeen || statusType !== null && !isTerminal;
104827
104849
  const { messages, error: messagesError } = await getSessionMessages(client, sessionID);
104828
104850
  const outcome = extractLatestAssistantOutcome(messages);
104829
- if (outcome.text && !isActive) {
104851
+ if (outcome.text && (!isActive || supportedButNeverSeen)) {
104830
104852
  return { messages, outcome, statusType };
104831
104853
  }
104832
- if (outcome.errorName && !isActive) {
104854
+ if (outcome.errorName && (!isActive || supportedButNeverSeen)) {
104833
104855
  return { messages, outcome, statusType };
104834
104856
  }
104835
104857
  if (isActive) {
@@ -104905,7 +104927,7 @@ Original error: ${createResult.error}`;
104905
104927
  const sessionID = createResult.data.id;
104906
104928
  log(`[look_at] Created session: ${sessionID}`);
104907
104929
  log(`[look_at] Sending prompt with ${isBase64Input ? "base64 image" : "file"} to session ${sessionID}`);
104908
- let promptFailed = false;
104930
+ let shouldWaitForStatus = true;
104909
104931
  try {
104910
104932
  await promptSyncWithModelSuggestionRetry(ctx.client, {
104911
104933
  path: { id: sessionID },
@@ -104928,15 +104950,14 @@ Original error: ${createResult.error}`;
104928
104950
  queueBehavior: "defer"
104929
104951
  });
104930
104952
  } catch (promptError) {
104931
- promptFailed = true;
104932
- log("[look_at] Prompt error (ignored, will still fetch messages):", promptError);
104953
+ log("[look_at] Prompt dispatch failed; checking child session evidence:", promptError);
104954
+ shouldWaitForStatus = isAmbiguousPromptDispatchFailure(promptError);
104933
104955
  }
104934
104956
  let observedMessages;
104935
104957
  let observedText;
104936
- if (typeof ctx.client.session.status === "function") {
104958
+ if (shouldWaitForStatus && typeof ctx.client.session.status === "function") {
104937
104959
  const waitResult = await waitForLookAtSessionResult(ctx.client, sessionID, {
104938
- allowStableIdleWithoutActivity: true,
104939
- allowEmptyStableIdleWithoutActivity: promptFailed
104960
+ allowStableIdleWithoutActivity: true
104940
104961
  });
104941
104962
  observedText = waitResult.outcome.text ?? undefined;
104942
104963
  if (observedText) {
@@ -106343,6 +106364,9 @@ async function executeBackgroundTask(args, ctx, executorCtx, parentContext, agen
106343
106364
 
106344
106365
  Task ID: ${task.id}`;
106345
106366
  }
106367
+ if (!sessionId && updatedTask?.sessionId) {
106368
+ sessionId = updatedTask.sessionId;
106369
+ }
106346
106370
  if (sessionId) {
106347
106371
  registerBackgroundSessionContext({
106348
106372
  sessionId,
@@ -110688,6 +110712,42 @@ function detectRepetitiveToolUse(window) {
110688
110712
 
110689
110713
  // src/features/background-agent/parent-wake-notifier.ts
110690
110714
  init_shared();
110715
+
110716
+ // src/features/background-agent/parent-wake-dedupe.ts
110717
+ function resolveParentWakePromptContext(promptContext) {
110718
+ const resolvedAgent = resolveRegisteredAgentName(promptContext.agent);
110719
+ return {
110720
+ ...promptContext,
110721
+ ...resolvedAgent ? { agent: resolvedAgent } : {},
110722
+ ...promptContext.model ? { model: { ...promptContext.model } } : {},
110723
+ ...promptContext.tools ? { tools: { ...promptContext.tools } } : {}
110724
+ };
110725
+ }
110726
+ function cloneParentWake(wake) {
110727
+ const promptContext = resolveParentWakePromptContext(wake.promptContext);
110728
+ return {
110729
+ promptContext,
110730
+ notifications: [...wake.notifications],
110731
+ shouldReply: wake.shouldReply,
110732
+ ...wake.dispatchedAt !== undefined ? { dispatchedAt: wake.dispatchedAt } : {},
110733
+ ...wake.toolCallDeferralStartedAt !== undefined ? { toolCallDeferralStartedAt: wake.toolCallDeferralStartedAt } : {}
110734
+ };
110735
+ }
110736
+ function isRedundantParentWake(latestWake, dispatchedWake) {
110737
+ return parentWakePromptContextMatches(latestWake, dispatchedWake) && parentWakeReplyModeIsCovered(latestWake, dispatchedWake) && parentWakeNotificationsAreCovered(latestWake, dispatchedWake);
110738
+ }
110739
+ function parentWakePromptContextMatches(left, right) {
110740
+ return JSON.stringify(left.promptContext) === JSON.stringify(right.promptContext);
110741
+ }
110742
+ function parentWakeReplyModeIsCovered(latestWake, dispatchedWake) {
110743
+ return !latestWake.shouldReply || dispatchedWake.shouldReply;
110744
+ }
110745
+ function parentWakeNotificationsAreCovered(latestWake, dispatchedWake) {
110746
+ const dispatchedNotifications = new Set(dispatchedWake.notifications);
110747
+ return latestWake.notifications.every((notification2) => dispatchedNotifications.has(notification2));
110748
+ }
110749
+
110750
+ // src/features/background-agent/parent-wake-notifier.ts
110691
110751
  function unrefTimerHandle(handle) {
110692
110752
  const maybeUnref = handle.unref;
110693
110753
  if (typeof maybeUnref === "function") {
@@ -110725,7 +110785,7 @@ class ParentWakeNotifier {
110725
110785
  this.recentParentSessionActivity.set(sessionID, Date.now());
110726
110786
  }
110727
110787
  queuePendingParentWake(sessionID, notification2, promptContext, shouldReply, delayMs) {
110728
- const resolvedPromptContext = this.resolveParentWakePromptContext(promptContext);
110788
+ const resolvedPromptContext = resolveParentWakePromptContext(promptContext);
110729
110789
  const pendingWake = this.pendingParentWakes.get(sessionID);
110730
110790
  if (pendingWake) {
110731
110791
  pendingWake.notifications.push(notification2);
@@ -110778,6 +110838,12 @@ class ParentWakeNotifier {
110778
110838
  });
110779
110839
  return;
110780
110840
  }
110841
+ const dispatchedWake = this.dispatchedParentWakes.get(sessionID);
110842
+ if (dispatchedWake && isRedundantParentWake(latestWake, dispatchedWake)) {
110843
+ this.pendingParentWakes.delete(sessionID);
110844
+ log("[background-agent] Suppressed duplicate parent wake already dispatched:", { sessionID });
110845
+ return;
110846
+ }
110781
110847
  this.pendingParentWakes.delete(sessionID);
110782
110848
  const notificationContent = latestWake.notifications.join(`
110783
110849
 
@@ -110805,9 +110871,9 @@ class ParentWakeNotifier {
110805
110871
  });
110806
110872
  if (promptResult.status === "failed") {
110807
110873
  if (isAmbiguousPostDispatchPromptFailure(promptResult)) {
110808
- const dispatchedWake = this.cloneParentWake(latestWake);
110809
- dispatchedWake.dispatchedAt = dispatchStartedAt;
110810
- if (await this.hasAcceptedMessageAfterDispatchedParentWake(sessionID, dispatchedWake)) {
110874
+ const dispatchedWake2 = cloneParentWake(latestWake);
110875
+ dispatchedWake2.dispatchedAt = dispatchStartedAt;
110876
+ if (await this.hasAcceptedMessageAfterDispatchedParentWake(sessionID, dispatchedWake2)) {
110811
110877
  this.trackDispatchedParentWake(sessionID, latestWake, dispatchStartedAt);
110812
110878
  log("[background-agent] Treated failed parent wake prompt as accepted after observing session history:", {
110813
110879
  sessionID,
@@ -110819,8 +110885,8 @@ class ParentWakeNotifier {
110819
110885
  throw promptResult.error;
110820
110886
  }
110821
110887
  if (promptResult.status === "reserved" && promptResult.reservedBy === "background-agent-parent-wake") {
110822
- const dispatchedWake = this.dispatchedParentWakes.get(sessionID);
110823
- if (dispatchedWake && this.isSameParentWake(latestWake, dispatchedWake)) {
110888
+ const dispatchedWake2 = this.dispatchedParentWakes.get(sessionID);
110889
+ if (dispatchedWake2 && isRedundantParentWake(latestWake, dispatchedWake2)) {
110824
110890
  log("[background-agent] Suppressed duplicate parent wake during promptAsync gate hold:", { sessionID });
110825
110891
  return;
110826
110892
  }
@@ -110929,28 +110995,9 @@ class ParentWakeNotifier {
110929
110995
  this.recentParentSessionActivity.delete(sessionID);
110930
110996
  return false;
110931
110997
  }
110932
- resolveParentWakePromptContext(promptContext) {
110933
- const resolvedAgent = resolveRegisteredAgentName(promptContext.agent);
110934
- return {
110935
- ...promptContext,
110936
- ...resolvedAgent ? { agent: resolvedAgent } : {},
110937
- ...promptContext.model ? { model: { ...promptContext.model } } : {},
110938
- ...promptContext.tools ? { tools: { ...promptContext.tools } } : {}
110939
- };
110940
- }
110941
- cloneParentWake(wake) {
110942
- const promptContext = this.resolveParentWakePromptContext(wake.promptContext);
110943
- return {
110944
- promptContext,
110945
- notifications: [...wake.notifications],
110946
- shouldReply: wake.shouldReply,
110947
- ...wake.dispatchedAt !== undefined ? { dispatchedAt: wake.dispatchedAt } : {},
110948
- ...wake.toolCallDeferralStartedAt !== undefined ? { toolCallDeferralStartedAt: wake.toolCallDeferralStartedAt } : {}
110949
- };
110950
- }
110951
110998
  trackDispatchedParentWake(sessionID, wake, dispatchedAt) {
110952
110999
  this.clearDispatchedParentWake(sessionID);
110953
- const dispatchedWake = this.cloneParentWake(wake);
111000
+ const dispatchedWake = cloneParentWake(wake);
110954
111001
  dispatchedWake.dispatchedAt = dispatchedAt;
110955
111002
  this.dispatchedParentWakes.set(sessionID, dispatchedWake);
110956
111003
  const timer = setTimeout(() => {
@@ -110960,9 +111007,6 @@ class ParentWakeNotifier {
110960
111007
  unrefTimerHandle(timer);
110961
111008
  this.dispatchedParentWakeTimers.set(sessionID, timer);
110962
111009
  }
110963
- isSameParentWake(left, right) {
110964
- return left.shouldReply === right.shouldReply && JSON.stringify(left.notifications) === JSON.stringify(right.notifications) && JSON.stringify(left.promptContext) === JSON.stringify(right.promptContext);
110965
- }
110966
111010
  async loadParentWakeSessionMessages(sessionID) {
110967
111011
  try {
110968
111012
  const messagesResp = await messagesInDirectory(this.deps.client, {
@@ -111131,7 +111175,7 @@ class ParentWakeNotifier {
111131
111175
  pendingWake.toolCallDeferralStartedAt ??= latestWake.toolCallDeferralStartedAt;
111132
111176
  return;
111133
111177
  }
111134
- this.pendingParentWakes.set(sessionID, this.cloneParentWake(latestWake));
111178
+ this.pendingParentWakes.set(sessionID, cloneParentWake(latestWake));
111135
111179
  }
111136
111180
  }
111137
111181
 
@@ -132354,13 +132398,16 @@ function maybeCreateAtlasConfig(input) {
132354
132398
  return;
132355
132399
  const orchestratorOverride = agentOverrides["atlas"];
132356
132400
  const atlasRequirement = AGENT_MODEL_REQUIREMENTS["atlas"];
132357
- const atlasResolution = applyModelResolution({
132401
+ let atlasResolution = applyModelResolution({
132358
132402
  uiSelectedModel: orchestratorOverride?.model !== undefined ? undefined : uiSelectedModel,
132359
132403
  userModel: orchestratorOverride?.model,
132360
132404
  requirement: atlasRequirement,
132361
132405
  availableModels,
132362
132406
  systemDefaultModel
132363
132407
  });
132408
+ if (!atlasResolution && orchestratorOverride?.model) {
132409
+ atlasResolution = { model: orchestratorOverride.model, provenance: "override" };
132410
+ }
132364
132411
  if (!atlasResolution)
132365
132412
  return;
132366
132413
  const { model: atlasModel, variant: atlasResolvedVariant } = atlasResolution;
@@ -136031,6 +136078,14 @@ function supportsImageInput(modelConfig) {
136031
136078
  }
136032
136079
  return modelConfig?.capabilities?.input?.image === true;
136033
136080
  }
136081
+ function parseTrustedModel(modelString) {
136082
+ const [providerID, ...modelIDParts] = modelString.split("/");
136083
+ const modelID = modelIDParts.join("/");
136084
+ if (!providerID || modelID.length === 0) {
136085
+ return;
136086
+ }
136087
+ return { providerID, modelID };
136088
+ }
136034
136089
  function applyProviderConfig(params) {
136035
136090
  const providers = params.config.provider;
136036
136091
  const modelContextLimitsCache = params.modelCacheState.modelContextLimitsCache;
@@ -136041,22 +136096,31 @@ function applyProviderConfig(params) {
136041
136096
  params.modelCacheState.visionCapableModelsCache = visionCapableModelsCache2;
136042
136097
  visionCapableModelsCache2.clear();
136043
136098
  setVisionCapableModelsCache(visionCapableModelsCache2);
136044
- if (!providers)
136045
- return;
136046
- for (const [providerID, providerConfig] of Object.entries(providers)) {
136047
- const models = providerConfig?.models;
136048
- if (!models)
136049
- continue;
136050
- for (const [modelID, modelConfig] of Object.entries(models)) {
136051
- if (supportsImageInput(modelConfig)) {
136052
- visionCapableModelsCache2.set(`${providerID}/${modelID}`, { providerID, modelID });
136053
- }
136054
- const contextLimit = modelConfig?.limit?.context;
136055
- if (!contextLimit)
136099
+ if (providers) {
136100
+ for (const [providerID, providerConfig] of Object.entries(providers)) {
136101
+ const models = providerConfig?.models;
136102
+ if (!models)
136056
136103
  continue;
136057
- modelContextLimitsCache.set(`${providerID}/${modelID}`, contextLimit);
136104
+ for (const [modelID, modelConfig] of Object.entries(models)) {
136105
+ if (supportsImageInput(modelConfig)) {
136106
+ visionCapableModelsCache2.set(`${providerID}/${modelID}`, { providerID, modelID });
136107
+ }
136108
+ const contextLimit = modelConfig?.limit?.context;
136109
+ if (!contextLimit)
136110
+ continue;
136111
+ modelContextLimitsCache.set(`${providerID}/${modelID}`, contextLimit);
136112
+ }
136058
136113
  }
136059
136114
  }
136115
+ for (const trustedModelString of params.trustedVisionCapableModels ?? []) {
136116
+ const trustedModel = parseTrustedModel(trustedModelString);
136117
+ if (!trustedModel)
136118
+ continue;
136119
+ const key = `${trustedModel.providerID}/${trustedModel.modelID}`;
136120
+ if (visionCapableModelsCache2.has(key))
136121
+ continue;
136122
+ visionCapableModelsCache2.set(key, trustedModel);
136123
+ }
136060
136124
  }
136061
136125
 
136062
136126
  // src/plugin-handlers/plugin-components-loader.ts
@@ -136218,12 +136282,25 @@ function applyToolConfig(params) {
136218
136282
  }
136219
136283
 
136220
136284
  // src/plugin-handlers/config-handler.ts
136285
+ function collectTrustedVisionCapableModels(pluginConfig) {
136286
+ const trusted = [];
136287
+ const multimodalLookerOverride = pluginConfig.agents?.["multimodal-looker"];
136288
+ const configuredModel = multimodalLookerOverride?.model;
136289
+ if (typeof configuredModel === "string" && configuredModel.includes("/")) {
136290
+ trusted.push(configuredModel);
136291
+ }
136292
+ return trusted;
136293
+ }
136221
136294
  function createConfigHandler(deps) {
136222
136295
  const { ctx, pluginConfig, modelCacheState } = deps;
136223
136296
  return async (config) => {
136224
136297
  const formatterConfig = config.formatter;
136225
136298
  setAdditionalAllowedMcpEnvVars(pluginConfig.mcp_env_allowlist ?? []);
136226
- applyProviderConfig({ config, modelCacheState });
136299
+ applyProviderConfig({
136300
+ config,
136301
+ modelCacheState,
136302
+ trustedVisionCapableModels: collectTrustedVisionCapableModels(pluginConfig)
136303
+ });
136227
136304
  clearFormatterCache();
136228
136305
  const pluginComponents = await loadPluginComponents({ pluginConfig });
136229
136306
  applyHookConfig({ pluginComponents, ctx });
@@ -2,4 +2,5 @@ import type { ModelCacheState } from "../plugin-state";
2
2
  export declare function applyProviderConfig(params: {
3
3
  config: Record<string, unknown>;
4
4
  modelCacheState: ModelCacheState;
5
+ trustedVisionCapableModels?: string[];
5
6
  }): void;
@@ -4,6 +4,12 @@
4
4
  * bumps to newer model versions.
5
5
  *
6
6
  * Keys are full "provider/model" strings. Only openai and anthropic entries needed.
7
+ *
8
+ * Only include genuinely retired/superseded models here. Do NOT add mappings
9
+ * for current, user-selectable variants — `gpt-5.3-codex` is the canonical
10
+ * codex powerhouse referenced in docs/guide/agent-model-matching.md and is
11
+ * NOT a deprecated alias for `gpt-5.4`. Auto-rewriting an explicit user
12
+ * choice silently broke configurations (#3777).
7
13
  */
8
14
  export declare const MODEL_VERSION_MAP: Record<string, string>;
9
15
  export declare function migrateModelVersions(configs: Record<string, unknown>, appliedMigrations?: Set<string>): {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode",
3
- "version": "4.3.1",
3
+ "version": "4.4.0",
4
4
  "description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
5
5
  "main": "./dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -110,17 +110,17 @@
110
110
  "zod": "^4.4.3"
111
111
  },
112
112
  "optionalDependencies": {
113
- "oh-my-opencode-darwin-arm64": "4.3.1",
114
- "oh-my-opencode-darwin-x64": "4.3.1",
115
- "oh-my-opencode-darwin-x64-baseline": "4.3.1",
116
- "oh-my-opencode-linux-arm64": "4.3.1",
117
- "oh-my-opencode-linux-arm64-musl": "4.3.1",
118
- "oh-my-opencode-linux-x64": "4.3.1",
119
- "oh-my-opencode-linux-x64-baseline": "4.3.1",
120
- "oh-my-opencode-linux-x64-musl": "4.3.1",
121
- "oh-my-opencode-linux-x64-musl-baseline": "4.3.1",
122
- "oh-my-opencode-windows-x64": "4.3.1",
123
- "oh-my-opencode-windows-x64-baseline": "4.3.1"
113
+ "oh-my-opencode-darwin-arm64": "4.4.0",
114
+ "oh-my-opencode-darwin-x64": "4.4.0",
115
+ "oh-my-opencode-darwin-x64-baseline": "4.4.0",
116
+ "oh-my-opencode-linux-arm64": "4.4.0",
117
+ "oh-my-opencode-linux-arm64-musl": "4.4.0",
118
+ "oh-my-opencode-linux-x64": "4.4.0",
119
+ "oh-my-opencode-linux-x64-baseline": "4.4.0",
120
+ "oh-my-opencode-linux-x64-musl": "4.4.0",
121
+ "oh-my-opencode-linux-x64-musl-baseline": "4.4.0",
122
+ "oh-my-opencode-windows-x64": "4.4.0",
123
+ "oh-my-opencode-windows-x64-baseline": "4.4.0"
124
124
  },
125
125
  "overrides": {
126
126
  "hono": "^4.12.18",