oh-my-opencode 3.15.1 → 3.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -17496,7 +17496,7 @@ function getOpenCodeConfigPaths(options) {
17496
17496
  configJson: join6(configDir, "opencode.json"),
17497
17497
  configJsonc: join6(configDir, "opencode.jsonc"),
17498
17498
  packageJson: join6(configDir, "package.json"),
17499
- omoConfig: join6(configDir, "oh-my-opencode.json")
17499
+ omoConfig: join6(configDir, `${CONFIG_BASENAME}.json`)
17500
17500
  };
17501
17501
  }
17502
17502
  // src/shared/opencode-version.ts
@@ -60789,15 +60789,19 @@ function convertSDKMessageToStoredMessage(msg) {
60789
60789
  async function findNearestMessageWithFieldsFromSDK(client, sessionID) {
60790
60790
  try {
60791
60791
  const response = await client.session.messages({ path: { id: sessionID } });
60792
- const messages = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
60793
- for (let i2 = messages.length - 1;i2 >= 0; i2--) {
60794
- const stored = convertSDKMessageToStoredMessage(messages[i2]);
60792
+ const messages = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true }).map((message) => ({
60793
+ stored: convertSDKMessageToStoredMessage(message),
60794
+ createdAt: message.info?.time?.created ?? Number.NEGATIVE_INFINITY,
60795
+ id: typeof message.id === "string" ? message.id : ""
60796
+ })).sort((left, right) => right.createdAt - left.createdAt || right.id.localeCompare(left.id));
60797
+ for (const message of messages) {
60798
+ const stored = message.stored;
60795
60799
  if (stored?.agent && stored.model?.providerID && stored.model?.modelID) {
60796
60800
  return stored;
60797
60801
  }
60798
60802
  }
60799
- for (let i2 = messages.length - 1;i2 >= 0; i2--) {
60800
- const stored = convertSDKMessageToStoredMessage(messages[i2]);
60803
+ for (const message of messages) {
60804
+ const stored = message.stored;
60801
60805
  if (stored?.agent || stored?.model?.providerID && stored?.model?.modelID) {
60802
60806
  return stored;
60803
60807
  }
@@ -60813,7 +60817,15 @@ async function findNearestMessageWithFieldsFromSDK(client, sessionID) {
60813
60817
  async function findFirstMessageWithAgentFromSDK(client, sessionID) {
60814
60818
  try {
60815
60819
  const response = await client.session.messages({ path: { id: sessionID } });
60816
- const messages = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
60820
+ const messages = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true }).sort((left, right) => {
60821
+ const leftTime = left.info?.time?.created ?? Number.POSITIVE_INFINITY;
60822
+ const rightTime = right.info?.time?.created ?? Number.POSITIVE_INFINITY;
60823
+ if (leftTime !== rightTime)
60824
+ return leftTime - rightTime;
60825
+ const leftId = typeof left.id === "string" ? left.id : "";
60826
+ const rightId = typeof right.id === "string" ? right.id : "";
60827
+ return leftId.localeCompare(rightId);
60828
+ });
60817
60829
  for (const msg of messages) {
60818
60830
  const stored = convertSDKMessageToStoredMessage(msg);
60819
60831
  if (stored?.agent) {
@@ -60833,27 +60845,27 @@ function findNearestMessageWithFields(messageDir) {
60833
60845
  return null;
60834
60846
  }
60835
60847
  try {
60836
- const files = readdirSync(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
60837
- for (const file of files) {
60848
+ const messages = readdirSync(messageDir).filter((f) => f.endsWith(".json")).map((fileName) => {
60838
60849
  try {
60839
- const content = readFileSync6(join12(messageDir, file), "utf-8");
60850
+ const content = readFileSync6(join12(messageDir, fileName), "utf-8");
60840
60851
  const msg = JSON.parse(content);
60841
- if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
60842
- return msg;
60843
- }
60852
+ return {
60853
+ fileName,
60854
+ msg,
60855
+ createdAt: typeof msg.time?.created === "number" ? msg.time.created : Number.NEGATIVE_INFINITY
60856
+ };
60844
60857
  } catch {
60845
- continue;
60858
+ return null;
60859
+ }
60860
+ }).filter((entry) => entry !== null).sort((left, right) => right.createdAt - left.createdAt || right.fileName.localeCompare(left.fileName));
60861
+ for (const entry of messages) {
60862
+ if (entry.msg.agent && entry.msg.model?.providerID && entry.msg.model?.modelID) {
60863
+ return entry.msg;
60846
60864
  }
60847
60865
  }
60848
- for (const file of files) {
60849
- try {
60850
- const content = readFileSync6(join12(messageDir, file), "utf-8");
60851
- const msg = JSON.parse(content);
60852
- if (msg.agent || msg.model?.providerID && msg.model?.modelID) {
60853
- return msg;
60854
- }
60855
- } catch {
60856
- continue;
60866
+ for (const entry of messages) {
60867
+ if (entry.msg.agent || entry.msg.model?.providerID && entry.msg.model?.modelID) {
60868
+ return entry.msg;
60857
60869
  }
60858
60870
  }
60859
60871
  } catch {
@@ -60866,16 +60878,22 @@ function findFirstMessageWithAgent(messageDir) {
60866
60878
  return null;
60867
60879
  }
60868
60880
  try {
60869
- const files = readdirSync(messageDir).filter((f) => f.endsWith(".json")).sort();
60870
- for (const file of files) {
60881
+ const messages = readdirSync(messageDir).filter((f) => f.endsWith(".json")).map((fileName) => {
60871
60882
  try {
60872
- const content = readFileSync6(join12(messageDir, file), "utf-8");
60883
+ const content = readFileSync6(join12(messageDir, fileName), "utf-8");
60873
60884
  const msg = JSON.parse(content);
60874
- if (msg.agent) {
60875
- return msg.agent;
60876
- }
60885
+ return {
60886
+ fileName,
60887
+ msg,
60888
+ createdAt: typeof msg.time?.created === "number" ? msg.time.created : Number.POSITIVE_INFINITY
60889
+ };
60877
60890
  } catch {
60878
- continue;
60891
+ return null;
60892
+ }
60893
+ }).filter((entry) => entry !== null).sort((left, right) => left.createdAt - right.createdAt || left.fileName.localeCompare(right.fileName));
60894
+ for (const entry of messages) {
60895
+ if (entry.msg.agent) {
60896
+ return entry.msg.agent;
60879
60897
  }
60880
60898
  }
60881
60899
  } catch {
@@ -60983,7 +61001,7 @@ function getAgentConfigKey(agentName) {
60983
61001
  return lower;
60984
61002
  return lower;
60985
61003
  }
60986
- function normalizeAgentForPrompt(agentName) {
61004
+ function normalizeAgentForPromptKey(agentName) {
60987
61005
  if (typeof agentName !== "string") {
60988
61006
  return;
60989
61007
  }
@@ -60994,10 +61012,10 @@ function normalizeAgentForPrompt(agentName) {
60994
61012
  const lower = trimmed.toLowerCase();
60995
61013
  const reversed = REVERSE_DISPLAY_NAMES[lower];
60996
61014
  if (reversed !== undefined) {
60997
- return AGENT_DISPLAY_NAMES[reversed] ?? trimmed;
61015
+ return reversed;
60998
61016
  }
60999
61017
  if (AGENT_DISPLAY_NAMES[lower] !== undefined) {
61000
- return AGENT_DISPLAY_NAMES[lower];
61018
+ return lower;
61001
61019
  }
61002
61020
  return trimmed;
61003
61021
  }
@@ -62294,11 +62312,11 @@ function getPluginsBaseDir() {
62294
62312
  }
62295
62313
  return join18(homedir6(), ".claude", "plugins");
62296
62314
  }
62297
- function getInstalledPluginsPath() {
62298
- return join18(getPluginsBaseDir(), "installed_plugins.json");
62315
+ function getInstalledPluginsPath(pluginsBaseDir) {
62316
+ return join18(pluginsBaseDir ?? getPluginsBaseDir(), "installed_plugins.json");
62299
62317
  }
62300
- function loadInstalledPlugins() {
62301
- const dbPath = getInstalledPluginsPath();
62318
+ function loadInstalledPlugins(pluginsBaseDir) {
62319
+ const dbPath = getInstalledPluginsPath(pluginsBaseDir);
62302
62320
  if (!existsSync14(dbPath)) {
62303
62321
  return null;
62304
62322
  }
@@ -62402,7 +62420,8 @@ function extractPluginEntries(db) {
62402
62420
  return Object.entries(db.plugins).map(([key, installations]) => [key, installations[0]]);
62403
62421
  }
62404
62422
  function discoverInstalledPlugins(options) {
62405
- const db = loadInstalledPlugins();
62423
+ const pluginsBaseDir = options?.pluginsHomeOverride ?? getPluginsBaseDir();
62424
+ const db = loadInstalledPlugins(pluginsBaseDir);
62406
62425
  const settings = loadClaudeSettings();
62407
62426
  const plugins = [];
62408
62427
  const errors = [];
@@ -62411,6 +62430,7 @@ function discoverInstalledPlugins(options) {
62411
62430
  }
62412
62431
  const settingsEnabledPlugins = settings?.enabledPlugins;
62413
62432
  const overrideEnabledPlugins = options?.enabledPluginsOverride;
62433
+ const pluginManifestLoader = options?.loadPluginManifestOverride ?? loadPluginManifest;
62414
62434
  for (const [pluginKey, installation] of extractPluginEntries(db)) {
62415
62435
  if (!installation)
62416
62436
  continue;
@@ -62427,7 +62447,7 @@ function discoverInstalledPlugins(options) {
62427
62447
  });
62428
62448
  continue;
62429
62449
  }
62430
- const manifest = loadPluginManifest(installPath);
62450
+ const manifest = pluginManifestLoader(installPath);
62431
62451
  const pluginName = manifest?.name || derivePluginNameFromKey(pluginKey);
62432
62452
  const loadedPlugin = {
62433
62453
  name: pluginName,
@@ -62928,11 +62948,21 @@ function loadPluginHooksConfigs(plugins) {
62928
62948
  }
62929
62949
 
62930
62950
  // src/features/claude-code-plugin-loader/loader.ts
62951
+ var cachedPluginComponentsByKey = new Map;
62952
+ function clonePluginComponentsResult(result) {
62953
+ return structuredClone(result);
62954
+ }
62931
62955
  function isClaudeCodePluginsDisabled() {
62932
62956
  const disableFlag = process.env.OPENCODE_DISABLE_CLAUDE_CODE;
62933
62957
  const disablePluginsFlag = process.env.OPENCODE_DISABLE_CLAUDE_CODE_PLUGINS;
62934
62958
  return disableFlag === "true" || disableFlag === "1" || disablePluginsFlag === "true" || disablePluginsFlag === "1";
62935
62959
  }
62960
+ function getPluginComponentsCacheKey(options) {
62961
+ const overrideEntries = Object.entries(options?.enabledPluginsOverride ?? {}).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey));
62962
+ return JSON.stringify({
62963
+ enabledPluginsOverride: overrideEntries
62964
+ });
62965
+ }
62936
62966
  async function loadAllPluginComponents(options) {
62937
62967
  if (isClaudeCodePluginsDisabled()) {
62938
62968
  log("Claude Code plugin loading disabled via OPENCODE_DISABLE_CLAUDE_CODE env var");
@@ -62946,6 +62976,11 @@ async function loadAllPluginComponents(options) {
62946
62976
  errors: []
62947
62977
  };
62948
62978
  }
62979
+ const cacheKey = getPluginComponentsCacheKey(options);
62980
+ const cachedPluginComponents = cachedPluginComponentsByKey.get(cacheKey);
62981
+ if (cachedPluginComponents) {
62982
+ return clonePluginComponentsResult(cachedPluginComponents);
62983
+ }
62949
62984
  const { plugins, errors } = discoverInstalledPlugins(options);
62950
62985
  const [commands, skills, agents, mcpServers, hooksConfigs] = await Promise.all([
62951
62986
  Promise.resolve(loadPluginCommands(plugins)),
@@ -62955,7 +62990,7 @@ async function loadAllPluginComponents(options) {
62955
62990
  Promise.resolve(loadPluginHooksConfigs(plugins))
62956
62991
  ]);
62957
62992
  log(`Loaded ${plugins.length} plugins with ${Object.keys(commands).length} commands, ${Object.keys(skills).length} skills, ${Object.keys(agents).length} agents, ${Object.keys(mcpServers).length} MCP servers`);
62958
- return {
62993
+ const result = {
62959
62994
  commands,
62960
62995
  skills,
62961
62996
  agents,
@@ -62964,6 +62999,8 @@ async function loadAllPluginComponents(options) {
62964
62999
  plugins,
62965
63000
  errors
62966
63001
  };
63002
+ cachedPluginComponentsByKey.set(cacheKey, clonePluginComponentsResult(result));
63003
+ return clonePluginComponentsResult(result);
62967
63004
  }
62968
63005
  // src/shared/plugin-command-discovery.ts
62969
63006
  function discoverPluginCommandDefinitions(options) {
@@ -63478,11 +63515,12 @@ async function injectContinuation(args) {
63478
63515
  } : undefined);
63479
63516
  tools = tools ?? previousMessage?.tools;
63480
63517
  }
63481
- if (agentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(agentName))) {
63518
+ const promptAgent = normalizeAgentForPromptKey(agentName);
63519
+ if (promptAgent && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(promptAgent))) {
63482
63520
  log(`[${HOOK_NAME}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName });
63483
63521
  return;
63484
63522
  }
63485
- if (!agentName) {
63523
+ if (!promptAgent) {
63486
63524
  const compactionState = sessionStateStore.getExistingState(sessionID);
63487
63525
  if (compactionState && isCompactionGuardActive(compactionState, Date.now())) {
63488
63526
  log(`[${HOOK_NAME}] Skipped: agent unknown after compaction`, { sessionID });
@@ -63513,7 +63551,7 @@ ${todoList}`;
63513
63551
  try {
63514
63552
  log(`[${HOOK_NAME}] Injecting continuation`, {
63515
63553
  sessionID,
63516
- agent: agentName,
63554
+ agent: promptAgent,
63517
63555
  model,
63518
63556
  incompleteCount: freshIncompleteCount
63519
63557
  });
@@ -63521,7 +63559,7 @@ ${todoList}`;
63521
63559
  await ctx.client.session.promptAsync({
63522
63560
  path: { id: sessionID },
63523
63561
  body: {
63524
- agent: agentName,
63562
+ agent: promptAgent,
63525
63563
  ...model !== undefined ? { model } : {},
63526
63564
  ...inheritedTools ? { tools: inheritedTools } : {},
63527
63565
  parts: [createInternalAgentTextPart(prompt)]
@@ -82203,6 +82241,7 @@ var RETRYABLE_MESSAGE_PATTERNS = [
82203
82241
  "over limit",
82204
82242
  "overloaded",
82205
82243
  "bad gateway",
82244
+ "bad request",
82206
82245
  "unknown provider",
82207
82246
  "provider not found",
82208
82247
  "model_not_supported",
@@ -82529,13 +82568,15 @@ init_logger();
82529
82568
  import { existsSync as existsSync39 } from "fs";
82530
82569
  import { join as join42 } from "path";
82531
82570
  var CONFIG_CACHE_TTL_MS2 = 30000;
82532
- var USER_CONFIG_PATH = join42(getOpenCodeConfigDir({ binary: "opencode" }), "opencode-cc-plugin.json");
82533
82571
  var configCache2 = new Map;
82572
+ function getUserConfigPath() {
82573
+ return join42(getOpenCodeConfigDir({ binary: "opencode" }), "opencode-cc-plugin.json");
82574
+ }
82534
82575
  function getProjectConfigPath() {
82535
82576
  return join42(process.cwd(), ".opencode", "opencode-cc-plugin.json");
82536
82577
  }
82537
82578
  function getCacheKey2() {
82538
- return process.cwd();
82579
+ return `${process.cwd()}::${getUserConfigPath()}`;
82539
82580
  }
82540
82581
  function getCachedConfig2(cacheKey) {
82541
82582
  const cachedEntry = configCache2.get(cacheKey);
@@ -82582,7 +82623,7 @@ async function loadPluginExtendedConfig() {
82582
82623
  if (cachedConfig) {
82583
82624
  return cachedConfig;
82584
82625
  }
82585
- const userConfig = await loadConfigFromPath(USER_CONFIG_PATH);
82626
+ const userConfig = await loadConfigFromPath(getUserConfigPath());
82586
82627
  const projectConfig = await loadConfigFromPath(getProjectConfigPath());
82587
82628
  const merged = {
82588
82629
  disabledHooks: mergeDisabledHooks(userConfig?.disabledHooks, projectConfig?.disabledHooks)
@@ -84427,26 +84468,33 @@ function getWindowsAppdataDir2() {
84427
84468
  return null;
84428
84469
  return process.env.APPDATA ?? path5.join(os4.homedir(), "AppData", "Roaming");
84429
84470
  }
84430
- var USER_CONFIG_DIR = getOpenCodeConfigDir({ binary: "opencode" });
84431
- var USER_OPENCODE_CONFIG = path5.join(USER_CONFIG_DIR, "opencode.json");
84432
- var USER_OPENCODE_CONFIG_JSONC = path5.join(USER_CONFIG_DIR, "opencode.jsonc");
84471
+ function getUserConfigDir() {
84472
+ return getOpenCodeConfigDir({ binary: "opencode" });
84473
+ }
84474
+ function getUserOpencodeConfig() {
84475
+ return path5.join(getUserConfigDir(), "opencode.json");
84476
+ }
84477
+ function getUserOpencodeConfigJsonc() {
84478
+ return path5.join(getUserConfigDir(), "opencode.jsonc");
84479
+ }
84433
84480
  var INSTALLED_PACKAGE_JSON = path5.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
84434
84481
 
84435
84482
  // src/hooks/auto-update-checker/checker/config-paths.ts
84436
84483
  import * as os5 from "os";
84437
84484
  import * as path6 from "path";
84438
84485
  function getConfigPaths2(directory) {
84486
+ const userConfigDir = getUserConfigDir();
84439
84487
  const paths = [
84440
84488
  path6.join(directory, ".opencode", "opencode.json"),
84441
84489
  path6.join(directory, ".opencode", "opencode.jsonc"),
84442
- USER_OPENCODE_CONFIG,
84443
- USER_OPENCODE_CONFIG_JSONC
84490
+ getUserOpencodeConfig(),
84491
+ getUserOpencodeConfigJsonc()
84444
84492
  ];
84445
84493
  if (process.platform === "win32") {
84446
84494
  const crossPlatformDir = path6.join(os5.homedir(), ".config");
84447
84495
  const appdataDir = getWindowsAppdataDir2();
84448
84496
  if (appdataDir) {
84449
- const alternateDir = USER_CONFIG_DIR === crossPlatformDir ? appdataDir : crossPlatformDir;
84497
+ const alternateDir = userConfigDir === crossPlatformDir ? appdataDir : crossPlatformDir;
84450
84498
  const alternateConfig = path6.join(alternateDir, "opencode", "opencode.json");
84451
84499
  const alternateConfigJsonc = path6.join(alternateDir, "opencode", "opencode.jsonc");
84452
84500
  if (!paths.includes(alternateConfig)) {
@@ -84937,8 +84985,9 @@ function removeFromBunLock(packageName) {
84937
84985
  }
84938
84986
  function invalidatePackage(packageName = PACKAGE_NAME) {
84939
84987
  try {
84988
+ const userConfigDir = getUserConfigDir();
84940
84989
  const pkgDirs = [
84941
- path10.join(USER_CONFIG_DIR, "node_modules", packageName),
84990
+ path10.join(userConfigDir, "node_modules", packageName),
84942
84991
  path10.join(CACHE_DIR, "node_modules", packageName)
84943
84992
  ];
84944
84993
  let packageRemoved = false;
@@ -94744,6 +94793,15 @@ function readBoulderState(directory) {
94744
94793
  if (!Array.isArray(parsed.session_ids)) {
94745
94794
  parsed.session_ids = [];
94746
94795
  }
94796
+ if (!parsed.session_origins || typeof parsed.session_origins !== "object" || Array.isArray(parsed.session_origins)) {
94797
+ parsed.session_origins = {};
94798
+ }
94799
+ if (parsed.session_ids.length === 1) {
94800
+ const soleSessionId = parsed.session_ids[0];
94801
+ if (typeof soleSessionId === "string" && parsed.session_origins[soleSessionId] !== "appended" && parsed.session_origins[soleSessionId] !== "direct") {
94802
+ parsed.session_origins[soleSessionId] = "direct";
94803
+ }
94804
+ }
94747
94805
  if (!parsed.task_sessions || typeof parsed.task_sessions !== "object" || Array.isArray(parsed.task_sessions)) {
94748
94806
  parsed.task_sessions = {};
94749
94807
  }
@@ -94765,22 +94823,34 @@ function writeBoulderState(directory, state3) {
94765
94823
  return false;
94766
94824
  }
94767
94825
  }
94768
- function appendSessionId(directory, sessionId) {
94826
+ function appendSessionId(directory, sessionId, origin = "direct") {
94769
94827
  const state3 = readBoulderState(directory);
94770
94828
  if (!state3)
94771
94829
  return null;
94830
+ if (!state3.session_origins || typeof state3.session_origins !== "object" || Array.isArray(state3.session_origins)) {
94831
+ state3.session_origins = {};
94832
+ }
94772
94833
  if (!state3.session_ids?.includes(sessionId)) {
94773
94834
  if (!Array.isArray(state3.session_ids)) {
94774
94835
  state3.session_ids = [];
94775
94836
  }
94776
94837
  const originalSessionIds = [...state3.session_ids];
94838
+ const originalSessionOrigins = { ...state3.session_origins };
94777
94839
  state3.session_ids.push(sessionId);
94840
+ state3.session_origins[sessionId] = origin;
94778
94841
  if (writeBoulderState(directory, state3)) {
94779
94842
  return state3;
94780
94843
  }
94781
94844
  state3.session_ids = originalSessionIds;
94845
+ state3.session_origins = originalSessionOrigins;
94782
94846
  return null;
94783
94847
  }
94848
+ if (!state3.session_origins[sessionId]) {
94849
+ state3.session_origins[sessionId] = origin;
94850
+ if (!writeBoulderState(directory, state3)) {
94851
+ return null;
94852
+ }
94853
+ }
94784
94854
  return state3;
94785
94855
  }
94786
94856
  function clearBoulderState(directory) {
@@ -94869,6 +94939,9 @@ function createBoulderState(planPath, sessionId, agent, worktreePath) {
94869
94939
  active_plan: planPath,
94870
94940
  started_at: new Date().toISOString(),
94871
94941
  session_ids: [sessionId],
94942
+ session_origins: {
94943
+ [sessionId]: "direct"
94944
+ },
94872
94945
  plan_name: getPlanName(planPath),
94873
94946
  ...agent !== undefined ? { agent } : {},
94874
94947
  ...worktreePath !== undefined ? { worktree_path: worktreePath } : {}
@@ -95500,6 +95573,100 @@ function isAbortError(error48) {
95500
95573
  return false;
95501
95574
  }
95502
95575
 
95576
+ // src/hooks/atlas/session-last-agent.ts
95577
+ import { readFileSync as readFileSync44, readdirSync as readdirSync17 } from "fs";
95578
+ import { join as join68 } from "path";
95579
+ function isCompactionAgent2(agent) {
95580
+ return typeof agent === "string" && agent.toLowerCase() === "compaction";
95581
+ }
95582
+ function getLastAgentFromMessageDir(messageDir) {
95583
+ try {
95584
+ const messages = readdirSync17(messageDir).filter((fileName) => fileName.endsWith(".json")).map((fileName) => {
95585
+ try {
95586
+ const content = readFileSync44(join68(messageDir, fileName), "utf-8");
95587
+ const parsed = JSON.parse(content);
95588
+ return {
95589
+ fileName,
95590
+ agent: parsed.agent,
95591
+ createdAt: typeof parsed.time?.created === "number" ? parsed.time.created : Number.NEGATIVE_INFINITY
95592
+ };
95593
+ } catch {
95594
+ return null;
95595
+ }
95596
+ }).filter((message) => message !== null).sort((left, right) => right.createdAt - left.createdAt || right.fileName.localeCompare(left.fileName));
95597
+ for (const message of messages) {
95598
+ if (typeof message.agent === "string" && !isCompactionAgent2(message.agent)) {
95599
+ return message.agent.toLowerCase();
95600
+ }
95601
+ }
95602
+ } catch {
95603
+ return null;
95604
+ }
95605
+ return null;
95606
+ }
95607
+ async function getLastAgentFromSession(sessionID, client) {
95608
+ if (isSqliteBackend() && client) {
95609
+ try {
95610
+ const response = await client.session.messages({ path: { id: sessionID } });
95611
+ const messages = normalizeSDKResponse(response, [], {
95612
+ preferResponseOnMissingData: true
95613
+ }).sort((left, right) => {
95614
+ const leftTime = left.info?.time?.created ?? Number.NEGATIVE_INFINITY;
95615
+ const rightTime = right.info?.time?.created ?? Number.NEGATIVE_INFINITY;
95616
+ if (leftTime !== rightTime) {
95617
+ return rightTime - leftTime;
95618
+ }
95619
+ const leftId = typeof left.id === "string" ? left.id : "";
95620
+ const rightId = typeof right.id === "string" ? right.id : "";
95621
+ return rightId.localeCompare(leftId);
95622
+ });
95623
+ for (const message of messages) {
95624
+ const agent = message.info?.agent;
95625
+ if (typeof agent === "string" && !isCompactionAgent2(agent)) {
95626
+ return agent.toLowerCase();
95627
+ }
95628
+ }
95629
+ } catch {
95630
+ return null;
95631
+ }
95632
+ return null;
95633
+ }
95634
+ const messageDir = getMessageDir(sessionID);
95635
+ if (!messageDir)
95636
+ return null;
95637
+ return getLastAgentFromMessageDir(messageDir);
95638
+ }
95639
+
95640
+ // src/hooks/atlas/boulder-session-lineage.ts
95641
+ init_logger();
95642
+ async function isSessionInBoulderLineage(input) {
95643
+ const visitedSessionIDs = new Set;
95644
+ let currentSessionID = input.sessionID;
95645
+ while (!visitedSessionIDs.has(currentSessionID)) {
95646
+ visitedSessionIDs.add(currentSessionID);
95647
+ const sessionResult = await input.client.session.get({ path: { id: currentSessionID } }).catch((error48) => {
95648
+ log(`[${HOOK_NAME7}] Failed to resolve session lineage`, {
95649
+ sessionID: input.sessionID,
95650
+ currentSessionID,
95651
+ error: error48
95652
+ });
95653
+ return null;
95654
+ });
95655
+ if (!sessionResult || sessionResult.error) {
95656
+ return false;
95657
+ }
95658
+ const parentSessionID = sessionResult.data?.parentID;
95659
+ if (!parentSessionID) {
95660
+ return false;
95661
+ }
95662
+ if (input.boulderSessionIDs.includes(parentSessionID)) {
95663
+ return true;
95664
+ }
95665
+ currentSessionID = parentSessionID;
95666
+ }
95667
+ return false;
95668
+ }
95669
+
95503
95670
  // src/hooks/atlas/idle-event.ts
95504
95671
  init_logger();
95505
95672
 
@@ -95696,9 +95863,17 @@ If you were given **multiple genuinely independent goals** (unrelated tasks, par
95696
95863
  async function resolveRecentPromptContextForSession(ctx, sessionID) {
95697
95864
  try {
95698
95865
  const messagesResp = await ctx.client.session.messages({ path: { id: sessionID } });
95699
- const messages = normalizeSDKResponse(messagesResp, []);
95700
- for (let i2 = messages.length - 1;i2 >= 0; i2--) {
95701
- const info = messages[i2].info;
95866
+ const messages = normalizeSDKResponse(messagesResp, []).sort((left, right) => {
95867
+ const leftTime = left.info?.time?.created ?? Number.NEGATIVE_INFINITY;
95868
+ const rightTime = right.info?.time?.created ?? Number.NEGATIVE_INFINITY;
95869
+ if (leftTime !== rightTime)
95870
+ return rightTime - leftTime;
95871
+ const leftId = typeof left.id === "string" ? left.id : "";
95872
+ const rightId = typeof right.id === "string" ? right.id : "";
95873
+ return rightId.localeCompare(leftId);
95874
+ });
95875
+ for (const message of messages) {
95876
+ const info = message.info;
95702
95877
  const model2 = info?.model;
95703
95878
  const tools2 = normalizePromptTools(info?.tools);
95704
95879
  if (model2?.providerID && model2?.modelID) {
@@ -95742,7 +95917,7 @@ async function injectBoulderContinuation(input) {
95742
95917
  const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
95743
95918
  if (hasRunningBgTasks) {
95744
95919
  log(`[${HOOK_NAME7}] Skipped injection: background tasks running`, { sessionID });
95745
- return;
95920
+ return "skipped_background_tasks";
95746
95921
  }
95747
95922
  const worktreeContext = worktreePath ? `
95748
95923
 
@@ -95759,7 +95934,7 @@ async function injectBoulderContinuation(input) {
95759
95934
  sessionID,
95760
95935
  agent: continuationAgent ?? agent ?? "unknown"
95761
95936
  });
95762
- return;
95937
+ return "skipped_agent_unavailable";
95763
95938
  }
95764
95939
  try {
95765
95940
  log(`[${HOOK_NAME7}] Injecting boulder continuation`, { sessionID, planName, remaining });
@@ -95768,7 +95943,7 @@ async function injectBoulderContinuation(input) {
95768
95943
  await ctx.client.session.promptAsync({
95769
95944
  path: { id: sessionID },
95770
95945
  body: {
95771
- agent: normalizeAgentForPrompt(continuationAgent) ?? continuationAgent,
95946
+ agent: continuationAgent,
95772
95947
  ...promptContext.model !== undefined ? { model: promptContext.model } : {},
95773
95948
  ...inheritedTools ? { tools: inheritedTools } : {},
95774
95949
  parts: [createInternalAgentTextPart(prompt)]
@@ -95777,6 +95952,7 @@ async function injectBoulderContinuation(input) {
95777
95952
  });
95778
95953
  sessionState.promptFailureCount = 0;
95779
95954
  log(`[${HOOK_NAME7}] Boulder continuation injected`, { sessionID });
95955
+ return "injected";
95780
95956
  } catch (err) {
95781
95957
  sessionState.promptFailureCount += 1;
95782
95958
  sessionState.lastFailureAt = Date.now();
@@ -95785,72 +95961,24 @@ async function injectBoulderContinuation(input) {
95785
95961
  error: String(err),
95786
95962
  promptFailureCount: sessionState.promptFailureCount
95787
95963
  });
95964
+ return "failed";
95788
95965
  }
95789
95966
  }
95790
95967
 
95791
- // src/hooks/atlas/boulder-session-lineage.ts
95792
- init_logger();
95793
- async function isSessionInBoulderLineage(input) {
95794
- const visitedSessionIDs = new Set;
95795
- let currentSessionID = input.sessionID;
95796
- while (!visitedSessionIDs.has(currentSessionID)) {
95797
- visitedSessionIDs.add(currentSessionID);
95798
- const sessionResult = await input.client.session.get({ path: { id: currentSessionID } }).catch((error48) => {
95799
- log(`[${HOOK_NAME7}] Failed to resolve session lineage`, {
95800
- sessionID: input.sessionID,
95801
- currentSessionID,
95802
- error: error48
95803
- });
95804
- return null;
95805
- });
95806
- if (!sessionResult || sessionResult.error) {
95807
- return false;
95808
- }
95809
- const parentSessionID = sessionResult.data?.parentID;
95810
- if (!parentSessionID) {
95811
- return false;
95812
- }
95813
- if (input.boulderSessionIDs.includes(parentSessionID)) {
95814
- return true;
95815
- }
95816
- currentSessionID = parentSessionID;
95817
- }
95818
- return false;
95819
- }
95820
-
95821
95968
  // src/hooks/atlas/resolve-active-boulder-session.ts
95822
95969
  async function resolveActiveBoulderSession(input) {
95823
95970
  const boulderState = readBoulderState(input.directory);
95824
95971
  if (!boulderState) {
95825
95972
  return null;
95826
95973
  }
95974
+ if (!boulderState.session_ids.includes(input.sessionID)) {
95975
+ return null;
95976
+ }
95827
95977
  const progress = getPlanProgress(boulderState.active_plan);
95828
95978
  if (progress.isComplete) {
95829
95979
  return { boulderState, progress, appendedSession: false };
95830
95980
  }
95831
- if (boulderState.session_ids.includes(input.sessionID)) {
95832
- return { boulderState, progress, appendedSession: false };
95833
- }
95834
- if (!subagentSessions.has(input.sessionID)) {
95835
- return null;
95836
- }
95837
- const belongsToActiveBoulder = await isSessionInBoulderLineage({
95838
- client: input.client,
95839
- sessionID: input.sessionID,
95840
- boulderSessionIDs: boulderState.session_ids
95841
- });
95842
- if (!belongsToActiveBoulder) {
95843
- return null;
95844
- }
95845
- const updatedBoulderState = appendSessionId(input.directory, input.sessionID);
95846
- if (!updatedBoulderState?.session_ids.includes(input.sessionID)) {
95847
- return null;
95848
- }
95849
- return {
95850
- boulderState: updatedBoulderState,
95851
- progress,
95852
- appendedSession: true
95853
- };
95981
+ return { boulderState, progress, appendedSession: false };
95854
95982
  }
95855
95983
 
95856
95984
  // src/hooks/atlas/idle-event.ts
@@ -95864,12 +95992,38 @@ function hasRunningBackgroundTasks(sessionID, options) {
95864
95992
  }
95865
95993
  async function injectContinuation2(input) {
95866
95994
  const remaining = input.progress.total - input.progress.completed;
95867
- input.sessionState.lastContinuationInjectedAt = Date.now();
95995
+ if (input.sessionState.isInjectingContinuation) {
95996
+ scheduleRetry({
95997
+ ctx: input.ctx,
95998
+ sessionID: input.sessionID,
95999
+ sessionState: input.sessionState,
96000
+ options: input.options
96001
+ });
96002
+ return;
96003
+ }
96004
+ input.sessionState.isInjectingContinuation = true;
95868
96005
  try {
95869
96006
  const currentBoulder = readBoulderState(input.ctx.directory);
95870
96007
  const currentTask = currentBoulder ? readCurrentTopLevelTask(currentBoulder.active_plan) : null;
95871
96008
  const preferredTaskSession = currentTask ? getTaskSessionState(input.ctx.directory, currentTask.key) : null;
95872
- await injectBoulderContinuation({
96009
+ if (!currentBoulder) {
96010
+ return;
96011
+ }
96012
+ const canContinueSession = await canContinueTrackedBoulderSession({
96013
+ client: input.ctx.client,
96014
+ sessionID: input.sessionID,
96015
+ sessionOrigin: currentBoulder.session_origins?.[input.sessionID],
96016
+ boulderSessionIDs: currentBoulder.session_ids,
96017
+ requiredAgent: currentBoulder.agent
96018
+ });
96019
+ if (!canContinueSession) {
96020
+ log(`[${HOOK_NAME7}] Skipped: tracked descendant agent does not match boulder agent`, {
96021
+ sessionID: input.sessionID,
96022
+ requiredAgent: currentBoulder.agent ?? "atlas"
96023
+ });
96024
+ return;
96025
+ }
96026
+ const result = await injectBoulderContinuation({
95873
96027
  ctx: input.ctx,
95874
96028
  sessionID: input.sessionID,
95875
96029
  planName: input.planName,
@@ -95882,9 +96036,43 @@ async function injectContinuation2(input) {
95882
96036
  backgroundManager: input.options?.backgroundManager,
95883
96037
  sessionState: input.sessionState
95884
96038
  });
96039
+ if (result === "injected") {
96040
+ if (input.sessionState.pendingRetryTimer) {
96041
+ clearTimeout(input.sessionState.pendingRetryTimer);
96042
+ input.sessionState.pendingRetryTimer = undefined;
96043
+ }
96044
+ input.sessionState.lastContinuationInjectedAt = Date.now();
96045
+ return;
96046
+ }
96047
+ if (result === "skipped_background_tasks") {
96048
+ scheduleRetry({
96049
+ ctx: input.ctx,
96050
+ sessionID: input.sessionID,
96051
+ sessionState: input.sessionState,
96052
+ options: input.options
96053
+ });
96054
+ return;
96055
+ }
96056
+ if (result === "failed") {
96057
+ scheduleRetry({
96058
+ ctx: input.ctx,
96059
+ sessionID: input.sessionID,
96060
+ sessionState: input.sessionState,
96061
+ options: input.options
96062
+ });
96063
+ }
95885
96064
  } catch (error48) {
95886
96065
  log(`[${HOOK_NAME7}] Failed to inject boulder continuation`, { sessionID: input.sessionID, error: error48 });
95887
96066
  input.sessionState.promptFailureCount += 1;
96067
+ input.sessionState.lastFailureAt = Date.now();
96068
+ scheduleRetry({
96069
+ ctx: input.ctx,
96070
+ sessionID: input.sessionID,
96071
+ sessionState: input.sessionState,
96072
+ options: input.options
96073
+ });
96074
+ } finally {
96075
+ input.sessionState.isInjectingContinuation = false;
95888
96076
  }
95889
96077
  }
95890
96078
  function scheduleRetry(input) {
@@ -95898,6 +96086,10 @@ function scheduleRetry(input) {
95898
96086
  return;
95899
96087
  if (sessionState.waitingForFinalWaveApproval)
95900
96088
  return;
96089
+ const now = Date.now();
96090
+ if (sessionState.lastContinuationInjectedAt && now - sessionState.lastContinuationInjectedAt < CONTINUATION_COOLDOWN_MS2) {
96091
+ return;
96092
+ }
95901
96093
  const currentBoulder = readBoulderState(ctx.directory);
95902
96094
  if (!currentBoulder)
95903
96095
  return;
@@ -95908,8 +96100,19 @@ function scheduleRetry(input) {
95908
96100
  return;
95909
96101
  if (options?.isContinuationStopped?.(sessionID))
95910
96102
  return;
95911
- if (hasRunningBackgroundTasks(sessionID, options))
96103
+ const canContinueSession = await canContinueTrackedBoulderSession({
96104
+ client: ctx.client,
96105
+ sessionID,
96106
+ sessionOrigin: currentBoulder.session_origins?.[sessionID],
96107
+ boulderSessionIDs: currentBoulder.session_ids,
96108
+ requiredAgent: currentBoulder.agent
96109
+ });
96110
+ if (!canContinueSession)
95912
96111
  return;
96112
+ if (hasRunningBackgroundTasks(sessionID, options)) {
96113
+ scheduleRetry({ ctx, sessionID, sessionState, options });
96114
+ return;
96115
+ }
95913
96116
  await injectContinuation2({
95914
96117
  ctx,
95915
96118
  sessionID,
@@ -95945,27 +96148,19 @@ async function handleAtlasSessionIdle(input) {
95945
96148
  plan: boulderState.plan_name
95946
96149
  });
95947
96150
  }
95948
- if (subagentSessions.has(sessionID)) {
95949
- const sessionAgent = getSessionAgent(sessionID);
95950
- const agentKey = getAgentConfigKey(sessionAgent ?? "");
95951
- const requiredAgentName = boulderState.agent ?? (isAgentRegistered("atlas") ? "atlas" : undefined);
95952
- if (!requiredAgentName || !isAgentRegistered(requiredAgentName)) {
95953
- log(`[${HOOK_NAME7}] Skipped: boulder agent is unavailable for continuation`, {
95954
- sessionID,
95955
- requiredAgent: boulderState.agent ?? "unknown"
95956
- });
95957
- return;
95958
- }
95959
- const requiredAgentKey = getAgentConfigKey(requiredAgentName);
95960
- const agentMatches = agentKey === requiredAgentKey || requiredAgentKey === getAgentConfigKey("atlas") && agentKey === getAgentConfigKey("sisyphus");
95961
- if (!agentMatches) {
95962
- log(`[${HOOK_NAME7}] Skipped: subagent agent does not match boulder agent`, {
95963
- sessionID,
95964
- agent: sessionAgent ?? "unknown",
95965
- requiredAgent: requiredAgentName
95966
- });
95967
- return;
95968
- }
96151
+ const canContinueSession = await canContinueTrackedBoulderSession({
96152
+ client: ctx.client,
96153
+ sessionID,
96154
+ sessionOrigin: boulderState.session_origins?.[sessionID],
96155
+ boulderSessionIDs: boulderState.session_ids,
96156
+ requiredAgent: boulderState.agent
96157
+ });
96158
+ if (!canContinueSession) {
96159
+ log(`[${HOOK_NAME7}] Skipped: tracked descendant agent does not match boulder agent`, {
96160
+ sessionID,
96161
+ requiredAgent: boulderState.agent ?? "atlas"
96162
+ });
96163
+ return;
95969
96164
  }
95970
96165
  const sessionState = getState(sessionID);
95971
96166
  const now = Date.now();
@@ -95992,6 +96187,7 @@ async function handleAtlasSessionIdle(input) {
95992
96187
  sessionState.lastFailureAt = undefined;
95993
96188
  }
95994
96189
  if (hasRunningBackgroundTasks(sessionID, options)) {
96190
+ scheduleRetry({ ctx, sessionID, sessionState, options });
95995
96191
  log(`[${HOOK_NAME7}] Skipped: background tasks running`, { sessionID });
95996
96192
  return;
95997
96193
  }
@@ -96019,6 +96215,30 @@ async function handleAtlasSessionIdle(input) {
96019
96215
  worktreePath: boulderState.worktree_path
96020
96216
  });
96021
96217
  }
96218
+ async function canContinueTrackedBoulderSession(input) {
96219
+ const ancestorSessionIDs = input.boulderSessionIDs.filter((trackedSessionID) => trackedSessionID !== input.sessionID);
96220
+ if (ancestorSessionIDs.length === 0) {
96221
+ return true;
96222
+ }
96223
+ const isTrackedDescendant = await isSessionInBoulderLineage({
96224
+ client: input.client,
96225
+ sessionID: input.sessionID,
96226
+ boulderSessionIDs: ancestorSessionIDs
96227
+ });
96228
+ if (input.sessionOrigin === "direct") {
96229
+ return true;
96230
+ }
96231
+ if (!isTrackedDescendant) {
96232
+ return false;
96233
+ }
96234
+ const sessionAgent = await getLastAgentFromSession(input.sessionID, input.client) ?? getSessionAgent(input.sessionID);
96235
+ if (!sessionAgent) {
96236
+ return false;
96237
+ }
96238
+ const requiredAgentKey = getAgentConfigKey(input.requiredAgent ?? "atlas");
96239
+ const sessionAgentKey = getAgentConfigKey(sessionAgent);
96240
+ return sessionAgentKey === requiredAgentKey || requiredAgentKey === getAgentConfigKey("atlas") && sessionAgentKey === getAgentConfigKey("sisyphus");
96241
+ }
96022
96242
 
96023
96243
  // src/hooks/atlas/event-handler.ts
96024
96244
  function createAtlasEventHandler(input) {
@@ -96108,8 +96328,141 @@ function createAtlasEventHandler(input) {
96108
96328
  // src/hooks/atlas/tool-execute-after.ts
96109
96329
  init_logger();
96110
96330
 
96331
+ // src/hooks/atlas/background-launch-session-tracking.ts
96332
+ init_logger();
96333
+
96334
+ // src/hooks/atlas/subagent-session-id.ts
96335
+ init_logger();
96336
+ function extractSessionIdFromMetadata(metadata) {
96337
+ if (metadata && typeof metadata === "object" && "sessionId" in metadata) {
96338
+ const value = metadata.sessionId;
96339
+ if (typeof value === "string" && value.startsWith("ses_")) {
96340
+ return value;
96341
+ }
96342
+ }
96343
+ return;
96344
+ }
96345
+ function extractSessionIdFromOutput(output) {
96346
+ const taskMetadataBlocks = [...output.matchAll(/<task_metadata>([\s\S]*?)<\/task_metadata>/gi)];
96347
+ const lastTaskMetadataBlock = taskMetadataBlocks.at(-1)?.[1];
96348
+ if (lastTaskMetadataBlock) {
96349
+ const taskMetadataSessionMatch = lastTaskMetadataBlock.match(/session_id:\s*(ses_[a-zA-Z0-9_-]+)/i);
96350
+ if (taskMetadataSessionMatch) {
96351
+ return taskMetadataSessionMatch[1];
96352
+ }
96353
+ }
96354
+ const explicitSessionMatches = [...output.matchAll(/Session ID:\s*(ses_[a-zA-Z0-9_-]+)/g)];
96355
+ return explicitSessionMatches.at(-1)?.[1];
96356
+ }
96357
+ async function validateSubagentSessionId(input) {
96358
+ if (!input.sessionID || input.lineageSessionIDs.length === 0) {
96359
+ return;
96360
+ }
96361
+ const belongsToLineage = await isSessionInBoulderLineage({
96362
+ client: input.client,
96363
+ sessionID: input.sessionID,
96364
+ boulderSessionIDs: input.lineageSessionIDs
96365
+ });
96366
+ if (!belongsToLineage) {
96367
+ log(`[${HOOK_NAME7}] Ignoring extracted session id outside active lineage`, {
96368
+ sessionID: input.sessionID,
96369
+ lineageSessionIDs: input.lineageSessionIDs
96370
+ });
96371
+ return;
96372
+ }
96373
+ return input.sessionID;
96374
+ }
96375
+
96376
+ // src/hooks/atlas/task-context.ts
96377
+ function resolvePreferredSessionId(currentSessionId, trackedSessionId) {
96378
+ return currentSessionId ?? trackedSessionId ?? "<session_id>";
96379
+ }
96380
+ function resolveTaskContext(pendingTaskRef, planPath) {
96381
+ if (!pendingTaskRef) {
96382
+ return {
96383
+ currentTask: readCurrentTopLevelTask(planPath),
96384
+ shouldSkipTaskSessionUpdate: false,
96385
+ shouldIgnoreCurrentSessionId: false
96386
+ };
96387
+ }
96388
+ if (pendingTaskRef.kind === "track") {
96389
+ return {
96390
+ currentTask: pendingTaskRef.task,
96391
+ shouldSkipTaskSessionUpdate: false,
96392
+ shouldIgnoreCurrentSessionId: false
96393
+ };
96394
+ }
96395
+ if (pendingTaskRef.reason === "explicit_resume") {
96396
+ return {
96397
+ currentTask: readCurrentTopLevelTask(planPath),
96398
+ shouldSkipTaskSessionUpdate: true,
96399
+ shouldIgnoreCurrentSessionId: true
96400
+ };
96401
+ }
96402
+ return {
96403
+ currentTask: pendingTaskRef.task,
96404
+ shouldSkipTaskSessionUpdate: true,
96405
+ shouldIgnoreCurrentSessionId: true
96406
+ };
96407
+ }
96408
+
96409
+ // src/hooks/atlas/background-launch-session-tracking.ts
96410
+ async function syncBackgroundLaunchSessionTracking(input) {
96411
+ const { ctx, boulderState, toolInput, toolOutput, pendingTaskRef, metadataSessionId } = input;
96412
+ if (!boulderState) {
96413
+ return;
96414
+ }
96415
+ const extractedSessionId = metadataSessionId ?? extractSessionIdFromOutput(toolOutput.output);
96416
+ const lineageSessionIDs = boulderState.session_ids;
96417
+ const subagentSessionId = await validateSubagentSessionId({
96418
+ client: ctx.client,
96419
+ sessionID: extractedSessionId,
96420
+ lineageSessionIDs
96421
+ });
96422
+ const trackedSessionId = subagentSessionId ?? await resolveFallbackTrackedSessionId({
96423
+ ctx,
96424
+ extractedSessionId,
96425
+ lineageSessionIDs
96426
+ });
96427
+ if (!trackedSessionId) {
96428
+ return;
96429
+ }
96430
+ appendSessionId(ctx.directory, trackedSessionId, "appended");
96431
+ const { currentTask, shouldSkipTaskSessionUpdate } = resolveTaskContext(pendingTaskRef, boulderState.active_plan);
96432
+ if (currentTask && !shouldSkipTaskSessionUpdate) {
96433
+ upsertTaskSessionState(ctx.directory, {
96434
+ taskKey: currentTask.key,
96435
+ taskLabel: currentTask.label,
96436
+ taskTitle: currentTask.title,
96437
+ sessionId: trackedSessionId,
96438
+ agent: typeof toolOutput.metadata?.agent === "string" ? toolOutput.metadata.agent : undefined,
96439
+ category: typeof toolOutput.metadata?.category === "string" ? toolOutput.metadata.category : undefined
96440
+ });
96441
+ }
96442
+ log(`[${HOOK_NAME7}] Background launch session tracked`, {
96443
+ sessionID: toolInput.sessionID,
96444
+ subagentSessionId: trackedSessionId,
96445
+ taskKey: currentTask?.key
96446
+ });
96447
+ }
96448
+ async function resolveFallbackTrackedSessionId(input) {
96449
+ if (!input.extractedSessionId) {
96450
+ return;
96451
+ }
96452
+ try {
96453
+ const session = await input.ctx.client.session.get({ path: { id: input.extractedSessionId } });
96454
+ const parentSessionId = session.data?.parentID;
96455
+ if (typeof parentSessionId === "string" && input.lineageSessionIDs.includes(parentSessionId)) {
96456
+ return input.extractedSessionId;
96457
+ }
96458
+ return;
96459
+ } catch {
96460
+ return;
96461
+ }
96462
+ }
96463
+
96111
96464
  // src/hooks/atlas/final-wave-plan-state.ts
96112
- import { existsSync as existsSync61, readFileSync as readFileSync44 } from "fs";
96465
+ import { existsSync as existsSync61, readFileSync as readFileSync45 } from "fs";
96113
96466
  var TODO_HEADING_PATTERN2 = /^##\s+TODOs\b/i;
96114
96467
  var FINAL_VERIFICATION_HEADING_PATTERN2 = /^##\s+Final Verification Wave\b/i;
96115
96468
  var SECOND_LEVEL_HEADING_PATTERN2 = /^##\s+/;
@@ -96121,7 +96474,7 @@ function readFinalWavePlanState(planPath) {
96121
96474
  return null;
96122
96475
  }
96123
96476
  try {
96124
- const content = readFileSync44(planPath, "utf-8");
96477
+ const content = readFileSync45(planPath, "utf-8");
96125
96478
  const lines = content.split(/\r?\n/);
96126
96479
  let section = "other";
96127
96480
  let pendingImplementationTaskCount = 0;
@@ -96190,48 +96543,6 @@ function isSisyphusPath(filePath) {
96190
96543
  return /\.sisyphus[/\\]/.test(filePath);
96191
96544
  }
96192
96545
 
96193
- // src/hooks/atlas/subagent-session-id.ts
96194
- init_logger();
96195
- function extractSessionIdFromMetadata(metadata) {
96196
- if (metadata && typeof metadata === "object" && "sessionId" in metadata) {
96197
- const value = metadata.sessionId;
96198
- if (typeof value === "string" && value.startsWith("ses_")) {
96199
- return value;
96200
- }
96201
- }
96202
- return;
96203
- }
96204
- function extractSessionIdFromOutput(output) {
96205
- const taskMetadataBlocks = [...output.matchAll(/<task_metadata>([\s\S]*?)<\/task_metadata>/gi)];
96206
- const lastTaskMetadataBlock = taskMetadataBlocks.at(-1)?.[1];
96207
- if (lastTaskMetadataBlock) {
96208
- const taskMetadataSessionMatch = lastTaskMetadataBlock.match(/session_id:\s*(ses_[a-zA-Z0-9_-]+)/i);
96209
- if (taskMetadataSessionMatch) {
96210
- return taskMetadataSessionMatch[1];
96211
- }
96212
- }
96213
- const explicitSessionMatches = [...output.matchAll(/Session ID:\s*(ses_[a-zA-Z0-9_-]+)/g)];
96214
- return explicitSessionMatches.at(-1)?.[1];
96215
- }
96216
- async function validateSubagentSessionId(input) {
96217
- if (!input.sessionID || input.lineageSessionIDs.length === 0) {
96218
- return;
96219
- }
96220
- const belongsToLineage = await isSessionInBoulderLineage({
96221
- client: input.client,
96222
- sessionID: input.sessionID,
96223
- boulderSessionIDs: input.lineageSessionIDs
96224
- });
96225
- if (!belongsToLineage) {
96226
- log(`[${HOOK_NAME7}] Ignoring extracted session id outside active lineage`, {
96227
- sessionID: input.sessionID,
96228
- lineageSessionIDs: input.lineageSessionIDs
96229
- });
96230
- return;
96231
- }
96232
- return input.sessionID;
96233
- }
96234
-
96235
96546
  // src/hooks/atlas/verification-reminders.ts
96236
96547
  function buildReuseHint(sessionId) {
96237
96548
  return `
@@ -96412,37 +96723,6 @@ function isWriteOrEditToolName(toolName) {
96412
96723
  }
96413
96724
 
96414
96725
  // src/hooks/atlas/tool-execute-after.ts
96415
- function resolvePreferredSessionId(currentSessionId, trackedSessionId) {
96416
- return currentSessionId ?? trackedSessionId ?? "<session_id>";
96417
- }
96418
- function resolveTaskContext(pendingTaskRef, planPath) {
96419
- if (!pendingTaskRef) {
96420
- return {
96421
- currentTask: readCurrentTopLevelTask(planPath),
96422
- shouldSkipTaskSessionUpdate: false,
96423
- shouldIgnoreCurrentSessionId: false
96424
- };
96425
- }
96426
- if (pendingTaskRef.kind === "track") {
96427
- return {
96428
- currentTask: pendingTaskRef.task,
96429
- shouldSkipTaskSessionUpdate: false,
96430
- shouldIgnoreCurrentSessionId: false
96431
- };
96432
- }
96433
- if (pendingTaskRef.reason === "explicit_resume") {
96434
- return {
96435
- currentTask: readCurrentTopLevelTask(planPath),
96436
- shouldSkipTaskSessionUpdate: true,
96437
- shouldIgnoreCurrentSessionId: true
96438
- };
96439
- }
96440
- return {
96441
- currentTask: pendingTaskRef.task,
96442
- shouldSkipTaskSessionUpdate: true,
96443
- shouldIgnoreCurrentSessionId: true
96444
- };
96445
- }
96446
96726
  function createToolExecuteAfterHandler2(input) {
96447
96727
  const { ctx, pendingFilePaths, pendingTaskRefs, autoCommit, getState } = input;
96448
96728
  return async (toolInput, toolOutput) => {
@@ -96480,12 +96760,20 @@ function createToolExecuteAfterHandler2(input) {
96480
96760
  if (toolInput.callID) {
96481
96761
  pendingTaskRefs.delete(toolInput.callID);
96482
96762
  }
96763
+ const boulderState = readBoulderState(ctx.directory);
96483
96764
  const isBackgroundLaunch = outputStr.includes("Background task launched") || outputStr.includes("Background task continued") || outputStr.includes("Background delegate launched") || outputStr.includes("Background agent task launched");
96484
96765
  if (isBackgroundLaunch) {
96766
+ await syncBackgroundLaunchSessionTracking({
96767
+ ctx,
96768
+ boulderState,
96769
+ toolInput,
96770
+ toolOutput,
96771
+ pendingTaskRef,
96772
+ metadataSessionId
96773
+ });
96485
96774
  return;
96486
96775
  }
96487
96776
  if (toolOutput.output && typeof toolOutput.output === "string") {
96488
- const boulderState = readBoulderState(ctx.directory);
96489
96777
  const worktreePath = boulderState?.worktree_path?.trim();
96490
96778
  const verificationDirectory = worktreePath ? worktreePath : ctx.directory;
96491
96779
  const gitStats = collectGitDiffStats(verificationDirectory);
@@ -96500,14 +96788,7 @@ function createToolExecuteAfterHandler2(input) {
96500
96788
  } = resolveTaskContext(pendingTaskRef, boulderState.active_plan);
96501
96789
  const trackedTaskSession = currentTask ? getTaskSessionState(ctx.directory, currentTask.key) : null;
96502
96790
  const sessionState = toolInput.sessionID ? getState(toolInput.sessionID) : undefined;
96503
- if (toolInput.sessionID && !boulderState.session_ids?.includes(toolInput.sessionID)) {
96504
- appendSessionId(ctx.directory, toolInput.sessionID);
96505
- log(`[${HOOK_NAME7}] Appended session to boulder`, {
96506
- sessionID: toolInput.sessionID,
96507
- plan: boulderState.plan_name
96508
- });
96509
- }
96510
- const lineageSessionIDs = toolInput.sessionID && !boulderState.session_ids.includes(toolInput.sessionID) ? [...boulderState.session_ids, toolInput.sessionID] : boulderState.session_ids;
96791
+ const lineageSessionIDs = boulderState.session_ids;
96511
96792
  const subagentSessionId = await validateSubagentSessionId({
96512
96793
  client: ctx.client,
96513
96794
  sessionID: extractedSessionId,
@@ -97000,7 +97281,7 @@ function clearSessionModel(sessionID) {
97000
97281
  }
97001
97282
 
97002
97283
  // src/hooks/compaction-context-injector/session-id.ts
97003
- function isCompactionAgent2(agent) {
97284
+ function isCompactionAgent3(agent) {
97004
97285
  return agent?.trim().toLowerCase() === "compaction";
97005
97286
  }
97006
97287
  function resolveSessionID(props) {
@@ -97009,7 +97290,7 @@ function resolveSessionID(props) {
97009
97290
 
97010
97291
  // src/hooks/compaction-context-injector/validated-model.ts
97011
97292
  function resolveValidatedModel(info) {
97012
- if (isCompactionAgent2(info?.agent)) {
97293
+ if (isCompactionAgent3(info?.agent)) {
97013
97294
  return;
97014
97295
  }
97015
97296
  const providerID = info?.model?.providerID ?? info?.providerID;
@@ -97043,7 +97324,7 @@ async function resolveSessionPromptConfig(ctx, sessionID) {
97043
97324
  });
97044
97325
  for (let index = messages.length - 1;index >= 0; index--) {
97045
97326
  const info = messages[index].info;
97046
- if (!promptConfig.agent && info?.agent && !isCompactionAgent2(info.agent)) {
97327
+ if (!promptConfig.agent && info?.agent && !isCompactionAgent3(info.agent)) {
97047
97328
  promptConfig.agent = info.agent;
97048
97329
  }
97049
97330
  if (!promptConfig.model) {
@@ -97134,7 +97415,7 @@ function trackAssistantOutput(state3, messageID) {
97134
97415
  init_logger();
97135
97416
 
97136
97417
  // src/hooks/compaction-context-injector/recovery-prompt-config.ts
97137
- function isCompactionAgent3(agent) {
97418
+ function isCompactionAgent4(agent) {
97138
97419
  return agent?.trim().toLowerCase() === "compaction";
97139
97420
  }
97140
97421
  function matchesExpectedModel(actualModel, expectedModel) {
@@ -97167,7 +97448,7 @@ function createExpectedRecoveryPromptConfig(checkpoint, currentPromptConfig) {
97167
97448
  }
97168
97449
  function isPromptConfigRecovered(actualPromptConfig, expectedPromptConfig) {
97169
97450
  const actualAgent = actualPromptConfig.agent;
97170
- const agentMatches = typeof actualAgent === "string" && !isCompactionAgent3(actualAgent) && actualAgent.toLowerCase() === expectedPromptConfig.agent.toLowerCase();
97451
+ const agentMatches = typeof actualAgent === "string" && !isCompactionAgent4(actualAgent) && actualAgent.toLowerCase() === expectedPromptConfig.agent.toLowerCase();
97171
97452
  return agentMatches && matchesExpectedModel(actualPromptConfig.model, expectedPromptConfig.model) && matchesExpectedTools(actualPromptConfig.tools, expectedPromptConfig.tools);
97172
97453
  }
97173
97454
 
@@ -98121,12 +98402,12 @@ import * as fs18 from "fs";
98121
98402
  import * as path11 from "path";
98122
98403
  // src/shared/migrate-legacy-config-file.ts
98123
98404
  init_logger();
98124
- import { existsSync as existsSync62, readFileSync as readFileSync45, renameSync as renameSync4, rmSync as rmSync3 } from "fs";
98125
- import { join as join68, dirname as dirname20, basename as basename9 } from "path";
98405
+ import { existsSync as existsSync62, readFileSync as readFileSync46, renameSync as renameSync4, rmSync as rmSync3 } from "fs";
98406
+ import { join as join69, dirname as dirname20, basename as basename9 } from "path";
98126
98407
  function buildCanonicalPath(legacyPath) {
98127
98408
  const dir = dirname20(legacyPath);
98128
98409
  const ext = basename9(legacyPath).includes(".jsonc") ? ".jsonc" : ".json";
98129
- return join68(dir, `${CONFIG_BASENAME}${ext}`);
98410
+ return join69(dir, `${CONFIG_BASENAME}${ext}`);
98130
98411
  }
98131
98412
  function archiveLegacyConfigFile(legacyPath) {
98132
98413
  const backupPath = `${legacyPath}.bak`;
@@ -98166,7 +98447,7 @@ function migrateLegacyConfigFile(legacyPath) {
98166
98447
  if (existsSync62(canonicalPath))
98167
98448
  return false;
98168
98449
  try {
98169
- const content = readFileSync45(legacyPath, "utf-8");
98450
+ const content = readFileSync46(legacyPath, "utf-8");
98170
98451
  writeFileAtomically(canonicalPath, content);
98171
98452
  const archivedLegacyConfig = archiveLegacyConfigFile(legacyPath);
98172
98453
  log("[migrateLegacyConfigFile] Migrated legacy config to canonical path", {
@@ -98309,7 +98590,7 @@ function mergeConfigs(base, override) {
98309
98590
  function loadPluginConfig(directory, ctx) {
98310
98591
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
98311
98592
  const userDetected = detectPluginConfigFile(configDir);
98312
- let userConfigPath = userDetected.format !== "none" ? userDetected.path : path11.join(configDir, "oh-my-opencode.json");
98593
+ let userConfigPath = userDetected.format !== "none" ? userDetected.path : path11.join(configDir, `${CONFIG_BASENAME}.json`);
98313
98594
  if (userDetected.legacyPath) {
98314
98595
  log("Canonical plugin config detected alongside legacy config. Remove the legacy file to avoid confusion.", {
98315
98596
  canonicalPath: userDetected.path,
@@ -98317,12 +98598,15 @@ function loadPluginConfig(directory, ctx) {
98317
98598
  });
98318
98599
  }
98319
98600
  if (userDetected.format !== "none" && path11.basename(userDetected.path).startsWith(LEGACY_CONFIG_BASENAME)) {
98320
- migrateLegacyConfigFile(userDetected.path);
98321
- userConfigPath = path11.join(path11.dirname(userDetected.path), `${CONFIG_BASENAME}${path11.extname(userDetected.path)}`);
98601
+ const migrated = migrateLegacyConfigFile(userDetected.path);
98602
+ const canonicalPath = path11.join(path11.dirname(userDetected.path), `${CONFIG_BASENAME}${path11.extname(userDetected.path)}`);
98603
+ if (migrated || fs18.existsSync(canonicalPath)) {
98604
+ userConfigPath = canonicalPath;
98605
+ }
98322
98606
  }
98323
98607
  const projectBasePath = path11.join(directory, ".opencode");
98324
98608
  const projectDetected = detectPluginConfigFile(projectBasePath);
98325
- let projectConfigPath = projectDetected.format !== "none" ? projectDetected.path : path11.join(projectBasePath, "oh-my-opencode.json");
98609
+ let projectConfigPath = projectDetected.format !== "none" ? projectDetected.path : path11.join(projectBasePath, `${CONFIG_BASENAME}.json`);
98326
98610
  if (projectDetected.legacyPath) {
98327
98611
  log("Canonical plugin config detected alongside legacy config. Remove the legacy file to avoid confusion.", {
98328
98612
  canonicalPath: projectDetected.path,
@@ -98330,8 +98614,11 @@ function loadPluginConfig(directory, ctx) {
98330
98614
  });
98331
98615
  }
98332
98616
  if (projectDetected.format !== "none" && path11.basename(projectDetected.path).startsWith(LEGACY_CONFIG_BASENAME)) {
98333
- migrateLegacyConfigFile(projectDetected.path);
98334
- projectConfigPath = path11.join(path11.dirname(projectDetected.path), `${CONFIG_BASENAME}${path11.extname(projectDetected.path)}`);
98617
+ const projectMigrated = migrateLegacyConfigFile(projectDetected.path);
98618
+ const canonicalProjectPath = path11.join(path11.dirname(projectDetected.path), `${CONFIG_BASENAME}${path11.extname(projectDetected.path)}`);
98619
+ if (projectMigrated || fs18.existsSync(canonicalProjectPath)) {
98620
+ projectConfigPath = canonicalProjectPath;
98621
+ }
98335
98622
  }
98336
98623
  const userConfig = loadConfigFromPath2(userConfigPath, ctx);
98337
98624
  let config2 = userConfig ?? OhMyOpenCodeConfigSchema.parse({});
@@ -99643,7 +99930,7 @@ function createRuntimeFallbackHook(ctx, options) {
99643
99930
  }
99644
99931
  // src/hooks/write-existing-file-guard/hook.ts
99645
99932
  import { existsSync as existsSync65, realpathSync as realpathSync7 } from "fs";
99646
- import { basename as basename11, dirname as dirname22, isAbsolute as isAbsolute10, join as join70, normalize as normalize2, relative as relative8, resolve as resolve12 } from "path";
99933
+ import { basename as basename11, dirname as dirname22, isAbsolute as isAbsolute10, join as join71, normalize as normalize2, relative as relative8, resolve as resolve12 } from "path";
99647
99934
 
99648
99935
  // src/hooks/write-existing-file-guard/tool-execute-before-handler.ts
99649
99936
  import { existsSync as existsSync64 } from "fs";
@@ -99818,7 +100105,7 @@ function toCanonicalPath2(absolutePath) {
99818
100105
  } else {
99819
100106
  const absoluteDir = dirname22(absolutePath);
99820
100107
  const resolvedDir = existsSync65(absoluteDir) ? realpathSync7.native(absoluteDir) : absoluteDir;
99821
- canonicalPath = join70(resolvedDir, basename11(absolutePath));
100108
+ canonicalPath = join71(resolvedDir, basename11(absolutePath));
99822
100109
  }
99823
100110
  return normalize2(canonicalPath);
99824
100111
  }
@@ -100833,12 +101120,12 @@ function createWebFetchRedirectGuardHook(_ctx) {
100833
101120
  init_logger();
100834
101121
 
100835
101122
  // src/hooks/legacy-plugin-toast/auto-migrate.ts
100836
- import { existsSync as existsSync66, readFileSync as readFileSync47 } from "fs";
100837
- import { join as join71 } from "path";
101123
+ import { existsSync as existsSync66, readFileSync as readFileSync48 } from "fs";
101124
+ import { join as join72 } from "path";
100838
101125
  function detectOpenCodeConfigPath(overrideConfigDir) {
100839
101126
  if (overrideConfigDir) {
100840
- const jsoncPath = join71(overrideConfigDir, "opencode.jsonc");
100841
- const jsonPath = join71(overrideConfigDir, "opencode.json");
101127
+ const jsoncPath = join72(overrideConfigDir, "opencode.jsonc");
101128
+ const jsonPath = join72(overrideConfigDir, "opencode.json");
100842
101129
  if (existsSync66(jsoncPath))
100843
101130
  return jsoncPath;
100844
101131
  if (existsSync66(jsonPath))
@@ -100857,7 +101144,7 @@ function autoMigrateLegacyPluginEntry(overrideConfigDir) {
100857
101144
  if (!configPath)
100858
101145
  return { migrated: false, from: null, to: null, configPath: null };
100859
101146
  try {
100860
- const content = readFileSync47(configPath, "utf-8");
101147
+ const content = readFileSync48(configPath, "utf-8");
100861
101148
  const parseResult = parseJsoncSafe(content);
100862
101149
  if (!parseResult.data?.plugin)
100863
101150
  return { migrated: false, from: null, to: null, configPath };
@@ -101251,13 +101538,13 @@ var DEFAULT_MAX_SYMBOLS = 200;
101251
101538
  var DEFAULT_MAX_DIAGNOSTICS = 200;
101252
101539
  var DEFAULT_MAX_DIRECTORY_FILES = 50;
101253
101540
  // src/tools/lsp/server-config-loader.ts
101254
- import { existsSync as existsSync67, readFileSync as readFileSync48 } from "fs";
101255
- import { join as join72 } from "path";
101541
+ import { existsSync as existsSync67, readFileSync as readFileSync49 } from "fs";
101542
+ import { join as join73 } from "path";
101256
101543
  function loadJsonFile(path12) {
101257
101544
  if (!existsSync67(path12))
101258
101545
  return null;
101259
101546
  try {
101260
- return parseJsonc(readFileSync48(path12, "utf-8"));
101547
+ return parseJsonc(readFileSync49(path12, "utf-8"));
101261
101548
  } catch {
101262
101549
  return null;
101263
101550
  }
@@ -101266,9 +101553,9 @@ function getConfigPaths3() {
101266
101553
  const cwd = process.cwd();
101267
101554
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
101268
101555
  return {
101269
- project: detectPluginConfigFile(join72(cwd, ".opencode")).path,
101556
+ project: detectPluginConfigFile(join73(cwd, ".opencode")).path,
101270
101557
  user: detectPluginConfigFile(configDir).path,
101271
- opencode: detectConfigFile(join72(configDir, "opencode")).path
101558
+ opencode: detectConfigFile(join73(configDir, "opencode")).path
101272
101559
  };
101273
101560
  }
101274
101561
  function loadAllConfigs() {
@@ -101338,19 +101625,19 @@ function getMergedServers() {
101338
101625
 
101339
101626
  // src/tools/lsp/server-installation.ts
101340
101627
  import { existsSync as existsSync68 } from "fs";
101341
- import { delimiter, join as join74 } from "path";
101628
+ import { delimiter, join as join75 } from "path";
101342
101629
 
101343
101630
  // src/tools/lsp/server-path-bases.ts
101344
- import { join as join73 } from "path";
101631
+ import { join as join74 } from "path";
101345
101632
  function getLspServerAdditionalPathBases(workingDirectory) {
101346
101633
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
101347
- const dataDir = join73(getDataDir(), "opencode");
101634
+ const dataDir = join74(getDataDir(), "opencode");
101348
101635
  return [
101349
- join73(workingDirectory, "node_modules", ".bin"),
101350
- join73(configDir, "bin"),
101351
- join73(configDir, "node_modules", ".bin"),
101352
- join73(dataDir, "bin"),
101353
- join73(dataDir, "bin", "node_modules", ".bin")
101636
+ join74(workingDirectory, "node_modules", ".bin"),
101637
+ join74(configDir, "bin"),
101638
+ join74(configDir, "node_modules", ".bin"),
101639
+ join74(dataDir, "bin"),
101640
+ join74(dataDir, "bin", "node_modules", ".bin")
101354
101641
  ];
101355
101642
  }
101356
101643
 
@@ -101381,14 +101668,14 @@ function isServerInstalled(command) {
101381
101668
  const paths = pathEnv.split(delimiter);
101382
101669
  for (const p of paths) {
101383
101670
  for (const suffix of exts) {
101384
- if (existsSync68(join74(p, cmd + suffix))) {
101671
+ if (existsSync68(join75(p, cmd + suffix))) {
101385
101672
  return true;
101386
101673
  }
101387
101674
  }
101388
101675
  }
101389
101676
  for (const base of getLspServerAdditionalPathBases(process.cwd())) {
101390
101677
  for (const suffix of exts) {
101391
- if (existsSync68(join74(base, cmd + suffix))) {
101678
+ if (existsSync68(join75(base, cmd + suffix))) {
101392
101679
  return true;
101393
101680
  }
101394
101681
  }
@@ -101584,7 +101871,7 @@ function spawnProcess(command, options) {
101584
101871
  return proc;
101585
101872
  }
101586
101873
  // src/tools/lsp/lsp-client.ts
101587
- import { readFileSync as readFileSync49 } from "fs";
101874
+ import { readFileSync as readFileSync50 } from "fs";
101588
101875
  import { extname as extname4, resolve as resolve13 } from "path";
101589
101876
  import { pathToFileURL as pathToFileURL2 } from "url";
101590
101877
 
@@ -101856,7 +102143,7 @@ class LSPClient extends LSPClientConnection {
101856
102143
  async openFile(filePath) {
101857
102144
  const absPath = resolve13(filePath);
101858
102145
  const uri = pathToFileURL2(absPath).href;
101859
- const text = readFileSync49(absPath, "utf-8");
102146
+ const text = readFileSync50(absPath, "utf-8");
101860
102147
  if (!this.openedFiles.has(absPath)) {
101861
102148
  const ext = extname4(absPath);
101862
102149
  const languageId = getLanguageId(ext);
@@ -102408,10 +102695,10 @@ function formatApplyResult(result) {
102408
102695
  `);
102409
102696
  }
102410
102697
  // src/tools/lsp/workspace-edit.ts
102411
- import { readFileSync as readFileSync50, writeFileSync as writeFileSync19 } from "fs";
102698
+ import { readFileSync as readFileSync51, writeFileSync as writeFileSync19 } from "fs";
102412
102699
  function applyTextEditsToFile(filePath, edits) {
102413
102700
  try {
102414
- let content = readFileSync50(filePath, "utf-8");
102701
+ let content = readFileSync51(filePath, "utf-8");
102415
102702
  const lines = content.split(`
102416
102703
  `);
102417
102704
  const sortedEdits = [...edits].sort((a, b) => {
@@ -102477,7 +102764,7 @@ function applyWorkspaceEdit(edit) {
102477
102764
  try {
102478
102765
  const oldPath = uriToPath(change.oldUri);
102479
102766
  const newPath = uriToPath(change.newUri);
102480
- const content = readFileSync50(oldPath, "utf-8");
102767
+ const content = readFileSync51(oldPath, "utf-8");
102481
102768
  writeFileSync19(newPath, content, "utf-8");
102482
102769
  __require("fs").unlinkSync(oldPath);
102483
102770
  result.filesModified.push(newPath);
@@ -114960,8 +115247,8 @@ var lsp_symbols = tool({
114960
115247
  import { resolve as resolve16 } from "path";
114961
115248
 
114962
115249
  // src/tools/lsp/directory-diagnostics.ts
114963
- import { existsSync as existsSync71, lstatSync as lstatSync2, readdirSync as readdirSync17 } from "fs";
114964
- import { extname as extname6, join as join75, resolve as resolve15 } from "path";
115250
+ import { existsSync as existsSync71, lstatSync as lstatSync2, readdirSync as readdirSync18 } from "fs";
115251
+ import { extname as extname6, join as join76, resolve as resolve15 } from "path";
114965
115252
  var SKIP_DIRECTORIES = new Set(["node_modules", ".git", "dist", "build", ".next", "out"]);
114966
115253
  function collectFilesWithExtension(dir, extension, maxFiles) {
114967
115254
  const files = [];
@@ -114970,14 +115257,14 @@ function collectFilesWithExtension(dir, extension, maxFiles) {
114970
115257
  return;
114971
115258
  let entries = [];
114972
115259
  try {
114973
- entries = readdirSync17(currentDir);
115260
+ entries = readdirSync18(currentDir);
114974
115261
  } catch {
114975
115262
  return;
114976
115263
  }
114977
115264
  for (const entry of entries) {
114978
115265
  if (files.length >= maxFiles)
114979
115266
  return;
114980
- const fullPath = join75(currentDir, entry);
115267
+ const fullPath = join76(currentDir, entry);
114981
115268
  let stat;
114982
115269
  try {
114983
115270
  stat = lstatSync2(fullPath);
@@ -115079,8 +115366,8 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
115079
115366
  }
115080
115367
 
115081
115368
  // src/tools/lsp/infer-extension.ts
115082
- import { readdirSync as readdirSync18, lstatSync as lstatSync3 } from "fs";
115083
- import { extname as extname7, join as join76 } from "path";
115369
+ import { readdirSync as readdirSync19, lstatSync as lstatSync3 } from "fs";
115370
+ import { extname as extname7, join as join77 } from "path";
115084
115371
  var SKIP_DIRECTORIES2 = new Set(["node_modules", ".git", "dist", "build", ".next", "out"]);
115085
115372
  var MAX_SCAN_ENTRIES = 500;
115086
115373
  function inferExtensionFromDirectory(directory) {
@@ -115091,14 +115378,14 @@ function inferExtensionFromDirectory(directory) {
115091
115378
  return;
115092
115379
  let entries;
115093
115380
  try {
115094
- entries = readdirSync18(dir);
115381
+ entries = readdirSync19(dir);
115095
115382
  } catch {
115096
115383
  return;
115097
115384
  }
115098
115385
  for (const entry of entries) {
115099
115386
  if (scanned >= MAX_SCAN_ENTRIES)
115100
115387
  return;
115101
- const fullPath = join76(dir, entry);
115388
+ const fullPath = join77(dir, entry);
115102
115389
  let stat;
115103
115390
  try {
115104
115391
  stat = lstatSync3(fullPath);
@@ -115263,12 +115550,12 @@ var DEFAULT_MAX_MATCHES = 500;
115263
115550
 
115264
115551
  // src/tools/ast-grep/sg-cli-path.ts
115265
115552
  import { createRequire as createRequire4 } from "module";
115266
- import { dirname as dirname23, join as join78 } from "path";
115553
+ import { dirname as dirname23, join as join79 } from "path";
115267
115554
  import { existsSync as existsSync73, statSync as statSync10 } from "fs";
115268
115555
 
115269
115556
  // src/tools/ast-grep/downloader.ts
115270
115557
  import { existsSync as existsSync72 } from "fs";
115271
- import { join as join77 } from "path";
115558
+ import { join as join78 } from "path";
115272
115559
  import { homedir as homedir14 } from "os";
115273
115560
  import { createRequire as createRequire3 } from "module";
115274
115561
  init_logger();
@@ -115295,12 +115582,12 @@ var PLATFORM_MAP2 = {
115295
115582
  function getCacheDir3() {
115296
115583
  if (process.platform === "win32") {
115297
115584
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
115298
- const base2 = localAppData || join77(homedir14(), "AppData", "Local");
115299
- return join77(base2, "oh-my-opencode", "bin");
115585
+ const base2 = localAppData || join78(homedir14(), "AppData", "Local");
115586
+ return join78(base2, "oh-my-opencode", "bin");
115300
115587
  }
115301
115588
  const xdgCache = process.env.XDG_CACHE_HOME;
115302
- const base = xdgCache || join77(homedir14(), ".cache");
115303
- return join77(base, "oh-my-opencode", "bin");
115589
+ const base = xdgCache || join78(homedir14(), ".cache");
115590
+ return join78(base, "oh-my-opencode", "bin");
115304
115591
  }
115305
115592
  function getBinaryName3() {
115306
115593
  return process.platform === "win32" ? "sg.exe" : "sg";
@@ -115317,7 +115604,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
115317
115604
  }
115318
115605
  const cacheDir = getCacheDir3();
115319
115606
  const binaryName = getBinaryName3();
115320
- const binaryPath = join77(cacheDir, binaryName);
115607
+ const binaryPath = join78(cacheDir, binaryName);
115321
115608
  if (existsSync72(binaryPath)) {
115322
115609
  return binaryPath;
115323
115610
  }
@@ -115326,7 +115613,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
115326
115613
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version3}/${assetName}`;
115327
115614
  log(`[oh-my-opencode] Downloading ast-grep binary...`);
115328
115615
  try {
115329
- const archivePath = join77(cacheDir, assetName);
115616
+ const archivePath = join78(cacheDir, assetName);
115330
115617
  ensureCacheDir(cacheDir);
115331
115618
  await downloadArchive(downloadUrl, archivePath);
115332
115619
  await extractZipArchive(archivePath, cacheDir);
@@ -115380,7 +115667,7 @@ function findSgCliPathSync() {
115380
115667
  const require2 = createRequire4(import.meta.url);
115381
115668
  const cliPackageJsonPath = require2.resolve("@ast-grep/cli/package.json");
115382
115669
  const cliDirectory = dirname23(cliPackageJsonPath);
115383
- const sgPath = join78(cliDirectory, binaryName);
115670
+ const sgPath = join79(cliDirectory, binaryName);
115384
115671
  if (existsSync73(sgPath) && isValidBinary(sgPath)) {
115385
115672
  return sgPath;
115386
115673
  }
@@ -115392,7 +115679,7 @@ function findSgCliPathSync() {
115392
115679
  const packageJsonPath = require2.resolve(`${platformPackage}/package.json`);
115393
115680
  const packageDirectory = dirname23(packageJsonPath);
115394
115681
  const astGrepBinaryName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
115395
- const binaryPath = join78(packageDirectory, astGrepBinaryName);
115682
+ const binaryPath = join79(packageDirectory, astGrepBinaryName);
115396
115683
  if (existsSync73(binaryPath) && isValidBinary(binaryPath)) {
115397
115684
  return binaryPath;
115398
115685
  }
@@ -115794,18 +116081,18 @@ var {spawn: spawn18 } = globalThis.Bun;
115794
116081
 
115795
116082
  // src/tools/grep/constants.ts
115796
116083
  import { existsSync as existsSync77 } from "fs";
115797
- import { join as join80, dirname as dirname24 } from "path";
116084
+ import { join as join81, dirname as dirname24 } from "path";
115798
116085
  import { spawnSync as spawnSync4 } from "child_process";
115799
116086
 
115800
116087
  // src/tools/grep/downloader.ts
115801
- import { existsSync as existsSync76, readdirSync as readdirSync19 } from "fs";
115802
- import { join as join79 } from "path";
116088
+ import { existsSync as existsSync76, readdirSync as readdirSync20 } from "fs";
116089
+ import { join as join80 } from "path";
115803
116090
  function findFileRecursive(dir, filename) {
115804
116091
  try {
115805
- const entries = readdirSync19(dir, { withFileTypes: true, recursive: true });
116092
+ const entries = readdirSync20(dir, { withFileTypes: true, recursive: true });
115806
116093
  for (const entry of entries) {
115807
116094
  if (entry.isFile() && entry.name === filename) {
115808
- return join79(entry.parentPath ?? dir, entry.name);
116095
+ return join80(entry.parentPath ?? dir, entry.name);
115809
116096
  }
115810
116097
  }
115811
116098
  } catch {
@@ -115826,11 +116113,11 @@ function getPlatformKey() {
115826
116113
  }
115827
116114
  function getInstallDir() {
115828
116115
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
115829
- return join79(homeDir, ".cache", "oh-my-opencode", "bin");
116116
+ return join80(homeDir, ".cache", "oh-my-opencode", "bin");
115830
116117
  }
115831
116118
  function getRgPath() {
115832
116119
  const isWindows2 = process.platform === "win32";
115833
- return join79(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
116120
+ return join80(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
115834
116121
  }
115835
116122
  async function extractTarGz2(archivePath, destDir) {
115836
116123
  const platformKey = getPlatformKey();
@@ -115847,7 +116134,7 @@ async function extractZip2(archivePath, destDir) {
115847
116134
  const binaryName = process.platform === "win32" ? "rg.exe" : "rg";
115848
116135
  const foundPath = findFileRecursive(destDir, binaryName);
115849
116136
  if (foundPath) {
115850
- const destPath = join79(destDir, binaryName);
116137
+ const destPath = join80(destDir, binaryName);
115851
116138
  if (foundPath !== destPath) {
115852
116139
  const { renameSync: renameSync5 } = await import("fs");
115853
116140
  renameSync5(foundPath, destPath);
@@ -115868,7 +116155,7 @@ async function downloadAndInstallRipgrep() {
115868
116155
  ensureCacheDir(installDir);
115869
116156
  const filename = `ripgrep-${RG_VERSION}-${config4.platform}.${config4.extension}`;
115870
116157
  const url3 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
115871
- const archivePath = join79(installDir, filename);
116158
+ const archivePath = join80(installDir, filename);
115872
116159
  try {
115873
116160
  await downloadArchive(url3, archivePath);
115874
116161
  if (config4.extension === "tar.gz") {
@@ -115914,11 +116201,11 @@ function getOpenCodeBundledRg() {
115914
116201
  const isWindows2 = process.platform === "win32";
115915
116202
  const rgName = isWindows2 ? "rg.exe" : "rg";
115916
116203
  const candidates = [
115917
- join80(getDataDir(), "opencode", "bin", rgName),
115918
- join80(execDir, rgName),
115919
- join80(execDir, "bin", rgName),
115920
- join80(execDir, "..", "bin", rgName),
115921
- join80(execDir, "..", "libexec", rgName)
116204
+ join81(getDataDir(), "opencode", "bin", rgName),
116205
+ join81(execDir, rgName),
116206
+ join81(execDir, "bin", rgName),
116207
+ join81(execDir, "..", "bin", rgName),
116208
+ join81(execDir, "..", "libexec", rgName)
115922
116209
  ];
115923
116210
  for (const candidate of candidates) {
115924
116211
  if (existsSync77(candidate)) {
@@ -117030,9 +117317,9 @@ function createSkillTool(options = {}) {
117030
117317
  }
117031
117318
  var skill = createSkillTool();
117032
117319
  // src/tools/session-manager/constants.ts
117033
- import { join as join81 } from "path";
117034
- var TODO_DIR2 = join81(getClaudeConfigDir(), "todos");
117035
- var TRANSCRIPT_DIR2 = join81(getClaudeConfigDir(), "transcripts");
117320
+ import { join as join82 } from "path";
117321
+ var TODO_DIR2 = join82(getClaudeConfigDir(), "todos");
117322
+ var TRANSCRIPT_DIR2 = join82(getClaudeConfigDir(), "transcripts");
117036
117323
  var SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.
117037
117324
 
117038
117325
  Returns a list of available session IDs with metadata including message count, date range, and agents used.
@@ -117107,7 +117394,7 @@ Has Transcript: Yes (234 entries)`;
117107
117394
  // src/tools/session-manager/file-storage.ts
117108
117395
  import { existsSync as existsSync78 } from "fs";
117109
117396
  import { readdir, readFile } from "fs/promises";
117110
- import { join as join82 } from "path";
117397
+ import { join as join83 } from "path";
117111
117398
  async function getFileMainSessions(directory) {
117112
117399
  if (!existsSync78(SESSION_STORAGE))
117113
117400
  return [];
@@ -117117,13 +117404,13 @@ async function getFileMainSessions(directory) {
117117
117404
  for (const projectDir of projectDirs) {
117118
117405
  if (!projectDir.isDirectory())
117119
117406
  continue;
117120
- const projectPath = join82(SESSION_STORAGE, projectDir.name);
117407
+ const projectPath = join83(SESSION_STORAGE, projectDir.name);
117121
117408
  const sessionFiles = await readdir(projectPath);
117122
117409
  for (const file3 of sessionFiles) {
117123
117410
  if (!file3.endsWith(".json"))
117124
117411
  continue;
117125
117412
  try {
117126
- const content = await readFile(join82(projectPath, file3), "utf-8");
117413
+ const content = await readFile(join83(projectPath, file3), "utf-8");
117127
117414
  const meta3 = JSON.parse(content);
117128
117415
  if (meta3.parentID)
117129
117416
  continue;
@@ -117150,7 +117437,7 @@ async function getFileAllSessions() {
117150
117437
  for (const entry of entries) {
117151
117438
  if (!entry.isDirectory())
117152
117439
  continue;
117153
- const sessionPath = join82(dir, entry.name);
117440
+ const sessionPath = join83(dir, entry.name);
117154
117441
  const files = await readdir(sessionPath);
117155
117442
  if (files.some((file3) => file3.endsWith(".json"))) {
117156
117443
  sessions.push(entry.name);
@@ -117179,7 +117466,7 @@ async function getFileSessionMessages(sessionID) {
117179
117466
  if (!file3.endsWith(".json"))
117180
117467
  continue;
117181
117468
  try {
117182
- const content = await readFile(join82(messageDir, file3), "utf-8");
117469
+ const content = await readFile(join83(messageDir, file3), "utf-8");
117183
117470
  const meta3 = JSON.parse(content);
117184
117471
  const parts = await readParts2(meta3.id);
117185
117472
  messages.push({
@@ -117205,7 +117492,7 @@ async function getFileSessionMessages(sessionID) {
117205
117492
  });
117206
117493
  }
117207
117494
  async function readParts2(messageID) {
117208
- const partDir = join82(PART_STORAGE, messageID);
117495
+ const partDir = join83(PART_STORAGE, messageID);
117209
117496
  if (!existsSync78(partDir))
117210
117497
  return [];
117211
117498
  const parts = [];
@@ -117215,7 +117502,7 @@ async function readParts2(messageID) {
117215
117502
  if (!file3.endsWith(".json"))
117216
117503
  continue;
117217
117504
  try {
117218
- const content = await readFile(join82(partDir, file3), "utf-8");
117505
+ const content = await readFile(join83(partDir, file3), "utf-8");
117219
117506
  parts.push(JSON.parse(content));
117220
117507
  } catch {
117221
117508
  continue;
@@ -117234,7 +117521,7 @@ async function getFileSessionTodos(sessionID) {
117234
117521
  const todoFiles = allFiles.filter((file3) => file3 === `${sessionID}.json`);
117235
117522
  for (const file3 of todoFiles) {
117236
117523
  try {
117237
- const content = await readFile(join82(TODO_DIR2, file3), "utf-8");
117524
+ const content = await readFile(join83(TODO_DIR2, file3), "utf-8");
117238
117525
  const data = JSON.parse(content);
117239
117526
  if (!Array.isArray(data))
117240
117527
  continue;
@@ -117256,7 +117543,7 @@ async function getFileSessionTodos(sessionID) {
117256
117543
  async function getFileSessionTranscript(sessionID) {
117257
117544
  if (!existsSync78(TRANSCRIPT_DIR2))
117258
117545
  return 0;
117259
- const transcriptFile = join82(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
117546
+ const transcriptFile = join83(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
117260
117547
  if (!existsSync78(transcriptFile))
117261
117548
  return 0;
117262
117549
  try {
@@ -117509,7 +117796,48 @@ async function readSessionTodos(sessionID) {
117509
117796
  }
117510
117797
  return getFileSessionTodos(sessionID);
117511
117798
  }
117799
+ async function readSessionTranscript(sessionID) {
117800
+ return getFileSessionTranscript(sessionID);
117801
+ }
117512
117802
  async function getSessionInfo(sessionID) {
117803
+ if (isSqliteBackend() && sdkClient) {
117804
+ try {
117805
+ const sdkMessages = await getSdkSessionMessages(sdkClient, sessionID);
117806
+ if (sdkMessages.length > 0) {
117807
+ const agentsUsed = new Set;
117808
+ let firstMessage;
117809
+ let lastMessage;
117810
+ for (const msg of sdkMessages) {
117811
+ if (msg.agent)
117812
+ agentsUsed.add(msg.agent);
117813
+ if (msg.time?.created) {
117814
+ const date9 = new Date(msg.time.created);
117815
+ if (!firstMessage || date9 < firstMessage)
117816
+ firstMessage = date9;
117817
+ if (!lastMessage || date9 > lastMessage)
117818
+ lastMessage = date9;
117819
+ }
117820
+ }
117821
+ const todos = await readSessionTodos(sessionID);
117822
+ const transcriptEntries = await readSessionTranscript(sessionID);
117823
+ return {
117824
+ id: sessionID,
117825
+ message_count: sdkMessages.length,
117826
+ first_message: firstMessage,
117827
+ last_message: lastMessage,
117828
+ agents_used: Array.from(agentsUsed),
117829
+ has_todos: todos.length > 0,
117830
+ has_transcript: transcriptEntries > 0,
117831
+ todos,
117832
+ transcript_entries: transcriptEntries
117833
+ };
117834
+ }
117835
+ } catch (error92) {
117836
+ if (!shouldFallbackFromSdkError(error92))
117837
+ throw error92;
117838
+ log("[session-manager] falling back to file session info after SDK unavailable error", { error: String(error92), sessionID });
117839
+ }
117840
+ }
117513
117841
  return getFileSessionInfo(sessionID);
117514
117842
  }
117515
117843
 
@@ -119490,9 +119818,9 @@ async function resolveMultimodalLookerAgentMetadata(ctx) {
119490
119818
 
119491
119819
  // src/tools/look-at/image-converter.ts
119492
119820
  import * as childProcess from "child_process";
119493
- import { existsSync as existsSync79, mkdtempSync, readFileSync as readFileSync51, rmSync as rmSync4, unlinkSync as unlinkSync11, writeFileSync as writeFileSync20 } from "fs";
119821
+ import { existsSync as existsSync79, mkdtempSync, readFileSync as readFileSync52, rmSync as rmSync4, unlinkSync as unlinkSync11, writeFileSync as writeFileSync20 } from "fs";
119494
119822
  import { tmpdir as tmpdir7 } from "os";
119495
- import { dirname as dirname27, join as join83 } from "path";
119823
+ import { dirname as dirname27, join as join84 } from "path";
119496
119824
  var SUPPORTED_FORMATS = new Set([
119497
119825
  "image/jpeg",
119498
119826
  "image/png",
@@ -119533,8 +119861,8 @@ function convertImageToJpeg(inputPath, mimeType) {
119533
119861
  if (!existsSync79(inputPath)) {
119534
119862
  throw new Error(`File not found: ${inputPath}`);
119535
119863
  }
119536
- const tempDir = mkdtempSync(join83(tmpdir7(), "opencode-img-"));
119537
- const outputPath = join83(tempDir, "converted.jpg");
119864
+ const tempDir = mkdtempSync(join84(tmpdir7(), "opencode-img-"));
119865
+ const outputPath = join84(tempDir, "converted.jpg");
119538
119866
  log(`[image-converter] Converting ${mimeType} to JPEG: ${inputPath}`);
119539
119867
  try {
119540
119868
  if (process.platform === "darwin") {
@@ -119599,9 +119927,9 @@ function cleanupConvertedImage(filePath) {
119599
119927
  }
119600
119928
  }
119601
119929
  function convertBase64ImageToJpeg(base64Data, mimeType) {
119602
- const tempDir = mkdtempSync(join83(tmpdir7(), "opencode-b64-"));
119930
+ const tempDir = mkdtempSync(join84(tmpdir7(), "opencode-b64-"));
119603
119931
  const inputExt = mimeType.split("/")[1] || "bin";
119604
- const inputPath = join83(tempDir, `input.${inputExt}`);
119932
+ const inputPath = join84(tempDir, `input.${inputExt}`);
119605
119933
  const tempFiles = [inputPath];
119606
119934
  try {
119607
119935
  const cleanBase64 = base64Data.replace(/^data:[^;]+;base64,/, "");
@@ -119610,7 +119938,7 @@ function convertBase64ImageToJpeg(base64Data, mimeType) {
119610
119938
  log(`[image-converter] Converting Base64 ${mimeType} to JPEG`);
119611
119939
  const outputPath = convertImageToJpeg(inputPath, mimeType);
119612
119940
  tempFiles.push(outputPath);
119613
- const convertedBuffer = readFileSync51(outputPath);
119941
+ const convertedBuffer = readFileSync52(outputPath);
119614
119942
  const convertedBase64 = convertedBuffer.toString("base64");
119615
119943
  log(`[image-converter] Base64 conversion successful`);
119616
119944
  return { base64: convertedBase64, tempFiles };
@@ -120062,6 +120390,11 @@ function formatDetailedError(error92, ctx) {
120062
120390
  `);
120063
120391
  }
120064
120392
 
120393
+ // src/tools/delegate-task/resolve-call-id.ts
120394
+ function resolveCallID(ctx) {
120395
+ return ctx.callID ?? ctx.callId ?? ctx.call_id;
120396
+ }
120397
+
120065
120398
  // src/tools/delegate-task/background-continuation.ts
120066
120399
  async function executeBackgroundContinuation(args, ctx, executorCtx, parentContext) {
120067
120400
  const { manager } = executorCtx;
@@ -120089,8 +120422,9 @@ async function executeBackgroundContinuation(args, ctx, executorCtx, parentConte
120089
120422
  }
120090
120423
  };
120091
120424
  await ctx.metadata?.(bgContMeta);
120092
- if (ctx.callID) {
120093
- storeToolMetadata(ctx.sessionID, ctx.callID, bgContMeta);
120425
+ const callID = resolveCallID(ctx);
120426
+ if (callID) {
120427
+ storeToolMetadata(ctx.sessionID, callID, bgContMeta);
120094
120428
  }
120095
120429
  return `Background task continued.
120096
120430
 
@@ -120409,8 +120743,9 @@ async function executeSyncContinuation(args, ctx, executorCtx, deps = syncContin
120409
120743
  }
120410
120744
  };
120411
120745
  await ctx.metadata?.(syncContMeta);
120412
- if (ctx.callID) {
120413
- storeToolMetadata(ctx.sessionID, ctx.callID, syncContMeta);
120746
+ const callID = resolveCallID(ctx);
120747
+ if (callID) {
120748
+ storeToolMetadata(ctx.sessionID, callID, syncContMeta);
120414
120749
  }
120415
120750
  const allowTask = isPlanFamily(resumeAgent);
120416
120751
  const tddEnabled = sisyphusAgentConfig?.tdd;
@@ -120554,8 +120889,9 @@ Task ID: ${task.id}`;
120554
120889
  }
120555
120890
  };
120556
120891
  await ctx.metadata?.(bgTaskMeta);
120557
- if (ctx.callID) {
120558
- storeToolMetadata(ctx.sessionID, ctx.callID, bgTaskMeta);
120892
+ const callID = resolveCallID(ctx);
120893
+ if (callID) {
120894
+ storeToolMetadata(ctx.sessionID, callID, bgTaskMeta);
120559
120895
  }
120560
120896
  const startTime = new Date;
120561
120897
  const timingCfg = getTimingConfig();
@@ -120797,8 +121133,9 @@ Task ID: ${task.id}`;
120797
121133
  metadata
120798
121134
  };
120799
121135
  await ctx.metadata?.(unstableMeta);
120800
- if (ctx.callID) {
120801
- storeToolMetadata(ctx.sessionID, ctx.callID, unstableMeta);
121136
+ const callID = resolveCallID(ctx);
121137
+ if (callID) {
121138
+ storeToolMetadata(ctx.sessionID, callID, unstableMeta);
120802
121139
  }
120803
121140
  const taskMetadataBlock = sessionId ? `
120804
121141
 
@@ -121026,8 +121363,9 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
121026
121363
  }
121027
121364
  };
121028
121365
  await ctx.metadata?.(syncTaskMeta);
121029
- if (ctx.callID) {
121030
- storeToolMetadata(ctx.sessionID, ctx.callID, syncTaskMeta);
121366
+ const callID = resolveCallID(ctx);
121367
+ if (callID) {
121368
+ storeToolMetadata(ctx.sessionID, callID, syncTaskMeta);
121031
121369
  }
121032
121370
  const promptError = await deps.sendSyncPrompt(client2, {
121033
121371
  sessionID,
@@ -121505,7 +121843,7 @@ Available categories: ${categoryNames.join(", ")}`
121505
121843
  };
121506
121844
  }
121507
121845
  const resolvedModel = actualModel?.toLowerCase();
121508
- const isUnstableAgent = resolved.config.is_unstable_agent ?? (resolvedModel ? resolvedModel.includes("gemini") || resolvedModel.includes("minimax") || resolvedModel.includes("kimi") : false);
121846
+ const isUnstableAgent = resolved.config.is_unstable_agent ?? (resolvedModel ? resolvedModel.includes("gemini") || resolvedModel.includes("minimax") : false);
121509
121847
  const defaultProviderID = categoryModel?.providerID ?? parseModelString(actualModel ?? "")?.providerID ?? "opencode";
121510
121848
  const configuredFallbackChain = buildFallbackChainFromModels(normalizedConfiguredFallbackModels, defaultProviderID);
121511
121849
  const effectiveEntry = matchedFallback && categoryModel ? fallbackEntry ?? (configuredFallbackChain ? findMostSpecificFallbackEntry(categoryModel.providerID, categoryModel.modelID, configuredFallbackChain) : undefined) : undefined;
@@ -121769,6 +122107,9 @@ function createDelegateTask(options) {
121769
122107
  await ctx.metadata?.({
121770
122108
  title: args.description
121771
122109
  });
122110
+ if (!args.description || typeof args.description !== "string") {
122111
+ throw new Error(`Invalid arguments: 'description' parameter is REQUIRED. Provide a short (3-5 words) task description.`);
122112
+ }
121772
122113
  if (args.run_in_background === undefined) {
121773
122114
  throw new Error(`Invalid arguments: 'run_in_background' parameter is REQUIRED. Specify run_in_background=false for task delegation, or run_in_background=true for parallel exploration.`);
121774
122115
  }
@@ -121888,7 +122229,7 @@ function createDelegateTask(options) {
121888
122229
  // src/tools/delegate-task/index.ts
121889
122230
  init_constants();
121890
122231
  // src/tools/task/task-create.ts
121891
- import { join as join85 } from "path";
122232
+ import { join as join86 } from "path";
121892
122233
 
121893
122234
  // src/tools/task/types.ts
121894
122235
  var TaskStatusSchema = exports_external.enum(["pending", "in_progress", "completed", "deleted"]);
@@ -121942,18 +122283,18 @@ var TaskDeleteInputSchema = exports_external.object({
121942
122283
  });
121943
122284
 
121944
122285
  // src/features/claude-tasks/storage.ts
121945
- import { join as join84, dirname as dirname28, basename as basename13, isAbsolute as isAbsolute11 } from "path";
121946
- import { existsSync as existsSync80, mkdirSync as mkdirSync16, readFileSync as readFileSync52, writeFileSync as writeFileSync21, renameSync as renameSync5, unlinkSync as unlinkSync12, readdirSync as readdirSync20 } from "fs";
122286
+ import { join as join85, dirname as dirname28, basename as basename13, isAbsolute as isAbsolute11 } from "path";
122287
+ import { existsSync as existsSync80, mkdirSync as mkdirSync16, readFileSync as readFileSync53, writeFileSync as writeFileSync21, renameSync as renameSync5, unlinkSync as unlinkSync12, readdirSync as readdirSync21 } from "fs";
121947
122288
  import { randomUUID as randomUUID3 } from "crypto";
121948
122289
  function getTaskDir(config4 = {}) {
121949
122290
  const tasksConfig = config4.sisyphus?.tasks;
121950
122291
  const storagePath = tasksConfig?.storage_path;
121951
122292
  if (storagePath) {
121952
- return isAbsolute11(storagePath) ? storagePath : join84(process.cwd(), storagePath);
122293
+ return isAbsolute11(storagePath) ? storagePath : join85(process.cwd(), storagePath);
121953
122294
  }
121954
122295
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
121955
122296
  const listId = resolveTaskListId(config4);
121956
- return join84(configDir, "tasks", listId);
122297
+ return join85(configDir, "tasks", listId);
121957
122298
  }
121958
122299
  function sanitizePathSegment(value) {
121959
122300
  return value.replace(/[^a-zA-Z0-9_-]/g, "-") || "default";
@@ -121980,7 +122321,7 @@ function readJsonSafe(filePath, schema2) {
121980
122321
  if (!existsSync80(filePath)) {
121981
122322
  return null;
121982
122323
  }
121983
- const content = readFileSync52(filePath, "utf-8");
122324
+ const content = readFileSync53(filePath, "utf-8");
121984
122325
  const parsed = JSON.parse(content);
121985
122326
  const result = schema2.safeParse(parsed);
121986
122327
  if (!result.success) {
@@ -122012,7 +122353,7 @@ function generateTaskId() {
122012
122353
  return `T-${randomUUID3()}`;
122013
122354
  }
122014
122355
  function acquireLock(dirPath) {
122015
- const lockPath = join84(dirPath, ".lock");
122356
+ const lockPath = join85(dirPath, ".lock");
122016
122357
  const lockId = randomUUID3();
122017
122358
  const createLock = (timestamp2) => {
122018
122359
  writeFileSync21(lockPath, JSON.stringify({ id: lockId, timestamp: timestamp2 }), {
@@ -122022,7 +122363,7 @@ function acquireLock(dirPath) {
122022
122363
  };
122023
122364
  const isStale = () => {
122024
122365
  try {
122025
- const lockContent = readFileSync52(lockPath, "utf-8");
122366
+ const lockContent = readFileSync53(lockPath, "utf-8");
122026
122367
  const lockData = JSON.parse(lockContent);
122027
122368
  const lockAge = Date.now() - lockData.timestamp;
122028
122369
  return lockAge > STALE_LOCK_THRESHOLD_MS;
@@ -122062,7 +122403,7 @@ function acquireLock(dirPath) {
122062
122403
  try {
122063
122404
  if (!existsSync80(lockPath))
122064
122405
  return;
122065
- const lockContent = readFileSync52(lockPath, "utf-8");
122406
+ const lockContent = readFileSync53(lockPath, "utf-8");
122066
122407
  const lockData = JSON.parse(lockContent);
122067
122408
  if (lockData.id !== lockId)
122068
122409
  return;
@@ -122225,7 +122566,7 @@ async function handleCreate(args, config4, ctx, context) {
122225
122566
  threadID: context.sessionID
122226
122567
  };
122227
122568
  const validatedTask = TaskObjectSchema.parse(task);
122228
- writeJsonAtomic(join85(taskDir, `${taskId}.json`), validatedTask);
122569
+ writeJsonAtomic(join86(taskDir, `${taskId}.json`), validatedTask);
122229
122570
  await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
122230
122571
  return JSON.stringify({
122231
122572
  task: {
@@ -122247,7 +122588,7 @@ async function handleCreate(args, config4, ctx, context) {
122247
122588
  }
122248
122589
  }
122249
122590
  // src/tools/task/task-get.ts
122250
- import { join as join86 } from "path";
122591
+ import { join as join87 } from "path";
122251
122592
  var TASK_ID_PATTERN = /^T-[A-Za-z0-9-]+$/;
122252
122593
  function parseTaskId(id) {
122253
122594
  if (!TASK_ID_PATTERN.test(id))
@@ -122272,7 +122613,7 @@ Returns null if the task does not exist or the file is invalid.`,
122272
122613
  return JSON.stringify({ error: "invalid_task_id" });
122273
122614
  }
122274
122615
  const taskDir = getTaskDir(config4);
122275
- const taskPath = join86(taskDir, `${taskId}.json`);
122616
+ const taskPath = join87(taskDir, `${taskId}.json`);
122276
122617
  const task = readJsonSafe(taskPath, TaskObjectSchema);
122277
122618
  return JSON.stringify({ task: task ?? null });
122278
122619
  } catch (error92) {
@@ -122285,8 +122626,8 @@ Returns null if the task does not exist or the file is invalid.`,
122285
122626
  });
122286
122627
  }
122287
122628
  // src/tools/task/task-list.ts
122288
- import { join as join87 } from "path";
122289
- import { existsSync as existsSync81, readdirSync as readdirSync21 } from "fs";
122629
+ import { join as join88 } from "path";
122630
+ import { existsSync as existsSync81, readdirSync as readdirSync22 } from "fs";
122290
122631
  function createTaskList(config4) {
122291
122632
  return tool({
122292
122633
  description: `List all active tasks with summary information.
@@ -122300,13 +122641,13 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript
122300
122641
  if (!existsSync81(taskDir)) {
122301
122642
  return JSON.stringify({ tasks: [] });
122302
122643
  }
122303
- const files = readdirSync21(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-")).map((f) => f.replace(".json", ""));
122644
+ const files = readdirSync22(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-")).map((f) => f.replace(".json", ""));
122304
122645
  if (files.length === 0) {
122305
122646
  return JSON.stringify({ tasks: [] });
122306
122647
  }
122307
122648
  const allTasks = [];
122308
122649
  for (const fileId of files) {
122309
- const task = readJsonSafe(join87(taskDir, `${fileId}.json`), TaskObjectSchema);
122650
+ const task = readJsonSafe(join88(taskDir, `${fileId}.json`), TaskObjectSchema);
122310
122651
  if (task) {
122311
122652
  allTasks.push(task);
122312
122653
  }
@@ -122334,7 +122675,7 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript
122334
122675
  });
122335
122676
  }
122336
122677
  // src/tools/task/task-update.ts
122337
- import { join as join88 } from "path";
122678
+ import { join as join89 } from "path";
122338
122679
  var TASK_ID_PATTERN2 = /^T-[A-Za-z0-9-]+$/;
122339
122680
  function parseTaskId2(id) {
122340
122681
  if (!TASK_ID_PATTERN2.test(id))
@@ -122382,7 +122723,7 @@ async function handleUpdate(args, config4, ctx, context) {
122382
122723
  return JSON.stringify({ error: "task_lock_unavailable" });
122383
122724
  }
122384
122725
  try {
122385
- const taskPath = join88(taskDir, `${taskId}.json`);
122726
+ const taskPath = join89(taskDir, `${taskId}.json`);
122386
122727
  const task = readJsonSafe(taskPath, TaskObjectSchema);
122387
122728
  if (!task) {
122388
122729
  return JSON.stringify({ error: "task_not_found" });
@@ -124046,6 +124387,9 @@ var builtinTools = {
124046
124387
  function isTmuxIntegrationEnabled(pluginConfig) {
124047
124388
  return pluginConfig.tmux?.enabled ?? false;
124048
124389
  }
124390
+ function isInteractiveBashEnabled(which = Bun.which) {
124391
+ return which("tmux") !== null;
124392
+ }
124049
124393
  function createRuntimeTmuxConfig(pluginConfig) {
124050
124394
  return TmuxConfigSchema.parse(pluginConfig.tmux ?? {});
124051
124395
  }
@@ -124594,6 +124938,40 @@ function createHooks(args) {
124594
124938
  }
124595
124939
  };
124596
124940
  }
124941
+ // src/features/background-agent/constants.ts
124942
+ var TASK_TTL_MS = 30 * 60 * 1000;
124943
+ var TERMINAL_TASK_TTL_MS = 30 * 60 * 1000;
124944
+ var MIN_STABILITY_TIME_MS2 = 10 * 1000;
124945
+ var DEFAULT_STALE_TIMEOUT_MS = 2700000;
124946
+ var DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 3600000;
124947
+ var DEFAULT_MAX_TOOL_CALLS = 4000;
124948
+ var DEFAULT_CIRCUIT_BREAKER_CONSECUTIVE_THRESHOLD = 20;
124949
+ var DEFAULT_CIRCUIT_BREAKER_ENABLED = true;
124950
+ var MIN_RUNTIME_BEFORE_STALE_MS = 30000;
124951
+ var DEFAULT_SESSION_GONE_TIMEOUT_MS = 60000;
124952
+ var MIN_IDLE_TIME_MS = 5000;
124953
+ var POLLING_INTERVAL_MS = 3000;
124954
+ var TASK_CLEANUP_DELAY_MS = 10 * 60 * 1000;
124955
+
124956
+ // src/features/background-agent/spawner.ts
124957
+ var FALLBACK_AGENT = "general";
124958
+ function isAgentNotFoundError(error92) {
124959
+ const message = typeof error92 === "string" ? error92 : error92 instanceof Error ? error92.message : typeof error92 === "object" && error92 !== null && typeof error92.message === "string" ? error92.message : String(error92);
124960
+ return message.includes("Agent not found") || message.includes("agent.name");
124961
+ }
124962
+ function buildFallbackBody(originalBody, fallbackAgent) {
124963
+ return {
124964
+ ...originalBody,
124965
+ agent: fallbackAgent,
124966
+ tools: {
124967
+ task: false,
124968
+ call_omo_agent: true,
124969
+ question: false,
124970
+ ...getAgentToolRestrictions(fallbackAgent)
124971
+ }
124972
+ };
124973
+ }
124974
+
124597
124975
  // src/features/background-agent/task-history.ts
124598
124976
  var MAX_ENTRIES_PER_PARENT = 100;
124599
124977
 
@@ -124751,21 +125129,6 @@ class ConcurrencyManager {
124751
125129
  }
124752
125130
  }
124753
125131
 
124754
- // src/features/background-agent/constants.ts
124755
- var TASK_TTL_MS = 30 * 60 * 1000;
124756
- var TERMINAL_TASK_TTL_MS = 30 * 60 * 1000;
124757
- var MIN_STABILITY_TIME_MS2 = 10 * 1000;
124758
- var DEFAULT_STALE_TIMEOUT_MS = 2700000;
124759
- var DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 3600000;
124760
- var DEFAULT_MAX_TOOL_CALLS = 4000;
124761
- var DEFAULT_CIRCUIT_BREAKER_CONSECUTIVE_THRESHOLD = 20;
124762
- var DEFAULT_CIRCUIT_BREAKER_ENABLED = true;
124763
- var MIN_RUNTIME_BEFORE_STALE_MS = 30000;
124764
- var DEFAULT_SESSION_GONE_TIMEOUT_MS = 60000;
124765
- var MIN_IDLE_TIME_MS = 5000;
124766
- var POLLING_INTERVAL_MS = 3000;
124767
- var TASK_CLEANUP_DELAY_MS = 10 * 60 * 1000;
124768
-
124769
125132
  // src/features/background-agent/duration-formatter.ts
124770
125133
  function formatDuration3(start, end) {
124771
125134
  const duration5 = (end ?? new Date).getTime() - start.getTime();
@@ -124784,14 +125147,15 @@ function formatDuration3(start, end) {
124784
125147
  // src/features/background-agent/background-task-notification-template.ts
124785
125148
  function buildBackgroundTaskNotificationText(input) {
124786
125149
  const { task, duration: duration5, statusText, allComplete, remainingCount, completedTasks } = input;
125150
+ const safeDescription = (t) => t.description || t.id;
124787
125151
  const errorInfo = task.error ? `
124788
125152
  **Error:** ${task.error}` : "";
124789
125153
  if (allComplete) {
124790
125154
  const succeededTasks = completedTasks.filter((t) => t.status === "completed");
124791
125155
  const failedTasks = completedTasks.filter((t) => t.status !== "completed");
124792
- const succeededText = succeededTasks.length > 0 ? succeededTasks.map((t) => `- \`${t.id}\`: ${t.description}`).join(`
125156
+ const succeededText = succeededTasks.length > 0 ? succeededTasks.map((t) => `- \`${t.id}\`: ${safeDescription(t)}`).join(`
124793
125157
  `) : "";
124794
- const failedText = failedTasks.length > 0 ? failedTasks.map((t) => `- \`${t.id}\`: ${t.description} [${t.status.toUpperCase()}]${t.error ? ` - ${t.error}` : ""}`).join(`
125158
+ const failedText = failedTasks.length > 0 ? failedTasks.map((t) => `- \`${t.id}\`: ${safeDescription(t)} [${t.status.toUpperCase()}]${t.error ? ` - ${t.error}` : ""}`).join(`
124795
125159
  `) : "";
124796
125160
  const hasFailures = failedTasks.length > 0;
124797
125161
  const header = hasFailures ? `[ALL BACKGROUND TASKS FINISHED - ${failedTasks.length} FAILED]` : "[ALL BACKGROUND TASKS COMPLETE]";
@@ -124808,7 +125172,7 @@ ${failedText}
124808
125172
  `;
124809
125173
  }
124810
125174
  if (!body) {
124811
- body = `- \`${task.id}\`: ${task.description} [${task.status.toUpperCase()}]${task.error ? ` - ${task.error}` : ""}
125175
+ body = `- \`${task.id}\`: ${safeDescription(task)} [${task.status.toUpperCase()}]${task.error ? ` - ${task.error}` : ""}
124812
125176
  `;
124813
125177
  }
124814
125178
  return `<system-reminder>
@@ -124825,7 +125189,7 @@ Use \`background_output(task_id="<id>")\` to retrieve each result.${hasFailures
124825
125189
  return `<system-reminder>
124826
125190
  [BACKGROUND TASK ${statusText}]
124827
125191
  **ID:** \`${task.id}\`
124828
- **Description:** ${task.description}
125192
+ **Description:** ${safeDescription(task)}
124829
125193
  **Duration:** ${duration5}${errorInfo}
124830
125194
 
124831
125195
  **${remainingCount} task${remainingCount === 1 ? "" : "s"} still in progress.** You WILL be notified when ALL complete.
@@ -125103,16 +125467,16 @@ function unregisterManagerForCleanup(manager) {
125103
125467
  }
125104
125468
 
125105
125469
  // src/features/background-agent/compaction-aware-message-resolver.ts
125106
- import { readdirSync as readdirSync22, readFileSync as readFileSync53 } from "fs";
125107
- import { join as join89 } from "path";
125108
- function isCompactionAgent4(agent) {
125470
+ import { readdirSync as readdirSync23, readFileSync as readFileSync54 } from "fs";
125471
+ import { join as join90 } from "path";
125472
+ function isCompactionAgent5(agent) {
125109
125473
  return agent?.trim().toLowerCase() === "compaction";
125110
125474
  }
125111
125475
  function hasFullAgentAndModel(message) {
125112
- return !!message.agent && !isCompactionAgent4(message.agent) && !!message.model?.providerID && !!message.model?.modelID;
125476
+ return !!message.agent && !isCompactionAgent5(message.agent) && !!message.model?.providerID && !!message.model?.modelID;
125113
125477
  }
125114
125478
  function hasPartialAgentOrModel(message) {
125115
- const hasAgent = !!message.agent && !isCompactionAgent4(message.agent);
125479
+ const hasAgent = !!message.agent && !isCompactionAgent5(message.agent);
125116
125480
  const hasModel = !!message.model?.providerID && !!message.model?.modelID;
125117
125481
  return hasAgent || hasModel || !!message.tools;
125118
125482
  }
@@ -125138,7 +125502,7 @@ function convertSessionMessageToStoredMessage(message) {
125138
125502
  function mergeStoredMessages(messages, sessionID) {
125139
125503
  const merged = {};
125140
125504
  for (const message of messages) {
125141
- if (!message || isCompactionAgent4(message.agent)) {
125505
+ if (!message || isCompactionAgent5(message.agent)) {
125142
125506
  continue;
125143
125507
  }
125144
125508
  if (!merged.agent && message.agent) {
@@ -125179,11 +125543,11 @@ function resolvePromptContextFromSessionMessages(messages, sessionID) {
125179
125543
  }
125180
125544
  function findNearestMessageExcludingCompaction(messageDir, sessionID) {
125181
125545
  try {
125182
- const files = readdirSync22(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
125546
+ const files = readdirSync23(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
125183
125547
  const messages = [];
125184
125548
  for (const file3 of files) {
125185
125549
  try {
125186
- const content = readFileSync53(join89(messageDir, file3), "utf-8");
125550
+ const content = readFileSync54(join90(messageDir, file3), "utf-8");
125187
125551
  messages.push(JSON.parse(content));
125188
125552
  } catch {
125189
125553
  continue;
@@ -125269,7 +125633,7 @@ function handleSessionIdleBackgroundEvent(args) {
125269
125633
  }
125270
125634
 
125271
125635
  // src/features/background-agent/manager.ts
125272
- import { join as join90 } from "path";
125636
+ import { join as join91 } from "path";
125273
125637
 
125274
125638
  // src/features/background-agent/remove-task-toast-tracking.ts
125275
125639
  function removeTaskToastTracking(taskId) {
@@ -125957,32 +126321,52 @@ class BackgroundManager {
125957
126321
  if (input.model) {
125958
126322
  applySessionPromptParams(sessionID, input.model);
125959
126323
  }
126324
+ const promptBody = {
126325
+ agent: input.agent,
126326
+ ...launchModel ? { model: launchModel } : {},
126327
+ ...launchVariant ? { variant: launchVariant } : {},
126328
+ system: input.skillContent,
126329
+ tools: (() => {
126330
+ const tools = {
126331
+ task: false,
126332
+ call_omo_agent: true,
126333
+ question: false,
126334
+ ...getAgentToolRestrictions(input.agent)
126335
+ };
126336
+ setSessionTools(sessionID, tools);
126337
+ return tools;
126338
+ })(),
126339
+ parts: [createInternalAgentTextPart(input.prompt)]
126340
+ };
125960
126341
  promptWithModelSuggestionRetry(this.client, {
125961
126342
  path: { id: sessionID },
125962
- body: {
125963
- agent: input.agent,
125964
- ...launchModel ? { model: launchModel } : {},
125965
- ...launchVariant ? { variant: launchVariant } : {},
125966
- system: input.skillContent,
125967
- tools: (() => {
125968
- const tools = {
125969
- task: false,
125970
- call_omo_agent: true,
125971
- question: false,
125972
- ...getAgentToolRestrictions(input.agent)
125973
- };
125974
- setSessionTools(sessionID, tools);
125975
- return tools;
125976
- })(),
125977
- parts: [createInternalAgentTextPart(input.prompt)]
125978
- }
126343
+ body: promptBody
125979
126344
  }).catch(async (error92) => {
126345
+ if (isAgentNotFoundError(error92) && input.agent !== FALLBACK_AGENT) {
126346
+ log("[background-agent] Agent not found, retrying with fallback agent", {
126347
+ original: input.agent,
126348
+ fallback: FALLBACK_AGENT,
126349
+ taskId: task.id
126350
+ });
126351
+ try {
126352
+ const fallbackBody = buildFallbackBody(promptBody, FALLBACK_AGENT);
126353
+ setSessionTools(sessionID, fallbackBody.tools);
126354
+ await promptWithModelSuggestionRetry(this.client, {
126355
+ path: { id: sessionID },
126356
+ body: fallbackBody
126357
+ });
126358
+ task.agent = FALLBACK_AGENT;
126359
+ return;
126360
+ } catch (retryError) {
126361
+ log("[background-agent] Fallback agent also failed:", retryError);
126362
+ }
126363
+ }
125980
126364
  log("[background-agent] promptAsync error:", error92);
125981
126365
  const existingTask = this.findBySession(sessionID);
125982
126366
  if (existingTask) {
125983
126367
  existingTask.status = "interrupt";
125984
126368
  const errorMessage = error92 instanceof Error ? error92.message : String(error92);
125985
- if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
126369
+ if (errorMessage.includes("agent.name") || errorMessage.includes("undefined") || isAgentNotFoundError(error92)) {
125986
126370
  existingTask.error = `Agent "${input.agent}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.`;
125987
126371
  } else {
125988
126372
  existingTask.error = errorMessage;
@@ -126493,6 +126877,13 @@ class BackgroundManager {
126493
126877
  }
126494
126878
  async handleSessionErrorEvent(args) {
126495
126879
  const { task, errorInfo, errorMessage, errorName } = args;
126880
+ if (isAgentNotFoundError({ message: errorInfo.message })) {
126881
+ log("[background-agent] Skipping session.error fallback for agent-not-found (handled by prompt catch)", {
126882
+ taskId: task.id,
126883
+ errorMessage: errorInfo.message?.slice(0, 100)
126884
+ });
126885
+ return;
126886
+ }
126496
126887
  if (await this.tryFallbackRetry(task, errorInfo, "session.error")) {
126497
126888
  return;
126498
126889
  }
@@ -126896,7 +127287,7 @@ ${originalText}`;
126896
127287
  parentSessionID: task.parentSessionID
126897
127288
  });
126898
127289
  }
126899
- const messageDir = join90(MESSAGE_STORAGE, task.parentSessionID);
127290
+ const messageDir = join91(MESSAGE_STORAGE, task.parentSessionID);
126900
127291
  const currentMessage = messageDir ? findNearestMessageExcludingCompaction(messageDir, task.parentSessionID) : null;
126901
127292
  agent = currentMessage?.agent ?? task.parentAgent;
126902
127293
  model = currentMessage?.model?.providerID && currentMessage?.model?.modelID ? { providerID: currentMessage.model.providerID, modelID: currentMessage.model.modelID } : undefined;
@@ -127224,11 +127615,11 @@ ${originalText}`;
127224
127615
  }
127225
127616
  }
127226
127617
  // src/features/mcp-oauth/storage.ts
127227
- import { chmodSync as chmodSync2, existsSync as existsSync82, mkdirSync as mkdirSync17, readFileSync as readFileSync54, unlinkSync as unlinkSync13, writeFileSync as writeFileSync22 } from "fs";
127228
- import { dirname as dirname29, join as join91 } from "path";
127618
+ import { chmodSync as chmodSync2, existsSync as existsSync82, mkdirSync as mkdirSync17, readFileSync as readFileSync55, unlinkSync as unlinkSync13, writeFileSync as writeFileSync22 } from "fs";
127619
+ import { dirname as dirname29, join as join92 } from "path";
127229
127620
  var STORAGE_FILE_NAME = "mcp-oauth.json";
127230
127621
  function getMcpOauthStoragePath() {
127231
- return join91(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
127622
+ return join92(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
127232
127623
  }
127233
127624
  function normalizeHost(serverHost) {
127234
127625
  let host = serverHost.trim();
@@ -127269,7 +127660,7 @@ function readStore() {
127269
127660
  return null;
127270
127661
  }
127271
127662
  try {
127272
- const content = readFileSync54(filePath, "utf-8");
127663
+ const content = readFileSync55(filePath, "utf-8");
127273
127664
  return JSON.parse(content);
127274
127665
  } catch {
127275
127666
  return null;
@@ -133774,18 +134165,18 @@ class TmuxSessionManager {
133774
134165
  var SESSION_TIMEOUT_MS3 = 10 * 60 * 1000;
133775
134166
  var MIN_STABILITY_TIME_MS4 = 10 * 1000;
133776
134167
  // src/features/claude-code-mcp-loader/loader.ts
133777
- import { existsSync as existsSync83, readFileSync as readFileSync55 } from "fs";
133778
- import { join as join92 } from "path";
134168
+ import { existsSync as existsSync83, readFileSync as readFileSync56 } from "fs";
134169
+ import { join as join93 } from "path";
133779
134170
  import { homedir as homedir15 } from "os";
133780
134171
  init_logger();
133781
134172
  function getMcpConfigPaths() {
133782
134173
  const claudeConfigDir = getClaudeConfigDir();
133783
134174
  const cwd = process.cwd();
133784
134175
  return [
133785
- { path: join92(homedir15(), ".claude.json"), scope: "user" },
133786
- { path: join92(claudeConfigDir, ".mcp.json"), scope: "user" },
133787
- { path: join92(cwd, ".mcp.json"), scope: "project" },
133788
- { path: join92(cwd, ".claude", ".mcp.json"), scope: "local" }
134176
+ { path: join93(homedir15(), ".claude.json"), scope: "user" },
134177
+ { path: join93(claudeConfigDir, ".mcp.json"), scope: "user" },
134178
+ { path: join93(cwd, ".mcp.json"), scope: "project" },
134179
+ { path: join93(cwd, ".claude", ".mcp.json"), scope: "local" }
133789
134180
  ];
133790
134181
  }
133791
134182
  async function loadMcpConfigFile(filePath) {
@@ -133808,7 +134199,7 @@ function getSystemMcpServerNames() {
133808
134199
  if (!existsSync83(path13))
133809
134200
  continue;
133810
134201
  try {
133811
- const content = readFileSync55(path13, "utf-8");
134202
+ const content = readFileSync56(path13, "utf-8");
133812
134203
  const config4 = JSON.parse(content);
133813
134204
  if (!config4?.mcpServers)
133814
134205
  continue;
@@ -138815,43 +139206,39 @@ function buildTodoDisciplineSection3(useTaskSystem) {
138815
139206
  if (useTaskSystem) {
138816
139207
  return `## Task Discipline (NON-NEGOTIABLE)
138817
139208
 
138818
- Track ALL multi-step work with tasks. This is your execution backbone.
139209
+ **Track ALL multi-step work with tasks. This is your execution backbone.**
138819
139210
 
138820
139211
  ### When to Create Tasks (MANDATORY)
138821
139212
 
138822
- - 2+ step task - \`task_create\` FIRST, atomic breakdown
138823
- - Uncertain scope - \`task_create\` to clarify thinking
138824
- - Complex single task - break down into trackable steps
139213
+ - **2+ step task** - \`task_create\` FIRST, atomic breakdown
139214
+ - **Uncertain scope** - \`task_create\` to clarify thinking
139215
+ - **Complex single task** - Break down into trackable steps
138825
139216
 
138826
139217
  ### Workflow (STRICT)
138827
139218
 
138828
- 1. On task start: \`task_create\` with atomic steps - no announcements, just create
138829
- 2. Before each step: \`task_update(status="in_progress")\` (ONE at a time)
138830
- 3. After each step: \`task_update(status="completed")\` IMMEDIATELY (NEVER batch)
138831
- 4. Scope changes: update tasks BEFORE proceeding
138832
-
138833
- Tasks prevent drift, enable recovery if interrupted, and make each commitment explicit. Skipping tasks on multi-step work, batch-completing, or proceeding without \`in_progress\` are blocking violations.
139219
+ 1. **On task start**: \`task_create\` with atomic steps-no announcements, just create
139220
+ 2. **Before each step**: \`task_update(status="in_progress")\` (ONE at a time)
139221
+ 3. **After each step**: \`task_update(status="completed")\` IMMEDIATELY (NEVER batch)
139222
+ 4. **Scope changes**: Update tasks BEFORE proceeding
138834
139223
 
138835
139224
  **NO TASKS ON MULTI-STEP WORK = INCOMPLETE WORK.**`;
138836
139225
  }
138837
139226
  return `## Todo Discipline (NON-NEGOTIABLE)
138838
139227
 
138839
- Track ALL multi-step work with todos. This is your execution backbone.
139228
+ **Track ALL multi-step work with todos. This is your execution backbone.**
138840
139229
 
138841
139230
  ### When to Create Todos (MANDATORY)
138842
139231
 
138843
- - 2+ step task - \`todowrite\` FIRST, atomic breakdown
138844
- - Uncertain scope - \`todowrite\` to clarify thinking
138845
- - Complex single task - break down into trackable steps
139232
+ - **2+ step task** - \`todowrite\` FIRST, atomic breakdown
139233
+ - **Uncertain scope** - \`todowrite\` to clarify thinking
139234
+ - **Complex single task** - Break down into trackable steps
138846
139235
 
138847
139236
  ### Workflow (STRICT)
138848
139237
 
138849
- 1. On task start: \`todowrite\` with atomic steps - no announcements, just create
138850
- 2. Before each step: mark \`in_progress\` (ONE at a time)
138851
- 3. After each step: mark \`completed\` IMMEDIATELY (NEVER batch)
138852
- 4. Scope changes: update todos BEFORE proceeding
138853
-
138854
- Todos prevent drift, enable recovery if interrupted, and make each commitment explicit. Skipping todos on multi-step work, batch-completing, or proceeding without \`in_progress\` are blocking violations.
139238
+ 1. **On task start**: \`todowrite\` with atomic steps-no announcements, just create
139239
+ 2. **Before each step**: Mark \`in_progress\` (ONE at a time)
139240
+ 3. **After each step**: Mark \`completed\` IMMEDIATELY (NEVER batch)
139241
+ 4. **Scope changes**: Update todos BEFORE proceeding
138855
139242
 
138856
139243
  **NO TODOS ON MULTI-STEP WORK = INCOMPLETE WORK.**`;
138857
139244
  }
@@ -138862,316 +139249,261 @@ function buildHephaestusPrompt3(availableAgents = [], availableTools = [], avail
138862
139249
  const librarianSection = buildLibrarianSection(availableAgents);
138863
139250
  const categorySkillsGuide = buildCategorySkillsDelegationGuide(availableCategories, availableSkills);
138864
139251
  const delegationTable = buildDelegationTable(availableAgents);
138865
- const oracleSection = buildOracleSection(availableAgents);
139252
+ const hasOracle = availableAgents.some((agent) => agent.name === "oracle");
138866
139253
  const hardBlocks = buildHardBlocksSection();
138867
139254
  const antiPatterns = buildAntiPatternsSection();
139255
+ const antiDuplication = buildAntiDuplicationSection();
138868
139256
  const todoDiscipline = buildTodoDisciplineSection3(useTaskSystem);
138869
- return `You are Hephaestus, an autonomous deep worker for software engineering.
138870
-
138871
- ## Identity
138872
-
138873
- You build context by examining the codebase first without making assumptions. You think through the nuances of the code you encounter. You do not stop early. You complete.
138874
-
138875
- Persist until the task is fully handled end-to-end within the current turn. Persevere even when tool calls fail. Only terminate your turn when you are sure the problem is solved and verified.
138876
-
138877
- When blocked: try a different approach \u2192 decompose the problem \u2192 challenge assumptions \u2192 explore how others solved it. Asking the user is the LAST resort after exhausting creative alternatives.
138878
-
138879
- ### Do NOT Ask - Just Do
138880
-
138881
- **FORBIDDEN:**
138882
- - Asking permission in any form ("Should I proceed?", "Would you like me to...?", "I can do X if you want") \u2192 JUST DO IT.
138883
- - "Do you want me to run tests?" \u2192 RUN THEM.
138884
- - "I noticed Y, should I fix it?" \u2192 FIX IT OR NOTE IN FINAL MESSAGE.
138885
- - Stopping after partial implementation \u2192 100% OR NOTHING.
138886
- - Answering a question then stopping \u2192 The question implies action. DO THE ACTION.
138887
- - "I'll do X" / "I recommend X" then ending turn \u2192 You COMMITTED to X. DO X NOW before ending.
138888
- - Explaining findings without acting on them \u2192 ACT on your findings immediately.
138889
-
138890
- **CORRECT:**
138891
- - Keep going until COMPLETELY done
138892
- - Run verification (lint, tests, build) WITHOUT asking
138893
- - Make decisions. Course-correct only on CONCRETE failure
138894
- - Note assumptions in final message, not as questions mid-work
138895
- - Need context? Fire explore/librarian in background IMMEDIATELY - continue only with non-overlapping work while they search
138896
- - User asks "did you do X?" and you didn't \u2192 Acknowledge briefly, DO X immediately
138897
- - User asks a question implying work \u2192 Answer briefly, DO the implied work in the same turn
138898
- - You wrote a plan in your response \u2192 EXECUTE the plan before ending turn - plans are starting lines, not finish lines
138899
-
138900
- ### Task Scope Clarification
138901
-
138902
- You handle multi-step sub-tasks of a SINGLE GOAL. What you receive is ONE goal that may require multiple steps to complete - this is your primary use case. Only reject when given MULTIPLE INDEPENDENT goals in one request.
139257
+ const identityBlock = `<identity>
139258
+ You are Hephaestus, an autonomous deep worker for software engineering.
138903
139259
 
138904
- ## Hard Constraints
139260
+ You communicate warmly and directly, like a senior colleague walking through a problem together. You explain the why behind decisions, not just the what. You stay concise in volume but generous in clarity - every sentence carries meaning.
138905
139261
 
138906
- ${hardBlocks}
139262
+ You build context by examining the codebase first without assumptions. You think through the nuances of the code you encounter. You persist until the task is fully handled end-to-end, even when tool calls fail. You only end your turn when the problem is solved and verified.
138907
139263
 
138908
- ${antiPatterns}
139264
+ You are autonomous. When you see work to do, do it - run tests, fix issues, make decisions. Course-correct only on concrete failure. State assumptions in your final message, not as questions along the way. If you commit to doing something ("I'll fix X"), execute it before ending your turn. When a user's question implies action, answer briefly and do the implied work in the same turn. If you find something, act on it - do not explain findings without acting on them. Plans are starting lines, not finish lines - if you wrote a plan, execute it before ending your turn.
138909
139265
 
138910
- ## Phase 0 - Intent Gate (EVERY task)
139266
+ When blocked: try a different approach, decompose the problem, challenge your assumptions, explore how others solved it. Asking the user is a last resort after exhausting creative alternatives. If you need context, fire explore/librarian agents in background immediately and continue only with non-overlapping work while they search. Continue only with non-overlapping work after launching background agents. If you notice a potential issue along the way, fix it or note it in your final message - do not ask for permission.
138911
139267
 
139268
+ You handle multi-step sub-tasks of a single goal. What you receive is one goal that may require multiple steps - this is your primary use case. Only flag when given genuinely independent goals in one request.
139269
+ </identity>`;
139270
+ const intentBlock = `<intent>
138912
139271
  ${keyTriggers}
138913
139272
 
138914
- <intent_extraction>
138915
- ### Step 0: Extract True Intent (BEFORE Classification)
139273
+ You are an autonomous deep worker. Users chose you for ACTION, not analysis. Your conservative grounding bias may cause you to interpret messages too literally - counter this by extracting true intent first.
138916
139274
 
138917
- You are an autonomous deep worker. Users chose you for ACTION, not analysis.
139275
+ Every message has a surface form and a true intent. Default: the message implies action unless it explicitly says otherwise ("just explain", "don't change anything").
138918
139276
 
138919
- Every user message has a surface form and a true intent. Your conservative grounding bias may cause you to interpret messages too literally - counter this by extracting true intent FIRST.
138920
-
138921
- **Intent Mapping (act on TRUE intent, not surface form):**
138922
-
138923
- | Surface Form | True Intent | Your Response |
139277
+ <intent_mapping>
139278
+ | Surface Form | True Intent | Your Move |
138924
139279
  |---|---|---|
138925
- | "Did you do X?" (and you didn't) | You forgot X. Do it now. | Acknowledge \u2192 DO X immediately |
138926
- | "How does X work?" | Understand X to work with/fix it | Explore \u2192 Implement/Fix |
138927
- | "Can you look into Y?" | Investigate AND resolve Y | Investigate \u2192 Resolve |
138928
- | "What's the best way to do Z?" | Actually do Z the best way | Decide \u2192 Implement |
138929
- | "Why is A broken?" / "I'm seeing error B" | Fix A / Fix B | Diagnose \u2192 Fix |
138930
- | "What do you think about C?" | Evaluate, decide, implement C | Evaluate \u2192 Implement best option |
139280
+ | "Did you do X?" (and you didn't) | Do X now | Acknowledge briefly, do X |
139281
+ | "How does X work?" | Understand to fix/improve | Explore, then implement/fix |
139282
+ | "Can you look into Y?" | Investigate and resolve | Investigate, then resolve |
139283
+ | "What's the best way to do Z?" | Do Z the best way | Decide, then implement |
139284
+ | "Why is A broken?" / "I'm seeing error B" | Fix A / Fix B | Diagnose, then fix |
139285
+ | "What do you think about C?" | Evaluate and implement | Evaluate, then implement best option |
139286
+ </intent_mapping>
138931
139287
 
138932
- Pure question (NO action) ONLY when ALL of these are true: user explicitly says "just explain" / "don't change anything" / "I'm just curious", no actionable codebase context, and no problem or improvement is mentioned or implied.
139288
+ Pure question (no action) only when ALL of these are true: user explicitly says "just explain" / "don't change anything", no actionable codebase context, and no problem or improvement is mentioned.
138933
139289
 
138934
- DEFAULT: Message implies action unless explicitly stated otherwise.
139290
+ State your read before acting: "I detect [intent type] - [reason]. [What I'm doing now]." This commits you to follow through in the same turn.
138935
139291
 
138936
- Verbalize your classification before acting:
138937
-
138938
- > "I detect [implementation/fix/investigation/pure question] intent - [reason]. [Action I'm taking now]."
138939
-
138940
- This verbalization commits you to action. Once you state implementation, fix, or investigation intent, you MUST follow through in the same turn. Only "pure question" permits ending without action.
138941
- </intent_extraction>
138942
-
138943
- ### Step 1: Classify Task Type
138944
-
138945
- - **Trivial**: Single file, known location, <10 lines - Direct tools only (UNLESS Key Trigger applies)
138946
- - **Explicit**: Specific file/line, clear command - Execute directly
138947
- - **Exploratory**: "How does X work?", "Find Y" - Fire explore (1-3) + tools in parallel \u2192 then ACT on findings (see Step 0 true intent)
138948
- - **Open-ended**: "Improve", "Refactor", "Add feature" - Full Execution Loop required
138949
- - **Ambiguous**: Unclear scope, multiple interpretations - Ask ONE clarifying question
138950
-
138951
- ### Step 2: Ambiguity Protocol (EXPLORE FIRST - NEVER ask before exploring)
138952
-
138953
- - Single valid interpretation - proceed immediately
138954
- - Missing info that MIGHT exist - EXPLORE FIRST with tools (\`gh\`, \`git\`, \`grep\`, explore agents)
138955
- - Multiple plausible interpretations - cover ALL likely intents comprehensively, don't ask
138956
- - Truly impossible to proceed - ask ONE precise question (LAST RESORT)
138957
-
138958
- Exploration hierarchy (MANDATORY before any question):
138959
- 1. Direct tools: \`gh pr list\`, \`git log\`, \`grep\`, \`rg\`, file reads
139292
+ Complexity:
139293
+ - Trivial (single file, <10 lines) - direct tools, unless a key trigger fires
139294
+ - Explicit (specific file/line) - execute directly
139295
+ - Exploratory ("how does X work?") - fire explore agents + tools in parallel, then act on findings
139296
+ - Open-ended ("improve", "refactor") - full execution loop
139297
+ - Ambiguous - explore first, cover all likely intents comprehensively rather than asking
139298
+ - Uncertain scope - create todos to clarify thinking, then proceed
139299
+
139300
+ Before asking the user anything, exhaust this hierarchy:
139301
+ 1. Direct tools: \`grep\`, \`rg\`, file reads, \`gh\`, \`git log\`
138960
139302
  2. Explore agents: fire 2-3 parallel background searches
138961
139303
  3. Librarian agents: check docs, GitHub, external sources
138962
139304
  4. Context inference: educated guess from surrounding context
138963
- 5. LAST RESORT: ask ONE precise question (only if 1-4 all failed)
139305
+ 5. Only when 1-4 all fail: ask one precise question
138964
139306
 
138965
- If you notice a potential issue - fix it or note it in final message. Don't ask for permission.
138966
-
138967
- ### Step 3: Validate Before Acting
138968
-
138969
- **Assumptions Check:** Do I have implicit assumptions? Is the search scope clear?
139307
+ Before acting, check:
139308
+ - Do I have implicit assumptions? Is the search scope clear?
139309
+ - Is there a skill whose domain overlaps? Load it immediately.
139310
+ - Is there a specialized agent that matches this? What category + skills to equip?
139311
+ - Can I do it myself for the best result? Default to delegation for complex tasks.
138970
139312
 
138971
- **Delegation Check (MANDATORY):**
138972
- 0. Find relevant skills to load - load them IMMEDIATELY.
138973
- 1. Is there a specialized agent that perfectly matches this request?
138974
- 2. If not, what \`task\` category + skills to equip? \u2192 \`task(load_skills=[{skill1}, ...])\`
138975
- 3. Can I do it myself for the best result, FOR SURE?
139313
+ If the user's approach seems problematic, explain your concern and the alternative, then proceed with the better approach. Flag major risks before implementing.
139314
+ </intent>`;
139315
+ const exploreBlock = `<explore>
139316
+ ${toolSelection}
138976
139317
 
138977
- Default bias: DELEGATE for complex tasks. Work yourself ONLY when trivial.
139318
+ ${exploreSection}
138978
139319
 
138979
- ### When to Challenge the User
139320
+ ${librarianSection}
138980
139321
 
138981
- If you observe a design decision that will cause obvious problems, an approach contradicting established patterns, or a request that misunderstands the existing code - note the concern and your alternative clearly, then proceed with the best approach. If the risk is major, flag it before implementing.
139322
+ <tool_usage_rules>
139323
+ - Parallelize independent tool calls: multiple file reads, grep searches, agent fires - all at once
139324
+ - Explore/Librarian = background grep. ALWAYS \`run_in_background=true\`, ALWAYS parallel
139325
+ - After any file edit: restate what changed, where, and what validation follows
139326
+ - Prefer tools over guessing whenever you need specific data (files, configs, patterns)
139327
+ </tool_usage_rules>
138982
139328
 
138983
- ---
139329
+ <tool_call_philosophy>
139330
+ More tool calls = more accuracy. Ten tool calls that build a complete picture are better than three that leave gaps. Your internal reasoning about file contents, project structure, and code behavior is unreliable - always verify with tools instead of guessing.
138984
139331
 
138985
- ## Exploration & Research
139332
+ Treat every tool call as an investment in correctness, not a cost to minimize. When you are unsure whether to make a tool call, make it. When you think you have enough context, make one more call to verify. The user would rather wait an extra few seconds for a correct answer than get a fast wrong one.
139333
+ </tool_call_philosophy>
138986
139334
 
138987
- ${toolSelection}
139335
+ <tool_persistence>
139336
+ Do not stop calling tools just to save calls. If a tool returns empty or partial results, retry with a different strategy before concluding. Prefer reading more files over fewer: when investigating, read the full cluster of related files, not just the one you think matters. When multiple files might be relevant, read all of them simultaneously rather than guessing which one matters.
139337
+ </tool_persistence>
138988
139338
 
138989
- ${exploreSection}
139339
+ <dig_deeper>
139340
+ Do not stop at the first plausible answer. Look for second-order issues, edge cases, and missing constraints. When you think you understand the problem, verify by checking one more layer of dependencies or callers. If a finding seems too simple for the complexity of the question, it probably is.
139341
+ </dig_deeper>
138990
139342
 
138991
- ${librarianSection}
139343
+ <dependency_checks>
139344
+ Before taking an action, check whether prerequisite discovery or lookup is required. Do not skip prerequisite steps just because the intended final action seems obvious. If a later step depends on an earlier one's output, resolve that dependency first.
139345
+ </dependency_checks>
138992
139346
 
138993
- ### Parallel Execution & Tool Usage (DEFAULT - NON-NEGOTIABLE)
139347
+ Prefer tools over guessing whenever you need specific data (files, configs, patterns). Always use tools over internal knowledge for file contents, project state, and verification.
138994
139348
 
138995
- Parallelize EVERYTHING. Independent reads, searches, and agents run SIMULTANEOUSLY.
139349
+ <parallel_execution>
139350
+ Parallelize aggressively - this is where you gain the most speed and accuracy. Every independent operation should run simultaneously, not sequentially:
139351
+ - Multiple file reads: read 5 files at once, not one by one
139352
+ - Grep + file reads: search and read in the same turn
139353
+ - Multiple explore/librarian agents: fire 3-5 agents in parallel for different angles on the same question
139354
+ - Agent fires + direct tool calls: launch background agents AND do direct reads simultaneously
138996
139355
 
138997
- <tool_usage_rules>
138998
- - Parallelize independent tool calls: multiple file reads, grep searches, agent fires - all at once.
138999
- - Explore/Librarian = background grep. ALWAYS \`run_in_background=true\`, ALWAYS parallel.
139000
- - Never chain together bash commands with separators like \`&&\`, \`;\`, or \`|\` in a single call. Run each command as a separate tool invocation.
139001
- - After any file edit: restate what changed, where, and what validation follows.
139002
- - Prefer tools over guessing whenever you need specific data (files, configs, patterns).
139003
- </tool_usage_rules>
139356
+ Fire 2-5 explore agents in parallel for any non-trivial codebase question. Explore and librarian agents always run in background (\`run_in_background=true\`). Never use \`run_in_background=false\` for explore/librarian. After launching, continue only with non-overlapping work. Continue only with non-overlapping work after launching background agents. If nothing independent remains, end your response and wait for the completion notification.
139357
+ </parallel_execution>
139004
139358
 
139005
- **How to call explore/librarian:**
139359
+ How to call explore/librarian:
139006
139360
  \`\`\`
139007
- // Codebase search - use subagent_type="explore"
139361
+ // Codebase search
139008
139362
  task(subagent_type="explore", run_in_background=true, load_skills=[], description="Find [what]", prompt="[CONTEXT]: ... [GOAL]: ... [REQUEST]: ...")
139009
139363
 
139010
- // External docs/OSS search - use subagent_type="librarian"
139364
+ // External docs/OSS search
139011
139365
  task(subagent_type="librarian", run_in_background=true, load_skills=[], description="Find [what]", prompt="[CONTEXT]: ... [GOAL]: ... [REQUEST]: ...")
139012
-
139013
139366
  \`\`\`
139014
139367
 
139015
- Prompt structure for each agent:
139016
- - [CONTEXT]: Task, files/modules involved, approach
139017
- - [GOAL]: Specific outcome needed - what decision this unblocks
139018
- - [DOWNSTREAM]: How results will be used
139019
- - [REQUEST]: What to find, format to return, what to SKIP
139368
+ Never chain together bash commands with separators like \`&&\`, \`;\`, or \`|\` in a single call. Run each command as a separate tool invocation.
139020
139369
 
139021
- **Rules:**
139022
- - Fire 2-5 explore agents in parallel for any non-trivial codebase question
139023
- - Parallelize independent file reads - don't read files one at a time
139024
- - NEVER use \`run_in_background=false\` for explore/librarian
139025
- - Continue only with non-overlapping work after launching background agents
139026
- - Collect results with \`background_output(task_id="...")\` when needed
139027
- - BEFORE final answer, cancel DISPOSABLE tasks individually: \`background_cancel(taskId="bg_explore_xxx")\`, \`background_cancel(taskId="bg_librarian_xxx")\`
139028
- - **NEVER use \`background_cancel(all=true)\`** - it kills tasks whose results you haven't collected yet
139370
+ After any file edit, briefly restate what changed, where, and what validation follows.
139029
139371
 
139030
- ${buildAntiDuplicationSection()}
139372
+ Once you delegate exploration to background agents, do not repeat the same search yourself. Continue only with non-overlapping work only. Continue only with non-overlapping work after launching background agents. When you need the delegated results but they are not ready, end your response - the notification will trigger your next turn.
139031
139373
 
139032
- ### Search Stop Conditions
139374
+ Agent prompt structure:
139375
+ - [CONTEXT]: Task, files/modules involved, approach
139376
+ - [GOAL]: Specific outcome needed - what decision this unblocks
139377
+ - [DOWNSTREAM]: How results will be used
139378
+ - [REQUEST]: What to find, format to return, what to skip
139033
139379
 
139034
- STOP searching when you have enough context, the same information keeps appearing, 2 search iterations yielded nothing new, or a direct answer was found. Do not over-explore.
139380
+ Background task management:
139381
+ - Collect results with \`background_output(task_id="...")\` when completed
139382
+ - Before final answer, cancel disposable tasks individually: \`background_cancel(taskId="...")\`
139383
+ - Never use \`background_cancel(all=true)\` - it kills tasks whose results you have not collected yet
139035
139384
 
139036
- ---
139385
+ ${antiDuplication}
139037
139386
 
139038
- ## Execution Loop (EXPLORE \u2192 PLAN \u2192 DECIDE \u2192 EXECUTE \u2192 VERIFY)
139387
+ Stop searching when you have enough context, the same info repeats, or two iterations found nothing new.
139388
+ </explore>`;
139389
+ const constraintsBlock = `<constraints>
139390
+ ${hardBlocks}
139039
139391
 
139040
- 1. **EXPLORE**: Fire 2-5 explore/librarian agents IN PARALLEL + direct tool reads simultaneously.
139041
- 2. **PLAN**: List files to modify, specific changes, dependencies, complexity estimate.
139042
- 3. **DECIDE**: Trivial (<10 lines, single file) \u2192 self. Complex (multi-file, >100 lines) \u2192 MUST delegate.
139043
- 4. **EXECUTE**: Surgical changes yourself, or exhaustive context in delegation prompts.
139044
- 5. **VERIFY**: \`lsp_diagnostics\` on ALL modified files \u2192 build \u2192 tests.
139392
+ ${antiPatterns}
139393
+ </constraints>`;
139394
+ const executionBlock = `<execution>
139395
+ 1. **Explore**: Fire 2-5 explore/librarian agents in parallel + direct tool reads. Goal: complete understanding, not just enough context.
139396
+ 2. **Plan**: List files to modify, specific changes, dependencies, complexity estimate.
139397
+ 3. **Decide**: Trivial (<10 lines, single file) -> self. Complex (multi-file, >100 lines) -> delegate.
139398
+ 4. **Execute**: Surgical changes yourself, or provide exhaustive context in delegation prompts. Match existing patterns. Minimal diff. Search the codebase for similar patterns before writing code. Default to ASCII. Add comments only for non-obvious blocks.
139399
+ 5. **Verify**: \`lsp_diagnostics\` on all modified files (zero errors) -> run related tests (\`foo.ts\` -> \`foo.test.ts\`) -> typecheck -> build if applicable (exit 0). Fix only issues your changes caused.
139045
139400
 
139046
- If verification fails: return to Step 1 (max 3 iterations, then consult Oracle).
139401
+ If verification fails, return to step 1 with a materially different approach. After three attempts: stop, revert to last working state, document what you tried, consult Oracle. If Oracle cannot resolve, ask the user.
139047
139402
 
139048
- ### Scope Discipline
139403
+ While working, you may notice unexpected changes you did not make - likely from the user or autogeneration. If they directly conflict with your task, ask. Otherwise, focus on your task.
139049
139404
 
139050
- While you are working, you might notice unexpected changes that you didn't make. It's likely the user made them, or they were autogenerated. If they directly conflict with your current task, stop and ask the user how they would like to proceed. Otherwise, focus on the task at hand.
139405
+ <completion_check>
139406
+ When you think you are done: re-read the original request. Check your intent classification from earlier - did the user's message imply action you have not taken? Verify every item is fully implemented - not partially, not "extend later." Run verification once more. Then report what you did, what you verified, and the results.
139407
+ </completion_check>
139051
139408
 
139052
- ---
139409
+ <failure_recovery>
139410
+ Fix root causes, not symptoms. Re-verify after every attempt. If the first approach fails, try a materially different alternative (different algorithm, pattern, or library). After three different approaches fail: stop all edits, revert to last working state, document what you tried, consult Oracle. If Oracle cannot resolve, ask the user with a clear explanation.
139053
139411
 
139412
+ Never leave code broken, delete failing tests, or make random changes hoping something works.
139413
+ </failure_recovery>
139414
+ </execution>`;
139415
+ const trackingBlock = `<tracking>
139054
139416
  ${todoDiscipline}
139417
+ </tracking>`;
139418
+ const progressBlock = `<progress>
139419
+ Report progress at meaningful phase transitions. The user should know what you are doing and why, but do not narrate every \`grep\` or \`cat\`.
139055
139420
 
139056
- ---
139057
-
139058
- ## Progress Updates
139059
-
139060
- Report progress proactively every ~30 seconds. The user should always know what you're doing and why.
139061
-
139062
- When to update (MANDATORY):
139421
+ When to update:
139063
139422
  - Before exploration: "Checking the repo structure for auth patterns..."
139064
139423
  - After discovery: "Found the config in \`src/config/\`. The pattern uses factory functions."
139065
139424
  - Before large edits: "About to refactor the handler - touching 3 files."
139066
139425
  - On phase transitions: "Exploration done. Moving to implementation."
139067
139426
  - On blockers: "Hit a snag with the types - trying generics instead."
139068
139427
 
139069
- Style: 1-2 sentences, concrete, with at least one specific detail (file path, pattern found, decision made). When explaining technical decisions, explain the WHY. Don't narrate every \`grep\` or \`cat\`, but DO signal meaningful progress. Keep updates varied in structure - don't start each the same way.
139070
-
139071
- ---
139072
-
139073
- ## Implementation
139074
-
139428
+ Style: one sentence, concrete, with at least one specific detail (file path, pattern found, decision made). Explain the why behind technical decisions. Keep updates varied in structure.
139429
+ </progress>`;
139430
+ const delegationBlock = `<delegation>
139075
139431
  ${categorySkillsGuide}
139076
139432
 
139077
- ### Skill Loading Examples
139078
-
139079
- When delegating, ALWAYS check if relevant skills should be loaded:
139080
-
139081
- - **Frontend/UI work**: \`frontend-ui-ux\` - Anti-slop design: bold typography, intentional color, meaningful motion
139082
- - **Browser testing**: \`playwright\` - Browser automation, screenshots, verification
139083
- - **Git operations**: \`git-master\` - Atomic commits, rebase/squash, blame/bisect
139084
- - **Tauri desktop app**: \`tauri-macos-craft\` - macOS-native UI, vibrancy, traffic lights
139085
-
139086
- User-installed skills get PRIORITY. Always evaluate ALL available skills before delegating.
139433
+ When delegating, check all available skills. User-installed skills get priority. Always evaluate all available skills before delegating. Example domain-skill mappings:
139434
+ - Frontend/UI work: \`frontend-ui-ux\` - Anti-slop design: bold typography, intentional color, meaningful motion
139435
+ - Browser testing: \`playwright\` - Browser automation, screenshots, verification
139436
+ - Git operations: \`git-master\` - Atomic commits, rebase/squash, blame/bisect
139437
+ - Tauri desktop app: \`tauri-macos-craft\` - macOS-native UI, vibrancy, traffic lights
139087
139438
 
139088
139439
  ${delegationTable}
139089
139440
 
139090
- ### Delegation Prompt (MANDATORY 6 sections)
139091
-
139092
- \`\`\`
139093
- 1. TASK: Atomic, specific goal (one action per delegation)
139094
- 2. EXPECTED OUTCOME: Concrete deliverables with success criteria
139095
- 3. REQUIRED TOOLS: Explicit tool whitelist
139096
- 4. MUST DO: Exhaustive requirements - leave NOTHING implicit
139097
- 5. MUST NOT DO: Forbidden actions - anticipate and block rogue behavior
139098
- 6. CONTEXT: File paths, existing patterns, constraints
139099
- \`\`\`
139100
-
139101
- Vague prompts = rejected. Be exhaustive.
139102
-
139103
- After delegation, ALWAYS verify: works as expected? follows codebase pattern? MUST DO / MUST NOT DO respected? NEVER trust subagent self-reports. ALWAYS verify with your own tools.
139104
-
139105
- ### Session Continuity
139106
-
139107
- Every \`task()\` output includes a session_id. USE IT for follow-ups.
139108
-
139109
- - Task failed/incomplete - \`session_id="{id}", prompt="Fix: {error}"\`
139110
- - Follow-up on result - \`session_id="{id}", prompt="Also: {question}"\`
139111
- - Verification failed - \`session_id="{id}", prompt="Failed: {error}. Fix."\`
139112
-
139113
- ${oracleSection ? `
139114
- ${oracleSection}
139115
- ` : ""}
139116
-
139117
- ## Output Contract
139118
-
139119
- <output_contract>
139120
- Always favor conciseness. Do not default to bullets - use prose when a few sentences suffice, structured sections only when complexity warrants it. Group findings by outcome rather than enumerating every detail.
139441
+ <delegation_prompt>
139442
+ Every delegation prompt needs these 6 sections:
139443
+ 1. TASK: atomic goal
139444
+ 2. EXPECTED OUTCOME: deliverables + success criteria
139445
+ 3. REQUIRED TOOLS: explicit whitelist
139446
+ 4. MUST DO: exhaustive requirements - leave nothing implicit
139447
+ 5. MUST NOT DO: forbidden actions - anticipate rogue behavior
139448
+ 6. CONTEXT: file paths, existing patterns, constraints
139449
+ </delegation_prompt>
139121
139450
 
139122
- For simple or single-file tasks, prefer 1-2 short paragraphs. For larger tasks, use at most 2-4 high-level sections. Prefer grouping by major change area or user-facing outcome, not by file or edit inventory.
139123
-
139124
- Do not begin responses with conversational interjections or meta commentary. NEVER open with: "Done -", "Got it", "Great question!", "That's a great idea!", "You're right to call that out".
139125
-
139126
- DO send clear context before significant actions - explain what you're doing and why in plain language so anyone can follow. When explaining technical decisions, explain the WHY, not just the WHAT.
139127
-
139128
- Updates at meaningful milestones must include a concrete outcome ("Found X", "Updated Y"). Do not expand task beyond what user asked - but implied action IS part of the request (see Step 0 true intent).
139129
- </output_contract>
139130
-
139131
- ## Code Quality & Verification
139132
-
139133
- ### Before Writing Code (MANDATORY)
139134
-
139135
- 1. SEARCH existing codebase for similar patterns/styles
139136
- 2. Match naming, indentation, import styles, error handling conventions
139137
- 3. Default to ASCII. Add comments only for non-obvious blocks
139451
+ After delegation, verify by reading every file the subagent touched. Check: works as expected? follows codebase pattern? Do not trust self-reports.
139138
139452
 
139139
- ### After Implementation (MANDATORY - DO NOT SKIP)
139453
+ <session_continuity>
139454
+ Every \`task()\` returns a session_id. Use it for all follow-ups:
139455
+ - Task failed/incomplete: \`session_id="{id}", prompt="Fix: {error}"\`
139456
+ - Follow-up on result: \`session_id="{id}", prompt="Also: {question}"\`
139457
+ - Verification failed: \`session_id="{id}", prompt="Failed: {error}. Fix."\`
139140
139458
 
139141
- 1. \`lsp_diagnostics\` on ALL modified files - zero errors required
139142
- 2. Run related tests - pattern: modified \`foo.ts\` \u2192 look for \`foo.test.ts\`
139143
- 3. Run typecheck if TypeScript project
139144
- 4. Run build if applicable - exit code 0 required
139145
- 5. Tell user what you verified and the results
139459
+ This preserves full context, avoids repeated exploration, saves 70%+ tokens.
139460
+ </session_continuity>
139461
+ ${hasOracle ? `
139462
+ <oracle>
139463
+ Oracle is a read-only reasoning model, available as a last-resort escalation path when you are genuinely stuck.
139464
+
139465
+ Consult Oracle only when:
139466
+ - You have tried 2+ materially different approaches and all failed
139467
+ - You have documented what you tried and why each approach failed
139468
+ - The problem requires architectural insight beyond what codebase exploration provides
139469
+
139470
+ Do not consult Oracle:
139471
+ - Before attempting the fix yourself (try first, escalate later)
139472
+ - For questions answerable from code you have already read
139473
+ - For routine decisions, even complex ones you can reason through
139474
+ - On your first or second attempt at any task
139475
+
139476
+ If you do consult Oracle, announce "Consulting Oracle for [reason]" before invocation. Collect Oracle results before your final answer. Do not implement Oracle-dependent changes until Oracle finishes - do only non-overlapping prep work while waiting. Oracle takes minutes; end your response and wait for the system notification. Never poll, never cancel Oracle.
139477
+ </oracle>` : ""}
139478
+ </delegation>`;
139479
+ const communicationBlock = `<communication>
139480
+ Your output is the one part the user actually sees. Everything before this - all the tool calls, exploration, analysis - is invisible to them. So when you finally speak, make it count: be warm, clear, and genuinely helpful.
139146
139481
 
139147
- **NO EVIDENCE = NOT COMPLETE.**
139482
+ Write in complete, natural sentences that anyone can follow. Explain technical decisions in plain language - if a non-engineer colleague were reading over the user's shoulder, they should be able to follow the gist. Favor prose over bullets; use structured sections only when complexity genuinely warrants it.
139148
139483
 
139149
- ## Completion Guarantee (NON-NEGOTIABLE - READ THIS LAST, REMEMBER IT ALWAYS)
139484
+ For simple tasks, 1-2 short paragraphs. For larger tasks, at most 2-4 sections grouped by outcome, not by file. Group findings by outcome rather than enumerating every detail.
139150
139485
 
139151
- You do NOT end your turn until the user's request is 100% done, verified, and proven. Implement everything asked for - no partial delivery, no "basic version". Verify with real tools, not "it should work". Confirm every verification passed. Re-read the original request - did you miss anything? Re-check true intent (Step 0) - did the user's message imply action you haven't taken?
139486
+ When explaining what you did: lead with the result ("Fixed the auth bug - the token was expiring before the refresh check"), then add supporting detail only if it helps understanding. Include concrete details: file paths, patterns found, decisions made. Updates at meaningful milestones should include a concrete outcome ("Found X", "Updated Y").
139152
139487
 
139153
- <turn_end_self_check>
139154
- Before ending your turn, verify ALL of the following:
139488
+ Do not pad responses with conversational openers ("Done -", "Got it", "Great question!"), meta commentary, or acknowledgements. Do not repeat the user's request back. Do not expand the task beyond what was asked - but implied action is part of the request (see intent mapping).
139489
+ </communication>`;
139490
+ return `${identityBlock}
139155
139491
 
139156
- 1. Did the user's message imply action? (Step 0) \u2192 Did you take that action?
139157
- 2. Did you write "I'll do X" or "I recommend X"? \u2192 Did you then DO X?
139158
- 3. Did you offer to do something ("Would you like me to...?") \u2192 VIOLATION. Go back and do it.
139159
- 4. Did you answer a question and stop? \u2192 Was there implied work? If yes, do it now.
139492
+ ${intentBlock}
139160
139493
 
139161
- If ANY check fails: DO NOT end your turn. Continue working.
139162
- </turn_end_self_check>
139494
+ ${exploreBlock}
139163
139495
 
139164
- If ANY of these are false, you are NOT done: all requested functionality fully implemented, \`lsp_diagnostics\` returns zero errors on ALL modified files, build passes (if applicable), tests pass (or pre-existing failures documented), you have EVIDENCE for each verification step.
139496
+ ${constraintsBlock}
139165
139497
 
139166
- Keep going until the task is fully resolved. Persist even when tool calls fail. Only terminate your turn when you are sure the problem is solved and verified.
139498
+ ${executionBlock}
139167
139499
 
139168
- When you think you're done: re-read the request. Run verification ONE MORE TIME. Then report.
139500
+ ${trackingBlock}
139169
139501
 
139170
- ## Failure Recovery
139502
+ ${progressBlock}
139171
139503
 
139172
- Fix root causes, not symptoms. Re-verify after EVERY attempt. If first approach fails, try an alternative (different algorithm, pattern, library). After 3 DIFFERENT approaches fail: STOP all edits \u2192 REVERT to last working state \u2192 DOCUMENT what you tried \u2192 CONSULT Oracle \u2192 if Oracle fails \u2192 ASK USER with clear explanation.
139504
+ ${delegationBlock}
139173
139505
 
139174
- Never leave code broken, delete failing tests, or shotgun debug.`;
139506
+ ${communicationBlock}`;
139175
139507
  }
139176
139508
 
139177
139509
  // src/agents/hephaestus/agent.ts
@@ -139234,7 +139566,7 @@ function createHephaestusAgent2(model, availableAgents, availableToolNames, avai
139234
139566
  }
139235
139567
  createHephaestusAgent2.mode = MODE10;
139236
139568
  // src/agents/builtin-agents/resolve-file-uri.ts
139237
- import { existsSync as existsSync84, readFileSync as readFileSync56 } from "fs";
139569
+ import { existsSync as existsSync84, readFileSync as readFileSync57 } from "fs";
139238
139570
  import { homedir as homedir16 } from "os";
139239
139571
  import { isAbsolute as isAbsolute12, resolve as resolve20 } from "path";
139240
139572
  init_logger();
@@ -139263,7 +139595,7 @@ function resolvePromptAppend(promptAppend, configDir) {
139263
139595
  return `[WARNING: Could not resolve file URI: ${promptAppend}]`;
139264
139596
  }
139265
139597
  try {
139266
- return readFileSync56(filePath, "utf8");
139598
+ return readFileSync57(filePath, "utf8");
139267
139599
  } catch {
139268
139600
  return `[WARNING: Could not read file: ${promptAppend}]`;
139269
139601
  }
@@ -140486,8 +140818,8 @@ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, dir
140486
140818
  return result;
140487
140819
  }
140488
140820
  // src/features/claude-code-agent-loader/loader.ts
140489
- import { existsSync as existsSync85, readdirSync as readdirSync23, readFileSync as readFileSync57 } from "fs";
140490
- import { join as join93, basename as basename14 } from "path";
140821
+ import { existsSync as existsSync85, readdirSync as readdirSync24, readFileSync as readFileSync58 } from "fs";
140822
+ import { join as join94, basename as basename14 } from "path";
140491
140823
  function parseToolsConfig2(toolsStr) {
140492
140824
  if (!toolsStr)
140493
140825
  return;
@@ -140504,15 +140836,15 @@ function loadAgentsFromDir(agentsDir, scope) {
140504
140836
  if (!existsSync85(agentsDir)) {
140505
140837
  return [];
140506
140838
  }
140507
- const entries = readdirSync23(agentsDir, { withFileTypes: true });
140839
+ const entries = readdirSync24(agentsDir, { withFileTypes: true });
140508
140840
  const agents = [];
140509
140841
  for (const entry of entries) {
140510
140842
  if (!isMarkdownFile(entry))
140511
140843
  continue;
140512
- const agentPath = join93(agentsDir, entry.name);
140844
+ const agentPath = join94(agentsDir, entry.name);
140513
140845
  const agentName = basename14(entry.name, ".md");
140514
140846
  try {
140515
- const content = readFileSync57(agentPath, "utf-8");
140847
+ const content = readFileSync58(agentPath, "utf-8");
140516
140848
  const { data, body } = parseFrontmatter(content);
140517
140849
  const name = data.name || agentName;
140518
140850
  const originalDescription = data.description || "";
@@ -140542,7 +140874,7 @@ function loadAgentsFromDir(agentsDir, scope) {
140542
140874
  return agents;
140543
140875
  }
140544
140876
  function loadUserAgents() {
140545
- const userAgentsDir = join93(getClaudeConfigDir(), "agents");
140877
+ const userAgentsDir = join94(getClaudeConfigDir(), "agents");
140546
140878
  const agents = loadAgentsFromDir(userAgentsDir, "user");
140547
140879
  const result = {};
140548
140880
  for (const agent of agents) {
@@ -140551,7 +140883,7 @@ function loadUserAgents() {
140551
140883
  return result;
140552
140884
  }
140553
140885
  function loadProjectAgents(directory) {
140554
- const projectAgentsDir = join93(directory ?? process.cwd(), ".claude", "agents");
140886
+ const projectAgentsDir = join94(directory ?? process.cwd(), ".claude", "agents");
140555
140887
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
140556
140888
  const result = {};
140557
140889
  for (const agent of agents) {
@@ -143041,7 +143373,7 @@ async function applyAgentConfig(params) {
143041
143373
  }
143042
143374
  // src/features/claude-code-command-loader/loader.ts
143043
143375
  import { promises as fs19 } from "fs";
143044
- import { join as join94, basename as basename15 } from "path";
143376
+ import { join as join95, basename as basename15 } from "path";
143045
143377
  init_logger();
143046
143378
  async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix = "") {
143047
143379
  try {
@@ -143072,7 +143404,7 @@ async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix
143072
143404
  if (entry.isDirectory()) {
143073
143405
  if (entry.name.startsWith("."))
143074
143406
  continue;
143075
- const subDirPath = join94(commandsDir, entry.name);
143407
+ const subDirPath = join95(commandsDir, entry.name);
143076
143408
  const subPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
143077
143409
  const subCommands = await loadCommandsFromDir(subDirPath, scope, visited, subPrefix);
143078
143410
  commands3.push(...subCommands);
@@ -143080,7 +143412,7 @@ async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix
143080
143412
  }
143081
143413
  if (!isMarkdownFile(entry))
143082
143414
  continue;
143083
- const commandPath = join94(commandsDir, entry.name);
143415
+ const commandPath = join95(commandsDir, entry.name);
143084
143416
  const baseCommandName = basename15(entry.name, ".md");
143085
143417
  const commandName = prefix ? `${prefix}/${baseCommandName}` : baseCommandName;
143086
143418
  try {
@@ -143139,12 +143471,12 @@ function commandsToRecord(commands3) {
143139
143471
  return result;
143140
143472
  }
143141
143473
  async function loadUserCommands() {
143142
- const userCommandsDir = join94(getClaudeConfigDir(), "commands");
143474
+ const userCommandsDir = join95(getClaudeConfigDir(), "commands");
143143
143475
  const commands3 = await loadCommandsFromDir(userCommandsDir, "user");
143144
143476
  return commandsToRecord(commands3);
143145
143477
  }
143146
143478
  async function loadProjectCommands(directory) {
143147
- const projectCommandsDir = join94(directory ?? process.cwd(), ".claude", "commands");
143479
+ const projectCommandsDir = join95(directory ?? process.cwd(), ".claude", "commands");
143148
143480
  const commands3 = await loadCommandsFromDir(projectCommandsDir, "project");
143149
143481
  return commandsToRecord(commands3);
143150
143482
  }
@@ -143551,7 +143883,9 @@ function createConfigHandler(deps) {
143551
143883
  // src/create-managers.ts
143552
143884
  function createManagers(args) {
143553
143885
  const { ctx, pluginConfig, tmuxConfig, modelCacheState, backgroundNotificationHookEnabled } = args;
143554
- markServerRunningInProcess();
143886
+ if (tmuxConfig.enabled) {
143887
+ markServerRunningInProcess();
143888
+ }
143555
143889
  const tmuxSessionManager = new TmuxSessionManager(ctx, tmuxConfig);
143556
143890
  registerManagerForCleanup({
143557
143891
  shutdown: async () => {
@@ -143751,8 +144085,14 @@ function trimToolsToCap(filteredTools, maxTools) {
143751
144085
  log(`[tool-registry] Trimmed ${removed} tools to satisfy max_tools=${maxTools}. Final plugin tool count=${currentCount}.`);
143752
144086
  }
143753
144087
  function createToolRegistry(args) {
143754
- const { ctx, pluginConfig, managers, skillContext, availableCategories } = args;
143755
- const tmuxIntegrationEnabled = isTmuxIntegrationEnabled(pluginConfig);
144088
+ const {
144089
+ ctx,
144090
+ pluginConfig,
144091
+ managers,
144092
+ skillContext,
144093
+ availableCategories,
144094
+ interactiveBashEnabled = isInteractiveBashEnabled()
144095
+ } = args;
143756
144096
  const backgroundTools = createBackgroundTools(managers.backgroundManager, ctx.client);
143757
144097
  const callOmoAgent = createCallOmoAgent(ctx, managers.backgroundManager, pluginConfig.disabled_agents ?? [], pluginConfig.agents, pluginConfig.categories);
143758
144098
  const isMultimodalLookerEnabled = !(pluginConfig.disabled_agents ?? []).some((agent) => agent.toLowerCase() === "multimodal-looker");
@@ -143829,7 +144169,7 @@ function createToolRegistry(args) {
143829
144169
  task: delegateTask,
143830
144170
  skill_mcp: skillMcpTool,
143831
144171
  skill: skillTool,
143832
- ...tmuxIntegrationEnabled ? { interactive_bash } : {},
144172
+ ...interactiveBashEnabled ? { interactive_bash } : {},
143833
144173
  ...taskToolsRecord,
143834
144174
  ...hashlineToolsRecord
143835
144175
  };
@@ -144122,10 +144462,10 @@ function createChatHeadersHandler(args) {
144122
144462
 
144123
144463
  // src/plugin/ultrawork-db-model-override.ts
144124
144464
  import { Database } from "bun:sqlite";
144125
- import { join as join95 } from "path";
144465
+ import { join as join96 } from "path";
144126
144466
  import { existsSync as existsSync86 } from "fs";
144127
144467
  function getDbPath() {
144128
- return join95(getDataDir(), "opencode", "opencode.db");
144468
+ return join96(getDataDir(), "opencode", "opencode.db");
144129
144469
  }
144130
144470
  var MAX_MICROTASK_RETRIES = 10;
144131
144471
  function tryUpdateMessageModel(db, messageId, targetModel, variant) {
@@ -144676,11 +145016,12 @@ function applyUserConfiguredFallbackChain(sessionID, agentName, currentProviderI
144676
145016
  setSessionFallbackChain(sessionID, fallbackChain);
144677
145017
  }
144678
145018
  }
144679
- function isCompactionAgent5(agent) {
145019
+ function isCompactionAgent6(agent) {
144680
145020
  return agent.toLowerCase() === "compaction";
144681
145021
  }
144682
145022
  function createEventHandler2(args) {
144683
- const { ctx, firstMessageVariantGate, managers, hooks: hooks2 } = args;
145023
+ const { ctx, pluginConfig, firstMessageVariantGate, managers, hooks: hooks2 } = args;
145024
+ const tmuxIntegrationEnabled = isTmuxIntegrationEnabled(pluginConfig);
144684
145025
  const pluginContext = ctx;
144685
145026
  const isRuntimeFallbackEnabled = hooks2.runtimeFallback !== null && hooks2.runtimeFallback !== undefined && (typeof args.pluginConfig.runtime_fallback === "boolean" ? args.pluginConfig.runtime_fallback : args.pluginConfig.runtime_fallback?.enabled ?? false);
144686
145027
  const isModelFallbackEnabled = hooks2.modelFallback !== null && hooks2.modelFallback !== undefined;
@@ -144823,7 +145164,7 @@ function createEventHandler2(args) {
144823
145164
  }
144824
145165
  const { event } = input;
144825
145166
  const props = event.properties;
144826
- if (TMUX_ACTIVITY_EVENT_TYPES.has(event.type)) {
145167
+ if (tmuxIntegrationEnabled && TMUX_ACTIVITY_EVENT_TYPES.has(event.type)) {
144827
145168
  managers.tmuxSessionManager.onEvent?.(event);
144828
145169
  }
144829
145170
  if (event.type === "session.created") {
@@ -144832,7 +145173,9 @@ function createEventHandler2(args) {
144832
145173
  setMainSession(sessionInfo?.id);
144833
145174
  }
144834
145175
  firstMessageVariantGate.markSessionCreated(sessionInfo);
144835
- await managers.tmuxSessionManager.onSessionCreated(event);
145176
+ if (tmuxIntegrationEnabled) {
145177
+ await managers.tmuxSessionManager.onSessionCreated(event);
145178
+ }
144836
145179
  }
144837
145180
  if (event.type === "session.deleted") {
144838
145181
  const sessionInfo = props?.info;
@@ -144860,9 +145203,11 @@ function createEventHandler2(args) {
144860
145203
  deleteSessionTools(sessionInfo.id);
144861
145204
  await managers.skillMcpManager.disconnectSession(sessionInfo.id);
144862
145205
  await lspManager.cleanupTempDirectoryClients();
144863
- await managers.tmuxSessionManager.onSessionDeleted({
144864
- sessionID: sessionInfo.id
144865
- });
145206
+ if (tmuxIntegrationEnabled) {
145207
+ await managers.tmuxSessionManager.onSessionDeleted({
145208
+ sessionID: sessionInfo.id
145209
+ });
145210
+ }
144866
145211
  }
144867
145212
  }
144868
145213
  if (event.type === "message.removed") {
@@ -144876,7 +145221,7 @@ function createEventHandler2(args) {
144876
145221
  const agent = info?.agent;
144877
145222
  const role = info?.role;
144878
145223
  if (sessionID && role === "user") {
144879
- const isCompactionMessage = agent ? isCompactionAgent5(agent) : false;
145224
+ const isCompactionMessage = agent ? isCompactionAgent6(agent) : false;
144880
145225
  if (agent && !isCompactionMessage) {
144881
145226
  updateSessionAgent(sessionID, agent);
144882
145227
  }