@wolfx/oh-my-openagent 3.17.6-patch.1 → 3.17.10

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;
@@ -85184,17 +85308,21 @@ function createModelFallbackStateController(input) {
85184
85308
  function setSessionFallbackChain(sessionID, fallbackChain) {
85185
85309
  if (!sessionID)
85186
85310
  return;
85187
- sessionFallbackChains.set(sessionID, fallbackChain?.length ? fallbackChain : []);
85311
+ sessionFallbackChains.set(sessionID, fallbackChain?.length ? [...fallbackChain] : []);
85188
85312
  }
85189
85313
  function clearSessionFallbackChain(sessionID) {
85190
85314
  sessionFallbackChains.delete(sessionID);
85191
85315
  }
85316
+ function getSessionFallbackChain(sessionID) {
85317
+ const fallbackChain = sessionFallbackChains.get(sessionID);
85318
+ return fallbackChain ? [...fallbackChain] : undefined;
85319
+ }
85192
85320
  function setPendingModelFallback(sessionID, agentName, currentProviderID, currentModelID) {
85193
85321
  const agentKey = getAgentConfigKey(agentName);
85194
85322
  const requirements = AGENT_MODEL_REQUIREMENTS[agentKey];
85195
85323
  const fallbackChain = sessionFallbackChains.get(sessionID) ?? requirements?.fallbackChain;
85196
85324
  if (!fallbackChain?.length) {
85197
- log("[model-fallback] No fallback chain for agent: " + agentName + " (key: " + agentKey + ")");
85325
+ log(`[model-fallback] No fallback chain for agent: ${agentName} (key: ${agentKey})`);
85198
85326
  return false;
85199
85327
  }
85200
85328
  const existing = pendingModelFallbacks.get(sessionID);
@@ -85206,21 +85334,21 @@ function createModelFallbackStateController(input) {
85206
85334
  attemptCount: 0,
85207
85335
  pending: true
85208
85336
  });
85209
- log("[model-fallback] Set pending fallback for session: " + sessionID + ", agent: " + agentName);
85337
+ log(`[model-fallback] Set pending fallback for session: ${sessionID}, agent: ${agentName}`);
85210
85338
  return true;
85211
85339
  }
85212
85340
  if (existing.pending) {
85213
- log("[model-fallback] Pending fallback already armed for session: " + sessionID);
85341
+ log(`[model-fallback] Pending fallback already armed for session: ${sessionID}`);
85214
85342
  return false;
85215
85343
  }
85216
85344
  existing.providerID = currentProviderID;
85217
85345
  existing.modelID = currentModelID;
85218
85346
  existing.pending = true;
85219
85347
  if (existing.attemptCount >= existing.fallbackChain.length) {
85220
- log("[model-fallback] Fallback chain exhausted for session: " + sessionID);
85348
+ log(`[model-fallback] Fallback chain exhausted for session: ${sessionID}`);
85221
85349
  return false;
85222
85350
  }
85223
- log("[model-fallback] Re-armed pending fallback for session: " + sessionID);
85351
+ log(`[model-fallback] Re-armed pending fallback for session: ${sessionID}`);
85224
85352
  return true;
85225
85353
  }
