oh-my-opencode 3.17.6 → 3.17.8

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
@@ -4446,6 +4446,56 @@ var require_picomatch2 = __commonJS((exports, module) => {
4446
4446
  module.exports = picomatch;
4447
4447
  });
4448
4448
 
4449
+ // src/agents/types.ts
4450
+ function extractModelName(model) {
4451
+ return model.includes("/") ? model.split("/").pop() ?? model : model;
4452
+ }
4453
+ function isGptModel(model) {
4454
+ const modelName = extractModelName(model).toLowerCase();
4455
+ return modelName.includes("gpt");
4456
+ }
4457
+ function isGptNativeSisyphusModel(model) {
4458
+ const modelName = extractModelName(model).toLowerCase();
4459
+ return GPT_NATIVE_SISYPHUS_RE.test(modelName);
4460
+ }
4461
+ function isGpt5_5Model(model) {
4462
+ const modelName = extractModelName(model).toLowerCase();
4463
+ return modelName.includes("gpt-5.5") || modelName.includes("gpt-5-5");
4464
+ }
4465
+ function isGpt5_3CodexModel(model) {
4466
+ const modelName = extractModelName(model).toLowerCase();
4467
+ return modelName.includes("gpt-5.3-codex") || modelName.includes("gpt-5-3-codex");
4468
+ }
4469
+ function isClaudeOpus47Model(model) {
4470
+ const modelName = extractModelName(model).toLowerCase().replaceAll(".", "-");
4471
+ return modelName.includes("claude-opus-4-7");
4472
+ }
4473
+ function isKimiK2Model(model) {
4474
+ const modelName = extractModelName(model).toLowerCase();
4475
+ if (modelName.includes("kimi"))
4476
+ return true;
4477
+ if (/k2[-.]?p[56]/.test(modelName))
4478
+ return true;
4479
+ return false;
4480
+ }
4481
+ function isGlmModel(model) {
4482
+ const modelName = extractModelName(model).toLowerCase();
4483
+ return modelName.includes("glm");
4484
+ }
4485
+ function isGeminiModel(model) {
4486
+ if (GEMINI_PROVIDERS.some((prefix) => model.startsWith(prefix)))
4487
+ return true;
4488
+ if (model.startsWith("github-copilot/") && extractModelName(model).toLowerCase().startsWith("gemini"))
4489
+ return true;
4490
+ const modelName = extractModelName(model).toLowerCase();
4491
+ return modelName.startsWith("gemini-");
4492
+ }
4493
+ var GPT_NATIVE_SISYPHUS_RE, GEMINI_PROVIDERS;
4494
+ var init_types = __esm(() => {
4495
+ GPT_NATIVE_SISYPHUS_RE = /gpt-5[.-](?:[4-9]|\d{2,})/i;
4496
+ GEMINI_PROVIDERS = ["google/", "google-vertex/"];
4497
+ });
4498
+
4449
4499
  // src/hooks/ralph-loop/constants.ts
4450
4500
  var HOOK_NAME3 = "ralph-loop", DEFAULT_STATE_FILE = ".sisyphus/ralph-loop.local.md", DEFAULT_MAX_ITERATIONS = 100, ULTRAWORK_MAX_ITERATIONS = 500, DEFAULT_COMPLETION_PROMISE = "DONE", ULTRAWORK_VERIFICATION_PROMISE = "VERIFIED";
4451
4501
  var init_constants = () => {};
@@ -8035,6 +8085,12 @@ var init_kimi_categories = __esm(() => {
8035
8085
  });
8036
8086
 
8037
8087
  // src/tools/delegate-task/openai-categories.ts
8088
+ function resolveDeepCategoryPromptAppend(model) {
8089
+ if (model && isGpt5_5Model(model)) {
8090
+ return DEEP_CATEGORY_PROMPT_APPEND_GPT_5_5;
8091
+ }
8092
+ return DEEP_CATEGORY_PROMPT_APPEND;
8093
+ }
8038
8094
  var ULTRABRAIN_CATEGORY_PROMPT_APPEND = `<Category_Context>
8039
8095
  You are working on DEEP LOGICAL REASONING / COMPLEX ARCHITECTURE tasks.
8040
8096
 
@@ -8074,6 +8130,26 @@ Genuinely independent tasks = flag and refuse, require separate delegations.
8074
8130
  Approach: explore extensively, understand deeply, then act decisively. Prefer comprehensive solutions over quick patches. If the goal is unclear, make reasonable assumptions and proceed.
8075
8131
 
8076
8132
  Minimal status updates. Focus on results, not play-by-play. Report completion with summary of changes.
8133
+ </Category_Context>`, DEEP_CATEGORY_PROMPT_APPEND_GPT_5_5 = `<Category_Context name="deep">
8134
+ You are operating in DEEP mode. This is the category reserved for goal-oriented autonomous work on hairy problems that reward thorough exploration and comprehensive solutions.
8135
+
8136
+ The orchestrator chose this category because the task benefits from depth over speed. You should feel empowered to spend the time needed: five to fifteen minutes of silent exploration before the first edit is normal and correct. Rushing to implementation on a deep task is a failure mode, not a feature.
8137
+
8138
+ # How deep mode adjusts the base behavior
8139
+
8140
+ **Exploration budget: generous.** Read the files you need, trace dependencies both directions, fire 2-5 explore/librarian sub-agents in parallel for broader questions. Build a complete mental model before the first \`apply_patch\`. Exploration here is an investment, not overhead.
8141
+
8142
+ **Goal, not plan.** You receive a GOAL describing the desired outcome. You figure out HOW to achieve it. The orchestrator deliberately did not hand you a step-by-step plan; producing one and asking for approval is not what was asked. Execute.
8143
+
8144
+ **Atomic task treatment.** When the goal contains numbered steps or phases, treat them as sub-steps of ONE task and execute them all in this turn. Splitting them across turns is wrong unless they reveal an architectural blocker that requires the user's input. If the "steps" turn out to be genuinely independent tasks that should have been separate delegations, flag that in your final message and refuse the ones beyond scope.
8145
+
8146
+ **Root cause bias.** Prefer root-cause fixes over symptom fixes. A null check around \`foo()\` is a symptom fix; fixing whatever causes \`foo()\` to return unexpected values is the root fix. Trace at least two levels up before settling on an answer. In deep mode, you have permission (and the expectation) to do the deeper fix.
8147
+
8148
+ **Ambition scaled to context.** For brand-new greenfield work, be ambitious. Choose strong defaults, avoid AI-slop aesthetics, produce something you would be proud to hand to another senior engineer. For changes in an existing codebase, be surgical and respect the existing patterns; depth does not mean invasiveness.
8149
+
8150
+ **Completion bar: full delivery.** "Simplified version", "proof of concept", and "you can extend this later" are not acceptable deliveries for a deep task. The orchestrator routed here specifically for a complete solution. If you hit a genuine blocker (missing secret, design decision only the user can make, three materially different attempts all failed), document it and return; otherwise, finish the task.
8151
+
8152
+ **Status cadence: sparse.** The user is not on the other side of this conversation; the orchestrator is, and they will synthesize your progress. Send commentary only at meaningful phase transitions (starting exploration, starting implementation, starting verification, hitting a genuine blocker). Do not narrate every tool call; silence during focused work is expected.
8077
8153
  </Category_Context>`, QUICK_CATEGORY_PROMPT_APPEND = `<Category_Context>
8078
8154
  You are working on SMALL / QUICK tasks.
8079
8155
 
@@ -8125,6 +8201,7 @@ EXPECTED OUTPUT:
8125
8201
  If your prompt lacks this structure, REWRITE IT before delegating.
8126
8202
  </Caller_Warning>`, OPENAI_CATEGORIES;
