oh-my-opencode 2.5.3 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3216,7 +3216,7 @@ async function getContextWindowUsage(ctx, sessionID) {
3216
3216
  return null;
3217
3217
  const lastAssistant = assistantMessages[assistantMessages.length - 1];
3218
3218
  const lastTokens = lastAssistant.tokens;
3219
- const usedTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0);
3219
+ const usedTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0) + (lastTokens?.output ?? 0);
3220
3220
  const remainingTokens = ANTHROPIC_ACTUAL_LIMIT - usedTokens;
3221
3221
  return {
3222
3222
  usedTokens,
@@ -3663,6 +3663,24 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
3663
3663
  errorSessions.delete(sessionID);
3664
3664
  return;
3665
3665
  }
3666
+ let freshTodos = [];
3667
+ try {
3668
+ log(`[${HOOK_NAME}] Re-verifying todos after countdown`, { sessionID });
3669
+ const response = await ctx.client.session.todo({
3670
+ path: { id: sessionID }
3671
+ });
3672
+ freshTodos = response.data ?? response;
3673
+ log(`[${HOOK_NAME}] Fresh todo count`, { sessionID, todosCount: freshTodos?.length ?? 0 });
3674
+ } catch (err) {
3675
+ log(`[${HOOK_NAME}] Failed to re-verify todos`, { sessionID, error: String(err) });
3676
+ return;
3677
+ }
3678
+ const freshIncomplete = freshTodos.filter((t) => t.status !== "completed" && t.status !== "cancelled");
3679
+ if (freshIncomplete.length === 0) {
3680
+ log(`[${HOOK_NAME}] Abort: no incomplete todos after countdown`, { sessionID, total: freshTodos.length });
3681
+ return;
3682
+ }
3683
+ log(`[${HOOK_NAME}] Confirmed incomplete todos, proceeding with injection`, { sessionID, incomplete: freshIncomplete.length, total: freshTodos.length });
3666
3684
  remindedSessions.add(sessionID);
3667
3685
  try {
3668
3686
  const messageDir = getMessageDir(sessionID);
@@ -3683,7 +3701,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
3683
3701
  type: "text",
3684
3702
  text: `${CONTINUATION_PROMPT}
3685
3703
 
3686
- [Status: ${todos.length - incomplete.length}/${todos.length} completed, ${incomplete.length} remaining]`
3704
+ [Status: ${freshTodos.length - freshIncomplete.length}/${freshTodos.length} completed, ${freshIncomplete.length} remaining]`
3687
3705
  }
3688
3706
  ]
3689
3707
  },
@@ -5082,6 +5100,7 @@ function clearInjectedPaths(sessionID) {
5082
5100
  function createDirectoryAgentsInjectorHook(ctx) {
5083
5101
  const sessionCaches = new Map;
5084
5102
  const pendingBatchReads = new Map;
5103
+ const truncator = createDynamicTruncator(ctx);
5085
5104
  function getSessionCache(sessionID) {
5086
5105
  if (!sessionCaches.has(sessionID)) {
5087
5106
  sessionCaches.set(sessionID, loadInjectedPaths(sessionID));
@@ -5114,7 +5133,7 @@ function createDirectoryAgentsInjectorHook(ctx) {
5114
5133
  }
5115
5134
  return found.reverse();
5116
5135
  }
5117
- function processFilePathForInjection(filePath, sessionID, output) {
5136
+ async function processFilePathForInjection(filePath, sessionID, output) {
5118
5137
  const resolved = resolveFilePath2(filePath);
5119
5138
  if (!resolved)
5120
5139
  return;
@@ -5127,10 +5146,14 @@ function createDirectoryAgentsInjectorHook(ctx) {
5127
5146
  continue;
5128
5147
  try {
5129
5148
  const content = readFileSync5(agentsPath, "utf-8");
5149
+ const { result, truncated } = await truncator.truncate(sessionID, content);
5150
+ const truncationNotice = truncated ? `
5151
+
5152
+ [Note: Content was truncated to save context window space. For full context, please read the file directly: ${agentsPath}]` : "";
5130
5153
  output.output += `
5131
5154
 
5132
5155
  [Directory Context: ${agentsPath}]
5133
- ${content}`;
5156
+ ${result}${truncationNotice}`;
5134
5157
  cache.add(agentsDir);
5135
5158
  } catch {}
5136
5159
  }
@@ -5155,14 +5178,14 @@ ${content}`;
5155
5178
  const toolExecuteAfter = async (input, output) => {
5156
5179
  const toolName = input.tool.toLowerCase();
5157
5180
  if (toolName === "read") {
5158
- processFilePathForInjection(output.title, input.sessionID, output);
5181
+ await processFilePathForInjection(output.title, input.sessionID, output);
5159
5182
  return;
5160
5183
  }
5161
5184
  if (toolName === "batch") {
5162
5185
  const filePaths = pendingBatchReads.get(input.callID);
5163
5186
  if (filePaths) {
5164
5187
  for (const filePath of filePaths) {
5165
- processFilePathForInjection(filePath, input.sessionID, output);
5188
+ await processFilePathForInjection(filePath, input.sessionID, output);
5166
5189
  }
5167
5190
  pendingBatchReads.delete(input.callID);
5168
5191
  }
@@ -5249,6 +5272,7 @@ function clearInjectedPaths2(sessionID) {
5249
5272
  function createDirectoryReadmeInjectorHook(ctx) {
5250
5273
  const sessionCaches = new Map;
5251
5274
  const pendingBatchReads = new Map;
5275
+ const truncator = createDynamicTruncator(ctx);
5252
5276
  function getSessionCache(sessionID) {
5253
5277
  if (!sessionCaches.has(sessionID)) {
5254
5278
  sessionCaches.set(sessionID, loadInjectedPaths2(sessionID));
@@ -5281,7 +5305,7 @@ function createDirectoryReadmeInjectorHook(ctx) {
5281
5305
  }
5282
5306
  return found.reverse();
5283
5307
  }
5284
- function processFilePathForInjection(filePath, sessionID, output) {
5308
+ async function processFilePathForInjection(filePath, sessionID, output) {
5285
5309
  const resolved = resolveFilePath2(filePath);
5286
5310
  if (!resolved)
5287
5311
  return;
@@ -5294,10 +5318,14 @@ function createDirectoryReadmeInjectorHook(ctx) {
5294
5318
  continue;
5295
5319
  try {
5296
5320
  const content = readFileSync7(readmePath, "utf-8");
5321
+ const { result, truncated } = await truncator.truncate(sessionID, content);
5322
+ const truncationNotice = truncated ? `
5323
+
5324
+ [Note: Content was truncated to save context window space. For full context, please read the file directly: ${readmePath}]` : "";
5297
5325
  output.output += `