85226
85354
  function getNextFallback2(sessionID) {
@@ -85230,7 +85358,7 @@ function createModelFallbackStateController(input) {
85230
85358
  const fallback = getNextReachableFallback(sessionID, state3);
85231
85359
  if (fallback)
85232
85360
  return fallback;
85233
- log("[model-fallback] No more fallbacks for session: " + sessionID);
85361
+ log(`[model-fallback] No more fallbacks for session: ${sessionID}`);
85234
85362
  pendingModelFallbacks.delete(sessionID);
85235
85363
  return null;
85236
85364
  }
@@ -85252,6 +85380,7 @@ function createModelFallbackStateController(input) {
85252
85380
  return {
85253
85381
  lastToastKey,
85254
85382
  setSessionFallbackChain,
85383
+ getSessionFallbackChain,
85255
85384
  clearSessionFallbackChain,
85256
85385
  setPendingModelFallback,
85257
85386
  getNextFallback: getNextFallback2,
@@ -85293,6 +85422,7 @@ function createModelFallbackHook(args) {
85293
85422
  return {
85294
85423
  lastToastKey: controller.lastToastKey,
85295
85424
  setSessionFallbackChain: controller.setSessionFallbackChain,
85425
+ getSessionFallbackChain: controller.getSessionFallbackChain,
85296
85426
  clearSessionFallbackChain: controller.clearSessionFallbackChain,
85297
85427
  setPendingModelFallback: controller.setPendingModelFallback,
85298
85428
  getNextFallback: controller.getNextFallback,
@@ -87604,13 +87734,6 @@ function readPackageVersion(packageJsonPath) {
87604
87734
  return pkg.version ?? null;
87605
87735
  }
87606
87736
  function getCachedVersion() {
87607
- for (const candidate of INSTALLED_PACKAGE_JSON_CANDIDATES) {
87608
- try {
87609
- if (fs12.existsSync(candidate)) {
87610
- return readPackageVersion(candidate);
87611
- }
87612
- } catch {}
87613
- }
87614
87737
  try {
87615
87738
  const currentDir = path10.dirname(fileURLToPath3(import.meta.url));
87616
87739
  const pkgPath = findPackageJsonUp(currentDir);
@@ -87620,6 +87743,13 @@ function getCachedVersion() {
87620
87743
  } catch (err) {
87621
87744
  log("[auto-update-checker] Failed to resolve version from current directory:", err);
87622
87745
  }
87746
+ for (const candidate of INSTALLED_PACKAGE_JSON_CANDIDATES) {
87747
+ try {
87748
+ if (fs12.existsSync(candidate)) {
87749
+ return readPackageVersion(candidate);
87750
+ }
87751
+ } catch {}
87752
+ }
87623
87753
  try {
87624
87754
  const execDir = path10.dirname(fs12.realpathSync(process.execPath));
87625
87755
  const pkgPath = findPackageJsonUp(execDir);
@@ -88547,54 +88677,8 @@ function createAgentUsageReminderHook(_ctx) {
88547
88677
  event: eventHandler
88548
88678
  };
88549
88679
  }
88550
- // src/agents/types.ts
88551
- function extractModelName(model) {
88552
- return model.includes("/") ? model.split("/").pop() ?? model : model;
88553
- }
88554
- function isGptModel(model) {
88555
- const modelName = extractModelName(model).toLowerCase();
88556
- return modelName.includes("gpt");
88557
- }
88558
- var GPT_NATIVE_SISYPHUS_RE = /gpt-5[.-](?:[4-9]|\d{2,})/i;
88559
- function isGptNativeSisyphusModel(model) {
88560
- const modelName = extractModelName(model).toLowerCase();
88561
- return GPT_NATIVE_SISYPHUS_RE.test(modelName);
88562
- }
88563
- function isGpt5_5Model(model) {
88564
- const modelName = extractModelName(model).toLowerCase();
88565
- return modelName.includes("gpt-5.5") || modelName.includes("gpt-5-5");
88566
- }
88567
- function isGpt5_3CodexModel(model) {
88568
- const modelName = extractModelName(model).toLowerCase();
88569
- return modelName.includes("gpt-5.3-codex") || modelName.includes("gpt-5-3-codex");
88570
- }
88571
- function isClaudeOpus47Model(model) {
88572
- const modelName = extractModelName(model).toLowerCase().replaceAll(".", "-");
88573
- return modelName.includes("claude-opus-4-7");
88574
- }
88575
- function isKimiK2Model(model) {
88576
- const modelName = extractModelName(model).toLowerCase();
88577
- if (modelName.includes("kimi"))
88578
- return true;
88579
- if (/k2[-.]?p[56]/.test(modelName))
88580
- return true;
88581
- return false;
88582
- }
88583
- var GEMINI_PROVIDERS = ["google/", "google-vertex/"];
88584
- function isGlmModel(model) {
88585
- const modelName = extractModelName(model).toLowerCase();
88586
- return modelName.includes("glm");
88587
- }
88588
- function isGeminiModel(model) {
88589
- if (GEMINI_PROVIDERS.some((prefix) => model.startsWith(prefix)))
88590
- return true;
88591
- if (model.startsWith("github-copilot/") && extractModelName(model).toLowerCase().startsWith("gemini"))
88592
- return true;
88593
- const modelName = extractModelName(model).toLowerCase();
88594
- return modelName.startsWith("gemini-");
88595
- }
88596
-
88597
88680
  // src/hooks/keyword-detector/ultrawork/source-detector.ts
88681
+ init_types();
88598
88682
  function isPlannerAgent(agentName) {
88599
88683
  if (!agentName)
88600
88684
  return false;
@@ -91455,6 +91539,7 @@ function createRalphLoopHook(ctx, options) {
91455
91539
  };
91456
91540
  }
91457
91541
  // src/hooks/no-sisyphus-gpt/hook.ts
91542
+ init_types();
91458
91543
  init_agent_display_names();
91459
91544
  var TOAST_TITLE = "NEVER Use Sisyphus with GPT";
91460
91545
  var TOAST_MESSAGE = [
@@ -91510,6 +91595,7 @@ function createNoSisyphusGptHook(ctx) {
91510
91595
  };
91511
91596
  }
91512
91597
  // src/hooks/no-hephaestus-non-gpt/hook.ts
91598
+ init_types();
91513
91599
  init_agent_display_names();
91514
91600
  var TOAST_TITLE2 = "NEVER Use Hephaestus with Non-GPT";
91515
91601
  var TOAST_MESSAGE2 = [
@@ -101328,7 +101414,10 @@ function findMessageByID(messages, messageID) {
101328
101414
  return messages.find((message) => message.info?.id === messageID);
101329
101415
  }
101330
101416
  async function resolveNoTextTailFromSession(args) {
101331
- const { client, sessionID, messageID, directory } = args;
101417
+ const { client, sessionID, messageID, directory, parts } = args;
101418
+ if (Array.isArray(parts)) {
101419
+ return isStepOnlyNoTextParts(parts);
101420
+ }
101332
101421
  try {
101333
101422
  const response = await client.session.messages({
101334
101423
  path: { id: sessionID },
@@ -101455,7 +101544,8 @@ function createPostCompactionDegradationMonitor(args) {
101455
101544
  client,
101456
101545
  sessionID: info.sessionID,
101457
101546
  messageID: info.id,
101458
- directory
101547
+ directory,
101548
+ parts: info.parts
101459
101549
  });
101460
101550
  if (!isNoTextTail) {
101461
101551
  postCompactionNoTextStreak.set(info.sessionID, 0);
@@ -101619,7 +101709,8 @@ function createPreemptiveCompactionHook(ctx, pluginConfig, modelCacheState) {
101619
101709
  compactedSessions.delete(info.sessionID);
101620
101710
  await postCompactionMonitor.onAssistantMessageUpdated({
101621
101711
  sessionID: info.sessionID,
101622
- id: info.id
101712
+ id: info.id,
101713
+ parts: info.parts
101623
101714
  });
101624
101715
  }
101625
101716
  };
@@ -102216,7 +102307,7 @@ function classifyErrorType(error48) {
102216
102307
  if (errorName?.includes("providermodelnotfounderror") || errorName?.includes("modelnotfounderror") || errorName?.includes("unknownerror") && /model\s+not\s+found/i.test(message)) {
102217
102308
  return "model_not_found";
102218
102309
  }
102219
- 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)) {
102310
+ 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)) {
102220
102311
  return "quota_exceeded";
102221
102312
  }
102222
102313
  return;
@@ -102244,8 +102335,7 @@ function isRetryableError(error48, retryOnErrors) {
102244
102335
  return true;
102245
102336
  }
102246
102337
  if (errorType === "quota_exceeded") {
102247
- const hasAutoRetrySignal = /retrying\s+in/i.test(message);
102248
- return hasAutoRetrySignal;
102338
+ return true;
102249
102339
  }
102250
102340
  if (statusCode && retryOnErrors.includes(statusCode)) {
102251
102341
  return true;
@@ -103334,6 +103424,19 @@ function extractFilePath(metadata) {
103334
103424
  }
103335
103425
  return;
103336
103426
  }
103427
+ function extractLineCount(metadata) {
103428
+ if (!metadata || typeof metadata !== "object") {
103429
+ return;
103430
+ }
103431
+ const objectMeta = metadata;
103432
+ const candidates = [objectMeta.lineCount, objectMeta.linesWritten, objectMeta.lines];
103433
+ for (const candidate of candidates) {
103434
+ if (typeof candidate === "number" && Number.isInteger(candidate) && candidate >= 0) {
103435
+ return candidate;
103436
+ }
103437
+ }
103438
+ return;
103439
+ }
103337
103440
  async function appendWriteHashlineOutput(output) {
103338
103441
  if (output.output.startsWith(WRITE_SUCCESS_MARKER)) {
103339
103442
  return;
@@ -103342,6 +103445,11 @@ async function appendWriteHashlineOutput(output) {
103342
103445
  if (outputLower.startsWith("error") || outputLower.includes("failed")) {
103343
103446
  return;
103344
103447
  }
103448
+ const metadataLineCount = extractLineCount(output.metadata);
103449
+ if (metadataLineCount !== undefined) {
103450
+ output.output = `${WRITE_SUCCESS_MARKER} ${metadataLineCount} lines written.`;
103451
+ return;
103452
+ }
103345
103453
  const filePath = extractFilePath(output.metadata);
103346
103454
  if (!filePath) {
103347
103455
  return;
@@ -122011,6 +122119,83 @@ async function formatFullSession(task, client2, options) {
122011
122119
  `);
122012
122120
  }
122013
122121
 
122122
+ // src/features/background-agent/error-classifier.ts
122123
+ function isRecord15(value) {
122124
+ return typeof value === "object" && value !== null;
122125
+ }
122126
+ function isAbortedSessionError(error92) {
122127
+ const message = getErrorText(error92);
122128
+ return message.toLowerCase().includes("aborted");
122129
+ }
122130
+ function getErrorText(error92) {
122131
+ if (!error92)
122132
+ return "";
122133
+ if (typeof error92 === "string")
122134
+ return error92;
122135
+ if (error92 instanceof Error) {
122136
+ return `${error92.name}: ${error92.message}`;
122137
+ }
122138
+ if (typeof error92 === "object" && error92 !== null) {
122139
+ if ("message" in error92 && typeof error92.message === "string") {
122140
+ return error92.message;
122141
+ }
122142
+ if ("name" in error92 && typeof error92.name === "string") {
122143
+ return error92.name;
122144
+ }
122145
+ }
122146
+ return "";
122147
+ }
122148
+ function extractErrorName2(error92) {
122149
+ if (isRecord15(error92) && typeof error92["name"] === "string")
122150
+ return error92["name"];
122151
+ if (error92 instanceof Error)
122152
+ return error92.name;
122153
+ return;
122154
+ }
122155
+ function extractErrorMessage(error92) {
122156
+ if (!error92)
122157
+ return;
122158
+ if (typeof error92 === "string")
122159
+ return error92;
122160
+ if (isRecord15(error92)) {
122161
+ const dataRaw = error92["data"];
122162
+ const candidates = [
122163
+ dataRaw,
122164
+ isRecord15(dataRaw) ? dataRaw["error"] : undefined,
122165
+ error92["error"],
122166
+ error92["cause"],
122167
+ error92
122168
+ ];
122169
+ for (const candidate of candidates) {
122170
+ if (typeof candidate === "string" && candidate.length > 0)
122171
+ return candidate;
122172
+ if (isRecord15(candidate) && typeof candidate["message"] === "string" && candidate["message"].length > 0) {
122173
+ return candidate["message"];
122174
+ }
122175
+ }
122176
+ }
122177
+ if (error92 instanceof Error)
122178
+ return error92.message;
122179
+ try {
122180
+ return JSON.stringify(error92);
122181
+ } catch {
122182
+ return String(error92);
122183
+ }
122184
+ }
122185
+ function getSessionErrorMessage(properties) {
122186
+ const errorRaw = properties["error"];
122187
+ if (!isRecord15(errorRaw))
122188
+ return;
122189
+ const dataRaw = errorRaw["data"];
122190
+ if (isRecord15(dataRaw)) {
122191
+ const message2 = dataRaw["message"];
122192
+ if (typeof message2 === "string")
122193
+ return message2;
122194
+ }
122195
+ const message = errorRaw["message"];
122196
+ return typeof message === "string" ? message : undefined;
122197
+ }
122198
+
122014
122199
  // src/tools/background-task/task-result-format.ts
122015
122200
  function getTimeString(value) {
122016
122201
  return typeof value === "string" ? value : "";
@@ -122057,6 +122242,19 @@ Session ID: ${task.sessionID}
122057
122242
  const timeB = getTimeString(b.info?.time);
122058
122243
  return timeA.localeCompare(timeB);
122059
122244
  });
122245
+ 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);
122246
+ if (sessionError) {
122247
+ return `Task Result
122248
+
122249
+ Task ID: ${task.id}
122250
+ Description: ${task.description}
122251
+ Duration: ${formatDuration(task.startedAt ?? new Date, task.completedAt)}
122252
+ Session ID: ${task.sessionID}
122253
+
122254
+ ---
122255
+
122256
+ Session error: ${sessionError}`;
122257
+ }
122060
122258
  const newMessages = consumeNewMessages(task.sessionID, sortedMessages);
122061
122259
  if (newMessages.length === 0) {
122062
122260
  const duration6 = formatDuration(task.startedAt ?? new Date, task.completedAt);
@@ -123984,6 +124182,18 @@ async function fetchSessionMessages(client2, sessionID) {
123984
124182
  const rawData = messagesResult?.data ?? messagesResult;
123985
124183
  return Array.isArray(rawData) ? rawData : [];
123986
124184
  }
124185
+ function getTerminalSessionError(messages) {
124186
+ const lastAssistant = [...messages].reverse().find((msg) => msg.info?.role === "assistant");
124187
+ const lastUser = [...messages].reverse().find((msg) => msg.info?.role === "user");
124188
+ if (lastUser?.info?.id && lastAssistant?.info?.id && lastAssistant.info.id <= lastUser.info.id) {
124189
+ return null;
124190
+ }
124191
+ if (!lastAssistant?.info || !("error" in lastAssistant.info)) {
124192
+ return null;
124193
+ }
124194
+ const errorMessage = extractErrorMessage(lastAssistant.info.error);
124195
+ return errorMessage && errorMessage.length > 0 ? errorMessage : "Session error";
124196
+ }
123987
124197
  function isSessionComplete(messages) {
123988
124198
  let lastUser;
123989
124199
  let lastAssistant;
@@ -124072,6 +124282,11 @@ Session ID: ${input.sessionID}`;
124072
124282
  if (input.anchorMessageCount !== undefined && messages.length <= input.anchorMessageCount) {
124073
124283
  continue;
124074
124284
  }
124285
+ const sessionError = getTerminalSessionError(messages);
124286
+ if (sessionError) {
124287
+ log("[task] Poll detected terminal session error", { sessionID: input.sessionID, sessionError });
124288
+ return sessionError;
124289
+ }
124075
124290
  if (isSessionComplete(messages)) {
124076
124291
  log("[task] Poll complete - terminal finish detected", { sessionID: input.sessionID, pollCount });
124077
124292
  break;
@@ -124840,7 +125055,8 @@ async function retrySyncPromptWithFallbacks(input) {
124840
125055
  if (!categoryModel || !fallbackChain || fallbackChain.length === 0) {
124841
125056
  return {
124842
125057
  promptError: initialError,
124843
- categoryModel
125058
+ categoryModel,
125059
+ fallbackState: undefined
124844
125060
  };
124845
125061
  }
124846
125062
  const fallbackState = {
@@ -124856,7 +125072,8 @@ async function retrySyncPromptWithFallbacks(input) {
124856
125072
  if (!nextFallback) {
124857
125073
  return {
124858
125074
  promptError: finalError,
124859
- categoryModel
125075
+ categoryModel,
125076
+ fallbackState
124860
125077
  };
124861
125078
  }
124862
125079
  const fallbackModel = toDelegatedModelConfig(nextFallback);
@@ -124864,7 +125081,8 @@ async function retrySyncPromptWithFallbacks(input) {
124864
125081
  if (!promptError) {
124865
125082
  return {
124866
125083
  promptError: null,
124867
- categoryModel: fallbackModel
125084
+ categoryModel: fallbackModel,
125085
+ fallbackState
124868
125086
  };
124869
125087
  }
124870
125088
  finalError = promptError;
@@ -124873,6 +125091,12 @@ async function retrySyncPromptWithFallbacks(input) {
124873
125091
  fallbackState.pending = true;
124874
125092
  }
124875
125093
  }
125094
+ function getNextSyncFallbackModel(sessionID, fallbackState) {
125095
+ if (!fallbackState)
125096
+ return null;
125097
+ const nextFallback = getNextReachableFallback(sessionID, fallbackState);
125098
+ return nextFallback ? toDelegatedModelConfig(nextFallback) : null;
125099
+ }
124876
125100
 
124877
125101
  // src/tools/delegate-task/sync-task.ts
124878
125102
  async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse, categoryModel, systemContent, modelInfo, fallbackChain, deps = syncTaskDeps) {
@@ -124911,26 +125135,50 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
124911
125135
  const sessionID = createSessionResult.sessionID;
124912
125136
  spawnReservation?.commit();
124913
125137
  syncSessionID = sessionID;
124914
- subagentSessions.add(sessionID);
124915
- syncSubagentSessions.add(sessionID);
124916
- setSessionAgent(sessionID, agentToUse);
124917
- executorCtx.modelFallbackControllerAccessor?.setSessionFallbackChain(sessionID, fallbackChain);
124918
- if (args.category) {
124919
- SessionCategoryRegistry.register(sessionID, args.category);
124920
- }
124921
- if (onSyncSessionCreated) {
124922
- log("[task] Invoking onSyncSessionCreated callback", { sessionID, parentID: parentContext.sessionID });
124923
- try {
124924
- await onSyncSessionCreated({
124925
- sessionID,
124926
- parentID: parentContext.sessionID,
124927
- title: args.description
124928
- });
124929
- } catch (error92) {
124930
- log("[task] onSyncSessionCreated callback failed", { error: String(error92) });
125138
+ const registerSyncSession = async (newSessionID) => {
125139
+ syncSessionID = newSessionID;
125140
+ subagentSessions.add(newSessionID);
125141
+ syncSubagentSessions.add(newSessionID);
125142
+ setSessionAgent(newSessionID, agentToUse);
125143
+ executorCtx.modelFallbackControllerAccessor?.setSessionFallbackChain(newSessionID, fallbackChain);
125144
+ if (args.category) {
125145
+ SessionCategoryRegistry.register(newSessionID, args.category);
125146
+ }
125147
+ if (onSyncSessionCreated) {
125148
+ log("[task] Invoking onSyncSessionCreated callback", { sessionID: newSessionID, parentID: parentContext.sessionID });
125149
+ try {
125150
+ await onSyncSessionCreated({
125151
+ sessionID: newSessionID,
125152
+ parentID: parentContext.sessionID,
125153
+ title: args.description
125154
+ });
125155
+ } catch (error92) {
125156
+ log("[task] onSyncSessionCreated callback failed", { error: String(error92) });
125157
+ }
125158
+ await new Promise((r) => setTimeout(r, 200));
124931
125159
  }
124932
- await new Promise((r) => setTimeout(r, 200));
124933
- }
125160
+ };
125161
+ const publishSyncMetadata = async (currentSessionID, currentModel, currentTaskId, spawnDepth) => {
125162
+ await publishToolMetadata(ctx, {
125163
+ title: args.description,
125164
+ metadata: {
125165
+ prompt: args.prompt,
125166
+ agent: agentToUse,
125167
+ category: args.category,
125168
+ ...args.requested_subagent_type !== undefined ? { requested_subagent_type: args.requested_subagent_type } : {},
125169
+ load_skills: args.load_skills,
125170
+ description: args.description,
125171
+ run_in_background: args.run_in_background,
125172
+ taskId: currentSessionID,
125173
+ sessionId: currentSessionID,
125174
+ sync: true,
125175
+ spawnDepth,
125176
+ command: args.command,
125177
+ model: resolveMetadataModel(currentModel, parentContext.model)
125178
+ }
125179
+ });
125180
+ };
125181
+ await registerSyncSession(sessionID);
124934
125182
  taskId = `sync_${sessionID.slice(0, 8)}`;
124935
125183
  const startTime = new Date;
124936
125184
  if (toastManager) {
@@ -124945,25 +125193,7 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
124945
125193
  modelInfo
124946
125194
  });
124947
125195
  }
124948
- const syncTaskMeta = {
124949
- title: args.description,
124950
- metadata: {
124951
- prompt: args.prompt,
124952
- agent: agentToUse,
124953
- category: args.category,
124954
- ...args.requested_subagent_type !== undefined ? { requested_subagent_type: args.requested_subagent_type } : {},
124955
- load_skills: args.load_skills,
124956
- description: args.description,
124957
- run_in_background: args.run_in_background,
124958
- taskId: sessionID,
124959
- sessionId: sessionID,
124960
- sync: true,
124961
- spawnDepth: spawnContext.childDepth,
124962
- command: args.command,
124963
- model: resolveMetadataModel(categoryModel, parentContext.model)
124964
- }
124965
- };
124966
- await publishToolMetadata(ctx, syncTaskMeta);
125196
+ await publishSyncMetadata(sessionID, categoryModel, taskId, spawnContext.childDepth);
124967
125197
  const syncPromptInput = {
124968
125198
  sessionID,
124969
125199
  agentToUse,
@@ -124974,55 +125204,106 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
124974
125204
  sisyphusAgentConfig: executorCtx.sisyphusAgentConfig
124975
125205
  };
124976
125206
  let effectiveCategoryModel = categoryModel;
124977
- let promptError = await deps.sendSyncPrompt(client2, {
124978
- ...syncPromptInput,
124979
- categoryModel: effectiveCategoryModel
124980
- });
124981
- if (promptError) {
124982
- const promptResult = await retrySyncPromptWithFallbacks({
124983
- sessionID,
124984
- initialError: promptError,
124985
- categoryModel: effectiveCategoryModel,
124986
- fallbackChain,
124987
- sendPrompt: async (fallbackModel) => {
124988
- return deps.sendSyncPrompt(client2, {
124989
- ...syncPromptInput,
124990
- categoryModel: fallbackModel
125207
+ let fallbackState = effectiveCategoryModel && fallbackChain?.length ? {
125208
+ providerID: effectiveCategoryModel.providerID,
125209
+ modelID: effectiveCategoryModel.modelID,
125210
+ fallbackChain,
125211
+ attemptCount: 0,
125212
+ pending: true
125213
+ } : undefined;
125214
+ let activeSessionID = sessionID;
125215
+ const cleanupRetrySession = (currentSessionID) => {
125216
+ subagentSessions.delete(currentSessionID);
125217
+ syncSubagentSessions.delete(currentSessionID);
125218
+ executorCtx.modelFallbackControllerAccessor?.clearSessionFallbackChain(currentSessionID);
125219
+ SessionCategoryRegistry.remove(currentSessionID);
125220
+ };
125221
+ try {
125222
+ while (true) {
125223
+ let promptError = await deps.sendSyncPrompt(client2, {
125224
+ ...syncPromptInput,
125225
+ sessionID: activeSessionID,
125226
+ categoryModel: effectiveCategoryModel
125227
+ });
125228
+ if (promptError) {
125229
+ const promptResult = await retrySyncPromptWithFallbacks({
125230
+ sessionID: activeSessionID,
125231
+ initialError: promptError,
125232
+ categoryModel: effectiveCategoryModel,
125233
+ fallbackChain,
125234
+ sendPrompt: async (fallbackModel) => {
125235
+ return deps.sendSyncPrompt(client2, {
125236
+ ...syncPromptInput,
125237
+ sessionID: activeSessionID,
125238
+ categoryModel: fallbackModel
125239
+ });
125240
+ }
124991
125241
  });
125242
+ promptError = promptResult.promptError;
125243
+ effectiveCategoryModel = promptResult.categoryModel;
125244
+ fallbackState = promptResult.fallbackState ?? fallbackState;
125245
+ if (promptError) {
125246
+ return promptError;
125247
+ }
124992
125248
  }
124993
- });
124994
- promptError = promptResult.promptError;
124995
- effectiveCategoryModel = promptResult.categoryModel;
124996
- if (promptError) {
124997
- return promptError;
124998
- }
124999
- }
125000
- try {
125001
- const pollError = await deps.pollSyncSession(ctx, client2, {
125002
- sessionID,
125003
- agentToUse,
125004
- toastManager,
125005
- taskId
125006
- }, syncPollTimeoutMs);
125007
- if (pollError) {
125008
- return pollError;
125009
- }
125010
- const result = await deps.fetchSyncResult(client2, sessionID);
125011
- if (!result.ok) {
125012
- return result.error;
125013
- }
125014
- const duration5 = formatDuration2(startTime);
125015
- const actualModelStr = effectiveCategoryModel ? `${effectiveCategoryModel.providerID}/${effectiveCategoryModel.modelID}` : undefined;
125016
- const parentModelStr = parentContext.model ? `${parentContext.model.providerID}/${parentContext.model.modelID}` : undefined;
125017
- let modelRoutingNote = "";
125018
- if (actualModelStr && parentModelStr && actualModelStr !== parentModelStr) {
125019
- modelRoutingNote = `
125249
+ const pollError = await deps.pollSyncSession(ctx, client2, {
125250
+ sessionID: activeSessionID,
125251
+ agentToUse,
125252
+ toastManager,
125253
+ taskId
125254
+ }, syncPollTimeoutMs);
125255
+ if (pollError) {
125256
+ const nextFallbackModel = shouldRetryError({ message: pollError }) ? getNextSyncFallbackModel(activeSessionID, fallbackState) : null;
125257
+ if (!nextFallbackModel) {
125258
+ return pollError;
125259
+ }
125260
+ cleanupRetrySession(activeSessionID);
125261
+ const retrySessionResult = await deps.createSyncSession(client2, {
125262
+ parentSessionID: parentContext.sessionID,
125263
+ agentToUse,
125264
+ description: args.description,
125265
+ defaultDirectory: directory
125266
+ });
125267
+ if (!retrySessionResult.ok) {
125268
+ return retrySessionResult.error;
125269
+ }
125270
+ activeSessionID = retrySessionResult.sessionID;
125271
+ effectiveCategoryModel = nextFallbackModel;
125272
+ await registerSyncSession(activeSessionID);
125273
+ if (toastManager && taskId) {
125274
+ toastManager.addTask({
125275
+ id: taskId,
125276
+ sessionID: activeSessionID,
125277
+ description: args.description,
125278
+ agent: agentToUse,
125279
+ isBackground: false,
125280
+ category: args.category,
125281
+ skills: args.load_skills,
125282
+ modelInfo
125283
+ });
125284
+ }
125285
+ if (taskId) {
125286
+ await publishSyncMetadata(activeSessionID, effectiveCategoryModel, taskId, spawnContext.childDepth);
125287
+ }
125288
+ continue;
125289
+ }
125290
+ const result = await deps.fetchSyncResult(client2, activeSessionID);
125291
+ if (!result.ok) {
125292
+ return result.error;
125293
+ }
125294
+ const duration5 = formatDuration2(startTime);
125295
+ const actualModelStr = effectiveCategoryModel ? `${effectiveCategoryModel.providerID}/${effectiveCategoryModel.modelID}` : undefined;
125296
+ const parentModelStr = parentContext.model ? `${parentContext.model.providerID}/${parentContext.model.modelID}` : undefined;
125297
+ let modelRoutingNote = "";
125298
+ if (actualModelStr && parentModelStr && actualModelStr !== parentModelStr) {
125299
+ modelRoutingNote = `
125020
125300
  \u26A0\uFE0F Model routing: parent used ${parentModelStr}, this subagent used ${actualModelStr} (via category: ${args.category ?? "unknown"})`;
125021
- } else if (actualModelStr) {
125022
- modelRoutingNote = `
125301
+ } else if (actualModelStr) {
125302
+ modelRoutingNote = `
125023
125303
  Model: ${actualModelStr}${args.category ? ` (category: ${args.category})` : ""}`;
125024
- }
125025
- return `Task completed in ${duration5}.
125304
+ }
125305
+ await publishSyncMetadata(activeSessionID, effectiveCategoryModel, taskId, spawnContext.childDepth);
125306
+ return `Task completed in ${duration5}.
125026
125307
 
125027
125308
  Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}${modelRoutingNote}
125028
125309
 
@@ -125031,11 +125312,12 @@ Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}${mod
125031
125312
  ${result.textContent || "(No text output)"}
125032
125313
 
125033
125314
  ${buildTaskMetadataBlock({
125034
- sessionId: sessionID,
125035
- taskId: sessionID,
125036
- agent: agentToUse,
125037
- category: args.category
125038
- })}`;
125315
+ sessionId: activeSessionID,
125316
+ taskId: activeSessionID,
125317
+ agent: agentToUse,
125318
+ category: args.category
125319
+ })}`;
125320
+ }
125039
125321
  } finally {
125040
125322
  if (toastManager && taskId !== undefined) {
125041
125323
  toastManager.removeTask(taskId);
@@ -125114,6 +125396,7 @@ function resolveCategoryConfig(categoryName, options) {
125114
125396
  }
125115
125397
 
125116
125398
  // src/tools/delegate-task/category-resolver.ts
125399
+ init_constants2();
125117
125400
  init_plugin_identity();
125118
125401
 
125119
125402
  // src/tools/delegate-task/available-models.ts
@@ -125338,6 +125621,19 @@ function applyCategoryParams(base, config4) {
125338
125621
  result.thinking = config4.thinking;
125339
125622
  return result;
125340
125623
  }
125624
+ function resolveCategoryPromptAppendForModel(categoryName, actualModel, staticPromptAppend, userPromptAppend) {
125625
+ const dynamicResolver = CATEGORY_PROMPT_APPEND_RESOLVERS[categoryName];
125626
+ if (!dynamicResolver) {
125627
+ return staticPromptAppend || undefined;
125628
+ }
125629
+ const dynamicBase = dynamicResolver(actualModel);
125630
+ if (!userPromptAppend) {
125631
+ return dynamicBase || undefined;
125632
+ }
125633
+ return dynamicBase ? `${dynamicBase}
125634
+
125635
+ ${userPromptAppend}` : userPromptAppend;
125636
+ }
125341
125637
  async function resolveCategoryExecution(args, executorCtx, inheritedModel, systemDefaultModel) {
125342
125638
  const { client: client2, userCategories, sisyphusJuniorModel } = executorCtx;
125343
125639
  const categoryName = args.category;
@@ -125467,7 +125763,7 @@ Available categories: ${allCategoryNames}`
125467
125763
  const parsedModel = parseModelString(actualModel);
125468
125764
  categoryModel = parsedModel ?? undefined;
125469
125765
  }
125470
- const categoryPromptAppend = resolved.promptAppend || undefined;
125766
+ const categoryPromptAppend = resolveCategoryPromptAppendForModel(args.category, actualModel, resolved.promptAppend, userCategories?.[args.category]?.prompt_append);
125471
125767
  if (!categoryModel && !actualModel && !isModelResolutionSkipped) {
125472
125768
  const categoryNames = Object.keys(enabledCategories);
125473
125769
  return {
@@ -129174,6 +129470,43 @@ function formatDuration3(start, end) {
129174
129470
  }
129175
129471
 
129176
129472
  // src/features/background-agent/background-task-notification-template.ts
129473
+ function formatAttemptModel(attempt) {
129474
+ if (attempt.providerID && attempt.modelID) {
129475
+ return `${attempt.providerID}/${attempt.modelID}`;
129476
+ }
129477
+ if (attempt.modelID) {
129478
+ return attempt.modelID;
129479
+ }
129480
+ if (attempt.providerID) {
129481
+ return attempt.providerID;
129482
+ }
129483
+ return "unknown-model";
129484
+ }
129485
+ function formatAttemptTimeline(task) {
129486
+ if (!task.attempts || task.attempts.length <= 1) {
129487
+ return "";
129488
+ }
129489
+ const lines = task.attempts.map((attempt) => {
129490
+ const attemptLines = [
129491
+ ` - Attempt ${attempt.attemptNumber} \u2014 ${attempt.status.toUpperCase()} \u2014 ${formatAttemptModel(attempt)} \u2014 ${attempt.sessionID ?? "unknown"}`
129492
+ ];
129493
+ if (attempt.status !== "completed" && attempt.error) {
129494
+ attemptLines.push(` Error: ${attempt.error}`);
129495
+ }
129496
+ return attemptLines.join(`
129497
+ `);
129498
+ }).join(`
129499
+ `);
129500
+ return `Background task attempts:
129501
+ ${lines}`;
129502
+ }
129503
+ function formatTaskSummaryLine(task) {
129504
+ const baseLine = `- \`${task.id}\`: ${task.description || task.id}`;
129505
+ const statusSuffix = task.status === "completed" ? "" : ` [${task.status.toUpperCase()}]${task.error ? ` - ${task.error}` : ""}`;
129506
+ const timeline = formatAttemptTimeline(task);
129507
+ return `${baseLine}${statusSuffix}${timeline ? `
129508
+ ${timeline}` : ""}`;
129509
+ }
129177
129510
  function buildBackgroundTaskNotificationText(input) {
129178
129511
  const { task, duration: duration5, statusText, allComplete, remainingCount, completedTasks } = input;
129179
129512
  const safeDescription = (t) => t.description || t.id;
@@ -129182,9 +129515,9 @@ function buildBackgroundTaskNotificationText(input) {
129182
129515
  if (allComplete) {
129183
129516
  const succeededTasks = completedTasks.filter((t) => t.status === "completed");
129184
129517
  const failedTasks = completedTasks.filter((t) => t.status !== "completed");
129185
- const succeededText = succeededTasks.length > 0 ? succeededTasks.map((t) => `- \`${t.id}\`: ${safeDescription(t)}`).join(`
129518
+ const succeededText = succeededTasks.length > 0 ? succeededTasks.map((t) => formatTaskSummaryLine(t)).join(`
129186
129519
  `) : "";
129187
- const failedText = failedTasks.length > 0 ? failedTasks.map((t) => `- \`${t.id}\`: ${safeDescription(t)} [${t.status.toUpperCase()}]${t.error ? ` - ${t.error}` : ""}`).join(`
129520
+ const failedText = failedTasks.length > 0 ? failedTasks.map((t) => formatTaskSummaryLine(t)).join(`
129188
129521
  `) : "";
129189
129522
  const hasFailures = failedTasks.length > 0;
129190
129523
  const header = hasFailures ? `[ALL BACKGROUND TASKS FINISHED - ${failedTasks.length} FAILED]` : "[ALL BACKGROUND TASKS COMPLETE]";
@@ -129201,7 +129534,7 @@ ${failedText}
129201
129534
  `;
129202
129535
  }
129203
129536
  if (!body) {
129204
- body = `- \`${task.id}\`: ${safeDescription(task)} [${task.status.toUpperCase()}]${task.error ? ` - ${task.error}` : ""}
129537
+ body = `${formatTaskSummaryLine(task)}
129205
129538
  `;
129206
129539
  }
129207
129540
  return `<system-reminder>
@@ -129228,83 +129561,6 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
129228
129561
  </system-reminder>`;
129229
129562
  }
129230
129563
 
129231
- // src/features/background-agent/error-classifier.ts
129232
- function isRecord15(value) {
129233
- return typeof value === "object" && value !== null;
129234
- }
129235
- function isAbortedSessionError(error92) {
129236
- const message = getErrorText(error92);
129237
- return message.toLowerCase().includes("aborted");
129238
- }
129239
- function getErrorText(error92) {
129240
- if (!error92)
129241
- return "";
129242
- if (typeof error92 === "string")
129243
- return error92;
129244
- if (error92 instanceof Error) {
129245
- return `${error92.name}: ${error92.message}`;
129246
- }
129247
- if (typeof error92 === "object" && error92 !== null) {
129248
- if ("message" in error92 && typeof error92.message === "string") {
129249
- return error92.message;
129250
- }
129251
- if ("name" in error92 && typeof error92.name === "string") {
129252
- return error92.name;
129253
- }
129254
- }
129255
- return "";
129256
- }
129257
- function extractErrorName2(error92) {
129258
- if (isRecord15(error92) && typeof error92["name"] === "string")
129259
- return error92["name"];
129260
- if (error92 instanceof Error)
129261
- return error92.name;
129262
- return;
129263
- }
129264
- function extractErrorMessage(error92) {
129265
- if (!error92)
129266
- return;
129267
- if (typeof error92 === "string")
129268
- return error92;
129269
- if (error92 instanceof Error)
129270
- return error92.message;
129271
- if (isRecord15(error92)) {
129272
- const dataRaw = error92["data"];
129273
- const candidates = [
129274
- error92,
129275
- dataRaw,
129276
- error92["error"],
129277
- isRecord15(dataRaw) ? dataRaw["error"] : undefined,
129278
- error92["cause"]
129279
- ];
129280
- for (const candidate of candidates) {
129281
- if (typeof candidate === "string" && candidate.length > 0)
129282
- return candidate;
129283
- if (isRecord15(candidate) && typeof candidate["message"] === "string" && candidate["message"].length > 0) {
129284
- return candidate["message"];
129285
- }
129286
- }
129287
- }
129288
- try {
129289
- return JSON.stringify(error92);
129290
- } catch {
129291
- return String(error92);
129292
- }
129293
- }
129294
- function getSessionErrorMessage(properties) {
129295
- const errorRaw = properties["error"];
129296
- if (!isRecord15(errorRaw))
129297
- return;
129298
- const dataRaw = errorRaw["data"];
129299
- if (isRecord15(dataRaw)) {
129300
- const message2 = dataRaw["message"];
129301
- if (typeof message2 === "string")
129302
- return message2;
129303
- }
129304
- const message = errorRaw["message"];
129305
- return typeof message === "string" ? message : undefined;
129306
- }
129307
-
129308
129564
  // src/features/background-agent/abort-with-timeout.ts
129309
129565
  async function abortWithTimeout(client2, sessionID, timeoutMs = 1e4) {
129310
129566
  let timeoutHandle;
@@ -129332,9 +129588,138 @@ async function abortWithTimeout(client2, sessionID, timeoutMs = 1e4) {
129332
129588
  }
129333
129589
  }
129334
129590
 
129591
+ // src/features/background-agent/attempt-lifecycle.ts
129592
+ function toAttemptModel(model) {
129593
+ return {
129594
+ providerID: model?.providerID,
129595
+ modelID: model?.modelID,
129596
+ variant: model?.variant
129597
+ };
129598
+ }
129599
+ function toTaskModel(attempt) {
129600
+ if (!attempt.providerID || !attempt.modelID) {
129601
+ return;
129602
+ }
129603
+ return {
129604
+ providerID: attempt.providerID,
129605
+ modelID: attempt.modelID,
129606
+ ...attempt.variant ? { variant: attempt.variant } : {}
129607
+ };
129608
+ }
129609
+ function getAttemptIndex(task, attemptID) {
129610
+ return task.attempts?.findIndex((attempt) => attempt.attemptID === attemptID) ?? -1;
129611
+ }
129612
+ function getAttempt(task, attemptID) {
129613
+ const index = getAttemptIndex(task, attemptID);
129614
+ return index === -1 ? undefined : task.attempts?.[index];
129615
+ }
129616
+ function isTerminalStatus(status) {
129617
+ return status === "completed" || status === "error" || status === "cancelled" || status === "interrupt";
129618
+ }
129619
+ function getCurrentAttempt(task) {
129620
+ if (!task.currentAttemptID) {
129621
+ return;
129622
+ }
129623
+ return getAttempt(task, task.currentAttemptID);
129624
+ }
129625
+ function ensureCurrentAttempt(task, model = task.model) {
129626
+ const existingAttempt = getCurrentAttempt(task);
129627
+ if (existingAttempt) {
129628
+ return existingAttempt;
129629
+ }
129630
+ const attempt = {
129631
+ attemptID: `att_${crypto.randomUUID().slice(0, 8)}`,
129632
+ attemptNumber: (task.attempts?.length ?? 0) + 1,
129633
+ sessionID: task.sessionID,
129634
+ ...toAttemptModel(model),
129635
+ status: task.status,
129636
+ error: task.error,
129637
+ startedAt: task.startedAt,
129638
+ completedAt: task.completedAt
129639
+ };
129640
+ task.attempts = [...task.attempts ?? [], attempt];
129641
+ task.currentAttemptID = attempt.attemptID;
129642
+ return attempt;
129643
+ }
129644
+ function projectTaskFromCurrentAttempt(task) {
129645
+ const currentAttempt = getCurrentAttempt(task);
129646
+ if (!currentAttempt) {
129647
+ return task;
129648
+ }
129649
+ task.status = currentAttempt.status;
129650
+ task.sessionID = currentAttempt.sessionID;
129651
+ task.startedAt = currentAttempt.startedAt;
129652
+ task.completedAt = currentAttempt.completedAt;
129653
+ task.error = currentAttempt.error;
129654
+ task.model = toTaskModel(currentAttempt);
129655
+ return task;
129656
+ }
129657
+ function startAttempt(task, model) {
129658
+ const attempt = {
129659
+ attemptID: `att_${crypto.randomUUID().slice(0, 8)}`,
129660
+ attemptNumber: (task.attempts?.length ?? 0) + 1,
129661
+ ...toAttemptModel(model),
129662
+ status: "pending"
129663
+ };
129664
+ task.attempts = [...task.attempts ?? [], attempt];
129665
+ task.currentAttemptID = attempt.attemptID;
129666
+ task.status = "pending";
129667
+ task.sessionID = undefined;
129668
+ task.startedAt = undefined;
129669
+ task.completedAt = undefined;
129670
+ task.error = undefined;
129671
+ task.model = model;
129672
+ return attempt;
129673
+ }
129674
+ function bindAttemptSession(task, attemptID, sessionID, model) {
129675
+ ensureCurrentAttempt(task, model);
129676
+ if (task.currentAttemptID !== attemptID) {
129677
+ return;
129678
+ }
129679
+ const attempt = getAttempt(task, attemptID);
129680
+ if (!attempt || isTerminalStatus(attempt.status)) {
129681
+ return;
129682
+ }
129683
+ attempt.sessionID = sessionID;
129684
+ attempt.status = "running";
129685
+ attempt.startedAt = new Date;
129686
+ attempt.completedAt = undefined;
129687
+ attempt.error = undefined;
129688
+ attempt.providerID = model?.providerID ?? attempt.providerID;
129689
+ attempt.modelID = model?.modelID ?? attempt.modelID;
129690
+ attempt.variant = model?.variant ?? attempt.variant;
129691
+ return getCurrentAttempt(projectTaskFromCurrentAttempt(task));
129692
+ }
129693
+ function finalizeAttempt(task, attemptID, status, error92) {
129694
+ const attempt = getAttempt(task, attemptID);
129695
+ if (!attempt) {
129696
+ return;
129697
+ }
129698
+ attempt.status = status;
129699
+ attempt.completedAt = new Date;
129700
+ attempt.error = error92;
129701
+ if (task.currentAttemptID === attemptID) {
129702
+ projectTaskFromCurrentAttempt(task);
129703
+ }
129704
+ return attempt;
129705
+ }
129706
+ function scheduleRetryAttempt(task, failedAttemptID, nextModel, error92) {
129707
+ const failedAttempt = finalizeAttempt(task, failedAttemptID, "error", error92);
129708
+ if (!failedAttempt || task.currentAttemptID !== failedAttemptID) {
129709
+ return;
129710
+ }
129711
+ return startAttempt(task, nextModel);
129712
+ }
129713
+ function findAttemptBySession(task, sessionID) {
129714
+ return task.attempts?.find((attempt) => attempt.sessionID === sessionID);
129715
+ }
129716
+
129335
129717
  // src/features/background-agent/fallback-retry-handler.ts
129718
+ function canonicalizeModelID2(modelID) {
129719
+ return modelID.toLowerCase().replace(/\./g, "-");
129720
+ }
129336
129721
  async function tryFallbackRetry(args) {
129337
- const { task, errorInfo, source, concurrencyManager, client: client2, idleDeferralTimers, queuesByKey, processKey } = args;
129722
+ const { task, errorInfo, source, concurrencyManager, client: client2, idleDeferralTimers, queuesByKey, processKey, onRetrying } = args;
129338
129723
  const fallbackChain = task.fallbackChain;
129339
129724
  const canRetry = shouldRetryError(errorInfo) && fallbackChain && fallbackChain.length > 0 && hasMoreFallbacks(fallbackChain, task.attemptCount ?? 0);
129340
129725
  if (!canRetry)
@@ -129354,6 +129739,7 @@ async function tryFallbackRetry(args) {
129354
129739
  };
129355
129740
  let selectedAttemptCount = attemptCount;
129356
129741
  let nextFallback;
129742
+ let nextProviderID;
129357
129743
  while (fallbackChain && selectedAttemptCount < fallbackChain.length) {
129358
129744
  const candidate = getNextFallback(fallbackChain, selectedAttemptCount);
129359
129745
  if (!candidate)
@@ -129368,12 +129754,25 @@ async function tryFallbackRetry(args) {
129368
129754
  });
129369
129755
  continue;
129370
129756
  }
129757
+ const candidateProviderID = selectFallbackProvider(candidate.providers, task.model?.providerID);
129758
+ const candidateModelID = transformModelForProvider(candidateProviderID, candidate.model);
129759
+ const isNoOpFallback = !!task.model && candidateProviderID.toLowerCase() === task.model.providerID.toLowerCase() && canonicalizeModelID2(candidateModelID) === canonicalizeModelID2(task.model.modelID);
129760
+ if (isNoOpFallback) {
129761
+ log("[background-agent] Skipping no-op fallback:", {
129762
+ taskId: task.id,
129763
+ source,
129764
+ model: candidate.model,
129765
+ providers: candidate.providers
129766
+ });
129767
+ continue;
129768
+ }
129371
129769
  nextFallback = candidate;
129770
+ nextProviderID = candidateProviderID;
129372
129771
  break;
129373
129772
  }
129374
129773
  if (!nextFallback)
129375
129774
  return false;
129376
- const providerID = selectFallbackProvider(nextFallback.providers, task.model?.providerID);
129775
+ const providerID = nextProviderID ?? selectFallbackProvider(nextFallback.providers, task.model?.providerID);
129377
129776
  log("[background-agent] Retryable error, attempting fallback:", {
129378
129777
  taskId: task.id,
129379
129778
  source,
@@ -129392,18 +129791,34 @@ async function tryFallbackRetry(args) {
129392
129791
  idleDeferralTimers.delete(task.id);
129393
129792
  }
129394
129793
  const previousSessionID = task.sessionID;
129395
- task.attemptCount = selectedAttemptCount;
129794
+ const previousModel = task.model;
129396
129795
  const transformedModelId = transformModelForProvider(providerID, nextFallback.model);
129397
- task.model = {
129796
+ const nextModel = {
129398
129797
  providerID,
129399
129798
  modelID: transformedModelId,
129400
129799
  variant: nextFallback.variant
129401
129800
  };
129402
- task.status = "pending";
129403
- task.sessionID = undefined;
129404
- task.startedAt = undefined;
129801
+ task.attemptCount = selectedAttemptCount;
129802
+ const failedAttemptID = ensureCurrentAttempt(task, previousModel).attemptID;
129803
+ const nextAttempt = failedAttemptID ? scheduleRetryAttempt(task, failedAttemptID, nextModel, errorInfo.message) : undefined;
129804
+ if (!nextAttempt) {
129805
+ return false;
129806
+ }
129405
129807
  task.queuedAt = new Date;
129406
- task.error = undefined;
129808
+ task.retryNotification = {
129809
+ previousSessionID,
129810
+ failedModel: previousModel ? `${previousModel.providerID}/${previousModel.modelID}` : undefined,
129811
+ failedError: errorInfo.message,
129812
+ nextModel: `${providerID}/${transformedModelId}`
129813
+ };
129814
+ onRetrying?.({
129815
+ task,
129816
+ source,
129817
+ previousSessionID,
129818
+ failedModel: task.retryNotification.failedModel,
129819
+ failedError: errorInfo.message,
129820
+ nextModel: `${providerID}/${transformedModelId}`
129821
+ });
129407
129822
  const key = task.model ? `${task.model.providerID}/${task.model.modelID}` : task.agent;
129408
129823
  const queue = queuesByKey.get(key) ?? [];
129409
129824
  const retryInput = {
@@ -129415,7 +129830,7 @@ async function tryFallbackRetry(args) {
129415
129830
  parentModel: task.parentModel,
129416
129831
  parentAgent: task.parentAgent,
129417
129832
  parentTools: task.parentTools,
129418
- model: task.model,
129833
+ model: nextModel,
129419
129834
  fallbackChain: task.fallbackChain,
129420
129835
  category: task.category,
129421
129836
  isUnstableAgent: task.isUnstableAgent
@@ -129423,7 +129838,7 @@ async function tryFallbackRetry(args) {
129423
129838
  if (previousSessionID) {
129424
129839
  await abortWithTimeout(client2, previousSessionID).catch(() => {});
129425
129840
  }
129426
- queue.push({ task, input: retryInput });
129841
+ queue.push({ task, input: retryInput, attemptID: nextAttempt.attemptID });
129427
129842
  queuesByKey.set(key, queue);
129428
129843
  processKey(key);
129429
129844
  return true;
@@ -130041,10 +130456,37 @@ function resolveMessagePartInfo(properties) {
130041
130456
  }
130042
130457
  return properties;
130043
130458
  }
130459
+ function formatAttemptModelSummary(attempt) {
130460
+ if (!attempt?.providerID || !attempt.modelID) {
130461
+ return;
130462
+ }
130463
+ return `${attempt.providerID}/${attempt.modelID}`;
130464
+ }
130465
+ function getPreviousAttempt(task, attemptID) {
130466
+ if (!attemptID || !task.attempts || task.attempts.length === 0) {
130467
+ return;
130468
+ }
130469
+ const attemptIndex = task.attempts.findIndex((attempt) => attempt.attemptID === attemptID);
130470
+ if (attemptIndex <= 0) {
130471
+ return;
130472
+ }
130473
+ return task.attempts[attemptIndex - 1];
130474
+ }
130475
+ function cloneAttempts(task) {
130476
+ if (!task.attempts) {
130477
+ return;
130478
+ }
130479
+ return task.attempts.map((attempt) => ({ ...attempt }));
130480
+ }
130481
+ function buildLocalSessionUrl(directory, sessionID) {
130482
+ const encodedDirectory = Buffer.from(directory).toString("base64url");
130483
+ return `http://127.0.0.1:4096/${encodedDirectory}/session/${sessionID}`;
130484
+ }
130044
130485
  var MAX_TASK_REMOVAL_RESCHEDULES = 6;
130045
130486
 
130046
130487
  class BackgroundManager {
130047
130488
  tasks;
130489
+ tasksByParentSession;
130048
130490
  notifications;
130049
130491
  pendingNotifications;
130050
130492
  pendingByParent;
@@ -130069,10 +130511,12 @@ class BackgroundManager {
130069
130511
  rootDescendantCounts;
130070
130512
  preStartDescendantReservations;
130071
130513
  enableParentSessionNotifications;
130514
+ modelFallbackControllerAccessor;
130072
130515
  taskHistory = new TaskHistory;
130073
130516
  cachedCircuitBreakerSettings;
130074
130517
  constructor(ctx, config4, options) {
130075
130518
  this.tasks = new Map;
130519
+ this.tasksByParentSession = new Map;
130076
130520
  this.notifications = new Map;
130077
130521
  this.pendingNotifications = new Map;
130078
130522
  this.pendingByParent = new Map;
@@ -130086,6 +130530,7 @@ class BackgroundManager {
130086
130530
  this.rootDescendantCounts = new Map;
130087
130531
  this.preStartDescendantReservations = new Set;
130088
130532
  this.enableParentSessionNotifications = options?.enableParentSessionNotifications ?? true;
130533
+ this.modelFallbackControllerAccessor = options?.modelFallbackControllerAccessor;
130089
130534
  this.registerProcessCleanup();
130090
130535
  }
130091
130536
  async abortSessionWithLogging(sessionID, reason) {
@@ -130158,6 +130603,42 @@ class BackgroundManager {
130158
130603
  }
130159
130604
  this.unregisterRootDescendant(task.rootSessionID);
130160
130605
  }
130606
+ addTask(task) {
130607
+ this.tasks.set(task.id, task);
130608
+ if (!task.parentSessionID) {
130609
+ return;
130610
+ }
130611
+ const taskIDs = this.tasksByParentSession.get(task.parentSessionID) ?? new Set;
130612
+ taskIDs.add(task.id);
130613
+ this.tasksByParentSession.set(task.parentSessionID, taskIDs);
130614
+ }
130615
+ removeTask(task) {
130616
+ this.tasks.delete(task.id);
130617
+ this.removeTaskFromParentIndex(task.id, task.parentSessionID);
130618
+ }
130619
+ updateTaskParent(task, parentSessionID) {
130620
+ if (task.parentSessionID === parentSessionID) {
130621
+ return;
130622
+ }
130623
+ this.removeTaskFromParentIndex(task.id, task.parentSessionID);
130624
+ task.parentSessionID = parentSessionID;
130625
+ const taskIDs = this.tasksByParentSession.get(parentSessionID) ?? new Set;
130626
+ taskIDs.add(task.id);
130627
+ this.tasksByParentSession.set(parentSessionID, taskIDs);
130628
+ }
130629
+ removeTaskFromParentIndex(taskID, parentSessionID) {
130630
+ if (!parentSessionID) {
130631
+ return;
130632
+ }
130633
+ const taskIDs = this.tasksByParentSession.get(parentSessionID);
130634
+ if (!taskIDs) {
130635
+ return;
130636
+ }
130637
+ taskIDs.delete(taskID);
130638
+ if (taskIDs.size === 0) {
130639
+ this.tasksByParentSession.delete(parentSessionID);
130640
+ }
130641
+ }
130161
130642
  async launch(input) {
130162
130643
  log("[background-agent] launch() called with:", {
130163
130644
  agent: input.agent,
@@ -130195,7 +130676,8 @@ class BackgroundManager {
130195
130676
  attemptCount: 0,
130196
130677
  category: input.category
130197
130678
  };
130198
- this.tasks.set(task.id, task);
130679
+ const firstAttempt = startAttempt(task, input.model);
130680
+ this.addTask(task);
130199
130681
  this.taskHistory.record(input.parentSessionID, { id: task.id, agent: input.agent, description: input.description, status: "pending", category: input.category });
130200
130682
  if (input.parentSessionID) {
130201
130683
  const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
@@ -130204,7 +130686,7 @@ class BackgroundManager {
130204
130686
  }
130205
130687
  const key = this.getConcurrencyKeyFromInput(input);
130206
130688
  const queue = this.queuesByKey.get(key) ?? [];
130207
- queue.push({ task, input });
130689
+ queue.push({ task, input, attemptID: firstAttempt.attemptID });
130208
130690
  this.queuesByKey.set(key, queue);
130209
130691
  log("[background-agent] Task queued:", { taskId: task.id, key, queueLength: queue.length });
130210
130692
  const toastManager = getTaskToastManager();
@@ -130250,9 +130732,13 @@ class BackgroundManager {
130250
130732
  } catch (error92) {
130251
130733
  log("[background-agent] Error starting task:", error92);
130252
130734
  this.rollbackPreStartDescendantReservation(item.task);
130253
- item.task.status = "error";
130254
- item.task.error = error92 instanceof Error ? error92.message : String(error92);
130255
- item.task.completedAt = new Date;
130735
+ if (item.task.currentAttemptID) {
130736
+ finalizeAttempt(item.task, item.task.currentAttemptID, "error", error92 instanceof Error ? error92.message : String(error92));
130737
+ } else {
130738
+ item.task.status = "error";
130739
+ item.task.error = error92 instanceof Error ? error92.message : String(error92);
130740
+ item.task.completedAt = new Date;
130741
+ }
130256
130742
  if (item.task.concurrencyKey) {
130257
130743
  this.concurrencyManager.release(item.task.concurrencyKey);
130258
130744
  item.task.concurrencyKey = undefined;
@@ -130275,6 +130761,7 @@ class BackgroundManager {
130275
130761
  }
130276
130762
  async startTask(item) {
130277
130763
  const { task, input } = item;
130764
+ const attemptID = item.attemptID ?? ensureCurrentAttempt(task, input.model).attemptID;
130278
130765
  log("[background-agent] Starting task:", {
130279
130766
  taskId: task.id,
130280
130767
  agent: input.agent,
@@ -130344,15 +130831,49 @@ class BackgroundManager {
130344
130831
  this.concurrencyManager.release(concurrencyKey);
130345
130832
  return;
130346
130833
  }
130347
- task.status = "running";
130348
- task.startedAt = new Date;
130349
- task.sessionID = sessionID;
130834
+ const boundAttempt = bindAttemptSession(task, attemptID, sessionID, input.model);
130835
+ if (!boundAttempt) {
130836
+ await this.abortSessionWithLogging(sessionID, "stale attempt binding cleanup");
130837
+ subagentSessions.delete(sessionID);
130838
+ if (task.rootSessionID) {
130839
+ this.unregisterRootDescendant(task.rootSessionID);
130840
+ }
130841
+ this.concurrencyManager.release(concurrencyKey);
130842
+ return;
130843
+ }
130350
130844
  task.progress = {
130351
130845
  toolCalls: 0,
130352
130846
  lastUpdate: new Date
130353
130847
  };
130354
130848
  task.concurrencyKey = concurrencyKey;
130355
130849
  task.concurrencyGroup = concurrencyKey;
130850
+ if (task.retryNotification) {
130851
+ const attemptNumber = boundAttempt.attemptNumber;
130852
+ const retrySessionUrl = buildLocalSessionUrl(parentDirectory, sessionID);
130853
+ const previousAttempt = getPreviousAttempt(task, boundAttempt.attemptID);
130854
+ const failedSessionID = previousAttempt?.sessionID ?? task.retryNotification.previousSessionID;
130855
+ const failedSessionLine = failedSessionID ? `
130856
+ - Failed session: \`${failedSessionID}\`` : "";
130857
+ const failedModel = formatAttemptModelSummary(previousAttempt) ?? task.retryNotification.failedModel;
130858
+ const failedModelLine = failedModel ? `
130859
+ - Failed model: \`${failedModel}\`` : "";
130860
+ const failedError = previousAttempt?.error ?? task.retryNotification.failedError;
130861
+ const failedErrorLine = failedError ? `
130862
+ - Error: ${failedError}` : "";
130863
+ const retryModel = formatAttemptModelSummary(boundAttempt) ?? task.retryNotification.nextModel;
130864
+ this.queuePendingNotification(task.parentSessionID, `<system-reminder>
130865
+ [BACKGROUND TASK RETRY SESSION READY]
130866
+ **ID:** \`${task.id}\`
130867
+ **Description:** ${task.description}
130868
+ **Retry attempt:** ${attemptNumber}
130869
+ **Retry session:** \`${sessionID}\`
130870
+ **Retry link:** ${retrySessionUrl}${failedSessionLine}${failedModelLine}${failedErrorLine}${retryModel ? `
130871
+ - Model: \`${retryModel}\`` : ""}
130872
+
130873
+ The fallback retry session is now created and can be inspected directly.
130874
+ </system-reminder>`);
130875
+ task.retryNotification = undefined;
130876
+ }
130356
130877
  this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID, agent: input.agent, description: input.description, status: "running", category: input.category, startedAt: task.startedAt });
130357
130878
  this.startPolling();
130358
130879
  log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent });
@@ -130416,16 +130937,33 @@ class BackgroundManager {
130416
130937
  }
130417
130938
  }
130418
130939
  log("[background-agent] promptAsync error:", error92);
130419
- const existingTask = this.findBySession(sessionID);
130940
+ const resolvedTask = this.resolveTaskAttemptBySession(sessionID);
130941
+ const existingTask = resolvedTask?.task;
130942
+ if (resolvedTask && !resolvedTask.isCurrent) {
130943
+ log("[background-agent] Ignoring prompt error from stale attempt session", {
130944
+ sessionID,
130945
+ currentAttemptID: resolvedTask.task.currentAttemptID,
130946
+ attemptID: resolvedTask.attemptID
130947
+ });
130948
+ return;
130949
+ }
130420
130950
  if (existingTask) {
130421
- existingTask.status = "interrupt";
130422
- const errorMessage = error92 instanceof Error ? error92.message : String(error92);
130423
- if (errorMessage.includes("agent.name") || errorMessage.includes("undefined") || isAgentNotFoundError(error92)) {
130424
- existingTask.error = `Agent "${input.agent}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.`;
130951
+ const errorInfo = {
130952
+ name: extractErrorName2(error92),
130953
+ message: extractErrorMessage(error92)
130954
+ };
130955
+ if (await this.tryFallbackRetry(existingTask, errorInfo, "promptAsync.launch")) {
130956
+ return;
130957
+ }
130958
+ const errorMessage = errorInfo.message ?? (error92 instanceof Error ? error92.message : String(error92));
130959
+ const terminalError = errorMessage.includes("agent.name") || errorMessage.includes("undefined") || isAgentNotFoundError(error92) ? `Agent "${input.agent}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.` : errorMessage;
130960
+ if (existingTask.currentAttemptID) {
130961
+ finalizeAttempt(existingTask, existingTask.currentAttemptID, "interrupt", terminalError);
130425
130962
  } else {
130426
- existingTask.error = errorMessage;
130963
+ existingTask.status = "interrupt";
130964
+ existingTask.error = terminalError;
130965
+ existingTask.completedAt = new Date;
130427
130966
  }
130428
- existingTask.completedAt = new Date;
130429
130967
  if (existingTask.rootSessionID) {
130430
130968
  this.unregisterRootDescendant(existingTask.rootSessionID);
130431
130969
  }
@@ -130446,13 +130984,24 @@ class BackgroundManager {
130446
130984
  return this.tasks.get(id);
130447
130985
  }
130448
130986
  getTasksByParentSession(sessionID) {
130449
- const result = [];
130450
- for (const task of this.tasks.values()) {
130451
- if (task.parentSessionID === sessionID) {
130452
- result.push(task);
130987
+ const taskIDs = this.tasksByParentSession.get(sessionID);
130988
+ if (!taskIDs) {
130989
+ const result = [];
130990
+ for (const task of this.tasks.values()) {
130991
+ if (task.parentSessionID === sessionID) {
130992
+ result.push(task);
130993
+ }
130994
+ }
130995
+ return result;
130996
+ }
130997
+ const tasks = [];
130998
+ for (const taskID of taskIDs) {
130999
+ const task = this.tasks.get(taskID);
131000
+ if (task) {
131001
+ tasks.push(task);
130453
131002
  }
130454
131003
  }
130455
- return result;
131004
+ return tasks;
130456
131005
  }
130457
131006
  getAllDescendantTasks(sessionID) {
130458
131007
  const result = [];
@@ -130471,9 +131020,31 @@ class BackgroundManager {
130471
131020
  if (task.sessionID === sessionID) {
130472
131021
  return task;
130473
131022
  }
131023
+ if (findAttemptBySession(task, sessionID)) {
131024
+ return task;
131025
+ }
130474
131026
  }
130475
131027
  return;
130476
131028
  }
131029
+ resolveTaskAttemptBySession(sessionID) {
131030
+ const task = this.findBySession(sessionID);
131031
+ if (!task) {
131032
+ return;
131033
+ }
131034
+ const attempt = findAttemptBySession(task, sessionID);
131035
+ if (!attempt) {
131036
+ return {
131037
+ task,
131038
+ attemptID: undefined,
131039
+ isCurrent: task.sessionID === sessionID
131040
+ };
131041
+ }
131042
+ return {
131043
+ task,
131044
+ attemptID: attempt.attemptID,
131045
+ isCurrent: task.currentAttemptID === attempt.attemptID
131046
+ };
131047
+ }
130477
131048
  getConcurrencyKeyFromInput(input) {
130478
131049
  if (input.model) {
130479
131050
  return `${input.model.providerID}/${input.model.modelID}`;
@@ -130486,7 +131057,7 @@ class BackgroundManager {
130486
131057
  const parentChanged = input.parentSessionID !== existingTask.parentSessionID;
130487
131058
  if (parentChanged) {
130488
131059
  this.cleanupPendingByParent(existingTask);
130489
- existingTask.parentSessionID = input.parentSessionID;
131060
+ this.updateTaskParent(existingTask, input.parentSessionID);
130490
131061
  }
130491
131062
  if (input.parentAgent !== undefined) {
130492
131063
  existingTask.parentAgent = input.parentAgent;
@@ -130530,7 +131101,7 @@ class BackgroundManager {
130530
131101
  concurrencyKey: input.concurrencyKey,
130531
131102
  concurrencyGroup
130532
131103
  };
130533
- this.tasks.set(task.id, task);
131104
+ this.addTask(task);
130534
131105
  subagentSessions.add(input.sessionID);
130535
131106
  this.startPolling();
130536
131107
  this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID: input.sessionID, agent: input.agent || "task", description: input.description, status: "running", startedAt: task.startedAt });
@@ -130569,7 +131140,7 @@ class BackgroundManager {
130569
131140
  existingTask.status = "running";
130570
131141
  existingTask.completedAt = undefined;
130571
131142
  existingTask.error = undefined;
130572
- existingTask.parentSessionID = input.parentSessionID;
131143
+ this.updateTaskParent(existingTask, input.parentSessionID);
130573
131144
  existingTask.parentMessageID = input.parentMessageID;
130574
131145
  existingTask.parentModel = input.parentModel;
130575
131146
  existingTask.parentAgent = input.parentAgent;
@@ -130636,8 +131207,15 @@ class BackgroundManager {
130636
131207
  }
130637
131208
  }).catch(async (error92) => {
130638
131209
  log("[background-agent] resume prompt error:", error92);
131210
+ const errorInfo = {
131211
+ name: extractErrorName2(error92),
131212
+ message: extractErrorMessage(error92)
131213
+ };
131214
+ if (await this.tryFallbackRetry(existingTask, errorInfo, "promptAsync.resume")) {
131215
+ return;
131216
+ }
130639
131217
  existingTask.status = "interrupt";
130640
- const errorMessage = error92 instanceof Error ? error92.message : String(error92);
131218
+ const errorMessage = errorInfo.message ?? (error92 instanceof Error ? error92.message : String(error92));
130641
131219
  existingTask.error = errorMessage;
130642
131220
  existingTask.completedAt = new Date;
130643
131221
  if (existingTask.rootSessionID) {
@@ -130720,8 +131298,11 @@ class BackgroundManager {
130720
131298
  }
130721
131299
  if (role !== "assistant")
130722
131300
  return;
130723
- const task = this.findBySession(sessionID);
130724
- if (!task || task.status !== "running")
131301
+ const resolved = this.resolveTaskAttemptBySession(sessionID);
131302
+ if (!resolved?.isCurrent)
131303
+ return;
131304
+ const { task } = resolved;
131305
+ if (task.status !== "running")
130725
131306
  return;
130726
131307
  const assistantError = info["error"];
130727
131308
  if (!assistantError)
@@ -130742,9 +131323,10 @@ class BackgroundManager {
130742
131323
  const sessionID = partInfo?.sessionID;
130743
131324
  if (!sessionID)
130744
131325
  return;
130745
- const task = this.findBySession(sessionID);
130746
- if (!task)
131326
+ const resolved = this.resolveTaskAttemptBySession(sessionID);
131327
+ if (!resolved?.isCurrent)
130747
131328
  return;
131329
+ const { task } = resolved;
130748
131330
  if (this.hasOutputSignalFromPart(partInfo)) {
130749
131331
  this.markSessionOutputObserved(sessionID);
130750
131332
  }
@@ -130829,7 +131411,10 @@ class BackgroundManager {
130829
131411
  return;
130830
131412
  handleSessionIdleBackgroundEvent({
130831
131413
  properties: props,
130832
- findBySession: (id) => this.findBySession(id),
131414
+ findBySession: (id) => {
131415
+ const resolved = this.resolveTaskAttemptBySession(id);
131416
+ return resolved?.isCurrent ? resolved.task : undefined;
131417
+ },
130833
131418
  idleDeferralTimers: this.idleDeferralTimers,
130834
131419
  validateSessionHasOutput: (id) => this.validateSessionHasOutput(id),
130835
131420
  checkSessionTodos: (id) => this.checkSessionTodos(id),
@@ -130841,8 +131426,11 @@ class BackgroundManager {
130841
131426
  const sessionID = typeof props?.sessionID === "string" ? props.sessionID : undefined;
130842
131427
  if (!sessionID)
130843
131428
  return;
130844
- const task = this.findBySession(sessionID);
130845
- if (!task || task.status !== "running")
131429
+ const resolved = this.resolveTaskAttemptBySession(sessionID);
131430
+ if (!resolved?.isCurrent)
131431
+ return;
131432
+ const { task } = resolved;
131433
+ if (task.status !== "running")
130846
131434
  return;
130847
131435
  const errorObj = props?.error;
130848
131436
  const errorName = errorObj?.name;
@@ -130869,9 +131457,9 @@ class BackgroundManager {
130869
131457
  this.clearSessionOutputObserved(sessionID);
130870
131458
  this.clearSessionTodoObservation(sessionID);
130871
131459
  const tasksToCancel = new Map;
130872
- const directTask = this.findBySession(sessionID);
130873
- if (directTask) {
130874
- tasksToCancel.set(directTask.id, directTask);
131460
+ const directTask = this.resolveTaskAttemptBySession(sessionID);
131461
+ if (directTask?.isCurrent) {
131462
+ tasksToCancel.set(directTask.task.id, directTask.task);
130875
131463
  }
130876
131464
  for (const descendant of this.getAllDescendantTasks(sessionID)) {
130877
131465
  tasksToCancel.set(descendant.id, descendant);
@@ -130917,8 +131505,11 @@ class BackgroundManager {
130917
131505
  const status = props?.status;
130918
131506
  if (!sessionID || status?.type !== "retry")
130919
131507
  return;
130920
- const task = this.findBySession(sessionID);
130921
- if (!task || task.status !== "running")
131508
+ const resolved = this.resolveTaskAttemptBySession(sessionID);
131509
+ if (!resolved?.isCurrent)
131510
+ return;
131511
+ const { task } = resolved;
131512
+ if (task.status !== "running")
130922
131513
  return;
130923
131514
  const errorMessage = typeof status.message === "string" ? status.message : undefined;
130924
131515
  const errorInfo = { name: "SessionRetry", message: errorMessage };
@@ -130932,6 +131523,12 @@ class BackgroundManager {
130932
131523
  }
130933
131524
  async handleSessionErrorEvent(args) {
130934
131525
  const { task, errorInfo, errorMessage, errorName } = args;
131526
+ if (!task.fallbackChain && task.sessionID) {
131527
+ const sessionFallbackChain = this.modelFallbackControllerAccessor?.getSessionFallbackChain(task.sessionID);
131528
+ if (sessionFallbackChain?.length) {
131529
+ task.fallbackChain = sessionFallbackChain;
131530
+ }
131531
+ }
130935
131532
  if (isAgentNotFoundError({ message: errorInfo.message })) {
130936
131533
  log("[background-agent] Skipping session.error fallback for agent-not-found (handled by prompt catch)", {
130937
131534
  taskId: task.id,
@@ -130951,9 +131548,13 @@ class BackgroundManager {
130951
131548
  hasFallbackChain: !!task.fallbackChain,
130952
131549
  canRetry
130953
131550
  });
130954
- task.status = "error";
130955
- task.error = errorMsg;
130956
- task.completedAt = new Date;
131551
+ if (task.currentAttemptID) {
131552
+ finalizeAttempt(task, task.currentAttemptID, "error", errorMsg);
131553
+ } else {
131554
+ task.status = "error";
131555
+ task.error = errorMsg;
131556
+ task.completedAt = new Date;
131557
+ }
130957
131558
  if (task.rootSessionID) {
130958
131559
  this.unregisterRootDescendant(task.rootSessionID);
130959
131560
  }
@@ -130997,7 +131598,28 @@ class BackgroundManager {
130997
131598
  client: this.client,
130998
131599
  idleDeferralTimers: this.idleDeferralTimers,
130999
131600
  queuesByKey: this.queuesByKey,
131000
- processKey: (key) => this.processKey(key)
131601
+ processKey: (key) => this.processKey(key),
131602
+ onRetrying: ({ task: task2, source: source2 }) => {
131603
+ const currentAttempt = getCurrentAttempt(task2);
131604
+ const previousAttempt = getPreviousAttempt(task2, currentAttempt?.attemptID);
131605
+ const sourceText = source2 ? ` via ${source2}` : "";
131606
+ const failedSessionLine = previousAttempt?.sessionID ? `
131607
+ - Failed session: \`${previousAttempt.sessionID}\`` : "";
131608
+ const failedModel = formatAttemptModelSummary(previousAttempt);
131609
+ const failedModelLine = failedModel ? `
131610
+ - Failed model: \`${failedModel}\`` : "";
131611
+ const failedErrorLine = previousAttempt?.error ? `
131612
+ - Error: ${previousAttempt.error}` : "";
131613
+ const nextModel = formatAttemptModelSummary(currentAttempt);
131614
+ this.queuePendingNotification(task2.parentSessionID, `<system-reminder>
131615
+ [BACKGROUND TASK RETRYING]
131616
+ **ID:** \`${task2.id}\`
131617
+ **Description:** ${task2.description}${sourceText}${failedSessionLine}${failedModelLine}${failedErrorLine}${nextModel ? `
131618
+ - Next model: \`${nextModel}\`` : ""}
131619
+
131620
+ The task was re-queued on a fallback model after a retryable failure.
131621
+ </system-reminder>`);
131622
+ }
131001
131623
  });
131002
131624
  return result.then((retried) => {
131003
131625
  if (retried && previousSessionID) {
@@ -131129,7 +131751,7 @@ ${originalText}`;
131129
131751
  }
131130
131752
  }
131131
131753
  this.clearNotificationsForTask(taskId);
131132
- this.tasks.delete(taskId);
131754
+ this.removeTask(task);
131133
131755
  this.clearTaskHistoryWhenParentTasksGone(task.parentSessionID);
131134
131756
  if (task.sessionID) {
131135
131757
  subagentSessions.delete(task.sessionID);
@@ -131163,14 +131785,18 @@ ${originalText}`;
131163
131785
  log("[background-agent] Cancelled pending task:", { taskId, key });
131164
131786
  }
131165
131787
  const wasRunning = task.status === "running";
131166
- task.status = "cancelled";
131167
- task.completedAt = new Date;
131788
+ if (task.currentAttemptID) {
131789
+ finalizeAttempt(task, task.currentAttemptID, "cancelled", reason);
131790
+ } else {
131791
+ task.status = "cancelled";
131792
+ task.completedAt = new Date;
131793
+ if (reason) {
131794
+ task.error = reason;
131795
+ }
131796
+ }
131168
131797
  if (wasRunning && task.rootSessionID) {
131169
131798
  this.unregisterRootDescendant(task.rootSessionID);
131170
131799
  }
131171
- if (reason) {
131172
- task.error = reason;
131173
- }
131174
131800
  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 });
131175
131801
  if (task.concurrencyKey) {
131176
131802
  this.concurrencyManager.release(task.concurrencyKey);
@@ -131245,8 +131871,12 @@ ${originalText}`;
131245
131871
  log("[background-agent] Task already completed, skipping:", { taskId: task.id, status: task.status, source });
131246
131872
  return false;
131247
131873
  }
131248
- task.status = "completed";
131249
- task.completedAt = new Date;
131874
+ if (task.currentAttemptID) {
131875
+ finalizeAttempt(task, task.currentAttemptID, "completed");
131876
+ } else {
131877
+ task.status = "completed";
131878
+ task.completedAt = new Date;
131879
+ }
131250
131880
  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 });
131251
131881
  if (task.rootSessionID) {
131252
131882
  this.unregisterRootDescendant(task.rootSessionID);
@@ -131292,7 +131922,8 @@ ${originalText}`;
131292
131922
  id: task.id,
131293
131923
  description: task.description,
131294
131924
  status: task.status,
131295
- error: task.error
131925
+ error: task.error,
131926
+ attempts: cloneAttempts(task)
131296
131927
  });
131297
131928
  const pendingSet = this.pendingByParent.get(task.parentSessionID);
131298
131929
  let allComplete = false;
@@ -131308,7 +131939,7 @@ ${originalText}`;
131308
131939
  remainingCount = Array.from(this.tasks.values()).filter((t) => t.parentSessionID === task.parentSessionID && t.id !== task.id && (t.status === "running" || t.status === "pending")).length;
131309
131940
  allComplete = remainingCount === 0;
131310
131941
  }
131311
- const completedTasks = allComplete ? this.completedTaskSummaries.get(task.parentSessionID) ?? [{ id: task.id, description: task.description, status: task.status, error: task.error }] : [];
131942
+ const completedTasks = allComplete ? this.completedTaskSummaries.get(task.parentSessionID) ?? [{ id: task.id, description: task.description, status: task.status, error: task.error, attempts: cloneAttempts(task) }] : [];
131312
131943
  if (allComplete) {
131313
131944
  this.completedTaskSummaries.delete(task.parentSessionID);
131314
131945
  }
@@ -131470,9 +132101,13 @@ ${originalText}`;
131470
132101
  return verifySessionExists(this.client, sessionID, this.directory);
131471
132102
  }
131472
132103
  async failCrashedTask(task, errorMessage) {
131473
- task.status = "error";
131474
- task.error = errorMessage;
131475
- task.completedAt = new Date;
132104
+ if (task.currentAttemptID) {
132105
+ finalizeAttempt(task, task.currentAttemptID, "error", errorMessage);
132106
+ } else {
132107
+ task.status = "error";
132108
+ task.error = errorMessage;
132109
+ task.completedAt = new Date;
132110
+ }
131476
132111
  if (task.rootSessionID) {
131477
132112
  this.unregisterRootDescendant(task.rootSessionID);
131478
132113
  }
@@ -131640,6 +132275,7 @@ ${originalText}`;
131640
132275
  }
131641
132276
  this.concurrencyManager.clear();
131642
132277
  this.tasks.clear();
132278
+ this.tasksByParentSession.clear();
131643
132279
  this.notifications.clear();
131644
132280
  this.pendingNotifications.clear();
131645
132281
  this.pendingByParent.clear();
@@ -136876,12 +137512,16 @@ function createModelFallbackControllerAccessor() {
136876
137512
  function setSessionFallbackChain2(sessionID, fallbackChain) {
136877
137513
  controller?.setSessionFallbackChain(sessionID, fallbackChain);
136878
137514
  }
137515
+ function getSessionFallbackChain(sessionID) {
137516
+ return controller?.getSessionFallbackChain(sessionID);
137517
+ }
136879
137518
  function clearSessionFallbackChain2(sessionID) {
136880
137519
  controller?.clearSessionFallbackChain(sessionID);
136881
137520
  }
136882
137521
  return {
136883
137522
  register,
136884
137523
  setSessionFallbackChain: setSessionFallbackChain2,
137524
+ getSessionFallbackChain,
136885
137525
  clearSessionFallbackChain: clearSessionFallbackChain2
136886
137526
  };
136887
137527
  }
@@ -139777,6 +140417,12 @@ async function loadMcpConfigs(disabledMcps = []) {
139777
140417
  }
139778
140418
  return { servers, loadedServers };
139779
140419
  }
140420
+ // src/agents/index.ts
140421
+ init_types();
140422
+
140423
+ // src/agents/sisyphus.ts
140424
+ init_types();
140425
+
139780
140426
  // src/agents/sisyphus/gemini.ts
139781
140427
  function buildGeminiToolMandate() {
139782
140428
  return `<TOOL_CALL_MANDATE>
@@ -140920,6 +141566,7 @@ ${antiPatterns}
140920
141566
  }
140921
141567
 
140922
141568
  // src/agents/gpt-apply-patch-guard.ts
141569
+ init_types();
140923
141570
  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.";
140924
141571
  function getGptApplyPatchPermission(model) {
140925
141572
  return isGptModel(model) ? { apply_patch: "deny" } : {};
@@ -142025,6 +142672,7 @@ ${styleBlock}`;
142025
142672
  }
142026
142673
 
142027
142674
  // src/agents/frontier-tool-schema-guard.ts
142675
+ init_types();
142028
142676
  var FRONTIER_TOOL_SCHEMA_NAMES = ["grep", "glob"];
142029
142677
  function isOpus47Model(model) {
142030
142678
  const modelName = model.includes("/") ? model.split("/").pop() ?? model : model;
@@ -142576,6 +143224,7 @@ ${buildGeminiVerificationOverride()}
142576
143224
  createSisyphusAgent.mode = MODE;
142577
143225
 
142578
143226
  // src/agents/oracle.ts
143227
+ init_types();
142579
143228
  var MODE2 = "subagent";
142580
143229
  var ORACLE_PROMPT_METADATA = {
142581
143230
  category: "advisor",
@@ -143789,6 +144438,9 @@ var metisPromptMetadata = {
143789
144438
  keyTrigger: "Ambiguous or complex request \u2192 consult Metis before Prometheus"
143790
144439
  };
143791
144440
 
144441
+ // src/agents/atlas/agent.ts
144442
+ init_types();
144443
+
143792
144444
  // src/agents/atlas/shared-prompt.ts
143793
144445
  var ATLAS_DELEGATION_SYSTEM = `<delegation_system>
143794
144446
  ## How to Delegate
@@ -145015,6 +145667,7 @@ var atlasPromptMetadata = {
145015
145667
  keyTrigger: "Todo list path provided OR multiple tasks requiring multi-agent orchestration"
145016
145668
  };
145017
145669
  // src/agents/momus.ts
145670
+ init_types();
145018
145671
  var MODE8 = "subagent";
145019
145672
  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**.
145020
145673
 
@@ -145319,6 +145972,9 @@ var momusPromptMetadata = {
145319
145972
  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.'
145320
145973
  };
145321
145974
 
145975
+ // src/agents/hephaestus/agent.ts
145976
+ init_types();
145977
+
145322
145978
  // src/agents/hephaestus/gpt.ts
145323
145979
  function buildTodoDisciplineSection(useTaskSystem) {
145324
145980
  if (useTaskSystem) {
@@ -147816,6 +148472,7 @@ No tasks on multi-step work = INCOMPLETE WORK. The user tracks your progress thr
147816
148472
  No todos on multi-step work = INCOMPLETE WORK. The user tracks your progress through todos.`;
147817
148473
  }
147818
148474
  // src/agents/sisyphus-junior/agent.ts
148475
+ init_types();
147819
148476
  var MODE11 = "subagent";
147820
148477
  var BLOCKED_TOOLS3 = ["task"];
147821
148478
  var GPT_BLOCKED_TOOLS = ["task", "apply_patch"];
@@ -150618,6 +151275,7 @@ function getGeminiPrometheusPrompt() {
150618
151275
  }
150619
151276
 
150620
151277
  // src/agents/prometheus/system-prompt.ts
151278
+ init_types();
150621
151279
  var PROMETHEUS_SYSTEM_PROMPT = `${PROMETHEUS_IDENTITY_CONSTRAINTS}
150622
151280
  ${PROMETHEUS_INTERVIEW_MODE}
150623
151281
  ${PROMETHEUS_PLAN_GENERATION}
@@ -151513,6 +152171,7 @@ function createManagers(args) {
151513
152171
  deps.markServerRunningInProcessFn();
151514
152172
  }
151515
152173
  const tmuxSessionManager = new deps.TmuxSessionManagerClass(ctx, tmuxConfig);
152174
+ const modelFallbackControllerAccessor = createModelFallbackControllerAccessor();
151516
152175
  deps.registerManagerForCleanupFn({
151517
152176
  shutdown: async () => {
151518
152177
  await tmuxSessionManager.cleanup().catch((error92) => {
@@ -151556,7 +152215,8 @@ function createManagers(args) {
151556
152215
  log("[create-managers] tmux cleanup error during shutdown:", error92);
151557
152216
  });
151558
152217
  },
151559
- enableParentSessionNotifications: backgroundNotificationHookEnabled
152218
+ enableParentSessionNotifications: backgroundNotificationHookEnabled,
152219
+ modelFallbackControllerAccessor
151560
152220
  });
151561
152221
  deps.initTaskToastManagerFn(ctx.client);
151562
152222
  const skillMcpManager = new deps.SkillMcpManagerClass;
@@ -151565,7 +152225,6 @@ function createManagers(args) {
151565
152225
  pluginConfig,
151566
152226
  modelCacheState
151567
152227
  });
151568
- const modelFallbackControllerAccessor = createModelFallbackControllerAccessor();
151569
152228
  return {
151570
152229
  tmuxSessionManager,
151571
152230
  backgroundManager,
@@ -152706,15 +153365,13 @@ function extractErrorMessage3(error92) {
152706
153365
  return "";
152707
153366
  if (typeof error92 === "string")
152708
153367
  return error92;
152709
- if (error92 instanceof Error)
152710
- return error92.message;
152711
153368
  if (isRecord19(error92)) {
152712
153369
  const candidates = [
152713
- error92,
152714
153370
  error92.data,
152715
- error92.error,
152716
153371
  isRecord19(error92.data) ? error92.data.error : undefined,
152717
- error92.cause
153372
+ error92.error,
153373
+ error92.cause,
153374
+ error92
152718
153375
  ];
152719
153376
  for (const candidate of candidates) {
152720
153377
  if (isRecord19(candidate) && typeof candidate.message === "string" && candidate.message.length > 0) {
@@ -152722,6 +153379,8 @@ function extractErrorMessage3(error92) {
152722
153379
  }
152723
153380
  }
152724
153381
  }
153382
+ if (error92 instanceof Error)
153383
+ return error92.message;
152725
153384
  try {
152726
153385
  return JSON.stringify(error92);
152727
153386
  } catch {
@@ -153011,6 +153670,9 @@ function createEventHandler2(args) {
153011
153670
  const sessionID = info?.sessionID;
153012
153671
  const agent = info?.agent;
153013
153672
  const role = info?.role;
153673
+ if (sessionID && info?.finish === true) {
153674
+ invalidateContextWindowUsageCache(pluginContext, sessionID);
153675
+ }
153014
153676
  if (sessionID && role === "user") {
153015
153677
  const isCompactionMessage2 = agent ? isCompactionAgent5(agent) : false;
153016
153678
  if (agent && !isCompactionMessage2) {