8127
8203
  var init_openai_categories = __esm(() => {
8204
+ init_types();
8128
8205
  OPENAI_CATEGORIES = [
8129
8206
  {
8130
8207
  name: "ultrabrain",
@@ -8136,7 +8213,8 @@ var init_openai_categories = __esm(() => {
8136
8213
  name: "deep",
8137
8214
  config: { model: "openai/gpt-5.5", variant: "medium" },
8138
8215
  description: "Goal-oriented autonomous problem-solving. Thorough research before action. For hairy problems requiring deep understanding.",
8139
- promptAppend: DEEP_CATEGORY_PROMPT_APPEND
8216
+ promptAppend: DEEP_CATEGORY_PROMPT_APPEND,
8217
+ resolvePromptAppend: resolveDeepCategoryPromptAppend
8140
8218
  },
8141
8219
  {
8142
8220
  name: "quick",
@@ -8151,7 +8229,7 @@ var init_openai_categories = __esm(() => {
8151
8229
  function buildCategoryRecord(selector) {
8152
8230
  return Object.fromEntries(BUILTIN_CATEGORIES.map((definition) => [definition.name, selector(definition)]));
8153
8231
  }
8154
- var BUILTIN_CATEGORIES, DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS, CATEGORY_DESCRIPTIONS;
8232
+ var BUILTIN_CATEGORIES, DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS, CATEGORY_DESCRIPTIONS, CATEGORY_PROMPT_APPEND_RESOLVERS;
8155
8233
  var init_builtin_categories = __esm(() => {
8156
8234
  init_anthropic_categories();
8157
8235
  init_google_categories();
@@ -8166,6 +8244,7 @@ var init_builtin_categories = __esm(() => {
8166
8244
  DEFAULT_CATEGORIES = buildCategoryRecord((definition) => definition.config);
8167
8245
  CATEGORY_PROMPT_APPENDS = buildCategoryRecord((definition) => definition.promptAppend);
8168
8246
  CATEGORY_DESCRIPTIONS = buildCategoryRecord((definition) => definition.description);
8247
+ CATEGORY_PROMPT_APPEND_RESOLVERS = Object.fromEntries(BUILTIN_CATEGORIES.filter((definition) => definition.resolvePromptAppend !== undefined).map((definition) => [definition.name, definition.resolvePromptAppend]));
8169
8248
  });
8170
8249
 
8171
8250
  // src/tools/delegate-task/constants.ts
@@ -15921,6 +16000,41 @@ function normalizeSDKResponse(response, fallback, options) {
15921
16000
  // src/shared/dynamic-truncator.ts
15922
16001
  var CHARS_PER_TOKEN_ESTIMATE = 4;
15923
16002
  var DEFAULT_TARGET_MAX_TOKENS = 50000;
16003
+ var usageCacheByClient = new WeakMap;
16004
+ function createModelCacheKey(modelCacheState) {
16005
+ if (!modelCacheState) {
16006
+ return "default";
16007
+ }
16008
+ const cachedLimits = modelCacheState.modelContextLimitsCache ? [...modelCacheState.modelContextLimitsCache.entries()].sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)).map(([modelKey, limit]) => `${modelKey}:${limit}`).join(",") : "";
16009
+ return `${modelCacheState.anthropicContext1MEnabled ? "1m" : "200k"}|${cachedLimits}`;
16010
+ }
16011
+ function getUsageCache(client, modelCacheState) {
16012
+ let cacheByModelState = usageCacheByClient.get(client);
16013
+ if (!cacheByModelState) {
16014
+ cacheByModelState = new Map;
16015
+ usageCacheByClient.set(client, cacheByModelState);
16016
+ }
16017
+ const modelCacheKey = createModelCacheKey(modelCacheState);
16018
+ let cache = cacheByModelState.get(modelCacheKey);
16019
+ if (!cache) {
16020
+ cache = new Map;
16021
+ cacheByModelState.set(modelCacheKey, cache);
16022
+ }
16023
+ return cache;
16024
+ }
16025
+ function invalidateContextWindowUsageCache(ctx, sessionID) {
16026
+ const cacheByModelState = usageCacheByClient.get(ctx.client);
16027
+ if (!cacheByModelState) {
16028
+ return;
16029
+ }
16030
+ for (const cache of cacheByModelState.values()) {
16031
+ if (sessionID) {
16032
+ cache.delete(sessionID);
16033
+ } else {
16034
+ cache.clear();
16035
+ }
16036
+ }
16037
+ }
15924
16038
  function estimateTokens(text) {
15925
16039
  return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE);
15926
16040
  }
@@ -15982,6 +16096,16 @@ function truncateToTokenLimit(output, maxTokens, preserveHeaderLines = 3) {
15982
16096
  };
15983
16097
  }
15984
16098
  async function getContextWindowUsage(ctx, sessionID, modelCacheState) {
16099
+ const cache = getUsageCache(ctx.client, modelCacheState);
16100
+ const cached = cache.get(sessionID);
16101
+ if (cached) {
16102
+ return cached;
16103
+ }
16104
+ const usagePromise = fetchContextWindowUsage(ctx, sessionID, modelCacheState);
16105
+ cache.set(sessionID, usagePromise);
16106
+ return usagePromise;
16107
+ }
16108
+ async function fetchContextWindowUsage(ctx, sessionID, modelCacheState) {
15985
16109
  try {
15986
16110
  const response = await ctx.client.session.messages({
15987
16111
  path: { id: sessionID }
@@ -66161,7 +66285,9 @@ var RETRYABLE_MESSAGE_PATTERNS = [
66161
66285
  "502",
66162
66286
  "504",
66163
66287
  "429",
66164
- "529"
66288
+ "529",
66289
+ "403",
66290
+ "forbidden"
66165
66291
  ];
66166
66292
  var STOP_MESSAGE_PATTERNS = [
66167
66293
  "quota will reset after",
@@ -66541,14 +66667,12 @@ async function handleSessionIdle(args) {
66541
66667
  return;
66542
66668
  }
66543
66669
  if (!todos || todos.length === 0) {
66544
- sessionStateStore.resetContinuationProgress(sessionID);
66545
66670
  sessionStateStore.resetContinuationProgress(sessionID);
66546
66671
  log(`[${HOOK_NAME}] No todos`, { sessionID });
66547
66672
  return;
66548
66673
  }
66549
66674
  const incompleteCount = getIncompleteCount(todos);
66550
66675
  if (incompleteCount === 0) {
66551
- sessionStateStore.resetContinuationProgress(sessionID);
66552
66676
  sessionStateStore.resetContinuationProgress(sessionID);
66553
66677
  log(`[${HOOK_NAME}] All todos complete`, { sessionID, total: todos.length });
66554
66678
  return;
@@ -71652,17 +71776,21 @@ function createModelFallbackStateController(input) {
71652
71776
  function setSessionFallbackChain(sessionID, fallbackChain) {
71653
71777
  if (!sessionID)
71654
71778
  return;
71655
- sessionFallbackChains.set(sessionID, fallbackChain?.length ? fallbackChain : []);
71779
+ sessionFallbackChains.set(sessionID, fallbackChain?.length ? [...fallbackChain] : []);
71656
71780
  }
71657
71781
  function clearSessionFallbackChain(sessionID) {
71658
71782
  sessionFallbackChains.delete(sessionID);
71659
71783
  }
71784
+ function getSessionFallbackChain(sessionID) {
71785
+ const fallbackChain = sessionFallbackChains.get(sessionID);
71786
+ return fallbackChain ? [...fallbackChain] : undefined;
71787
+ }
71660
71788
  function setPendingModelFallback(sessionID, agentName, currentProviderID, currentModelID) {
71661
71789
  const agentKey = getAgentConfigKey(agentName);
71662
71790
  const requirements = AGENT_MODEL_REQUIREMENTS[agentKey];
71663
71791
  const fallbackChain = sessionFallbackChains.get(sessionID) ?? requirements?.fallbackChain;
71664
71792
  if (!fallbackChain?.length) {
71665
- log("[model-fallback] No fallback chain for agent: " + agentName + " (key: " + agentKey + ")");
71793
+ log(`[model-fallback] No fallback chain for agent: ${agentName} (key: ${agentKey})`);
71666
71794
  return false;
71667
71795
  }
71668
71796
  const existing = pendingModelFallbacks.get(sessionID);
@@ -71674,21 +71802,21 @@ function createModelFallbackStateController(input) {
71674
71802
  attemptCount: 0,
71675
71803
  pending: true
71676
71804
  });
71677
- log("[model-fallback] Set pending fallback for session: " + sessionID + ", agent: " + agentName);
71805
+ log(`[model-fallback] Set pending fallback for session: ${sessionID}, agent: ${agentName}`);
71678
71806
  return true;
71679
71807
  }
71680
71808
  if (existing.pending) {
71681
- log("[model-fallback] Pending fallback already armed for session: " + sessionID);
71809
+ log(`[model-fallback] Pending fallback already armed for session: ${sessionID}`);
71682
71810
  return false;
71683
71811
  }
71684
71812
  existing.providerID = currentProviderID;
71685
71813
  existing.modelID = currentModelID;
71686
71814
  existing.pending = true;
71687
71815
  if (existing.attemptCount >= existing.fallbackChain.length) {
71688
- log("[model-fallback] Fallback chain exhausted for session: " + sessionID);
71816
+ log(`[model-fallback] Fallback chain exhausted for session: ${sessionID}`);
71689
71817
  return false;
71690
71818
  }
71691
- log("[model-fallback] Re-armed pending fallback for session: " + sessionID);
71819
+ log(`[model-fallback] Re-armed pending fallback for session: ${sessionID}`);
71692
71820
  return true;
71693
71821
  }
71694
71822
  function getNextFallback2(sessionID) {
@@ -71698,7 +71826,7 @@ function createModelFallbackStateController(input) {
71698
71826
  const fallback = getNextReachableFallback(sessionID, state3);
71699
71827
  if (fallback)
71700
71828
  return fallback;
71701
- log("[model-fallback] No more fallbacks for session: " + sessionID);
71829
+ log(`[model-fallback] No more fallbacks for session: ${sessionID}`);
71702
71830
  pendingModelFallbacks.delete(sessionID);
71703
71831
  return null;
71704
71832
  }
@@ -71720,6 +71848,7 @@ function createModelFallbackStateController(input) {
71720
71848
  return {
71721
71849
  lastToastKey,
71722
71850
  setSessionFallbackChain,
71851
+ getSessionFallbackChain,
71723
71852
  clearSessionFallbackChain,
71724
71853
  setPendingModelFallback,
71725
71854
  getNextFallback: getNextFallback2,
@@ -71761,6 +71890,7 @@ function createModelFallbackHook(args) {
71761
71890
  return {
71762
71891
  lastToastKey: controller.lastToastKey,
71763
71892
  setSessionFallbackChain: controller.setSessionFallbackChain,
71893
+ getSessionFallbackChain: controller.getSessionFallbackChain,
71764
71894
  clearSessionFallbackChain: controller.clearSessionFallbackChain,
71765
71895
  setPendingModelFallback: controller.setPendingModelFallback,
71766
71896
  getNextFallback: controller.getNextFallback,
@@ -74072,13 +74202,6 @@ function readPackageVersion(packageJsonPath) {
74072
74202
  return pkg.version ?? null;
74073
74203
  }
74074
74204
  function getCachedVersion() {
74075
- for (const candidate of INSTALLED_PACKAGE_JSON_CANDIDATES) {
74076
- try {
74077
- if (fs12.existsSync(candidate)) {
74078
- return readPackageVersion(candidate);
74079
- }
74080
- } catch {}
74081
- }
74082
74205
  try {
74083
74206
  const currentDir = path10.dirname(fileURLToPath3(import.meta.url));
74084
74207
  const pkgPath = findPackageJsonUp(currentDir);
@@ -74088,6 +74211,13 @@ function getCachedVersion() {
74088
74211
  } catch (err) {
74089
74212
  log("[auto-update-checker] Failed to resolve version from current directory:", err);
74090
74213
  }
74214
+ for (const candidate of INSTALLED_PACKAGE_JSON_CANDIDATES) {
74215
+ try {
74216
+ if (fs12.existsSync(candidate)) {
74217
+ return readPackageVersion(candidate);
74218
+ }
74219
+ } catch {}
74220
+ }
74091
74221
  try {
74092
74222
  const execDir = path10.dirname(fs12.realpathSync(process.execPath));
74093
74223
  const pkgPath = findPackageJsonUp(execDir);
@@ -75101,54 +75231,8 @@ function createAgentUsageReminderHook(_ctx) {
75101
75231
  event: eventHandler
75102
75232
  };
75103
75233
  }
75104
- // src/agents/types.ts
75105
- function extractModelName(model) {
75106
- return model.includes("/") ? model.split("/").pop() ?? model : model;
75107
- }
75108
- function isGptModel(model) {
75109
- const modelName = extractModelName(model).toLowerCase();
75110
- return modelName.includes("gpt");
75111
- }
75112
- var GPT_NATIVE_SISYPHUS_RE = /gpt-5[.-](?:[4-9]|\d{2,})/i;
75113
- function isGptNativeSisyphusModel(model) {
75114
- const modelName = extractModelName(model).toLowerCase();
75115
- return GPT_NATIVE_SISYPHUS_RE.test(modelName);
75116
- }
75117
- function isGpt5_5Model(model) {
75118
- const modelName = extractModelName(model).toLowerCase();
75119
- return modelName.includes("gpt-5.5") || modelName.includes("gpt-5-5");
75120
- }
75121
- function isGpt5_3CodexModel(model) {
75122
- const modelName = extractModelName(model).toLowerCase();
75123
- return modelName.includes("gpt-5.3-codex") || modelName.includes("gpt-5-3-codex");
75124
- }
75125
- function isClaudeOpus47Model(model) {
75126
- const modelName = extractModelName(model).toLowerCase().replaceAll(".", "-");
75127
- return modelName.includes("claude-opus-4-7");
75128
- }
75129
- function isKimiK2Model(model) {
75130
- const modelName = extractModelName(model).toLowerCase();
75131
- if (modelName.includes("kimi"))
75132
- return true;
75133
- if (/k2[-.]?p[56]/.test(modelName))
75134
- return true;
75135
- return false;
75136
- }
75137
- var GEMINI_PROVIDERS = ["google/", "google-vertex/"];
75138
- function isGlmModel(model) {
75139
- const modelName = extractModelName(model).toLowerCase();
75140
- return modelName.includes("glm");
75141
- }
75142
- function isGeminiModel(model) {
75143
- if (GEMINI_PROVIDERS.some((prefix) => model.startsWith(prefix)))
75144
- return true;
75145
- if (model.startsWith("github-copilot/") && extractModelName(model).toLowerCase().startsWith("gemini"))
75146
- return true;
75147
- const modelName = extractModelName(model).toLowerCase();
75148
- return modelName.startsWith("gemini-");
75149
- }
75150
-
75151
75234
  // src/hooks/keyword-detector/ultrawork/source-detector.ts
75235
+ init_types();
75152
75236
  function isPlannerAgent(agentName) {
75153
75237
  if (!agentName)
75154
75238
  return false;
@@ -78005,6 +78089,7 @@ function createRalphLoopHook(ctx, options) {
78005
78089
  };
78006
78090
  }
78007
78091
  // src/hooks/no-sisyphus-gpt/hook.ts
78092
+ init_types();
78008
78093
  init_agent_display_names();
78009
78094
  var TOAST_TITLE = "NEVER Use Sisyphus with GPT";
78010
78095
  var TOAST_MESSAGE = [
@@ -78060,6 +78145,7 @@ function createNoSisyphusGptHook(ctx) {
78060
78145
  };
78061
78146
  }
78062
78147
  // src/hooks/no-hephaestus-non-gpt/hook.ts
78148
+ init_types();
78063
78149
  init_agent_display_names();
78064
78150
  var TOAST_TITLE2 = "NEVER Use Hephaestus with Non-GPT";
78065
78151
  var TOAST_MESSAGE2 = [
@@ -87912,7 +87998,10 @@ function findMessageByID(messages, messageID) {
87912
87998
  return messages.find((message) => message.info?.id === messageID);
87913
87999
  }
87914
88000
  async function resolveNoTextTailFromSession(args) {
87915
- const { client, sessionID, messageID, directory } = args;
88001
+ const { client, sessionID, messageID, directory, parts } = args;
88002
+ if (Array.isArray(parts)) {
88003
+ return isStepOnlyNoTextParts(parts);
88004
+ }
87916
88005
  try {
87917
88006
  const response = await client.session.messages({
87918
88007
  path: { id: sessionID },
@@ -88039,7 +88128,8 @@ function createPostCompactionDegradationMonitor(args) {
88039
88128
  client,
88040
88129
  sessionID: info.sessionID,
88041
88130
  messageID: info.id,
88042
- directory
88131
+ directory,
88132
+ parts: info.parts
88043
88133
  });
88044
88134
  if (!isNoTextTail) {
88045
88135
  postCompactionNoTextStreak.set(info.sessionID, 0);
@@ -88203,7 +88293,8 @@ function createPreemptiveCompactionHook(ctx, pluginConfig, modelCacheState) {
88203
88293
  compactedSessions.delete(info.sessionID);
88204
88294
  await postCompactionMonitor.onAssistantMessageUpdated({
88205
88295
  sessionID: info.sessionID,
88206
- id: info.id
88296
+ id: info.id,
88297
+ parts: info.parts
88207
88298
  });
88208
88299
  }
88209
88300
  };
@@ -88800,7 +88891,7 @@ function classifyErrorType(error) {
88800
88891
  if (errorName?.includes("providermodelnotfounderror") || errorName?.includes("modelnotfounderror") || errorName?.includes("unknownerror") && /model\s+not\s+found/i.test(message)) {
88801
88892
  return "model_not_found";
88802
88893
  }
88803
- if (errorName?.includes("quotaexceeded") || errorName?.includes("insufficientquota") || errorName?.includes("billingerror") || /quota.?exceeded/i.test(message) || /subscription.*quota/i.test(message) || /insufficient.?quota/i.test(message) || /billing.?(?:hard.?)?limit/i.test(message) || /exhausted\s+your\s+capacity/i.test(message) || /out\s+of\s+credits?/i.test(message) || /payment.?required/i.test(message) || /usage\s+limit/i.test(message)) {
88894
+ if (errorName?.includes("quotaexceeded") || errorName?.includes("insufficientquota") || errorName?.includes("billingerror") || /quota.?exceeded/i.test(message) || /subscription.*quota/i.test(message) || /insufficient.?(?:quota|balance|funds?)/i.test(message) || /billing.?(?:hard.?)?limit/i.test(message) || /exhausted\s+your\s+capacity/i.test(message) || /out\s+of\s+credits?/i.test(message) || /payment.?required/i.test(message) || /usage\s+limit/i.test(message)) {
88804
88895
  return "quota_exceeded";
88805
88896
  }
88806
88897
  return;
@@ -88828,8 +88919,7 @@ function isRetryableError(error, retryOnErrors) {
88828
88919
  return true;
88829
88920
  }
88830
88921
  if (errorType === "quota_exceeded") {
88831
- const hasAutoRetrySignal = /retrying\s+in/i.test(message);
88832
- return hasAutoRetrySignal;
88922
+ return true;
88833
88923
  }
88834
88924
  if (statusCode && retryOnErrors.includes(statusCode)) {
88835
88925
  return true;
@@ -89918,6 +90008,19 @@ function extractFilePath(metadata) {
89918
90008
  }
89919
90009
  return;
89920
90010
  }
90011
+ function extractLineCount(metadata) {
90012
+ if (!metadata || typeof metadata !== "object") {
90013
+ return;
90014
+ }
90015
+ const objectMeta = metadata;
90016
+ const candidates = [objectMeta.lineCount, objectMeta.linesWritten, objectMeta.lines];
90017
+ for (const candidate of candidates) {
90018
+ if (typeof candidate === "number" && Number.isInteger(candidate) && candidate >= 0) {
90019
+ return candidate;
90020
+ }
90021
+ }
90022
+ return;
90023
+ }
89921
90024
  async function appendWriteHashlineOutput(output) {
89922
90025
  if (output.output.startsWith(WRITE_SUCCESS_MARKER)) {
89923
90026
  return;
@@ -89926,6 +90029,11 @@ async function appendWriteHashlineOutput(output) {
89926
90029
  if (outputLower.startsWith("error") || outputLower.includes("failed")) {
89927
90030
  return;
89928
90031
  }
90032
+ const metadataLineCount = extractLineCount(output.metadata);
90033
+ if (metadataLineCount !== undefined) {
90034
+ output.output = `${WRITE_SUCCESS_MARKER} ${metadataLineCount} lines written.`;
90035
+ return;
90036
+ }
89929
90037
  const filePath = extractFilePath(output.metadata);
89930
90038
  if (!filePath) {
89931
90039
  return;
@@ -96281,6 +96389,83 @@ async function formatFullSession(task, client2, options) {
96281
96389
  `);
96282
96390
  }
96283
96391
 
96392
+ // src/features/background-agent/error-classifier.ts
96393
+ function isRecord15(value) {
96394
+ return typeof value === "object" && value !== null;
96395
+ }
96396
+ function isAbortedSessionError(error) {
96397
+ const message = getErrorText(error);
96398
+ return message.toLowerCase().includes("aborted");
96399
+ }
96400
+ function getErrorText(error) {
96401
+ if (!error)
96402
+ return "";
96403
+ if (typeof error === "string")
96404
+ return error;
96405
+ if (error instanceof Error) {
96406
+ return `${error.name}: ${error.message}`;
96407
+ }
96408
+ if (typeof error === "object" && error !== null) {
96409
+ if ("message" in error && typeof error.message === "string") {
96410
+ return error.message;
96411
+ }
96412
+ if ("name" in error && typeof error.name === "string") {
96413
+ return error.name;
96414
+ }
96415
+ }
96416
+ return "";
96417
+ }
96418
+ function extractErrorName2(error) {
96419
+ if (isRecord15(error) && typeof error["name"] === "string")
96420
+ return error["name"];
96421
+ if (error instanceof Error)
96422
+ return error.name;
96423
+ return;
96424
+ }
96425
+ function extractErrorMessage(error) {
96426
+ if (!error)
96427
+ return;
96428
+ if (typeof error === "string")
96429
+ return error;
96430
+ if (isRecord15(error)) {
96431
+ const dataRaw = error["data"];
96432
+ const candidates = [
96433
+ dataRaw,
96434
+ isRecord15(dataRaw) ? dataRaw["error"] : undefined,
96435
+ error["error"],
96436
+ error["cause"],
96437
+ error
96438
+ ];
96439
+ for (const candidate of candidates) {
96440
+ if (typeof candidate === "string" && candidate.length > 0)
96441
+ return candidate;
96442
+ if (isRecord15(candidate) && typeof candidate["message"] === "string" && candidate["message"].length > 0) {
96443
+ return candidate["message"];
96444
+ }
96445
+ }
96446
+ }
96447
+ if (error instanceof Error)
96448
+ return error.message;
96449
+ try {
96450
+ return JSON.stringify(error);
96451
+ } catch {
96452
+ return String(error);
96453
+ }
96454
+ }
96455
+ function getSessionErrorMessage(properties) {
96456
+ const errorRaw = properties["error"];
96457
+ if (!isRecord15(errorRaw))
96458
+ return;
96459
+ const dataRaw = errorRaw["data"];
96460
+ if (isRecord15(dataRaw)) {
96461
+ const message2 = dataRaw["message"];
96462
+ if (typeof message2 === "string")
96463
+ return message2;
96464
+ }
96465
+ const message = errorRaw["message"];
96466
+ return typeof message === "string" ? message : undefined;
96467
+ }
96468
+
96284
96469
  // src/tools/background-task/task-result-format.ts
96285
96470
  function getTimeString(value) {
96286
96471
  return typeof value === "string" ? value : "";
@@ -96327,6 +96512,19 @@ Session ID: ${task.sessionID}
96327
96512
  const timeB = getTimeString(b.info?.time);
96328
96513
  return timeA.localeCompare(timeB);
96329
96514
  });
96515
+ const sessionError = sortedMessages.filter((message) => message.info?.role === "assistant" && message.info?.error).map((message) => extractErrorMessage(message.info?.error)).find((message) => typeof message === "string" && message.length > 0);
96516
+ if (sessionError) {
96517
+ return `Task Result
96518
+
96519
+ Task ID: ${task.id}
96520
+ Description: ${task.description}
96521
+ Duration: ${formatDuration(task.startedAt ?? new Date, task.completedAt)}
96522
+ Session ID: ${task.sessionID}
96523
+
96524
+ ---
96525
+
96526
+ Session error: ${sessionError}`;
96527
+ }
96330
96528
  const newMessages = consumeNewMessages(task.sessionID, sortedMessages);
96331
96529
  if (newMessages.length === 0) {
96332
96530
  const duration2 = formatDuration(task.startedAt ?? new Date, task.completedAt);
@@ -98254,6 +98452,18 @@ async function fetchSessionMessages(client2, sessionID) {
98254
98452
  const rawData = messagesResult?.data ?? messagesResult;
98255
98453
  return Array.isArray(rawData) ? rawData : [];
98256
98454
  }
98455
+ function getTerminalSessionError(messages) {
98456
+ const lastAssistant = [...messages].reverse().find((msg) => msg.info?.role === "assistant");
98457
+ const lastUser = [...messages].reverse().find((msg) => msg.info?.role === "user");
98458
+ if (lastUser?.info?.id && lastAssistant?.info?.id && lastAssistant.info.id <= lastUser.info.id) {
98459
+ return null;
98460
+ }
98461
+ if (!lastAssistant?.info || !("error" in lastAssistant.info)) {
98462
+ return null;
98463
+ }
98464
+ const errorMessage = extractErrorMessage(lastAssistant.info.error);
98465
+ return errorMessage && errorMessage.length > 0 ? errorMessage : "Session error";
98466
+ }
98257
98467
  function isSessionComplete(messages) {
98258
98468
  let lastUser;
98259
98469
  let lastAssistant;
@@ -98342,6 +98552,11 @@ Session ID: ${input.sessionID}`;
98342
98552
  if (input.anchorMessageCount !== undefined && messages.length <= input.anchorMessageCount) {
98343
98553
  continue;
98344
98554
  }
98555
+ const sessionError = getTerminalSessionError(messages);
98556
+ if (sessionError) {
98557
+ log("[task] Poll detected terminal session error", { sessionID: input.sessionID, sessionError });
98558
+ return sessionError;
98559
+ }
98345
98560
  if (isSessionComplete(messages)) {
98346
98561
  log("[task] Poll complete - terminal finish detected", { sessionID: input.sessionID, pollCount });
98347
98562
  break;
@@ -99110,7 +99325,8 @@ async function retrySyncPromptWithFallbacks(input) {
99110
99325
  if (!categoryModel || !fallbackChain || fallbackChain.length === 0) {
99111
99326
  return {
99112
99327
  promptError: initialError,
99113
- categoryModel
99328
+ categoryModel,
99329
+ fallbackState: undefined
99114
99330
  };
99115
99331
  }
99116
99332
  const fallbackState = {
@@ -99126,7 +99342,8 @@ async function retrySyncPromptWithFallbacks(input) {
99126
99342
  if (!nextFallback) {
99127
99343
  return {
99128
99344
  promptError: finalError,
99129
- categoryModel
99345
+ categoryModel,
99346
+ fallbackState
99130
99347
  };
99131
99348
  }
99132
99349
  const fallbackModel = toDelegatedModelConfig(nextFallback);
@@ -99134,7 +99351,8 @@ async function retrySyncPromptWithFallbacks(input) {
99134
99351
  if (!promptError) {
99135
99352
  return {
99136
99353
  promptError: null,
99137
- categoryModel: fallbackModel
99354
+ categoryModel: fallbackModel,
99355
+ fallbackState
99138
99356
  };
99139
99357
  }
99140
99358
  finalError = promptError;
@@ -99143,6 +99361,12 @@ async function retrySyncPromptWithFallbacks(input) {
99143
99361
  fallbackState.pending = true;
99144
99362
  }
99145
99363
  }
99364
+ function getNextSyncFallbackModel(sessionID, fallbackState) {
99365
+ if (!fallbackState)
99366
+ return null;
99367
+ const nextFallback = getNextReachableFallback(sessionID, fallbackState);
99368
+ return nextFallback ? toDelegatedModelConfig(nextFallback) : null;
99369
+ }
99146
99370
 
99147
99371
  // src/tools/delegate-task/sync-task.ts
99148
99372
  async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse, categoryModel, systemContent, modelInfo, fallbackChain, deps = syncTaskDeps) {
@@ -99181,26 +99405,50 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
99181
99405
  const sessionID = createSessionResult.sessionID;
99182
99406
  spawnReservation?.commit();
99183
99407
  syncSessionID = sessionID;
99184
- subagentSessions.add(sessionID);
99185
- syncSubagentSessions.add(sessionID);
99186
- setSessionAgent(sessionID, agentToUse);
99187
- executorCtx.modelFallbackControllerAccessor?.setSessionFallbackChain(sessionID, fallbackChain);
99188
- if (args.category) {
99189
- SessionCategoryRegistry.register(sessionID, args.category);
99190
- }
99191
- if (onSyncSessionCreated) {
99192
- log("[task] Invoking onSyncSessionCreated callback", { sessionID, parentID: parentContext.sessionID });
99193
- try {
99194
- await onSyncSessionCreated({
99195
- sessionID,
99196
- parentID: parentContext.sessionID,
99197
- title: args.description
99198
- });
99199
- } catch (error) {
99200
- log("[task] onSyncSessionCreated callback failed", { error: String(error) });
99408
+ const registerSyncSession = async (newSessionID) => {
99409
+ syncSessionID = newSessionID;
99410
+ subagentSessions.add(newSessionID);
99411
+ syncSubagentSessions.add(newSessionID);
99412
+ setSessionAgent(newSessionID, agentToUse);
99413
+ executorCtx.modelFallbackControllerAccessor?.setSessionFallbackChain(newSessionID, fallbackChain);
99414
+ if (args.category) {
99415
+ SessionCategoryRegistry.register(newSessionID, args.category);
99416
+ }
99417
+ if (onSyncSessionCreated) {
99418
+ log("[task] Invoking onSyncSessionCreated callback", { sessionID: newSessionID, parentID: parentContext.sessionID });
99419
+ try {
99420
+ await onSyncSessionCreated({
99421
+ sessionID: newSessionID,
99422
+ parentID: parentContext.sessionID,
99423
+ title: args.description
99424
+ });
99425
+ } catch (error) {
99426
+ log("[task] onSyncSessionCreated callback failed", { error: String(error) });
99427
+ }
99428
+ await new Promise((r) => setTimeout(r, 200));
99201
99429
  }
99202
- await new Promise((r) => setTimeout(r, 200));
99203
- }
99430
+ };
99431
+ const publishSyncMetadata = async (currentSessionID, currentModel, currentTaskId, spawnDepth) => {
99432
+ await publishToolMetadata(ctx, {
99433
+ title: args.description,
99434
+ metadata: {
99435
+ prompt: args.prompt,
99436
+ agent: agentToUse,
99437
+ category: args.category,
99438
+ ...args.requested_subagent_type !== undefined ? { requested_subagent_type: args.requested_subagent_type } : {},
99439
+ load_skills: args.load_skills,
99440
+ description: args.description,
99441
+ run_in_background: args.run_in_background,
99442
+ taskId: currentSessionID,
99443
+ sessionId: currentSessionID,
99444
+ sync: true,
99445
+ spawnDepth,
99446
+ command: args.command,
99447
+ model: resolveMetadataModel(currentModel, parentContext.model)
99448
+ }
99449
+ });
99450
+ };
99451
+ await registerSyncSession(sessionID);
99204
99452
  taskId = `sync_${sessionID.slice(0, 8)}`;
99205
99453
  const startTime = new Date;
99206
99454
  if (toastManager) {
@@ -99215,25 +99463,7 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
99215
99463
  modelInfo
99216
99464
  });
99217
99465
  }
99218
- const syncTaskMeta = {
99219
- title: args.description,
99220
- metadata: {
99221
- prompt: args.prompt,
99222
- agent: agentToUse,
99223
- category: args.category,
99224
- ...args.requested_subagent_type !== undefined ? { requested_subagent_type: args.requested_subagent_type } : {},
99225
- load_skills: args.load_skills,
99226
- description: args.description,
99227
- run_in_background: args.run_in_background,
99228
- taskId: sessionID,
99229
- sessionId: sessionID,
99230
- sync: true,
99231
- spawnDepth: spawnContext.childDepth,
99232
- command: args.command,
99233
- model: resolveMetadataModel(categoryModel, parentContext.model)
99234
- }
99235
- };
99236
- await publishToolMetadata(ctx, syncTaskMeta);
99466
+ await publishSyncMetadata(sessionID, categoryModel, taskId, spawnContext.childDepth);
99237
99467
  const syncPromptInput = {
99238
99468
  sessionID,
99239
99469
  agentToUse,
@@ -99244,55 +99474,106 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
99244
99474
  sisyphusAgentConfig: executorCtx.sisyphusAgentConfig
99245
99475
  };
99246
99476
  let effectiveCategoryModel = categoryModel;
99247
- let promptError = await deps.sendSyncPrompt(client2, {
99248
- ...syncPromptInput,
99249
- categoryModel: effectiveCategoryModel
99250
- });
99251
- if (promptError) {
99252
- const promptResult = await retrySyncPromptWithFallbacks({
99253
- sessionID,
99254
- initialError: promptError,
99255
- categoryModel: effectiveCategoryModel,
99256
- fallbackChain,
99257
- sendPrompt: async (fallbackModel) => {
99258
- return deps.sendSyncPrompt(client2, {
99259
- ...syncPromptInput,
99260
- categoryModel: fallbackModel
99477
+ let fallbackState = effectiveCategoryModel && fallbackChain?.length ? {
99478
+ providerID: effectiveCategoryModel.providerID,
99479
+ modelID: effectiveCategoryModel.modelID,
99480
+ fallbackChain,
99481
+ attemptCount: 0,
99482
+ pending: true
99483
+ } : undefined;
99484
+ let activeSessionID = sessionID;
99485
+ const cleanupRetrySession = (currentSessionID) => {
99486
+ subagentSessions.delete(currentSessionID);
99487
+ syncSubagentSessions.delete(currentSessionID);
99488
+ executorCtx.modelFallbackControllerAccessor?.clearSessionFallbackChain(currentSessionID);
99489
+ SessionCategoryRegistry.remove(currentSessionID);
99490
+ };
99491
+ try {
99492
+ while (true) {
99493
+ let promptError = await deps.sendSyncPrompt(client2, {
99494
+ ...syncPromptInput,
99495
+ sessionID: activeSessionID,
99496
+ categoryModel: effectiveCategoryModel
99497
+ });
99498
+ if (promptError) {
99499
+ const promptResult = await retrySyncPromptWithFallbacks({
99500
+ sessionID: activeSessionID,
99501
+ initialError: promptError,
99502
+ categoryModel: effectiveCategoryModel,
99503
+ fallbackChain,
99504
+ sendPrompt: async (fallbackModel) => {
99505
+ return deps.sendSyncPrompt(client2, {
99506
+ ...syncPromptInput,
99507
+ sessionID: activeSessionID,
99508
+ categoryModel: fallbackModel
99509
+ });
99510
+ }
99261
99511
  });
99512
+ promptError = promptResult.promptError;
99513
+ effectiveCategoryModel = promptResult.categoryModel;
99514
+ fallbackState = promptResult.fallbackState ?? fallbackState;
99515
+ if (promptError) {
99516
+ return promptError;
99517
+ }
99262
99518
  }
99263
- });
99264
- promptError = promptResult.promptError;
99265
- effectiveCategoryModel = promptResult.categoryModel;
99266
- if (promptError) {
99267
- return promptError;
99268
- }
99269
- }
99270
- try {
99271
- const pollError = await deps.pollSyncSession(ctx, client2, {
99272
- sessionID,
99273
- agentToUse,
99274
- toastManager,
99275
- taskId
99276
- }, syncPollTimeoutMs);
99277
- if (pollError) {
99278
- return pollError;
99279
- }
99280
- const result = await deps.fetchSyncResult(client2, sessionID);
99281
- if (!result.ok) {
99282
- return result.error;
99283
- }
99284
- const duration = formatDuration2(startTime);
99285
- const actualModelStr = effectiveCategoryModel ? `${effectiveCategoryModel.providerID}/${effectiveCategoryModel.modelID}` : undefined;
99286
- const parentModelStr = parentContext.model ? `${parentContext.model.providerID}/${parentContext.model.modelID}` : undefined;
99287
- let modelRoutingNote = "";
99288
- if (actualModelStr && parentModelStr && actualModelStr !== parentModelStr) {
99289
- modelRoutingNote = `
99519
+ const pollError = await deps.pollSyncSession(ctx, client2, {
99520
+ sessionID: activeSessionID,
99521
+ agentToUse,
99522
+ toastManager,
99523
+ taskId
99524
+ }, syncPollTimeoutMs);
99525
+ if (pollError) {
99526
+ const nextFallbackModel = shouldRetryError({ message: pollError }) ? getNextSyncFallbackModel(activeSessionID, fallbackState) : null;
99527
+ if (!nextFallbackModel) {
99528
+ return pollError;
99529
+ }
99530
+ cleanupRetrySession(activeSessionID);
99531
+ const retrySessionResult = await deps.createSyncSession(client2, {
99532
+ parentSessionID: parentContext.sessionID,
99533
+ agentToUse,
99534
+ description: args.description,
99535
+ defaultDirectory: directory
99536
+ });
99537
+ if (!retrySessionResult.ok) {
99538
+ return retrySessionResult.error;
99539
+ }
99540
+ activeSessionID = retrySessionResult.sessionID;
99541
+ effectiveCategoryModel = nextFallbackModel;
99542
+ await registerSyncSession(activeSessionID);
99543
+ if (toastManager && taskId) {
99544
+ toastManager.addTask({
99545
+ id: taskId,
99546
+ sessionID: activeSessionID,
99547
+ description: args.description,
99548
+ agent: agentToUse,
99549
+ isBackground: false,
99550
+ category: args.category,
99551
+ skills: args.load_skills,
99552
+ modelInfo
99553
+ });
99554
+ }
99555
+ if (taskId) {
99556
+ await publishSyncMetadata(activeSessionID, effectiveCategoryModel, taskId, spawnContext.childDepth);
99557
+ }
99558
+ continue;
99559
+ }
99560
+ const result = await deps.fetchSyncResult(client2, activeSessionID);
99561
+ if (!result.ok) {
99562
+ return result.error;
99563
+ }
99564
+ const duration = formatDuration2(startTime);
99565
+ const actualModelStr = effectiveCategoryModel ? `${effectiveCategoryModel.providerID}/${effectiveCategoryModel.modelID}` : undefined;
99566
+ const parentModelStr = parentContext.model ? `${parentContext.model.providerID}/${parentContext.model.modelID}` : undefined;
99567
+ let modelRoutingNote = "";
99568
+ if (actualModelStr && parentModelStr && actualModelStr !== parentModelStr) {
99569
+ modelRoutingNote = `
99290
99570
  \u26A0\uFE0F Model routing: parent used ${parentModelStr}, this subagent used ${actualModelStr} (via category: ${args.category ?? "unknown"})`;
99291
- } else if (actualModelStr) {
99292
- modelRoutingNote = `
99571
+ } else if (actualModelStr) {
99572
+ modelRoutingNote = `
99293
99573
  Model: ${actualModelStr}${args.category ? ` (category: ${args.category})` : ""}`;
99294
- }
99295
- return `Task completed in ${duration}.
99574
+ }
99575
+ await publishSyncMetadata(activeSessionID, effectiveCategoryModel, taskId, spawnContext.childDepth);
99576
+ return `Task completed in ${duration}.
99296
99577
 
99297
99578
  Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}${modelRoutingNote}
99298
99579
 
@@ -99301,11 +99582,12 @@ Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}${mod
99301
99582
  ${result.textContent || "(No text output)"}
99302
99583
 
99303
99584
  ${buildTaskMetadataBlock({
99304
- sessionId: sessionID,
99305
- taskId: sessionID,
99306
- agent: agentToUse,
99307
- category: args.category
99308
- })}`;
99585
+ sessionId: activeSessionID,
99586
+ taskId: activeSessionID,
99587
+ agent: agentToUse,
99588
+ category: args.category
99589
+ })}`;
99590
+ }
99309
99591
  } finally {
99310
99592
  if (toastManager && taskId !== undefined) {
99311
99593
  toastManager.removeTask(taskId);
@@ -99384,6 +99666,7 @@ function resolveCategoryConfig(categoryName, options) {
99384
99666
  }
99385
99667
 
99386
99668
  // src/tools/delegate-task/category-resolver.ts
99669
+ init_constants2();
99387
99670
  init_plugin_identity();
99388
99671
 
99389
99672
  // src/tools/delegate-task/available-models.ts
@@ -99608,6 +99891,19 @@ function applyCategoryParams(base, config2) {
99608
99891
  result.thinking = config2.thinking;
99609
99892
  return result;
99610
99893
  }
99894
+ function resolveCategoryPromptAppendForModel(categoryName, actualModel, staticPromptAppend, userPromptAppend) {
99895
+ const dynamicResolver = CATEGORY_PROMPT_APPEND_RESOLVERS[categoryName];
99896
+ if (!dynamicResolver) {
99897
+ return staticPromptAppend || undefined;
99898
+ }
99899
+ const dynamicBase = dynamicResolver(actualModel);
99900
+ if (!userPromptAppend) {
99901
+ return dynamicBase || undefined;
99902
+ }
99903
+ return dynamicBase ? `${dynamicBase}
99904
+
99905
+ ${userPromptAppend}` : userPromptAppend;
99906
+ }
99611
99907
  async function resolveCategoryExecution(args, executorCtx, inheritedModel, systemDefaultModel) {
99612
99908
  const { client: client2, userCategories, sisyphusJuniorModel } = executorCtx;
99613
99909
  const categoryName = args.category;
@@ -99737,7 +100033,7 @@ Available categories: ${allCategoryNames}`
99737
100033
  const parsedModel = parseModelString(actualModel);
99738
100034
  categoryModel = parsedModel ?? undefined;
99739
100035
  }
99740
- const categoryPromptAppend = resolved.promptAppend || undefined;
100036
+ const categoryPromptAppend = resolveCategoryPromptAppendForModel(args.category, actualModel, resolved.promptAppend, userCategories?.[args.category]?.prompt_append);
99741
100037
  if (!categoryModel && !actualModel && !isModelResolutionSkipped) {
99742
100038
  const categoryNames = Object.keys(enabledCategories);
99743
100039
  return {
@@ -103445,6 +103741,43 @@ function formatDuration3(start, end) {
103445
103741
  }
103446
103742
 
103447
103743
  // src/features/background-agent/background-task-notification-template.ts
103744
+ function formatAttemptModel(attempt) {
103745
+ if (attempt.providerID && attempt.modelID) {
103746
+ return `${attempt.providerID}/${attempt.modelID}`;
103747
+ }
103748
+ if (attempt.modelID) {
103749
+ return attempt.modelID;
103750
+ }
103751
+ if (attempt.providerID) {
103752
+ return attempt.providerID;
103753
+ }
103754
+ return "unknown-model";
103755
+ }
103756
+ function formatAttemptTimeline(task) {
103757
+ if (!task.attempts || task.attempts.length <= 1) {
103758
+ return "";
103759
+ }
103760
+ const lines = task.attempts.map((attempt) => {
103761
+ const attemptLines = [
103762
+ ` - Attempt ${attempt.attemptNumber} \u2014 ${attempt.status.toUpperCase()} \u2014 ${formatAttemptModel(attempt)} \u2014 ${attempt.sessionID ?? "unknown"}`
103763
+ ];
103764
+ if (attempt.status !== "completed" && attempt.error) {
103765
+ attemptLines.push(` Error: ${attempt.error}`);
103766
+ }
103767
+ return attemptLines.join(`
103768
+ `);
103769
+ }).join(`
103770
+ `);
103771
+ return `Background task attempts:
103772
+ ${lines}`;
103773
+ }
103774
+ function formatTaskSummaryLine(task) {
103775
+ const baseLine = `- \`${task.id}\`: ${task.description || task.id}`;
103776
+ const statusSuffix = task.status === "completed" ? "" : ` [${task.status.toUpperCase()}]${task.error ? ` - ${task.error}` : ""}`;
103777
+ const timeline = formatAttemptTimeline(task);
103778
+ return `${baseLine}${statusSuffix}${timeline ? `
103779
+ ${timeline}` : ""}`;
103780
+ }
103448
103781
  function buildBackgroundTaskNotificationText(input) {
103449
103782
  const { task, duration, statusText, allComplete, remainingCount, completedTasks } = input;
103450
103783
  const safeDescription = (t) => t.description || t.id;
@@ -103453,9 +103786,9 @@ function buildBackgroundTaskNotificationText(input) {
103453
103786
  if (allComplete) {
103454
103787
  const succeededTasks = completedTasks.filter((t) => t.status === "completed");
103455
103788
  const failedTasks = completedTasks.filter((t) => t.status !== "completed");
103456
- const succeededText = succeededTasks.length > 0 ? succeededTasks.map((t) => `- \`${t.id}\`: ${safeDescription(t)}`).join(`
103789
+ const succeededText = succeededTasks.length > 0 ? succeededTasks.map((t) => formatTaskSummaryLine(t)).join(`
103457
103790
  `) : "";
103458
- const failedText = failedTasks.length > 0 ? failedTasks.map((t) => `- \`${t.id}\`: ${safeDescription(t)} [${t.status.toUpperCase()}]${t.error ? ` - ${t.error}` : ""}`).join(`
103791
+ const failedText = failedTasks.length > 0 ? failedTasks.map((t) => formatTaskSummaryLine(t)).join(`
103459
103792
  `) : "";
103460
103793
  const hasFailures = failedTasks.length > 0;
103461
103794
  const header = hasFailures ? `[ALL BACKGROUND TASKS FINISHED - ${failedTasks.length} FAILED]` : "[ALL BACKGROUND TASKS COMPLETE]";
@@ -103472,7 +103805,7 @@ ${failedText}
103472
103805
  `;
103473
103806
  }
103474
103807
  if (!body) {
103475
- body = `- \`${task.id}\`: ${safeDescription(task)} [${task.status.toUpperCase()}]${task.error ? ` - ${task.error}` : ""}
103808
+ body = `${formatTaskSummaryLine(task)}
103476
103809
  `;
103477
103810
  }
103478
103811
  return `<system-reminder>
@@ -103499,83 +103832,6 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
103499
103832
  </system-reminder>`;
103500
103833
  }
103501
103834
 
103502
- // src/features/background-agent/error-classifier.ts
103503
- function isRecord15(value) {
103504
- return typeof value === "object" && value !== null;
103505
- }
103506
- function isAbortedSessionError(error) {
103507
- const message = getErrorText(error);
103508
- return message.toLowerCase().includes("aborted");
103509
- }
103510
- function getErrorText(error) {
103511
- if (!error)
103512
- return "";
103513
- if (typeof error === "string")
103514
- return error;
103515
- if (error instanceof Error) {
103516
- return `${error.name}: ${error.message}`;
103517
- }
103518
- if (typeof error === "object" && error !== null) {
103519
- if ("message" in error && typeof error.message === "string") {
103520
- return error.message;
103521
- }
103522
- if ("name" in error && typeof error.name === "string") {
103523
- return error.name;
103524
- }
103525
- }
103526
- return "";
103527
- }
103528
- function extractErrorName2(error) {
103529
- if (isRecord15(error) && typeof error["name"] === "string")
103530
- return error["name"];
103531
- if (error instanceof Error)
103532
- return error.name;
103533
- return;
103534
- }
103535
- function extractErrorMessage(error) {
103536
- if (!error)
103537
- return;
103538
- if (typeof error === "string")
103539
- return error;
103540
- if (error instanceof Error)
103541
- return error.message;
103542
- if (isRecord15(error)) {
103543
- const dataRaw = error["data"];
103544
- const candidates = [
103545
- error,
103546
- dataRaw,
103547
- error["error"],
103548
- isRecord15(dataRaw) ? dataRaw["error"] : undefined,
103549
- error["cause"]
103550
- ];
103551
- for (const candidate of candidates) {
103552
- if (typeof candidate === "string" && candidate.length > 0)
103553
- return candidate;
103554
- if (isRecord15(candidate) && typeof candidate["message"] === "string" && candidate["message"].length > 0) {
103555
- return candidate["message"];
103556
- }
103557
- }
103558
- }
103559
- try {
103560
- return JSON.stringify(error);
103561
- } catch {
103562
- return String(error);
103563
- }
103564
- }
103565
- function getSessionErrorMessage(properties) {
103566
- const errorRaw = properties["error"];
103567
- if (!isRecord15(errorRaw))
103568
- return;
103569
- const dataRaw = errorRaw["data"];
103570
- if (isRecord15(dataRaw)) {
103571
- const message2 = dataRaw["message"];
103572
- if (typeof message2 === "string")
103573
- return message2;
103574
- }
103575
- const message = errorRaw["message"];
103576
- return typeof message === "string" ? message : undefined;
103577
- }
103578
-
103579
103835
  // src/features/background-agent/abort-with-timeout.ts
103580
103836
  async function abortWithTimeout(client2, sessionID, timeoutMs = 1e4) {
103581
103837
  let timeoutHandle;
@@ -103603,9 +103859,138 @@ async function abortWithTimeout(client2, sessionID, timeoutMs = 1e4) {
103603
103859
  }
103604
103860
  }
103605
103861
 
103862
+ // src/features/background-agent/attempt-lifecycle.ts
103863
+ function toAttemptModel(model) {
103864
+ return {
103865
+ providerID: model?.providerID,
103866
+ modelID: model?.modelID,
103867
+ variant: model?.variant
103868
+ };
103869
+ }
103870
+ function toTaskModel(attempt) {
103871
+ if (!attempt.providerID || !attempt.modelID) {
103872
+ return;
103873
+ }
103874
+ return {
103875
+ providerID: attempt.providerID,
103876
+ modelID: attempt.modelID,
103877
+ ...attempt.variant ? { variant: attempt.variant } : {}
103878
+ };
103879
+ }
103880
+ function getAttemptIndex(task, attemptID) {
103881
+ return task.attempts?.findIndex((attempt) => attempt.attemptID === attemptID) ?? -1;
103882
+ }
103883
+ function getAttempt(task, attemptID) {
103884
+ const index = getAttemptIndex(task, attemptID);
103885
+ return index === -1 ? undefined : task.attempts?.[index];
103886
+ }
103887
+ function isTerminalStatus(status) {
103888
+ return status === "completed" || status === "error" || status === "cancelled" || status === "interrupt";
103889
+ }
103890
+ function getCurrentAttempt(task) {
103891
+ if (!task.currentAttemptID) {
103892
+ return;
103893
+ }
103894
+ return getAttempt(task, task.currentAttemptID);
103895
+ }
103896
+ function ensureCurrentAttempt(task, model = task.model) {
103897
+ const existingAttempt = getCurrentAttempt(task);
103898
+ if (existingAttempt) {
103899
+ return existingAttempt;
103900
+ }
103901
+ const attempt = {
103902
+ attemptID: `att_${crypto.randomUUID().slice(0, 8)}`,
103903
+ attemptNumber: (task.attempts?.length ?? 0) + 1,
103904
+ sessionID: task.sessionID,
103905
+ ...toAttemptModel(model),
103906
+ status: task.status,
103907
+ error: task.error,
103908
+ startedAt: task.startedAt,
103909
+ completedAt: task.completedAt
103910
+ };
103911
+ task.attempts = [...task.attempts ?? [], attempt];
103912
+ task.currentAttemptID = attempt.attemptID;
103913
+ return attempt;
103914
+ }
103915
+ function projectTaskFromCurrentAttempt(task) {
103916
+ const currentAttempt = getCurrentAttempt(task);
103917
+ if (!currentAttempt) {
103918
+ return task;
103919
+ }
103920
+ task.status = currentAttempt.status;
103921
+ task.sessionID = currentAttempt.sessionID;
103922
+ task.startedAt = currentAttempt.startedAt;
103923
+ task.completedAt = currentAttempt.completedAt;
103924
+ task.error = currentAttempt.error;
103925
+ task.model = toTaskModel(currentAttempt);
103926
+ return task;
103927
+ }
103928
+ function startAttempt(task, model) {
103929
+ const attempt = {
103930
+ attemptID: `att_${crypto.randomUUID().slice(0, 8)}`,
103931
+ attemptNumber: (task.attempts?.length ?? 0) + 1,
103932
+ ...toAttemptModel(model),
103933
+ status: "pending"
103934
+ };
103935
+ task.attempts = [...task.attempts ?? [], attempt];
103936
+ task.currentAttemptID = attempt.attemptID;
103937
+ task.status = "pending";
103938
+ task.sessionID = undefined;
103939
+ task.startedAt = undefined;
103940
+ task.completedAt = undefined;
103941
+ task.error = undefined;
103942
+ task.model = model;
103943
+ return attempt;
103944
+ }
103945
+ function bindAttemptSession(task, attemptID, sessionID, model) {
103946
+ ensureCurrentAttempt(task, model);
103947
+ if (task.currentAttemptID !== attemptID) {
103948
+ return;
103949
+ }
103950
+ const attempt = getAttempt(task, attemptID);
103951
+ if (!attempt || isTerminalStatus(attempt.status)) {
103952
+ return;
103953
+ }
103954
+ attempt.sessionID = sessionID;
103955
+ attempt.status = "running";
103956
+ attempt.startedAt = new Date;
103957
+ attempt.completedAt = undefined;
103958
+ attempt.error = undefined;
103959
+ attempt.providerID = model?.providerID ?? attempt.providerID;
103960
+ attempt.modelID = model?.modelID ?? attempt.modelID;
103961
+ attempt.variant = model?.variant ?? attempt.variant;
103962
+ return getCurrentAttempt(projectTaskFromCurrentAttempt(task));
103963
+ }
103964
+ function finalizeAttempt(task, attemptID, status, error) {
103965
+ const attempt = getAttempt(task, attemptID);
103966
+ if (!attempt) {
103967
+ return;
103968
+ }
103969
+ attempt.status = status;
103970
+ attempt.completedAt = new Date;
103971
+ attempt.error = error;
103972
+ if (task.currentAttemptID === attemptID) {
103973
+ projectTaskFromCurrentAttempt(task);
103974
+ }
103975
+ return attempt;
103976
+ }
103977
+ function scheduleRetryAttempt(task, failedAttemptID, nextModel, error) {
103978
+ const failedAttempt = finalizeAttempt(task, failedAttemptID, "error", error);
103979
+ if (!failedAttempt || task.currentAttemptID !== failedAttemptID) {
103980
+ return;
103981
+ }
103982
+ return startAttempt(task, nextModel);
103983
+ }
103984
+ function findAttemptBySession(task, sessionID) {
103985
+ return task.attempts?.find((attempt) => attempt.sessionID === sessionID);
103986
+ }
103987
+
103606
103988
  // src/features/background-agent/fallback-retry-handler.ts
103989
+ function canonicalizeModelID2(modelID) {
103990
+ return modelID.toLowerCase().replace(/\./g, "-");
103991
+ }
103607
103992
  async function tryFallbackRetry(args) {
103608
- const { task, errorInfo, source, concurrencyManager, client: client2, idleDeferralTimers, queuesByKey, processKey } = args;
103993
+ const { task, errorInfo, source, concurrencyManager, client: client2, idleDeferralTimers, queuesByKey, processKey, onRetrying } = args;
103609
103994
  const fallbackChain = task.fallbackChain;
103610
103995
  const canRetry = shouldRetryError(errorInfo) && fallbackChain && fallbackChain.length > 0 && hasMoreFallbacks(fallbackChain, task.attemptCount ?? 0);
103611
103996
  if (!canRetry)
@@ -103625,6 +104010,7 @@ async function tryFallbackRetry(args) {
103625
104010
  };
103626
104011
  let selectedAttemptCount = attemptCount;
103627
104012
  let nextFallback;
104013
+ let nextProviderID;
103628
104014
  while (fallbackChain && selectedAttemptCount < fallbackChain.length) {
103629
104015
  const candidate = getNextFallback(fallbackChain, selectedAttemptCount);
103630
104016
  if (!candidate)
@@ -103639,12 +104025,25 @@ async function tryFallbackRetry(args) {
103639
104025
  });
103640
104026
  continue;
103641
104027
  }
104028
+ const candidateProviderID = selectFallbackProvider(candidate.providers, task.model?.providerID);
104029
+ const candidateModelID = transformModelForProvider(candidateProviderID, candidate.model);
104030
+ const isNoOpFallback = !!task.model && candidateProviderID.toLowerCase() === task.model.providerID.toLowerCase() && canonicalizeModelID2(candidateModelID) === canonicalizeModelID2(task.model.modelID);
104031
+ if (isNoOpFallback) {
104032
+ log("[background-agent] Skipping no-op fallback:", {
104033
+ taskId: task.id,
104034
+ source,
104035
+ model: candidate.model,
104036
+ providers: candidate.providers
104037
+ });
104038
+ continue;
104039
+ }
103642
104040
  nextFallback = candidate;
104041
+ nextProviderID = candidateProviderID;
103643
104042
  break;
103644
104043
  }
103645
104044
  if (!nextFallback)
103646
104045
  return false;
103647
- const providerID = selectFallbackProvider(nextFallback.providers, task.model?.providerID);
104046
+ const providerID = nextProviderID ?? selectFallbackProvider(nextFallback.providers, task.model?.providerID);
103648
104047
  log("[background-agent] Retryable error, attempting fallback:", {
103649
104048
  taskId: task.id,
103650
104049
  source,
@@ -103663,18 +104062,34 @@ async function tryFallbackRetry(args) {
103663
104062
  idleDeferralTimers.delete(task.id);
103664
104063
  }
103665
104064
  const previousSessionID = task.sessionID;
103666
- task.attemptCount = selectedAttemptCount;
104065
+ const previousModel = task.model;
103667
104066
  const transformedModelId = transformModelForProvider(providerID, nextFallback.model);
103668
- task.model = {
104067
+ const nextModel = {
103669
104068
  providerID,
103670
104069
  modelID: transformedModelId,
103671
104070
  variant: nextFallback.variant
103672
104071
  };
103673
- task.status = "pending";
103674
- task.sessionID = undefined;
103675
- task.startedAt = undefined;
104072
+ task.attemptCount = selectedAttemptCount;
104073
+ const failedAttemptID = ensureCurrentAttempt(task, previousModel).attemptID;
104074
+ const nextAttempt = failedAttemptID ? scheduleRetryAttempt(task, failedAttemptID, nextModel, errorInfo.message) : undefined;
104075
+ if (!nextAttempt) {
104076
+ return false;
104077
+ }
103676
104078
  task.queuedAt = new Date;
103677
- task.error = undefined;
104079
+ task.retryNotification = {
104080
+ previousSessionID,
104081
+ failedModel: previousModel ? `${previousModel.providerID}/${previousModel.modelID}` : undefined,
104082
+ failedError: errorInfo.message,
104083
+ nextModel: `${providerID}/${transformedModelId}`
104084
+ };
104085
+ onRetrying?.({
104086
+ task,
104087
+ source,
104088
+ previousSessionID,
104089
+ failedModel: task.retryNotification.failedModel,
104090
+ failedError: errorInfo.message,
104091
+ nextModel: `${providerID}/${transformedModelId}`
104092
+ });
103678
104093
  const key = task.model ? `${task.model.providerID}/${task.model.modelID}` : task.agent;
103679
104094
  const queue = queuesByKey.get(key) ?? [];
103680
104095
  const retryInput = {
@@ -103686,7 +104101,7 @@ async function tryFallbackRetry(args) {
103686
104101
  parentModel: task.parentModel,
103687
104102
  parentAgent: task.parentAgent,
103688
104103
  parentTools: task.parentTools,
103689
- model: task.model,
104104
+ model: nextModel,
103690
104105
  fallbackChain: task.fallbackChain,
103691
104106
  category: task.category,
103692
104107
  isUnstableAgent: task.isUnstableAgent
@@ -103694,7 +104109,7 @@ async function tryFallbackRetry(args) {
103694
104109
  if (previousSessionID) {
103695
104110
  await abortWithTimeout(client2, previousSessionID).catch(() => {});
103696
104111
  }
103697
- queue.push({ task, input: retryInput });
104112
+ queue.push({ task, input: retryInput, attemptID: nextAttempt.attemptID });
103698
104113
  queuesByKey.set(key, queue);
103699
104114
  processKey(key);
103700
104115
  return true;
@@ -104312,10 +104727,37 @@ function resolveMessagePartInfo(properties) {
104312
104727
  }
104313
104728
  return properties;
104314
104729
  }
104730
+ function formatAttemptModelSummary(attempt) {
104731
+ if (!attempt?.providerID || !attempt.modelID) {
104732
+ return;
104733
+ }
104734
+ return `${attempt.providerID}/${attempt.modelID}`;
104735
+ }
104736
+ function getPreviousAttempt(task, attemptID) {
104737
+ if (!attemptID || !task.attempts || task.attempts.length === 0) {
104738
+ return;
104739
+ }
104740
+ const attemptIndex = task.attempts.findIndex((attempt) => attempt.attemptID === attemptID);
104741
+ if (attemptIndex <= 0) {
104742
+ return;
104743
+ }
104744
+ return task.attempts[attemptIndex - 1];
104745
+ }
104746
+ function cloneAttempts(task) {
104747
+ if (!task.attempts) {
104748
+ return;
104749
+ }
104750
+ return task.attempts.map((attempt) => ({ ...attempt }));
104751
+ }
104752
+ function buildLocalSessionUrl(directory, sessionID) {
104753
+ const encodedDirectory = Buffer.from(directory).toString("base64url");
104754
+ return `http://127.0.0.1:4096/${encodedDirectory}/session/${sessionID}`;
104755
+ }
104315
104756
  var MAX_TASK_REMOVAL_RESCHEDULES = 6;
104316
104757
 
104317
104758
  class BackgroundManager {
104318
104759
  tasks;
104760
+ tasksByParentSession;
104319
104761
  notifications;
104320
104762
  pendingNotifications;
104321
104763
  pendingByParent;
@@ -104340,10 +104782,12 @@ class BackgroundManager {
104340
104782
  rootDescendantCounts;
104341
104783
  preStartDescendantReservations;
104342
104784
  enableParentSessionNotifications;
104785
+ modelFallbackControllerAccessor;
104343
104786
  taskHistory = new TaskHistory;
104344
104787
  cachedCircuitBreakerSettings;
104345
104788
  constructor(ctx, config2, options) {
104346
104789
  this.tasks = new Map;
104790
+ this.tasksByParentSession = new Map;
104347
104791
  this.notifications = new Map;
104348
104792
  this.pendingNotifications = new Map;
104349
104793
  this.pendingByParent = new Map;
@@ -104357,6 +104801,7 @@ class BackgroundManager {
104357
104801
  this.rootDescendantCounts = new Map;
104358
104802
  this.preStartDescendantReservations = new Set;
104359
104803
  this.enableParentSessionNotifications = options?.enableParentSessionNotifications ?? true;
104804
+ this.modelFallbackControllerAccessor = options?.modelFallbackControllerAccessor;
104360
104805
  this.registerProcessCleanup();
104361
104806
  }
104362
104807
  async abortSessionWithLogging(sessionID, reason) {
@@ -104429,6 +104874,42 @@ class BackgroundManager {
104429
104874
  }
104430
104875
  this.unregisterRootDescendant(task.rootSessionID);
104431
104876
  }
104877
+ addTask(task) {
104878
+ this.tasks.set(task.id, task);
104879
+ if (!task.parentSessionID) {
104880
+ return;
104881
+ }
104882
+ const taskIDs = this.tasksByParentSession.get(task.parentSessionID) ?? new Set;
104883
+ taskIDs.add(task.id);
104884
+ this.tasksByParentSession.set(task.parentSessionID, taskIDs);
104885
+ }
104886
+ removeTask(task) {
104887
+ this.tasks.delete(task.id);
104888
+ this.removeTaskFromParentIndex(task.id, task.parentSessionID);
104889
+ }
104890
+ updateTaskParent(task, parentSessionID) {
104891
+ if (task.parentSessionID === parentSessionID) {
104892
+ return;
104893
+ }
104894
+ this.removeTaskFromParentIndex(task.id, task.parentSessionID);
104895
+ task.parentSessionID = parentSessionID;
104896
+ const taskIDs = this.tasksByParentSession.get(parentSessionID) ?? new Set;
104897
+ taskIDs.add(task.id);
104898
+ this.tasksByParentSession.set(parentSessionID, taskIDs);
104899
+ }
104900
+ removeTaskFromParentIndex(taskID, parentSessionID) {
104901
+ if (!parentSessionID) {
104902
+ return;
104903
+ }
104904
+ const taskIDs = this.tasksByParentSession.get(parentSessionID);
104905
+ if (!taskIDs) {
104906
+ return;
104907
+ }
104908
+ taskIDs.delete(taskID);
104909
+ if (taskIDs.size === 0) {
104910
+ this.tasksByParentSession.delete(parentSessionID);
104911
+ }
104912
+ }
104432
104913
  async launch(input) {
104433
104914
  log("[background-agent] launch() called with:", {
104434
104915
  agent: input.agent,
@@ -104466,7 +104947,8 @@ class BackgroundManager {
104466
104947
  attemptCount: 0,
104467
104948
  category: input.category
104468
104949
  };
104469
- this.tasks.set(task.id, task);
104950
+ const firstAttempt = startAttempt(task, input.model);
104951
+ this.addTask(task);
104470
104952
  this.taskHistory.record(input.parentSessionID, { id: task.id, agent: input.agent, description: input.description, status: "pending", category: input.category });
104471
104953
  if (input.parentSessionID) {
104472
104954
  const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
@@ -104475,7 +104957,7 @@ class BackgroundManager {
104475
104957
  }
104476
104958
  const key = this.getConcurrencyKeyFromInput(input);
104477
104959
  const queue = this.queuesByKey.get(key) ?? [];
104478
- queue.push({ task, input });
104960
+ queue.push({ task, input, attemptID: firstAttempt.attemptID });
104479
104961
  this.queuesByKey.set(key, queue);
104480
104962
  log("[background-agent] Task queued:", { taskId: task.id, key, queueLength: queue.length });
104481
104963
  const toastManager = getTaskToastManager();
@@ -104521,9 +105003,13 @@ class BackgroundManager {
104521
105003
  } catch (error) {
104522
105004
  log("[background-agent] Error starting task:", error);
104523
105005
  this.rollbackPreStartDescendantReservation(item.task);
104524
- item.task.status = "error";
104525
- item.task.error = error instanceof Error ? error.message : String(error);
104526
- item.task.completedAt = new Date;
105006
+ if (item.task.currentAttemptID) {
105007
+ finalizeAttempt(item.task, item.task.currentAttemptID, "error", error instanceof Error ? error.message : String(error));
105008
+ } else {
105009
+ item.task.status = "error";
105010
+ item.task.error = error instanceof Error ? error.message : String(error);
105011
+ item.task.completedAt = new Date;
105012
+ }
104527
105013
  if (item.task.concurrencyKey) {
104528
105014
  this.concurrencyManager.release(item.task.concurrencyKey);
104529
105015
  item.task.concurrencyKey = undefined;
@@ -104546,6 +105032,7 @@ class BackgroundManager {
104546
105032
  }
104547
105033
  async startTask(item) {
104548
105034
  const { task, input } = item;
105035
+ const attemptID = item.attemptID ?? ensureCurrentAttempt(task, input.model).attemptID;
104549
105036
  log("[background-agent] Starting task:", {
104550
105037
  taskId: task.id,
104551
105038
  agent: input.agent,
@@ -104615,15 +105102,49 @@ class BackgroundManager {
104615
105102
  this.concurrencyManager.release(concurrencyKey);
104616
105103
  return;
104617
105104
  }
104618
- task.status = "running";
104619
- task.startedAt = new Date;
104620
- task.sessionID = sessionID;
105105
+ const boundAttempt = bindAttemptSession(task, attemptID, sessionID, input.model);
105106
+ if (!boundAttempt) {
105107
+ await this.abortSessionWithLogging(sessionID, "stale attempt binding cleanup");
105108
+ subagentSessions.delete(sessionID);
105109
+ if (task.rootSessionID) {
105110
+ this.unregisterRootDescendant(task.rootSessionID);
105111
+ }
105112
+ this.concurrencyManager.release(concurrencyKey);
105113
+ return;
105114
+ }
104621
105115
  task.progress = {
104622
105116
  toolCalls: 0,
104623
105117
  lastUpdate: new Date
104624
105118
  };
104625
105119
  task.concurrencyKey = concurrencyKey;
104626
105120
  task.concurrencyGroup = concurrencyKey;
105121
+ if (task.retryNotification) {
105122
+ const attemptNumber = boundAttempt.attemptNumber;
105123
+ const retrySessionUrl = buildLocalSessionUrl(parentDirectory, sessionID);
105124
+ const previousAttempt = getPreviousAttempt(task, boundAttempt.attemptID);
105125
+ const failedSessionID = previousAttempt?.sessionID ?? task.retryNotification.previousSessionID;
105126
+ const failedSessionLine = failedSessionID ? `
105127
+ - Failed session: \`${failedSessionID}\`` : "";
105128
+ const failedModel = formatAttemptModelSummary(previousAttempt) ?? task.retryNotification.failedModel;
105129
+ const failedModelLine = failedModel ? `
105130
+ - Failed model: \`${failedModel}\`` : "";
105131
+ const failedError = previousAttempt?.error ?? task.retryNotification.failedError;
105132
+ const failedErrorLine = failedError ? `
105133
+ - Error: ${failedError}` : "";
105134
+ const retryModel = formatAttemptModelSummary(boundAttempt) ?? task.retryNotification.nextModel;
105135
+ this.queuePendingNotification(task.parentSessionID, `<system-reminder>
105136
+ [BACKGROUND TASK RETRY SESSION READY]
105137
+ **ID:** \`${task.id}\`
105138
+ **Description:** ${task.description}
105139
+ **Retry attempt:** ${attemptNumber}
105140
+ **Retry session:** \`${sessionID}\`
105141
+ **Retry link:** ${retrySessionUrl}${failedSessionLine}${failedModelLine}${failedErrorLine}${retryModel ? `
105142
+ - Model: \`${retryModel}\`` : ""}
105143
+
105144
+ The fallback retry session is now created and can be inspected directly.
105145
+ </system-reminder>`);
105146
+ task.retryNotification = undefined;
105147
+ }
104627
105148
  this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID, agent: input.agent, description: input.description, status: "running", category: input.category, startedAt: task.startedAt });
104628
105149
  this.startPolling();
104629
105150
  log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent });
@@ -104687,16 +105208,33 @@ class BackgroundManager {
104687
105208
  }
104688
105209
  }
104689
105210
  log("[background-agent] promptAsync error:", error);
104690
- const existingTask = this.findBySession(sessionID);
105211
+ const resolvedTask = this.resolveTaskAttemptBySession(sessionID);
105212
+ const existingTask = resolvedTask?.task;
105213
+ if (resolvedTask && !resolvedTask.isCurrent) {
105214
+ log("[background-agent] Ignoring prompt error from stale attempt session", {
105215
+ sessionID,
105216
+ currentAttemptID: resolvedTask.task.currentAttemptID,
105217
+ attemptID: resolvedTask.attemptID
105218
+ });
105219
+ return;
105220
+ }
104691
105221
  if (existingTask) {
104692
- existingTask.status = "interrupt";
104693
- const errorMessage = error instanceof Error ? error.message : String(error);
104694
- if (errorMessage.includes("agent.name") || errorMessage.includes("undefined") || isAgentNotFoundError(error)) {
104695
- existingTask.error = `Agent "${input.agent}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.`;
105222
+ const errorInfo = {
105223
+ name: extractErrorName2(error),
105224
+ message: extractErrorMessage(error)
105225
+ };
105226
+ if (await this.tryFallbackRetry(existingTask, errorInfo, "promptAsync.launch")) {
105227
+ return;
105228
+ }
105229
+ const errorMessage = errorInfo.message ?? (error instanceof Error ? error.message : String(error));
105230
+ const terminalError = errorMessage.includes("agent.name") || errorMessage.includes("undefined") || isAgentNotFoundError(error) ? `Agent "${input.agent}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.` : errorMessage;
105231
+ if (existingTask.currentAttemptID) {
105232
+ finalizeAttempt(existingTask, existingTask.currentAttemptID, "interrupt", terminalError);
104696
105233
  } else {
104697
- existingTask.error = errorMessage;
105234
+ existingTask.status = "interrupt";
105235
+ existingTask.error = terminalError;
105236
+ existingTask.completedAt = new Date;
104698
105237
  }
104699
- existingTask.completedAt = new Date;
104700
105238
  if (existingTask.rootSessionID) {
104701
105239
  this.unregisterRootDescendant(existingTask.rootSessionID);
104702
105240
  }
@@ -104717,13 +105255,24 @@ class BackgroundManager {
104717
105255
  return this.tasks.get(id);
104718
105256
  }
104719
105257
  getTasksByParentSession(sessionID) {
104720
- const result = [];
104721
- for (const task of this.tasks.values()) {
104722
- if (task.parentSessionID === sessionID) {
104723
- result.push(task);
105258
+ const taskIDs = this.tasksByParentSession.get(sessionID);
105259
+ if (!taskIDs) {
105260
+ const result = [];
105261
+ for (const task of this.tasks.values()) {
105262
+ if (task.parentSessionID === sessionID) {
105263
+ result.push(task);
105264
+ }
105265
+ }
105266
+ return result;
105267
+ }
105268
+ const tasks = [];
105269
+ for (const taskID of taskIDs) {
105270
+ const task = this.tasks.get(taskID);
105271
+ if (task) {
105272
+ tasks.push(task);
104724
105273
  }
104725
105274
  }
104726
- return result;
105275
+ return tasks;
104727
105276
  }
104728
105277
  getAllDescendantTasks(sessionID) {
104729
105278
  const result = [];
@@ -104742,9 +105291,31 @@ class BackgroundManager {
104742
105291
  if (task.sessionID === sessionID) {
104743
105292
  return task;
104744
105293
  }
105294
+ if (findAttemptBySession(task, sessionID)) {
105295
+ return task;
105296
+ }
104745
105297
  }
104746
105298
  return;
104747
105299
  }
105300
+ resolveTaskAttemptBySession(sessionID) {
105301
+ const task = this.findBySession(sessionID);
105302
+ if (!task) {
105303
+ return;
105304
+ }
105305
+ const attempt = findAttemptBySession(task, sessionID);
105306
+ if (!attempt) {
105307
+ return {
105308
+ task,
105309
+ attemptID: undefined,
105310
+ isCurrent: task.sessionID === sessionID
105311
+ };
105312
+ }
105313
+ return {
105314
+ task,
105315
+ attemptID: attempt.attemptID,
105316
+ isCurrent: task.currentAttemptID === attempt.attemptID
105317
+ };
105318
+ }
104748
105319
  getConcurrencyKeyFromInput(input) {
104749
105320
  if (input.model) {
104750
105321
  return `${input.model.providerID}/${input.model.modelID}`;
@@ -104757,7 +105328,7 @@ class BackgroundManager {
104757
105328
  const parentChanged = input.parentSessionID !== existingTask.parentSessionID;
104758
105329
  if (parentChanged) {
104759
105330
  this.cleanupPendingByParent(existingTask);
104760
- existingTask.parentSessionID = input.parentSessionID;
105331
+ this.updateTaskParent(existingTask, input.parentSessionID);
104761
105332
  }
104762
105333
  if (input.parentAgent !== undefined) {
104763
105334
  existingTask.parentAgent = input.parentAgent;
@@ -104801,7 +105372,7 @@ class BackgroundManager {
104801
105372
  concurrencyKey: input.concurrencyKey,
104802
105373
  concurrencyGroup
104803
105374
  };
104804
- this.tasks.set(task.id, task);
105375
+ this.addTask(task);
104805
105376
  subagentSessions.add(input.sessionID);
104806
105377
  this.startPolling();
104807
105378
  this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID: input.sessionID, agent: input.agent || "task", description: input.description, status: "running", startedAt: task.startedAt });
@@ -104840,7 +105411,7 @@ class BackgroundManager {
104840
105411
  existingTask.status = "running";
104841
105412
  existingTask.completedAt = undefined;
104842
105413
  existingTask.error = undefined;
104843
- existingTask.parentSessionID = input.parentSessionID;
105414
+ this.updateTaskParent(existingTask, input.parentSessionID);
104844
105415
  existingTask.parentMessageID = input.parentMessageID;
104845
105416
  existingTask.parentModel = input.parentModel;
104846
105417
  existingTask.parentAgent = input.parentAgent;
@@ -104907,8 +105478,15 @@ class BackgroundManager {
104907
105478
  }
104908
105479
  }).catch(async (error) => {
104909
105480
  log("[background-agent] resume prompt error:", error);
105481
+ const errorInfo = {
105482
+ name: extractErrorName2(error),
105483
+ message: extractErrorMessage(error)
105484
+ };
105485
+ if (await this.tryFallbackRetry(existingTask, errorInfo, "promptAsync.resume")) {
105486
+ return;
105487
+ }
104910
105488
  existingTask.status = "interrupt";
104911
- const errorMessage = error instanceof Error ? error.message : String(error);
105489
+ const errorMessage = errorInfo.message ?? (error instanceof Error ? error.message : String(error));
104912
105490
  existingTask.error = errorMessage;
104913
105491
  existingTask.completedAt = new Date;
104914
105492
  if (existingTask.rootSessionID) {
@@ -104991,8 +105569,11 @@ class BackgroundManager {
104991
105569
  }
104992
105570
  if (role !== "assistant")
104993
105571
  return;
104994
- const task = this.findBySession(sessionID);
104995
- if (!task || task.status !== "running")
105572
+ const resolved = this.resolveTaskAttemptBySession(sessionID);
105573
+ if (!resolved?.isCurrent)
105574
+ return;
105575
+ const { task } = resolved;
105576
+ if (task.status !== "running")
104996
105577
  return;
104997
105578
  const assistantError = info["error"];
104998
105579
  if (!assistantError)
@@ -105013,9 +105594,10 @@ class BackgroundManager {
105013
105594
  const sessionID = partInfo?.sessionID;
105014
105595
  if (!sessionID)
105015
105596
  return;
105016
- const task = this.findBySession(sessionID);
105017
- if (!task)
105597
+ const resolved = this.resolveTaskAttemptBySession(sessionID);
105598
+ if (!resolved?.isCurrent)
105018
105599
  return;
105600
+ const { task } = resolved;
105019
105601
  if (this.hasOutputSignalFromPart(partInfo)) {
105020
105602
  this.markSessionOutputObserved(sessionID);
105021
105603
  }
@@ -105100,7 +105682,10 @@ class BackgroundManager {
105100
105682
  return;
105101
105683
  handleSessionIdleBackgroundEvent({
105102
105684
  properties: props,
105103
- findBySession: (id) => this.findBySession(id),
105685
+ findBySession: (id) => {
105686
+ const resolved = this.resolveTaskAttemptBySession(id);
105687
+ return resolved?.isCurrent ? resolved.task : undefined;
105688
+ },
105104
105689
  idleDeferralTimers: this.idleDeferralTimers,
105105
105690
  validateSessionHasOutput: (id) => this.validateSessionHasOutput(id),
105106
105691
  checkSessionTodos: (id) => this.checkSessionTodos(id),
@@ -105112,8 +105697,11 @@ class BackgroundManager {
105112
105697
  const sessionID = typeof props?.sessionID === "string" ? props.sessionID : undefined;
105113
105698
  if (!sessionID)
105114
105699
  return;
105115
- const task = this.findBySession(sessionID);
105116
- if (!task || task.status !== "running")
105700
+ const resolved = this.resolveTaskAttemptBySession(sessionID);
105701
+ if (!resolved?.isCurrent)
105702
+ return;
105703
+ const { task } = resolved;
105704
+ if (task.status !== "running")
105117
105705
  return;
105118
105706
  const errorObj = props?.error;
105119
105707
  const errorName = errorObj?.name;
@@ -105140,9 +105728,9 @@ class BackgroundManager {
105140
105728
  this.clearSessionOutputObserved(sessionID);
105141
105729
  this.clearSessionTodoObservation(sessionID);
105142
105730
  const tasksToCancel = new Map;
105143
- const directTask = this.findBySession(sessionID);
105144
- if (directTask) {
105145
- tasksToCancel.set(directTask.id, directTask);
105731
+ const directTask = this.resolveTaskAttemptBySession(sessionID);
105732
+ if (directTask?.isCurrent) {
105733
+ tasksToCancel.set(directTask.task.id, directTask.task);
105146
105734
  }
105147
105735
  for (const descendant of this.getAllDescendantTasks(sessionID)) {
105148
105736
  tasksToCancel.set(descendant.id, descendant);
@@ -105188,8 +105776,11 @@ class BackgroundManager {
105188
105776
  const status = props?.status;
105189
105777
  if (!sessionID || status?.type !== "retry")
105190
105778
  return;
105191
- const task = this.findBySession(sessionID);
105192
- if (!task || task.status !== "running")
105779
+ const resolved = this.resolveTaskAttemptBySession(sessionID);
105780
+ if (!resolved?.isCurrent)
105781
+ return;
105782
+ const { task } = resolved;
105783
+ if (task.status !== "running")
105193
105784
  return;
105194
105785
  const errorMessage = typeof status.message === "string" ? status.message : undefined;
105195
105786
  const errorInfo = { name: "SessionRetry", message: errorMessage };
@@ -105203,6 +105794,12 @@ class BackgroundManager {
105203
105794
  }
105204
105795
  async handleSessionErrorEvent(args) {
105205
105796
  const { task, errorInfo, errorMessage, errorName } = args;
105797
+ if (!task.fallbackChain && task.sessionID) {
105798
+ const sessionFallbackChain = this.modelFallbackControllerAccessor?.getSessionFallbackChain(task.sessionID);
105799
+ if (sessionFallbackChain?.length) {
105800
+ task.fallbackChain = sessionFallbackChain;
105801
+ }
105802
+ }
105206
105803
  if (isAgentNotFoundError({ message: errorInfo.message })) {
105207
105804
  log("[background-agent] Skipping session.error fallback for agent-not-found (handled by prompt catch)", {
105208
105805
  taskId: task.id,
@@ -105222,9 +105819,13 @@ class BackgroundManager {
105222
105819
  hasFallbackChain: !!task.fallbackChain,
105223
105820
  canRetry
105224
105821
  });
105225
- task.status = "error";
105226
- task.error = errorMsg;
105227
- task.completedAt = new Date;
105822
+ if (task.currentAttemptID) {
105823
+ finalizeAttempt(task, task.currentAttemptID, "error", errorMsg);
105824
+ } else {
105825
+ task.status = "error";
105826
+ task.error = errorMsg;
105827
+ task.completedAt = new Date;
105828
+ }
105228
105829
  if (task.rootSessionID) {
105229
105830
  this.unregisterRootDescendant(task.rootSessionID);
105230
105831
  }
@@ -105268,7 +105869,28 @@ class BackgroundManager {
105268
105869
  client: this.client,
105269
105870
  idleDeferralTimers: this.idleDeferralTimers,
105270
105871
  queuesByKey: this.queuesByKey,
105271
- processKey: (key) => this.processKey(key)
105872
+ processKey: (key) => this.processKey(key),
105873
+ onRetrying: ({ task: task2, source: source2 }) => {
105874
+ const currentAttempt = getCurrentAttempt(task2);
105875
+ const previousAttempt = getPreviousAttempt(task2, currentAttempt?.attemptID);
105876
+ const sourceText = source2 ? ` via ${source2}` : "";
105877
+ const failedSessionLine = previousAttempt?.sessionID ? `
105878
+ - Failed session: \`${previousAttempt.sessionID}\`` : "";
105879
+ const failedModel = formatAttemptModelSummary(previousAttempt);
105880
+ const failedModelLine = failedModel ? `
105881
+ - Failed model: \`${failedModel}\`` : "";
105882
+ const failedErrorLine = previousAttempt?.error ? `
105883
+ - Error: ${previousAttempt.error}` : "";
105884
+ const nextModel = formatAttemptModelSummary(currentAttempt);
105885
+ this.queuePendingNotification(task2.parentSessionID, `<system-reminder>
105886
+ [BACKGROUND TASK RETRYING]
105887
+ **ID:** \`${task2.id}\`
105888
+ **Description:** ${task2.description}${sourceText}${failedSessionLine}${failedModelLine}${failedErrorLine}${nextModel ? `
105889
+ - Next model: \`${nextModel}\`` : ""}
105890
+
105891
+ The task was re-queued on a fallback model after a retryable failure.
105892
+ </system-reminder>`);
105893
+ }
105272
105894
  });
105273
105895
  return result.then((retried) => {
105274
105896
  if (retried && previousSessionID) {
@@ -105400,7 +106022,7 @@ ${originalText}`;
105400
106022
  }
105401
106023
  }
105402
106024
  this.clearNotificationsForTask(taskId);
105403
- this.tasks.delete(taskId);
106025
+ this.removeTask(task);
105404
106026
  this.clearTaskHistoryWhenParentTasksGone(task.parentSessionID);
105405
106027
  if (task.sessionID) {
105406
106028
  subagentSessions.delete(task.sessionID);
@@ -105434,14 +106056,18 @@ ${originalText}`;
105434
106056
  log("[background-agent] Cancelled pending task:", { taskId, key });
105435
106057
  }
105436
106058
  const wasRunning = task.status === "running";
105437
- task.status = "cancelled";
105438
- task.completedAt = new Date;
106059
+ if (task.currentAttemptID) {
106060
+ finalizeAttempt(task, task.currentAttemptID, "cancelled", reason);
106061
+ } else {
106062
+ task.status = "cancelled";
106063
+ task.completedAt = new Date;
106064
+ if (reason) {
106065
+ task.error = reason;
106066
+ }
106067
+ }
105439
106068
  if (wasRunning && task.rootSessionID) {
105440
106069
  this.unregisterRootDescendant(task.rootSessionID);
105441
106070
  }
105442
- if (reason) {
105443
- task.error = reason;
105444
- }
105445
106071
  this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "cancelled", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt });
105446
106072
  if (task.concurrencyKey) {
105447
106073
  this.concurrencyManager.release(task.concurrencyKey);
@@ -105516,8 +106142,12 @@ ${originalText}`;
105516
106142
  log("[background-agent] Task already completed, skipping:", { taskId: task.id, status: task.status, source });
105517
106143
  return false;
105518
106144
  }
105519
- task.status = "completed";
105520
- task.completedAt = new Date;
106145
+ if (task.currentAttemptID) {
106146
+ finalizeAttempt(task, task.currentAttemptID, "completed");
106147
+ } else {
106148
+ task.status = "completed";
106149
+ task.completedAt = new Date;
106150
+ }
105521
106151
  this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "completed", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt });
105522
106152
  if (task.rootSessionID) {
105523
106153
  this.unregisterRootDescendant(task.rootSessionID);
@@ -105563,7 +106193,8 @@ ${originalText}`;
105563
106193
  id: task.id,
105564
106194
  description: task.description,
105565
106195
  status: task.status,
105566
- error: task.error
106196
+ error: task.error,
106197
+ attempts: cloneAttempts(task)
105567
106198
  });
105568
106199
  const pendingSet = this.pendingByParent.get(task.parentSessionID);
105569
106200
  let allComplete = false;
@@ -105579,7 +106210,7 @@ ${originalText}`;
105579
106210
  remainingCount = Array.from(this.tasks.values()).filter((t) => t.parentSessionID === task.parentSessionID && t.id !== task.id && (t.status === "running" || t.status === "pending")).length;
105580
106211
  allComplete = remainingCount === 0;
105581
106212
  }
105582
- const completedTasks = allComplete ? this.completedTaskSummaries.get(task.parentSessionID) ?? [{ id: task.id, description: task.description, status: task.status, error: task.error }] : [];
106213
+ const completedTasks = allComplete ? this.completedTaskSummaries.get(task.parentSessionID) ?? [{ id: task.id, description: task.description, status: task.status, error: task.error, attempts: cloneAttempts(task) }] : [];
105583
106214
  if (allComplete) {
105584
106215
  this.completedTaskSummaries.delete(task.parentSessionID);
105585
106216
  }
@@ -105741,9 +106372,13 @@ ${originalText}`;
105741
106372
  return verifySessionExists(this.client, sessionID, this.directory);
105742
106373
  }
105743
106374
  async failCrashedTask(task, errorMessage) {
105744
- task.status = "error";
105745
- task.error = errorMessage;
105746
- task.completedAt = new Date;
106375
+ if (task.currentAttemptID) {
106376
+ finalizeAttempt(task, task.currentAttemptID, "error", errorMessage);
106377
+ } else {
106378
+ task.status = "error";
106379
+ task.error = errorMessage;
106380
+ task.completedAt = new Date;
106381
+ }
105747
106382
  if (task.rootSessionID) {
105748
106383
  this.unregisterRootDescendant(task.rootSessionID);
105749
106384
  }
@@ -105911,6 +106546,7 @@ ${originalText}`;
105911
106546
  }
105912
106547
  this.concurrencyManager.clear();
105913
106548
  this.tasks.clear();
106549
+ this.tasksByParentSession.clear();
105914
106550
  this.notifications.clear();
105915
106551
  this.pendingNotifications.clear();
105916
106552
  this.pendingByParent.clear();
@@ -111164,12 +111800,16 @@ function createModelFallbackControllerAccessor() {
111164
111800
  function setSessionFallbackChain2(sessionID, fallbackChain) {
111165
111801
  controller?.setSessionFallbackChain(sessionID, fallbackChain);
111166
111802
  }
111803
+ function getSessionFallbackChain(sessionID) {
111804
+ return controller?.getSessionFallbackChain(sessionID);
111805
+ }
111167
111806
  function clearSessionFallbackChain2(sessionID) {
111168
111807
  controller?.clearSessionFallbackChain(sessionID);
111169
111808
  }
111170
111809
  return {
111171
111810
  register,
111172
111811
  setSessionFallbackChain: setSessionFallbackChain2,
111812
+ getSessionFallbackChain,
111173
111813
  clearSessionFallbackChain: clearSessionFallbackChain2
111174
111814
  };
111175
111815
  }
@@ -114065,6 +114705,12 @@ async function loadMcpConfigs(disabledMcps = []) {
114065
114705
  }
114066
114706
  return { servers, loadedServers };
114067
114707
  }
114708
+ // src/agents/index.ts
114709
+ init_types();
114710
+
114711
+ // src/agents/sisyphus.ts
114712
+ init_types();
114713
+
114068
114714
  // src/agents/sisyphus/gemini.ts
114069
114715
  function buildGeminiToolMandate() {
114070
114716
  return `<TOOL_CALL_MANDATE>
@@ -115208,6 +115854,7 @@ ${antiPatterns}
115208
115854
  }
115209
115855
 
115210
115856
  // src/agents/gpt-apply-patch-guard.ts
115857
+ init_types();
115211
115858
  var GPT_APPLY_PATCH_GUIDANCE = "Use the `edit` and `write` tools for file changes. Do not use `apply_patch` on GPT models - it is unreliable here and can hang during verification.";
115212
115859
  function getGptApplyPatchPermission(model) {
115213
115860
  return isGptModel(model) ? { apply_patch: "deny" } : {};
@@ -116313,6 +116960,7 @@ ${styleBlock}`;
116313
116960
  }
116314
116961
 
116315
116962
  // src/agents/frontier-tool-schema-guard.ts
116963
+ init_types();
116316
116964
  var FRONTIER_TOOL_SCHEMA_NAMES = ["grep", "glob"];
116317
116965
  function isOpus47Model(model) {
116318
116966
  const modelName = model.includes("/") ? model.split("/").pop() ?? model : model;
@@ -116864,6 +117512,7 @@ ${buildGeminiVerificationOverride()}
116864
117512
  createSisyphusAgent.mode = MODE;
116865
117513
 
116866
117514
  // src/agents/oracle.ts
117515
+ init_types();
116867
117516
  var MODE2 = "subagent";
116868
117517
  var ORACLE_PROMPT_METADATA = {
116869
117518
  category: "advisor",
@@ -118077,6 +118726,9 @@ var metisPromptMetadata = {
118077
118726
  keyTrigger: "Ambiguous or complex request \u2192 consult Metis before Prometheus"
118078
118727
  };
118079
118728
 
118729
+ // src/agents/atlas/agent.ts
118730
+ init_types();
118731
+
118080
118732
  // src/agents/atlas/shared-prompt.ts
118081
118733
  var ATLAS_DELEGATION_SYSTEM = `<delegation_system>
118082
118734
  ## How to Delegate
@@ -119303,6 +119955,7 @@ var atlasPromptMetadata = {
119303
119955
  keyTrigger: "Todo list path provided OR multiple tasks requiring multi-agent orchestration"
119304
119956
  };
119305
119957
  // src/agents/momus.ts
119958
+ init_types();
119306
119959
  var MODE8 = "subagent";
119307
119960
  var MOMUS_DEFAULT_PROMPT = `You are a **practical** work plan reviewer. Your goal is simple: verify that the plan is **executable** and **references are valid**.
119308
119961
 
@@ -119607,6 +120260,9 @@ var momusPromptMetadata = {
119607
120260
  keyTrigger: 'Work plan saved to `.sisyphus/plans/*.md` \u2192 invoke Momus with the file path as the sole prompt (e.g. `prompt=".sisyphus/plans/my-plan.md"`). Do NOT invoke Momus for inline plans or todo lists.'
119608
120261
  };
119609
120262
 
120263
+ // src/agents/hephaestus/agent.ts
120264
+ init_types();
120265
+
119610
120266
  // src/agents/hephaestus/gpt.ts
119611
120267
  function buildTodoDisciplineSection(useTaskSystem) {
119612
120268
  if (useTaskSystem) {
@@ -122104,6 +122760,7 @@ No tasks on multi-step work = INCOMPLETE WORK. The user tracks your progress thr
122104
122760
  No todos on multi-step work = INCOMPLETE WORK. The user tracks your progress through todos.`;
122105
122761
  }
122106
122762
  // src/agents/sisyphus-junior/agent.ts
122763
+ init_types();
122107
122764
  var MODE11 = "subagent";
122108
122765
  var BLOCKED_TOOLS3 = ["task"];
122109
122766
  var GPT_BLOCKED_TOOLS = ["task", "apply_patch"];
@@ -124906,6 +125563,7 @@ function getGeminiPrometheusPrompt() {
124906
125563
  }
124907
125564
 
124908
125565
  // src/agents/prometheus/system-prompt.ts
125566
+ init_types();
124909
125567
  var PROMETHEUS_SYSTEM_PROMPT = `${PROMETHEUS_IDENTITY_CONSTRAINTS}
124910
125568
  ${PROMETHEUS_INTERVIEW_MODE}
124911
125569
  ${PROMETHEUS_PLAN_GENERATION}
@@ -125801,6 +126459,7 @@ function createManagers(args) {
125801
126459
  deps.markServerRunningInProcessFn();
125802
126460
  }
125803
126461
  const tmuxSessionManager = new deps.TmuxSessionManagerClass(ctx, tmuxConfig);
126462
+ const modelFallbackControllerAccessor = createModelFallbackControllerAccessor();
125804
126463
  deps.registerManagerForCleanupFn({
125805
126464
  shutdown: async () => {
125806
126465
  await tmuxSessionManager.cleanup().catch((error) => {
@@ -125844,7 +126503,8 @@ function createManagers(args) {
125844
126503
  log("[create-managers] tmux cleanup error during shutdown:", error);
125845
126504
  });
125846
126505
  },
125847
- enableParentSessionNotifications: backgroundNotificationHookEnabled
126506
+ enableParentSessionNotifications: backgroundNotificationHookEnabled,
126507
+ modelFallbackControllerAccessor
125848
126508
  });
125849
126509
  deps.initTaskToastManagerFn(ctx.client);
125850
126510
  const skillMcpManager = new deps.SkillMcpManagerClass;
@@ -125853,7 +126513,6 @@ function createManagers(args) {
125853
126513
  pluginConfig,
125854
126514
  modelCacheState
125855
126515
  });
125856
- const modelFallbackControllerAccessor = createModelFallbackControllerAccessor();
125857
126516
  return {
125858
126517
  tmuxSessionManager,
125859
126518
  backgroundManager,
@@ -126994,15 +127653,13 @@ function extractErrorMessage3(error) {
126994
127653
  return "";
126995
127654
  if (typeof error === "string")
126996
127655
  return error;
126997
- if (error instanceof Error)
126998
- return error.message;
126999
127656
  if (isRecord19(error)) {
127000
127657
  const candidates = [
127001
- error,
127002
127658
  error.data,
127003
- error.error,
127004
127659
  isRecord19(error.data) ? error.data.error : undefined,
127005
- error.cause
127660
+ error.error,
127661
+ error.cause,
127662
+ error
127006
127663
  ];
127007
127664
  for (const candidate of candidates) {
127008
127665
  if (isRecord19(candidate) && typeof candidate.message === "string" && candidate.message.length > 0) {
@@ -127010,6 +127667,8 @@ function extractErrorMessage3(error) {
127010
127667
  }
127011
127668
  }
127012
127669
  }
127670
+ if (error instanceof Error)
127671
+ return error.message;
127013
127672
  try {
127014
127673
  return JSON.stringify(error);
127015
127674
  } catch {
@@ -127299,6 +127958,9 @@ function createEventHandler2(args) {
127299
127958
  const sessionID = info?.sessionID;
127300
127959
  const agent = info?.agent;
127301
127960
  const role = info?.role;
127961
+ if (sessionID && info?.finish === true) {
127962
+ invalidateContextWindowUsageCache(pluginContext, sessionID);
127963
+ }
127302
127964
  if (sessionID && role === "user") {
127303
127965
  const isCompactionMessage2 = agent ? isCompactionAgent5(agent) : false;
127304
127966
  if (agent && !isCompactionMessage2) {
@@ -132595,7 +133257,7 @@ class PostHog extends PostHogBackendClient {
132595
133257
  // package.json
132596
133258
  var package_default = {
132597
133259
  name: "oh-my-opencode",
132598
- version: "3.17.6",
133260
+ version: "3.17.8",
132599
133261
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
132600
133262
  main: "./dist/index.js",
132601
133263
  types: "dist/index.d.ts",
@@ -132675,17 +133337,17 @@ var package_default = {
132675
133337
  zod: "^4.3.0"
132676
133338
  },
132677
133339
  optionalDependencies: {
132678
- "oh-my-opencode-darwin-arm64": "3.17.6",
132679
- "oh-my-opencode-darwin-x64": "3.17.6",
132680
- "oh-my-opencode-darwin-x64-baseline": "3.17.6",
132681
- "oh-my-opencode-linux-arm64": "3.17.6",
132682
- "oh-my-opencode-linux-arm64-musl": "3.17.6",
132683
- "oh-my-opencode-linux-x64": "3.17.6",
132684
- "oh-my-opencode-linux-x64-baseline": "3.17.6",
132685
- "oh-my-opencode-linux-x64-musl": "3.17.6",
132686
- "oh-my-opencode-linux-x64-musl-baseline": "3.17.6",
132687
- "oh-my-opencode-windows-x64": "3.17.6",
132688
- "oh-my-opencode-windows-x64-baseline": "3.17.6"
133340
+ "oh-my-opencode-darwin-arm64": "3.17.8",
133341
+ "oh-my-opencode-darwin-x64": "3.17.8",
133342
+ "oh-my-opencode-darwin-x64-baseline": "3.17.8",
133343
+ "oh-my-opencode-linux-arm64": "3.17.8",
133344
+ "oh-my-opencode-linux-arm64-musl": "3.17.8",
133345
+ "oh-my-opencode-linux-x64": "3.17.8",
133346
+ "oh-my-opencode-linux-x64-baseline": "3.17.8",
133347
+ "oh-my-opencode-linux-x64-musl": "3.17.8",
133348
+ "oh-my-opencode-linux-x64-musl-baseline": "3.17.8",
133349
+ "oh-my-opencode-windows-x64": "3.17.8",
133350
+ "oh-my-opencode-windows-x64-baseline": "3.17.8"
132689
133351
  },
132690
133352
  overrides: {},
132691
133353
  trustedDependencies: [