5298
5326
 
5299
5327
  [Project README: ${readmePath}]
5300
- ${content}`;
5328
+ ${result}${truncationNotice}`;
5301
5329
  cache.add(readmeDir);
5302
5330
  } catch {}
5303
5331
  }
@@ -5322,14 +5350,14 @@ ${content}`;
5322
5350
  const toolExecuteAfter = async (input, output) => {
5323
5351
  const toolName = input.tool.toLowerCase();
5324
5352
  if (toolName === "read") {
5325
- processFilePathForInjection(output.title, input.sessionID, output);
5353
+ await processFilePathForInjection(output.title, input.sessionID, output);
5326
5354
  return;
5327
5355
  }
5328
5356
  if (toolName === "batch") {
5329
5357
  const filePaths = pendingBatchReads.get(input.callID);
5330
5358
  if (filePaths) {
5331
5359
  for (const filePath of filePaths) {
5332
- processFilePathForInjection(filePath, input.sessionID, output);
5360
+ await processFilePathForInjection(filePath, input.sessionID, output);
5333
5361
  }
5334
5362
  pendingBatchReads.delete(input.callID);
5335
5363
  }
@@ -5875,244 +5903,250 @@ async function fixEmptyMessages(sessionID, autoCompactState, client, messageInde
5875
5903
  }
5876
5904
  async function executeCompact(sessionID, msg, autoCompactState, client, directory, experimental) {
5877
5905
  if (autoCompactState.compactionInProgress.has(sessionID)) {
5906
+ await client.tui.showToast({
5907
+ body: {
5908
+ title: "Compact In Progress",
5909
+ message: "Recovery already running. Please wait or start new session if stuck.",
5910
+ variant: "warning",
5911
+ duration: 5000
5912
+ }
5913
+ }).catch(() => {});
5878
5914
  return;
5879
5915
  }
5880
5916
  autoCompactState.compactionInProgress.add(sessionID);
5881
- const errorData = autoCompactState.errorDataBySession.get(sessionID);
5882
- const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
5883
- if (experimental?.aggressive_truncation && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens && truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
5884
- log("[auto-compact] aggressive truncation triggered (experimental)", {
5885
- currentTokens: errorData.currentTokens,
5886
- maxTokens: errorData.maxTokens,
5887
- targetRatio: TRUNCATE_CONFIG.targetTokenRatio
5888
- });
5889
- const aggressiveResult = truncateUntilTargetTokens(sessionID, errorData.currentTokens, errorData.maxTokens, TRUNCATE_CONFIG.targetTokenRatio, TRUNCATE_CONFIG.charsPerToken);
5890
- if (aggressiveResult.truncatedCount > 0) {
5891
- truncateState.truncateAttempt += aggressiveResult.truncatedCount;
5892
- const toolNames = aggressiveResult.truncatedTools.map((t) => t.toolName).join(", ");
5893
- const statusMsg = aggressiveResult.sufficient ? `Truncated ${aggressiveResult.truncatedCount} outputs (${formatBytes(aggressiveResult.totalBytesRemoved)})` : `Truncated ${aggressiveResult.truncatedCount} outputs (${formatBytes(aggressiveResult.totalBytesRemoved)}) but need ${formatBytes(aggressiveResult.targetBytesToRemove)}. Falling back to summarize/revert...`;
5894
- await client.tui.showToast({
5895
- body: {
5896
- title: aggressiveResult.sufficient ? "Aggressive Truncation" : "Partial Truncation",
5897
- message: `${statusMsg}: ${toolNames}`,
5898
- variant: "warning",
5899
- duration: 4000
5900
- }
5901
- }).catch(() => {});
5902
- log("[auto-compact] aggressive truncation completed", aggressiveResult);
5903
- if (aggressiveResult.sufficient) {
5904
- autoCompactState.compactionInProgress.delete(sessionID);
5905
- setTimeout(async () => {
5906
- try {
5907
- await client.session.prompt_async({
5908
- path: { sessionID },
5909
- body: { parts: [{ type: "text", text: "Continue" }] },
5910
- query: { directory }
5911
- });
5912
- } catch {}
5913
- }, 500);
5914
- return;
5915
- }
5916
- } else {
5917
- await client.tui.showToast({
5918
- body: {
5919
- title: "Truncation Skipped",
5920
- message: "No tool outputs found to truncate.",
5921
- variant: "warning",
5922
- duration: 3000
5917
+ try {
5918
+ const errorData = autoCompactState.errorDataBySession.get(sessionID);
5919
+ const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
5920
+ if (experimental?.aggressive_truncation && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens && truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
5921
+ log("[auto-compact] aggressive truncation triggered (experimental)", {
5922
+ currentTokens: errorData.currentTokens,
5923
+ maxTokens: errorData.maxTokens,
5924
+ targetRatio: TRUNCATE_CONFIG.targetTokenRatio
5925
+ });
5926
+ const aggressiveResult = truncateUntilTargetTokens(sessionID, errorData.currentTokens, errorData.maxTokens, TRUNCATE_CONFIG.targetTokenRatio, TRUNCATE_CONFIG.charsPerToken);
5927
+ if (aggressiveResult.truncatedCount > 0) {
5928
+ truncateState.truncateAttempt += aggressiveResult.truncatedCount;
5929
+ const toolNames = aggressiveResult.truncatedTools.map((t) => t.toolName).join(", ");
5930
+ const statusMsg = aggressiveResult.sufficient ? `Truncated ${aggressiveResult.truncatedCount} outputs (${formatBytes(aggressiveResult.totalBytesRemoved)})` : `Truncated ${aggressiveResult.truncatedCount} outputs (${formatBytes(aggressiveResult.totalBytesRemoved)}) but need ${formatBytes(aggressiveResult.targetBytesToRemove)}. Falling back to summarize/revert...`;
5931
+ await client.tui.showToast({
5932
+ body: {
5933
+ title: aggressiveResult.sufficient ? "Aggressive Truncation" : "Partial Truncation",
5934
+ message: `${statusMsg}: ${toolNames}`,
5935
+ variant: "warning",
5936
+ duration: 4000
5937
+ }
5938
+ }).catch(() => {});
5939
+ log("[auto-compact] aggressive truncation completed", aggressiveResult);
5940
+ if (aggressiveResult.sufficient) {
5941
+ setTimeout(async () => {
5942
+ try {
5943
+ await client.session.prompt_async({
5944
+ path: { sessionID },
5945
+ body: { parts: [{ type: "text", text: "Continue" }] },
5946
+ query: { directory }
5947
+ });
5948
+ } catch {}
5949
+ }, 500);
5950
+ return;
5923
5951
  }
5924
- }).catch(() => {});
5925
- }
5926
- }
5927
- let skipSummarize = false;
5928
- if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
5929
- const largest = findLargestToolResult(sessionID);
5930
- if (largest && largest.outputSize >= TRUNCATE_CONFIG.minOutputSizeToTruncate) {
5931
- const result = truncateToolResult(largest.partPath);
5932
- if (result.success) {
5933
- truncateState.truncateAttempt++;
5934
- truncateState.lastTruncatedPartId = largest.partId;
5952
+ } else {
5935
5953
  await client.tui.showToast({
5936
5954
  body: {
5937
- title: "Truncating Large Output",
5938
- message: `Truncated ${result.toolName} (${formatBytes(result.originalSize ?? 0)}). Retrying...`,
5955
+ title: "Truncation Skipped",
5956
+ message: "No tool outputs found to truncate.",
5939
5957
  variant: "warning",
5940
5958
  duration: 3000
5941
5959
  }
5942
5960
  }).catch(() => {});
5943
- autoCompactState.compactionInProgress.delete(sessionID);
5944
- setTimeout(async () => {
5945
- try {
5946
- await client.session.prompt_async({
5947
- path: { sessionID },
5948
- body: { parts: [{ type: "text", text: "Continue" }] },
5949
- query: { directory }
5950
- });
5951
- } catch {}
5952
- }, 500);
5953
- return;
5954
5961
  }
5955
- } else if (errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
5956
- skipSummarize = true;
5957
- await client.tui.showToast({
5958
- body: {
5959
- title: "Summarize Skipped",
5960
- message: `Over token limit (${errorData.currentTokens}/${errorData.maxTokens}) with nothing to truncate. Going to revert...`,
5961
- variant: "warning",
5962
- duration: 3000
5963
- }
5964
- }).catch(() => {});
5965
- } else if (!errorData?.currentTokens) {
5966
- await client.tui.showToast({
5967
- body: {
5968
- title: "Truncation Skipped",
5969
- message: "No large tool outputs found.",
5970
- variant: "warning",
5971
- duration: 3000
5972
- }
5973
- }).catch(() => {});
5974
5962
  }
5975
- }
5976
- const retryState = getOrCreateRetryState(autoCompactState, sessionID);
5977
- if (errorData?.errorType?.includes("non-empty content")) {
5978
- const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
5979
- if (attempt < 3) {
5980
- const fixed = await fixEmptyMessages(sessionID, autoCompactState, client, errorData.messageIndex);
5981
- if (fixed) {
5982
- autoCompactState.compactionInProgress.delete(sessionID);
5983
- setTimeout(() => {
5984
- executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
5985
- }, 500);
5986
- return;
5987
- }
5988
- } else {
5989
- await client.tui.showToast({
5990
- body: {
5991
- title: "Recovery Failed",
5992
- message: "Max recovery attempts (3) reached for empty content error. Please start a new session.",
5993
- variant: "error",
5994
- duration: 1e4
5963
+ let skipSummarize = false;
5964
+ if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
5965
+ const largest = findLargestToolResult(sessionID);
5966
+ if (largest && largest.outputSize >= TRUNCATE_CONFIG.minOutputSizeToTruncate) {
5967
+ const result = truncateToolResult(largest.partPath);
5968
+ if (result.success) {
5969
+ truncateState.truncateAttempt++;
5970
+ truncateState.lastTruncatedPartId = largest.partId;
5971
+ await client.tui.showToast({
5972
+ body: {
5973
+ title: "Truncating Large Output",
5974
+ message: `Truncated ${result.toolName} (${formatBytes(result.originalSize ?? 0)}). Retrying...`,
5975
+ variant: "warning",
5976
+ duration: 3000
5977
+ }
5978
+ }).catch(() => {});
5979
+ setTimeout(async () => {
5980
+ try {
5981
+ await client.session.prompt_async({
5982
+ path: { sessionID },
5983
+ body: { parts: [{ type: "text", text: "Continue" }] },
5984
+ query: { directory }
5985
+ });
5986
+ } catch {}
5987
+ }, 500);
5988
+ return;
5995
5989
  }
5996
- }).catch(() => {});
5997
- autoCompactState.compactionInProgress.delete(sessionID);
5998
- return;
5999
- }
6000
- }
6001
- if (Date.now() - retryState.lastAttemptTime > 300000) {
6002
- retryState.attempt = 0;
6003
- autoCompactState.fallbackStateBySession.delete(sessionID);
6004
- autoCompactState.truncateStateBySession.delete(sessionID);
6005
- }
6006
- if (!skipSummarize && retryState.attempt < RETRY_CONFIG.maxAttempts) {
6007
- retryState.attempt++;
6008
- retryState.lastAttemptTime = Date.now();
6009
- const providerID = msg.providerID;
6010
- const modelID = msg.modelID;
6011
- if (providerID && modelID) {
6012
- try {
5990
+ } else if (errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
5991
+ skipSummarize = true;
6013
5992
  await client.tui.showToast({
6014
5993
  body: {
6015
- title: "Auto Compact",
6016
- message: `Summarizing session (attempt ${retryState.attempt}/${RETRY_CONFIG.maxAttempts})...`,
5994
+ title: "Summarize Skipped",
5995
+ message: `Over token limit (${errorData.currentTokens}/${errorData.maxTokens}) with nothing to truncate. Going to revert...`,
5996
+ variant: "warning",
5997
+ duration: 3000
5998
+ }
5999
+ }).catch(() => {});
6000
+ } else if (!errorData?.currentTokens) {
6001
+ await client.tui.showToast({
6002
+ body: {
6003
+ title: "Truncation Skipped",
6004
+ message: "No large tool outputs found.",
6017
6005
  variant: "warning",
6018
6006
  duration: 3000
6019
6007
  }
6020
6008
  }).catch(() => {});
6021
- await client.session.summarize({
6022
- path: { id: sessionID },
6023
- body: { providerID, modelID },
6024
- query: { directory }
6025
- });
6026
- autoCompactState.compactionInProgress.delete(sessionID);
6027
- setTimeout(async () => {
6028
- try {
6029
- await client.session.prompt_async({
6030
- path: { sessionID },
6031
- body: { parts: [{ type: "text", text: "Continue" }] },
6032
- query: { directory }
6033
- });
6034
- } catch {}
6035
- }, 500);
6036
- return;
6037
- } catch {
6038
- autoCompactState.compactionInProgress.delete(sessionID);
6039
- const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
6040
- const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
6041
- setTimeout(() => {
6042
- executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
6043
- }, cappedDelay);
6044
- return;
6045
6009
  }
6046
- } else {
6047
- await client.tui.showToast({
6048
- body: {
6049
- title: "Summarize Skipped",
6050
- message: "Missing providerID or modelID. Skipping to revert...",
6051
- variant: "warning",
6052
- duration: 3000
6010
+ }
6011
+ const retryState = getOrCreateRetryState(autoCompactState, sessionID);
6012
+ if (errorData?.errorType?.includes("non-empty content")) {
6013
+ const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
6014
+ if (attempt < 3) {
6015
+ const fixed = await fixEmptyMessages(sessionID, autoCompactState, client, errorData.messageIndex);
6016
+ if (fixed) {
6017
+ setTimeout(() => {
6018
+ executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
6019
+ }, 500);
6020
+ return;
6053
6021
  }
6054
- }).catch(() => {});
6022
+ } else {
6023
+ await client.tui.showToast({
6024
+ body: {
6025
+ title: "Recovery Failed",
6026
+ message: "Max recovery attempts (3) reached for empty content error. Please start a new session.",
6027
+ variant: "error",
6028
+ duration: 1e4
6029
+ }
6030
+ }).catch(() => {});
6031
+ return;
6032
+ }
6055
6033
  }
6056
- }
6057
- const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
6058
- if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) {
6059
- const pair = await getLastMessagePair(sessionID, client, directory);
6060
- if (pair) {
6061
- try {
6034
+ if (Date.now() - retryState.lastAttemptTime > 300000) {
6035
+ retryState.attempt = 0;
6036
+ autoCompactState.fallbackStateBySession.delete(sessionID);
6037
+ autoCompactState.truncateStateBySession.delete(sessionID);
6038
+ }
6039
+ if (!skipSummarize && retryState.attempt < RETRY_CONFIG.maxAttempts) {
6040
+ retryState.attempt++;
6041
+ retryState.lastAttemptTime = Date.now();
6042
+ const providerID = msg.providerID;
6043
+ const modelID = msg.modelID;
6044
+ if (providerID && modelID) {
6045
+ try {
6046
+ await client.tui.showToast({
6047
+ body: {
6048
+ title: "Auto Compact",
6049
+ message: `Summarizing session (attempt ${retryState.attempt}/${RETRY_CONFIG.maxAttempts})...`,
6050
+ variant: "warning",
6051
+ duration: 3000
6052
+ }
6053
+ }).catch(() => {});
6054
+ await client.session.summarize({
6055
+ path: { id: sessionID },
6056
+ body: { providerID, modelID },
6057
+ query: { directory }
6058
+ });
6059
+ setTimeout(async () => {
6060
+ try {
6061
+ await client.session.prompt_async({
6062
+ path: { sessionID },
6063
+ body: { parts: [{ type: "text", text: "Continue" }] },
6064
+ query: { directory }
6065
+ });
6066
+ } catch {}
6067
+ }, 500);
6068
+ return;
6069
+ } catch {
6070
+ const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
6071
+ const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
6072
+ setTimeout(() => {
6073
+ executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
6074
+ }, cappedDelay);
6075
+ return;
6076
+ }
6077
+ } else {
6062
6078
  await client.tui.showToast({
6063
6079
  body: {
6064
- title: "Emergency Recovery",
6065
- message: "Removing last message pair...",
6080
+ title: "Summarize Skipped",
6081
+ message: "Missing providerID or modelID. Skipping to revert...",
6066
6082
  variant: "warning",
6067
6083
  duration: 3000
6068
6084
  }
6069
6085
  }).catch(() => {});
6070
- if (pair.assistantMessageID) {
6086
+ }
6087
+ }
6088
+ const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
6089
+ if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) {
6090
+ const pair = await getLastMessagePair(sessionID, client, directory);
6091
+ if (pair) {
6092
+ try {
6093
+ await client.tui.showToast({
6094
+ body: {
6095
+ title: "Emergency Recovery",
6096
+ message: "Removing last message pair...",
6097
+ variant: "warning",
6098
+ duration: 3000
6099
+ }
6100
+ }).catch(() => {});
6101
+ if (pair.assistantMessageID) {
6102
+ await client.session.revert({
6103
+ path: { id: sessionID },
6104
+ body: { messageID: pair.assistantMessageID },
6105
+ query: { directory }
6106
+ });
6107
+ }
6071
6108
  await client.session.revert({
6072
6109
  path: { id: sessionID },
6073
- body: { messageID: pair.assistantMessageID },
6110
+ body: { messageID: pair.userMessageID },
6074
6111
  query: { directory }
6075
6112
  });
6076
- }
6077
- await client.session.revert({
6078
- path: { id: sessionID },
6079
- body: { messageID: pair.userMessageID },
6080
- query: { directory }
6081
- });
6082
- fallbackState.revertAttempt++;
6083
- fallbackState.lastRevertedMessageID = pair.userMessageID;
6084
- clearSessionState(autoCompactState, sessionID);
6085
- setTimeout(async () => {
6086
- try {
6087
- await client.session.prompt_async({
6088
- path: { sessionID },
6089
- body: { parts: [{ type: "text", text: "Continue" }] },
6090
- query: { directory }
6091
- });
6092
- } catch {}
6093
- }, 500);
6094
- return;
6095
- } catch {}
6096
- } else {
6097
- await client.tui.showToast({
6098
- body: {
6099
- title: "Revert Skipped",
6100
- message: "Could not find last message pair to revert.",
6101
- variant: "warning",
6102
- duration: 3000
6103
- }
6104
- }).catch(() => {});
6113
+ fallbackState.revertAttempt++;
6114
+ fallbackState.lastRevertedMessageID = pair.userMessageID;
6115
+ clearSessionState(autoCompactState, sessionID);
6116
+ setTimeout(async () => {
6117
+ try {
6118
+ await client.session.prompt_async({
6119
+ path: { sessionID },
6120
+ body: { parts: [{ type: "text", text: "Continue" }] },
6121
+ query: { directory }
6122
+ });
6123
+ } catch {}
6124
+ }, 500);
6125
+ return;
6126
+ } catch {}
6127
+ } else {
6128
+ await client.tui.showToast({
6129
+ body: {
6130
+ title: "Revert Skipped",
6131
+ message: "Could not find last message pair to revert.",
6132
+ variant: "warning",
6133
+ duration: 3000
6134
+ }
6135
+ }).catch(() => {});
6136
+ }
6105
6137
  }
6138
+ clearSessionState(autoCompactState, sessionID);
6139
+ await client.tui.showToast({
6140
+ body: {
6141
+ title: "Auto Compact Failed",
6142
+ message: "All recovery attempts failed. Please start a new session.",
6143
+ variant: "error",
6144
+ duration: 5000
6145
+ }
6146
+ }).catch(() => {});
6147
+ } finally {
6148
+ autoCompactState.compactionInProgress.delete(sessionID);
6106
6149
  }
6107
- clearSessionState(autoCompactState, sessionID);
6108
- await client.tui.showToast({
6109
- body: {
6110
- title: "Auto Compact Failed",
6111
- message: "All recovery attempts failed. Please start a new session.",
6112
- variant: "error",
6113
- duration: 5000
6114
- }
6115
- }).catch(() => {});
6116
6150
  }
6117
6151
 
6118
6152
  // src/hooks/anthropic-auto-compact/index.ts
@@ -8094,6 +8128,7 @@ var TRACKED_TOOLS = ["read", "write", "edit", "multiedit"];
8094
8128
  function createRulesInjectorHook(ctx) {
8095
8129
  const sessionCaches = new Map;
8096
8130
  const pendingBatchFiles = new Map;
8131
+ const truncator = createDynamicTruncator(ctx);
8097
8132
  function getSessionCache(sessionID) {
8098
8133
  if (!sessionCaches.has(sessionID)) {
8099
8134
  sessionCaches.set(sessionID, loadInjectedRules(sessionID));
@@ -8107,7 +8142,7 @@ function createRulesInjectorHook(ctx) {
8107
8142
  return path5;
8108
8143
  return resolve4(ctx.directory, path5);
8109
8144
  }
8110
- function processFilePathForInjection(filePath, sessionID, output) {
8145
+ async function processFilePathForInjection(filePath, sessionID, output) {
8111
8146
  const resolved = resolveFilePath2(filePath);
8112
8147
  if (!resolved)
8113
8148
  return;
@@ -8143,11 +8178,15 @@ function createRulesInjectorHook(ctx) {
8143
8178
  return;
8144
8179
  toInject.sort((a, b) => a.distance - b.distance);
8145
8180
  for (const rule of toInject) {
8181
+ const { result, truncated } = await truncator.truncate(sessionID, rule.content);
8182
+ const truncationNotice = truncated ? `
8183
+
8184
+ [Note: Content was truncated to save context window space. For full context, please read the file directly: ${rule.relativePath}]` : "";
8146
8185
  output.output += `
8147
8186
 
8148
8187
  [Rule: ${rule.relativePath}]
8149
8188
  [Match: ${rule.matchReason}]
8150
- ${rule.content}`;
8189
+ ${result}${truncationNotice}`;
8151
8190
  }
8152
8191
  saveInjectedRules(sessionID, cache2);
8153
8192
  }
@@ -8177,14 +8216,14 @@ ${rule.content}`;
8177
8216
  const toolExecuteAfter = async (input, output) => {
8178
8217
  const toolName = input.tool.toLowerCase();
8179
8218
  if (TRACKED_TOOLS.includes(toolName)) {
8180
- processFilePathForInjection(output.title, input.sessionID, output);
8219
+ await processFilePathForInjection(output.title, input.sessionID, output);
8181
8220
  return;
8182
8221
  }
8183
8222
  if (toolName === "batch") {
8184
8223
  const filePaths = pendingBatchFiles.get(input.callID);
8185
8224
  if (filePaths) {
8186
8225
  for (const filePath of filePaths) {
8187
- processFilePathForInjection(filePath, input.sessionID, output);
8226
+ await processFilePathForInjection(filePath, input.sessionID, output);
8188
8227
  }
8189
8228
  pendingBatchFiles.delete(input.callID);
8190
8229
  }
@@ -8836,6 +8875,7 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
8836
8875
 
8837
8876
  ## ZERO TOLERANCE FAILURES
8838
8877
  - **NO Scope Reduction**: Never make "demo", "skeleton", "simplified", "basic" versions - deliver FULL implementation
8878
+ - **NO MockUp Work**: When user asked you to do "port A", you must "port A", fully, 100%. No Extra feature, No reduced feature, no mock data, fully working 100% port.
8839
8879
  - **NO Partial Completion**: Never stop at 60-80% saying "you can extend this..." - finish 100%
8840
8880
  - **NO Assumed Shortcuts**: Never skip requirements you deem "optional" or "can be added later"
8841
8881
  - **NO Premature Stopping**: Never declare done until ALL TODOs are completed and verified
@@ -27085,6 +27125,10 @@ var interactive_bash = tool({
27085
27125
  }
27086
27126
  }
27087
27127
  });
27128
+ // src/tools/background-task/tools.ts
27129
+ import { existsSync as existsSync38, readdirSync as readdirSync13 } from "fs";
27130
+ import { join as join47 } from "path";
27131
+
27088
27132
  // src/tools/background-task/constants.ts
27089
27133
  var BACKGROUND_TASK_DESCRIPTION = `Run agent task in background. Returns task_id immediately; notifies on completion.
27090
27134
 
@@ -27093,6 +27137,19 @@ var BACKGROUND_OUTPUT_DESCRIPTION = `Get output from background task. System not
27093
27137
  var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=true to cancel ALL before final answer.`;
27094
27138
 
27095
27139
  // src/tools/background-task/tools.ts
27140
+ function getMessageDir6(sessionID) {
27141
+ if (!existsSync38(MESSAGE_STORAGE))
27142
+ return null;
27143
+ const directPath = join47(MESSAGE_STORAGE, sessionID);
27144
+ if (existsSync38(directPath))
27145
+ return directPath;
27146
+ for (const dir of readdirSync13(MESSAGE_STORAGE)) {
27147
+ const sessionPath = join47(MESSAGE_STORAGE, dir, sessionID);
27148
+ if (existsSync38(sessionPath))
27149
+ return sessionPath;
27150
+ }
27151
+ return null;
27152
+ }
27096
27153
  function formatDuration(start, end) {
27097
27154
  const duration3 = (end ?? new Date).getTime() - start.getTime();
27098
27155
  const seconds = Math.floor(duration3 / 1000);
@@ -27119,12 +27176,16 @@ function createBackgroundTask(manager) {
27119
27176
  return `\u274C Agent parameter is required. Please specify which agent to use (e.g., "explore", "librarian", "build", etc.)`;
27120
27177
  }
27121
27178
  try {
27179
+ const messageDir = getMessageDir6(toolContext.sessionID);
27180
+ const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
27181
+ const parentModel = prevMessage?.model?.providerID && prevMessage?.model?.modelID ? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID } : undefined;
27122
27182
  const task = await manager.launch({
27123
27183
  description: args.description,
27124
27184
  prompt: args.prompt,
27125
27185
  agent: args.agent.trim(),
27126
27186
  parentSessionID: toolContext.sessionID,
27127
- parentMessageID: toolContext.messageID
27187
+ parentMessageID: toolContext.messageID,
27188
+ parentModel
27128
27189
  });
27129
27190
  return `Background task launched successfully.
27130
27191
 
@@ -27641,17 +27702,17 @@ var builtinTools = {
27641
27702
  session_info
27642
27703
  };
27643
27704
  // src/features/background-agent/manager.ts
27644
- import { existsSync as existsSync38, readdirSync as readdirSync13 } from "fs";
27645
- import { join as join47 } from "path";
27646
- function getMessageDir6(sessionID) {
27647
- if (!existsSync38(MESSAGE_STORAGE))
27705
+ import { existsSync as existsSync39, readdirSync as readdirSync14 } from "fs";
27706
+ import { join as join48 } from "path";
27707
+ function getMessageDir7(sessionID) {
27708
+ if (!existsSync39(MESSAGE_STORAGE))
27648
27709
  return null;
27649
- const directPath = join47(MESSAGE_STORAGE, sessionID);
27650
- if (existsSync38(directPath))
27710
+ const directPath = join48(MESSAGE_STORAGE, sessionID);
27711
+ if (existsSync39(directPath))
27651
27712
  return directPath;
27652
- for (const dir of readdirSync13(MESSAGE_STORAGE)) {
27653
- const sessionPath = join47(MESSAGE_STORAGE, dir, sessionID);
27654
- if (existsSync38(sessionPath))
27713
+ for (const dir of readdirSync14(MESSAGE_STORAGE)) {
27714
+ const sessionPath = join48(MESSAGE_STORAGE, dir, sessionID);
27715
+ if (existsSync39(sessionPath))
27655
27716
  return sessionPath;
27656
27717
  }
27657
27718
  return null;
@@ -27697,7 +27758,8 @@ class BackgroundManager {
27697
27758
  progress: {
27698
27759
  toolCalls: 0,
27699
27760
  lastUpdate: new Date
27700
- }
27761
+ },
27762
+ parentModel: input.parentModel
27701
27763
  };
27702
27764
  this.tasks.set(task.id, task);
27703
27765
  this.startPolling();
@@ -27887,12 +27949,15 @@ class BackgroundManager {
27887
27949
  log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
27888
27950
  setTimeout(async () => {
27889
27951
  try {
27890
- const messageDir = getMessageDir6(task.parentSessionID);
27952
+ const messageDir = getMessageDir7(task.parentSessionID);
27891
27953
  const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
27954
+ const modelContext = task.parentModel ?? prevMessage?.model;
27955
+ const modelField = modelContext?.providerID && modelContext?.modelID ? { providerID: modelContext.providerID, modelID: modelContext.modelID } : undefined;
27892
27956
  await this.client.session.prompt({
27893
27957
  path: { id: task.parentSessionID },
27894
27958
  body: {
27895
27959
  agent: prevMessage?.agent,
27960
+ model: modelField,
27896
27961
  parts: [{ type: "text", text: message }]
27897
27962
  },
27898
27963
  query: { directory: this.directory }
@@ -28055,7 +28120,7 @@ var OverridableAgentNameSchema = exports_external.enum([
28055
28120
  "build",
28056
28121
  "plan",
28057
28122
  "Sisyphus",
28058
- "Builder-Sisyphus",
28123
+ "OpenCode-Builder",
28059
28124
  "Planner-Sisyphus",
28060
28125
  "oracle",
28061
28126
  "librarian",
@@ -28104,7 +28169,7 @@ var AgentOverridesSchema = exports_external.object({
28104
28169
  build: AgentOverrideConfigSchema.optional(),
28105
28170
  plan: AgentOverrideConfigSchema.optional(),
28106
28171
  Sisyphus: AgentOverrideConfigSchema.optional(),
28107
- "Builder-Sisyphus": AgentOverrideConfigSchema.optional(),
28172
+ "OpenCode-Builder": AgentOverrideConfigSchema.optional(),
28108
28173
  "Planner-Sisyphus": AgentOverrideConfigSchema.optional(),
28109
28174
  oracle: AgentOverrideConfigSchema.optional(),
28110
28175
  librarian: AgentOverrideConfigSchema.optional(),
@@ -28122,9 +28187,8 @@ var ClaudeCodeConfigSchema = exports_external.object({
28122
28187
  });
28123
28188
  var SisyphusAgentConfigSchema = exports_external.object({
28124
28189
  disabled: exports_external.boolean().optional(),
28125
- builder_enabled: exports_external.boolean().optional(),
28190
+ default_builder_enabled: exports_external.boolean().optional(),
28126
28191
  planner_enabled: exports_external.boolean().optional(),
28127
- replace_build: exports_external.boolean().optional(),
28128
28192
  replace_plan: exports_external.boolean().optional()
28129
28193
  });
28130
28194
  var ExperimentalConfigSchema = exports_external.object({
@@ -28217,50 +28281,6 @@ var PLAN_PERMISSION = {
28217
28281
  webfetch: "allow"
28218
28282
  };
28219
28283
 
28220
- // src/agents/build-prompt.ts
28221
- var BUILD_SYSTEM_PROMPT = `<system-reminder>
28222
- # Build Mode - System Reminder
28223
-
28224
- BUILD MODE ACTIVE - you are in EXECUTION phase. Your responsibility is to:
28225
- - Implement features and make code changes
28226
- - Execute commands and run tests
28227
- - Fix bugs and refactor code
28228
- - Deploy and build systems
28229
- - Make all necessary file modifications
28230
-
28231
- You have FULL permissions to edit files, run commands, and make system changes.
28232
- This is the implementation phase - execute decisively and thoroughly.
28233
-
28234
- ---
28235
-
28236
- ## Responsibility
28237
-
28238
- Your current responsibility is to implement, build, and execute. You should:
28239
- - Write and modify code to accomplish the user's goals
28240
- - Run tests and builds to verify your changes
28241
- - Fix errors and issues that arise
28242
- - Use all available tools to complete the task efficiently
28243
- - Delegate to specialized agents when appropriate for better results
28244
-
28245
- **NOTE:** You should ask the user for clarification when requirements are ambiguous,
28246
- but once the path is clear, execute confidently. The goal is to deliver working,
28247
- tested, production-ready solutions.
28248
-
28249
- ---
28250
-
28251
- ## Important
28252
-
28253
- The user wants you to execute and implement. You SHOULD make edits, run necessary
28254
- tools, and make changes to accomplish the task. Use your full capabilities to
28255
- deliver excellent results.
28256
- </system-reminder>
28257
- `;
28258
- var BUILD_PERMISSION = {
28259
- edit: "ask",
28260
- bash: "ask",
28261
- webfetch: "allow"
28262
- };
28263
-
28264
28284
  // src/index.ts
28265
28285
  import * as fs7 from "fs";
28266
28286
  import * as path8 from "path";
@@ -28316,7 +28336,7 @@ function migrateConfigFile(configPath, rawConfig) {
28316
28336
  }
28317
28337
  return needsWrite;
28318
28338
  }
28319
- function loadConfigFromPath2(configPath) {
28339
+ function loadConfigFromPath2(configPath, ctx) {
28320
28340
  try {
28321
28341
  if (fs7.existsSync(configPath)) {
28322
28342
  const content = fs7.readFileSync(configPath, "utf-8");
@@ -28327,6 +28347,21 @@ function loadConfigFromPath2(configPath) {
28327
28347
  const errorMsg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
28328
28348
  log(`Config validation error in ${configPath}:`, result.error.issues);
28329
28349
  addConfigLoadError({ path: configPath, error: `Validation error: ${errorMsg}` });
28350
+ const errorList = result.error.issues.map((issue2) => `\u2022 ${issue2.path.join(".")}: ${issue2.message}`).join(`
28351
+ `);
28352
+ ctx.client.tui.showToast({
28353
+ body: {
28354
+ title: "\u274C OhMyOpenCode: Config Validation Failed",
28355
+ message: `Failed to load ${configPath}
28356
+
28357
+ Validation errors:
28358
+ ${errorList}
28359
+
28360
+ Config will be ignored. Please fix the errors above.`,
28361
+ variant: "error",
28362
+ duration: 1e4
28363
+ }
28364
+ }).catch(() => {});
28330
28365
  return null;
28331
28366
  }
28332
28367
  log(`Config loaded from ${configPath}`, { agents: result.data.agents });
@@ -28336,6 +28371,21 @@ function loadConfigFromPath2(configPath) {
28336
28371
  const errorMsg = err instanceof Error ? err.message : String(err);
28337
28372
  log(`Error loading config from ${configPath}:`, err);
28338
28373
  addConfigLoadError({ path: configPath, error: errorMsg });
28374
+ const hint = err instanceof SyntaxError ? `
28375
+
28376
+ Hint: Check for syntax errors in your JSON file (missing commas, quotes, brackets, etc.)` : "";
28377
+ ctx.client.tui.showToast({
28378
+ body: {
28379
+ title: "\u274C OhMyOpenCode: Config Load Failed",
28380
+ message: `Failed to load ${configPath}
28381
+
28382
+ Error: ${errorMsg}${hint}
28383
+
28384
+ Config will be ignored. Please fix the error above.`,
28385
+ variant: "error",
28386
+ duration: 1e4
28387
+ }
28388
+ }).catch(() => {});
28339
28389
  }
28340
28390
  return null;
28341
28391
  }
@@ -28365,11 +28415,11 @@ function mergeConfigs(base, override) {
28365
28415
  claude_code: deepMerge(base.claude_code, override.claude_code)
28366
28416
  };
28367
28417
  }
28368
- function loadPluginConfig(directory) {
28418
+ function loadPluginConfig(directory, ctx) {
28369
28419
  const userConfigPath = path8.join(getUserConfigDir(), "opencode", "oh-my-opencode.json");
28370
28420
  const projectConfigPath = path8.join(directory, ".opencode", "oh-my-opencode.json");
28371
- let config3 = loadConfigFromPath2(userConfigPath) ?? {};
28372
- const projectConfig = loadConfigFromPath2(projectConfigPath);
28421
+ let config3 = loadConfigFromPath2(userConfigPath, ctx) ?? {};
28422
+ const projectConfig = loadConfigFromPath2(projectConfigPath, ctx);
28373
28423
  if (projectConfig) {
28374
28424
  config3 = mergeConfigs(config3, projectConfig);
28375
28425
  }
@@ -28383,7 +28433,7 @@ function loadPluginConfig(directory) {
28383
28433
  return config3;
28384
28434
  }
28385
28435
  var OhMyOpenCodePlugin = async (ctx) => {
28386
- const pluginConfig = loadPluginConfig(ctx.directory);
28436
+ const pluginConfig = loadPluginConfig(ctx.directory, ctx);
28387
28437
  const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
28388
28438
  const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
28389
28439
  const modelContextLimitsCache = new Map;
@@ -28477,9 +28527,8 @@ var OhMyOpenCodePlugin = async (ctx) => {
28477
28527
  const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
28478
28528
  const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
28479
28529
  const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
28480
- const builderEnabled = pluginConfig.sisyphus_agent?.builder_enabled ?? false;
28530
+ const builderEnabled = pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
28481
28531
  const plannerEnabled = pluginConfig.sisyphus_agent?.planner_enabled ?? true;
28482
- const replaceBuild = pluginConfig.sisyphus_agent?.replace_build ?? true;
28483
28532
  const replacePlan = pluginConfig.sisyphus_agent?.replace_plan ?? true;
28484
28533
  if (isSisyphusEnabled && builtinAgents.Sisyphus) {
28485
28534
  const agentConfig = {
@@ -28487,15 +28536,12 @@ var OhMyOpenCodePlugin = async (ctx) => {
28487
28536
  };
28488
28537
  if (builderEnabled) {
28489
28538
  const { name: _buildName, ...buildConfigWithoutName } = config3.agent?.build ?? {};
28490
- const builderSisyphusOverride = pluginConfig.agents?.["Builder-Sisyphus"];
28491
- const builderSisyphusBase = {
28539
+ const openCodeBuilderOverride = pluginConfig.agents?.["OpenCode-Builder"];
28540
+ const openCodeBuilderBase = {
28492
28541
  ...buildConfigWithoutName,
28493
- prompt: BUILD_SYSTEM_PROMPT,
28494
- permission: BUILD_PERMISSION,
28495
- description: `${config3.agent?.build?.description ?? "Build agent"} (OhMyOpenCode version)`,
28496
- color: config3.agent?.build?.color ?? "#32CD32"
28542
+ description: `${config3.agent?.build?.description ?? "Build agent"} (OpenCode default)`
28497
28543
  };
28498
- agentConfig["Builder-Sisyphus"] = builderSisyphusOverride ? { ...builderSisyphusBase, ...builderSisyphusOverride } : builderSisyphusBase;
28544
+ agentConfig["OpenCode-Builder"] = openCodeBuilderOverride ? { ...openCodeBuilderBase, ...openCodeBuilderOverride } : openCodeBuilderBase;
28499
28545
  }
28500
28546
  if (plannerEnabled) {
28501
28547
  const { name: _planName, ...planConfigWithoutName } = config3.agent?.plan ?? {};
@@ -28509,13 +28555,20 @@ var OhMyOpenCodePlugin = async (ctx) => {
28509
28555
  };
28510
28556
  agentConfig["Planner-Sisyphus"] = plannerSisyphusOverride ? { ...plannerSisyphusBase, ...plannerSisyphusOverride } : plannerSisyphusBase;
28511
28557
  }
28558
+ const filteredConfigAgents = config3.agent ? Object.fromEntries(Object.entries(config3.agent).filter(([key]) => {
28559
+ if (key === "build")
28560
+ return false;
28561
+ if (key === "plan" && replacePlan)
28562
+ return false;
28563
+ return true;
28564
+ })) : {};
28512
28565
  config3.agent = {
28513
28566
  ...agentConfig,
28514
28567
  ...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "Sisyphus")),
28515
28568
  ...userAgents,
28516
28569
  ...projectAgents,
28517
- ...config3.agent,
28518
- ...replaceBuild ? { build: { ...config3.agent?.build, mode: "subagent" } } : {},
28570
+ ...filteredConfigAgents,
28571
+ build: { ...config3.agent?.build, mode: "subagent" },
28519
28572
  ...replacePlan ? { plan: { ...config3.agent?.plan, mode: "subagent" } } : {}
28520
28573
  };
28521
28574
  } else {