// Force strict mode and setup for ESM
"use strict";
import {
  classifyApiError,
  prefixMidTurnUserMessageParts
} from "./chunk-Q64RZ24L.js";
import {
  EXTENSION_CONTEXT_BUDGET,
  buildExtensionMentionContext,
  matchExtensionByRef,
  parseExtensionRef
} from "./chunk-J4TMCUCZ.js";
import {
  HistoryReplayer,
  MessageEmitter,
  PlanEmitter,
  ToolCallEmitter,
  getAvailableCommands,
  handleSlashCommand,
  insertAfterFunctionResponses,
  isSlashCommand,
  normalizePartList
} from "./chunk-H2S47HYW.js";
import {
  appEvents,
  buildDisabledSkillNamesProvider,
  loadCliConfig,
  normalizeDisabledToolList
} from "./chunk-A5AG664O.js";
import {
  collectContextData,
  formatContextUsageText
} from "./chunk-XL5WDTO5.js";
import {
  getCommandSubcommandNames
} from "./chunk-AR4VP4WD.js";
import "./chunk-6OOS3TNP.js";
import {
  getEffectiveSupportedModes
} from "./chunk-RMXPR6HS.js";
import {
  isTerminalGoalStatusKind
} from "./chunk-KSPVDFE4.js";
import "./chunk-MRO43B25.js";
import "./chunk-VXGPYF7U.js";
import "./chunk-4FRUXVSE.js";
import "./chunk-KYMBIKIW.js";
import "./chunk-HTWHAN44.js";
import "./chunk-JZSY4WP3.js";
import "./chunk-ZHGT5WWW.js";
import "./chunk-3GONHQOA.js";
import "./chunk-V655JORE.js";
import "./chunk-ZI4MGBB5.js";
import {
  MAX_REMEMBER_CONTENT_BYTES,
  extractRememberErrorCode,
  formatWorkspaceMemoryForgetSummary,
  isValidServerName
} from "./chunk-6SHO7WFF.js";
import "./chunk-L7QPM2YD.js";
import "./chunk-54YZOP7P.js";
import "./chunk-UVNBI7NN.js";
import "./chunk-PFS2I5LF.js";
import "./chunk-KHDZHZMH.js";
import {
  formatAcpModelId,
  parseAcpBaseModelId,
  parseAcpModelOption,
  sanitizeProviderBaseUrl
} from "./chunk-A76FGLIX.js";
import {
  mapSkillConfigToStatus
} from "./chunk-6IAWPIQ6.js";
import {
  PERMISSION_RULE_TYPES,
  PermissionRulesValidationError,
  buildPermissionSettings,
  normalizePermissionRules,
  readPermissionRuleSet
} from "./chunk-HWS2TPHA.js";
import "./chunk-YQARVN3E.js";
import "./chunk-NIOPRZNN.js";
import "./chunk-AHNXYU4O.js";
import "./chunk-RNBYOUGV.js";
import "./chunk-Q5JX2J3K.js";
import "./chunk-BJBWRCSK.js";
import "./chunk-QFVH4WQN.js";
import "./chunk-6BEOH4KK.js";
import "./chunk-S6RLAIUR.js";
import "./chunk-MX2YXRER.js";
import "./chunk-YHTNWGRR.js";
import {
  createLoadedSettingsAdapter
} from "./chunk-4GWFOP6B.js";
import {
  OUTPUT_LANGUAGE_AUTO,
  getOutputLanguageFilePath,
  getPersistScopeForModelSelection,
  isAutoLanguage,
  loadSettings,
  reloadEnvironment,
  resolveOutputLanguage,
  updateOutputLanguageFile,
  writeOutputLanguageAndRegisterPath
} from "./chunk-AFPB2TZB.js";
import {
  SUPPORTED_LANGUAGES,
  getCurrentLanguage,
  setLanguageAsync
} from "./chunk-RYT2ET4O.js";
import "./chunk-TLRYABYP.js";
import "./chunk-4RQIB5UZ.js";
import {
  NOT_CURRENTLY_GENERATING_CANCEL_MESSAGE
} from "./chunk-T22H4V4R.js";
import {
  CLIENT_MCP_OVER_WS_CONFIG_FLAG,
  MID_TURN_QUEUE_DRAIN_METHOD
} from "./chunk-EWALXUAD.js";
import "./chunk-SEZC2725.js";
import "./chunk-ALX6BUYW.js";
import {
  AgentSideConnection,
  PROTOCOL_VERSION,
  RequestError
} from "./chunk-VRDMOSQS.js";
import {
  ndJsonStream
} from "./chunk-3VJ7MT3N.js";
import {
  ACP_PREFLIGHT_KINDS,
  IDLE_HOOK_EVENTS,
  SERVE_CONTROL_EXT_METHODS,
  SERVE_STATUS_EXT_METHODS,
  STATUS_SCHEMA_VERSION,
  mapDomainErrorToErrorKind
} from "./chunk-7MFZZRJS.js";
import "./chunk-3RW4AZJV.js";
import "./chunk-SL2P3JUA.js";
import {
  isFolderTrustEnabled,
  isWorkspaceTrusted,
  loadTrustedFolders
} from "./chunk-CJSSVQAR.js";
import {
  writeStderrLine
} from "./chunk-Y6Z2O3WR.js";
import "./chunk-QYUE6W3T.js";
import {
  ALL_PROVIDERS,
  APPROVAL_MODE_INFO,
  BTW_MAX_INPUT_LENGTH,
  ExtensionManager,
  InvalidMcpConfigError,
  LoopTickResolver,
  MAX_TRANSCRIPT_MESSAGES,
  MCPOAuthProvider,
  MCPOAuthTokenStorage,
  MCPServerConfig,
  MCP_BUDGET_WARN_FRACTION,
  McpBudgetWouldExceedError,
  McpServerSpawnFailedError,
  McpTransportPool,
  ORPHAN_TOOL_USE_REPAIR_REASON,
  POOLED_TRANSPORTS_DEFAULT,
  SESSION_TITLE_MAX_LENGTH,
  SessionService,
  TURN_INTERRUPTION_HISTORY_TAIL_COUNT,
  WorkspaceMcpBudget,
  abortGoalForStopHookCap,
  acquireSleepInhibitor,
  applyAutoModeDecision,
  applyProviderInstallPlan,
  buildBackgroundEntryLabel,
  buildBtwCacheSafeParams,
  buildBtwPrompt,
  buildContextUsage,
  buildInstallPlan,
  buildSyntheticToolResponseParts,
  clampInlineMediaPart,
  clearGoalTerminalObserver,
  computeUniqueBranchTitle,
  convertToFunctionResponse,
  createDuplicateProviderToolCallResponse,
  dedupeToolCallsById,
  detectAutonomousSentinel,
  detectLoopSentinel,
  detectTurnInterruption,
  evaluateAutoMode,
  evaluatePermissionFlow,
  findExistingProviderModels,
  findProviderById,
  findRepeatedDuplicateProviderToolCall,
  fireNotificationHook,
  firePermissionRequestHook,
  firePostToolUseFailureHook,
  firePostToolUseHook,
  firePreToolUseHook,
  formatDenialStateLog,
  formatStopHookBlockingCapWarning,
  generatePromptSuggestion,
  generateSessionRecap,
  generateToolUseId,
  getArenaSystemReminder,
  getAutoModePermissionDeniedReason,
  getDefaultBaseUrlForProtocol,
  getDefaultModelIds,
  getEffectivePermissionForConfirmation,
  getMCPDiscoveryState,
  getPlanModeSystemReminder,
  getProviderToolCallId,
  getScopedEnvContents,
  getStartupContextLength,
  hasImageParts,
  injectPermissionRulesIfMissing,
  isApproveOutcome,
  isDenialFallbackReason,
  isPlanModeBlocked,
  isSystemReminderContent,
  markDuplicateProviderToolCallResponseSent,
  matchesAnyServerPattern,
  needsConfirmation,
  normalizeParts,
  parsePositiveIntegerEnv,
  persistPermissionOutcome,
  readManyFiles,
  recordAllow,
  recordFallbackApprove,
  redactUrlCredentials,
  resolveBaseUrl,
  restoreWorktreeContext,
  runForkedAgent,
  runManagedAutoMemoryDream,
  runManagedRememberByAgent,
  runVisionBridge,
  sessionIdContext,
  setGoalTerminalObserver,
  shouldClassifyAllShellForAutoMode,
  shouldFallback,
  shouldFirePermissionDeniedForAutoMode,
  shouldForceAutoModeReviewForAllow,
  shouldRunAutoModeForCall,
  shouldRunVisionBridge,
  splitImageParts,
  subagentGenerator,
  unregisterGoalHook,
  updateSetting
} from "./chunk-IQ7FOOIH.js";
import "./chunk-5P5XGNYH.js";
import "./chunk-SKAZWEV5.js";
import {
  external_exports
} from "./chunk-K5PGHDBN.js";
import "./chunk-CLQPBI4H.js";
import "./chunk-QOMAHFYI.js";
import "./chunk-G7OQ3RFJ.js";
import "./chunk-LKWAVAB5.js";
import {
  runSideQuery
} from "./chunk-5ES5YPED.js";
import "./chunk-2MPVVENX.js";
import "./chunk-ZTQ26VBE.js";
import "./chunk-DMSNJDWG.js";
import "./chunk-MLZQVCF3.js";
import "./chunk-LQ7TMOCE.js";
import "./chunk-E5A7LHNN.js";
import {
  APPROVAL_MODES,
  FORK_SUBAGENT_TYPE
} from "./chunk-LM3YCCZJ.js";
import "./chunk-GO6LNQXT.js";
import "./chunk-B236XPGF.js";
import {
  HookEventName,
  createHookOutput
} from "./chunk-NVFJSIAX.js";
import "./chunk-TOAUDIS7.js";
import "./chunk-NPQWQYU6.js";
import {
  AuthType,
  ConversationFinishedEvent,
  DiscoveredMCPTool,
  LoopDetectedEvent,
  PromptSuggestionEvent,
  UserPromptEvent,
  endToolExecutionSpan,
  endToolSpan,
  extractDaemonTraceContext,
  getAllGeminiMdFilenames,
  getMCPServerStatus,
  logConversationFinishedEvent,
  logLoopDetected,
  logPromptSuggestion,
  logToolCall,
  logUserPrompt,
  registerAcpEventLoopLagGauge,
  runInToolSpanContext,
  startEventLoopLagMonitor,
  startToolExecutionSpan,
  startToolSpan,
  uiTelemetryService,
  withInteractionSpan
} from "./chunk-4IUH45NF.js";
import "./chunk-E6US47KI.js";
import "./chunk-UWCTAVOD.js";
import "./chunk-OFEVLU4C.js";
import "./chunk-OMX7CUOE.js";
import {
  getAutoMemoryRoot,
  getUserAutoMemoryRoot
} from "./chunk-MOUXUCXQ.js";
import "./chunk-BZHLWC6T.js";
import "./chunk-RZORUN6X.js";
import {
  ToolConfirmationOutcome,
  ToolNames
} from "./chunk-MJKMIQMK.js";
import {
  clearCachedCredentialFile,
  qwenOAuth2Events
} from "./chunk-TENOUWY2.js";
import "./chunk-GLPV75ES.js";
import "./chunk-22IFUCVR.js";
import "./chunk-T42ACHFI.js";
import "./chunk-SIUQ3YYX.js";
import {
  DEFAULT_TOKEN_LIMIT,
  tokenLimit
} from "./chunk-KYTQOCDE.js";
import "./chunk-YPCPZSZB.js";
import "./chunk-IRJWHZWU.js";
import "./chunk-SYCJMSIJ.js";
import "./chunk-55ZMG67I.js";
import "./chunk-H6BD2ELD.js";
import "./chunk-5IFG2VC4.js";
import "./chunk-64WXLC72.js";
import "./chunk-D5KISTLW.js";
import {
  Storage,
  createDebugLogger,
  getErrorMessage,
  getErrorStatus,
  isSubpath
} from "./chunk-AVQDCKNF.js";
import "./chunk-DTPH7UZK.js";
import {
  runExitCleanup
} from "./chunk-N2OMTT2Q.js";
import "./chunk-ZERZSAZL.js";
import "./chunk-QN5NZ3UQ.js";
import "./chunk-BR4QREVK.js";
import "./chunk-Z2Z3GUXZ.js";
import {
  init_esbuild_shims
} from "./chunk-A4BMJM77.js";
import {
  __name
} from "./chunk-J2S4EL5Y.js";

// packages/cli/src/acp-integration/acpAgent.ts
init_esbuild_shims();
import { randomUUID } from "node:crypto";

// packages/cli/src/acp-integration/authMethods.ts
init_esbuild_shims();
function buildAuthMethods() {
  return [
    {
      id: "openai" /* USE_OPENAI */,
      name: "Use OpenAI API key",
      description: "Requires setting the `OPENAI_API_KEY` environment variable",
      _meta: {
        type: "terminal",
        args: ["--auth-type=openai"]
      }
    }
  ];
}
__name(buildAuthMethods, "buildAuthMethods");
function pickAuthMethodsForAuthRequired(selectedType) {
  const authMethods = buildAuthMethods();
  if (selectedType) {
    const matched = authMethods.filter((method) => method.id === selectedType);
    return matched.length ? matched : authMethods;
  }
  return authMethods;
}
__name(pickAuthMethodsForAuthRequired, "pickAuthMethodsForAuthRequired");

// packages/cli/src/acp-integration/service/filesystem.ts
init_esbuild_shims();
import { realpath } from "node:fs/promises";
import path from "node:path";
var RESOURCE_NOT_FOUND_CODE = -32002;
var PATH_OUTSIDE_WORKSPACE_KIND = "path_outside_workspace";
var SYMLINK_ESCAPE_KIND = "symlink_escape";
var LOCAL_READ_FALLBACK_ERROR_KINDS = /* @__PURE__ */ new Set([
  PATH_OUTSIDE_WORKSPACE_KIND,
  SYMLINK_ESCAPE_KIND
]);
var debugLogger = createDebugLogger("ACP_FILE_SYSTEM");
function isRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
__name(isRecord, "isRecord");
function getErrorCode(error) {
  if (error instanceof RequestError) {
    return error.code;
  }
  if (isRecord(error)) {
    return error["code"];
  }
  return void 0;
}
__name(getErrorCode, "getErrorCode");
function getErrorData(error) {
  const data = isRecord(error) ? error["data"] : void 0;
  return isRecord(data) ? data : void 0;
}
__name(getErrorData, "getErrorData");
function getErrorKind(error) {
  const data = getErrorData(error);
  if (data && typeof data["errorKind"] === "string") {
    return data["errorKind"];
  }
  return void 0;
}
__name(getErrorKind, "getErrorKind");
function normalizeError(error) {
  if (error instanceof Error) return error;
  return new Error(getErrorMessage(error), {
    cause: error
  });
}
__name(normalizeError, "normalizeError");
function createEnoentError(filePath) {
  const err = new Error(`File not found: ${filePath}`);
  err.code = "ENOENT";
  err.errno = -2;
  err.path = filePath;
  return err;
}
__name(createEnoentError, "createEnoentError");
function isLocalReadFallbackErrorKind(errorKind) {
  return typeof errorKind === "string" && LOCAL_READ_FALLBACK_ERROR_KINDS.has(errorKind);
}
__name(isLocalReadFallbackErrorKind, "isLocalReadFallbackErrorKind");
async function resolveRealPath(value) {
  if (!value.trim()) return void 0;
  try {
    return await realpath(path.resolve(value));
  } catch (error) {
    if (getErrorCode(error) !== "ENOENT") {
      debugLogger.warn("realpath failed during ACP local read fallback check", {
        path: value,
        error: getErrorMessage(error)
      });
    }
    return void 0;
  }
}
__name(resolveRealPath, "resolveRealPath");
var AcpFileSystemService = class {
  constructor(connection, sessionId, capabilities, fallback, options = {}) {
    this.connection = connection;
    this.sessionId = sessionId;
    this.capabilities = capabilities;
    this.fallback = fallback;
    this.options = options;
  }
  static {
    __name(this, "AcpFileSystemService");
  }
  async readTextFile(params) {
    if (!this.capabilities.readTextFile) {
      return this.fallback.readTextFile(params);
    }
    let response;
    try {
      response = await this.connection.readTextFile({
        ...params,
        sessionId: this.sessionId
      });
    } catch (error) {
      const errorCode = getErrorCode(error);
      if (errorCode === RESOURCE_NOT_FOUND_CODE) {
        throw createEnoentError(params.path);
      }
      const errorKind = getErrorKind(error);
      const shouldTryLocalReadFallback = isLocalReadFallbackErrorKind(errorKind);
      const fallbackPath = shouldTryLocalReadFallback ? await this.getLocalReadFallbackPath(params.path) : void 0;
      if (shouldTryLocalReadFallback && !fallbackPath) {
        debugLogger.debug("Local read fallback skipped - no safe local path", {
          path: params.path,
          errorKind
        });
      }
      if (shouldTryLocalReadFallback && fallbackPath) {
        debugLogger.debug("Falling back to local read after ACP error", {
          path: params.path,
          resolvedPath: fallbackPath,
          errorKind,
          error: getErrorMessage(error)
        });
        try {
          return await this.fallback.readTextFile({
            ...params,
            path: fallbackPath
          });
        } catch (fallbackError) {
          if (getErrorCode(fallbackError) === "ENOENT") {
            throw fallbackError;
          }
          debugLogger.warn("Local read fallback failed after ACP error", {
            path: params.path,
            resolvedPath: fallbackPath,
            errorKind,
            originalError: getErrorMessage(error),
            fallbackError: getErrorMessage(fallbackError)
          });
          throw new Error(
            `Local fallback read failed for ${params.path}: ${getErrorMessage(fallbackError)} (original ACP error: ${getErrorMessage(error)})`,
            { cause: { fallbackError, acpError: error } }
          );
        }
      }
      throw normalizeError(error);
    }
    return response;
  }
  async writeTextFile(params) {
    if (!this.capabilities.writeTextFile) {
      return this.fallback.writeTextFile(params);
    }
    const finalContent = params._meta?.["bom"] && params.content.charCodeAt(0) !== 65279 ? "\uFEFF" + params.content : params.content;
    try {
      await this.connection.writeTextFile({
        ...params,
        content: finalContent,
        sessionId: this.sessionId
      });
    } catch (error) {
      if (getErrorCode(error) === RESOURCE_NOT_FOUND_CODE) {
        throw createEnoentError(params.path);
      }
      throw normalizeError(error);
    }
    return { _meta: params._meta };
  }
  findFiles(fileName, searchPaths) {
    return this.fallback.findFiles(fileName, searchPaths);
  }
  async getResolvedLocalReadRoots() {
    const roots = await Promise.all(
      (this.options.localReadRoots ?? []).map(resolveRealPath)
    );
    return roots.filter((root) => Boolean(root));
  }
  async getLocalReadFallbackPath(filePath) {
    const normalizedFilePath = path.resolve(filePath);
    const realFilePath = await resolveRealPath(normalizedFilePath);
    if (!realFilePath) return void 0;
    for (const realRoot of await this.getResolvedLocalReadRoots()) {
      if (isSubpath(realRoot, realFilePath)) {
        return realFilePath;
      }
    }
    return void 0;
  }
};

// packages/cli/src/acp-integration/acpAgent.ts
import { Readable, Writable } from "node:stream";
import { pipeline } from "node:stream/promises";
import * as fs from "node:fs/promises";
import * as path3 from "node:path";
import { createGunzip } from "node:zlib";

// packages/cli/src/acp-integration/session/Session.ts
init_esbuild_shims();
import * as os from "node:os";
import * as path2 from "node:path";

// packages/cli/src/acp-integration/session/SubAgentTracker.ts
init_esbuild_shims();

// packages/cli/src/acp-integration/session/permissionUtils.ts
init_esbuild_shims();
var basicPermissionOptions = [
  {
    optionId: "proceed_once" /* ProceedOnce */,
    name: "Allow",
    kind: "allow_once"
  },
  {
    optionId: "cancel" /* Cancel */,
    name: "Reject",
    kind: "reject_once"
  }
];
function supportsHideAlwaysAllow(confirmation) {
  return confirmation.type !== "ask_user_question";
}
__name(supportsHideAlwaysAllow, "supportsHideAlwaysAllow");
function filterAlwaysAllowOptions(confirmation, options, forceHideAlwaysAllow = false) {
  const hideAlwaysAllow = forceHideAlwaysAllow || supportsHideAlwaysAllow(confirmation) && confirmation.hideAlwaysAllow === true;
  return hideAlwaysAllow ? options.filter((option) => option.kind !== "allow_always") : options;
}
__name(filterAlwaysAllowOptions, "filterAlwaysAllowOptions");
function formatExecPermissionScopeLabel(confirmation) {
  const permissionRules = confirmation.permissionRules ?? [];
  const bashRules = permissionRules.map((rule) => {
    const match = /^Bash\((.*)\)$/.exec(rule.trim());
    return match?.[1]?.trim() || void 0;
  }).filter((rule) => Boolean(rule));
  const uniqueRules = [...new Set(bashRules)];
  if (uniqueRules.length === 1) {
    return uniqueRules[0];
  }
  if (uniqueRules.length > 1) {
    return uniqueRules.join(", ");
  }
  return confirmation.rootCommand;
}
__name(formatExecPermissionScopeLabel, "formatExecPermissionScopeLabel");
function buildPermissionRequestContent(confirmation) {
  const content = [];
  if (confirmation.type === "edit") {
    content.push({
      type: "diff",
      path: confirmation.filePath ?? confirmation.fileName,
      oldText: confirmation.originalContent ?? "",
      newText: confirmation.newContent
    });
  }
  if (confirmation.type === "plan") {
    content.push({
      type: "content",
      content: {
        type: "text",
        text: confirmation.plan
      }
    });
  }
  return content;
}
__name(buildPermissionRequestContent, "buildPermissionRequestContent");
function toPermissionOptions(confirmation, forceHideAlwaysAllow = false) {
  switch (confirmation.type) {
    case "edit":
      return filterAlwaysAllowOptions(
        confirmation,
        [
          {
            optionId: "proceed_always" /* ProceedAlways */,
            name: "Allow All Edits",
            kind: "allow_always"
          },
          ...basicPermissionOptions
        ],
        forceHideAlwaysAllow
      );
    case "exec": {
      const label = formatExecPermissionScopeLabel(confirmation);
      return filterAlwaysAllowOptions(
        confirmation,
        [
          {
            optionId: "proceed_always_project" /* ProceedAlwaysProject */,
            name: `Always Allow in project: ${label}`,
            kind: "allow_always"
          },
          {
            optionId: "proceed_always_user" /* ProceedAlwaysUser */,
            name: `Always Allow for user: ${label}`,
            kind: "allow_always"
          },
          ...basicPermissionOptions
        ],
        forceHideAlwaysAllow
      );
    }
    case "mcp":
      return filterAlwaysAllowOptions(
        confirmation,
        [
          {
            optionId: "proceed_always_project" /* ProceedAlwaysProject */,
            name: `Always Allow in project: ${confirmation.toolName}`,
            kind: "allow_always"
          },
          {
            optionId: "proceed_always_user" /* ProceedAlwaysUser */,
            name: `Always Allow for user: ${confirmation.toolName}`,
            kind: "allow_always"
          },
          ...basicPermissionOptions
        ],
        forceHideAlwaysAllow
      );
    case "info":
      return filterAlwaysAllowOptions(
        confirmation,
        [
          {
            optionId: "proceed_always_project" /* ProceedAlwaysProject */,
            name: "Always Allow in project",
            kind: "allow_always"
          },
          {
            optionId: "proceed_always_user" /* ProceedAlwaysUser */,
            name: "Always Allow for user",
            kind: "allow_always"
          },
          ...basicPermissionOptions
        ],
        forceHideAlwaysAllow
      );
    case "plan":
      return [
        {
          optionId: "restore_previous" /* RestorePrevious */,
          name: `Yes, restore previous mode (${confirmation.prePlanMode ?? "default"})`,
          kind: "allow_once"
        },
        {
          optionId: "proceed_always" /* ProceedAlways */,
          name: "Yes, and auto-accept edits",
          kind: "allow_always"
        },
        {
          optionId: "proceed_once" /* ProceedOnce */,
          name: "Yes, and manually approve edits",
          kind: "allow_once"
        },
        {
          optionId: "cancel" /* Cancel */,
          name: "No, keep planning (esc)",
          kind: "reject_once"
        }
      ];
    case "ask_user_question":
      return [
        {
          optionId: "proceed_once" /* ProceedOnce */,
          name: "Submit",
          kind: "allow_once"
        },
        {
          optionId: "cancel" /* Cancel */,
          name: "Cancel",
          kind: "reject_once"
        }
      ];
    default: {
      const unreachable = confirmation;
      throw new Error(`Unexpected: ${unreachable}`);
    }
  }
}
__name(toPermissionOptions, "toPermissionOptions");

// packages/cli/src/acp-integration/session/SubAgentTracker.ts
var debugLogger2 = createDebugLogger("ACP_SUBAGENT_TRACKER");
var SubAgentTracker = class {
  constructor(ctx, client, parentToolCallId, subagentType, onPermissionCancel) {
    this.ctx = ctx;
    this.client = client;
    this.onPermissionCancel = onPermissionCancel;
    this.toolCallEmitter = new ToolCallEmitter(ctx);
    this.messageEmitter = new MessageEmitter(ctx);
    this.subagentMeta = { parentToolCallId, subagentType };
  }
  static {
    __name(this, "SubAgentTracker");
  }
  toolCallEmitter;
  messageEmitter;
  subagentMeta;
  toolStates = /* @__PURE__ */ new Map();
  /**
   * Sets up event listeners for a sub-agent's tool events.
   *
   * @param eventEmitter - The AgentEventEmitter from AgentTool
   * @param abortSignal - Signal to abort tracking if parent is cancelled
   * @returns Array of cleanup functions to remove listeners
   */
  setup(eventEmitter, abortSignal) {
    const onToolCall = this.createToolCallHandler(abortSignal);
    const onToolResult = this.createToolResultHandler(abortSignal);
    const onApproval = this.createApprovalHandler(abortSignal);
    const onUsageMetadata = this.createUsageMetadataHandler(abortSignal);
    const onStreamText = this.createStreamTextHandler(abortSignal);
    eventEmitter.on("tool_call" /* TOOL_CALL */, onToolCall);
    eventEmitter.on("tool_result" /* TOOL_RESULT */, onToolResult);
    eventEmitter.on("tool_waiting_approval" /* TOOL_WAITING_APPROVAL */, onApproval);
    eventEmitter.on("usage_metadata" /* USAGE_METADATA */, onUsageMetadata);
    eventEmitter.on("stream_text" /* STREAM_TEXT */, onStreamText);
    return [
      () => {
        eventEmitter.off("tool_call" /* TOOL_CALL */, onToolCall);
        eventEmitter.off("tool_result" /* TOOL_RESULT */, onToolResult);
        eventEmitter.off("tool_waiting_approval" /* TOOL_WAITING_APPROVAL */, onApproval);
        eventEmitter.off("usage_metadata" /* USAGE_METADATA */, onUsageMetadata);
        eventEmitter.off("stream_text" /* STREAM_TEXT */, onStreamText);
        this.toolStates.clear();
      }
    ];
  }
  /**
   * Creates a handler for tool call start events.
   */
  createToolCallHandler(abortSignal) {
    return (...args) => {
      const event = args[0];
      if (abortSignal.aborted) return;
      const toolRegistry = this.ctx.config.getToolRegistry();
      const tool = toolRegistry.getTool(event.name);
      let invocation;
      if (tool) {
        try {
          invocation = tool.build(event.args);
        } catch (e) {
          debugLogger2.warn(`Failed to build subagent tool ${event.name}:`, e);
        }
      }
      this.toolStates.set(event.callId, {
        tool,
        invocation,
        args: event.args
      });
      void this.toolCallEmitter.emitStart({
        toolName: event.name,
        callId: event.callId,
        args: event.args,
        subagentMeta: this.subagentMeta
      });
    };
  }
  /**
   * Creates a handler for tool result events.
   */
  createToolResultHandler(abortSignal) {
    return (...args) => {
      const event = args[0];
      if (abortSignal.aborted) return;
      const state = this.toolStates.get(event.callId);
      void this.toolCallEmitter.emitResult({
        toolName: event.name,
        callId: event.callId,
        success: event.success,
        message: event.responseParts ?? [],
        resultDisplay: event.resultDisplay,
        args: state?.args,
        subagentMeta: this.subagentMeta
      });
      this.toolStates.delete(event.callId);
    };
  }
  /**
   * Creates a handler for tool approval request events.
   */
  createApprovalHandler(abortSignal) {
    return async (...args) => {
      const event = args[0];
      if (abortSignal.aborted) return;
      const state = this.toolStates.get(event.callId);
      const fullConfirmationDetails = {
        ...event.confirmationDetails,
        onConfirm: /* @__PURE__ */ __name(async () => {
        }, "onConfirm")
      };
      const { title, locations, kind } = this.toolCallEmitter.resolveToolMetadata(event.name, state?.args);
      const params = {
        sessionId: this.ctx.sessionId,
        options: toPermissionOptions(fullConfirmationDetails),
        toolCall: {
          toolCallId: event.callId,
          status: "pending",
          title,
          content: buildPermissionRequestContent(fullConfirmationDetails),
          locations,
          kind,
          rawInput: state?.args,
          // Mirror the tool name so consumers can give specific tools (e.g. the
          // Agent tool) dedicated permission UI without relying on a protocol
          // `kind` ACP can't carry. This is the second producer path (nested
          // sub-agent tool calls); Session.ts adds the same _meta on the primary
          // path.
          _meta: { toolName: event.name }
        }
      };
      try {
        const output = await this.client.requestPermission(params);
        const outcome = output.outcome.outcome === "cancelled" ? "cancel" /* Cancel */ : external_exports.nativeEnum(ToolConfirmationOutcome).parse(output.outcome.optionId);
        await event.respond(outcome, {
          answers: "answers" in output ? output.answers : void 0
        });
        if (outcome === "cancel" /* Cancel */) {
          this.onPermissionCancel?.();
        }
      } catch (error) {
        debugLogger2.error(
          `Permission request failed for subagent tool ${event.name}:`,
          error
        );
        this.onPermissionCancel?.();
        try {
          await event.respond("cancel" /* Cancel */);
        } catch (respondError) {
          debugLogger2.error(
            `Failed to cancel subagent tool ${event.name} after permission request failure:`,
            respondError
          );
        }
      }
    };
  }
  /**
   * Creates a handler for usage metadata events.
   */
  createUsageMetadataHandler(abortSignal) {
    return (...args) => {
      const event = args[0];
      if (abortSignal.aborted) return;
      this.messageEmitter.emitUsageMetadata(
        event.usage,
        "",
        event.durationMs,
        this.subagentMeta
      );
    };
  }
  /**
   * Creates a handler for stream text events.
   * Emits agent message or thought chunks for text content from subagent model responses.
   */
  createStreamTextHandler(abortSignal) {
    return (...args) => {
      const event = args[0];
      if (abortSignal.aborted) return;
      void this.messageEmitter.emitMessage(
        event.text,
        "assistant",
        event.thought ?? false,
        void 0,
        this.subagentMeta
      );
    };
  }
};

// packages/cli/src/acp-integration/session/rewrite/index.ts
init_esbuild_shims();

// packages/cli/src/acp-integration/session/rewrite/MessageRewriteMiddleware.ts
init_esbuild_shims();

// packages/cli/src/acp-integration/session/rewrite/TurnBuffer.ts
init_esbuild_shims();
var TurnBuffer = class {
  static {
    __name(this, "TurnBuffer");
  }
  thoughts = [];
  messages = [];
  _hasToolCalls = false;
  appendThought(text) {
    if (text) this.thoughts.push(text);
  }
  appendMessage(text) {
    if (text) this.messages.push(text);
  }
  markToolCall() {
    this._hasToolCalls = true;
  }
  /**
   * Returns accumulated content and resets the buffer.
   * Returns null if buffer is empty.
   */
  flush() {
    const thoughtText = this.thoughts.join("");
    const messageText = this.messages.join("");
    if (!thoughtText.trim() && !messageText.trim()) {
      this.reset();
      return null;
    }
    const content = {
      thoughts: this.thoughts.filter((t) => t.trim()),
      messages: this.messages.filter((m) => m.trim()),
      hasToolCalls: this._hasToolCalls
    };
    this.reset();
    return content;
  }
  reset() {
    this.thoughts = [];
    this.messages = [];
    this._hasToolCalls = false;
  }
  get isEmpty() {
    return this.thoughts.length === 0 && this.messages.length === 0;
  }
};

// packages/cli/src/acp-integration/session/rewrite/LlmRewriter.ts
init_esbuild_shims();
import { readFileSync, existsSync } from "node:fs";
import { resolve } from "node:path";
var debugLogger3 = createDebugLogger("MESSAGE_REWRITER");
var DEFAULT_REWRITE_PROMPT = `You are an assistant that rewrites raw coding-agent output into concise, user-friendly progress updates.

The agent is a software engineering assistant that reads files, writes code, runs commands, and uses tools. Its raw output mixes internal reasoning with user-facing information. Your job: extract what the user cares about, drop what they don't.

## Rules

1. **Strictly based on original**: only surface information already in the input. Never invent details, plans, or conclusions the agent didn't state.
2. **Keep**: goals, decisions, key findings, results, errors that affect the user, status updates.
3. **Drop**: file paths, tool/skill names, internal reasoning about which tool to call, code snippets, stack traces, "let me\u2026" / "now I'll\u2026" filler phrases.
4. **Progress turns**: if the agent is just starting a step (reading files, running a command, exploring code), output one short sentence describing what's happening \u2014 so the user isn't staring at silence.
5. **Rich content**: if the input already contains well-structured user-facing content (tables, lists, formatted results), do light cleanup only (remove stray paths/tool names) and preserve the structure.
6. **Pure internal ops**: if the input is entirely internal (fixing a typo in its own code, retrying a failed tool call, creating temp directories) \u2192 return empty string.
7. **Preserve data exactly**: never alter numbers, percentages, file sizes, error codes, or quoted output.

## Context continuity

If "Previous rewrite output" is provided, the user has already seen it. Don't repeat \u2014 build on it. If this turn adds nothing new, return empty string.

Output only the rewritten text, or empty string if the input has no user-facing value.`;
var LlmRewriter = class {
  constructor(config, rewriteConfig) {
    this.config = config;
    this.rewriteModel = rewriteConfig.model || void 0;
    this.contextTurns = rewriteConfig.contextTurns === "all" ? Infinity : rewriteConfig.contextTurns ?? 1;
    if (rewriteConfig.promptFile) {
      const filePath = resolve(rewriteConfig.promptFile);
      if (existsSync(filePath)) {
        this.prompt = readFileSync(filePath, "utf-8").trim();
        debugLogger3.info(
          `Loaded rewrite prompt from file: ${filePath} (${this.prompt.length} chars)`
        );
      } else {
        debugLogger3.warn(
          `Rewrite prompt file not found: ${filePath}, using default`
        );
        this.prompt = DEFAULT_REWRITE_PROMPT;
      }
    } else {
      this.prompt = rewriteConfig.prompt || DEFAULT_REWRITE_PROMPT;
    }
  }
  static {
    __name(this, "LlmRewriter");
  }
  prompt;
  /** Previous successful rewrite outputs, used as context for coherence */
  outputHistory = [];
  /** How many previous outputs to include: 0=none, N=last N, Infinity=all */
  contextTurns;
  rewriteModel;
  /**
   * Rewrite a turn's content using LLM.
   * Returns null if the turn has no valuable content for users.
   */
  async rewrite(turnContent, signal) {
    const inputParts = [];
    if (turnContent.thoughts.length > 0) {
      inputParts.push("[\u5185\u90E8\u63A8\u7406]\n" + turnContent.thoughts.join("\n"));
    }
    if (turnContent.messages.length > 0) {
      inputParts.push("[\u56DE\u590D\u6587\u672C]\n" + turnContent.messages.join("\n"));
    }
    if (this.contextTurns > 0 && this.outputHistory.length > 0) {
      const contextSlice = this.contextTurns === Infinity ? this.outputHistory : this.outputHistory.slice(-this.contextTurns);
      inputParts.unshift("[\u4E0A\u4E00\u8F6E\u6539\u5199\u7ED3\u679C]\n" + contextSlice.join("\n---\n"));
    }
    const inputText = inputParts.join("\n\n");
    if (!inputText.trim()) return null;
    if (inputText.length < 10) return null;
    debugLogger3.info(
      `[REWRITE INPUT] system_prompt_len=${this.prompt.length} input_len=${inputText.length} context_turns=${this.outputHistory.length}
--- INPUT TEXT ---
${inputText}
---`
    );
    try {
      const model = this.rewriteModel || this.config.getModel();
      const result = await runSideQuery(this.config, {
        purpose: "acp-rewrite",
        model,
        // Best-effort: failure path returns null gracefully, so don't burn
        // 7 retries on transient outages the user will never see.
        maxAttempts: 1,
        systemInstruction: this.prompt,
        config: {
          temperature: 0.3,
          maxOutputTokens: 1024
        },
        abortSignal: signal ?? new AbortController().signal,
        contents: [
          {
            role: "user",
            parts: [{ text: inputText }]
          }
        ]
      });
      const rewritten = result.text;
      if (!rewritten || rewritten.length < 5) {
        debugLogger3.info(`[REWRITE OUTPUT] empty or too short, skipping`);
        return null;
      }
      debugLogger3.info(
        `[REWRITE OUTPUT] len=${rewritten.length}
--- OUTPUT ---
${rewritten}
---`
      );
      this.outputHistory.push(rewritten);
      return rewritten;
    } catch (error) {
      debugLogger3.warn(
        `LLM rewrite failed, skipping: ${error instanceof Error ? error.message : String(error)}`
      );
      return null;
    }
  }
};

// packages/cli/src/acp-integration/session/rewrite/MessageRewriteMiddleware.ts
var debugLogger4 = createDebugLogger("MESSAGE_REWRITE");
var DEFAULT_REWRITE_TIMEOUT_MS = 3e4;
var REWRITE_META_EXCLUDED_KEYS = /* @__PURE__ */ new Set([]);
var MessageRewriteMiddleware = class {
  constructor(config, rewriteConfig, sendUpdate) {
    this.sendUpdate = sendUpdate;
    this.turnBuffer = new TurnBuffer();
    this.rewriter = new LlmRewriter(config, rewriteConfig);
    this.target = rewriteConfig.target;
    this.timeoutMs = rewriteConfig.timeoutMs ?? DEFAULT_REWRITE_TIMEOUT_MS;
  }
  static {
    __name(this, "MessageRewriteMiddleware");
  }
  turnBuffer;
  rewriter;
  target;
  timeoutMs;
  turnIndex = 0;
  turnMeta;
  /**
   * Intercept an ACP update. Original messages pass through,
   * thought/message chunks are also accumulated for turn-end rewriting.
   */
  async interceptUpdate(update, signal) {
    const updateRecord = update;
    const updateType = updateRecord["sessionUpdate"];
    if (updateType === "tool_call") {
      await this.flushTurn(signal);
      this.turnBuffer.markToolCall();
      return this.sendUpdate(update);
    }
    if (updateType !== "agent_thought_chunk" && updateType !== "agent_message_chunk") {
      return this.sendUpdate(update);
    }
    const content = updateRecord["content"];
    const text = content?.["text"] ?? "";
    await this.sendUpdate(update);
    let didAccumulate = false;
    if (updateType === "agent_thought_chunk") {
      if (this.target === "thought" || this.target === "all") {
        this.turnBuffer.appendThought(text);
        didAccumulate = true;
      }
    } else if (updateType === "agent_message_chunk") {
      if (this.target === "message" || this.target === "all") {
        this.turnBuffer.appendMessage(text);
        didAccumulate = true;
      }
    }
    if (didAccumulate) {
      this.captureTurnMeta(updateRecord);
    }
  }
  /** Pending rewrite promises — all must settle before session exits */
  pendingRewrites = [];
  /**
   * Flush the turn buffer: rewrite accumulated content and emit.
   *
   * Non-blocking: rewrite runs in background, parallel to tool execution.
   *
   * Called when:
   * - A tool_call is about to be emitted (turn boundary)
   * - Usage metadata is emitted (end of model response)
   * - Session prompt ends
   */
  async flushTurn(signal) {
    const content = this.turnBuffer.flush();
    const turnMeta = this.turnMeta;
    this.turnMeta = void 0;
    if (!content) return;
    this.turnIndex++;
    const turnIdx = this.turnIndex;
    const timeoutSignal = AbortSignal.timeout(this.timeoutMs);
    const rewriteSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
    this.pendingRewrites.push(
      (async () => {
        try {
          const rewritten = await this.rewriter.rewrite(content, rewriteSignal);
          if (!rewritten) {
            debugLogger4.info(`Turn ${turnIdx}: no rewrite output`);
            return;
          }
          debugLogger4.info(
            `Turn ${turnIdx}: rewritten ${rewritten.length} chars`
          );
          await this.sendUpdate({
            sessionUpdate: "agent_message_chunk",
            content: { type: "text", text: rewritten },
            _meta: {
              ...turnMeta,
              rewritten: true,
              turnIndex: turnIdx
            }
          });
        } catch (error) {
          debugLogger4.warn(
            `Turn ${turnIdx}: rewrite failed: ${error instanceof Error ? error.message : String(error)}`
          );
        }
      })()
    );
  }
  captureTurnMeta(update) {
    const meta = update["_meta"];
    if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
      return;
    }
    const safeMeta = Object.fromEntries(
      Object.entries(meta).filter(
        ([key]) => !REWRITE_META_EXCLUDED_KEYS.has(key)
      )
    );
    if (Object.keys(safeMeta).length === 0) return;
    this.turnMeta = {
      ...this.turnMeta,
      ...safeMeta
    };
  }
  /**
   * Wait for all pending rewrites to complete.
   * Call this before session ends to ensure all rewrites are flushed.
   */
  async waitForPendingRewrites() {
    if (this.pendingRewrites.length > 0) {
      await Promise.allSettled(this.pendingRewrites);
      this.pendingRewrites = [];
    }
  }
};

// packages/cli/src/acp-integration/session/rewrite/config.ts
init_esbuild_shims();
function loadRewriteConfig(settings) {
  const userOriginal = settings.user?.originalSettings;
  const workspaceOriginal = settings.isTrusted ? settings.workspace?.originalSettings : void 0;
  return workspaceOriginal?.["messageRewrite"] ?? userOriginal?.["messageRewrite"];
}
__name(loadRewriteConfig, "loadRewriteConfig");

// packages/cli/src/acp-integration/session/Session.ts
var debugLogger5 = createDebugLogger("SESSION");
var USER_CANCEL_ABORT_REASON = "qwen:user-cancel";
var DAEMON_RETRY_META_KEY = "qwen.daemon.retry";
var DAEMON_CONTINUE_META_KEY = "qwen.daemon.continueLastTurn";
function maskApiKeyForDisplay(apiKey) {
  const trimmed = apiKey?.trim() ?? "";
  if (trimmed.length === 0) return "(not set)";
  if (trimmed.length <= 6) return "***";
  return `${trimmed.slice(0, 3)}...${trimmed.slice(-4)}`;
}
__name(maskApiKeyForDisplay, "maskApiKeyForDisplay");
var DAEMON_TURN_TOOL_CALL_CAP = 100;
var DAEMON_INVALID_TOOL_PARAMS_THRESHOLD = 3;
var PERMISSION_CANCEL_SKIP_MESSAGE = "Skipped because a permission request was cancelled before the user answered; user input is required before continuing.";
var LOOP_DETECTED_SKIP_MESSAGE = "Skipped because loop detection stopped the current turn before this tool call could run.";
var LOOP_DETECTED_CONTEXT_MESSAGE = "System: this turn was terminated because the model exceeded tool-call safety limits. Try a different approach on the next turn.";
function createDaemonToolLoopState() {
  return {
    totalToolCalls: 0,
    invalidToolParamErrors: /* @__PURE__ */ new Map(),
    loopDetected: false
  };
}
__name(createDaemonToolLoopState, "createDaemonToolLoopState");
function recordDaemonLoopDetected(config, promptId, loopType, message, loopState) {
  if (!loopState.loopDetected) {
    loopState.loopDetected = true;
    debugLogger5.warn(message);
    logLoopDetected(config, new LoopDetectedEvent(loopType, promptId));
  }
  return true;
}
__name(recordDaemonLoopDetected, "recordDaemonLoopDetected");
function recordDaemonToolCalls(config, promptId, loopState, count) {
  if (!loopState || loopState.loopDetected)
    return loopState?.loopDetected ?? false;
  loopState.totalToolCalls += count;
  if (loopState.totalToolCalls <= DAEMON_TURN_TOOL_CALL_CAP) return false;
  return recordDaemonLoopDetected(
    config,
    promptId,
    "turn_tool_call_cap" /* TURN_TOOL_CALL_CAP */,
    `Stopping ACP turn after ${loopState.totalToolCalls} tool calls in one turn.`,
    loopState
  );
}
__name(recordDaemonToolCalls, "recordDaemonToolCalls");
function recordDaemonInvalidToolParams(config, promptId, loopState, toolName, error) {
  if (!loopState || loopState.loopDetected)
    return loopState?.loopDetected ?? false;
  const key = toolName;
  const count = (loopState.invalidToolParamErrors.get(key) ?? 0) + 1;
  loopState.invalidToolParamErrors.set(key, count);
  if (count < DAEMON_INVALID_TOOL_PARAMS_THRESHOLD) return false;
  return recordDaemonLoopDetected(
    config,
    promptId,
    "invalid_tool_params_stagnation" /* INVALID_TOOL_PARAMS_STAGNATION */,
    `Stopping ACP turn after repeated tool parameter errors from ${toolName}: ${error.message}`,
    loopState
  );
}
__name(recordDaemonInvalidToolParams, "recordDaemonInvalidToolParams");
var MID_TURN_QUEUE_DRAIN_TIMEOUT_MS = 2e3;
var MID_TURN_QUEUE_RECOVERY_TIMEOUT_MS = 3e4;
var MID_TURN_QUEUE_RESOLVE_TIMEOUT_MS = 1e4;
var MAX_MID_TURN_DRAIN_ITEMS = 10;
var MID_TURN_ATTACHMENT_PROCESSING_FAILURE_TEXT = "[Attachment could not be processed]";
var MAX_MID_TURN_RESOURCE_TEXT_LENGTH = 1e5;
var MID_TURN_QUEUE_DRAIN_MAX_TIMEOUT_STRIKES = 3;
var TRANSIENT_FS_CODES = [
  "EACCES",
  "EIO",
  "EBUSY",
  "EPERM",
  "EISDIR",
  "ENOTDIR"
];
function isRecord2(value) {
  return value !== null && typeof value === "object";
}
__name(isRecord2, "isRecord");
function isContentBlock(value) {
  if (!isRecord2(value) || typeof value["type"] !== "string") return false;
  switch (value["type"]) {
    case "text":
      return typeof value["text"] === "string";
    case "image":
      return typeof value["mimeType"] === "string" && value["mimeType"].startsWith("image/") && typeof value["data"] === "string";
    case "audio":
      return typeof value["mimeType"] === "string" && value["mimeType"].startsWith("audio/") && typeof value["data"] === "string";
    case "resource_link":
      return false;
    case "resource":
      return isEmbeddedResourceResource(value["resource"]);
    default:
      debugLogger5.warn(`Unknown ContentBlock type: ${value["type"]}`);
      return false;
  }
}
__name(isContentBlock, "isContentBlock");
async function withTimeoutSignal(parentSignal, timeoutMs, fn) {
  const signal = AbortSignal.any([
    parentSignal,
    AbortSignal.timeout(timeoutMs)
  ]);
  const toAbortError = /* @__PURE__ */ __name(() => signal.reason instanceof Error ? signal.reason : new Error("Mid-turn message resolution aborted"), "toAbortError");
  if (signal.aborted) throw toAbortError();
  let rejectOnAbort;
  const abortPromise = new Promise((_, reject) => {
    rejectOnAbort = /* @__PURE__ */ __name(() => reject(toAbortError()), "rejectOnAbort");
    signal.addEventListener("abort", rejectOnAbort, { once: true });
    if (signal.aborted) rejectOnAbort();
  });
  try {
    return await Promise.race([fn(signal), abortPromise]);
  } finally {
    if (rejectOnAbort) signal.removeEventListener("abort", rejectOnAbort);
  }
}
__name(withTimeoutSignal, "withTimeoutSignal");
function isEmbeddedResourceResource(value) {
  if (!isRecord2(value) || typeof value["uri"] !== "string") return false;
  if (typeof value["text"] === "string") {
    return value["text"].length <= MAX_MID_TURN_RESOURCE_TEXT_LENGTH;
  }
  return typeof value["blob"] === "string";
}
__name(isEmbeddedResourceResource, "isEmbeddedResourceResource");
function hasInlineMediaContentBlock(content) {
  return content.some((part) => part.type === "image" || part.type === "audio");
}
__name(hasInlineMediaContentBlock, "hasInlineMediaContentBlock");
function capMidTurnDrainItems(items, fieldName) {
  if (items.length <= MAX_MID_TURN_DRAIN_ITEMS) return items;
  debugLogger5.warn(
    `Mid-turn drain response had ${items.length} ${fieldName}; processing first ${MAX_MID_TURN_DRAIN_ITEMS}`
  );
  return items.slice(0, MAX_MID_TURN_DRAIN_ITEMS);
}
__name(capMidTurnDrainItems, "capMidTurnDrainItems");
function getMidTurnItemDisplayTextForLog(displayText) {
  if (typeof displayText !== "string" || displayText.trim().length === 0) {
    return "(no display text)";
  }
  return JSON.stringify(displayText.trim().slice(0, 120));
}
__name(getMidTurnItemDisplayTextForLog, "getMidTurnItemDisplayTextForLog");
function getValidMidTurnContentBlocks(content, displayText) {
  if (!Array.isArray(content)) {
    debugLogger5.warn(
      `Dropped invalid mid-turn item: ${getMidTurnItemDisplayTextForLog(
        displayText
      )}`
    );
    return [];
  }
  const validBlocks = content.filter(isContentBlock);
  const invalidBlockCount = content.length - validBlocks.length;
  if (invalidBlockCount > 0) {
    debugLogger5.warn(
      `Dropped ${invalidBlockCount} invalid mid-turn content block(s): ${getMidTurnItemDisplayTextForLog(
        displayText
      )}`
    );
  }
  return validBlocks;
}
__name(getValidMidTurnContentBlocks, "getValidMidTurnContentBlocks");
function getStructuredMidTurnDisplayText(content, displayText) {
  if (typeof displayText === "string" && displayText.trim().length > 0) {
    return displayText.trim();
  }
  const text = content.filter(
    (part) => part.type === "text"
  ).map((part) => part.text).join("\n").trim();
  return text || "[User message with attachments]";
}
__name(getStructuredMidTurnDisplayText, "getStructuredMidTurnDisplayText");
function parseMidTurnDrainResponse(response) {
  if (!isRecord2(response)) return [];
  if (Array.isArray(response["items"])) {
    return capMidTurnDrainItems(response["items"], "item(s)").flatMap(
      (item) => {
        if (!isRecord2(item)) {
          return [];
        }
        const content = getValidMidTurnContentBlocks(
          item["content"],
          item["displayText"]
        );
        if (content.length === 0) return [];
        return [
          {
            kind: "structured",
            content,
            displayText: getStructuredMidTurnDisplayText(
              content,
              item["displayText"]
            )
          }
        ];
      }
    );
  }
  if (!Array.isArray(response["messages"])) {
    debugLogger5.warn(
      `Mid-turn drain response had no recognized 'items' or 'messages' field; keys: ${Object.keys(
        response
      ).join(", ")}`
    );
    return [];
  }
  return capMidTurnDrainItems(response["messages"], "message(s)").filter(
    (message) => typeof message === "string" && message.trim().length > 0
  ).map((message) => ({ kind: "text", message }));
}
__name(parseMidTurnDrainResponse, "parseMidTurnDrainResponse");
var MidTurnDrainTimeoutError = class extends Error {
  static {
    __name(this, "MidTurnDrainTimeoutError");
  }
  constructor() {
    super(
      `mid-turn queue drain got no response within ${MID_TURN_QUEUE_DRAIN_TIMEOUT_MS}ms`
    );
  }
};
var MAX_NOTIFICATION_QUEUE = 20;
function resolveHomeLoopResolverRoots({
  homeQwenDir = Storage.getGlobalQwenDir(),
  homeDir = os.homedir(),
  qwenHome = process.env["QWEN_HOME"]
} = {}) {
  return {
    homeConfineRoot: (qwenHome ? homeQwenDir : homeDir) || path2.dirname(homeQwenDir),
    homeQwenDir
  };
}
__name(resolveHomeLoopResolverRoots, "resolveHomeLoopResolverRoots");
function computeInitialTurnFromHistory(records, sessionId) {
  let maxPromptTurn = 0;
  let userMessageCount = 0;
  const promptIdPrefix = `${sessionId}########`;
  for (const record of records) {
    if (record.sessionId === sessionId && isUserPromptRecord(record)) {
      userMessageCount += 1;
    }
    for (const promptId of getRecordPromptIds(record)) {
      if (!promptId.startsWith(promptIdPrefix)) {
        continue;
      }
      const suffix = promptId.slice(promptIdPrefix.length);
      if (!/^\d+$/.test(suffix)) {
        continue;
      }
      maxPromptTurn = Math.max(maxPromptTurn, Number(suffix));
    }
  }
  return maxPromptTurn > 0 ? maxPromptTurn : userMessageCount;
}
__name(computeInitialTurnFromHistory, "computeInitialTurnFromHistory");
async function fireSessionPermissionDeniedForAutoMode(config, decision, outcome, toolName, toolParams, callId, signal) {
  if (!config.getDisableAllHooks?.() && shouldFirePermissionDeniedForAutoMode(decision, outcome)) {
    try {
      await config.getHookSystem?.()?.firePermissionDeniedEvent(
        toolName,
        toolParams,
        callId,
        getAutoModePermissionDeniedReason(decision),
        signal,
        callId
      );
    } catch (hookError) {
      debugLogger5.warn(
        `PermissionDenied hook failed for tool ${callId}: ${hookError instanceof Error ? hookError.message : String(hookError)}`
      );
    }
  }
}
__name(fireSessionPermissionDeniedForAutoMode, "fireSessionPermissionDeniedForAutoMode");
function getRecordPromptIds(record) {
  const promptIds = [];
  const recordPromptId = record.promptId;
  if (typeof recordPromptId === "string") {
    promptIds.push(recordPromptId);
  }
  const telemetryPromptId = readTelemetryPromptId(record.systemPayload);
  if (telemetryPromptId) {
    promptIds.push(telemetryPromptId);
  }
  return promptIds;
}
__name(getRecordPromptIds, "getRecordPromptIds");
function readTelemetryPromptId(payload) {
  if (!payload || typeof payload !== "object" || !("uiEvent" in payload)) {
    return void 0;
  }
  const uiEvent = payload.uiEvent;
  if (!uiEvent || typeof uiEvent !== "object" || !("prompt_id" in uiEvent)) {
    return void 0;
  }
  const promptId = uiEvent.prompt_id;
  return typeof promptId === "string" ? promptId : void 0;
}
__name(readTelemetryPromptId, "readTelemetryPromptId");
function isUserPromptRecord(record) {
  if (record.type !== "user") {
    return false;
  }
  return record.message?.parts?.some(
    (part) => typeof part.text === "string" && part.text.trim().length > 0
  ) ?? false;
}
__name(isUserPromptRecord, "isUserPromptRecord");
var AT_TOKEN_RE = /@([^\s,;!?()[\]{}]+)/g;
function collectExtensionMentionRefs(text, mentions) {
  for (const match of text.matchAll(AT_TOKEN_RE)) {
    const pathName = match[1];
    if (!pathName) continue;
    const ref = parseExtensionRef(pathName);
    if (ref) {
      mentions.set(ref.name.toLowerCase(), ref.name);
    }
  }
}
__name(collectExtensionMentionRefs, "collectExtensionMentionRefs");
async function buildAvailableCommandsSnapshot(config, abortSignal = AbortSignal.timeout(1e4), settings) {
  const slashCommands = await getAvailableCommands(
    config,
    abortSignal,
    "acp",
    settings
  );
  const disabledSkillNames = config.getDisabledSkillNames();
  const visibleSlashCommands = slashCommands.filter((cmd) => {
    if (cmd.kind !== "skill" /* SKILL */ || !cmd.skillDetail) return true;
    return !disabledSkillNames.has(cmd.skillDetail.name.toLowerCase());
  });
  const availableCommands = visibleSlashCommands.map(
    (cmd) => {
      const acceptsInput = cmd.acceptsInput ?? (cmd.kind !== "built-in" /* BUILT_IN */ || cmd.completion != null || cmd.argumentHint != null || cmd.subCommands != null && cmd.subCommands.length > 0);
      return {
        name: cmd.name,
        description: cmd.description,
        input: acceptsInput ? { hint: cmd.argumentHint ?? "" } : null,
        _meta: {
          argumentHint: cmd.argumentHint,
          source: cmd.source,
          sourceLabel: cmd.sourceLabel,
          supportedModes: getEffectiveSupportedModes(cmd),
          subcommands: getCommandSubcommandNames(cmd),
          modelInvocable: cmd.modelInvocable === true,
          // Carry aliases so a channel consumer (which only sees the wire snapshot,
          // not the command registry) can recognize an aliased command and avoid
          // tagging it. _meta is ACP's extension point; omitted when there are none
          // so command entries without aliases stay byte-identical on the wire.
          ...cmd.altNames && cmd.altNames.length > 0 ? { altNames: cmd.altNames } : {}
        }
      };
    }
  );
  let availableSkills;
  const skillDetailsByName = /* @__PURE__ */ new Map();
  try {
    const skillManager = config.getSkillManager();
    if (skillManager) {
      const skills = (await skillManager.listSkills()).filter(
        (skill) => !disabledSkillNames.has(skill.name.toLowerCase())
      );
      availableSkills = skills.map((skill) => skill.name);
      for (const skill of skills) {
        skillDetailsByName.set(skill.name, {
          name: skill.name,
          description: skill.description,
          body: skill.body,
          filePath: skill.filePath,
          level: skill.level,
          modelInvocable: skill.disableModelInvocation !== true
        });
      }
    }
  } catch (error) {
    debugLogger5.error("Error loading available skills:", error);
  }
  for (const command of visibleSlashCommands) {
    if (command.kind !== "skill" /* SKILL */ || !command.skillDetail) {
      continue;
    }
    const existing = skillDetailsByName.get(command.skillDetail.name);
    skillDetailsByName.set(command.skillDetail.name, {
      ...existing,
      ...command.skillDetail,
      modelInvocable: command.modelInvocable === true
    });
  }
  const availableSkillDetails = skillDetailsByName.size > 0 ? Array.from(skillDetailsByName.values()) : void 0;
  availableSkills = availableSkillDetails?.map((skill) => skill.name);
  return {
    availableCommands,
    ...availableSkills !== void 0 ? { availableSkills } : {},
    ...availableSkillDetails !== void 0 ? { availableSkillDetails } : {}
  };
}
__name(buildAvailableCommandsSnapshot, "buildAvailableCommandsSnapshot");
var Session = class {
  constructor(id, config, client, settings) {
    this.config = config;
    this.client = client;
    this.settings = settings;
    this.sessionId = id;
    this.runtimeBaseDir = Storage.getRuntimeBaseDir();
    this.toolCallEmitter = new ToolCallEmitter(this);
    this.planEmitter = new PlanEmitter(this);
    this.historyReplayer = new HistoryReplayer(this);
    this.messageEmitter = new MessageEmitter(this);
    this.#installGoalTerminalObserver();
    this.#registerBackgroundNotificationCallbacks();
  }
  static {
    __name(this, "Session");
  }
  pendingPrompt = null;
  /**
   * Tracks the completion of the current prompt so that the next prompt
   * can await it.  This prevents a new prompt from reading chat history
   * before the previous prompt's tool results have been added —
   * a race condition that causes malformed history on Windows where
   * process termination is slow.
   */
  pendingPromptCompletion = null;
  /**
   * Per-turn AbortController for the fire-and-forget follow-up suggestion
   * generation. Aborted on the top of the next `prompt()` and on
   * `cancelPendingPrompt()` so a stale suggestion never lands after the
   * user has moved on. Null when no suggestion generation is in flight.
   */
  followupAbort = null;
  turn = 0;
  createdAt = Date.now();
  /**
   * Running cumulative usage for this session, snapshotted onto each todo/plan
   * update by PlanEmitter so the web-shell can show per-task token/API spend.
   */
  cumulativeUsage = {
    promptTokens: 0,
    cachedTokens: 0,
    candidateTokens: 0,
    apiTimeMs: 0
  };
  runtimeBaseDir;
  // Cron scheduling state
  cronQueue = [];
  cronProcessing = false;
  cronAbortController = null;
  // Resolves the `<<loop.md>>` / `<<loop.md-dynamic>>` sentinels at fire time.
  // Lazily created on the first loop tick; its content cache is reset on
  // compaction (see #sendMessageStreamWithAutoCompression) and it is rebuilt if
  // the working dir changes (e.g. /cd) so it always reads the current project's
  // loop.md.
  loopTickResolver = null;
  loopTickResolverRoot = null;
  cronCompletion = null;
  cronDisabledByTokenLimit = false;
  lastPromptTokenCount = 0;
  lastPromptTokenCountChat = null;
  midTurnDrainUnavailable = false;
  midTurnDrainTimeoutStrikes = 0;
  // ACP can continue one logical conversation through prompt, cron, and
  // background loops, so keep this with the session instead of a single
  // runToolCalls invocation.
  duplicateProviderToolCallResponseIds = /* @__PURE__ */ new Set();
  // Messages from a drain that the daemon answered but we timed out waiting for
  // (the daemon already spliced + SSE-published them). Re-injected on the next
  // batch so a transient stall can't silently lose them. See
  // `#drainMidTurnUserMessages`.
  midTurnRecoveredMessages = [];
  // Background notification drain state. ACP does not have the TUI's idle
  // hook, so the session serializes registry callbacks through this queue.
  notificationQueue = [];
  notificationProcessing = false;
  notificationAbortController = null;
  notificationCompletion = null;
  // Set true in dispose(). Guards #drainCronQueue and #drainNotificationQueue
  // against the race where #drainNotificationQueue's finally block kicks off
  // #drainCronQueue after the session has already been disposed (e.g. /clear
  // or session reload), which would otherwise execute orphaned cron prompts
  // on a session whose registries are already unregistered.
  disposed = false;
  // Modular components
  historyReplayer;
  toolCallEmitter;
  planEmitter;
  messageEmitter;
  // Message rewrite middleware (optional, installed after history replay)
  messageRewriter;
  /**
   * Phase C worktree restore notice. Set by acpAgent.loadSession when a
   * resumed session has a live worktree sidecar; prepended to the next
   * #executePrompt call as a <system-reminder>, then cleared.
   *
   * One-shot by design — after the first prompt the worktree path is
   * already in the conversation context (the reminder we just sent + any
   * subsequent tool calls), so re-injecting on every turn would clutter
   * the history without adding signal. TUI uses historyManager.addItem(INFO)
   * for the equivalent UX hint and headless prepends to the single shot
   * prompt; all three modes share the `restoreWorktreeContext` helper
   * that produces this string.
   */
  pendingWorktreeNotice = null;
  // Implement SessionContext interface
  sessionId;
  getId() {
    return this.sessionId;
  }
  /**
   * Starts the cron scheduler at session creation. Durable tasks live on
   * disk; waiting for the end of the first prompt (the in-turn start at
   * the bottom of prompt()) would leave them invisible to cron_list /
   * cron_delete for the whole first turn and unfired while the session
   * idles before any prompt — the TUI equivalent enables durable cron on
   * mount.
   */
  startCronScheduler() {
    this.#startCronSchedulerIfNeeded().catch((error) => {
      debugLogger5.warn(
        `Cron scheduler startup failed [session ${this.sessionId}]: ${error}`
      );
    });
  }
  getConfig() {
    return this.config;
  }
  isIdle() {
    return !this.pendingPrompt && !this.pendingPromptCompletion && !this.cronProcessing && !this.cronAbortController && !this.notificationProcessing && !this.notificationAbortController;
  }
  getTurnCount() {
    return this.turn;
  }
  getCreatedAt() {
    return this.createdAt;
  }
  dispose() {
    this.disposed = true;
    this.notificationQueue = [];
    this.cronQueue = [];
    this.notificationAbortController?.abort();
    this.notificationAbortController = null;
    this.notificationProcessing = false;
    this.notificationCompletion = null;
    if (this.cronAbortController) {
      this.cronAbortController.abort();
      this.cronAbortController = null;
    }
    this.cronProcessing = false;
    this.cronCompletion = null;
    if (this.config.isCronEnabled()) {
      this.config.getCronScheduler().stop();
    }
    this.config.getBackgroundTaskRegistry().setNotificationCallback(void 0);
    this.config.getMonitorRegistry().setNotificationCallback(void 0);
    this.config.getBackgroundShellRegistry().setNotificationCallback(void 0);
    this.config.getChatRecordingService()?.setTitleRecordedCallback(void 0);
    clearGoalTerminalObserver(this.sessionId);
  }
  /**
   * Install the message rewrite middleware if configured.
   * Must be called AFTER history replay to avoid rewriting historical messages.
   */
  installRewriter() {
    const rewriteConfig = loadRewriteConfig(this.settings);
    if (rewriteConfig?.enabled) {
      debugLogger5.info("Message rewrite middleware enabled");
      this.messageRewriter = new MessageRewriteMiddleware(
        this.config,
        rewriteConfig,
        (update) => this.sendUpdate(update)
      );
    }
  }
  #installGoalTerminalObserver() {
    setGoalTerminalObserver(this.sessionId, (event) => {
      void this.messageEmitter.emitGoalTerminal(event).catch((error) => {
        debugLogger5.warn(
          `Failed to emit goal terminal update: ${this.#formatError(error)}`
        );
      });
    });
  }
  emitGoalStatus(status) {
    void this.messageEmitter.emitGoalStatus(status).catch((error) => {
      debugLogger5.warn(
        `Failed to emit goal status update: ${this.#formatError(error)}`
      );
    });
  }
  /**
   * Replays conversation history to the client using modular components.
   * Delegates to HistoryReplayer for consistent event emission.
   */
  async replayHistory(records) {
    this.turn = Math.max(
      this.turn,
      computeInitialTurnFromHistory(records, this.config.getSessionId())
    );
    await this.historyReplayer.replay(records);
  }
  rewindToTurn(targetTurnIndex, opts) {
    if (!Number.isInteger(targetTurnIndex) || targetTurnIndex < 0) {
      throw RequestError.invalidParams(
        void 0,
        "targetTurnIndex must be a non-negative integer"
      );
    }
    if (this.pendingPrompt || this.cronProcessing || this.cronAbortController || this.notificationProcessing || this.notificationAbortController) {
      throw RequestError.invalidParams(
        void 0,
        "Cannot rewind while a prompt is running"
      );
    }
    const chat = this.config.getGeminiClient().getChat();
    const apiHistory = chat.getHistoryShallow();
    const apiTruncateIndex = this.#computeApiTruncationIndexForUserTurn(
      apiHistory,
      targetTurnIndex
    );
    if (apiTruncateIndex < 0) {
      throw RequestError.invalidParams(
        void 0,
        "Cannot rewind to the requested turn. It may have been compressed or does not exist."
      );
    }
    chat.truncateHistory(apiTruncateIndex);
    chat.stripThoughtsFromHistory();
    const rewindFiles = opts?.rewindFiles !== false;
    const fileHistoryService = this.config.getFileHistoryService();
    const survivingSnapshots = rewindFiles ? fileHistoryService.getSnapshots().slice(0, targetTurnIndex + 1) : void 0;
    if (survivingSnapshots) {
      fileHistoryService.restoreFromSnapshots(survivingSnapshots);
    }
    this.config.getChatRecordingService()?.rewindRecording(
      targetTurnIndex,
      { truncatedCount: Math.max(0, apiHistory.length - apiTruncateIndex) },
      survivingSnapshots
    );
    return { targetTurnIndex, apiTruncateIndex };
  }
  captureHistorySnapshot() {
    return this.config.getGeminiClient().getChat().getHistoryShallow();
  }
  getRewindableUserTurnCount() {
    const apiHistory = this.captureHistorySnapshot();
    const startIndex = getStartupContextLength(apiHistory);
    let count = 0;
    for (let i = startIndex; i < apiHistory.length; i++) {
      if (this.#isUserTextContent(apiHistory[i])) {
        count += 1;
      }
    }
    return count;
  }
  restoreHistory(history) {
    if (this.pendingPrompt || this.cronProcessing || this.cronAbortController || this.notificationProcessing || this.notificationAbortController) {
      throw RequestError.invalidParams(
        void 0,
        "Cannot restore history while a prompt is running"
      );
    }
    this.config.getGeminiClient().getChat().setHistory(structuredClone(history));
  }
  #computeApiTruncationIndexForUserTurn(apiHistory, targetTurnIndex) {
    const startIndex = getStartupContextLength(apiHistory);
    if (targetTurnIndex === 0) {
      return startIndex;
    }
    let realUserPromptCount = 0;
    for (let i = startIndex; i < apiHistory.length; i++) {
      if (!this.#isUserTextContent(apiHistory[i])) {
        continue;
      }
      if (realUserPromptCount === targetTurnIndex) {
        return i;
      }
      realUserPromptCount += 1;
    }
    return -1;
  }
  #isUserTextContent(content) {
    if (content.role !== "user") return false;
    if (!content.parts || content.parts.length === 0) return false;
    const hasFunctionResponse = content.parts.some(
      (part) => "functionResponse" in part
    );
    if (hasFunctionResponse) return false;
    if (isSystemReminderContent(content)) return false;
    return content.parts.some((part) => "text" in part && part.text);
  }
  async cancelPendingPrompt() {
    const hadPrompt = !!this.pendingPrompt;
    const hadCron = !!this.cronAbortController;
    const hadNotification = !!this.notificationAbortController || this.notificationProcessing;
    if (this.followupAbort) {
      this.followupAbort.abort();
      this.followupAbort = null;
    }
    if (!hadPrompt && !hadCron && !hadNotification) {
      throw new Error(NOT_CURRENTLY_GENERATING_CANCEL_MESSAGE);
    }
    if (this.pendingPrompt) {
      this.pendingPrompt.abort(USER_CANCEL_ABORT_REASON);
      this.pendingPrompt = null;
    }
    if (this.cronAbortController) {
      this.cronAbortController.abort();
      this.cronAbortController = null;
      this.cronQueue = [];
      this.cronProcessing = false;
    }
    if (this.notificationAbortController) {
      this.notificationAbortController.abort();
      this.notificationAbortController = null;
    }
    this.notificationQueue = [];
    this.notificationProcessing = false;
    const scheduler = this.config.isCronEnabled() ? this.config.getCronScheduler() : null;
    if (scheduler) {
      const summary = scheduler.getExitSummary();
      scheduler.stop();
      if (summary) {
        await this.messageEmitter.emitAgentMessage(summary);
      }
    }
  }
  async prompt(params) {
    this.pendingPrompt?.abort();
    const pendingSend = new AbortController();
    this.pendingPrompt = pendingSend;
    if (this.followupAbort) {
      this.followupAbort.abort();
      this.followupAbort = null;
    }
    if (this.cronAbortController) {
      this.cronAbortController.abort();
      this.cronAbortController = null;
      this.cronQueue = [];
      this.cronProcessing = false;
    }
    if (this.cronCompletion) {
      try {
        await this.cronCompletion;
      } catch {
      }
      this.cronCompletion = null;
    }
    if (this.pendingPromptCompletion) {
      try {
        await this.pendingPromptCompletion;
      } catch {
      }
    }
    if (this.notificationAbortController) {
      this.notificationAbortController.abort();
      this.notificationAbortController = null;
      this.notificationQueue = [];
      this.notificationProcessing = false;
    }
    if (this.notificationCompletion) {
      try {
        await this.notificationCompletion;
      } catch {
      }
    }
    if (pendingSend.signal.aborted) {
      return { stopReason: "cancelled" };
    }
    this.duplicateProviderToolCallResponseIds.clear();
    let resolveCompletion;
    this.pendingPromptCompletion = new Promise((resolve3) => {
      resolveCompletion = resolve3;
    });
    try {
      const result = await this.#executePrompt(params, pendingSend);
      this.pendingPrompt = null;
      void this.#drainCronQueue();
      void this.#drainNotificationQueue();
      this.#maybeEmitFollowupSuggestion(result);
      return result;
    } finally {
      this.pendingPrompt = null;
      void this.#startCronSchedulerIfNeeded();
      resolveCompletion();
      this.pendingPromptCompletion = null;
    }
  }
  /**
   * Classify whether an unfinished previous turn can be resumed — an
   * interrupted prompt (the model never answered) or a turn left with dangling
   * tool calls — without injecting a synthetic "continue" user message.
   * Classifies from persisted history. Idempotent no-op (accepted:false) when
   * the last turn ended cleanly or a prompt is already in flight.
   *
   * This is the accept/reject pre-check only — it does NOT fire the turn. When
   * accepted, the daemon bridge drives the continuation through the normal
   * prompt-admission path (`sendPrompt` with the trusted continue meta) so it is
   * tracked like any other prompt; `prompt()` then re-detects/strips
   * authoritatively. Powers `qwen/control/session/continue`.
   */
  async continueLastTurn() {
    const geminiClient = this.config.getGeminiClient();
    if (!geminiClient || !geminiClient.isInitialized()) {
      return { accepted: false, interruption: "none" };
    }
    const chat = this.#getCurrentChat();
    const detection = detectTurnInterruption(
      chat.getHistoryTailShallow?.(TURN_INTERRUPTION_HISTORY_TAIL_COUNT) ?? chat.getHistoryTail(TURN_INTERRUPTION_HISTORY_TAIL_COUNT)
    );
    if (detection.kind === "none") {
      return { accepted: false, interruption: "none" };
    }
    if (this.pendingPrompt && !this.pendingPrompt.signal.aborted) {
      return { accepted: false, interruption: detection.kind };
    }
    return { accepted: true, interruption: detection.kind };
  }
  /**
   * Generate a server-side follow-up suggestion for the just-completed
   * turn and push it to attached clients via the daemon's
   * `qwen/notify/session/prompt-suggestion` extNotification. Mirrors
   * the CLI's `AppContainer.tsx` integration: same `generatePromptSuggestion`
   * call, same `enableCacheSharing` flag forwarding, same curated
   * history slice (`getHistory(true).slice(-40)`).
   *
   * Differences from the CLI:
   *   - Triggers only on `stopReason === 'end_turn'` (the daemon
   *     equivalent of "the assistant finished cleanly"). Cancelled /
   *     errored turns don't get a suggestion.
   *   - Aborted via `this.followupAbort`, which is reset on the next
   *     `prompt()` and on `cancelPendingPrompt()`.
   *   - Filter-reason logging only — accept / dismiss telemetry stays
   *     client-side (the CLI hook owns it).
   *
   * Fire-and-forget by design: an unawaited IIFE that swallows its own
   * errors. A failed suggestion is invisible to the user; a thrown
   * error here would propagate up through `prompt()` and break the
   * primary response path.
   */
  #maybeEmitFollowupSuggestion(result) {
    if (result.stopReason !== "end_turn") return;
    if (this.settings.merged.ui?.enableFollowupSuggestions === false) return;
    if (this.config.getApprovalMode() === "plan" /* PLAN */) return;
    const chat = this.config.getGeminiClient()?.getChat();
    if (!chat) return;
    const ac = new AbortController();
    this.followupAbort = ac;
    const promptId = this.config.getSessionId() + "########" + String(this.turn);
    void (async () => {
      try {
        const fullHistory = chat.getHistory(true);
        const lastEntry = fullHistory[fullHistory.length - 1];
        if (!lastEntry || lastEntry.role !== "model") {
          debugLogger5.debug(
            "Skipping followup suggestion: last history entry is not model"
          );
          return;
        }
        const conversationHistory = fullHistory.length > 40 ? fullHistory.slice(-40) : fullHistory;
        const r = await generatePromptSuggestion(
          this.config,
          conversationHistory,
          ac.signal,
          {
            enableCacheSharing: this.settings.merged.ui?.enableCacheSharing === true
          }
        );
        if (ac.signal.aborted) return;
        if (r.suggestion) {
          await this.client.extNotification(
            "qwen/notify/session/prompt-suggestion",
            {
              v: 1,
              sessionId: this.sessionId,
              suggestion: r.suggestion,
              promptId
            }
          );
        } else if (r.filterReason) {
          logPromptSuggestion(
            this.config,
            new PromptSuggestionEvent({
              outcome: "suppressed",
              reason: r.filterReason
            })
          );
        }
      } catch (error) {
        if (ac.signal.aborted) {
          debugLogger5.debug("Follow-up suggestion generation aborted");
        } else {
          debugLogger5.warn("Follow-up suggestion generation failed", error);
        }
      } finally {
        if (this.followupAbort === ac) {
          this.followupAbort = null;
        }
      }
    })();
  }
  async #executePrompt(params, pendingSend) {
    return sessionIdContext.run(
      this.config.getSessionId(),
      () => this.#executePromptInner(params, pendingSend)
    );
  }
  async #executePromptInner(params, pendingSend) {
    return Storage.runWithRuntimeBaseDir(
      this.runtimeBaseDir,
      this.config.getWorkingDir(),
      async () => {
        this.turn += 1;
        const promptId = this.config.getSessionId() + "########" + this.turn;
        const parentContext = extractDaemonTraceContext(params);
        return await withInteractionSpan(
          this.config,
          {
            promptId,
            model: this.config.getModel(),
            messageType: "acp_prompt",
            ...parentContext ? { parentContext } : {}
          },
          async () => {
            const promptText = params.prompt.filter((block) => block.type === "text").map((block) => block.type === "text" ? block.text : "").join(" ");
            logUserPrompt(
              this.config,
              new UserPromptEvent(
                promptText.length,
                promptId,
                this.config.getContentGeneratorConfig()?.authType,
                promptText
              )
            );
            const isRetry = params.retry === true || params._meta?.[DAEMON_RETRY_META_KEY] === true;
            const isContinue = params._meta?.[DAEMON_CONTINUE_META_KEY] === true;
            let continuationParts = null;
            let strippedOrphanEntries = null;
            let orphanPushCountSnapshot = 0;
            if (isContinue) {
              const detection = detectTurnInterruption(
                this.#getCurrentChat().getHistory()
              );
              if (detection.kind === "none") {
                debugLogger5.warn(
                  `[Session] continue ${promptId}: no interrupted turn on re-detection, nothing to continue`
                );
                logConversationFinishedEvent(
                  this.config,
                  new ConversationFinishedEvent(
                    this.config.getApprovalMode(),
                    0
                  )
                );
                return { stopReason: "end_turn" };
              }
              if (detection.kind === "interrupted_prompt") {
                strippedOrphanEntries = this.#getCurrentChat().stripOrphanedUserEntriesFromHistory() ?? null;
                orphanPushCountSnapshot = this.#getCurrentChat().getUserContentPushCount?.() ?? 0;
                continuationParts = detection.parts;
              } else {
                continuationParts = buildSyntheticToolResponseParts(
                  detection.danglingCalls,
                  ORPHAN_TOOL_USE_REPAIR_REASON
                );
              }
            }
            if (isContinue) {
            } else if (isRetry) {
              this.#getCurrentChat().stripOrphanedUserEntriesFromHistory();
            } else {
              this.config.getChatRecordingService()?.recordUserMessage(promptText);
            }
            const firstTextBlock = params.prompt.find(
              (block) => block.type === "text"
            );
            const inputText = firstTextBlock?.text || "";
            let parts;
            if (isContinue) {
              parts = continuationParts;
            } else if (isSlashCommand(inputText)) {
              const slashCommandResult = await handleSlashCommand(
                inputText,
                pendingSend,
                this.config,
                this.settings
              );
              parts = await this.#processSlashCommandResult(
                slashCommandResult,
                params.prompt
              );
              if (parts === null) {
                return { stopReason: "end_turn" };
              }
            } else {
              parts = await this.#resolvePrompt(
                params.prompt,
                pendingSend.signal
              );
            }
            const hooksEnabled = !this.config.getDisableAllHooks?.();
            const messageBus = this.config.getMessageBus?.();
            if (!isContinue && hooksEnabled && messageBus && this.config.hasHooksForEvent?.("UserPromptSubmit")) {
              const response = await messageBus.request(
                {
                  type: "hook-execution-request" /* HOOK_EXECUTION_REQUEST */,
                  eventName: "UserPromptSubmit",
                  input: {
                    prompt: promptText
                  },
                  signal: pendingSend.signal
                },
                "hook-execution-response" /* HOOK_EXECUTION_RESPONSE */
              );
              const hookOutput = response.output ? createHookOutput("UserPromptSubmit", response.output) : void 0;
              if (hookOutput?.isBlockingDecision() || hookOutput?.shouldStopExecution()) {
                const blockReason = hookOutput?.getEffectiveReason() || "No reason provided";
                await this.messageEmitter.emitAgentMessage(
                  `\u2717 **UserPromptSubmit blocked**: ${blockReason}`
                );
                return { stopReason: "end_turn" };
              }
              const additionalContext = hookOutput?.getAdditionalContext();
              if (additionalContext) {
                parts = [...parts, { text: additionalContext }];
              }
            }
            try {
              const fileHistoryService = this.config.getFileHistoryService();
              await fileHistoryService.makeSnapshot(promptId);
              try {
                const latestSnapshot = fileHistoryService.getSnapshots().at(-1);
                if (latestSnapshot) {
                  this.config.getChatRecordingService()?.recordFileHistorySnapshot(latestSnapshot);
                }
              } catch (e) {
                debugLogger5.error(`FileHistory: recordSnapshot failed: ${e}`);
              }
            } catch (e) {
              debugLogger5.error(`FileHistory: makeSnapshot failed: ${e}`);
            }
            const systemReminders = await this.#buildInitialSystemReminders();
            if (systemReminders.length > 0) {
              const alreadyHasReminder = isContinue && parts.some(
                (part) => isSystemReminderContent({ role: "user", parts: [part] })
              );
              if (!alreadyHasReminder) {
                parts = insertAfterFunctionResponses(parts, systemReminders);
              }
            }
            if (this.pendingWorktreeNotice) {
              const noticePart = {
                text: `<system-reminder>
${this.pendingWorktreeNotice}
</system-reminder>

`
              };
              parts = insertAfterFunctionResponses(parts, [noticePart]);
              this.pendingWorktreeNotice = null;
            }
            let nextMessage = { role: "user", parts };
            let turnCount = 0;
            const toolLoopState = createDaemonToolLoopState();
            try {
              while (nextMessage !== null) {
                turnCount++;
                if (pendingSend.signal.aborted) {
                  this.#getCurrentChat().addHistory(nextMessage);
                  return { stopReason: "cancelled" };
                }
                const functionCalls = [];
                let usageMetadata = null;
                const streamStartTime = Date.now();
                try {
                  const sendResult = await this.#sendMessageStreamWithAutoCompression(
                    promptId,
                    nextMessage?.parts ?? [],
                    pendingSend.signal
                  );
                  if (!sendResult.responseStream) {
                    this.#preserveUnsentMessageHistory(
                      nextMessage,
                      isContinue || sendResult.stopReason === "cancelled"
                    );
                    return { stopReason: sendResult.stopReason };
                  }
                  const responseStream = sendResult.responseStream;
                  nextMessage = null;
                  for await (const resp of responseStream) {
                    if (pendingSend.signal.aborted) {
                      return { stopReason: "cancelled" };
                    }
                    if (resp.type === "chunk" /* CHUNK */ && resp.value.candidates && resp.value.candidates.length > 0) {
                      const candidate = resp.value.candidates[0];
                      for (const part of candidate.content?.parts ?? []) {
                        if (!part.text) {
                          continue;
                        }
                        this.messageEmitter.emitMessage(
                          part.text,
                          "assistant",
                          part.thought
                        );
                      }
                    }
                    if (resp.type === "chunk" /* CHUNK */ && resp.value.usageMetadata) {
                      usageMetadata = resp.value.usageMetadata;
                    }
                    if (resp.type === "chunk" /* CHUNK */ && resp.value.functionCalls) {
                      functionCalls.push(...resp.value.functionCalls);
                    }
                  }
                } catch (error) {
                  if (strippedOrphanEntries && (this.#getCurrentChat().getUserContentPushCount?.() ?? 0) <= orphanPushCountSnapshot) {
                    for (const entry of strippedOrphanEntries) {
                      this.#getCurrentChat().addHistory(entry);
                    }
                    strippedOrphanEntries = null;
                  }
                  if (pendingSend.signal.aborted && pendingSend.signal.reason === USER_CANCEL_ABORT_REASON && this.#isAbortError(error)) {
                    return { stopReason: "cancelled" };
                  }
                  const errorStatus = getErrorStatus(error);
                  const errorMessage = error instanceof Error ? error.message : String(error);
                  const errorType = classifyApiError({
                    message: errorMessage,
                    status: errorStatus
                  });
                  const hookSystem = this.config.getHookSystem?.();
                  const hooksEnabledForStopFailure = !this.config.getDisableAllHooks?.();
                  if (hooksEnabledForStopFailure && hookSystem && this.config.hasHooksForEvent?.("StopFailure")) {
                    hookSystem.fireStopFailureEvent(errorType, errorMessage).catch((err) => {
                      debugLogger5.warn(`StopFailure hook failed: ${err}`);
                    });
                  }
                  if (errorStatus === 429) {
                    throw new RequestError(
                      429,
                      "Rate limit exceeded. Try again later."
                    );
                  }
                  throw error;
                }
                if (usageMetadata) {
                  this.#recordPromptTokenCount(usageMetadata);
                  if (this.messageRewriter) {
                    this.messageRewriter.flushTurn(pendingSend.signal);
                  }
                  const durationMs = Date.now() - streamStartTime;
                  await this.messageEmitter.emitUsageMetadata(
                    usageMetadata,
                    "",
                    durationMs
                  );
                }
                if (functionCalls.length > 0) {
                  const toolRun = await this.runToolCalls(
                    pendingSend.signal,
                    promptId,
                    functionCalls,
                    toolLoopState
                  );
                  if (toolRun.stopAfterPermissionCancel) {
                    await this.#preserveStoppedToolRun(
                      toolRun,
                      pendingSend.signal
                    );
                    return { stopReason: "end_turn" };
                  }
                  nextMessage = await this.#buildNextMessageAfterToolRun(
                    toolRun,
                    pendingSend.signal
                  );
                  if (toolRun.loopDetected) {
                    await this.#preserveStoppedToolRun(
                      toolRun,
                      pendingSend.signal
                    );
                    return { stopReason: "end_turn" };
                  }
                }
              }
              if (this.messageRewriter) {
                await this.messageRewriter.waitForPendingRewrites();
              }
              return await this.#handleStopHookLoop(
                pendingSend,
                promptId,
                hooksEnabled,
                messageBus
              );
            } finally {
              logConversationFinishedEvent(
                this.config,
                new ConversationFinishedEvent(
                  this.config.getApprovalMode(),
                  turnCount
                )
              );
            }
          },
          (result) => result.stopReason === "cancelled" ? "cancelled" : "ok"
        );
      }
    );
  }
  /**
   * Handles the Stop hook iteration loop.
   * This method processes Stop hooks after a model response completes with no pending tool calls.
   * If a Stop hook requests continuation, it sends a follow-up message and loops back.
   * Maximum iterations (100) prevent infinite loops.
   *
   * @param pendingSend - The abort controller for the current prompt
   * @param promptId - The prompt ID for tracking
   * @param hooksEnabled - Whether hooks are enabled
   * @param messageBus - The MessageBus for hook communication (may be undefined)
   * @returns The ACP stop reason for the prompt.
   */
  async #handleStopHookLoop(pendingSend, promptId, hooksEnabled, messageBus) {
    const stopHookBlockingCap = this.config.getStopHookBlockingCap();
    let stopHookIterationCount = 0;
    let stopHookReasons = [];
    while (stopHookIterationCount < stopHookBlockingCap) {
      if (!hooksEnabled || !messageBus || pendingSend.signal.aborted || !this.config.hasHooksForEvent?.("Stop")) {
        return { stopReason: "end_turn" };
      }
      const responseText = this.#getCurrentChat().getLastModelMessageText?.() || "[no response text]";
      const contextUsage = buildContextUsage(
        this.config.getContentGeneratorConfig()?.contextWindowSize ?? DEFAULT_TOKEN_LIMIT,
        this.lastPromptTokenCount
      );
      const response = await messageBus.request(
        {
          type: "hook-execution-request" /* HOOK_EXECUTION_REQUEST */,
          eventName: "Stop",
          input: {
            stop_hook_active: true,
            last_assistant_message: responseText,
            ...contextUsage
          },
          signal: pendingSend.signal
        },
        "hook-execution-response" /* HOOK_EXECUTION_RESPONSE */
      );
      if (pendingSend.signal.aborted) {
        return { stopReason: "cancelled" };
      }
      const hookOutput = response.output ? createHookOutput("Stop", response.output) : void 0;
      const stopOutput = hookOutput;
      if (stopOutput?.systemMessage) {
        await this.messageEmitter.emitAgentMessage(stopOutput.systemMessage);
      }
      if (stopOutput?.isBlockingDecision() || stopOutput?.shouldStopExecution()) {
        const continueReason = stopOutput.getEffectiveReason();
        stopHookIterationCount++;
        stopHookReasons = [...stopHookReasons, continueReason];
        if (stopHookIterationCount >= stopHookBlockingCap) {
          const warning = formatStopHookBlockingCapWarning(
            "Stop",
            stopHookBlockingCap
          );
          abortGoalForStopHookCap(
            this.config,
            this.config.getSessionId(),
            warning
          );
          await this.messageEmitter.emitAgentMessage(warning);
          debugLogger5.warn(warning);
          return { stopReason: "end_turn" };
        }
        if (stopHookIterationCount > 1) {
          await this.messageEmitter.emitStopHookLoop(
            stopHookIterationCount,
            stopHookReasons,
            response.stopHookCount ?? 1
          );
        }
        const continueParts = [{ text: continueReason }];
        let nextMessage = {
          role: "user",
          parts: continueParts
        };
        const toolLoopState = createDaemonToolLoopState();
        while (nextMessage !== null) {
          if (pendingSend.signal.aborted) {
            return { stopReason: "cancelled" };
          }
          const functionCalls = [];
          let usageMetadata = null;
          const streamStartTime = Date.now();
          try {
            const continueSendResult = await this.#sendMessageStreamWithAutoCompression(
              promptId + "_stop_hook_" + stopHookIterationCount,
              nextMessage?.parts ?? [],
              pendingSend.signal,
              { skipCompression: stopHookIterationCount > 1 }
            );
            if (!continueSendResult.responseStream) {
              this.#preserveUnsentMessageHistory(
                nextMessage,
                continueSendResult.stopReason === "cancelled"
              );
              return { stopReason: continueSendResult.stopReason };
            }
            const continueResponseStream = continueSendResult.responseStream;
            nextMessage = null;
            for await (const resp of continueResponseStream) {
              if (pendingSend.signal.aborted) {
                return { stopReason: "cancelled" };
              }
              if (resp.type === "chunk" /* CHUNK */ && resp.value.candidates && resp.value.candidates.length > 0) {
                const candidate = resp.value.candidates[0];
                for (const part of candidate.content?.parts ?? []) {
                  if (!part.text) continue;
                  this.messageEmitter.emitMessage(
                    part.text,
                    "assistant",
                    part.thought
                  );
                }
              }
              if (resp.type === "chunk" /* CHUNK */ && resp.value.usageMetadata) {
                usageMetadata = resp.value.usageMetadata;
              }
              if (resp.type === "chunk" /* CHUNK */ && resp.value.functionCalls) {
                functionCalls.push(...resp.value.functionCalls);
              }
            }
          } catch (error) {
            const errorStatus = getErrorStatus(error);
            const errorMessage = error instanceof Error ? error.message : String(error);
            const errorType = classifyApiError({
              message: errorMessage,
              status: errorStatus
            });
            const hookSystem = this.config.getHookSystem?.();
            const hooksEnabledForStopFailure = !this.config.getDisableAllHooks?.();
            if (hooksEnabledForStopFailure && hookSystem && this.config.hasHooksForEvent?.("StopFailure")) {
              hookSystem.fireStopFailureEvent(errorType, errorMessage).catch((err) => {
                debugLogger5.warn(`StopFailure hook failed: ${err}`);
              });
            }
            if (errorStatus === 429) {
              throw new RequestError(
                429,
                "Rate limit exceeded. Try again later."
              );
            }
            throw error;
          }
          if (usageMetadata) {
            this.#recordPromptTokenCount(usageMetadata);
            const durationMs = Date.now() - streamStartTime;
            await this.messageEmitter.emitUsageMetadata(
              usageMetadata,
              "",
              durationMs
            );
          }
          if (functionCalls.length > 0) {
            const toolRun = await this.runToolCalls(
              pendingSend.signal,
              promptId,
              functionCalls,
              toolLoopState
            );
            if (toolRun.stopAfterPermissionCancel) {
              await this.#preserveStoppedToolRun(toolRun, pendingSend.signal);
              return { stopReason: "end_turn" };
            }
            nextMessage = await this.#buildNextMessageAfterToolRun(
              toolRun,
              pendingSend.signal
            );
            if (toolRun.loopDetected) {
              await this.#preserveStoppedToolRun(toolRun, pendingSend.signal);
              return { stopReason: "end_turn" };
            }
          }
        }
        continue;
      }
      break;
    }
    return { stopReason: "end_turn" };
  }
  async sendUpdate(update) {
    const params = {
      sessionId: this.sessionId,
      update
    };
    await this.client.sessionUpdate(params);
  }
  #getCurrentChat() {
    return this.config.getGeminiClient().getChat();
  }
  /**
   * Mirrors the core send path for ACP model sends.
   *
   * Attempts automatic chat compression first, checks the session token limit,
   * emits an ACP-visible notice when compression succeeds, and returns the ACP
   * stop reason when the provider send should be skipped because the request
   * was cancelled or the session token limit was exceeded.
   */
  async #sendMessageStreamWithAutoCompression(promptId, message, abortSignal, options = {}) {
    const geminiClient = this.config.getGeminiClient();
    let compressionDiagnostic = null;
    let compressionInfo = null;
    if (!options.skipCompression) {
      try {
        const compressed = await geminiClient.tryCompressChat(
          promptId,
          false,
          abortSignal
        );
        compressionInfo = compressed;
        this.#recordCompressionTokenCount(compressed);
        if (compressed.compressionStatus === 1 /* COMPRESSED */) {
          this.loopTickResolver?.resetCache();
          const reasonClause = compressed.triggerReason === "image_overflow" ? `accumulated enough tool screenshots to trigger compaction for ${this.config.getModel()}` : `approached the input token limit for ${this.config.getModel()}`;
          compressionDiagnostic = `IMPORTANT: This conversation ${reasonClause}. A compressed context will be sent for future messages (compressed from: ${compressed.originalTokenCount ?? "unknown"} to ${compressed.newTokenCount ?? "unknown"} tokens).`;
        }
      } catch (compressionError) {
        if (abortSignal.aborted || this.#isAbortError(compressionError)) {
          debugLogger5.debug(`Auto-compression aborted for prompt ${promptId}`);
          return { responseStream: null, stopReason: "cancelled" };
        }
        debugLogger5.warn(
          `Auto-compression failed for prompt ${promptId}; proceeding without compression: ` + this.#formatError(compressionError)
        );
      }
    }
    if (abortSignal.aborted) {
      debugLogger5.debug(`Auto-compression aborted for prompt ${promptId}`);
      return { responseStream: null, stopReason: "cancelled" };
    }
    if (!compressionInfo) {
      this.#syncPromptTokenCountWithCurrentChat();
    }
    const sessionTokenLimit = this.config.getSessionTokenLimit();
    if (sessionTokenLimit > 0) {
      const lastPromptTokenCount = this.#getPostCompressionTokenCount(compressionInfo);
      if (lastPromptTokenCount > sessionTokenLimit) {
        debugLogger5.warn(
          `Session token limit exceeded for prompt ${promptId}: ${lastPromptTokenCount} > ${sessionTokenLimit}. Send dropped.`
        );
        await this.#emitAgentDiagnosticMessageSafely(
          `Session token limit exceeded: ${lastPromptTokenCount} tokens > ${sessionTokenLimit} limit. Please start a new session or increase the sessionTokenLimit in your settings.json.`,
          `Failed to emit token limit diagnostic for prompt ${promptId}`
        );
        return { responseStream: null, stopReason: "max_tokens" };
      }
    }
    if (compressionDiagnostic) {
      await this.#emitAgentDiagnosticMessageSafely(
        compressionDiagnostic,
        `Failed to emit compression notification for prompt ${promptId}`
      );
    }
    if (abortSignal.aborted) {
      debugLogger5.debug(
        `Send aborted after compression diagnostic for prompt ${promptId}`
      );
      return { responseStream: null, stopReason: "cancelled" };
    }
    const responseStream = await this.#getCurrentChat().sendMessageStream(
      this.config.getModel(),
      {
        message,
        config: {
          abortSignal
        }
      },
      promptId
    );
    return { responseStream };
  }
  #preserveUnsentMessageHistory(message, preserveFullMessage) {
    if (!message) return;
    if (preserveFullMessage) {
      this.#getCurrentChat().addHistory(message);
      return;
    }
    const functionResponseParts = message.parts?.filter(
      (part) => "functionResponse" in part && part.functionResponse
    ) ?? [];
    const droppedParts = (message.parts?.length ?? 0) - functionResponseParts.length;
    if (droppedParts > 0) {
      debugLogger5.debug(
        `Dropping ${droppedParts} non-functionResponse part(s) from unsent ACP message after send was skipped.`
      );
    }
    if (functionResponseParts.length > 0) {
      this.#getCurrentChat().addHistory({
        ...message,
        parts: functionResponseParts
      });
    }
  }
  async #preserveStoppedToolRun(toolRun, abortSignal) {
    this.#preserveUnsentMessageHistory(
      {
        role: "user",
        parts: [
          ...toolRun.parts,
          ...toolRun.loopDetected ? [{ text: LOOP_DETECTED_CONTEXT_MESSAGE }] : [],
          ...await this.#drainMidTurnUserMessages(abortSignal)
        ]
      },
      true
    );
    await this.messageRewriter?.waitForPendingRewrites();
  }
  async #buildNextMessageAfterToolRun(toolRun, abortSignal) {
    if (toolRun.loopDetected) {
      debugLogger5.debug("Stopping ACP turn after daemon loop detection.");
      return null;
    }
    if (toolRun.repeatedDuplicateProviderToolCall) {
      debugLogger5.debug(
        "Stopping ACP turn after dropping repeated duplicate provider tool-call response."
      );
      return null;
    }
    const parts = [
      ...toolRun.parts,
      ...await this.#drainMidTurnUserMessages(abortSignal)
    ];
    return { role: "user", parts };
  }
  #recordCompressionTokenCount(info) {
    this.#syncPromptTokenCountWithCurrentChat();
    const tokenCount = this.#extractCompressionTokenCount(info);
    if (tokenCount !== null && tokenCount > 0) {
      this.lastPromptTokenCount = tokenCount;
    }
  }
  #recordPromptTokenCount(usageMetadata) {
    this.#syncPromptTokenCountWithCurrentChat();
    const tokenCount = usageMetadata.promptTokenCount ?? usageMetadata.totalTokenCount;
    if (tokenCount !== void 0 && tokenCount > 0) {
      this.lastPromptTokenCount = tokenCount;
    }
  }
  #getPostCompressionTokenCount(info) {
    const tokenCount = this.#extractCompressionTokenCount(info);
    if (tokenCount !== null) {
      return tokenCount;
    }
    return this.lastPromptTokenCount;
  }
  #extractCompressionTokenCount(info) {
    if (!info) {
      return null;
    }
    if (info.compressionStatus === 1 /* COMPRESSED */) {
      return info.newTokenCount > 0 ? info.newTokenCount : null;
    }
    const tokenCount = info.originalTokenCount ?? info.newTokenCount ?? null;
    if (tokenCount === 0 && info.compressionStatus === 5 /* NOOP */) {
      return null;
    }
    return tokenCount;
  }
  #syncPromptTokenCountWithCurrentChat() {
    const chat = this.#getCurrentChat();
    if (this.lastPromptTokenCountChat && this.lastPromptTokenCountChat !== chat) {
      this.lastPromptTokenCount = 0;
    }
    this.lastPromptTokenCountChat = chat;
  }
  #isAbortError(error) {
    return error instanceof Error && error.name === "AbortError" || typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError" || typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
  }
  #formatError(error) {
    if (error instanceof Error) {
      const parts = [error.message];
      const cause = error.cause;
      if (cause instanceof Error) {
        parts.push(`cause: ${cause.message}`);
      }
      const status = error.status;
      if (status !== void 0) {
        parts.push(`status: ${String(status)}`);
      }
      return parts.join(" | ");
    }
    try {
      return JSON.stringify(error) ?? String(error);
    } catch {
      return String(error);
    }
  }
  async #emitAgentDiagnosticMessageSafely(text, failureContext) {
    try {
      await this.#emitAgentDiagnosticMessage(text);
    } catch (notifyError) {
      debugLogger5.warn(`${failureContext}: ${this.#formatError(notifyError)}`);
    }
  }
  async #emitAgentDiagnosticMessage(text) {
    await this.sendUpdate({
      sessionUpdate: "agent_message_chunk",
      content: { type: "text", text }
    });
  }
  async #drainMidTurnUserMessages(abortSignal) {
    const recovered = this.#takeRecoveredMidTurnMessages();
    if (this.midTurnDrainUnavailable) {
      return this.#buildMidTurnParts(recovered, abortSignal);
    }
    let drainPromise;
    try {
      drainPromise = this.client.extMethod(MID_TURN_QUEUE_DRAIN_METHOD, {
        sessionId: this.sessionId
      });
      let timeoutHandle;
      const timeoutPromise = new Promise((_, reject) => {
        timeoutHandle = setTimeout(
          () => reject(new MidTurnDrainTimeoutError()),
          MID_TURN_QUEUE_DRAIN_TIMEOUT_MS
        );
      });
      let response;
      try {
        response = await Promise.race([drainPromise, timeoutPromise]);
      } finally {
        clearTimeout(timeoutHandle);
      }
      this.midTurnDrainTimeoutStrikes = 0;
      return this.#buildMidTurnParts(
        [...recovered, ...parseMidTurnDrainResponse(response)],
        abortSignal
      );
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : error && typeof error === "object" && "message" in error ? String(error.message) : String(error);
      const errorCode = error && typeof error === "object" && "code" in error ? error.code : void 0;
      const isTimeout = error instanceof MidTurnDrainTimeoutError;
      if (isTimeout) {
        this.midTurnDrainTimeoutStrikes += 1;
        if (drainPromise) void this.#recoverLateDrain(drainPromise);
      }
      const isPermanentError = errorCode === -32601 || /method not found/i.test(errorMessage) || isTimeout && this.midTurnDrainTimeoutStrikes >= MID_TURN_QUEUE_DRAIN_MAX_TIMEOUT_STRIKES;
      if (isPermanentError) {
        this.midTurnDrainUnavailable = true;
      }
      debugLogger5.warn(
        `Mid-turn queue drain ${isPermanentError ? "permanently " : ""}unavailable [session ${this.sessionId}]: ${errorMessage}`
      );
      return this.#buildMidTurnParts(recovered, abortSignal);
    }
  }
  /** Read and clear the buffer of messages recovered from a timed-out drain. */
  #takeRecoveredMidTurnMessages() {
    if (this.midTurnRecoveredMessages.length === 0) return [];
    const out = this.midTurnRecoveredMessages;
    this.midTurnRecoveredMessages = [];
    return out;
  }
  /**
   * After a drain times out, the request is still pending; the daemon settles it
   * shortly after (it splices + SSE-publishes synchronously, so the browser has
   * already deduped). Recover that late response for the next batch instead of
   * discarding it, but bound the wait with a secondary deadline so a response
   * that only arrives long after the turn isn't pushed into an unrelated
   * context. A late rejection is swallowed (no unhandled rejection).
   */
  async #recoverLateDrain(pending) {
    pending.catch(() => {
    });
    const expired = Symbol("mid-turn-recovery-expired");
    let timer;
    const deadline = new Promise((resolve3) => {
      timer = setTimeout(
        () => resolve3(expired),
        MID_TURN_QUEUE_RECOVERY_TIMEOUT_MS
      );
      timer.unref?.();
    });
    let late;
    try {
      late = await Promise.race([pending, deadline]);
    } catch {
      return;
    } finally {
      clearTimeout(timer);
    }
    if (late === expired) {
      debugLogger5.warn(
        `[mid-turn] dropped a drain response that arrived after the ${MID_TURN_QUEUE_RECOVERY_TIMEOUT_MS}ms recovery deadline [session ${this.sessionId}]`
      );
      return;
    }
    const lateMessages = parseMidTurnDrainResponse(late);
    if (lateMessages.length > 0) {
      debugLogger5.debug(
        `[mid-turn] recovered ${lateMessages.length} message(s) from a timed-out drain [session ${this.sessionId}]`
      );
      this.midTurnRecoveredMessages.push(...lateMessages);
    }
  }
  /**
   * Resolve each drained mid-turn message (text or structured content) into
   * agent-visible `Part`s and record it once to the chat transcript. Recording
   * happens on injection (here), so a message recovered from an earlier
   * timed-out drain is still recorded exactly once.
   */
  async #buildMidTurnParts(messages, abortSignal) {
    const parts = [];
    for (const message of messages) {
      const displayText = message.kind === "text" ? message.message : message.displayText;
      let rawParts;
      try {
        rawParts = message.kind === "text" ? [{ text: message.message }] : await withTimeoutSignal(
          abortSignal,
          MID_TURN_QUEUE_RESOLVE_TIMEOUT_MS,
          (signal) => this.#resolvePrompt(message.content, signal)
        );
      } catch (messageError) {
        if (abortSignal.aborted) return parts;
        const errorMessage = this.#formatError(messageError);
        debugLogger5.warn(`Failed to resolve mid-turn message: ${errorMessage}`);
        rawParts = [{ text: displayText }];
        if (message.kind === "structured" && hasInlineMediaContentBlock(message.content)) {
          rawParts.push({ text: MID_TURN_ATTACHMENT_PROCESSING_FAILURE_TEXT });
        }
      }
      const built = prefixMidTurnUserMessageParts(rawParts, displayText);
      this.config.getChatRecordingService()?.recordMidTurnUserMessage(built, displayText);
      parts.push(...built);
    }
    return parts;
  }
  /**
   * Starts the cron scheduler if cron is enabled and jobs exist.
   * The scheduler runs in the background, pushing fired prompts into
   * `cronQueue` and triggering `#drainCronQueue`.
   */
  async #startCronSchedulerIfNeeded() {
    if (this.disposed) return;
    if (!this.config.isCronEnabled()) return;
    if (this.cronDisabledByTokenLimit) return;
    const scheduler = this.config.getCronScheduler();
    try {
      await scheduler.enableDurable(this.sessionId);
    } catch (err) {
      debugLogger5.warn(
        `Durable cron init failed \u2014 persistent tasks will not fire in this session: ${err}`
      );
    }
    if (this.disposed) return;
    if (!scheduler.hasPendingWork) return;
    scheduler.start(
      (job) => {
        if (this.cronDisabledByTokenLimit) return;
        if (job.missed && detectAutonomousSentinel(job.prompt)) return;
        this.cronQueue.push({
          prompt: job.prompt,
          source: job.cronExpr === "@wakeup" ? "loop" : "cron"
        });
        void this.#drainCronQueue();
      }
    );
  }
  /**
   * Processes queued cron prompts one at a time. Uses `cronProcessing`
   * as a mutex to prevent concurrent access to the chat.
   */
  async #drainCronQueue() {
    if (this.disposed) return;
    if (this.cronProcessing) return;
    if (this.pendingPrompt) return;
    if (this.notificationProcessing) return;
    this.cronProcessing = true;
    let resolveCompletion;
    this.cronCompletion = new Promise((resolve3) => {
      resolveCompletion = resolve3;
    });
    try {
      while (this.cronQueue.length > 0) {
        const item = this.cronQueue.shift();
        await this.#executeCronPrompt(item);
      }
    } finally {
      this.cronProcessing = false;
      resolveCompletion();
      this.cronCompletion = null;
      void this.#drainNotificationQueue();
      if (this.config.isCronEnabled()) {
        const scheduler = this.config.getCronScheduler();
        if (!scheduler.hasPendingWork) {
          scheduler.stop();
        }
      }
    }
  }
  #getLoopTickResolver() {
    const root = this.config.getWorkingDir();
    if (!this.loopTickResolver || this.loopTickResolverRoot !== root) {
      const { homeConfineRoot, homeQwenDir } = resolveHomeLoopResolverRoots();
      this.loopTickResolver = new LoopTickResolver({
        projectRoot: root,
        homeDir: homeConfineRoot,
        homeQwenDir,
        // The project `.qwen/loop.md` is repo-controlled, so an untrusted folder
        // must not read it and feed it to the model (mirrors getProjectHooks()'s
        // trust gate). The home/global `~/.qwen/loop.md` is user-owned and stays
        // allowed. Pass a getter, not a snapshot: isTrustedFolder() can flip
        // mid-session on an IDE workspace-trust update, and the resolver outlives
        // a single tick — re-read it on every resolve() so a trusted→untrusted
        // flip stops reading the project file immediately.
        allowProjectFile: /* @__PURE__ */ __name(() => this.config.isTrustedFolder(), "allowProjectFile")
      });
      this.loopTickResolverRoot = root;
    }
    return this.loopTickResolver;
  }
  /**
   * Executes a single cron-fired prompt: echoes it as a user message with
   * `_meta.source='cron'`, streams the model response, and handles tool calls.
   */
  async #executeCronPrompt(item) {
    return sessionIdContext.run(
      this.config.getSessionId(),
      () => this.#executeCronPromptInner(item)
    );
  }
  async #executeCronPromptInner(item) {
    const { prompt } = item;
    return Storage.runWithRuntimeBaseDir(
      this.runtimeBaseDir,
      this.config.getWorkingDir(),
      async () => {
        const ac = new AbortController();
        this.cronAbortController = ac;
        const promptId = this.config.getSessionId() + "########cron" + Date.now();
        let cronHadError = false;
        await withInteractionSpan(
          this.config,
          {
            promptId,
            model: this.config.getModel(),
            messageType: "cron"
          },
          async () => {
            let turnCount = 0;
            try {
              const loopMode = detectLoopSentinel(prompt);
              const autonomousMode = loopMode ? null : detectAutonomousSentinel(prompt);
              let loopTick = null;
              if (loopMode) {
                const resolver = this.#getLoopTickResolver();
                const trustedAtResolve = this.config.isTrustedFolder();
                try {
                  loopTick = await resolver.resolve(loopMode, trustedAtResolve);
                } catch (resolveErr) {
                  const code = resolveErr.code ?? "unknown";
                  debugLogger5.warn(
                    `loop.md sentinel resolution failed (mode=${loopMode}, code=${code}) \u2014 check .qwen/loop.md permissions/IO`,
                    resolveErr
                  );
                  if (loopMode === "dynamic" && TRANSIENT_FS_CODES.includes(code)) {
                    loopTick = resolver.buildTransientErrorTick(
                      loopMode,
                      trustedAtResolve,
                      code
                    );
                  } else {
                    throw new Error(
                      `loop.md resolution failed (${code}) for ${resolver.absentLocations(
                        trustedAtResolve
                      )}`
                    );
                  }
                }
              } else if (autonomousMode) {
                loopTick = this.#getLoopTickResolver().resolveAutonomous(autonomousMode);
              }
              const modelText = loopTick ? loopTick.modelText : prompt;
              if (loopTick) {
                debugLogger5.debug(
                  `loop tick: mode=${loopMode ?? autonomousMode} delivery=${loopTick.full ? "full" : loopTick.transientError ? "transient-error" : loopTick.autonomous ? "autonomous-tick" : loopTick.sourceLabel ? "reminder" : "absent"} source=${loopTick.sourceLabel ?? "none"} autonomous=${loopTick.autonomous ?? false} transient=${loopTick.transientError ?? false}`
                );
              }
              const echoText = !loopTick ? prompt : (
                // An autonomous tick (a bare-`/loop` sentinel, or a loop.md
                // sentinel whose file is gone and converged on the preamble).
                loopTick.autonomous ? "Autonomous loop tick" : loopTick.sourceLabel ? `Loop tick \u2014 tasks from ${loopTick.sourceLabel}` : (
                  // The only remaining tick is a transient read failure
                  // (buildTransientErrorTick): a loop.md exists but couldn't be
                  // read this tick. A genuinely-absent loop.md converges on the
                  // autonomous branch above, so there is no "not present" echo.
                  "Loop tick \u2014 loop.md temporarily unavailable"
                )
              );
              await this.sendUpdate({
                sessionUpdate: "user_message_chunk",
                content: { type: "text", text: echoText },
                _meta: { source: item.source }
              });
              const cronReminders = await this.#buildInitialSystemReminders();
              let nextMessage = {
                role: "user",
                parts: [...cronReminders, { text: modelText }]
              };
              const toolLoopState = createDaemonToolLoopState();
              while (nextMessage !== null) {
                turnCount++;
                if (ac.signal.aborted) return;
                const functionCalls = [];
                let usageMetadata = null;
                const streamStartTime = Date.now();
                const sendResult = await this.#sendMessageStreamWithAutoCompression(
                  promptId,
                  nextMessage.parts ?? [],
                  ac.signal
                );
                if (!sendResult.responseStream) {
                  this.#preserveUnsentMessageHistory(
                    nextMessage,
                    sendResult.stopReason === "cancelled"
                  );
                  if (sendResult.stopReason === "max_tokens") {
                    this.#stopCronAfterTokenLimit();
                  }
                  return;
                }
                const responseStream = sendResult.responseStream;
                if (loopTick && turnCount === 1) {
                  this.loopTickResolver?.markDelivered();
                }
                nextMessage = null;
                for await (const resp of responseStream) {
                  if (ac.signal.aborted) return;
                  if (resp.type === "chunk" /* CHUNK */ && resp.value.candidates && resp.value.candidates.length > 0) {
                    const candidate = resp.value.candidates[0];
                    for (const part of candidate.content?.parts ?? []) {
                      if (!part.text) continue;
                      this.messageEmitter.emitMessage(
                        part.text,
                        "assistant",
                        part.thought
                      );
                    }
                  }
                  if (resp.type === "chunk" /* CHUNK */ && resp.value.usageMetadata) {
                    usageMetadata = resp.value.usageMetadata;
                  }
                  if (resp.type === "chunk" /* CHUNK */ && resp.value.functionCalls) {
                    functionCalls.push(...resp.value.functionCalls);
                  }
                }
                if (usageMetadata) {
                  this.#recordPromptTokenCount(usageMetadata);
                  if (this.messageRewriter) {
                    this.messageRewriter.flushTurn(ac.signal);
                  }
                  const durationMs = Date.now() - streamStartTime;
                  await this.messageEmitter.emitUsageMetadata(
                    usageMetadata,
                    "",
                    durationMs
                  );
                }
                if (functionCalls.length > 0) {
                  const toolRun = await this.runToolCalls(
                    ac.signal,
                    promptId,
                    functionCalls,
                    toolLoopState
                  );
                  if (toolRun.stopAfterPermissionCancel) {
                    await this.#preserveStoppedToolRun(toolRun, ac.signal);
                    return;
                  }
                  nextMessage = await this.#buildNextMessageAfterToolRun(
                    toolRun,
                    ac.signal
                  );
                  if (toolRun.loopDetected) {
                    await this.#preserveStoppedToolRun(toolRun, ac.signal);
                    return;
                  }
                }
              }
            } catch (error) {
              if (ac.signal.aborted) return;
              cronHadError = true;
              debugLogger5.error("Error processing cron prompt:", error);
              const msg = error instanceof Error ? error.message : String(error);
              await this.messageEmitter.emitAgentMessage(
                `[${item.source} error] ${msg}`
              );
            } finally {
              if (this.cronAbortController === ac) {
                this.cronAbortController = null;
              }
              logConversationFinishedEvent(
                this.config,
                new ConversationFinishedEvent(
                  this.config.getApprovalMode(),
                  turnCount
                )
              );
            }
          },
          () => ac.signal.aborted ? "cancelled" : cronHadError ? "error" : "ok"
        );
      }
    );
  }
  #stopCronAfterTokenLimit() {
    this.cronDisabledByTokenLimit = true;
    this.cronQueue = [];
    if (!this.config.isCronEnabled()) return;
    this.config.getCronScheduler().disable();
    void this.#emitAgentDiagnosticMessageSafely(
      "Cron jobs and loop wakeups disabled for the rest of this session due to token limit. Restart the session to re-enable.",
      "Failed to emit cron-disabled diagnostic"
    );
  }
  #registerBackgroundNotificationCallbacks() {
    const backgroundRegistry = this.config.getBackgroundTaskRegistry();
    backgroundRegistry.setNotificationCallback(
      (displayText, modelText, meta) => {
        this.#enqueueBackgroundNotification({
          displayText,
          modelText,
          taskId: meta.agentId,
          status: meta.status,
          kind: "agent",
          toolUseId: meta.toolUseId
        });
      }
    );
    const monitorRegistry = this.config.getMonitorRegistry();
    monitorRegistry.setNotificationCallback((displayText, modelText, meta) => {
      if (meta.status === "running") {
        return;
      }
      this.#enqueueBackgroundNotification({
        displayText,
        modelText,
        taskId: meta.monitorId,
        status: meta.status,
        kind: "monitor",
        toolUseId: meta.toolUseId
      });
    });
    const shellRegistry = this.config.getBackgroundShellRegistry();
    shellRegistry.setNotificationCallback((displayText, modelText, meta) => {
      this.#enqueueBackgroundNotification({
        displayText,
        modelText,
        taskId: meta.shellId,
        status: meta.status,
        kind: "shell"
      });
    });
    this.config.getChatRecordingService()?.setTitleRecordedCallback((customTitle, titleSource) => {
      void this.client.extNotification("qwen/notify/session/title-update", {
        v: 1,
        sessionId: this.sessionId,
        title: customTitle,
        titleSource
      }).catch(() => {
      });
    });
  }
  #enqueueBackgroundNotification(item) {
    while (this.notificationQueue.length >= MAX_NOTIFICATION_QUEUE) {
      const evicted = this.notificationQueue.shift();
      debugLogger5.warn(
        `Notification queue overflow: evicting task=${evicted.taskId} kind=${evicted.kind}`
      );
    }
    this.notificationQueue.push(item);
    void this.#drainNotificationQueue();
  }
  async #drainNotificationQueue() {
    if (this.disposed) return;
    if (this.notificationProcessing) return;
    if (this.pendingPrompt || this.cronProcessing || this.cronAbortController) {
      return;
    }
    if (this.notificationQueue.length === 0) return;
    this.notificationProcessing = true;
    let resolveCompletion;
    this.notificationCompletion = new Promise((resolve3) => {
      resolveCompletion = resolve3;
    });
    try {
      while (this.notificationQueue.length > 0) {
        if (this.pendingPrompt || this.cronProcessing || this.cronAbortController) {
          break;
        }
        const item = this.notificationQueue.shift();
        await sessionIdContext.run(
          this.config.getSessionId(),
          () => this.#executeBackgroundNotificationPromptInner(item)
        );
      }
    } finally {
      this.notificationProcessing = false;
      resolveCompletion();
      this.notificationCompletion = null;
      void this.#drainCronQueue();
      if (this.notificationQueue.length > 0 && !this.pendingPrompt && !this.cronProcessing && !this.cronAbortController) {
        void this.#drainNotificationQueue();
      }
    }
  }
  async #executeBackgroundNotificationPromptInner(item) {
    return Storage.runWithRuntimeBaseDir(
      this.runtimeBaseDir,
      this.config.getWorkingDir(),
      async () => {
        const ac = new AbortController();
        this.notificationAbortController = ac;
        const promptId = this.config.getSessionId() + "########notification" + Date.now();
        try {
          await this.#emitBackgroundNotificationDisplay(item);
          const notificationParts = [{ text: item.modelText }];
          this.config.getChatRecordingService()?.recordNotification(notificationParts, item.displayText);
          const notificationReminders = await this.#buildInitialSystemReminders();
          let nextMessage = {
            role: "user",
            parts: [...notificationReminders, ...notificationParts]
          };
          const toolLoopState = createDaemonToolLoopState();
          while (nextMessage !== null) {
            if (ac.signal.aborted) {
              await this.#emitBackgroundNotificationEndTurn("cancelled");
              return;
            }
            const functionCalls = [];
            let usageMetadata = null;
            let responseText = "";
            const streamStartTime = Date.now();
            const sendResult = await this.#sendMessageStreamWithAutoCompression(
              promptId,
              nextMessage.parts ?? [],
              ac.signal
            );
            if (!sendResult.responseStream) {
              this.#preserveUnsentMessageHistory(
                nextMessage,
                sendResult.stopReason === "cancelled"
              );
              await this.#emitBackgroundNotificationEndTurn(
                sendResult.stopReason
              );
              return;
            }
            const responseStream = sendResult.responseStream;
            nextMessage = null;
            for await (const resp of responseStream) {
              if (ac.signal.aborted) {
                await this.#emitBackgroundNotificationEndTurn("cancelled");
                return;
              }
              if (resp.type === "chunk" /* CHUNK */ && resp.value.candidates && resp.value.candidates.length > 0) {
                const candidate = resp.value.candidates[0];
                for (const part of candidate.content?.parts ?? []) {
                  if (!part.text) continue;
                  if (part.thought) {
                    await this.messageEmitter.emitMessage(
                      part.text,
                      "assistant",
                      true
                    );
                  } else {
                    responseText += part.text;
                  }
                }
              }
              if (resp.type === "chunk" /* CHUNK */ && resp.value.usageMetadata) {
                usageMetadata = resp.value.usageMetadata;
              }
              if (resp.type === "chunk" /* CHUNK */ && resp.value.functionCalls) {
                functionCalls.push(...resp.value.functionCalls);
              }
            }
            if (responseText.length > 0) {
              await this.#emitBackgroundNotificationResponse(
                item,
                responseText,
                ac.signal
              );
            }
            if (this.messageRewriter) {
              await this.messageRewriter.flushTurn(ac.signal);
            }
            if (usageMetadata) {
              this.#recordPromptTokenCount(usageMetadata);
              const durationMs = Date.now() - streamStartTime;
              await this.messageEmitter.emitUsageMetadata(
                usageMetadata,
                "",
                durationMs
              );
            }
            if (functionCalls.length > 0) {
              const toolRun = await this.runToolCalls(
                ac.signal,
                promptId,
                functionCalls,
                toolLoopState
              );
              if (toolRun.stopAfterPermissionCancel) {
                await this.#preserveStoppedToolRun(toolRun, ac.signal);
                await this.#emitBackgroundNotificationEndTurn("end_turn");
                return;
              }
              nextMessage = await this.#buildNextMessageAfterToolRun(
                toolRun,
                ac.signal
              );
              if (toolRun.loopDetected) {
                await this.#preserveStoppedToolRun(toolRun, ac.signal);
                await this.#emitBackgroundNotificationEndTurn("end_turn");
                return;
              }
            }
          }
          if (this.messageRewriter) {
            await this.messageRewriter.waitForPendingRewrites();
          }
          await this.#emitBackgroundNotificationEndTurn("end_turn");
        } catch (error) {
          if (ac.signal.aborted) {
            await this.#emitBackgroundNotificationEndTurn("cancelled");
            return;
          }
          debugLogger5.error("Error processing background notification:", error);
          const msg = error instanceof Error ? error.message : String(error);
          try {
            await this.messageEmitter.emitAgentMessage(
              `[notification error] ${msg}`
            );
          } catch (emitError) {
            debugLogger5.error(
              "Failed to emit background notification error:",
              emitError
            );
          } finally {
            await this.#emitBackgroundNotificationEndTurn("end_turn");
          }
        } finally {
          if (this.notificationAbortController === ac) {
            this.notificationAbortController = null;
          }
        }
      }
    );
  }
  async #emitBackgroundNotificationDisplay(item) {
    await this.sendUpdate({
      sessionUpdate: "agent_message_chunk",
      content: { type: "text", text: item.displayText },
      _meta: {
        source: "background_notification",
        qwenDiscreteMessage: true,
        backgroundTask: {
          taskId: item.taskId,
          status: item.status,
          kind: item.kind,
          toolUseId: item.toolUseId
        }
      }
    });
  }
  async #emitBackgroundNotificationResponse(item, text, signal) {
    const update = {
      sessionUpdate: "agent_message_chunk",
      content: { type: "text", text },
      _meta: {
        source: "background_notification_response",
        qwenDiscreteMessage: true,
        backgroundTask: {
          taskId: item.taskId,
          status: item.status,
          kind: item.kind,
          toolUseId: item.toolUseId
        }
      }
    };
    if (this.messageRewriter) {
      await this.messageRewriter.interceptUpdate(update, signal);
      return;
    }
    await this.sendUpdate(update);
  }
  async #emitBackgroundNotificationEndTurn(reason) {
    try {
      await this.client.extNotification("_qwencode/end_turn", {
        sessionId: this.sessionId,
        reason,
        source: "background_notification"
      });
    } catch (error) {
      debugLogger5.debug(
        `Background notification end-turn extNotification dropped: ${this.#formatError(error)}`
      );
    }
  }
  async sendAvailableCommandsUpdate() {
    try {
      const { availableCommands, availableSkills, availableSkillDetails } = await buildAvailableCommandsSnapshot(
        this.config,
        void 0,
        this.settings
      );
      const update = {
        sessionUpdate: "available_commands_update",
        availableCommands,
        ...availableSkills !== void 0 ? {
          _meta: {
            availableSkills,
            ...availableSkillDetails ? { availableSkillDetails } : {}
          }
        } : {}
      };
      await this.sendUpdate(update);
    } catch (error) {
      debugLogger5.error("Error sending available commands update:", error);
    }
  }
  /**
   * Requests permission from the client for a tool call.
   * Used by SubAgentTracker for sub-agent approval requests.
   */
  async requestPermission(params) {
    return this.client.requestPermission(params);
  }
  /**
   * Sets the approval mode for the current session.
   * Maps ACP approval mode values to core ApprovalMode enum.
   */
  async setMode(params) {
    const modeMap = {
      plan: "plan" /* PLAN */,
      default: "default" /* DEFAULT */,
      "auto-edit": "auto-edit" /* AUTO_EDIT */,
      auto: "auto" /* AUTO */,
      yolo: "yolo" /* YOLO */
    };
    const approvalMode = modeMap[params.modeId];
    if (approvalMode === void 0) {
      throw RequestError.invalidParams(
        void 0,
        `Unknown approval mode: ${params.modeId}`
      );
    }
    this.config.setApprovalMode(approvalMode);
    void this.client.extNotification("qwen/notify/session/mode-update", {
      v: 1,
      sessionId: this.sessionId,
      currentModeId: params.modeId
    }).catch((error) => {
      debugLogger5.debug("mode-update extNotification failed", error);
    });
  }
  /**
   * Sets the model for the current session.
   * Validates the model ID and switches the model via Config.
   */
  async setModel(params, options = {}) {
    const rawModelId = params.modelId.trim();
    if (!rawModelId) {
      throw RequestError.invalidParams(void 0, "modelId cannot be empty");
    }
    const parsed = parseAcpModelOption(rawModelId);
    const previousAuthType = this.config.getAuthType?.();
    const selectedAuthType = parsed.authType ?? previousAuthType;
    if (!selectedAuthType) {
      throw RequestError.invalidParams(
        void 0,
        `authType cannot be determined for modelId "${parsed.modelId}"`
      );
    }
    await this.config.switchModel(
      selectedAuthType,
      parsed.modelId,
      selectedAuthType !== previousAuthType && selectedAuthType === "qwen-oauth" /* QWEN_OAUTH */ ? { requireCachedCredentials: true } : void 0
    );
    const after = this.config.getContentGeneratorConfig?.();
    const effectiveAuthType = after?.authType ?? selectedAuthType;
    const effectiveModelId = after?.model ?? parsed.modelId;
    void this.client.extNotification("qwen/notify/session/model-update", {
      v: 1,
      sessionId: this.sessionId,
      currentModelId: effectiveModelId
    }).catch((error) => {
      debugLogger5.debug("model-update extNotification failed", error);
    });
    if (options.persistDefault ?? true) {
      const persistScope = getPersistScopeForModelSelection(this.settings);
      this.settings.setValue(persistScope, "model.name", parsed.modelId);
      this.settings.setValue(persistScope, "model.baseUrl", "");
      this.settings.setValue(
        persistScope,
        "security.auth.selectedType",
        selectedAuthType
      );
    }
    return {
      _meta: {
        qwenModelSwitch: {
          authType: effectiveAuthType,
          modelId: effectiveModelId,
          baseUrl: after?.baseUrl ?? "(default)",
          apiKey: maskApiKeyForDisplay(after?.apiKey),
          isRuntime: rawModelId.startsWith("$runtime|")
        }
      }
    };
  }
  /**
   * Sends a current_mode_update notification to the client.
   * Called after the agent switches modes (e.g., from exit_plan_mode tool).
   */
  async sendCurrentModeUpdateNotification(outcome) {
    let newModeId;
    switch (outcome) {
      case "proceed_always" /* ProceedAlways */:
        newModeId = "auto-edit";
        break;
      case "restore_previous" /* RestorePrevious */:
        newModeId = this.config.getApprovalMode();
        break;
      case "proceed_once" /* ProceedOnce */:
      default:
        newModeId = "default";
        break;
    }
    const update = {
      sessionUpdate: "current_mode_update",
      currentModeId: newModeId
    };
    await this.sendUpdate(update);
    void this.client.extNotification("qwen/notify/session/mode-update", {
      v: 1,
      sessionId: this.sessionId,
      currentModeId: newModeId,
      legacyFrameSent: true
    }).catch((error) => {
      debugLogger5.debug("mode-update extNotification failed", error);
    });
  }
  /**
   * Execute a batch of model-returned tool calls, running Agent calls
   * concurrently while keeping other tools sequential.
   *
   * Mirrors the partition logic in `coreToolScheduler.partitionToolCalls`:
   * consecutive Agent calls form a parallel batch (they spawn independent
   * sub-agents with no shared mutable state); any other tool forms its own
   * sequential batch to preserve the implicit ordering the model may rely
   * on. Response-part ordering matches the original `functionCalls` order.
   */
  async runToolCalls(abortSignal, promptId, functionCalls, toolLoopState) {
    const dedupedFunctionCalls = dedupeToolCallsById(functionCalls);
    let skippedToolCallCounter = 0;
    const recordSkippedToolCall = /* @__PURE__ */ __name(async (fc, message = PERMISSION_CANCEL_SKIP_MESSAGE, emitStart = true) => {
      const toolName = fc.name ?? "unknown_tool";
      const callId = fc.id ?? `${toolName}-skip-${++skippedToolCallCounter}`;
      const part = {
        functionResponse: {
          id: callId,
          name: toolName,
          response: { error: message }
        }
      };
      const error = new Error(message);
      try {
        this.config.getChatRecordingService()?.recordToolResult([part], {
          callId,
          status: "error",
          resultDisplay: void 0,
          error,
          errorType: void 0
        });
        if (emitStart) {
          await this.toolCallEmitter.emitStart({
            callId,
            toolName,
            args: fc.args ?? {},
            status: "pending"
          });
        }
        await this.toolCallEmitter.emitError(callId, toolName, error);
      } catch (recordError) {
        debugLogger5.error("Failed to record skipped tool call:", recordError);
      }
      return part;
    }, "recordSkippedToolCall");
    if (recordDaemonToolCalls(
      this.config,
      promptId,
      toolLoopState,
      dedupedFunctionCalls.length
    )) {
      return {
        parts: await Promise.all(
          dedupedFunctionCalls.map(
            (fc) => recordSkippedToolCall(fc, LOOP_DETECTED_SKIP_MESSAGE, false)
          )
        ),
        stopAfterPermissionCancel: false,
        loopDetected: true
      };
    }
    const batches = [];
    const handledProviderToolCallIds = new Set(
      this.#getCurrentChat().getHistoryFunctionResponseIds()
    );
    const repeatedDuplicateCall = findRepeatedDuplicateProviderToolCall(
      dedupedFunctionCalls,
      (fc) => getProviderToolCallId(fc) ?? fc.id,
      handledProviderToolCallIds,
      this.duplicateProviderToolCallResponseIds
    );
    if (repeatedDuplicateCall) {
      const providerCallId = getProviderToolCallId(repeatedDuplicateCall) ?? repeatedDuplicateCall.id;
      debugLogger5.debug(
        `[Session.runToolCalls] Dropping batch after repeated duplicate provider tool-call id: ${providerCallId} (tool: ${repeatedDuplicateCall.name ?? "unknown_tool"})`
      );
      return {
        parts: [],
        stopAfterPermissionCancel: false,
        repeatedDuplicateProviderToolCall: true
      };
    }
    const pushDuplicateBatch = /* @__PURE__ */ __name((request) => {
      const providerCallId = request.providerCallId ?? request.callId;
      markDuplicateProviderToolCallResponseSent(
        providerCallId,
        this.duplicateProviderToolCallResponseIds
      );
      const response = createDuplicateProviderToolCallResponse(request);
      debugLogger5.debug(
        `[Session.runToolCalls] Suppressing duplicate provider tool-call id: ${providerCallId} (tool: ${request.name})`
      );
      batches.push({ kind: "duplicate", request, response });
    }, "pushDuplicateBatch");
    const emitDuplicateBatch = /* @__PURE__ */ __name(async (batch) => {
      const { request, response } = batch;
      if (request.name === ToolNames.TODO_WRITE) {
        const provenance = ToolCallEmitter.resolveToolProvenance(request.name);
        await this.sendUpdate({
          sessionUpdate: "tool_call_update",
          toolCallId: response.callId,
          status: "failed",
          content: [
            {
              type: "content",
              content: {
                type: "text",
                text: response.error?.message ?? String(response.resultDisplay)
              }
            }
          ],
          rawOutput: response.resultDisplay,
          _meta: {
            toolName: request.name,
            provenance: provenance.provenance,
            ...provenance.serverId ? { serverId: provenance.serverId } : {}
          }
        });
      } else {
        await this.toolCallEmitter.emitResult({
          callId: response.callId,
          toolName: request.name,
          args: request.args,
          message: response.responseParts,
          resultDisplay: response.resultDisplay,
          error: response.error,
          success: false
        });
      }
      this.config.getChatRecordingService()?.recordToolResult(response.responseParts, {
        callId: response.callId,
        status: "error",
        resultDisplay: response.resultDisplay,
        error: response.error,
        errorType: response.errorType
      });
    }, "emitDuplicateBatch");
    for (const fc of dedupedFunctionCalls) {
      const providerCallId = getProviderToolCallId(fc) ?? fc.id;
      if (providerCallId) {
        if (handledProviderToolCallIds.has(providerCallId)) {
          const callId = fc.id ?? `${fc.name}-${Date.now()}`;
          pushDuplicateBatch({
            callId,
            providerCallId,
            name: fc.name ?? "unknown_tool",
            args: fc.args ?? {},
            isClientInitiated: false,
            prompt_id: promptId
          });
          continue;
        }
        handledProviderToolCallIds.add(providerCallId);
      }
      const isAgent = fc.name === ToolNames.AGENT;
      const last = batches[batches.length - 1];
      if (isAgent && last?.kind === "execute" && last.concurrent) {
        last.calls.push(fc);
      } else {
        batches.push({ kind: "execute", concurrent: isAgent, calls: [fc] });
      }
    }
    const appendSkippedAfter = /* @__PURE__ */ __name(async (parts2, fc, message = PERMISSION_CANCEL_SKIP_MESSAGE) => {
      const startIndex = dedupedFunctionCalls.indexOf(fc) + 1;
      for (const remainingCall of dedupedFunctionCalls.slice(startIndex)) {
        parts2.push(await recordSkippedToolCall(remainingCall, message));
      }
    }, "appendSkippedAfter");
    const runBounded = /* @__PURE__ */ __name(async (calls, runAbortSignal, onStopAfterPermissionCancel, onStopAfterLoopDetected, shouldSkipUnstarted) => {
      const configuredMaxConcurrency = parsePositiveIntegerEnv(
        process.env["QWEN_CODE_MAX_TOOL_CONCURRENCY"],
        10
      );
      const maxConcurrency = toolLoopState ? Math.min(
        configuredMaxConcurrency,
        DAEMON_INVALID_TOOL_PARAMS_THRESHOLD
      ) : configuredMaxConcurrency;
      const results = new Array(calls.length);
      const executing = /* @__PURE__ */ new Set();
      const fillLoopSkippedFrom = /* @__PURE__ */ __name(async (startIndex2) => {
        for (let i = startIndex2; i < calls.length; i++) {
          if (results[i]) continue;
          results[i] = {
            parts: [
              await recordSkippedToolCall(calls[i], LOOP_DETECTED_SKIP_MESSAGE)
            ],
            stopAfterPermissionCancel: false,
            loopDetected: true
          };
        }
      }, "fillLoopSkippedFrom");
      const fillPermissionSkippedFrom = /* @__PURE__ */ __name(async (startIndex2) => {
        for (let i = startIndex2; i < calls.length; i++) {
          if (results[i]) continue;
          results[i] = {
            parts: [await recordSkippedToolCall(calls[i])],
            stopAfterPermissionCancel: false
          };
        }
      }, "fillPermissionSkippedFrom");
      let startIndex = 0;
      if (toolLoopState && calls.length > DAEMON_INVALID_TOOL_PARAMS_THRESHOLD) {
        startIndex = DAEMON_INVALID_TOOL_PARAMS_THRESHOLD;
        for (let i = 0; i < startIndex; i++) {
          if (runAbortSignal.aborted && shouldSkipUnstarted?.()) {
            results[i] = {
              parts: [await recordSkippedToolCall(calls[i])],
              stopAfterPermissionCancel: false
            };
            continue;
          }
          const r = await this.runTool(
            runAbortSignal,
            promptId,
            calls[i],
            onStopAfterPermissionCancel,
            toolLoopState,
            recordSkippedToolCall
          );
          results[i] = r;
          if (r.loopDetected) {
            await fillLoopSkippedFrom(i + 1);
            return results;
          }
          if (r.stopAfterPermissionCancel) {
            await fillPermissionSkippedFrom(i + 1);
            return results;
          }
        }
      }
      for (let i = startIndex; i < calls.length; i++) {
        const idx = i;
        if (toolLoopState?.loopDetected) {
          await fillLoopSkippedFrom(idx);
          return results;
        }
        if (runAbortSignal.aborted && shouldSkipUnstarted?.()) {
          results[idx] = {
            parts: [await recordSkippedToolCall(calls[idx])],
            stopAfterPermissionCancel: false
          };
          continue;
        }
        const p = this.runTool(
          runAbortSignal,
          promptId,
          calls[idx],
          onStopAfterPermissionCancel,
          toolLoopState,
          recordSkippedToolCall
        ).then((r) => {
          results[idx] = r;
        }).finally(() => {
          executing.delete(p);
        });
        executing.add(p);
        if (executing.size >= maxConcurrency) {
          await Promise.race(executing);
          if (results.some((result) => result?.loopDetected)) {
            onStopAfterLoopDetected?.();
            await Promise.all(executing);
            await fillLoopSkippedFrom(idx + 1);
            return results;
          }
          const invalidToolErrorNearThreshold = toolLoopState && [...toolLoopState.invalidToolParamErrors.values()].some(
            (count) => count >= DAEMON_INVALID_TOOL_PARAMS_THRESHOLD - 1
          );
          if (invalidToolErrorNearThreshold && executing.size > 0) {
            await Promise.all(executing);
            if (results.some((result) => result?.loopDetected)) {
              onStopAfterLoopDetected?.();
              await fillLoopSkippedFrom(idx + 1);
              return results;
            }
          }
        }
      }
      await Promise.all(executing);
      return results;
    }, "runBounded");
    const parts = [];
    for (const batch of batches) {
      if (batch.kind === "duplicate") {
        await emitDuplicateBatch(batch);
        parts.push(...batch.response.responseParts);
        continue;
      }
      if (batch.concurrent && batch.calls.length > 1) {
        const batchAbortController = new AbortController();
        let batchStopAfterPermissionCancel = false;
        const propagateAbort = /* @__PURE__ */ __name(() => {
          batchAbortController.abort(abortSignal.reason);
        }, "propagateAbort");
        if (abortSignal.aborted) {
          propagateAbort();
        } else {
          abortSignal.addEventListener("abort", propagateAbort, {
            once: true
          });
        }
        const stopBatchAfterPermissionCancel = /* @__PURE__ */ __name(() => {
          batchStopAfterPermissionCancel = true;
          batchAbortController.abort(USER_CANCEL_ABORT_REASON);
        }, "stopBatchAfterPermissionCancel");
        let results;
        try {
          results = await runBounded(
            batch.calls,
            batchAbortController.signal,
            stopBatchAfterPermissionCancel,
            () => batchAbortController.abort("loop_detected"),
            () => batchStopAfterPermissionCancel
          );
        } finally {
          abortSignal.removeEventListener("abort", propagateAbort);
        }
        let shouldStop = false;
        let shouldStopForLoop = false;
        for (const r of results) {
          parts.push(...r.parts);
          shouldStop ||= r.stopAfterPermissionCancel;
          shouldStopForLoop ||= r.loopDetected === true;
        }
        if (shouldStopForLoop) {
          await appendSkippedAfter(
            parts,
            batch.calls[batch.calls.length - 1],
            LOOP_DETECTED_SKIP_MESSAGE
          );
          return {
            parts,
            stopAfterPermissionCancel: false,
            loopDetected: true
          };
        }
        if (shouldStop) {
          await appendSkippedAfter(parts, batch.calls[batch.calls.length - 1]);
          return {
            parts,
            stopAfterPermissionCancel: true,
            repeatedDuplicateProviderToolCall: false
          };
        }
      } else {
        for (const fc of batch.calls) {
          const r = await this.runTool(
            abortSignal,
            promptId,
            fc,
            void 0,
            toolLoopState,
            recordSkippedToolCall
          );
          parts.push(...r.parts);
          if (r.loopDetected) {
            await appendSkippedAfter(parts, fc, LOOP_DETECTED_SKIP_MESSAGE);
            return {
              parts,
              stopAfterPermissionCancel: false,
              loopDetected: true
            };
          }
          if (r.stopAfterPermissionCancel) {
            await appendSkippedAfter(parts, fc);
            return {
              parts,
              stopAfterPermissionCancel: true,
              repeatedDuplicateProviderToolCall: false
            };
          }
        }
      }
    }
    return {
      parts,
      stopAfterPermissionCancel: false,
      repeatedDuplicateProviderToolCall: false
    };
  }
  /**
   * Assemble the per-turn system reminders the model needs to see at the
   * start of a user query or cron fire. Mirrors the subagent/plan/arena
   * branches in `GeminiClient.sendMessageStream` (`client.ts:848-878`) —
   * the ACP path bypasses that code, so without this helper plan mode is
   * silently inert and subagent/arena sessions lose context.
   *
   * Scope note: the `relevantAutoMemory` reminder is intentionally NOT
   * included here. Managed auto-memory requires a prefetch pipeline that
   * lives in `GeminiClient`, and porting it into the ACP path is tracked
   * separately as part of the broader middleware-alignment work.
   */
  async #buildInitialSystemReminders() {
    const reminders = [];
    if (this.config.getApprovalMode() === "plan" /* PLAN */) {
      reminders.push({
        text: getPlanModeSystemReminder(this.config.getSdkMode?.())
      });
    }
    const arenaManager = this.config.getArenaManager?.();
    if (arenaManager) {
      try {
        const sessionDir = arenaManager.getArenaSessionDir();
        const configPath = `${sessionDir}/config.json`;
        reminders.push({ text: getArenaSystemReminder(configPath) });
      } catch {
      }
    }
    return reminders;
  }
  async runTool(abortSignal, promptId, fc, onStopAfterPermissionCancel, toolLoopState, recordSkippedToolCall) {
    const callId = fc.id ?? `${fc.name}-${Date.now()}`;
    let args = fc.args ?? {};
    if (toolLoopState?.loopDetected) {
      return {
        parts: [
          recordSkippedToolCall ? await recordSkippedToolCall(fc, LOOP_DETECTED_SKIP_MESSAGE, false) : {
            functionResponse: {
              id: callId,
              name: fc.name ?? "unknown_tool",
              response: { error: LOOP_DETECTED_SKIP_MESSAGE }
            }
          }
        ],
        stopAfterPermissionCancel: false,
        loopDetected: true
      };
    }
    const startTime = Date.now();
    let spanError;
    let activeToolAbortSignal = abortSignal;
    let nestedPermissionCancelled = false;
    let agentToolAbortController;
    let removeAgentToolAbortPropagation;
    let subAgentCleanupFunctions = [];
    const cleanupAgentToolResources = /* @__PURE__ */ __name(() => {
      subAgentCleanupFunctions.forEach((cleanup) => cleanup());
      subAgentCleanupFunctions = [];
      removeAgentToolAbortPropagation?.();
      removeAgentToolAbortPropagation = void 0;
    }, "cleanupAgentToolResources");
    const errorResponse = /* @__PURE__ */ __name((error) => {
      const durationMs = Date.now() - startTime;
      logToolCall(this.config, {
        "event.name": "tool_call",
        "event.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
        prompt_id: promptId,
        function_name: fc.name ?? "",
        function_args: args,
        duration_ms: durationMs,
        // An aborted signal means the call was cancelled, not a genuine error.
        status: activeToolAbortSignal.aborted ? "cancelled" : "error",
        success: false,
        error: error.message,
        tool_type: typeof tool !== "undefined" && tool instanceof DiscoveredMCPTool ? "mcp" : "native"
      });
      return [
        {
          functionResponse: {
            id: callId,
            name: fc.name ?? "",
            response: { error: error.message }
          }
        }
      ];
    }, "errorResponse");
    const earlyErrorResponse = /* @__PURE__ */ __name(async (error, toolName2 = fc.name ?? "unknown_tool", opts) => {
      spanError = error.message;
      cleanupAgentToolResources();
      if (toolName2 !== ToolNames.TODO_WRITE) {
        await this.toolCallEmitter.emitError(callId, toolName2, error);
      }
      const errorParts = errorResponse(error);
      this.config.getChatRecordingService()?.recordToolResult(errorParts, {
        callId,
        status: "error",
        resultDisplay: void 0,
        error,
        errorType: void 0
      });
      const loopDetected = opts?.recordInvalidToolParams === true && !activeToolAbortSignal.aborted && !opts?.stopAfterPermissionCancel && recordDaemonInvalidToolParams(
        this.config,
        promptId,
        toolLoopState,
        toolName2,
        error
      );
      return {
        parts: errorParts,
        stopAfterPermissionCancel: opts?.stopAfterPermissionCancel ?? false,
        loopDetected
      };
    }, "earlyErrorResponse");
    if (!fc.name) {
      return earlyErrorResponse(new Error("Missing function name"), void 0, {
        recordInvalidToolParams: true
      });
    }
    const toolName = fc.name;
    const toolRegistry = this.config.getToolRegistry();
    const tool = toolRegistry.getTool(toolName);
    if (!tool) {
      return earlyErrorResponse(
        new Error(`Tool "${toolName}" not found in registry.`),
        toolName,
        { recordInvalidToolParams: true }
      );
    }
    const toolSpan = startToolSpan(toolName, {
      "tool.call_id": callId,
      // Dual-emit the legacy call_id/tool_name aliases like CoreToolScheduler
      // (coreToolScheduler.ts) so pre-Phase-2 dashboards keyed off call_id keep
      // matching daemon/ACP tool spans during the migration window.
      call_id: callId,
      tool_name: toolName
    });
    let spanSuccess = false;
    try {
      return await runInToolSpanContext(toolSpan, async () => {
        const pm = this.config.getPermissionManager?.();
        if (pm && !await pm.isToolEnabled(toolName)) {
          return earlyErrorResponse(
            new Error(`Tool "${toolName}" is disabled.`),
            toolName
          );
        }
        const isTodoWriteTool = tool.name === ToolNames.TODO_WRITE;
        const isAgentTool = tool.name === ToolNames.AGENT;
        const isExitPlanModeTool = tool.name === ToolNames.EXIT_PLAN_MODE;
        const isEnterPlanModeTool = tool.name === ToolNames.ENTER_PLAN_MODE;
        if (isAgentTool) {
          agentToolAbortController = new AbortController();
          activeToolAbortSignal = agentToolAbortController.signal;
          const propagateAbort = /* @__PURE__ */ __name(() => {
            agentToolAbortController?.abort(abortSignal.reason);
          }, "propagateAbort");
          if (abortSignal.aborted) {
            propagateAbort();
          } else {
            abortSignal.addEventListener("abort", propagateAbort, {
              once: true
            });
            removeAgentToolAbortPropagation = /* @__PURE__ */ __name(() => {
              abortSignal.removeEventListener("abort", propagateAbort);
            }, "removeAgentToolAbortPropagation");
          }
        }
        const toolUseId = generateToolUseId();
        const approvalMode = this.config.getApprovalMode();
        let toolBuildSucceeded = false;
        try {
          const invocation = tool.build(args);
          toolBuildSucceeded = true;
          const taskEventEmitter = invocation.eventEmitter;
          if (isAgentTool && taskEventEmitter) {
            const parentToolCallId = callId;
            const subagentType = args["subagent_type"] ?? "";
            const subSubAgentTracker = new SubAgentTracker(
              this,
              this.client,
              parentToolCallId,
              subagentType,
              () => {
                nestedPermissionCancelled = true;
                agentToolAbortController?.abort(USER_CANCEL_ABORT_REASON);
                onStopAfterPermissionCancel?.();
              }
            );
            subAgentCleanupFunctions = subSubAgentTracker.setup(
              taskEventEmitter,
              activeToolAbortSignal
            );
          }
          const isAskUserQuestionTool = toolName === ToolNames.ASK_USER_QUESTION;
          const toolParams = invocation.params;
          const flowResult = await evaluatePermissionFlow(
            this.config,
            invocation,
            toolName,
            toolParams
          );
          const { finalPermission, pmForcedAsk, pmCtx, denyMessage } = flowResult;
          const isPlanMode = approvalMode === "plan" /* PLAN */;
          if (finalPermission === "deny") {
            return earlyErrorResponse(
              new Error(denyMessage ?? `Tool "${toolName}" is denied.`),
              toolName
            );
          }
          const forceAutoReviewForAllow = approvalMode === "auto" /* AUTO */ && (shouldForceAutoModeReviewForAllow(pmCtx, this.config.getCwd()) || shouldClassifyAllShellForAutoMode(toolName, this.config));
          const confirmationPermission = getEffectivePermissionForConfirmation(
            finalPermission,
            forceAutoReviewForAllow
          );
          if (finalPermission === "allow" && forceAutoReviewForAllow) {
            debugLogger5.info(
              `Auto mode: L4 allow overridden by protected-write guard for ${toolName}`
            );
          }
          let autoModeAllowed = finalPermission === "allow" && !forceAutoReviewForAllow;
          if (autoModeAllowed && approvalMode === "auto" /* AUTO */) {
            this.config.setAutoModeDenialState(
              recordAllow(this.config.getAutoModeDenialState())
            );
          }
          let wasAutoModeDenialFallback = false;
          if (!autoModeAllowed && shouldRunAutoModeForCall(approvalMode, toolName)) {
            const denialState = this.config.getAutoModeDenialState();
            const fallback = shouldFallback(denialState);
            const messages = this.config.getGeminiClient?.()?.getHistoryTail(MAX_TRANSCRIPT_MESSAGES, false) ?? [];
            const decision = await evaluateAutoMode({
              ctx: pmCtx,
              pmForcedAsk,
              toolParams,
              messages,
              config: this.config,
              signal: abortSignal,
              skipClassifierReason: fallback.fallback ? fallback.reason : void 0
            });
            const outcome = applyAutoModeDecision(
              decision,
              this.config,
              denialState
            );
            await fireSessionPermissionDeniedForAutoMode(
              this.config,
              decision,
              outcome,
              toolName,
              toolParams,
              callId,
              abortSignal
            );
            switch (outcome.kind) {
              case "approved":
                autoModeAllowed = true;
                break;
              case "blocked":
                debugLogger5.warn(
                  `Auto mode blocked (${outcome.reason}): tool=${toolName}, ` + formatDenialStateLog(denialState)
                );
                return earlyErrorResponse(
                  new Error(outcome.errorMessage),
                  toolName
                );
              case "fallback":
                wasAutoModeDenialFallback = isDenialFallbackReason(
                  outcome.reason
                );
                if (wasAutoModeDenialFallback) {
                  debugLogger5.warn(
                    `Auto mode fallback to manual approval (${outcome.reason}): ` + formatDenialStateLog(denialState)
                  );
                }
                break;
              default: {
                const _exhaustive = outcome;
                void _exhaustive;
              }
            }
          }
          let didRequestPermission = false;
          let confirmationDetails;
          const recordAutoModeFallbackResolution = /* @__PURE__ */ __name((outcome) => {
            if (approvalMode === "auto" /* AUTO */ && wasAutoModeDenialFallback && isApproveOutcome(outcome)) {
              const before = this.config.getAutoModeDenialState();
              const after = recordFallbackApprove(before);
              if (after === before) {
                debugLogger5.warn(
                  `Auto mode denial counters already clear after fallback approval: ` + formatDenialStateLog(before)
                );
                return;
              }
              debugLogger5.warn(
                `Auto mode denial counters reset after fallback approval: ${formatDenialStateLog(before)} -> ${formatDenialStateLog(after)}`
              );
              this.config.setAutoModeDenialState(after);
            }
          }, "recordAutoModeFallbackResolution");
          if (!autoModeAllowed && needsConfirmation(confirmationPermission, approvalMode, toolName)) {
            confirmationDetails = await invocation.getConfirmationDetails(abortSignal);
            injectPermissionRulesIfMissing(confirmationDetails, pmCtx);
            if (isPlanModeBlocked(
              isPlanMode,
              isExitPlanModeTool,
              isAskUserQuestionTool,
              confirmationDetails,
              isEnterPlanModeTool
            )) {
              return earlyErrorResponse(
                new Error(
                  `Plan mode is active. The tool "${toolName}" cannot be executed because it modifies the system. Please use the exit_plan_mode tool to present your plan and exit plan mode before making changes.`
                ),
                toolName
              );
            }
            const messageBus = this.config.getMessageBus?.();
            const hooksEnabled = !this.config.getDisableAllHooks?.();
            let hookHandled = false;
            if (hooksEnabled && messageBus) {
              const hookResult = await firePermissionRequestHook(
                messageBus,
                toolName,
                args,
                String(approvalMode)
              );
              if (hookResult.hasDecision) {
                hookHandled = true;
                if (hookResult.shouldAllow) {
                  if (hookResult.updatedInput) {
                    args = hookResult.updatedInput;
                    invocation.params = hookResult.updatedInput;
                  }
                  await confirmationDetails.onConfirm(
                    "proceed_once" /* ProceedOnce */
                  );
                  recordAutoModeFallbackResolution(
                    "proceed_once" /* ProceedOnce */
                  );
                } else {
                  return earlyErrorResponse(
                    new Error(
                      hookResult.denyMessage || `Permission denied by hook for "${toolName}"`
                    ),
                    toolName
                  );
                }
              }
            }
            if (approvalMode === "auto-edit" /* AUTO_EDIT */ && (confirmationDetails.type === "edit" || confirmationDetails.type === "info")) {
            } else if (!hookHandled) {
              didRequestPermission = true;
              const content = buildPermissionRequestContent(confirmationDetails);
              const mappedKind = this.toolCallEmitter.mapToolKind(
                tool.kind,
                toolName
              );
              if (hooksEnabled && messageBus) {
                this.fireNotificationHookWithTerminalSequence(
                  messageBus,
                  `Qwen Code needs your permission to use ${toolName}`,
                  "permission_prompt" /* PermissionPrompt */,
                  "Permission needed"
                );
              }
              const params = {
                sessionId: this.sessionId,
                options: toPermissionOptions(confirmationDetails, pmForcedAsk),
                toolCall: {
                  toolCallId: callId,
                  status: "pending",
                  title: invocation.getDescription(),
                  content,
                  locations: invocation.toolLocations(),
                  kind: mappedKind,
                  rawInput: args,
                  // Carry the tool name so consumers can give specific tools
                  // (e.g. the Agent tool) dedicated permission UI without
                  // relying on a protocol `kind` ACP can't carry. The tool_call
                  // frame already ships _meta.toolName; mirror it here.
                  _meta: { toolName }
                }
              };
              const stopAfterPermissionCancel = /* @__PURE__ */ __name(() => {
                onStopAfterPermissionCancel?.();
                return earlyErrorResponse(
                  new Error(`Tool "${toolName}" was canceled by the user.`),
                  toolName,
                  { stopAfterPermissionCancel: true }
                );
              }, "stopAfterPermissionCancel");
              let output;
              let outcome;
              try {
                output = await this.client.requestPermission(
                  params
                );
                outcome = output.outcome.outcome === "cancelled" ? "cancel" /* Cancel */ : external_exports.nativeEnum(ToolConfirmationOutcome).parse(output.outcome.optionId);
              } catch (error) {
                debugLogger5.error(
                  `Permission request failed for tool ${toolName}:`,
                  error
                );
                try {
                  await confirmationDetails.onConfirm(
                    "cancel" /* Cancel */
                  );
                } catch (confirmError) {
                  debugLogger5.error(
                    `Failed to cancel tool ${toolName} after permission request failure:`,
                    confirmError
                  );
                }
                onStopAfterPermissionCancel?.();
                return earlyErrorResponse(
                  new Error(
                    `Permission request failed for "${toolName}": ${this.#formatError(
                      error
                    )}`
                  ),
                  toolName,
                  { stopAfterPermissionCancel: true }
                );
              }
              recordAutoModeFallbackResolution(outcome);
              try {
                await confirmationDetails.onConfirm(outcome, {
                  answers: output.answers
                });
              } catch (error) {
                if (outcome !== "cancel" /* Cancel */) {
                  throw error;
                }
                debugLogger5.error(
                  `Failed to confirm cancellation for tool ${toolName}:`,
                  error
                );
                return stopAfterPermissionCancel();
              }
              if (outcome === "proceed_always" /* ProceedAlways */ || outcome === "proceed_always_project" /* ProceedAlwaysProject */ || outcome === "proceed_always_user" /* ProceedAlwaysUser */) {
                await persistPermissionOutcome(
                  outcome,
                  confirmationDetails,
                  this.config.getOnPersistPermissionRule?.(),
                  this.config.getPermissionManager?.(),
                  { answers: output.answers }
                );
              }
              if (isExitPlanModeTool && outcome !== "cancel" /* Cancel */) {
                await this.sendCurrentModeUpdateNotification(outcome);
              }
              if (confirmationDetails.type === "edit" && outcome === "proceed_always" /* ProceedAlways */) {
                await this.sendCurrentModeUpdateNotification(outcome);
              }
              switch (outcome) {
                case "cancel" /* Cancel */:
                  return stopAfterPermissionCancel();
                case "proceed_once" /* ProceedOnce */:
                case "proceed_always" /* ProceedAlways */:
                case "proceed_always_project" /* ProceedAlwaysProject */:
                case "proceed_always_user" /* ProceedAlwaysUser */:
                case "proceed_always_server" /* ProceedAlwaysServer */:
                case "proceed_always_tool" /* ProceedAlwaysTool */:
                case "modify_with_editor" /* ModifyWithEditor */:
                case "restore_previous" /* RestorePrevious */:
                  break;
                default: {
                  const resultOutcome = outcome;
                  throw new Error(`Unexpected: ${resultOutcome}`);
                }
              }
            }
          }
          if (!didRequestPermission && !isTodoWriteTool) {
            const startParams = {
              callId,
              toolName,
              args,
              status: "in_progress"
            };
            await this.toolCallEmitter.emitStart(startParams);
          }
          const hooksEnabledForTool = !this.config.getDisableAllHooks?.();
          const messageBusForTool = this.config.getMessageBus?.();
          const permissionMode = String(approvalMode);
          if (hooksEnabledForTool && messageBusForTool) {
            const preHookResult = await firePreToolUseHook(
              messageBusForTool,
              toolName,
              args,
              toolUseId,
              permissionMode,
              activeToolAbortSignal,
              callId
            );
            if (!preHookResult.shouldProceed) {
              const blockReason = preHookResult.blockReason || "Blocked by PreToolUse hook";
              await this.messageEmitter.emitAgentMessage(
                `\u2717 **PreToolUse blocked**: ${toolName} - ${blockReason}`
              );
              return earlyErrorResponse(new Error(blockReason), toolName);
            }
            if (preHookResult.additionalContext) {
              debugLogger5.debug(
                `PreToolUse hook additional context for ${toolName}: ${preHookResult.additionalContext}`
              );
            }
          }
          const execSpan = startToolExecutionSpan();
          let toolResult;
          try {
            const sleepInhibitorHandle = acquireSleepInhibitor(
              this.config,
              `Qwen Code is executing tool ${toolName}`
            );
            try {
              toolResult = await invocation.execute(activeToolAbortSignal);
            } finally {
              sleepInhibitorHandle.release();
            }
            const aborted2 = activeToolAbortSignal.aborted;
            endToolExecutionSpan(execSpan, {
              success: !toolResult.error && !aborted2,
              error: aborted2 ? "tool_cancelled" : toolResult.error ? "tool_error" : void 0,
              cancelled: aborted2
            });
          } catch (execError) {
            endToolExecutionSpan(execSpan, {
              success: false,
              error: activeToolAbortSignal.aborted ? "tool_cancelled" : "tool_exception",
              cancelled: activeToolAbortSignal.aborted
            });
            throw execError;
          }
          cleanupAgentToolResources();
          if ((isEnterPlanModeTool || isExitPlanModeTool) && !didRequestPermission && !toolResult.error && this.config.getApprovalMode() !== approvalMode) {
            await this.sendUpdate({
              sessionUpdate: "current_mode_update",
              currentModeId: this.config.getApprovalMode()
            });
          }
          const responseParts = convertToFunctionResponse(
            toolName,
            callId,
            toolResult.llmContent
          );
          const aborted = activeToolAbortSignal.aborted;
          const status = aborted ? "cancelled" : toolResult.error ? "error" : "success";
          const succeeded = status === "success";
          const responseError = toolResult.error ? new Error(toolResult.error.message) : aborted ? new Error("Tool execution was cancelled") : void 0;
          if (hooksEnabledForTool && messageBusForTool && !toolResult.error && !aborted && !nestedPermissionCancelled) {
            const toolResponse = {
              llmContent: toolResult.llmContent,
              returnDisplay: toolResult.returnDisplay
            };
            const postHookResult = await firePostToolUseHook(
              messageBusForTool,
              toolName,
              args,
              toolResponse,
              toolUseId,
              permissionMode,
              activeToolAbortSignal,
              callId
            );
            if (postHookResult.shouldStop) {
              const stopMessage = postHookResult.stopReason || "Execution stopped by PostToolUse hook";
              debugLogger5.info(
                `PostToolUse hook requested stop for ${toolName}: ${stopMessage}`
              );
              return earlyErrorResponse(new Error(stopMessage), toolName);
            }
            if (postHookResult.additionalContext) {
              const contextPart = { text: postHookResult.additionalContext };
              responseParts.push(contextPart);
            }
            await this.emitHookArtifactsNotification({
              hookEventName: "PostToolUse",
              toolName,
              toolCallId: callId,
              artifacts: postHookResult.artifacts
            });
          } else if (hooksEnabledForTool && messageBusForTool && (toolResult.error || aborted)) {
            const isInterrupt = aborted;
            const failureHookResult = await firePostToolUseFailureHook(
              messageBusForTool,
              toolUseId,
              toolName,
              args,
              toolResult.error?.message ?? "Tool execution was cancelled",
              isInterrupt,
              permissionMode,
              activeToolAbortSignal,
              callId
            );
            if (failureHookResult.additionalContext) {
              debugLogger5.debug(
                `PostToolUseFailure hook additional context for ${toolName}: ${failureHookResult.additionalContext}`
              );
            }
            await this.emitHookArtifactsNotification({
              hookEventName: "PostToolUseFailure",
              toolName,
              toolCallId: callId,
              artifacts: failureHookResult.artifacts
            });
          }
          if (isTodoWriteTool) {
            const todos = this.planEmitter.extractTodos(
              toolResult.returnDisplay,
              args
            );
            if (todos && todos.length > 0 || Array.isArray(args["todos"])) {
              await this.planEmitter.emitPlan(todos ?? []);
            }
          } else {
            await this.toolCallEmitter.emitResult({
              callId,
              toolName,
              args,
              message: responseParts,
              resultDisplay: toolResult.returnDisplay,
              error: responseError,
              success: succeeded,
              artifacts: toolResult.artifacts
            });
          }
          const durationMs = Date.now() - startTime;
          logToolCall(this.config, {
            "event.name": "tool_call",
            "event.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
            function_name: toolName,
            function_args: args,
            duration_ms: durationMs,
            status,
            success: succeeded,
            error: toolResult.error?.message,
            error_type: toolResult.error?.type,
            prompt_id: promptId,
            tool_type: typeof tool !== "undefined" && tool instanceof DiscoveredMCPTool ? "mcp" : "native"
          });
          this.config.getChatRecordingService()?.recordToolResult(responseParts, {
            callId,
            status,
            resultDisplay: toolResult.returnDisplay,
            error: toolResult.error ? new Error(toolResult.error.message) : void 0,
            errorType: toolResult.error?.type
          });
          spanSuccess = succeeded;
          if (toolResult.error) {
            spanError = toolResult.error.message;
          } else if (aborted) {
            spanError = "Tool execution was cancelled";
          }
          return {
            parts: responseParts,
            stopAfterPermissionCancel: nestedPermissionCancelled
          };
        } catch (e) {
          cleanupAgentToolResources();
          const error = e instanceof Error ? e : new Error(String(e));
          spanError = error.message;
          const hooksEnabledForError = !this.config.getDisableAllHooks?.();
          const messageBusForError = this.config.getMessageBus?.();
          const isInterrupt = activeToolAbortSignal.aborted;
          if (hooksEnabledForError && messageBusForError) {
            const failureHookResult = await firePostToolUseFailureHook(
              messageBusForError,
              toolUseId,
              toolName,
              args,
              error.message,
              isInterrupt,
              String(approvalMode),
              activeToolAbortSignal,
              callId
            );
            if (failureHookResult.additionalContext) {
              debugLogger5.debug(
                `PostToolUseFailure hook additional context for ${toolName}: ${failureHookResult.additionalContext}`
              );
            }
            await this.emitHookArtifactsNotification({
              hookEventName: "PostToolUseFailure",
              toolName,
              toolCallId: callId,
              artifacts: failureHookResult.artifacts
            });
          }
          await this.toolCallEmitter.emitError(callId, toolName, error);
          const errorParts = [
            {
              functionResponse: {
                id: callId,
                name: toolName,
                response: { error: error.message }
              }
            }
          ];
          this.config.getChatRecordingService()?.recordToolResult(errorParts, {
            callId,
            // A throw caused by abort (e.g. AbortError) is a cancellation, not
            // a genuine tool error — keep it consistent with the success path.
            status: activeToolAbortSignal.aborted ? "cancelled" : "error",
            resultDisplay: void 0,
            error,
            errorType: void 0
          });
          const loopDetected = !activeToolAbortSignal.aborted && !toolBuildSucceeded && recordDaemonInvalidToolParams(
            this.config,
            promptId,
            toolLoopState,
            toolName,
            error
          );
          const responseParts = errorResponse(error);
          return {
            parts: responseParts,
            stopAfterPermissionCancel: nestedPermissionCancelled,
            loopDetected
          };
        }
      });
    } finally {
      endToolSpan(toolSpan, { success: spanSuccess, error: spanError });
    }
  }
  #emitGoalStatusItems(result) {
    if (!("outputHistoryItems" in result)) {
      return;
    }
    let hasActiveGoalStatus = false;
    for (const item of result.outputHistoryItems ?? []) {
      if (item.type === "goal_status" /* GOAL_STATUS */) {
        this.emitGoalStatus({
          kind: item.kind,
          condition: item.condition,
          ...item.iterations !== void 0 ? { iterations: item.iterations } : {},
          ...item.setAt !== void 0 ? { setAt: item.setAt } : {},
          ...item.durationMs !== void 0 ? { durationMs: item.durationMs } : {},
          ...item.lastReason !== void 0 ? { lastReason: item.lastReason } : {}
        });
        if (!isTerminalGoalStatusKind(item.kind)) {
          hasActiveGoalStatus = true;
        }
      }
    }
    if (hasActiveGoalStatus) {
      this.#installGoalTerminalObserver();
    }
  }
  /**
   * Processes the result of a slash command execution.
   *
   * Supported result types in ACP mode:
   * - submit_prompt: Submits content to the model
   * - stream_messages: Streams multiple messages to the client (ACP-specific)
   * - unsupported: Command cannot be executed in ACP mode
   * - no_command: No command was found, use original prompt
   *
   * Note: 'message' type is not supported in ACP mode - commands should use
   * 'stream_messages' instead for consistent async handling.
   *
   * @param result The result from handleSlashCommand
   * @param originalPrompt The original prompt blocks
   * @returns Parts to use for the prompt, or null if command was handled without needing model interaction
   */
  async #processSlashCommandResult(result, originalPrompt) {
    this.#emitGoalStatusItems(result);
    switch (result.type) {
      case "submit_prompt":
        return normalizePartList(result.content);
      case "message": {
        if (result.messageType === "error") {
          throw new Error(result.content || "Slash command failed.");
        }
        const rendered = (result.content || "").replace(/\n/g, "  \n");
        await this.messageEmitter.emitAgentMessage(rendered);
        this.config.getChatRecordingService()?.recordSlashCommand({
          phase: "result",
          rawCommand: originalPrompt.filter((b) => b.type === "text").map((b) => b.type === "text" ? b.text : "").join(" "),
          outputHistoryItems: [
            { type: "assistant", text: result.content || "" }
          ]
        });
        return null;
      }
      case "stream_messages": {
        const chunks = [];
        for await (const msg of result.messages) {
          if (msg.messageType === "error") {
            throw new Error(msg.content || "Slash command failed.");
          }
          await this.messageEmitter.emitAgentMessage(
            (msg.content || "").replace(/\n/g, "  \n")
          );
          chunks.push(msg.content || "");
        }
        if (chunks.length > 0) {
          this.config.getChatRecordingService()?.recordSlashCommand({
            phase: "result",
            rawCommand: originalPrompt.filter((b) => b.type === "text").map((b) => b.type === "text" ? b.text : "").join(" "),
            outputHistoryItems: [
              { type: "assistant", text: chunks.join("\n") }
            ]
          });
        }
        return null;
      }
      case "unsupported": {
        const unsupportedError = `Slash command not supported in ACP integration: ${result.reason}`;
        throw new Error(unsupportedError);
      }
      case "no_command":
        return this.#resolvePrompt(
          originalPrompt,
          new AbortController().signal
        );
      default: {
        const _exhaustive = result;
        const unknownError = `Unknown slash command result type: ${_exhaustive.type}`;
        throw new Error(unknownError);
      }
    }
  }
  async #resolvePrompt(message, abortSignal) {
    const FILE_URI_SCHEME = "file://";
    const embeddedContext = [];
    const extensionMentions = /* @__PURE__ */ new Map();
    const preserveUnsupportedImageForBridge = shouldRunVisionBridge(
      this.config
    );
    const parts = message.map((part) => {
      switch (part.type) {
        case "text":
          collectExtensionMentionRefs(part.text, extensionMentions);
          return { text: part.text };
        case "image":
          if (preserveUnsupportedImageForBridge) {
            return {
              inlineData: {
                mimeType: part.mimeType,
                data: part.data
              }
            };
          }
          return clampInlineMediaPart({
            inlineData: {
              mimeType: part.mimeType,
              data: part.data
            }
          });
        case "audio":
          return clampInlineMediaPart({
            inlineData: {
              mimeType: part.mimeType,
              data: part.data
            }
          });
        case "resource_link": {
          if (part.uri.startsWith(FILE_URI_SCHEME)) {
            return {
              fileData: {
                mimeData: part.mimeType,
                name: part.name,
                fileUri: part.uri.slice(FILE_URI_SCHEME.length)
              }
            };
          } else {
            return { text: `@${part.uri}` };
          }
        }
        case "resource": {
          embeddedContext.push(part.resource);
          return { text: `@${part.resource.uri}` };
        }
        default: {
          const unreachable = part;
          throw new Error(`Unexpected chunk type: '${unreachable}'`);
        }
      }
    });
    const atPathCommandParts = parts.filter((part) => "fileData" in part);
    const extensionParts = await this.#resolveExtensionMentionParts(
      extensionMentions,
      abortSignal
    );
    if (atPathCommandParts.length === 0 && embeddedContext.length === 0 && extensionParts.length === 0) {
      return this.#applyVisionBridgeIfNeeded(parts, abortSignal);
    }
    if (atPathCommandParts.length === 0 && embeddedContext.length === 0) {
      return this.#applyVisionBridgeIfNeeded(
        [...parts, ...extensionParts],
        abortSignal
      );
    }
    const pathSpecsToRead = atPathCommandParts.map(
      (part) => part.fileData.fileUri
    );
    let initialQueryText = "";
    for (let i = 0; i < parts.length; i++) {
      const chunk = parts[i];
      if ("text" in chunk) {
        initialQueryText += chunk.text;
      } else if ("fileData" in chunk) {
        const pathName = chunk.fileData.fileUri;
        if (i > 0 && initialQueryText.length > 0 && !initialQueryText.endsWith(" ")) {
          initialQueryText += " ";
        }
        initialQueryText += `@${pathName}`;
      }
    }
    const processedQueryParts = [];
    if (pathSpecsToRead.length > 0) {
      const readResult = await readManyFiles(this.config, {
        paths: pathSpecsToRead,
        signal: abortSignal,
        ...preserveUnsupportedImageForBridge ? { preserveUnsupportedImageForBridge } : {}
      });
      const contentParts = Array.isArray(readResult.contentParts) ? readResult.contentParts : [readResult.contentParts];
      processedQueryParts.push({ text: initialQueryText });
      processedQueryParts.push(...extensionParts);
      for (const part of contentParts) {
        if (typeof part === "string") {
          processedQueryParts.push({ text: part });
        } else if (preserveUnsupportedImageForBridge && hasImageParts([part])) {
          processedQueryParts.push(part);
        } else {
          processedQueryParts.push(clampInlineMediaPart(part));
        }
      }
    } else {
      processedQueryParts.push({ text: initialQueryText.trim() });
      processedQueryParts.push(...extensionParts);
    }
    for (const contextPart of embeddedContext) {
      if ("text" in contextPart && contextPart.text) {
        processedQueryParts.push({
          text: `File: ${contextPart.uri}
${contextPart.text}`
        });
      }
      if ("blob" in contextPart && contextPart.blob) {
        const inlinePart = {
          inlineData: {
            mimeType: contextPart.mimeType ?? "application/octet-stream",
            data: contextPart.blob
          }
        };
        processedQueryParts.push(
          preserveUnsupportedImageForBridge && hasImageParts([inlinePart]) ? inlinePart : clampInlineMediaPart(inlinePart)
        );
      }
    }
    return this.#applyVisionBridgeIfNeeded(processedQueryParts, abortSignal);
  }
  async #applyVisionBridgeIfNeeded(parts, abortSignal) {
    if (!hasImageParts(parts) || !shouldRunVisionBridge(this.config)) {
      return parts;
    }
    let bridgeResult;
    try {
      debugLogger5.debug("vision bridge: gate matched, running conversion");
      bridgeResult = await runVisionBridge({
        config: this.config,
        parts,
        signal: abortSignal
      });
    } catch (error) {
      debugLogger5.debug(
        `vision bridge: failed before replacement; falling back to text-only parts error=${String(error instanceof Error ? error.message : error)}`
      );
      return splitImageParts(parts).nonImageParts;
    }
    debugLogger5.debug(
      `vision bridge: status=${bridgeResult.status} applied=${bridgeResult.applied} model=${bridgeResult.modelId ?? "(none)"}${bridgeResult.error ? ` error=${bridgeResult.error}` : ""}`
    );
    if (bridgeResult.status !== "skipped" || bridgeResult.egressOccurred) {
      try {
        await this.messageEmitter.emitAgentMessage(
          this.#formatVisionBridgeNotice(bridgeResult)
        );
      } catch (error) {
        debugLogger5.debug(
          `vision bridge: failed to emit notice; continuing with bridge result error=${String(error instanceof Error ? error.message : error)}`
        );
      }
    }
    if (abortSignal.aborted) {
      debugLogger5.debug("vision bridge: turn aborted after bridge returned");
      return splitImageParts(parts).nonImageParts;
    }
    if (bridgeResult.applied && bridgeResult.parts != null) {
      return normalizeParts(bridgeResult.parts);
    }
    return splitImageParts(parts).nonImageParts;
  }
  #formatVisionBridgeNotice(result) {
    const modelName = result.modelId ?? "vision model";
    const target = result.modelEndpoint ? `${modelName} (${result.modelEndpoint})` : modelName;
    const egressNote = result.egressOccurred ? ` Your image and prompt/context were sent to ${target}.` : "";
    if (result.status === "failed") {
      const reason = result.egressOccurred ? "the vision model request failed" : "the vision bridge could not run";
      return `Vision bridge (${modelName}) failed: ${reason}.${egressNote} The image was not interpreted.`;
    }
    if (result.status === "skipped") {
      return `Vision bridge cancelled.${egressNote}`;
    }
    const omitted = result.omittedCount > 0 ? ` (${result.omittedCount} image(s) omitted)` : "";
    return `Converted ${result.convertedCount} image(s)${omitted} to text via ${target}. Your image and prompt/context were sent to that model.`;
  }
  async #resolveExtensionMentionParts(extensionMentions, abortSignal) {
    if (extensionMentions.size === 0) return [];
    const activeExtensions = this.config.getActiveExtensions?.() ?? [];
    if (activeExtensions.length === 0) return [];
    const extensionParts = [];
    const resolvedExtensionNames = /* @__PURE__ */ new Set();
    let remainingBudget = EXTENSION_CONTEXT_BUDGET;
    for (const name of extensionMentions.values()) {
      const extension = matchExtensionByRef(name, activeExtensions);
      if (!extension) {
        this.debug(
          `Extension "${name}" not found among active extensions. Available: ${activeExtensions.map((e) => e.name).join(", ") || "(none)"}`
        );
        continue;
      }
      if (resolvedExtensionNames.has(extension.name)) continue;
      resolvedExtensionNames.add(extension.name);
      const context = await buildExtensionMentionContext(extension, {
        remainingBudget,
        signal: abortSignal,
        onDebugMessage: /* @__PURE__ */ __name((message) => this.debug(message), "onDebugMessage")
      });
      remainingBudget = context.remainingBudget;
      extensionParts.push({ text: context.text });
    }
    return extensionParts;
  }
  debug(msg) {
    if (this.config.getDebugMode()) {
      debugLogger5.warn(msg);
    }
  }
  async emitHookArtifactsNotification(args) {
    if (!args.artifacts || args.artifacts.length === 0) {
      return;
    }
    try {
      await this.client.extNotification("qwen/notify/session/artifact-event", {
        v: 1,
        sessionId: this.sessionId,
        source: "hook",
        hookEventName: args.hookEventName,
        toolName: args.toolName,
        toolCallId: args.toolCallId,
        artifacts: args.artifacts
      });
    } catch (error) {
      writeStderrLine(
        `Hook artifact notification dropped for ${args.toolName ?? args.hookEventName}: ${error instanceof Error ? error.message : String(error)}`
      );
    }
  }
  /**
   * Fire a notification hook and forward any terminalSequence to the ACP
   * client as an extNotification. Fire-and-forget — errors are logged at
   * debug level.
   */
  fireNotificationHookWithTerminalSequence(messageBus, message, notificationType, title) {
    void fireNotificationHook(messageBus, message, notificationType, title).then((hookResult) => {
      if (!hookResult.terminalSequence) return;
      return this.client.extNotification(
        "qwen/notify/session/terminal-sequence",
        {
          v: 1,
          sessionId: this.sessionId,
          terminalSequence: hookResult.terminalSequence
        }
      );
    }).catch((err) => {
      debugLogger5.debug(
        `ACP terminalSequence notification dropped (session=${this.sessionId}): ${err instanceof Error ? err.message : String(err)}`
      );
    });
  }
};

// packages/cli/src/acp-integration/session/tasksSnapshot.ts
init_esbuild_shims();
function runtimeMs(entry, now) {
  return Math.max(0, (entry.endTime ?? now) - entry.startTime);
}
__name(runtimeMs, "runtimeMs");
function optionalField(key, value) {
  return value !== void 0 ? { [key]: value } : {};
}
__name(optionalField, "optionalField");
function serializeAgentTask(entry, now) {
  return {
    kind: "agent",
    id: entry.id,
    label: buildBackgroundEntryLabel(entry),
    description: entry.description,
    status: entry.status,
    startTime: entry.startTime,
    runtimeMs: runtimeMs(entry, now),
    outputFile: entry.outputFile,
    ...optionalField("endTime", entry.endTime),
    ...optionalField("subagentType", entry.subagentType),
    isBackgrounded: entry.isBackgrounded,
    // Nested-agent lineage for client-side tree rendering. AgentTask uses
    // `null` parentAgentId for top-level launches — normalize to absent.
    ...optionalField("parentAgentId", entry.parentAgentId ?? void 0),
    ...optionalField("parentName", entry.parentName),
    ...optionalField("depth", entry.depth),
    ...optionalField("error", entry.error),
    ...optionalField("resumeBlockedReason", entry.resumeBlockedReason),
    ...optionalField("stats", entry.stats),
    ...entry.recentActivities && entry.recentActivities.length > 0 ? {
      recentActivities: entry.recentActivities.map((a) => ({
        name: a.name,
        description: a.description,
        at: a.at
      }))
    } : {},
    ...optionalField("prompt", entry.prompt)
  };
}
__name(serializeAgentTask, "serializeAgentTask");
function serializeShellTask(entry, now) {
  return {
    kind: "shell",
    id: entry.id,
    label: entry.command,
    description: entry.description,
    status: entry.status,
    startTime: entry.startTime,
    runtimeMs: runtimeMs(entry, now),
    outputFile: entry.outputFile,
    command: entry.command,
    cwd: entry.cwd,
    ...optionalField("endTime", entry.endTime),
    ...optionalField("pid", entry.pid),
    ...optionalField("exitCode", entry.exitCode),
    ...optionalField("error", entry.error)
  };
}
__name(serializeShellTask, "serializeShellTask");
function serializeMonitorTask(entry, now) {
  return {
    kind: "monitor",
    id: entry.id,
    label: entry.description,
    description: entry.description,
    status: entry.status,
    startTime: entry.startTime,
    runtimeMs: runtimeMs(entry, now),
    command: entry.command,
    eventCount: entry.eventCount,
    lastEventTime: entry.lastEventTime,
    droppedLines: entry.droppedLines,
    ...optionalField("endTime", entry.endTime),
    ...optionalField("pid", entry.pid),
    ...optionalField("exitCode", entry.exitCode),
    ...optionalField("error", entry.error),
    ...optionalField("ownerAgentId", entry.ownerAgentId)
  };
}
__name(serializeMonitorTask, "serializeMonitorTask");
function buildSessionTasksStatus(sessionId, config, now = Date.now()) {
  const tasks = [
    ...config.getBackgroundTaskRegistry().getAll().map((entry) => serializeAgentTask(entry, now)),
    ...config.getBackgroundShellRegistry().getAll().map((entry) => serializeShellTask(entry, now)),
    ...config.getMonitorRegistry().getAll().map((entry) => serializeMonitorTask(entry, now))
  ].sort((a, b) => a.startTime - b.startTime);
  return {
    v: STATUS_SCHEMA_VERSION,
    sessionId,
    now,
    tasks
  };
}
__name(buildSessionTasksStatus, "buildSessionTasksStatus");

// packages/cli/src/acp-integration/runtimeOutputDirContext.ts
init_esbuild_shims();
function runWithAcpRuntimeOutputDir(settings, cwd, fn) {
  return Storage.runWithRuntimeBaseDir(
    settings.merged.advanced?.runtimeOutputDir,
    cwd,
    fn
  );
}
__name(runWithAcpRuntimeOutputDir, "runWithAcpRuntimeOutputDir");

// packages/cli/src/acp-integration/acpAgent.ts
var debugLogger6 = createDebugLogger("ACP_AGENT");
var BTW_CHILD_TIMEOUT_MS = 55e3;
var WORKSPACE_MEMORY_REMEMBER_CHILD_TIMEOUT_MS = 295e3;
function createHiddenWorkspaceMemoryConfig(config) {
  return new Proxy(config, {
    get(target, prop) {
      if (prop === "getChatRecordingService") {
        return () => void 0;
      }
      if (prop === "getTranscriptPath") {
        return () => "";
      }
      const value = Reflect.get(target, prop, target);
      return typeof value === "function" ? value.bind(target) : value;
    }
  });
}
__name(createHiddenWorkspaceMemoryConfig, "createHiddenWorkspaceMemoryConfig");
function collapseForkDirective(directive, maxLength) {
  const oneLine = directive.replace(/\s+/g, " ").trim();
  return oneLine.length > maxLength ? `${oneLine.slice(0, maxLength - 3)}\u2026` : oneLine;
}
__name(collapseForkDirective, "collapseForkDirective");
function deriveForkDescription(directive) {
  return collapseForkDirective(directive, 60);
}
__name(deriveForkDescription, "deriveForkDescription");
function truncateForkDirectiveForHistory(directive) {
  return collapseForkDirective(directive, 200);
}
__name(truncateForkDirectiveForHistory, "truncateForkDirectiveForHistory");
function hasFailedDisplayStatus(display) {
  return display !== null && typeof display === "object" && "status" in display && display.status === "failed";
}
__name(hasFailedDisplayStatus, "hasFailedDisplayStatus");
var AUTH_PREFLIGHT_ENV_KEYS = {
  openai: ["OPENAI_API_KEY"],
  anthropic: ["ANTHROPIC_API_KEY"],
  gemini: ["GEMINI_API_KEY"],
  "vertex-ai": ["GOOGLE_API_KEY"]
};
var AUTH_PREFLIGHT_WAIVED_AUTH_TYPES = /* @__PURE__ */ new Set([
  "qwen-oauth"
]);
var PROJECT_SKILL_DIRS = [".qwen", ".agents"];
var SKILLS_DIR = "skills";
var QWEN_CORE_SETTING_DEFINITIONS = {
  "model.name": { type: "string" },
  fastModel: { type: "string" },
  "general.outputLanguage": { type: "string" },
  "general.language": { type: "string" },
  "tools.approvalMode": {
    type: "enum",
    values: ["plan", "default", "auto-edit", "auto", "yolo"]
  },
  "general.vimMode": { type: "boolean" },
  "general.enableAutoUpdate": { type: "boolean" },
  "general.showSessionRecap": { type: "boolean" },
  "general.sessionRecapAwayThresholdMinutes": { type: "number", min: 1 },
  "general.terminalBell": { type: "boolean" },
  "general.gitCoAuthor.commit": { type: "boolean" },
  "general.gitCoAuthor.pr": { type: "boolean" },
  "general.defaultFileEncoding": {
    type: "enum",
    values: ["utf-8", "utf-8-bom"]
  },
  "context.fileFiltering.respectGitIgnore": { type: "boolean" },
  "context.fileFiltering.respectQwenIgnore": { type: "boolean" },
  "context.fileFiltering.enableFuzzySearch": { type: "boolean" },
  "memory.enableManagedAutoMemory": { type: "boolean" },
  "memory.enableManagedAutoDream": { type: "boolean" },
  "memory.enableAutoSkill": { type: "boolean" },
  "memory.autoSkillConfirm": { type: "boolean" },
  "memory.enableTeamMemory": { type: "boolean" },
  "memory.enableTeamMemorySync": { type: "boolean" },
  disableAllHooks: { type: "boolean" }
};
var QWEN_CORE_SETTING_KEYS = Object.keys(
  QWEN_CORE_SETTING_DEFINITIONS
);
var QWEN_HOOK_EVENTS = Object.values(HookEventName);
var DEFAULT_QWEN_MEMORY_SETTINGS = {
  enableManagedAutoMemory: true,
  enableManagedAutoDream: true,
  enableAutoSkill: true,
  autoSkillConfirm: true,
  enableTeamMemory: false,
  enableTeamMemorySync: false
};
var QWEN_MEMORY_SETTING_KEYS = [
  "enableManagedAutoMemory",
  "enableManagedAutoDream",
  "enableAutoSkill",
  "autoSkillConfirm",
  "enableTeamMemory",
  "enableTeamMemorySync"
];
function normalizeQwenMemorySettings(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return { ...DEFAULT_QWEN_MEMORY_SETTINGS };
  }
  const record = value;
  return {
    enableManagedAutoMemory: typeof record["enableManagedAutoMemory"] === "boolean" ? record["enableManagedAutoMemory"] : DEFAULT_QWEN_MEMORY_SETTINGS.enableManagedAutoMemory,
    enableManagedAutoDream: typeof record["enableManagedAutoDream"] === "boolean" ? record["enableManagedAutoDream"] : DEFAULT_QWEN_MEMORY_SETTINGS.enableManagedAutoDream,
    enableAutoSkill: typeof record["enableAutoSkill"] === "boolean" ? record["enableAutoSkill"] : DEFAULT_QWEN_MEMORY_SETTINGS.enableAutoSkill,
    autoSkillConfirm: typeof record["autoSkillConfirm"] === "boolean" ? record["autoSkillConfirm"] : DEFAULT_QWEN_MEMORY_SETTINGS.autoSkillConfirm,
    enableTeamMemory: typeof record["enableTeamMemory"] === "boolean" ? record["enableTeamMemory"] : DEFAULT_QWEN_MEMORY_SETTINGS.enableTeamMemory,
    enableTeamMemorySync: typeof record["enableTeamMemorySync"] === "boolean" ? record["enableTeamMemorySync"] : DEFAULT_QWEN_MEMORY_SETTINGS.enableTeamMemorySync
  };
}
__name(normalizeQwenMemorySettings, "normalizeQwenMemorySettings");
function toRecord(value) {
  return !!value && typeof value === "object" && !Array.isArray(value) ? value : {};
}
__name(toRecord, "toRecord");
function readOptionalString(value, fieldName) {
  if (value === void 0 || value === null) return void 0;
  if (typeof value !== "string") {
    throw RequestError.invalidParams(
      void 0,
      `Invalid ${fieldName}: expected string`
    );
  }
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : void 0;
}
__name(readOptionalString, "readOptionalString");
function readRequiredString(value, fieldName) {
  const stringValue = readOptionalString(value, fieldName);
  if (!stringValue) {
    throw RequestError.invalidParams(
      void 0,
      `Invalid or missing ${fieldName}`
    );
  }
  return stringValue;
}
__name(readRequiredString, "readRequiredString");
function validateSkillSlug(slug) {
  if (!slug || slug === "." || slug === ".." || slug.includes("/") || slug.includes(path3.sep) || !/^[a-zA-Z0-9._-]+$/.test(slug)) {
    throw RequestError.invalidParams(void 0, "Invalid skill.slug");
  }
}
__name(validateSkillSlug, "validateSkillSlug");
function readSkillInstallRequest(params) {
  const skillParams = toRecord(params["skill"]);
  const input = Object.keys(skillParams).length > 0 ? skillParams : params;
  const slug = readRequiredString(input["slug"], "skill.slug");
  validateSkillSlug(slug);
  const scope = readOptionalString(input["scope"], "skill.scope") ?? "global";
  if (scope !== "global") {
    throw RequestError.invalidParams(
      void 0,
      "Only global skill installation is supported"
    );
  }
  const description = readOptionalString(
    input["description"],
    "skill.description"
  );
  return {
    id: readOptionalString(input["id"], "skill.id") ?? slug,
    slug,
    name: readOptionalString(input["name"], "skill.name") ?? slug,
    ...description ? { description } : {},
    sourceUrl: readRequiredString(input["sourceUrl"], "skill.sourceUrl"),
    scope
  };
}
__name(readSkillInstallRequest, "readSkillInstallRequest");
function readSkillSlugRequest(params) {
  const skillParams = toRecord(params["skill"]);
  const input = Object.keys(skillParams).length > 0 ? skillParams : params;
  const slug = readRequiredString(input["slug"], "skill.slug");
  validateSkillSlug(slug);
  const scope = readOptionalString(input["scope"], "skill.scope") ?? "global";
  if (scope !== "global") {
    throw RequestError.invalidParams(
      void 0,
      "Only global skill management is supported"
    );
  }
  return { slug, scope };
}
__name(readSkillSlugRequest, "readSkillSlugRequest");
function readSkillSetEnabledRequest(params) {
  const skillParams = toRecord(params["skill"]);
  const input = Object.keys(skillParams).length > 0 ? skillParams : params;
  const slug = readRequiredString(input["slug"], "skill.slug");
  validateSkillSlug(slug);
  const scope = readOptionalString(input["scope"], "skill.scope") ?? "global";
  if (scope !== "global" && scope !== "project") {
    throw RequestError.invalidParams(
      void 0,
      "Only global or project skill management is supported"
    );
  }
  if (typeof input["enabled"] !== "boolean") {
    throw RequestError.invalidParams(
      void 0,
      "Invalid skill.enabled: expected boolean"
    );
  }
  return {
    slug,
    scope,
    enabled: input["enabled"]
  };
}
__name(readSkillSetEnabledRequest, "readSkillSetEnabledRequest");
function splitSkillMarkdown(content) {
  const normalized = content.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
  const match = normalized.match(/^---\n([\s\S]*?)\n---(?:\n|$)([\s\S]*)$/);
  if (!match) {
    throw RequestError.invalidParams(
      void 0,
      "Invalid skill file: missing YAML frontmatter"
    );
  }
  return {
    frontmatter: match[1],
    body: match[2]
  };
}
__name(splitSkillMarkdown, "splitSkillMarkdown");
function setSkillFrontmatterEnabled(content, enabled) {
  const { frontmatter, body } = splitSkillMarkdown(content);
  const lines = frontmatter.split("\n");
  const disabledLineIndex = lines.findIndex(
    (line) => /^disable-model-invocation\s*:/.test(line)
  );
  if (enabled) {
    if (disabledLineIndex !== -1) {
      lines.splice(disabledLineIndex, 1);
    }
  } else if (disabledLineIndex !== -1) {
    lines[disabledLineIndex] = "disable-model-invocation: true";
  } else {
    let insertIndex = lines.length;
    while (insertIndex > 0 && lines[insertIndex - 1].trim() === "") {
      insertIndex -= 1;
    }
    lines.splice(insertIndex, 0, "disable-model-invocation: true");
  }
  const nextFrontmatter = lines.join("\n");
  return `---
${nextFrontmatter}
---
${body}`;
}
__name(setSkillFrontmatterEnabled, "setSkillFrontmatterEnabled");
var ALLOWED_SKILL_SOURCE_HOSTS = /* @__PURE__ */ new Set([
  "github.com",
  "raw.githubusercontent.com",
  "codeload.github.com",
  "api.github.com"
]);
function assertAllowedSkillSourceUrl(sourceUrl) {
  let parsed;
  try {
    parsed = new URL(sourceUrl);
  } catch {
    throw RequestError.invalidParams(
      void 0,
      "Skill sourceUrl must be a valid URL"
    );
  }
  if (parsed.protocol !== "https:") {
    throw RequestError.invalidParams(
      void 0,
      "Skill sourceUrl must be an HTTPS URL"
    );
  }
  if (!ALLOWED_SKILL_SOURCE_HOSTS.has(parsed.hostname)) {
    throw RequestError.invalidParams(
      void 0,
      "Skill sourceUrl host is not allowed (only github.com sources are supported)"
    );
  }
}
__name(assertAllowedSkillSourceUrl, "assertAllowedSkillSourceUrl");
function parseGitHubBlobSkillUrl(sourceUrl) {
  const parsed = new URL(sourceUrl);
  if (parsed.protocol !== "https:") {
    throw RequestError.invalidParams(
      void 0,
      "Skill sourceUrl must be an HTTPS URL"
    );
  }
  if (parsed.hostname !== "github.com") return null;
  const parts = parsed.pathname.split("/").filter(Boolean);
  if (parts.length < 5 || parts[2] !== "blob") return null;
  const owner = parts[0];
  const repo = parts[1];
  const ref = parts[3];
  const filePathParts = parts.slice(4);
  if (!owner || !repo || !ref || filePathParts.length === 0) return null;
  return {
    owner,
    repo,
    ref,
    filePath: filePathParts.join("/")
  };
}
__name(parseGitHubBlobSkillUrl, "parseGitHubBlobSkillUrl");
function toRawGitHubUrl(githubUrl) {
  return `https://raw.githubusercontent.com/${githubUrl.owner}/${githubUrl.repo}/${githubUrl.ref}/${githubUrl.filePath}`;
}
__name(toRawGitHubUrl, "toRawGitHubUrl");
function encodeGitHubPath(filePath) {
  if (!filePath || filePath === ".") return "";
  return filePath.split("/").map(encodeURIComponent).join("/");
}
__name(encodeGitHubPath, "encodeGitHubPath");
function readTarString(archive, offset, length) {
  const bytes = archive.subarray(offset, offset + length);
  const nul = bytes.indexOf(0);
  const end = nul >= 0 ? nul : bytes.length;
  return Buffer.from(bytes.subarray(0, end)).toString("utf8").trim();
}
__name(readTarString, "readTarString");
function readTarSize(archive, offset) {
  const raw = readTarString(archive, offset + 124, 12);
  return raw ? Number.parseInt(raw, 8) : 0;
}
__name(readTarSize, "readTarSize");
function isZeroTarBlock(archive, offset) {
  for (let i = 0; i < 512; i += 1) {
    if (archive[offset + i] !== 0) return false;
  }
  return true;
}
__name(isZeroTarBlock, "isZeroTarBlock");
function readTarPath(archive, offset) {
  const name = readTarString(archive, offset, 100);
  const prefix = readTarString(archive, offset + 345, 155);
  return prefix ? `${prefix}/${name}` : name;
}
__name(readTarPath, "readTarPath");
function stripArchiveRoot(filePath) {
  const parts = filePath.split("/").filter(Boolean);
  return parts.length > 1 ? parts.slice(1).join("/") : "";
}
__name(stripArchiveRoot, "stripArchiveRoot");
var MAX_SKILL_DOWNLOAD_BYTES = 100 * 1024 * 1024;
var MAX_SKILL_DECOMPRESSED_BYTES = 500 * 1024 * 1024;
var MAX_SKILL_API_DIR_DEPTH = 16;
var MAX_SKILL_API_FILE_COUNT = 2e3;
var DecompressedSizeExceededError = class extends Error {
  static {
    __name(this, "DecompressedSizeExceededError");
  }
};
async function extractFilesFromTarGz(archiveBytes, directoryPath, limits = {}) {
  const maxCompressedBytes = limits.maxCompressedBytes ?? MAX_SKILL_DOWNLOAD_BYTES;
  const maxDecompressedBytes = limits.maxDecompressedBytes ?? MAX_SKILL_DECOMPRESSED_BYTES;
  if (archiveBytes.length > maxCompressedBytes) {
    throw RequestError.invalidParams(
      void 0,
      "Skill archive exceeds the maximum allowed size"
    );
  }
  let archive;
  try {
    const chunks = [];
    let total = 0;
    await pipeline(
      // Wrap in an array so the whole archive is emitted as a single chunk;
      // `Readable.from(uint8array)` would otherwise iterate it byte-by-byte.
      Readable.from([Buffer.from(archiveBytes)]),
      createGunzip(),
      new Writable({
        write(chunk, _enc, cb) {
          total += chunk.length;
          if (total > maxDecompressedBytes) {
            cb(new DecompressedSizeExceededError());
            return;
          }
          chunks.push(chunk);
          cb();
        }
      })
    );
    archive = Buffer.concat(chunks);
  } catch (error) {
    if (error instanceof DecompressedSizeExceededError) {
      throw RequestError.invalidParams(
        void 0,
        "Decompressed skill archive exceeds the maximum allowed size"
      );
    }
    throw RequestError.invalidParams(
      void 0,
      `Failed to decompress skill archive: ${error instanceof Error ? error.message : String(error)}`
    );
  }
  const normalizedDirectory = directoryPath.replace(/^\/+|\/+$/g, "");
  const directoryPrefix = normalizedDirectory && normalizedDirectory !== "." ? `${normalizedDirectory}/` : "";
  const files = [];
  for (let offset = 0; offset + 512 <= archive.length; ) {
    if (isZeroTarBlock(archive, offset)) break;
    const fullPath = readTarPath(archive, offset);
    const typeFlag = String.fromCharCode(archive[offset + 156] || 0);
    const size = readTarSize(archive, offset);
    const dataOffset = offset + 512;
    const nextOffset = dataOffset + Math.ceil(size / 512) * 512;
    if (typeFlag === "0" || typeFlag === "\0") {
      const repoPath = stripArchiveRoot(fullPath);
      if (repoPath.startsWith(directoryPrefix)) {
        const relativePath = repoPath.slice(directoryPrefix.length);
        if (relativePath) {
          files.push({
            relativePath,
            content: archive.subarray(dataOffset, dataOffset + size)
          });
        }
      }
    }
    offset = nextOffset;
  }
  return files;
}
__name(extractFilesFromTarGz, "extractFilesFromTarGz");
var ALLOWED_REDIRECT_HOST_SUFFIXES = [
  ".githubusercontent.com",
  ".github.com"
  // Note: '.github.io' is intentionally excluded — *.github.io are
  // user-controlled GitHub Pages sites, so allowing redirects there would
  // reopen the SSRF/exfiltration surface this allowlist exists to close.
];
function isAllowedSkillFetchHost(hostname) {
  if (ALLOWED_SKILL_SOURCE_HOSTS.has(hostname)) return true;
  return ALLOWED_REDIRECT_HOST_SUFFIXES.some(
    (suffix) => hostname.endsWith(suffix)
  );
}
__name(isAllowedSkillFetchHost, "isAllowedSkillFetchHost");
async function fetchAllowedGitHub(url, init = {}, maxRedirects = 5) {
  let current = url;
  for (let hop = 0; hop <= maxRedirects; hop += 1) {
    const response = await fetch(current, { ...init, redirect: "manual" });
    if (response.status < 300 || response.status >= 400) {
      return response;
    }
    const location = response.headers?.get("location");
    if (!location) return response;
    let next;
    try {
      next = new URL(location, current);
    } catch {
      throw RequestError.invalidParams(
        void 0,
        "Skill download redirected to an invalid URL"
      );
    }
    if (next.protocol !== "https:" || !isAllowedSkillFetchHost(next.hostname)) {
      throw RequestError.invalidParams(
        void 0,
        "Skill download redirected to a disallowed host"
      );
    }
    current = next.toString();
  }
  throw RequestError.invalidParams(
    void 0,
    "Skill download exceeded the maximum number of redirects"
  );
}
__name(fetchAllowedGitHub, "fetchAllowedGitHub");
async function readBodyWithLimit(response, maxBytes) {
  const body = response.body;
  if (!body) {
    const buf = new Uint8Array(await response.arrayBuffer());
    if (buf.byteLength > maxBytes) {
      throw RequestError.invalidParams(
        void 0,
        "Skill download exceeds the maximum allowed size"
      );
    }
    return buf;
  }
  const reader = body.getReader();
  const chunks = [];
  let total = 0;
  for (; ; ) {
    const { done, value } = await reader.read();
    if (done) break;
    if (!value) continue;
    total += value.byteLength;
    if (total > maxBytes) {
      await reader.cancel();
      throw RequestError.invalidParams(
        void 0,
        "Skill download exceeds the maximum allowed size"
      );
    }
    chunks.push(value);
  }
  const result = new Uint8Array(total);
  let offset = 0;
  for (const chunk of chunks) {
    result.set(chunk, offset);
    offset += chunk.byteLength;
  }
  return result;
}
__name(readBodyWithLimit, "readBodyWithLimit");
async function fetchBytes(url) {
  const response = await fetchAllowedGitHub(url);
  if (!response.ok) {
    throw RequestError.invalidParams(
      void 0,
      `Failed to download skill (${response.status})`
    );
  }
  const contentLength = response.headers?.get("content-length");
  if (contentLength) {
    const declaredSize = Number.parseInt(contentLength, 10);
    if (Number.isFinite(declaredSize) && declaredSize > MAX_SKILL_DOWNLOAD_BYTES) {
      throw RequestError.invalidParams(
        void 0,
        "Skill download exceeds the maximum allowed size"
      );
    }
  }
  return readBodyWithLimit(response, MAX_SKILL_DOWNLOAD_BYTES);
}
__name(fetchBytes, "fetchBytes");
async function downloadSingleSkillFile(sourceUrl) {
  const githubUrl = parseGitHubBlobSkillUrl(sourceUrl);
  const fetchUrl = githubUrl ? toRawGitHubUrl(githubUrl) : sourceUrl;
  const content = await fetchBytes(fetchUrl);
  return {
    skillContent: Buffer.from(content).toString("utf8"),
    files: [{ relativePath: "SKILL.md", content }]
  };
}
__name(downloadSingleSkillFile, "downloadSingleSkillFile");
async function downloadGitHubSkillDirectoryFromArchive(githubUrl, directoryPath) {
  const archiveUrl = `https://codeload.github.com/${githubUrl.owner}/${githubUrl.repo}/tar.gz/${encodeURIComponent(
    githubUrl.ref
  )}`;
  const response = await fetchAllowedGitHub(archiveUrl, {
    headers: {
      "User-Agent": "qwen-code"
    }
  });
  if (!response.ok) {
    throw RequestError.invalidParams(
      void 0,
      `Failed to download GitHub skill archive (${response.status})`
    );
  }
  const contentLength = response.headers?.get("content-length");
  if (contentLength) {
    const declaredSize = Number.parseInt(contentLength, 10);
    if (Number.isFinite(declaredSize) && declaredSize > MAX_SKILL_DOWNLOAD_BYTES) {
      throw RequestError.invalidParams(
        void 0,
        "Skill archive exceeds the maximum allowed size"
      );
    }
  }
  return extractFilesFromTarGz(
    await readBodyWithLimit(response, MAX_SKILL_DOWNLOAD_BYTES),
    directoryPath
  );
}
__name(downloadGitHubSkillDirectoryFromArchive, "downloadGitHubSkillDirectoryFromArchive");
async function fetchGitHubDirectoryItems(githubUrl, directoryPath) {
  const encodedPath = encodeGitHubPath(directoryPath);
  const apiUrl = `https://api.github.com/repos/${githubUrl.owner}/${githubUrl.repo}/contents/${encodedPath}?ref=${encodeURIComponent(githubUrl.ref)}`;
  const response = await fetchAllowedGitHub(apiUrl, {
    headers: {
      Accept: "application/vnd.github+json",
      "User-Agent": "qwen-code"
    }
  });
  if (!response.ok) {
    throw RequestError.invalidParams(
      void 0,
      `Failed to list GitHub skill files (${response.status})`
    );
  }
  const data = await response.json();
  if (!Array.isArray(data)) {
    throw RequestError.invalidParams(
      void 0,
      "GitHub skill URL must point to a directory-backed SKILL.md file"
    );
  }
  return data;
}
__name(fetchGitHubDirectoryItems, "fetchGitHubDirectoryItems");
async function downloadGitHubSkillDirectoryFromApi(githubUrl, directoryPath, relativeRoot = "", depth = 0, budget = { files: 0, bytes: 0 }) {
  if (depth > MAX_SKILL_API_DIR_DEPTH) {
    throw RequestError.invalidParams(
      void 0,
      "Skill directory nesting exceeds the maximum allowed depth"
    );
  }
  const items = await fetchGitHubDirectoryItems(githubUrl, directoryPath);
  const files = [];
  for (const item of items) {
    const record = toRecord(item);
    const name = readRequiredString(record["name"], "github.name");
    const itemPath = readRequiredString(record["path"], "github.path");
    const type = readRequiredString(record["type"], "github.type");
    const relativePath = relativeRoot ? path3.posix.join(relativeRoot, name) : name;
    if (type === "dir") {
      files.push(
        ...await downloadGitHubSkillDirectoryFromApi(
          githubUrl,
          itemPath,
          relativePath,
          depth + 1,
          budget
        )
      );
      continue;
    }
    if (type !== "file") continue;
    budget.files += 1;
    if (budget.files > MAX_SKILL_API_FILE_COUNT) {
      throw RequestError.invalidParams(
        void 0,
        "Skill directory contains too many files"
      );
    }
    const downloadUrl = readRequiredString(
      record["download_url"],
      "github.download_url"
    );
    assertAllowedSkillSourceUrl(downloadUrl);
    const content = await fetchBytes(downloadUrl);
    budget.bytes += content.length;
    if (budget.bytes > MAX_SKILL_DECOMPRESSED_BYTES) {
      throw RequestError.invalidParams(
        void 0,
        "Skill directory exceeds the maximum allowed size"
      );
    }
    files.push({
      relativePath,
      content
    });
  }
  return files;
}
__name(downloadGitHubSkillDirectoryFromApi, "downloadGitHubSkillDirectoryFromApi");
async function downloadGitHubSkillDirectory(githubUrl, directoryPath) {
  const apiFiles = await downloadGitHubSkillDirectoryFromApi(
    githubUrl,
    directoryPath
  ).catch((error) => {
    debugLogger6.warn(
      "GitHub API directory listing failed, falling back to archive download:",
      error
    );
    return null;
  });
  if (apiFiles) return apiFiles;
  return downloadGitHubSkillDirectoryFromArchive(githubUrl, directoryPath);
}
__name(downloadGitHubSkillDirectory, "downloadGitHubSkillDirectory");
async function downloadSkill(sourceUrl) {
  assertAllowedSkillSourceUrl(sourceUrl);
  const githubUrl = parseGitHubBlobSkillUrl(sourceUrl);
  if (!githubUrl || path3.posix.basename(githubUrl.filePath) !== "SKILL.md") {
    return downloadSingleSkillFile(sourceUrl);
  }
  const skillDirectory = path3.posix.dirname(githubUrl.filePath);
  const files = await downloadGitHubSkillDirectory(githubUrl, skillDirectory);
  const skillFile = files.find((file) => file.relativePath === "SKILL.md");
  if (!skillFile) {
    throw RequestError.invalidParams(
      void 0,
      "GitHub skill directory does not contain SKILL.md"
    );
  }
  return {
    skillContent: Buffer.from(skillFile.content).toString("utf8"),
    files
  };
}
__name(downloadSkill, "downloadSkill");
function resolveSkillInstallPath(skillDir, relativePath) {
  const root = path3.resolve(skillDir);
  const target = path3.resolve(skillDir, relativePath);
  if (target !== root && !target.startsWith(root + path3.sep)) {
    throw RequestError.invalidParams(
      void 0,
      `Invalid skill file path: ${relativePath}`
    );
  }
  return target;
}
__name(resolveSkillInstallPath, "resolveSkillInstallPath");
function resolveManagedSkillDir(skillsBaseDir, slug) {
  const root = path3.resolve(skillsBaseDir);
  const skillDir = path3.resolve(skillsBaseDir, slug);
  if (!skillDir.startsWith(root + path3.sep)) {
    throw RequestError.invalidParams(void 0, "Invalid skill.slug");
  }
  return skillDir;
}
__name(resolveManagedSkillDir, "resolveManagedSkillDir");
function readStringArray(value, fieldName) {
  if (value === void 0 || value === null) return [];
  if (!Array.isArray(value)) {
    throw RequestError.invalidParams(
      void 0,
      `Invalid ${fieldName}: expected string[]`
    );
  }
  return Array.from(
    new Set(
      value.map((item) => {
        if (typeof item !== "string") {
          throw RequestError.invalidParams(
            void 0,
            `Invalid ${fieldName}: expected string[]`
          );
        }
        return item.trim();
      }).filter(Boolean)
    )
  );
}
__name(readStringArray, "readStringArray");
function readPositiveNumber(value, fieldName) {
  if (value === void 0 || value === null || value === "") return void 0;
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
    throw RequestError.invalidParams(
      void 0,
      `Invalid ${fieldName}: expected positive number`
    );
  }
  return value;
}
__name(readPositiveNumber, "readPositiveNumber");
function readProviderAdvancedConfig(value) {
  if (value === void 0 || value === null) return void 0;
  const record = toRecord(value);
  if (record["enableThinking"] !== void 0 && typeof record["enableThinking"] !== "boolean") {
    throw RequestError.invalidParams(
      void 0,
      "Invalid advancedConfig.enableThinking: expected boolean"
    );
  }
  const multimodalRecord = toRecord(record["multimodal"]);
  const multimodal = {};
  for (const key of ["image", "video", "audio", "pdf"]) {
    const flag = multimodalRecord[key];
    if (flag !== void 0) {
      if (typeof flag !== "boolean") {
        throw RequestError.invalidParams(
          void 0,
          `Invalid advancedConfig.multimodal.${key}: expected boolean`
        );
      }
      multimodal[key] = flag;
    }
  }
  const contextWindowSize = readPositiveNumber(
    record["contextWindowSize"],
    "advancedConfig.contextWindowSize"
  );
  const maxTokens = readPositiveNumber(
    record["maxTokens"],
    "advancedConfig.maxTokens"
  );
  const advancedConfig = {
    ...typeof record["enableThinking"] === "boolean" ? { enableThinking: record["enableThinking"] } : {},
    ...Object.keys(multimodal).length > 0 ? { multimodal } : {},
    ...contextWindowSize ? { contextWindowSize } : {},
    ...maxTokens ? { maxTokens } : {}
  };
  return Object.keys(advancedConfig).length > 0 ? advancedConfig : void 0;
}
__name(readProviderAdvancedConfig, "readProviderAdvancedConfig");
function resolveProviderDocumentationUrl(config, baseUrl) {
  if (typeof config.documentationUrl === "string") {
    return config.documentationUrl;
  }
  if (typeof config.documentationUrl === "function") {
    try {
      return config.documentationUrl(baseUrl);
    } catch {
      return void 0;
    }
  }
  return void 0;
}
__name(resolveProviderDocumentationUrl, "resolveProviderDocumentationUrl");
function readSettingsEnv(settings, envKey) {
  if (!envKey) return void 0;
  const env = toRecord(settings.merged["env"]);
  const value = env[envKey];
  return typeof value === "string" && value.length > 0 ? value : void 0;
}
__name(readSettingsEnv, "readSettingsEnv");
function resolveProviderEnvKey(config, protocol, baseUrl) {
  try {
    return typeof config.envKey === "function" ? config.envKey(protocol, baseUrl) : config.envKey;
  } catch {
    return void 0;
  }
}
__name(resolveProviderEnvKey, "resolveProviderEnvKey");
function readExistingAdvancedConfig(model) {
  const generationConfig = toRecord(model?.generationConfig);
  const extraBody = toRecord(generationConfig["extra_body"]);
  const advancedConfig = {};
  if (typeof extraBody["enable_thinking"] === "boolean") {
    advancedConfig["enableThinking"] = extraBody["enable_thinking"];
  }
  if (typeof generationConfig["contextWindowSize"] === "number") {
    advancedConfig["contextWindowSize"] = generationConfig["contextWindowSize"];
  }
  return Object.keys(advancedConfig).length > 0 ? advancedConfig : void 0;
}
__name(readExistingAdvancedConfig, "readExistingAdvancedConfig");
function readExistingProviderConfig(config, settings) {
  const existing = findExistingProviderModels(
    config,
    settings.merged["modelProviders"]
  );
  const firstModel = existing?.models[0];
  const protocol = existing?.protocol ?? config.protocol;
  const baseUrl = typeof firstModel?.baseUrl === "string" ? firstModel.baseUrl : resolveBaseUrl(config);
  const envKey = typeof firstModel?.envKey === "string" ? firstModel.envKey : resolveProviderEnvKey(config, protocol, baseUrl);
  const apiKey = readSettingsEnv(settings, envKey);
  const hasExistingConfig = !!apiKey || !!existing;
  if (!hasExistingConfig) return void 0;
  const advancedConfig = readExistingAdvancedConfig(firstModel);
  return {
    protocol,
    baseUrl: sanitizeProviderBaseUrl(baseUrl),
    // Never serialize the raw secret over the ACP wire. Expose only whether a
    // key is stored; the client can omit `apiKey` on connect to keep it.
    ...apiKey ? { hasApiKey: true } : {},
    ...existing ? { modelIds: existing.models.map((model) => model.id) } : {},
    ...advancedConfig ? { advancedConfig } : {}
  };
}
__name(readExistingProviderConfig, "readExistingProviderConfig");
function resolveExistingProviderApiKey(config, settings, protocol, baseUrl) {
  const envKey = resolveProviderEnvKey(config, protocol, baseUrl);
  return readSettingsEnv(settings, envKey);
}
__name(resolveExistingProviderApiKey, "resolveExistingProviderApiKey");
function serializeProviderConfig(config, settings) {
  const defaultProtocol = config.protocolOptions?.[0] ?? config.protocol;
  const defaultBaseUrl = config.baseUrl === void 0 ? getDefaultBaseUrlForProtocol(defaultProtocol) : resolveBaseUrl(config);
  const existingConfig = readExistingProviderConfig(config, settings);
  return {
    id: config.id,
    label: config.label,
    description: config.description,
    protocol: config.protocol,
    protocolOptions: config.protocolOptions ?? [],
    baseUrl: config.baseUrl,
    baseUrlPlaceholder: config.baseUrl === void 0 ? defaultBaseUrl : void 0,
    defaultModelIds: getDefaultModelIds(config),
    models: config.models ?? [],
    modelsEditable: config.modelsEditable === true || !config.models,
    showAdvancedConfig: config.showAdvancedConfig === true,
    apiKeyPlaceholder: config.apiKeyPlaceholder,
    documentationUrl: resolveProviderDocumentationUrl(config, defaultBaseUrl),
    uiGroup: config.uiGroup ?? "third-party",
    uiLabels: config.uiLabels,
    ...existingConfig ? { existingConfig } : {}
  };
}
__name(serializeProviderConfig, "serializeProviderConfig");
function readProviderSetupInputs(config, params, resolveExistingApiKey) {
  const protocol = readOptionalString(params["protocol"], "protocol");
  if (protocol && protocol !== config.protocol && !config.protocolOptions?.includes(protocol)) {
    throw RequestError.invalidParams(
      void 0,
      `Invalid protocol for provider "${config.id}"`
    );
  }
  let baseUrl = resolveBaseUrl(
    config,
    readOptionalString(params["baseUrl"], "baseUrl")
  ).trim();
  if (!baseUrl && config.baseUrl === void 0) {
    baseUrl = getDefaultBaseUrlForProtocol(protocol ?? config.protocol);
  }
  if (!baseUrl) {
    throw RequestError.invalidParams(
      void 0,
      `Invalid or missing baseUrl for provider "${config.id}"`
    );
  }
  const apiKey = readOptionalString(params["apiKey"], "apiKey") ?? resolveExistingApiKey?.(protocol ?? config.protocol, baseUrl);
  if (!apiKey) {
    throw RequestError.invalidParams(void 0, "Invalid or missing apiKey");
  }
  const apiKeyError = config.validateApiKey?.(apiKey, baseUrl);
  if (apiKeyError) {
    throw RequestError.invalidParams(void 0, apiKeyError);
  }
  const defaultModelIds = getDefaultModelIds(config);
  const modelIds = readStringArray(params["modelIds"], "modelIds");
  const resolvedModelIds = modelIds.length > 0 ? modelIds : defaultModelIds;
  if (resolvedModelIds.length === 0) {
    throw RequestError.invalidParams(
      void 0,
      `Invalid or missing modelIds for provider "${config.id}"`
    );
  }
  const advancedConfig = readProviderAdvancedConfig(params["advancedConfig"]);
  return {
    ...protocol ? { protocol } : {},
    baseUrl,
    apiKey,
    modelIds: resolvedModelIds,
    ...advancedConfig ? { advancedConfig } : {}
  };
}
__name(readProviderSetupInputs, "readProviderSetupInputs");
function readProviderConnectScope(value) {
  if (value === void 0) return void 0;
  if (value === "user") return "User" /* User */;
  if (value === "workspace") return "Workspace" /* Workspace */;
  throw RequestError.invalidParams(
    void 0,
    "Invalid scope for provider connect"
  );
}
__name(readProviderConnectScope, "readProviderConnectScope");
function getNestedSettingValue(source, key) {
  let current = source;
  for (const segment of key.split(".")) {
    if (!current || typeof current !== "object" || Array.isArray(current)) {
      return void 0;
    }
    current = current[segment];
  }
  if (typeof current === "string" || typeof current === "number" || typeof current === "boolean" || Array.isArray(current)) {
    return current;
  }
  return void 0;
}
__name(getNestedSettingValue, "getNestedSettingValue");
function readCoreSettingValues(source) {
  const values = {};
  for (const key of QWEN_CORE_SETTING_KEYS) {
    const value = getNestedSettingValue(source, key);
    if (value !== void 0) {
      values[key] = value;
    }
  }
  return values;
}
__name(readCoreSettingValues, "readCoreSettingValues");
function normalizeCoreSettingValue(key, value) {
  const definition = QWEN_CORE_SETTING_DEFINITIONS[key];
  switch (definition.type) {
    case "boolean":
      if (typeof value !== "boolean") {
        throw RequestError.invalidParams(void 0, `${key} must be a boolean`);
      }
      return value;
    case "number":
      if (typeof value !== "number" || !Number.isFinite(value)) {
        throw RequestError.invalidParams(void 0, `${key} must be a number`);
      }
      if (definition.min !== void 0 && value < definition.min) {
        throw RequestError.invalidParams(
          void 0,
          `${key} must be at least ${definition.min}`
        );
      }
      return value;
    case "enum": {
      const values = definition.values;
      if (typeof value !== "string" || !values?.includes(value)) {
        throw RequestError.invalidParams(
          void 0,
          `${key} must be one of ${values?.join(", ")}`
        );
      }
      return value;
    }
    case "string": {
      if (value === void 0) return void 0;
      if (typeof value !== "string") {
        throw RequestError.invalidParams(void 0, `${key} must be a string`);
      }
      const controlChars = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g;
      const sanitized = value.replace(controlChars, " ").trim();
      return sanitized || void 0;
    }
    default:
      throw RequestError.invalidParams(
        void 0,
        `${key} has an unsupported setting type`
      );
  }
}
__name(normalizeCoreSettingValue, "normalizeCoreSettingValue");
function normalizeStringArray(value) {
  if (value === void 0) return void 0;
  if (!Array.isArray(value)) {
    throw RequestError.invalidParams(void 0, "Expected an array of strings");
  }
  return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
}
__name(normalizeStringArray, "normalizeStringArray");
function normalizeStringRecord(value) {
  if (value === void 0) return void 0;
  const record = toRecord(value);
  const result = {};
  for (const [key, item] of Object.entries(record)) {
    if (typeof item === "string" && key.trim()) {
      result[key.trim()] = item;
    }
  }
  return result;
}
__name(normalizeStringRecord, "normalizeStringRecord");
function normalizeOptionalNumber(value) {
  if (value === void 0 || value === null || value === "") return void 0;
  let numberValue;
  if (typeof value === "number") {
    numberValue = value;
  } else if (typeof value === "string") {
    const trimmed = value.trim();
    numberValue = /^\d+$/.test(trimmed) ? Number(trimmed) : Number.NaN;
  } else {
    numberValue = Number.NaN;
  }
  if (!Number.isInteger(numberValue) || numberValue <= 0) {
    throw RequestError.invalidParams(void 0, "Expected a positive integer");
  }
  return numberValue;
}
__name(normalizeOptionalNumber, "normalizeOptionalNumber");
function normalizeMcpServerConfig(value) {
  const input = toRecord(value);
  const transport = input["transport"];
  if (transport !== "stdio" && transport !== "http" && transport !== "sse") {
    throw RequestError.invalidParams(
      void 0,
      "MCP transport must be stdio, http, or sse"
    );
  }
  const server = { transport };
  const description = input["description"];
  if (typeof description === "string" && description.trim()) {
    server.description = description.trim();
  }
  const cwd = input["cwd"];
  if (typeof cwd === "string" && cwd.trim()) server.cwd = cwd.trim();
  const timeout = normalizeOptionalNumber(input["timeout"]);
  if (timeout !== void 0) server.timeout = timeout;
  if (typeof input["trust"] === "boolean") server.trust = input["trust"];
  server.includeTools = normalizeStringArray(input["includeTools"]);
  server.excludeTools = normalizeStringArray(input["excludeTools"]);
  if (transport === "stdio") {
    const command = input["command"];
    if (typeof command !== "string" || !command.trim()) {
      throw RequestError.invalidParams(
        void 0,
        "Stdio MCP servers require a command"
      );
    }
    server.command = command.trim();
    server.args = normalizeStringArray(input["args"]);
    server.env = normalizeStringRecord(input["env"]);
    return server;
  }
  const urlKey = transport === "http" ? "httpUrl" : "url";
  const url = input[urlKey];
  if (typeof url !== "string" || !url.trim()) {
    throw RequestError.invalidParams(
      void 0,
      `${transport.toUpperCase()} MCP servers require a URL`
    );
  }
  if (transport === "http") server.httpUrl = url.trim();
  else server.url = url.trim();
  server.headers = normalizeStringRecord(input["headers"]);
  return server;
}
__name(normalizeMcpServerConfig, "normalizeMcpServerConfig");
function toStoredMcpServerConfig(server) {
  const result = {};
  for (const key of [
    "timeout",
    "trust",
    "description",
    "includeTools",
    "excludeTools"
  ]) {
    if (server[key] !== void 0) result[key] = server[key];
  }
  if (server.transport === "stdio") {
    result["command"] = server.command;
    if (server.args !== void 0) result["args"] = server.args;
    if (server.cwd !== void 0) result["cwd"] = server.cwd;
    if (server.env !== void 0) result["env"] = server.env;
  } else if (server.transport === "http") {
    result["httpUrl"] = server.httpUrl;
    if (server.headers !== void 0) result["headers"] = server.headers;
  } else {
    result["url"] = server.url;
    if (server.headers !== void 0) result["headers"] = server.headers;
  }
  return result;
}
__name(toStoredMcpServerConfig, "toStoredMcpServerConfig");
function toMcpServerConfig(value) {
  const server = toRecord(value);
  if (typeof server["httpUrl"] === "string") {
    return {
      transport: "http",
      httpUrl: server["httpUrl"],
      headers: normalizeStringRecord(server["headers"]),
      timeout: normalizeOptionalNumber(server["timeout"]),
      trust: typeof server["trust"] === "boolean" ? server["trust"] : void 0,
      description: typeof server["description"] === "string" ? server["description"] : void 0,
      includeTools: normalizeStringArray(server["includeTools"]),
      excludeTools: normalizeStringArray(server["excludeTools"]),
      extensionName: typeof server["extensionName"] === "string" ? server["extensionName"] : void 0
    };
  }
  if (typeof server["url"] === "string") {
    return {
      transport: "sse",
      url: server["url"],
      headers: normalizeStringRecord(server["headers"]),
      timeout: normalizeOptionalNumber(server["timeout"]),
      trust: typeof server["trust"] === "boolean" ? server["trust"] : void 0,
      description: typeof server["description"] === "string" ? server["description"] : void 0,
      includeTools: normalizeStringArray(server["includeTools"]),
      excludeTools: normalizeStringArray(server["excludeTools"]),
      extensionName: typeof server["extensionName"] === "string" ? server["extensionName"] : void 0
    };
  }
  if (typeof server["command"] === "string") {
    return {
      transport: "stdio",
      command: server["command"],
      args: normalizeStringArray(server["args"]),
      cwd: typeof server["cwd"] === "string" ? server["cwd"] : void 0,
      env: normalizeStringRecord(server["env"]),
      timeout: normalizeOptionalNumber(server["timeout"]),
      trust: typeof server["trust"] === "boolean" ? server["trust"] : void 0,
      description: typeof server["description"] === "string" ? server["description"] : void 0,
      includeTools: normalizeStringArray(server["includeTools"]),
      excludeTools: normalizeStringArray(server["excludeTools"]),
      extensionName: typeof server["extensionName"] === "string" ? server["extensionName"] : void 0
    };
  }
  return void 0;
}
__name(toMcpServerConfig, "toMcpServerConfig");
var REDACTED_MCP_SECRET = "__redacted__";
function redactMcpServerSecrets(server) {
  const redactValues = /* @__PURE__ */ __name((record) => record ? Object.fromEntries(
    Object.keys(record).map((key) => [key, REDACTED_MCP_SECRET])
  ) : record, "redactValues");
  return {
    ...server,
    env: redactValues(server.env),
    headers: redactValues(server.headers)
  };
}
__name(redactMcpServerSecrets, "redactMcpServerSecrets");
function restoreRedactedMcpSecrets(server, existing) {
  const restore = /* @__PURE__ */ __name((incoming, prior) => {
    if (!incoming) return incoming;
    const priorRecord = toRecord(prior);
    const result = {};
    for (const [key, value] of Object.entries(incoming)) {
      if (value !== REDACTED_MCP_SECRET) {
        result[key] = value;
        continue;
      }
      const priorValue = priorRecord[key];
      if (typeof priorValue === "string") {
        result[key] = priorValue;
      }
    }
    return result;
  }, "restore");
  return {
    ...server,
    env: restore(server.env, existing["env"]),
    headers: restore(server.headers, existing["headers"])
  };
}
__name(restoreRedactedMcpSecrets, "restoreRedactedMcpSecrets");
function redactSecretRecord(record) {
  return record ? Object.fromEntries(
    Object.keys(record).map((key) => [key, REDACTED_MCP_SECRET])
  ) : record;
}
__name(redactSecretRecord, "redactSecretRecord");
function restoreSecretRecord(incoming, prior) {
  if (!incoming) return incoming;
  const priorRecord = toRecord(prior);
  const result = {};
  for (const [key, value] of Object.entries(incoming)) {
    if (value !== REDACTED_MCP_SECRET) {
      result[key] = value;
      continue;
    }
    const priorValue = priorRecord[key];
    if (typeof priorValue === "string") result[key] = priorValue;
  }
  return result;
}
__name(restoreSecretRecord, "restoreSecretRecord");
function redactHookSecrets(hook) {
  return {
    ...hook,
    hooks: hook.hooks.map((config) => ({
      ...config,
      ...config.env ? { env: redactSecretRecord(config.env) } : {},
      ...config.headers ? { headers: redactSecretRecord(config.headers) } : {}
    }))
  };
}
__name(redactHookSecrets, "redactHookSecrets");
function restoreRedactedHookSecrets(hook, prior) {
  const priorHooks = Array.isArray(prior["hooks"]) ? prior["hooks"] : [];
  return {
    ...hook,
    hooks: hook.hooks.map((config, i) => {
      const priorConfig = toRecord(priorHooks[i]);
      return {
        ...config,
        ...config.env ? { env: restoreSecretRecord(config.env, priorConfig["env"]) } : {},
        ...config.headers ? {
          headers: restoreSecretRecord(
            config.headers,
            priorConfig["headers"]
          )
        } : {}
      };
    })
  };
}
__name(restoreRedactedHookSecrets, "restoreRedactedHookSecrets");
function readMcpServers(source, scope) {
  const servers = toRecord(source["mcpServers"]);
  return Object.entries(servers).map(([name, value]) => {
    try {
      const server = toMcpServerConfig(value);
      return server ? { name, scope, server: redactMcpServerSecrets(server) } : void 0;
    } catch (error) {
      debugLogger6.warn(
        `Skipping malformed MCP server config [${scope}:${name}]:`,
        error
      );
      return void 0;
    }
  }).filter(
    (entry) => !!entry
  );
}
__name(readMcpServers, "readMcpServers");
function isHookEvent(value) {
  return typeof value === "string" && QWEN_HOOK_EVENTS.includes(value);
}
__name(isHookEvent, "isHookEvent");
function normalizeHookConfig(value) {
  const input = toRecord(value);
  const type = input["type"];
  if (type !== "command" && type !== "http") {
    throw RequestError.invalidParams(
      void 0,
      "Hook type must be command or http"
    );
  }
  const config = { type };
  if (type === "command") {
    const command = input["command"];
    if (typeof command !== "string" || !command.trim()) {
      throw RequestError.invalidParams(
        void 0,
        "Command hooks require a command"
      );
    }
    config.command = command.trim();
    config.env = normalizeStringRecord(input["env"]);
    if (typeof input["async"] === "boolean") config.async = input["async"];
    const shell = input["shell"];
    if (shell === "bash" || shell === "powershell") config.shell = shell;
  } else {
    const url = input["url"];
    if (typeof url !== "string" || !url.trim()) {
      throw RequestError.invalidParams(void 0, "HTTP hooks require a URL");
    }
    config.url = url.trim();
    config.headers = normalizeStringRecord(input["headers"]);
    config.allowedEnvVars = normalizeStringArray(input["allowedEnvVars"]);
    if (typeof input["once"] === "boolean") config.once = input["once"];
  }
  const timeout = normalizeOptionalNumber(input["timeout"]);
  if (timeout !== void 0) config.timeout = timeout;
  for (const key of ["name", "description", "statusMessage"]) {
    const item = input[key];
    if (typeof item === "string" && item.trim()) {
      config[key] = item.trim();
    }
  }
  return config;
}
__name(normalizeHookConfig, "normalizeHookConfig");
function normalizeHookDefinition(value) {
  const input = toRecord(value);
  const hooks = input["hooks"];
  if (!Array.isArray(hooks) || hooks.length === 0) {
    throw RequestError.invalidParams(
      void 0,
      "Hook definition requires at least one hook"
    );
  }
  const definition = {
    hooks: hooks.map(normalizeHookConfig)
  };
  if (typeof input["matcher"] === "string") {
    definition.matcher = input["matcher"];
  }
  if (typeof input["sequential"] === "boolean") {
    definition.sequential = input["sequential"];
  }
  return definition;
}
__name(normalizeHookDefinition, "normalizeHookDefinition");
function readHooks(source, scope, extensionName) {
  const hooksRoot = toRecord(source["hooks"]);
  const entries = [];
  for (const event of QWEN_HOOK_EVENTS) {
    const eventHooks = hooksRoot[event];
    if (!Array.isArray(eventHooks)) continue;
    eventHooks.forEach((hookValue, index) => {
      try {
        entries.push({
          event,
          scope,
          index,
          hook: redactHookSecrets(normalizeHookDefinition(hookValue)),
          extensionName
        });
      } catch (error) {
        debugLogger6.warn(
          `Skipping malformed hook entry [${scope}:${event}:${index}]:`,
          error
        );
      }
    });
  }
  return entries;
}
__name(readHooks, "readHooks");
function toSettingsScope(scope) {
  if (scope === "workspace") return "Workspace" /* Workspace */;
  if (scope === "user") return "User" /* User */;
  throw RequestError.invalidParams(
    void 0,
    "scope must be user or workspace"
  );
}
__name(toSettingsScope, "toSettingsScope");
function readScopeSettings(settings, scope) {
  return settings.forScope(toSettingsScope(scope)).settings;
}
__name(readScopeSettings, "readScopeSettings");
async function resolvePreferredMemoryFile(dir, fallbackFilename) {
  for (const filename of getAllGeminiMdFilenames()) {
    const filePath = path3.join(dir, filename);
    try {
      await fs.access(filePath);
      return filePath;
    } catch {
    }
  }
  return path3.join(dir, fallbackFilename);
}
__name(resolvePreferredMemoryFile, "resolvePreferredMemoryFile");
async function resolveQwenMemoryPaths(params) {
  const fallbackFilename = getAllGeminiMdFilenames()[0] ?? "QWEN.md";
  const userMemoryFile = await resolvePreferredMemoryFile(
    Storage.getGlobalQwenDir(),
    fallbackFilename
  );
  const projectMemoryFile = await resolvePreferredMemoryFile(
    params.cwd,
    fallbackFilename
  );
  const autoMemoryDir = getAutoMemoryRoot(params.projectRoot);
  return {
    userMemoryFile,
    projectMemoryFile,
    autoMemoryDir
  };
}
__name(resolveQwenMemoryPaths, "resolveQwenMemoryPaths");
async function deliverClientMcpMessage(connection, serverName, message) {
  if (!connection) {
    throw new Error(
      `client MCP server '${serverName}' has no ACP connection yet`
    );
  }
  const response = await connection.extMethod(
    SERVE_CONTROL_EXT_METHODS.clientMcpMessage,
    { server: serverName, payload: message }
  );
  const payload = response["payload"];
  if (payload === void 0 || payload === null) {
    throw new Error(
      `client_mcp/message returned no payload for server '${serverName}'`
    );
  }
  return payload;
}
__name(deliverClientMcpMessage, "deliverClientMcpMessage");
async function runAcpAgent(config, settings, argv) {
  let acpConnection;
  const bootstrapClientMcpSender = /* @__PURE__ */ __name((serverName, message) => deliverClientMcpMessage(acpConnection, serverName, message), "bootstrapClientMcpSender");
  await config.initialize({
    skipGeminiInitialization: true,
    // Bootstrap skips MCP discovery — each session runs its own
    // pool-routed discovery, so bootstrap-level spawns would be
    // redundant subprocess leaks (W119).
    skipMcpDiscovery: true,
    // Bind the workspace-level manager's SDK callback so a runtime-added
    // client-hosted MCP server (#5626) round-trips over the parent WS.
    sendSdkMcpMessage: bootstrapClientMcpSender
  });
  const eventLoopMonitor = startEventLoopLagMonitor({
    onNewMaxStall: /* @__PURE__ */ __name((maxMs) => {
      console.error(`[perf] acp agent event loop stall: max=${maxMs}ms`);
    }, "onNewMaxStall")
  });
  let agentInstance;
  let connection;
  try {
    const stdout = Writable.toWeb(process.stdout);
    const stdin = Readable.toWeb(process.stdin);
    console.log = console.error;
    console.info = console.error;
    console.debug = console.error;
    const stream = ndJsonStream(stdout, stdin);
    connection = new AgentSideConnection((conn) => {
      acpConnection = conn;
      agentInstance = new QwenAgent(config, settings, argv, conn);
      return agentInstance;
    }, stream);
  } catch (err) {
    eventLoopMonitor.dispose();
    throw err;
  }
  registerAcpEventLoopLagGauge(() => eventLoopMonitor.snapshot());
  const drainPoolBeforeExit = /* @__PURE__ */ __name(async (label) => {
    if (!agentInstance) return;
    try {
      await agentInstance.shutdownMcpPool(8e3);
    } catch (err) {
      debugLogger6.error(`[ACP] MCP pool drain (${label}) error:`, err);
    }
  }, "drainPoolBeforeExit");
  let shuttingDown = false;
  let sessionEndFired = false;
  const fireSessionEndOnce = /* @__PURE__ */ __name(async (reason) => {
    if (sessionEndFired) return;
    sessionEndFired = true;
    const configs = /* @__PURE__ */ new Set([config]);
    const sessions = agentInstance?.getActiveSessions();
    if (sessions) {
      for (const session of sessions) {
        const sessionConfig = session.getConfig?.();
        if (sessionConfig) {
          configs.add(sessionConfig);
        }
      }
    }
    for (const cfg of configs) {
      const hookSystem = cfg.getHookSystem?.();
      const hooksEnabled = !cfg.getDisableAllHooks?.();
      if (!hooksEnabled || !hookSystem || !cfg.hasHooksForEvent?.("SessionEnd")) {
        continue;
      }
      try {
        await hookSystem.fireSessionEndEvent(reason);
      } catch (err) {
        debugLogger6.warn(
          `SessionEnd hook failed: ${err instanceof Error ? err.message : String(err)}`
        );
      }
    }
  }, "fireSessionEndOnce");
  const shutdownHandler = /* @__PURE__ */ __name(async () => {
    if (shuttingDown) return;
    shuttingDown = true;
    debugLogger6.debug("[ACP] Shutdown signal received, closing streams");
    try {
      await fireSessionEndOnce("other" /* Other */);
      agentInstance?.disposeSessions();
      try {
        process.stdin.destroy();
      } catch {
      }
      try {
        process.stdout.destroy();
      } catch {
      }
      await drainPoolBeforeExit("signal");
    } catch (err) {
      debugLogger6.error("[ACP] Shutdown error:", err);
    } finally {
      eventLoopMonitor.dispose();
    }
    runExitCleanup().catch((err) => {
      debugLogger6.error("[ACP] Cleanup error:", err);
    }).finally(() => {
      process.exit(0);
    });
  }, "shutdownHandler");
  process.on("SIGTERM", shutdownHandler);
  process.on("SIGINT", shutdownHandler);
  try {
    await connection.closed;
    await fireSessionEndOnce("prompt_input_exit" /* PromptInputExit */);
    await drainPoolBeforeExit("ide_close");
    agentInstance?.disposeSessions();
  } finally {
    process.off("SIGTERM", shutdownHandler);
    process.off("SIGINT", shutdownHandler);
    eventLoopMonitor.dispose();
  }
}
__name(runAcpAgent, "runAcpAgent");
function toStdioServer(server) {
  if ("command" in server && "args" in server && "env" in server) {
    return server;
  }
  return void 0;
}
__name(toStdioServer, "toStdioServer");
function toSseServer(server) {
  if ("type" in server && server.type === "sse") {
    return server;
  }
  return void 0;
}
__name(toSseServer, "toSseServer");
function toHttpServer(server) {
  if ("type" in server && server.type === "http") {
    return server;
  }
  return void 0;
}
__name(toHttpServer, "toHttpServer");
function parsePooledTransports(envValue) {
  if (!envValue || !envValue.trim()) return POOLED_TRANSPORTS_DEFAULT;
  const KNOWN = /* @__PURE__ */ new Set([
    "stdio",
    "websocket",
    "http",
    "sse"
  ]);
  const out = /* @__PURE__ */ new Set();
  for (const raw of envValue.split(",")) {
    const trimmed = raw.trim().toLowerCase();
    if (KNOWN.has(trimmed)) {
      out.add(trimmed);
    }
  }
  return out.size > 0 ? out : POOLED_TRANSPORTS_DEFAULT;
}
__name(parsePooledTransports, "parsePooledTransports");
function parsePoolDrainMs(envValue) {
  if (!envValue) return 3e4;
  const trimmed = envValue.trim();
  if (!/^\d+$/.test(trimmed)) {
    process.stderr.write(
      `qwen serve: QWEN_SERVE_MCP_POOL_DRAIN_MS=${JSON.stringify(envValue)} is not a valid integer; using default 30000ms.
`
    );
    return 3e4;
  }
  const n = Number.parseInt(trimmed, 10);
  if (!Number.isFinite(n)) return 3e4;
  return Math.min(6e5, Math.max(1e3, n));
}
__name(parsePoolDrainMs, "parsePoolDrainMs");
function createWorkspaceMcpBudget(onEvent) {
  const rawBudget = process.env["QWEN_SERVE_MCP_CLIENT_BUDGET"];
  const rawMode = process.env["QWEN_SERVE_MCP_BUDGET_MODE"];
  let budget;
  if (rawBudget !== void 0 && rawBudget !== "") {
    const trimmed = rawBudget.trim();
    const parsed = Number(trimmed);
    if (/^\d+$/.test(trimmed) && Number.isSafeInteger(parsed) && parsed > 0) {
      budget = parsed;
    } else {
      process.stderr.write(
        `qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET='${rawBudget}' (expected positive integer); MCP budget enforcement disabled for this child.
`
      );
    }
  }
  const mode = (() => {
    if (rawMode === "enforce" || rawMode === "warn" || rawMode === "off") {
      return rawMode;
    }
    return budget !== void 0 ? "warn" : "off";
  })();
  if (mode === "off" || budget === void 0) {
    return void 0;
  }
  return new WorkspaceMcpBudget({
    clientBudget: budget,
    mode,
    onEvent
  });
}
__name(createWorkspaceMcpBudget, "createWorkspaceMcpBudget");
var MAX_ACP_SESSION_PAGE_SIZE = 100;
function normalizeAcpSessionListSize(value) {
  if (value == null) return void 0;
  if (typeof value !== "number" || !Number.isSafeInteger(value)) {
    return void 0;
  }
  return Math.min(Math.max(value, 1), MAX_ACP_SESSION_PAGE_SIZE);
}
__name(normalizeAcpSessionListSize, "normalizeAcpSessionListSize");
function parseAcpSessionListCursor(value) {
  if (value == null || value === "") return void 0;
  const trimmed = value.trim();
  const parsedCursor = Number(trimmed);
  if (trimmed === "" || !Number.isFinite(parsedCursor) || parsedCursor < 0 || parsedCursor > Number.MAX_SAFE_INTEGER) {
    throw RequestError.invalidParams(
      void 0,
      `Invalid cursor: "${value}" is not a valid numeric cursor`
    );
  }
  return parsedCursor;
}
__name(parseAcpSessionListCursor, "parseAcpSessionListCursor");
var QwenAgent = class {
  constructor(config, settings, argv, connection) {
    this.config = config;
    this.settings = settings;
    this.argv = argv;
    this.connection = connection;
    if (process.env["QWEN_SERVE_NO_MCP_POOL"] === "1") {
      this.mcpPool = void 0;
      this.workspaceMcpBudget = void 0;
    } else {
      this.workspaceMcpBudget = createWorkspaceMcpBudget((event) => {
        this.broadcastBudgetEvent(event);
      });
      this.mcpPool = new McpTransportPool(this.config, {
        workspaceContext: this.config.getWorkspaceContext(),
        debugMode: this.config.getDebugMode(),
        // sendSdkMcpMessage left undefined: SDK MCP servers always
        // bypass the pool via createUnpooledConnection (per-session
        // routing through ACP control plane). The legacy
        // McpClientManager path retains its own per-session SDK
        // wiring; pool-mode discoverAllMcpToolsViaPool delegates SDK
        // MCP to that bypass.
        pooledTransports: parsePooledTransports(
          process.env["QWEN_SERVE_MCP_POOL_TRANSPORTS"]
        ),
        drainDelayMs: parsePoolDrainMs(
          process.env["QWEN_SERVE_MCP_POOL_DRAIN_MS"]
        ),
        budget: this.workspaceMcpBudget
      });
    }
  }
  static {
    __name(this, "QwenAgent");
  }
  sessions = /* @__PURE__ */ new Map();
  clientCapabilities;
  /**
   * Workspace-shared MCP transport pool. Eagerly constructed; lazy
   * w.r.t. actual MCP work — spawns nothing until `pool.acquire`.
   *
   * `undefined` when `QWEN_SERVE_NO_MCP_POOL=1` (kill switch); sessions
   * then fall back to per-session McpClient spawn.
   */
  mcpPool;
  /**
   * Workspace-scoped MCP budget controller. Constructed alongside
   * `mcpPool` when `--mcp-client-budget=N` is configured. `undefined`
   * when no budget is configured or pool kill switch is on.
   */
  workspaceMcpBudget;
  getActiveSessions() {
    return [...this.sessions.values()];
  }
  /**
   * Drain the workspace MCP transport pool. Called on shutdown so all
   * pool entries get a coordinated SIGTERM before process.exit. No-op
   * when pool is undefined (kill-switch mode).
   */
  async shutdownMcpPool(timeoutMs = 1e4) {
    if (!this.mcpPool) return;
    try {
      const result = await this.mcpPool.drainAll({ force: true, timeoutMs });
      if (result.forced > 0 || result.errors.length > 0) {
        debugLogger6.warn(
          `MCP pool drain: ${result.drained} clean, ${result.forced} timed out, ${result.errors.length} errors`
        );
      }
    } catch (err) {
      debugLogger6.error(
        `MCP pool drainAll failed: ${err instanceof Error ? err.message : String(err)}`
      );
    }
  }
  async closeStoredSession(sessionId, opts) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      this.mcpPool?.releaseSession(sessionId);
      return;
    }
    const requireFlush = opts?.requireFlush === true;
    const flushRecording = /* @__PURE__ */ __name(async () => {
      try {
        await session.getConfig().getChatRecordingService()?.flush();
        return void 0;
      } catch (err) {
        debugLogger6.debug(
          `Session ${sessionId} chat recording flush during close failed: ${err instanceof Error ? err.message : String(err)}`
        );
        return err;
      }
    }, "flushRecording");
    if (requireFlush) {
      const preCancelFlushError = await flushRecording();
      if (preCancelFlushError !== void 0) {
        throw preCancelFlushError;
      }
    }
    try {
      await session.cancelPendingPrompt();
    } catch (err) {
      debugLogger6.debug(
        `Session ${sessionId} cancel during close failed: ${err instanceof Error ? err.message : String(err)}`
      );
    }
    const flushError = await flushRecording();
    if (flushError !== void 0 && requireFlush) {
      throw flushError;
    }
    try {
      await session.getConfig().getToolRegistry()?.stop();
    } catch (err) {
      debugLogger6.debug(
        `Session ${sessionId} tool registry stop during close failed: ${err instanceof Error ? err.message : String(err)}`
      );
    }
    unregisterGoalHook(session.getConfig(), sessionId);
    this.mcpPool?.releaseSession(sessionId);
    uiTelemetryService.removeSession(sessionId);
    this.sessions.delete(sessionId);
  }
  disposeSessions() {
    for (const session of this.sessions.values()) {
      session.dispose();
    }
    this.sessions.clear();
  }
  /** Expose the pool's workspace-scoped budget controller for snapshot builders. */
  getWorkspaceMcpBudget() {
    return this.workspaceMcpBudget;
  }
  /**
   * Fan-out a workspace-scoped MCP budget event to every active
   * session's SSE bus. Each notification is independently
   * fire-and-forget.
   */
  broadcastBudgetEvent(event) {
    const sessionIds = Array.from(this.sessions.keys());
    for (const sid of sessionIds) {
      void this.connection.extNotification("qwen/notify/session/mcp-budget-event", {
        v: 1,
        sessionId: sid,
        // Tag workspace-scoped events so SDK reducers can branch.
        scope: "workspace",
        ...event
      }).catch((err) => {
        debugLogger6.debug(
          `MCP workspace budget event delivery to session ${sid} failed (kind=${event.kind}): ${err instanceof Error ? err.message : String(err)}`
        );
      });
    }
  }
  async initialize(args) {
    this.clientCapabilities = args.clientCapabilities;
    const authMethods = buildAuthMethods();
    const version = "0.19.6";
    return {
      protocolVersion: PROTOCOL_VERSION,
      agentInfo: {
        name: "qwen-code",
        title: "Qwen Code",
        version
      },
      authMethods,
      agentCapabilities: {
        loadSession: true,
        promptCapabilities: {
          image: true,
          audio: true,
          embeddedContext: true
        },
        sessionCapabilities: {
          list: {},
          resume: {}
        },
        mcpCapabilities: {
          sse: true,
          http: true
        }
      }
    };
  }
  async authenticate({ methodId }) {
    const method = external_exports.nativeEnum(AuthType).parse(methodId);
    let authUri;
    const authUriHandler = /* @__PURE__ */ __name((deviceAuth) => {
      authUri = deviceAuth.verification_uri_complete;
      void this.connection.extNotification("authenticate/update", {
        _meta: { authUri }
      });
    }, "authUriHandler");
    if (method === "qwen-oauth" /* QWEN_OAUTH */) {
      qwenOAuth2Events.once("auth-uri" /* AuthUri */, authUriHandler);
    }
    await clearCachedCredentialFile();
    try {
      await this.config.refreshAuth(method);
      this.settings.setValue(
        "User" /* User */,
        "security.auth.selectedType",
        method
      );
    } finally {
      if (method === "qwen-oauth" /* QWEN_OAUTH */) {
        qwenOAuth2Events.off("auth-uri" /* AuthUri */, authUriHandler);
      }
    }
  }
  async newSession({
    cwd,
    mcpServers
  }) {
    const config = await this.newSessionConfig(cwd, mcpServers);
    await this.ensureAuthenticated(config);
    this.setupFileSystem(config);
    const session = await this.createAndStoreSession(config);
    const availableModels = this.buildAvailableModels(config);
    const modesData = this.buildModesData(config);
    const configOptions = this.buildConfigOptions(config);
    return {
      sessionId: session.getId(),
      models: availableModels,
      modes: modesData,
      configOptions
    };
  }
  async loadSession(params) {
    const exists = await runWithAcpRuntimeOutputDir(
      this.settings,
      params.cwd,
      async () => {
        const sessionService = new SessionService(params.cwd);
        return sessionService.sessionExists(params.sessionId);
      }
    );
    if (!exists) {
      throw RequestError.resourceNotFound(`session:${params.sessionId}`);
    }
    const config = await this.newSessionConfig(
      params.cwd,
      // `LoadSessionRequest.mcpServers` is required in today's ACP
      // schema, but mirror `unstable_resumeSession` and tolerate a
      // future loosening — `newSessionConfig` iterates the list, so
      // a `null`/`undefined` would otherwise throw `TypeError`.
      params.mcpServers ?? [],
      params.sessionId,
      true
    );
    await this.ensureAuthenticated(config);
    this.setupFileSystem(config);
    const sessionData = config.getResumedSessionData();
    const session = await this.createAndStoreSession(config, sessionData);
    await this.#restoreWorktreeOnResume(config, session);
    const modesData = this.buildModesData(config);
    const availableModels = this.buildAvailableModels(config);
    const configOptions = this.buildConfigOptions(config);
    return {
      modes: modesData,
      models: availableModels,
      configOptions
    };
  }
  async unstable_resumeSession(params) {
    const exists = await runWithAcpRuntimeOutputDir(
      this.settings,
      params.cwd,
      async () => {
        const sessionService = new SessionService(params.cwd);
        return sessionService.sessionExists(params.sessionId);
      }
    );
    if (!exists) {
      throw RequestError.resourceNotFound(`session:${params.sessionId}`);
    }
    const config = await this.newSessionConfig(
      params.cwd,
      params.mcpServers ?? [],
      params.sessionId,
      true
    );
    await this.ensureAuthenticated(config);
    this.setupFileSystem(config);
    const session = await this.createAndStoreSession(
      config,
      config.getResumedSessionData(),
      { replayHistory: false }
    );
    await this.#restoreWorktreeOnResume(config, session);
    const modesData = this.buildModesData(config);
    const availableModels = this.buildAvailableModels(config);
    const configOptions = this.buildConfigOptions(config);
    return {
      modes: modesData,
      models: availableModels,
      configOptions
    };
  }
  /**
   * Shared worktree restore for both ACP entry points (`loadSession` and
   * `unstable_resumeSession`). Best-effort: failures don't block session
   * load — worktree context is a hint to the model, not a correctness
   * requirement.
   */
  async #restoreWorktreeOnResume(config, session) {
    try {
      const sessionPath = config.getSessionService().getWorktreeSessionPath(config.getSessionId());
      const restored = await restoreWorktreeContext(sessionPath);
      if (restored.contextMessage) {
        session.pendingWorktreeNotice = restored.contextMessage;
      }
    } catch (error) {
      debugLogger6.warn(`ACP worktree restore failed: ${error}`);
    }
  }
  async unstable_listSessions(params) {
    const cwd = params.cwd || process.cwd();
    const numericCursor = parseAcpSessionListCursor(params.cursor);
    const size = normalizeAcpSessionListSize(params._meta?.["size"]);
    const result = await runWithAcpRuntimeOutputDir(this.settings, cwd, () => {
      const sessionService = new SessionService(cwd);
      return sessionService.listSessions({
        cursor: numericCursor,
        size
      });
    });
    const sessions = result.items.map((item) => ({
      _meta: {
        createdAt: item.startTime,
        startTime: item.startTime,
        preview: item.prompt,
        ...item.gitBranch ? { gitBranch: item.gitBranch } : {},
        ...item.titleSource ? { titleSource: item.titleSource } : {}
      },
      cwd: item.cwd,
      sessionId: item.sessionId,
      title: item.customTitle || item.prompt || "(session)",
      updatedAt: new Date(item.mtime).toISOString()
    }));
    return {
      sessions,
      nextCursor: result.nextCursor != null ? String(result.nextCursor) : void 0
    };
  }
  async setSessionMode(params) {
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw RequestError.invalidParams(
        void 0,
        `Session not found for id: ${params.sessionId}`
      );
    }
    return session.setMode(params);
  }
  async unstable_setSessionModel(params) {
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw RequestError.invalidParams(
        void 0,
        `Session not found for id: ${params.sessionId}`
      );
    }
    return await session.setModel(params);
  }
  async setSessionConfigOption(params) {
    const { sessionId, configId, value } = params;
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw RequestError.invalidParams(
        void 0,
        `Session not found for id: ${sessionId}`
      );
    }
    switch (configId) {
      case "mode": {
        await this.setSessionMode({
          sessionId,
          modeId: value
        });
        break;
      }
      case "model": {
        await session.setModel(
          {
            sessionId,
            modelId: value
          },
          { persistDefault: false }
        );
        break;
      }
      default:
        throw RequestError.invalidParams(
          void 0,
          `Unsupported configId: ${configId}`
        );
    }
    return {
      configOptions: this.buildConfigOptions(session.getConfig())
    };
  }
  async prompt(params) {
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw new Error(`Session not found: ${params.sessionId}`);
    }
    return session.prompt(params);
  }
  async cancel(params) {
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw new Error(`Session not found: ${params.sessionId}`);
    }
    await session.cancelPendingPrompt();
  }
  loadPermissionSettings(cwd) {
    this.settings = loadSettings(cwd);
    return this.settings;
  }
  async buildCoreSettings(settings, cwd) {
    const userSettings = settings.user.settings;
    const workspaceSettings = settings.workspace.settings;
    const mergedSettings = settings.merged;
    let extensions = [];
    try {
      const extensionManager = new ExtensionManager({
        workspaceDir: cwd,
        isWorkspaceTrusted: settings.isTrusted,
        locale: getCurrentLanguage()
      });
      await extensionManager.refreshCache();
      extensions = extensionManager.getLoadedExtensions();
    } catch (error) {
      debugLogger6.warn(
        "Extension loading failed, continuing without extensions:",
        error
      );
    }
    const extensionEntries = await Promise.all(
      extensions.map(async (extension) => {
        const userEnv = await getScopedEnvContents(
          extension.config,
          extension.id,
          "user" /* USER */
        );
        const workspaceEnv = await getScopedEnvContents(
          extension.config,
          extension.id,
          "workspace" /* WORKSPACE */
        );
        const settingDefs = extension.settings ?? [];
        return {
          id: extension.id,
          name: extension.name,
          displayName: extension.displayName,
          version: extension.version,
          isActive: extension.isActive,
          path: extension.path,
          commands: extension.commands ?? [],
          skills: (extension.skills ?? []).map((skill) => skill.name),
          mcpServers: Object.keys(extension.config.mcpServers ?? {}),
          settings: settingDefs.map((setting) => {
            const userValue = userEnv[setting.envVar];
            const workspaceValue = workspaceEnv[setting.envVar];
            const hasWorkspaceValue = workspaceValue !== void 0;
            const hasUserValue = userValue !== void 0;
            const effectiveValue = hasWorkspaceValue ? workspaceValue : userValue;
            const effectiveScope = hasWorkspaceValue ? "workspace" : hasUserValue ? "user" : void 0;
            return {
              name: setting.name,
              description: setting.description,
              envVar: setting.envVar,
              sensitive: !!setting.sensitive,
              userValue: setting.sensitive ? void 0 : userValue,
              workspaceValue: setting.sensitive ? void 0 : workspaceValue,
              effectiveValue: setting.sensitive ? void 0 : effectiveValue,
              effectiveScope,
              hasUserValue,
              hasWorkspaceValue
            };
          })
        };
      })
    );
    const activeExtensions = extensions.filter(
      (extension) => extension.isActive
    );
    const extensionMcpServers = activeExtensions.flatMap(
      (extension) => readMcpServers(
        { mcpServers: extension.config.mcpServers ?? {} },
        "extension"
      ).map((entry) => ({
        ...entry,
        server: {
          ...entry.server,
          extensionName: extension.displayName ?? extension.name
        }
      }))
    );
    const extensionHooks = activeExtensions.flatMap(
      (extension) => readHooks(
        { hooks: extension.hooks ?? {} },
        "extension",
        extension.displayName ?? extension.name
      )
    );
    const mergedMcpByName = /* @__PURE__ */ new Map();
    for (const entry of readMcpServers(userSettings, "user")) {
      mergedMcpByName.set(entry.name, entry);
    }
    if (settings.isTrusted) {
      for (const entry of readMcpServers(workspaceSettings, "workspace")) {
        mergedMcpByName.set(entry.name, entry);
      }
    }
    const mergedHooks = [
      ...readHooks(userSettings, "user"),
      ...settings.isTrusted ? readHooks(workspaceSettings, "workspace") : []
    ];
    return {
      user: {
        path: settings.user.path,
        values: readCoreSettingValues(userSettings),
        mcpServers: readMcpServers(userSettings, "user"),
        hooks: readHooks(userSettings, "user")
      },
      workspace: {
        path: settings.workspace.path,
        values: readCoreSettingValues(workspaceSettings),
        mcpServers: readMcpServers(workspaceSettings, "workspace"),
        hooks: readHooks(workspaceSettings, "workspace")
      },
      merged: {
        values: readCoreSettingValues(mergedSettings),
        mcpServers: [...mergedMcpByName.values(), ...extensionMcpServers],
        hooks: [...mergedHooks, ...extensionHooks]
      },
      extensions: extensionEntries,
      isTrusted: settings.isTrusted
    };
  }
  syncLivePermissionManagers(before, after) {
    for (const ruleType of PERMISSION_RULE_TYPES) {
      const oldRules = new Set(before[ruleType]);
      const newRules = new Set(after[ruleType]);
      const removed = before[ruleType].filter((rule) => !newRules.has(rule));
      const added = after[ruleType].filter((rule) => !oldRules.has(rule));
      if (removed.length === 0 && added.length === 0) continue;
      for (const session of this.sessions.values()) {
        const pm = session.getConfig().getPermissionManager?.();
        if (!pm) continue;
        try {
          for (const rule of removed) {
            pm.removePersistentRule(rule, ruleType);
          }
          for (const rule of added) {
            pm.addPersistentRule(rule, ruleType);
          }
        } catch (error) {
          debugLogger6.warn(
            `Failed to sync permission rules to a live session: ${error instanceof Error ? error.message : String(error)}`
          );
        }
      }
    }
  }
  workspaceCwd(config) {
    return config.getTargetDir();
  }
  safeWorkspaceCwd(config) {
    try {
      return this.workspaceCwd(config);
    } catch {
      return "";
    }
  }
  mcpTransport(server) {
    if (!server || typeof server !== "object") return "unknown";
    const s = server;
    if (s["type"] === "sdk") return "sdk";
    if (typeof s["httpUrl"] === "string") return "http";
    if (typeof s["url"] === "string") return "sse";
    if (typeof s["tcp"] === "string") return "websocket";
    if (typeof s["command"] === "string") return "stdio";
    return "unknown";
  }
  mcpStatus(status) {
    switch (status) {
      case "connected" /* CONNECTED */:
        return "connected";
      case "connecting" /* CONNECTING */:
        return "connecting";
      case "disconnected" /* DISCONNECTED */:
      default:
        return "disconnected";
    }
  }
  mcpCellStatus(status, disabled) {
    if (disabled) return "disabled";
    switch (status) {
      case "connected" /* CONNECTED */:
        return "ok";
      case "connecting" /* CONNECTING */:
        return "warning";
      case "disconnected" /* DISCONNECTED */:
      default:
        return "error";
    }
  }
  discoveryState() {
    const state = getMCPDiscoveryState();
    switch (state) {
      case "in_progress" /* IN_PROGRESS */:
        return "in_progress";
      case "completed" /* COMPLETED */:
        return "completed";
      case "not_started" /* NOT_STARTED */:
      default:
        return "not_started";
    }
  }
  async buildWorkspaceMcpStatus(config) {
    try {
      const workspaceCwd = this.workspaceCwd(config);
      const settings = loadSettings(config.getTargetDir());
      const workspaceSettings = settings.forScope(
        "Workspace" /* Workspace */
      ).settings;
      const servers = config.getMcpServers() ?? {};
      let poolByName = {};
      try {
        const snap = this.mcpPool?.getSnapshot();
        if (snap) poolByName = snap.byName;
      } catch (err) {
        process.stderr.write(
          `qwen serve: pool snapshot for workspace MCP status failed: ${err instanceof Error ? err.message : String(err)}
`
        );
      }
      let clientCount;
      let clientBudget;
      let budgetMode;
      let refusedSet = /* @__PURE__ */ new Set();
      let budgetCellScope = "session";
      const wsBudget = this.workspaceMcpBudget;
      if (wsBudget !== void 0) {
        budgetCellScope = "workspace";
        clientCount = wsBudget.getReservedCount();
        clientBudget = wsBudget.getBudget();
        budgetMode = this.coerceBudgetMode(wsBudget.getMode());
        refusedSet = new Set(wsBudget.getRefusedServerNames());
      } else {
        try {
          const manager = config.getToolRegistry()?.getMcpClientManager();
          if (manager) {
            const accounting = manager.getMcpClientAccounting();
            clientCount = accounting.total;
            clientBudget = manager.getMcpClientBudget();
            budgetMode = manager.getMcpBudgetMode();
            refusedSet = new Set(accounting.refusedServerNames);
          }
        } catch (err) {
          process.stderr.write(
            `qwen serve: getMcpClientAccounting failed: ${err instanceof Error ? err.message : String(err)}
`
          );
        }
      }
      const sharedTokenStorage = new MCPOAuthTokenStorage();
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd,
        initialized: true,
        discoveryState: this.discoveryState(),
        servers: await Promise.all(
          Object.entries(servers).map(async ([name, server]) => {
            const disabled = config.isMcpServerDisabled(name);
            let hasOAuthTokens = false;
            try {
              const credentials = await sharedTokenStorage.getCredentials(name);
              hasOAuthTokens = credentials !== null;
            } catch {
            }
            const rawStatus = getMCPServerStatus(name);
            const refusedByBudget = refusedSet.has(name);
            const effectivelyRefused = refusedByBudget && !disabled;
            const out = {
              kind: "mcp_server",
              // Refused-by-budget shadows the raw status: the rawStatus
              // is `DISCONNECTED` (we never tried to connect), but the
              // operator-facing severity is `error` with an explanatory
              // errorKind rather than the generic disconnected `error`.
              status: effectivelyRefused ? "error" : this.mcpCellStatus(rawStatus, disabled),
              name,
              mcpStatus: this.mcpStatus(rawStatus),
              transport: this.mcpTransport(server),
              disabled,
              hasOAuthTokens
            };
            if (effectivelyRefused) {
              out.errorKind = "budget_exhausted";
              out.disabledReason = "budget";
              out.hint = "Raise --mcp-client-budget or remove servers from mcpServers config.";
            } else if (disabled) {
              out.disabledReason = "config";
            }
            const description = server && typeof server === "object" ? server.description : void 0;
            const extensionName = server && typeof server === "object" ? server.extensionName : void 0;
            if (typeof description === "string") {
              out.description = description;
            }
            if (typeof extensionName === "string") {
              out.extensionName = extensionName;
            }
            out.source = out.extensionName ? "extension" : workspaceSettings.mcpServers?.[name] ? "project" : "user";
            if (server && typeof server === "object") {
              const candidate = server;
              const serverConfig = {};
              if (typeof candidate.command === "string") {
                serverConfig.command = candidate.command;
              }
              if (Array.isArray(candidate.args)) {
                const args = candidate.args.filter(
                  (arg) => typeof arg === "string"
                );
                if (args.length > 0) {
                  serverConfig.args = args;
                }
              }
              if (typeof candidate.httpUrl === "string") {
                serverConfig.httpUrl = candidate.httpUrl;
              }
              if (typeof candidate.url === "string") {
                serverConfig.url = candidate.url;
              }
              if (typeof candidate.cwd === "string") {
                serverConfig.cwd = candidate.cwd;
              }
              if (Object.keys(serverConfig).length > 0) {
                out.config = serverConfig;
              }
            }
            const poolRow = poolByName[name];
            if (poolRow) {
              out.entryCount = poolRow.entryCount;
              out.entrySummary = poolRow.entrySummary.map((e) => ({
                entryIndex: e.entryIndex,
                refs: e.refs,
                status: this.mcpStatus(e.status)
              }));
            }
            if (!disabled) {
              out.resourceCount = this.resolveServerMcpResources(
                config,
                name
              ).length;
              out.promptCount = this.resolveServerMcpPrompts(
                config,
                name
              ).length;
            }
            return out;
          })
        ),
        ...clientCount !== void 0 ? { clientCount } : {},
        ...clientBudget !== void 0 ? { clientBudget } : {},
        ...budgetMode !== void 0 ? { budgetMode } : {},
        ...budgetMode !== void 0 ? {
          // Filter out config-disabled servers so the workspace
          // cell matches the per-server cell precedence.
          budgets: this.buildBudgetCells(
            clientCount ?? 0,
            clientBudget,
            budgetMode,
            Array.from(refusedSet).filter(
              (n) => !config.isMcpServerDisabled(n)
            ).length,
            budgetCellScope
          )
        } : {}
      };
    } catch (error) {
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd: this.safeWorkspaceCwd(config),
        initialized: true,
        servers: [],
        errors: [this.errorCell("mcp", error)]
      };
    }
  }
  buildWorkspaceMcpToolsStatus(config, serverName) {
    const workspaceCwd = this.safeWorkspaceCwd(config);
    try {
      const servers = config.getMcpServers() ?? {};
      if (!Object.prototype.hasOwnProperty.call(servers, serverName)) {
        return {
          v: STATUS_SCHEMA_VERSION,
          workspaceCwd,
          serverName,
          initialized: true,
          acpChannelLive: true,
          tools: [],
          errors: [
            {
              kind: "mcp_tools",
              status: "error",
              error: `MCP server not configured: ${serverName}`
            }
          ]
        };
      }
      let registry = config.getToolRegistry();
      let allTools = registry?.getAllTools() ?? [];
      if (allTools.filter(
        (t) => t instanceof DiscoveredMCPTool && t.serverName === serverName
      ).length === 0) {
        for (const session of this.getActiveSessions()) {
          const sessionRegistry = session.getConfig().getToolRegistry();
          const sessionTools = sessionRegistry?.getAllTools() ?? [];
          if (sessionTools.some(
            (t) => t instanceof DiscoveredMCPTool && t.serverName === serverName
          )) {
            registry = sessionRegistry;
            allTools = sessionTools;
            break;
          }
        }
      }
      const tools = allTools.filter(
        (tool) => tool instanceof DiscoveredMCPTool && tool.serverName === serverName
      ).map((tool) => {
        const invalidReasons = [];
        if (!tool.name) invalidReasons.push("missing name");
        if (!tool.description) invalidReasons.push("missing description");
        const schema = tool.parameterSchema && typeof tool.parameterSchema === "object" && !Array.isArray(tool.parameterSchema) ? tool.parameterSchema : void 0;
        const annotations = tool.annotations && typeof tool.annotations === "object" && !Array.isArray(tool.annotations) ? tool.annotations : void 0;
        return {
          name: tool.name || "(unnamed)",
          serverToolName: tool.serverToolName,
          description: tool.description,
          ...schema ? { schema } : {},
          ...annotations ? { annotations } : {},
          isValid: invalidReasons.length === 0,
          ...invalidReasons.length > 0 ? { invalidReason: invalidReasons.join(", ") } : {}
        };
      });
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd,
        serverName,
        initialized: true,
        acpChannelLive: true,
        tools
      };
    } catch (error) {
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd,
        serverName,
        initialized: true,
        acpChannelLive: true,
        tools: [],
        errors: [this.errorCell("mcp_tools", error)]
      };
    }
  }
  /**
   * Resolve the resources discovered for one server, with the same
   * pool-mode fallback `buildWorkspaceMcpToolsStatus` uses for tools: the
   * workspace `Config`'s `ResourceRegistry` is authoritative in
   * single-session mode, but in pool mode resources are registered into
   * per-session registries (`SessionMcpView.applyResources`), leaving the
   * workspace registry empty. Fall back to the first active session that
   * has this server's resources.
   */
  resolveServerMcpResources(config, serverName) {
    const resources = config.getResourceRegistry?.()?.getResourcesByServer(serverName) ?? [];
    if (resources.length > 0) {
      return resources;
    }
    for (const session of this.getActiveSessions()) {
      try {
        const sessionResources = session.getConfig().getResourceRegistry?.()?.getResourcesByServer(serverName) ?? [];
        if (sessionResources.length > 0) {
          return sessionResources;
        }
      } catch {
      }
    }
    return resources;
  }
  /**
   * Resolve the prompts discovered for one server, mirroring
   * {@link resolveServerMcpResources}. Used only for the per-server
   * `promptCount` on the base status — prompts have no drill-down
   * endpoint (they surface as slash commands).
   */
  resolveServerMcpPrompts(config, serverName) {
    const prompts = config.getPromptRegistry?.()?.getPromptsByServer(serverName) ?? [];
    if (prompts.length > 0) {
      return prompts;
    }
    for (const session of this.getActiveSessions()) {
      try {
        const sessionPrompts = session.getConfig().getPromptRegistry?.()?.getPromptsByServer(serverName) ?? [];
        if (sessionPrompts.length > 0) {
          return sessionPrompts;
        }
      } catch {
      }
    }
    return prompts;
  }
  buildWorkspaceMcpResourcesStatus(config, serverName) {
    const workspaceCwd = this.safeWorkspaceCwd(config);
    try {
      const servers = config.getMcpServers() ?? {};
      if (!Object.prototype.hasOwnProperty.call(servers, serverName)) {
        return {
          v: STATUS_SCHEMA_VERSION,
          workspaceCwd,
          serverName,
          initialized: true,
          acpChannelLive: true,
          resources: [],
          errors: [
            {
              kind: "mcp_resources",
              status: "error",
              error: `MCP server not configured: ${serverName}`
            }
          ]
        };
      }
      const resources = this.resolveServerMcpResources(config, serverName).map((resource) => ({
        uri: resource.uri,
        ...typeof resource.name === "string" ? { name: resource.name } : {},
        ...typeof resource.title === "string" ? { title: resource.title } : {},
        ...typeof resource.description === "string" ? { description: resource.description } : {},
        ...typeof resource.mimeType === "string" ? { mimeType: resource.mimeType } : {},
        ...typeof resource.size === "number" ? { size: resource.size } : {}
      }));
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd,
        serverName,
        initialized: true,
        acpChannelLive: true,
        resources
      };
    } catch (error) {
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd,
        serverName,
        initialized: true,
        acpChannelLive: true,
        resources: [],
        errors: [this.errorCell("mcp_resources", error)]
      };
    }
  }
  /**
   * Build the MCP budget status cells exposed on `GET /workspace/mcp`.
   *
   * Cell `status` semantics:
   *   - `error`   — refusals happened this pass (enforce mode only)
   *   - `warning` — live count crossed 75% of budget
   *   - `ok`      — under threshold (or `off` mode)
   *
   * `liveCount` is the connected-client count (for operator
   * observability), while enforcement uses `reservedSlots.size` to
   * prevent capacity races.
   */
  buildBudgetCells(liveCount, budget, mode, refusedCount, scope = "session") {
    if (mode === "off") return [];
    let status = "ok";
    let errorKind;
    let hint;
    if (refusedCount > 0) {
      status = "error";
      errorKind = "budget_exhausted";
      hint = "Raise --mcp-client-budget or remove servers from mcpServers config.";
    } else if (budget !== void 0 && budget > 0 && liveCount >= MCP_BUDGET_WARN_FRACTION * budget) {
      status = "warning";
      hint = `Live MCP clients are above ${Math.round(
        MCP_BUDGET_WARN_FRACTION * 100
      )}% of the configured budget.`;
    }
    const cell = {
      kind: "mcp_budget",
      // `scope` is 'workspace' when the workspace budget controller is
      // active, otherwise 'session' for legacy per-session caps.
      scope,
      status,
      liveCount,
      mode,
      refusedCount
    };
    if (budget !== void 0) cell.budget = budget;
    if (errorKind) cell.errorKind = errorKind;
    if (hint) cell.hint = hint;
    return [cell];
  }
  /** Map core `McpBudgetMode` to protocol `ServeMcpBudgetMode`. */
  coerceBudgetMode(mode) {
    return mode;
  }
  errorCell(kind, error, errorKind) {
    const inferred = errorKind ?? mapDomainErrorToErrorKind(error);
    return {
      kind,
      status: "error",
      error: error instanceof Error ? error.message : String(error),
      ...inferred ? { errorKind: inferred } : {}
    };
  }
  async buildWorkspaceSkillsStatus(config) {
    const skillManager = config.getSkillManager();
    if (!skillManager) {
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd: this.workspaceCwd(config),
        initialized: true,
        skills: []
      };
    }
    try {
      const disabled = config.getDisabledSkillNames();
      const skills = await skillManager.listSkills();
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd: this.workspaceCwd(config),
        initialized: true,
        skills: skills.map((skill) => mapSkillConfigToStatus(skill, disabled))
      };
    } catch (error) {
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd: this.workspaceCwd(config),
        initialized: true,
        skills: [],
        errors: [this.errorCell("skills", error)]
      };
    }
  }
  buildWorkspaceProvidersStatus(config) {
    try {
      const workspaceCwd = this.workspaceCwd(config);
      const currentAuthType = config.getAuthType?.();
      const activeRuntimeSnapshot = config.getActiveRuntimeModelSnapshot?.();
      const currentModelId = activeRuntimeSnapshot ? activeRuntimeSnapshot.id : (config.getModel() || "").trim();
      const hasCurrentModel = currentModelId.length > 0;
      const currentAuth = activeRuntimeSnapshot?.authType ?? currentAuthType;
      const currentAcpModelId = hasCurrentModel && currentAuth ? formatAcpModelId(currentModelId, currentAuth) : currentModelId || void 0;
      const providers = /* @__PURE__ */ new Map();
      for (const model of config.getAllConfiguredModels().filter(isMainSelectableModel)) {
        const authType = String(model.authType);
        let provider = providers.get(authType);
        if (!provider) {
          provider = {
            kind: "model_provider",
            status: "ok",
            authType,
            current: false,
            models: []
          };
          providers.set(authType, provider);
        }
        const effectiveModelId = model.isRuntimeModel && model.runtimeSnapshotId ? model.runtimeSnapshotId : model.id;
        const modelId = formatAcpModelId(effectiveModelId, model.authType);
        const isCurrent = currentAuth === model.authType && hasCurrentModel && (currentModelId === effectiveModelId || currentModelId === model.id || currentAcpModelId === modelId);
        const providerModel = {
          modelId,
          baseModelId: parseAcpBaseModelId(effectiveModelId),
          name: model.label,
          ...model.description !== void 0 ? { description: model.description } : {},
          contextLimit: model.contextWindowSize ?? tokenLimit(effectiveModelId),
          ...model.modalities !== void 0 ? { modalities: model.modalities } : {},
          ...model.baseUrl !== void 0 ? { baseUrl: sanitizeProviderBaseUrl(model.baseUrl) } : {},
          ...model.envKey !== void 0 ? { envKey: model.envKey } : {},
          isCurrent,
          isRuntime: model.isRuntimeModel === true
        };
        provider.models.push(providerModel);
        if (isCurrent) provider.current = true;
      }
      const cgConfig = config.getContentGeneratorConfig?.();
      const baseUrl = cgConfig?.baseUrl || void 0;
      const fastModelId = this.settings.merged?.fastModel || void 0;
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd,
        initialized: true,
        ...currentAuth || currentAcpModelId ? {
          current: {
            ...currentAuth ? { authType: String(currentAuth) } : {},
            ...currentAcpModelId ? { modelId: currentAcpModelId } : {},
            ...baseUrl ? { baseUrl: sanitizeProviderBaseUrl(baseUrl) } : {},
            ...fastModelId ? { fastModelId } : {}
          }
        } : {},
        providers: [...providers.values()]
      };
    } catch (error) {
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd: this.safeWorkspaceCwd(config),
        initialized: true,
        providers: [],
        errors: [this.errorCell("providers", error)]
      };
    }
  }
  async buildAcpPreflightCells(config) {
    const builders = {
      auth: /* @__PURE__ */ __name(() => this.buildAuthPreflightCell(config), "auth"),
      mcp_discovery: /* @__PURE__ */ __name(() => this.buildMcpDiscoveryPreflightCell(config), "mcp_discovery"),
      skills: /* @__PURE__ */ __name(() => this.buildSkillsPreflightCell(config), "skills"),
      providers: /* @__PURE__ */ __name(() => this.buildProvidersPreflightCell(config), "providers"),
      tool_registry: /* @__PURE__ */ __name(() => this.buildToolRegistryPreflightCell(config), "tool_registry"),
      egress: /* @__PURE__ */ __name(() => ({
        kind: "egress",
        status: "not_started",
        locality: "acp",
        hint: "egress probing not yet implemented"
      }), "egress")
    };
    const cells = [];
    for (const kind of ACP_PREFLIGHT_KINDS) {
      cells.push(await builders[kind]());
    }
    return { cells };
  }
  acpCell(kind, spec) {
    return { kind, locality: "acp", ...spec };
  }
  /**
   * Pure auth preflight check. Looks up the well-known env var keys for the
   * configured auth method (via `AUTH_ENV_MAPPINGS`) and reports whether at
   * least one is present.
   *
   * Deliberately does NOT call `validateAuthMethod` from `cli/config/auth.ts`:
   * that helper has side effects (reloads `.env` from disk via
   * `loadEnvironment`, writes `process.env['GOOGLE_GENAI_USE_VERTEXAI']` for
   * Vertex auth) which would let a read-only `GET /workspace/preflight`
   * mutate daemon state and produce torn snapshots when racing
   * `GET /workspace/env`. Full validation still happens at session start.
   */
  buildAuthPreflightCell(config) {
    try {
      const authType = config.getAuthType?.();
      if (!authType) {
        return this.acpCell("auth", {
          status: "warning",
          errorKind: "auth_env_error",
          error: "No auth method configured.",
          hint: "Run `qwen` and complete the auth flow, or set a provider env var.",
          detail: { source: "none", hasToken: false }
        });
      }
      const apiKeyVars = AUTH_PREFLIGHT_ENV_KEYS[String(authType)] ?? [];
      const presentVar = apiKeyVars.find(
        (name) => Boolean(process.env[name])
      );
      const hasToken = Boolean(presentVar);
      if (apiKeyVars.length === 0) {
        return this.acpCell("auth", {
          status: "unknown",
          hint: "Auth credentials for this provider are not env-keyed; full validation runs at session start.",
          detail: {
            source: String(authType),
            hasToken: "unknown",
            envVarCandidates: []
          }
        });
      }
      return this.acpCell("auth", {
        status: hasToken ? "ok" : "warning",
        ...hasToken ? {} : {
          errorKind: "auth_env_error",
          error: `None of the env vars [${apiKeyVars.join(", ")}] is set for authType '${String(authType)}'.`,
          hint: `Set one of: ${apiKeyVars.join(" / ")}.`
        },
        detail: {
          source: String(authType),
          hasToken,
          envVarCandidates: apiKeyVars,
          ...presentVar ? { presentVar } : {}
        }
      });
    } catch (err) {
      const errorKind = mapDomainErrorToErrorKind(err) ?? "auth_env_error";
      return this.acpCell("auth", {
        status: "error",
        error: err instanceof Error ? err.message : String(err),
        errorKind
      });
    }
  }
  buildMcpDiscoveryPreflightCell(config) {
    try {
      const discovery = this.discoveryState();
      const servers = config.getMcpServers() ?? {};
      const total = Object.keys(servers).length;
      const counts = {
        connected: 0,
        connecting: 0,
        disconnected: 0,
        unknown: 0
      };
      for (const name of Object.keys(servers)) {
        const raw = getMCPServerStatus(name);
        switch (raw) {
          case "connected" /* CONNECTED */:
            counts.connected += 1;
            break;
          case "connecting" /* CONNECTING */:
            counts.connecting += 1;
            break;
          case "disconnected" /* DISCONNECTED */:
            counts.disconnected += 1;
            break;
          default:
            counts.unknown += 1;
            break;
        }
      }
      const detail = { discoveryState: discovery, total, ...counts };
      if (total === 0) {
        return this.acpCell("mcp_discovery", {
          status: "ok",
          detail,
          hint: "No MCP servers configured."
        });
      }
      if (counts.unknown > 0) {
        return this.acpCell("mcp_discovery", {
          status: "warning",
          errorKind: "protocol_error",
          error: `${counts.unknown}/${total} MCP server(s) in an unrecognized state.`,
          detail
        });
      }
      if (counts.disconnected > 0 && discovery === "completed") {
        return this.acpCell("mcp_discovery", {
          status: "error",
          errorKind: "protocol_error",
          error: `${counts.disconnected}/${total} MCP server(s) disconnected after discovery.`,
          detail
        });
      }
      if (counts.connecting > 0 || discovery === "in_progress") {
        return this.acpCell("mcp_discovery", {
          status: "warning",
          error: `${counts.connecting}/${total} MCP server(s) still connecting.`,
          detail
        });
      }
      return this.acpCell("mcp_discovery", { status: "ok", detail });
    } catch (err) {
      const errorKind = mapDomainErrorToErrorKind(err);
      return this.acpCell("mcp_discovery", {
        status: "error",
        error: err instanceof Error ? err.message : String(err),
        ...errorKind ? { errorKind } : {}
      });
    }
  }
  async buildSkillsPreflightCell(config) {
    try {
      const skillManager = config.getSkillManager();
      if (!skillManager) {
        return this.acpCell("skills", {
          status: "disabled",
          // `disabled` here is the structural state — Config has no
          // SkillManager attached. That can mean the user opted out OR a
          // mis-config silently dropped the manager; preflight cannot
          // distinguish the two without settings introspection. Hint
          // surfaces the ambiguity so operators investigate when
          // unexpected.
          hint: "No SkillManager attached to Config; verify settings if you expected skills to load.",
          detail: { configured: false }
        });
      }
      const skills = await skillManager.listSkills();
      return this.acpCell("skills", {
        status: "ok",
        detail: { count: skills.length }
      });
    } catch (err) {
      const errorKind = mapDomainErrorToErrorKind(err);
      return this.acpCell("skills", {
        status: "error",
        error: err instanceof Error ? err.message : String(err),
        ...errorKind ? { errorKind } : {}
      });
    }
  }
  buildProvidersPreflightCell(config) {
    try {
      const models = config.getAllConfiguredModels();
      const authType = config.getAuthType?.();
      if (models.length === 0) {
        return this.acpCell("providers", {
          status: authType ? "error" : "disabled",
          ...authType ? { errorKind: "auth_env_error" } : {},
          ...authType ? {
            error: `No model configured for authType ${String(authType)}.`
          } : {},
          detail: { count: 0, authType: authType ? String(authType) : null }
        });
      }
      const authTypes = new Set(models.map((m) => String(m.authType)));
      return this.acpCell("providers", {
        status: "ok",
        detail: {
          count: models.length,
          providers: [...authTypes]
        }
      });
    } catch (err) {
      const errorKind = mapDomainErrorToErrorKind(err) ?? "auth_env_error";
      return this.acpCell("providers", {
        status: "error",
        error: err instanceof Error ? err.message : String(err),
        errorKind
      });
    }
  }
  buildToolRegistryPreflightCell(config) {
    try {
      const registry = config.getToolRegistry();
      if (!registry) {
        return this.acpCell("tool_registry", {
          status: "error",
          errorKind: "protocol_error",
          error: "Tool registry is not initialized."
        });
      }
      const tools = registry.getAllTools();
      return this.acpCell("tool_registry", {
        status: "ok",
        detail: { count: tools.length }
      });
    } catch (err) {
      const errorKind = mapDomainErrorToErrorKind(err) ?? "protocol_error";
      return this.acpCell("tool_registry", {
        status: "error",
        error: err instanceof Error ? err.message : String(err),
        errorKind
      });
    }
  }
  buildWorkspaceToolsStatus(config) {
    const workspaceCwd = this.safeWorkspaceCwd(config);
    try {
      const registry = config.getToolRegistry();
      if (!registry) {
        return {
          v: STATUS_SCHEMA_VERSION,
          workspaceCwd,
          initialized: true,
          acpChannelLive: true,
          tools: [],
          errors: [
            {
              kind: "tools",
              status: "error",
              errorKind: "protocol_error",
              error: "Tool registry is not initialized."
            }
          ]
        };
      }
      const disabled = config.getDisabledTools();
      const tools = registry.getAllTools().filter((tool) => !("serverName" in tool)).map((tool) => ({
        name: tool.name,
        displayName: tool.displayName,
        description: tool.description,
        enabled: !disabled.has(tool.name)
      }));
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd,
        initialized: true,
        acpChannelLive: true,
        tools
      };
    } catch (err) {
      const errorKind = mapDomainErrorToErrorKind(err) ?? "protocol_error";
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd,
        initialized: true,
        acpChannelLive: true,
        tools: [],
        errors: [
          {
            kind: "tools",
            status: "error",
            error: err instanceof Error ? err.message : String(err),
            errorKind
          }
        ]
      };
    }
  }
  sessionOrThrow(sessionId) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw RequestError.invalidParams(
        void 0,
        `Session not found for id: ${sessionId}`
      );
    }
    return session;
  }
  buildSessionContextStatus(sessionId) {
    const session = this.sessionOrThrow(sessionId);
    const config = session.getConfig();
    return {
      v: STATUS_SCHEMA_VERSION,
      sessionId,
      workspaceCwd: this.workspaceCwd(config),
      state: {
        models: this.buildAvailableModels(config),
        modes: this.buildModesData(config),
        configOptions: this.buildConfigOptions(config)
      }
    };
  }
  async buildSessionContextUsageStatus(sessionId, showDetails) {
    const session = this.sessionOrThrow(sessionId);
    const config = session.getConfig();
    let usage;
    try {
      usage = await collectContextData(config, showDetails);
    } catch (err) {
      console.warn("[context-usage] collectContextData failed:", err);
      usage = {
        type: "context_usage",
        modelName: config.getModel() || "unknown",
        totalTokens: 0,
        contextWindowSize: 0,
        breakdown: {
          systemPrompt: 0,
          builtinTools: 0,
          mcpTools: 0,
          memoryFiles: 0,
          skills: 0,
          messages: 0,
          freeSpace: 0,
          autocompactBuffer: 0
        },
        builtinTools: [],
        mcpTools: [],
        memoryFiles: [],
        skills: [],
        isEstimated: true,
        showDetails
      };
    }
    return {
      v: STATUS_SCHEMA_VERSION,
      sessionId,
      workspaceCwd: this.workspaceCwd(config),
      usage: {
        modelName: usage.modelName,
        totalTokens: usage.totalTokens,
        contextWindowSize: usage.contextWindowSize,
        breakdown: usage.breakdown,
        builtinTools: usage.builtinTools,
        mcpTools: usage.mcpTools,
        memoryFiles: usage.memoryFiles,
        skills: usage.skills,
        isEstimated: usage.isEstimated,
        showDetails: usage.showDetails
      },
      formattedText: formatContextUsageText(usage)
    };
  }
  async buildSessionSupportedCommandsStatus(sessionId) {
    const session = this.sessionOrThrow(sessionId);
    const { availableCommands, availableSkills } = await buildAvailableCommandsSnapshot(session.getConfig());
    return {
      v: STATUS_SCHEMA_VERSION,
      sessionId,
      availableCommands,
      availableSkills: availableSkills ?? []
    };
  }
  buildSessionTasksStatus(sessionId) {
    const session = this.sessionOrThrow(sessionId);
    return buildSessionTasksStatus(sessionId, session.getConfig());
  }
  buildSessionLspStatus(sessionId) {
    const session = this.sessionOrThrow(sessionId);
    const config = session.getConfig();
    const snapshot = config.getLspStatusSnapshot();
    return {
      v: STATUS_SCHEMA_VERSION,
      sessionId,
      workspaceCwd: this.workspaceCwd(config),
      enabled: snapshot.enabled,
      configuredServers: snapshot.configuredServers,
      readyServers: snapshot.readyServers,
      failedServers: snapshot.failedServers,
      inProgressServers: snapshot.inProgressServers,
      notStartedServers: snapshot.notStartedServers,
      ...snapshot.statusUnavailable ? { statusUnavailable: true } : {},
      ...snapshot.initializationError ? { initializationError: snapshot.initializationError } : {},
      servers: snapshot.servers.map((server) => ({
        name: server.name,
        status: server.status,
        languages: server.languages,
        ...server.transport ? { transport: server.transport } : {},
        ...server.command ? { command: server.command } : {},
        ...server.error ? { error: server.error } : {}
      }))
    };
  }
  buildSessionStatsStatus(sessionId) {
    const session = this.sessionOrThrow(sessionId);
    const config = session.getConfig();
    const metrics = uiTelemetryService.getMetricsForSession(sessionId);
    const now = Date.now();
    const createdAt = session.getCreatedAt();
    const models = {};
    for (const [name, m] of Object.entries(metrics.models)) {
      models[name] = {
        api: { ...m.api },
        tokens: { ...m.tokens }
      };
    }
    const byName = {};
    for (const [name, t] of Object.entries(metrics.tools.byName)) {
      byName[name] = {
        count: t.count,
        success: t.success,
        fail: t.fail,
        durationMs: t.durationMs,
        decisions: {
          accept: t.decisions.accept,
          reject: t.decisions.reject,
          modify: t.decisions.modify,
          auto_accept: t.decisions.auto_accept
        }
      };
    }
    const skillMetrics = metrics.skills ?? {
      totalCalls: 0,
      totalSuccess: 0,
      totalFail: 0,
      byName: {}
    };
    const skillsByName = {};
    for (const [name, skill] of Object.entries(skillMetrics.byName)) {
      Object.defineProperty(skillsByName, name, {
        value: {
          count: skill.count,
          success: skill.success,
          fail: skill.fail
        },
        enumerable: true,
        configurable: true,
        writable: true
      });
    }
    return {
      v: STATUS_SCHEMA_VERSION,
      sessionId,
      workspaceCwd: this.workspaceCwd(config),
      sessionStartTimeMs: createdAt,
      durationMs: now - createdAt,
      promptCount: session.getTurnCount(),
      models,
      tools: {
        totalCalls: metrics.tools.totalCalls,
        totalSuccess: metrics.tools.totalSuccess,
        totalFail: metrics.tools.totalFail,
        totalDurationMs: metrics.tools.totalDurationMs,
        byName
      },
      files: {
        totalLinesAdded: metrics.files.totalLinesAdded,
        totalLinesRemoved: metrics.files.totalLinesRemoved
      },
      skills: {
        totalCalls: skillMetrics.totalCalls,
        totalSuccess: skillMetrics.totalSuccess,
        totalFail: skillMetrics.totalFail,
        byName: skillsByName
      }
    };
  }
  serializeHookConfig(config) {
    switch (config.type) {
      case "command":
        return {
          type: "command",
          command: config.command,
          ...config.name !== void 0 ? { name: config.name } : {},
          ...config.description !== void 0 ? { description: config.description } : {},
          ...config.timeout !== void 0 ? { timeout: config.timeout } : {},
          ...config.env ? { env: config.env } : {},
          ...config.async !== void 0 ? { async: config.async } : {},
          ...config.shell ? { shell: config.shell } : {},
          ...config.statusMessage !== void 0 ? { statusMessage: config.statusMessage } : {}
        };
      case "http":
        return {
          type: "http",
          url: config.url,
          ...config.name !== void 0 ? { name: config.name } : {},
          ...config.description !== void 0 ? { description: config.description } : {},
          ...config.timeout !== void 0 ? { timeout: config.timeout } : {},
          ...config.headers ? { headers: config.headers } : {},
          ...config.allowedEnvVars ? { allowedEnvVars: config.allowedEnvVars } : {},
          ...config.if !== void 0 ? { if: config.if } : {},
          ...config.statusMessage !== void 0 ? { statusMessage: config.statusMessage } : {},
          ...config.once !== void 0 ? { once: config.once } : {}
        };
      case "function":
        return {
          type: "function",
          ...config.id !== void 0 ? { id: config.id } : {},
          ...config.name !== void 0 ? { name: config.name } : {},
          ...config.description !== void 0 ? { description: config.description } : {},
          ...config.timeout !== void 0 ? { timeout: config.timeout } : {},
          ...config.errorMessage !== void 0 ? { errorMessage: config.errorMessage } : {},
          ...config.statusMessage !== void 0 ? { statusMessage: config.statusMessage } : {}
        };
      case "prompt":
        return {
          type: "prompt",
          prompt: config.prompt,
          ...config.name !== void 0 ? { name: config.name } : {},
          ...config.description !== void 0 ? { description: config.description } : {},
          ...config.timeout !== void 0 ? { timeout: config.timeout } : {},
          ...config.model ? { model: config.model } : {},
          ...config.statusMessage !== void 0 ? { statusMessage: config.statusMessage } : {}
        };
      default:
        return { type: config.type };
    }
  }
  buildWorkspaceHooksStatus(config) {
    try {
      const workspaceCwd = this.workspaceCwd(config);
      const disabled = config.getDisableAllHooks();
      const hookSystem = config.getHookSystem();
      if (!hookSystem) {
        return {
          v: STATUS_SCHEMA_VERSION,
          workspaceCwd,
          initialized: true,
          disabled,
          hooks: [],
          events: IDLE_HOOK_EVENTS
        };
      }
      const registryEntries = hookSystem.getAllHooks();
      const hooks = registryEntries.map(
        (entry) => ({
          kind: "hook",
          eventName: entry.eventName,
          config: this.serializeHookConfig(entry.config),
          source: entry.source,
          ...entry.matcher ? { matcher: entry.matcher } : {},
          ...entry.sequential !== void 0 ? { sequential: entry.sequential } : {},
          enabled: entry.enabled
        })
      );
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd,
        initialized: true,
        disabled,
        hooks,
        events: IDLE_HOOK_EVENTS
      };
    } catch (error) {
      let disabled = false;
      try {
        disabled = config.getDisableAllHooks();
      } catch {
      }
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd: this.safeWorkspaceCwd(config),
        initialized: false,
        disabled,
        hooks: [],
        events: IDLE_HOOK_EVENTS,
        errors: [this.errorCell("hooks", error)]
      };
    }
  }
  buildSessionHooksStatus(sessionId) {
    const session = this.sessionOrThrow(sessionId);
    const config = session.getConfig();
    try {
      const workspaceCwd = this.workspaceCwd(config);
      const disabled = config.getDisableAllHooks();
      const hookSystem = config.getHookSystem();
      if (!hookSystem) {
        return {
          v: STATUS_SCHEMA_VERSION,
          sessionId,
          workspaceCwd,
          disabled,
          hooks: []
        };
      }
      const sessionHooks = hookSystem.getSessionHooksManager().getAllSessionHooks(sessionId);
      const hooks = sessionHooks.map(
        (entry) => ({
          kind: "hook",
          eventName: entry.eventName,
          config: this.serializeHookConfig(entry.config),
          source: "session",
          ...entry.matcher ? { matcher: entry.matcher } : {},
          ...entry.sequential !== void 0 ? { sequential: entry.sequential } : {},
          enabled: true,
          hookId: entry.hookId,
          ...entry.skillRoot ? { skillRoot: entry.skillRoot } : {}
        })
      );
      return {
        v: STATUS_SCHEMA_VERSION,
        sessionId,
        workspaceCwd,
        disabled,
        hooks
      };
    } catch (error) {
      let disabled = false;
      try {
        disabled = config.getDisableAllHooks();
      } catch {
      }
      return {
        v: STATUS_SCHEMA_VERSION,
        sessionId,
        workspaceCwd: this.safeWorkspaceCwd(config),
        disabled,
        hooks: [],
        errors: [this.errorCell("session_hooks", error)]
      };
    }
  }
  buildWorkspaceExtensionsStatus(config) {
    try {
      const workspaceCwd = this.workspaceCwd(config);
      const extensions = config.getExtensions();
      const entries = extensions.map(
        (ext) => {
          const capabilities = {
            mcpServerCount: ext.mcpServers ? Object.keys(ext.mcpServers).length : 0,
            skillCount: ext.skills?.length ?? 0,
            agentCount: ext.agents?.length ?? 0,
            hookCount: ext.hooks ? Object.values(ext.hooks).reduce(
              (sum, defs) => sum + (defs?.length ?? 0),
              0
            ) : 0,
            commandCount: ext.commands?.length ?? 0,
            contextFileCount: ext.contextFiles.length,
            channelCount: ext.channels ? Object.keys(ext.channels).length : 0,
            hasSettings: (ext.settings?.length ?? 0) > 0
          };
          return {
            kind: "extension",
            id: ext.id,
            name: ext.name,
            displayName: ext.displayName,
            ...ext.config.description ? { description: ext.config.description } : {},
            version: ext.version,
            isActive: ext.isActive,
            path: ext.path,
            ...ext.installMetadata?.source ? { source: redactUrlCredentials(ext.installMetadata.source) } : {},
            ...ext.installMetadata?.type ? { installType: ext.installMetadata.type } : {},
            ...ext.installMetadata?.originSource ? { originSource: ext.installMetadata.originSource } : {},
            ...ext.installMetadata?.ref ? { ref: ext.installMetadata.ref } : {},
            ...ext.installMetadata?.autoUpdate !== void 0 ? { autoUpdate: ext.installMetadata.autoUpdate } : {},
            capabilities,
            updateState: ext.installMetadata ? "unknown" : "not updatable",
            details: {
              mcpServers: ext.mcpServers ? Object.keys(ext.mcpServers) : [],
              commands: ext.commands ?? [],
              skills: ext.skills?.map((skill) => skill.name) ?? [],
              agents: ext.agents?.map((agent) => agent.name) ?? [],
              contextFiles: ext.contextFiles,
              settings: ext.resolvedSettings?.map((setting) => setting.name) ?? []
            }
          };
        }
      );
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd,
        initialized: true,
        extensions: entries
      };
    } catch (error) {
      return {
        v: STATUS_SCHEMA_VERSION,
        workspaceCwd: this.safeWorkspaceCwd(config),
        initialized: false,
        extensions: [],
        errors: [this.errorCell("extensions", error)]
      };
    }
  }
  async installSkillFromUrl(request) {
    const skillManager = this.config.getSkillManager();
    if (!skillManager) {
      throw RequestError.invalidParams(
        void 0,
        "SkillManager is not available"
      );
    }
    const download = await downloadSkill(request.sourceUrl);
    const skillsBaseDir = path3.join(Storage.getGlobalQwenDir(), "skills");
    const skillDir = resolveManagedSkillDir(skillsBaseDir, request.slug);
    const skillFile = path3.join(skillDir, "SKILL.md");
    const parsed = skillManager.parseSkillContent(
      download.skillContent,
      skillFile,
      "user"
    );
    if (parsed.name !== request.slug) {
      throw RequestError.invalidParams(
        void 0,
        `Skill name "${parsed.name}" does not match requested slug "${request.slug}"`
      );
    }
    const stagingDir = `${skillDir}.installing-${process.pid}-${Date.now()}`;
    try {
      await fs.rm(stagingDir, { recursive: true, force: true });
      for (const file of download.files) {
        const targetPath = resolveSkillInstallPath(
          stagingDir,
          file.relativePath
        );
        await fs.mkdir(path3.dirname(targetPath), { recursive: true });
        await fs.writeFile(targetPath, file.content);
      }
      await fs.rm(skillDir, { recursive: true, force: true });
      await fs.rename(stagingDir, skillDir);
    } catch (error) {
      await fs.rm(stagingDir, { recursive: true, force: true }).catch(() => {
      });
      throw error;
    }
    await skillManager.refreshCache();
    return {
      id: request.id,
      slug: parsed.name,
      installed: true,
      installedPath: skillFile,
      sourceUrl: request.sourceUrl
    };
  }
  async deleteGlobalSkill(request) {
    const skillManager = this.config.getSkillManager();
    if (!skillManager) {
      throw RequestError.invalidParams(
        void 0,
        "SkillManager is not available"
      );
    }
    const { skillDir, skillFile, content } = await this.readManagedSkillFile(
      request.slug,
      "global",
      skillManager
    );
    const parsed = skillManager.parseSkillContent(content, skillFile, "user");
    if (parsed.name !== request.slug) {
      throw RequestError.invalidParams(
        void 0,
        `Skill name "${parsed.name}" does not match requested slug "${request.slug}"`
      );
    }
    const resolvedSkillDir = path3.resolve(skillDir);
    const resolvedSkillFile = path3.resolve(skillFile);
    const globalDir = path3.resolve(Storage.getGlobalQwenDir());
    const isDedicatedSkillDir = resolvedSkillFile === path3.join(resolvedSkillDir, "SKILL.md");
    if (!isDedicatedSkillDir || resolvedSkillDir === path3.parse(resolvedSkillDir).root || resolvedSkillDir === globalDir) {
      throw RequestError.invalidParams(
        void 0,
        `Refusing to delete unexpected skill directory: ${skillDir}`
      );
    }
    await fs.rm(skillDir, { recursive: true, force: true });
    await skillManager.refreshCache();
    return {
      slug: request.slug,
      deleted: true
    };
  }
  async readManagedSkillFile(slug, scope, skillManager, cwd) {
    if (scope === "global") {
      const qwenSkillDir = resolveManagedSkillDir(
        path3.join(Storage.getGlobalQwenDir(), "skills"),
        slug
      );
      const qwenSkillFile = path3.join(qwenSkillDir, "SKILL.md");
      const qwenContent = await fs.readFile(qwenSkillFile, "utf8").catch(() => void 0);
      if (qwenContent !== void 0) {
        return {
          skillDir: qwenSkillDir,
          skillFile: qwenSkillFile,
          content: qwenContent
        };
      }
    }
    if (scope === "project" && cwd?.trim()) {
      const projectSkill = await this.findProjectSkillFileFromCwd(
        slug,
        cwd,
        skillManager
      );
      if (projectSkill) return projectSkill;
    }
    const level = scope === "project" ? "project" : "user";
    const skill = (await skillManager.listSkills({ level })).find(
      (candidate) => candidate.name === slug
    );
    const skillFile = skill?.filePath;
    if (!skillFile) {
      throw RequestError.invalidParams(
        void 0,
        `${scope === "project" ? "Project" : "Global"} skill not found: ${slug}`
      );
    }
    const content = await fs.readFile(skillFile, "utf8").catch(() => {
      throw RequestError.invalidParams(
        void 0,
        `${scope === "project" ? "Project" : "Global"} skill not found: ${slug}`
      );
    });
    return {
      skillDir: path3.dirname(skillFile),
      skillFile,
      content
    };
  }
  async findProjectSkillFileFromCwd(slug, cwd, skillManager) {
    const projectRoot = path3.resolve(cwd);
    for (const configDir of PROJECT_SKILL_DIRS) {
      const baseDir = path3.join(projectRoot, configDir, SKILLS_DIR);
      const skills = await skillManager.loadSkillsFromDir(baseDir, "project");
      const skill = skills.find((candidate) => candidate.name === slug);
      const skillFile = skill?.filePath;
      if (!skillFile) continue;
      const content = await fs.readFile(skillFile, "utf8").catch(() => {
        throw RequestError.invalidParams(
          void 0,
          `Project skill not found: ${slug}`
        );
      });
      return {
        skillDir: path3.dirname(skillFile),
        skillFile,
        content
      };
    }
    return void 0;
  }
  async setGlobalSkillEnabled(request, cwd) {
    const skillManager = this.config.getSkillManager();
    if (!skillManager) {
      throw RequestError.invalidParams(
        void 0,
        "SkillManager is not available"
      );
    }
    const { skillFile, content } = await this.readManagedSkillFile(
      request.slug,
      request.scope,
      skillManager,
      cwd
    );
    const level = request.scope === "project" ? "project" : "user";
    const parsed = skillManager.parseSkillContent(content, skillFile, level);
    if (parsed.name !== request.slug) {
      throw RequestError.invalidParams(
        void 0,
        `Skill name "${parsed.name}" does not match requested slug "${request.slug}"`
      );
    }
    const nextContent = setSkillFrontmatterEnabled(content, request.enabled);
    skillManager.parseSkillContent(nextContent, skillFile, level);
    if (path3.basename(skillFile) !== "SKILL.md") {
      throw RequestError.invalidParams(
        void 0,
        `Refusing to write to unexpected skill file: ${skillFile}`
      );
    }
    await fs.writeFile(skillFile, nextContent, "utf8");
    await skillManager.refreshCache();
    return {
      slug: request.slug,
      enabled: request.enabled,
      installedPath: skillFile
    };
  }
  async extMethod(method, params) {
    const requestedCwd = typeof params["cwd"] === "string" ? params["cwd"] : void 0;
    const cwd = requestedCwd || process.cwd();
    const SESSION_ID_RE = /^[0-9a-fA-F-]{32,36}$/;
    switch (method) {
      case "qwen/providers/list": {
        return {
          providers: ALL_PROVIDERS.map(
            (provider) => serializeProviderConfig(provider, this.settings)
          )
        };
      }
      case "qwen/providers/connect": {
        const providerId = readRequiredString(
          params["providerId"],
          "providerId"
        );
        const providerConfig = findProviderById(providerId);
        if (!providerConfig) {
          throw RequestError.invalidParams(
            void 0,
            `Unknown provider: ${providerId}`
          );
        }
        const inputs = readProviderSetupInputs(
          providerConfig,
          params,
          (protocol, baseUrl) => resolveExistingProviderApiKey(
            providerConfig,
            this.settings,
            protocol,
            baseUrl
          )
        );
        const persistScope = readProviderConnectScope(params["scope"]);
        const plan = buildInstallPlan(providerConfig, inputs);
        const adapter = createLoadedSettingsAdapter(
          this.settings,
          persistScope
        );
        await applyProviderInstallPlan(plan, {
          settings: adapter,
          reloadModelProviders: /* @__PURE__ */ __name((modelProviders) => this.config.reloadModelProvidersConfig(modelProviders), "reloadModelProviders"),
          syncAuthState: /* @__PURE__ */ __name((authType, modelId, baseUrl) => this.config.getModelsConfig().syncAfterAuthRefresh(authType, modelId, baseUrl), "syncAuthState"),
          refreshAuth: /* @__PURE__ */ __name((authType) => this.config.refreshAuth(authType), "refreshAuth")
        });
        const effectiveModelId = adapter.getValue("model.name") ?? plan.modelSelection?.modelId;
        const effectiveBaseUrl = adapter.getValue("model.baseUrl") ?? plan.modelSelection?.baseUrl;
        return {
          success: true,
          providerId: providerConfig.id,
          providerLabel: providerConfig.label,
          authType: plan.authType,
          ...effectiveModelId ? { modelId: effectiveModelId } : {},
          ...effectiveBaseUrl ? { baseUrl: effectiveBaseUrl } : {}
        };
      }
      case "qwen/skills/install": {
        return this.installSkillFromUrl(readSkillInstallRequest(params));
      }
      case "qwen/skills/delete": {
        return this.deleteGlobalSkill(readSkillSlugRequest(params));
      }
      case "qwen/skills/setEnabled": {
        return this.setGlobalSkillEnabled(
          readSkillSetEnabledRequest(params),
          requestedCwd
        );
      }
      case "qwen/settings/getMemory": {
        const settings = loadSettings(cwd);
        this.settings = settings;
        return {
          settings: normalizeQwenMemorySettings(settings.merged.memory)
        };
      }
      case "qwen/settings/setMemory": {
        const updates = toRecord(params["updates"]);
        const settings = loadSettings(cwd);
        for (const key of QWEN_MEMORY_SETTING_KEYS) {
          if (updates[key] === void 0) continue;
          if (typeof updates[key] !== "boolean") {
            throw RequestError.invalidParams(
              void 0,
              `Invalid memory setting '${key}': expected boolean`
            );
          }
          settings.setValue("User" /* User */, `memory.${key}`, updates[key]);
        }
        this.settings = settings;
        return {
          settings: normalizeQwenMemorySettings(settings.merged.memory)
        };
      }
      case "qwen/settings/getPath": {
        return { path: this.settings.user.path };
      }
      case "qwen/settings/getMemoryPaths": {
        const projectRoot = typeof params["projectRoot"] === "string" ? params["projectRoot"] : cwd;
        return {
          paths: await resolveQwenMemoryPaths({ cwd, projectRoot })
        };
      }
      case SERVE_STATUS_EXT_METHODS.workspaceMcp:
        return await this.buildWorkspaceMcpStatus(
          this.config
        );
      case SERVE_STATUS_EXT_METHODS.workspaceMcpTools: {
        const serverName = params["serverName"];
        if (typeof serverName !== "string" || serverName.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing serverName"
          );
        }
        return this.buildWorkspaceMcpToolsStatus(
          this.config,
          serverName
        );
      }
      case SERVE_STATUS_EXT_METHODS.workspaceMcpResources: {
        const serverName = params["serverName"];
        if (typeof serverName !== "string" || serverName.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing serverName"
          );
        }
        return this.buildWorkspaceMcpResourcesStatus(
          this.config,
          serverName
        );
      }
      case SERVE_STATUS_EXT_METHODS.workspaceSkills:
        return await this.buildWorkspaceSkillsStatus(
          this.config
        );
      case SERVE_STATUS_EXT_METHODS.workspaceTools:
        return this.buildWorkspaceToolsStatus(this.config);
      case SERVE_STATUS_EXT_METHODS.workspaceProviders:
        return this.buildWorkspaceProvidersStatus(
          this.config
        );
      case SERVE_STATUS_EXT_METHODS.workspacePreflight:
        return await this.buildAcpPreflightCells(
          this.config
        );
      case SERVE_STATUS_EXT_METHODS.sessionContext: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        return this.buildSessionContextStatus(sessionId);
      }
      case SERVE_STATUS_EXT_METHODS.sessionContextUsage: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        return await this.buildSessionContextUsageStatus(
          sessionId,
          params["detail"] === true
        );
      }
      case SERVE_STATUS_EXT_METHODS.sessionSupportedCommands: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        return await this.buildSessionSupportedCommandsStatus(
          sessionId
        );
      }
      case SERVE_STATUS_EXT_METHODS.sessionTasks: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        return this.buildSessionTasksStatus(sessionId);
      }
      case SERVE_STATUS_EXT_METHODS.sessionLspStatus: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        return this.buildSessionLspStatus(sessionId);
      }
      case SERVE_STATUS_EXT_METHODS.sessionStats: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        return this.buildSessionStatsStatus(sessionId);
      }
      case SERVE_STATUS_EXT_METHODS.sessionRewindSnapshots: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || !SESSION_ID_RE.test(sessionId)) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const session = this.sessions.get(sessionId);
        if (!session) {
          throw RequestError.invalidParams(
            void 0,
            `Session not found for id: ${sessionId}`
          );
        }
        const fhs = session.getConfig().getFileHistoryService();
        const snapshots = fhs.getSnapshots();
        const rewindableTurnCount = session.getRewindableUserTurnCount();
        const prefix = sessionId + "########";
        const results = await Promise.all(
          snapshots.map((s, idx) => ({ s, idx })).filter(
            ({ s }) => s.promptId.startsWith(prefix) && /^\d+$/.test(s.promptId.slice(prefix.length))
          ).filter(({ idx }) => idx < rewindableTurnCount).map(async ({ s, idx }) => {
            const stats = await fhs.getDiffStats(s.promptId);
            return {
              promptId: s.promptId,
              turnIndex: idx,
              timestamp: s.timestamp.toISOString(),
              diffStats: {
                filesChanged: stats?.filesChanged?.length ?? 0,
                insertions: stats?.insertions ?? 0,
                deletions: stats?.deletions ?? 0
              }
            };
          })
        );
        return { snapshots: results };
      }
      case SERVE_STATUS_EXT_METHODS.workspaceHooks:
        return this.buildWorkspaceHooksStatus(this.config);
      case SERVE_STATUS_EXT_METHODS.sessionHooks: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        return this.buildSessionHooksStatus(sessionId);
      }
      case SERVE_STATUS_EXT_METHODS.workspaceExtensions:
        return this.buildWorkspaceExtensionsStatus(
          this.config
        );
      case SERVE_CONTROL_EXT_METHODS.workspaceMemoryRememberAvailability:
        return {
          available: this.config.isManagedMemoryAvailable()
        };
      case SERVE_CONTROL_EXT_METHODS.workspaceMemoryRemember: {
        const content = params["content"];
        if (typeof content !== "string" || !content.trim()) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing content"
          );
        }
        if (Buffer.byteLength(content, "utf8") > MAX_REMEMBER_CONTENT_BYTES) {
          throw RequestError.invalidParams(
            void 0,
            "Content exceeds maximum size"
          );
        }
        const rawContextMode = params["contextMode"] ?? "workspace";
        if (rawContextMode !== "workspace" && rawContextMode !== "clean") {
          throw RequestError.invalidParams(void 0, "Invalid contextMode");
        }
        const contextMode = rawContextMode;
        if (!this.config.isManagedMemoryAvailable()) {
          throw new RequestError(
            -32009,
            "Managed memory is unavailable for this daemon workspace",
            { errorKind: "managed_memory_unavailable" }
          );
        }
        const childSignal = AbortSignal.timeout(
          WORKSPACE_MEMORY_REMEMBER_CHILD_TIMEOUT_MS
        );
        try {
          const result = await runManagedRememberByAgent({
            config: this.config,
            projectRoot: this.config.getProjectRoot(),
            content: content.trim(),
            contextMode,
            abortSignal: childSignal
          });
          return result;
        } catch (err) {
          if (err instanceof RequestError) {
            throw err;
          }
          if (childSignal.aborted) {
            throw new RequestError(
              -32099,
              "Workspace memory remember timed out",
              { errorKind: "remember_timeout" }
            );
          }
          const code = extractRememberErrorCode(err);
          if (code === "managed_memory_unavailable") {
            throw new RequestError(
              -32009,
              "Managed memory is unavailable for this daemon workspace",
              { errorKind: "managed_memory_unavailable" }
            );
          }
          throw new RequestError(
            -32099,
            err instanceof Error && err.message ? err.message : "Workspace memory remember failed",
            {
              errorKind: code
            }
          );
        }
      }
      case SERVE_CONTROL_EXT_METHODS.workspaceMemoryForget: {
        const query = params["query"];
        if (typeof query !== "string" || !query.trim()) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing query"
          );
        }
        const trimmedQuery = query.trim();
        if (Buffer.byteLength(trimmedQuery, "utf8") > MAX_REMEMBER_CONTENT_BYTES) {
          throw RequestError.invalidParams(
            void 0,
            "Query exceeds maximum size"
          );
        }
        if (!this.config.isManagedMemoryAvailable()) {
          throw new RequestError(
            -32009,
            "Managed memory is unavailable for this daemon workspace",
            { errorKind: "managed_memory_unavailable" }
          );
        }
        const childSignal = AbortSignal.timeout(
          WORKSPACE_MEMORY_REMEMBER_CHILD_TIMEOUT_MS
        );
        try {
          const projectRoot = this.config.getProjectRoot();
          const hiddenConfig = createHiddenWorkspaceMemoryConfig(this.config);
          const result = await this.config.getMemoryManager().forget(projectRoot, trimmedQuery, {
            config: hiddenConfig,
            abortSignal: childSignal
          });
          return {
            summary: result.systemMessage ?? formatWorkspaceMemoryForgetSummary(result.removedEntries.length),
            removedEntries: result.removedEntries,
            touchedTopics: result.touchedTopics
          };
        } catch (err) {
          if (err instanceof RequestError) {
            throw err;
          }
          if (childSignal.aborted) {
            throw new RequestError(
              -32099,
              "Workspace memory forget timed out",
              {
                errorKind: "forget_timeout"
              }
            );
          }
          const code = extractRememberErrorCode(err, "forget_failed");
          if (code === "managed_memory_unavailable") {
            throw new RequestError(
              -32009,
              "Managed memory is unavailable for this daemon workspace",
              { errorKind: "managed_memory_unavailable" }
            );
          }
          throw new RequestError(-32099, "Workspace memory forget failed", {
            errorKind: code
          });
        }
      }
      case SERVE_CONTROL_EXT_METHODS.workspaceMemoryDream: {
        if (!this.config.isManagedMemoryAvailable()) {
          throw new RequestError(
            -32009,
            "Managed memory is unavailable for this daemon workspace",
            { errorKind: "managed_memory_unavailable" }
          );
        }
        const childSignal = AbortSignal.timeout(
          WORKSPACE_MEMORY_REMEMBER_CHILD_TIMEOUT_MS
        );
        try {
          const result = await runManagedAutoMemoryDream(
            this.config.getProjectRoot(),
            /* @__PURE__ */ new Date(),
            createHiddenWorkspaceMemoryConfig(this.config),
            childSignal,
            {
              trigger: "manual",
              recordMetadata: true,
              suppressChatRecording: true
            }
          );
          return {
            summary: result.systemMessage,
            touchedTopics: result.touchedTopics,
            dedupedEntries: result.dedupedEntries
          };
        } catch (err) {
          if (err instanceof RequestError) {
            throw err;
          }
          if (childSignal.aborted) {
            throw new RequestError(-32099, "Workspace memory dream timed out", {
              errorKind: "dream_timeout"
            });
          }
          const code = extractRememberErrorCode(err, "dream_failed");
          if (code === "managed_memory_unavailable") {
            throw new RequestError(
              -32009,
              "Managed memory is unavailable for this daemon workspace",
              { errorKind: "managed_memory_unavailable" }
            );
          }
          throw new RequestError(-32099, "Workspace memory dream failed", {
            errorKind: code
          });
        }
      }
      case SERVE_CONTROL_EXT_METHODS.workspaceMcpRestart: {
        const serverName = params["serverName"];
        if (typeof serverName !== "string" || serverName.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing serverName"
          );
        }
        let entryIndex;
        const rawEntryIndex = params["entryIndex"];
        if (rawEntryIndex !== void 0 && rawEntryIndex !== "*") {
          if (typeof rawEntryIndex !== "number" || !Number.isInteger(rawEntryIndex) || rawEntryIndex < 0) {
            throw RequestError.invalidParams(
              void 0,
              'entryIndex must be a non-negative integer or "*"'
            );
          }
          entryIndex = rawEntryIndex;
        }
        const servers = this.config.getMcpServers() ?? {};
        if (!Object.prototype.hasOwnProperty.call(servers, serverName)) {
          throw new RequestError(
            -32004,
            `MCP server not configured: ${JSON.stringify(serverName)}`,
            { errorKind: "mcp_server_not_found", serverName }
          );
        }
        if (this.config.isMcpServerDisabled(serverName)) {
          return {
            serverName,
            restarted: false,
            skipped: true,
            reason: "disabled"
          };
        }
        const manager = this.config.getToolRegistry()?.getMcpClientManager();
        if (!manager) {
          throw RequestError.internalError(
            void 0,
            "McpClientManager unavailable on this Config"
          );
        }
        if (manager.isServerDiscovering(serverName)) {
          return {
            serverName,
            restarted: false,
            skipped: true,
            reason: "in_flight"
          };
        }
        const accounting = manager.getMcpClientAccounting();
        const budget = manager.getMcpClientBudget();
        const mode = manager.getMcpBudgetMode();
        if (mode === "enforce" && budget !== void 0 && !accounting.reservedSlots.includes(serverName) && accounting.reservedSlots.length >= budget) {
          return {
            serverName,
            restarted: false,
            skipped: true,
            reason: "budget_would_exceed"
          };
        }
        try {
          const fresh = loadSettings(this.config.getTargetDir());
          const mergedDisabled = fresh.merged.tools?.disabled;
          if (mergedDisabled !== void 0 && !Array.isArray(mergedDisabled)) {
            process.stderr.write(
              `qwen serve: MCP restart for ${JSON.stringify(serverName)}: tools.disabled has unexpected type ${typeof mergedDisabled}; clearing disabled set \u2014 check settings.json. Expected an array of strings.
`
            );
          }
          const disabledList = normalizeDisabledToolList(mergedDisabled);
          this.config.setDisabledTools(new Set(disabledList));
        } catch (err) {
          process.stderr.write(
            `qwen serve: MCP restart for ${JSON.stringify(serverName)} could not refresh disabledTools from merged settings (${err instanceof Error ? err.message : String(err)}); proceeding with the bootstrap snapshot \u2014 recently toggled tools may not take effect until daemon restart.
`
          );
        }
        const poolSnapshot = this.mcpPool?.getSnapshot();
        const poolHasEntries = poolSnapshot !== void 0 && (poolSnapshot.byName[serverName]?.entryCount ?? 0) > 0;
        if (this.mcpPool && poolHasEntries) {
          const restartResults = await this.mcpPool.restartByName(serverName, {
            ...entryIndex !== void 0 ? { entryIndex } : {}
          });
          return {
            serverName,
            entries: restartResults
          };
        }
        if (entryIndex !== void 0 && entryIndex !== 0) {
          throw RequestError.invalidParams(
            void 0,
            `entryIndex=${entryIndex} requested but pool not active for ${JSON.stringify(serverName)} \u2014 legacy single-entry path only supports entryIndex=0 or undefined`
          );
        }
        const start = Date.now();
        const toolRegistry = this.config.getToolRegistry();
        if (!toolRegistry) {
          throw RequestError.internalError(
            void 0,
            "ToolRegistry unavailable on this Config"
          );
        }
        await toolRegistry.discoverToolsForServer(serverName);
        const postStatus = getMCPServerStatus(serverName);
        if (postStatus !== "connected" /* CONNECTED */) {
          throw new RequestError(
            -32099,
            `MCP server ${JSON.stringify(serverName)} did not reach a connected state after restart (status: ${postStatus}).`,
            {
              errorKind: "mcp_restart_failed",
              serverName,
              mcpStatus: postStatus
            }
          );
        }
        return {
          serverName,
          restarted: true,
          durationMs: Date.now() - start
        };
      }
      case SERVE_CONTROL_EXT_METHODS.workspaceMcpManage: {
        const serverName = params["serverName"];
        const action = params["action"];
        if (typeof serverName !== "string" || serverName.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing serverName"
          );
        }
        if (action !== "enable" && action !== "disable" && action !== "authenticate" && action !== "clear-auth") {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing MCP manage action"
          );
        }
        const servers = this.config.getMcpServers() ?? {};
        const server = servers[serverName];
        if (!server) {
          throw new RequestError(
            -32004,
            `MCP server not configured: ${JSON.stringify(serverName)}`,
            { errorKind: "mcp_server_not_found", serverName }
          );
        }
        const toolRegistry = this.config.getToolRegistry();
        if (!toolRegistry) {
          throw RequestError.internalError(
            void 0,
            "ToolRegistry unavailable on this Config"
          );
        }
        if (action === "enable") {
          const settings = loadSettings(this.config.getTargetDir());
          let settingsChanged = false;
          for (const scope of ["User" /* User */, "Workspace" /* Workspace */]) {
            const scopeSettings = settings.forScope(scope).settings;
            const currentExcluded2 = scopeSettings.mcp?.excluded || [];
            const filtered = currentExcluded2.filter(
              (pattern) => pattern !== serverName
            );
            if (filtered.length !== currentExcluded2.length) {
              settings.setValue(scope, "mcp.excluded", filtered);
              settingsChanged = true;
            }
          }
          const currentExcluded = this.config.getExcludedMcpServers() || [];
          const runtimeFiltered = currentExcluded.filter(
            (pattern) => pattern !== serverName
          );
          let runtimeChanged = false;
          if (runtimeFiltered.length !== currentExcluded.length) {
            this.config.setExcludedMcpServers(runtimeFiltered);
            runtimeChanged = true;
          }
          await toolRegistry.discoverToolsForServer(serverName);
          return {
            serverName,
            action,
            ok: true,
            changed: settingsChanged || runtimeChanged
          };
        }
        if (action === "disable") {
          const settings = loadSettings(this.config.getTargetDir());
          const userSettings = settings.forScope("User" /* User */).settings;
          const workspaceSettings = settings.forScope(
            "Workspace" /* Workspace */
          ).settings;
          let targetScope = "User" /* User */;
          if (server.extensionName) {
            throw RequestError.invalidParams(
              void 0,
              `Cannot disable extension MCP server: ${serverName}`
            );
          }
          if (workspaceSettings.mcpServers?.[serverName]) {
            targetScope = "Workspace" /* Workspace */;
          } else if (userSettings.mcpServers?.[serverName]) {
            targetScope = "User" /* User */;
          }
          const scopeSettings = settings.forScope(targetScope).settings;
          const currentExcluded = scopeSettings.mcp?.excluded || [];
          let settingsChanged = false;
          if (!matchesAnyServerPattern(serverName, currentExcluded)) {
            settings.setValue(targetScope, "mcp.excluded", [
              ...currentExcluded,
              serverName
            ]);
            settingsChanged = true;
          }
          const runtimeExcluded = this.config.getExcludedMcpServers() || [];
          let runtimeChanged = false;
          if (!matchesAnyServerPattern(serverName, runtimeExcluded)) {
            this.config.setExcludedMcpServers([...runtimeExcluded, serverName]);
            runtimeChanged = true;
          }
          await toolRegistry.disableMcpServer(serverName);
          return {
            serverName,
            action,
            ok: true,
            changed: settingsChanged || runtimeChanged
          };
        }
        if (action === "clear-auth") {
          const tokenStorage = new MCPOAuthTokenStorage();
          await tokenStorage.deleteCredentials(serverName);
          await toolRegistry.disconnectServer(serverName);
          return { serverName, action, ok: true, changed: true };
        }
        const messages = [];
        let authUrl;
        const displayListener = /* @__PURE__ */ __name((message) => {
          if (typeof message === "string") {
            messages.push(message);
          } else if (message && typeof message === "object") {
            const key = message.key;
            if (typeof key === "string") {
              messages.push(key);
            }
          }
        }, "displayListener");
        const authUrlListener = /* @__PURE__ */ __name((url) => {
          if (typeof url === "string") {
            authUrl = url;
          }
        }, "authUrlListener");
        appEvents.on("oauth-display-message" /* OauthDisplayMessage */, displayListener);
        appEvents.on("oauth-auth-url" /* OauthAuthUrl */, authUrlListener);
        try {
          const oauthConfig = server.oauth ?? { enabled: false };
          const mcpServerUrl = server.httpUrl || server.url;
          const authProvider = new MCPOAuthProvider(new MCPOAuthTokenStorage());
          await authProvider.authenticate(
            serverName,
            oauthConfig,
            mcpServerUrl,
            appEvents
          );
          messages.push(
            `Successfully authenticated and refreshed tools for '${serverName}'.`
          );
          await toolRegistry.discoverToolsForServer(serverName);
          const geminiClient = this.config.getGeminiClient();
          if (geminiClient) {
            await geminiClient.setTools();
          }
          return {
            serverName,
            action,
            ok: true,
            changed: true,
            messages,
            ...authUrl ? { authUrl } : {}
          };
        } finally {
          appEvents.removeListener(
            "oauth-display-message" /* OauthDisplayMessage */,
            displayListener
          );
          appEvents.removeListener("oauth-auth-url" /* OauthAuthUrl */, authUrlListener);
        }
      }
      case SERVE_CONTROL_EXT_METHODS.workspaceAgentGenerate: {
        const description = params["description"];
        if (typeof description !== "string" || !description.trim() || description.length > 4096) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing description (max 4096 chars)"
          );
        }
        return await subagentGenerator(
          description.trim(),
          this.config,
          AbortSignal.timeout(5 * 6e4)
        );
      }
      case SERVE_CONTROL_EXT_METHODS.sessionTitle: {
        const sessionId = params["sessionId"];
        const displayName = params["displayName"];
        const titleSource = params["titleSource"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        if (typeof displayName !== "string") {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing displayName"
          );
        }
        if (displayName.length > SESSION_TITLE_MAX_LENGTH) {
          throw RequestError.invalidParams(
            void 0,
            `Title too long (max ${SESSION_TITLE_MAX_LENGTH} chars)`
          );
        }
        const session = this.sessionOrThrow(sessionId);
        const source = titleSource === "auto" ? "auto" : "manual";
        const recording = session.getConfig().getChatRecordingService();
        let ok = false;
        if (recording) {
          ok = recording.recordCustomTitle(displayName, source);
          await recording.flush();
        }
        return { sessionId, displayName, titleSource: source, persisted: ok };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionClose: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        await this.closeStoredSession(sessionId, {
          requireFlush: params["requireFlush"] === true
        });
        return { sessionId, closed: true };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionCd: {
        const sessionId = params["sessionId"];
        const targetPath = params["path"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        if (typeof targetPath !== "string" || targetPath.length === 0 || !path3.isAbsolute(targetPath) || targetPath.includes("\0")) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing path (must be an absolute path)"
          );
        }
        const session = this.sessionOrThrow(sessionId);
        const config = session.getConfig();
        if (config.isRestrictiveSandbox()) {
          throw new RequestError(-32003, "Restrictive sandbox mode active", {
            errorKind: "restrictive_sandbox"
          });
        }
        let stats;
        try {
          stats = await fs.stat(targetPath);
        } catch {
          throw new RequestError(-32002, `Directory not found: ${targetPath}`, {
            errorKind: "directory_not_found",
            path: targetPath
          });
        }
        if (!stats.isDirectory()) {
          throw new RequestError(-32002, `Not a directory: ${targetPath}`, {
            errorKind: "directory_not_found",
            path: targetPath
          });
        }
        const canonicalPath = await fs.realpath(targetPath);
        const previousCwd = config.getTargetDir();
        if (canonicalPath === previousCwd) {
          return { previousCwd, newCwd: canonicalPath, warnings: [] };
        }
        if (isFolderTrustEnabled(this.settings.merged)) {
          const trustedFolders = loadTrustedFolders();
          if (trustedFolders.isPathTrusted(canonicalPath) !== true) {
            throw new RequestError(
              -32001,
              `Directory not trusted: ${canonicalPath}`,
              { errorKind: "directory_not_trusted", path: canonicalPath }
            );
          }
        }
        const warnings = [];
        const relocation = await config.relocateWorkingDirectory(
          canonicalPath,
          canonicalPath,
          { skipProcessChdir: true, skipArtifactMigration: true }
        );
        if (relocation.memoryRefreshError) {
          warnings.push(
            `Memory refresh failed: ${relocation.memoryRefreshError instanceof Error ? relocation.memoryRefreshError.message : String(relocation.memoryRefreshError)}`
          );
        }
        try {
          await config.getGeminiClient()?.addWorkingDirectoryChangedContext(previousCwd, canonicalPath);
        } catch (error) {
          warnings.push(
            `Model context refresh failed: ${error instanceof Error ? error.message : String(error)}`
          );
        }
        return { previousCwd, newCwd: canonicalPath, warnings };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionApprovalMode: {
        const sessionId = params["sessionId"];
        const mode = params["mode"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        if (typeof mode !== "string" || !APPROVAL_MODES.includes(mode)) {
          throw RequestError.invalidParams(
            void 0,
            `Invalid approval mode; allowed: ${APPROVAL_MODES.join(", ")}`
          );
        }
        const session = this.sessionOrThrow(sessionId);
        const config = session.getConfig();
        const previous = config.getApprovalMode();
        try {
          config.setApprovalMode(mode);
        } catch (err) {
          if (err instanceof Error && err.name === "TrustGateError") {
            throw new RequestError(-32003, err.message, {
              errorKind: "trust_gate"
            });
          }
          throw err;
        }
        const current = config.getApprovalMode();
        return { previous, current };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionLanguage: {
        const sessionId = params["sessionId"];
        const language = params["language"];
        const syncOutputLanguage = params["syncOutputLanguage"] === true;
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const allowedLanguages = [
          ...SUPPORTED_LANGUAGES.map((l) => l.code),
          "auto"
        ];
        if (typeof language !== "string" || !allowedLanguages.includes(language)) {
          throw RequestError.invalidParams(
            void 0,
            `Invalid language; must be one of: ${allowedLanguages.join(", ")}`
          );
        }
        const session = this.sessionOrThrow(sessionId);
        try {
          await setLanguageAsync(language);
        } catch (err) {
          debugLogger6.warn("setLanguageAsync failed:", err);
          throw new RequestError(
            -32603,
            `Failed to switch UI language: ${err instanceof Error ? err.message : String(err)}`
          );
        }
        const resolvedLanguage = getCurrentLanguage();
        try {
          this.settings.setValue(
            "User" /* User */,
            "general.language",
            language
          );
        } catch (err) {
          debugLogger6.warn("Failed to persist UI language setting:", err);
        }
        let outputLanguage = null;
        let refreshed = false;
        if (syncOutputLanguage) {
          const resolved = resolveOutputLanguage(language);
          const settingValue = isAutoLanguage(language) ? OUTPUT_LANGUAGE_AUTO : resolved;
          let fileWriteOk = false;
          try {
            writeOutputLanguageAndRegisterPath(
              settingValue,
              session.getConfig()
            );
            fileWriteOk = true;
          } catch (err) {
            debugLogger6.warn("Failed to write output-language.md:", err);
          }
          if (fileWriteOk) {
            try {
              this.settings.setValue(
                "User" /* User */,
                "general.outputLanguage",
                settingValue
              );
            } catch (err) {
              debugLogger6.warn(
                "Failed to persist output language setting:",
                err
              );
            }
            const writtenPath = session.getConfig().getOutputLanguageFilePath() ?? getOutputLanguageFilePath();
            const allSessions = [...this.sessions.values()];
            const results = await Promise.allSettled(
              allSessions.map(async (s) => {
                const cfg = s.getConfig();
                let sessionPath;
                try {
                  sessionPath = cfg.getOutputLanguageFilePath();
                  if (sessionPath && sessionPath !== writtenPath) {
                    updateOutputLanguageFile(settingValue, sessionPath);
                  }
                  if (!sessionPath) {
                    writeOutputLanguageAndRegisterPath(settingValue, cfg);
                  }
                } catch (err) {
                  debugLogger6.warn(
                    `Failed to write output-language.md for session ${s.getId()} (path=${sessionPath ?? "global-default"}):`,
                    err
                  );
                }
                await cfg.refreshHierarchicalMemory();
                await cfg.getGeminiClient()?.refreshSystemInstruction();
              })
            );
            const failedCount = results.filter(
              (r) => r.status === "rejected"
            ).length;
            if (failedCount > 0) {
              debugLogger6.warn(
                `Language refresh failed for ${failedCount}/${results.length} session(s)`
              );
            }
            refreshed = results.length === 0 || failedCount === 0;
          }
          outputLanguage = fileWriteOk ? resolved : null;
        }
        return { language: resolvedLanguage, outputLanguage, refreshed };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionRecap: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        debugLogger6.debug(`recap ext-method received for session=${sessionId}`);
        const session = this.sessionOrThrow(sessionId);
        const config = session.getConfig();
        const recap = await generateSessionRecap(
          config,
          new AbortController().signal
        );
        debugLogger6.debug(
          `recap ext-method completed for session=${sessionId} result=${recap ? `len=${recap.length}` : "null"}`
        );
        return { sessionId, recap };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionBtw: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const question = params["question"];
        if (typeof question !== "string" || !question.trim() || question.length > BTW_MAX_INPUT_LENGTH) {
          throw RequestError.invalidParams(
            void 0,
            `Invalid or missing question (max ${BTW_MAX_INPUT_LENGTH} chars)`
          );
        }
        const session = this.sessionOrThrow(sessionId);
        const config = session.getConfig();
        const cacheSafeParams = buildBtwCacheSafeParams(config);
        if (!cacheSafeParams) {
          debugLogger6.debug(`btw: no cacheSafeParams for session=${sessionId}`);
          return { sessionId, answer: null };
        }
        const childSignal = AbortSignal.timeout(BTW_CHILD_TIMEOUT_MS);
        let result;
        try {
          result = await runForkedAgent({
            config,
            userMessage: buildBtwPrompt(question.trim()),
            cacheSafeParams,
            abortSignal: childSignal
          });
        } catch (err) {
          if (childSignal.aborted) {
            throw RequestError.internalError(
              void 0,
              "Side question timed out after 55s"
            );
          }
          throw err;
        }
        return { sessionId, answer: result.text || null };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionForkAgent: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const directive = typeof params["directive"] === "string" ? params["directive"] : "";
        const trimmed = directive.trim();
        if (!trimmed) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing directive"
          );
        }
        const session = this.sessionOrThrow(sessionId);
        const config = session.getConfig();
        if (!config.getModel()) {
          throw RequestError.invalidParams(void 0, "No model configured.");
        }
        let hasHistory = false;
        try {
          hasHistory = (config.getGeminiClient().getHistoryShallow() ?? []).length > 0;
        } catch (error) {
          debugLogger6.debug("Failed to read history before /fork:", error);
        }
        if (!hasHistory) {
          throw RequestError.invalidParams(
            void 0,
            "Cannot fork before the first conversation turn."
          );
        }
        const agentTool = config.getToolRegistry().getTool(ToolNames.AGENT);
        if (!agentTool) {
          throw RequestError.invalidParams(
            void 0,
            "The agent tool is unavailable; cannot fork."
          );
        }
        const description = deriveForkDescription(trimmed);
        const agentParams = {
          description,
          prompt: trimmed,
          subagent_type: FORK_SUBAGENT_TYPE,
          run_in_background: true
        };
        const result = await agentTool.build(agentParams).execute(new AbortController().signal);
        if (hasFailedDisplayStatus(result?.returnDisplay)) {
          const reason = typeof result.llmContent === "string" && result.llmContent.trim() ? result.llmContent.trim() : "the background agent could not be started.";
          throw RequestError.invalidParams(
            void 0,
            `Failed to launch fork: ${reason}`
          );
        }
        try {
          config.getGeminiClient().addHistory({
            role: "user",
            parts: [
              {
                text: `User launched a background fork via /fork. Directive (truncated): ${truncateForkDirectiveForHistory(
                  trimmed
                )}`
              }
            ]
          });
        } catch (error) {
          debugLogger6.debug("Failed to record fork event in history:", error);
        }
        return { sessionId, description, launched: true };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionShellHistory: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const command = params["command"];
        if (typeof command !== "string") {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing command"
          );
        }
        const session = this.sessionOrThrow(sessionId);
        const config = session.getConfig();
        const geminiClient = config.getGeminiClient();
        const outputText = typeof params["output"] === "string" ? params["output"] : "";
        geminiClient.addHistory({
          role: "user",
          parts: [
            {
              text: `I ran the following shell command:
\`\`\`sh
${command}
\`\`\`

This produced the following result:
\`\`\`
${outputText}
\`\`\``
            }
          ]
        });
        return { sessionId, injected: true };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionTaskCancel: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const taskId = params["taskId"];
        if (typeof taskId !== "string" || taskId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing taskId"
          );
        }
        const taskKind = params["taskKind"];
        if (taskKind !== "agent" && taskKind !== "shell" && taskKind !== "monitor") {
          throw RequestError.invalidParams(
            void 0,
            'taskKind must be "agent", "shell", or "monitor"'
          );
        }
        debugLogger6.info(
          `sessionTaskCancel requested sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind}`
        );
        const session = this.sessionOrThrow(sessionId);
        const config = session.getConfig();
        switch (taskKind) {
          case "agent": {
            const task = config.getBackgroundTaskRegistry().get(taskId);
            if (!task || task.status !== "running" && task.status !== "paused") {
              const reason = task ? "not_running" : "not_found";
              debugLogger6.info(
                `sessionTaskCancel skipped sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} reason=${reason} status=${task?.status ?? "missing"}`
              );
              return { cancelled: false, reason, status: task?.status };
            }
            if (task.status === "paused") {
              config.getBackgroundTaskRegistry().abandon(taskId);
            } else {
              config.getBackgroundTaskRegistry().cancel(taskId);
            }
            debugLogger6.info(
              `sessionTaskCancel completed sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} status=${task.status}`
            );
            return { cancelled: true, status: task.status };
          }
          case "shell": {
            const task = config.getBackgroundShellRegistry().get(taskId);
            if (!task || task.status !== "running") {
              const reason = task ? "not_running" : "not_found";
              debugLogger6.info(
                `sessionTaskCancel skipped sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} reason=${reason} status=${task?.status ?? "missing"}`
              );
              return { cancelled: false, reason, status: task?.status };
            }
            config.getBackgroundShellRegistry().requestCancel(taskId);
            debugLogger6.info(
              `sessionTaskCancel completed sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} status=${task.status}`
            );
            return { cancelled: true, status: task.status };
          }
          case "monitor": {
            const task = config.getMonitorRegistry().get(taskId);
            if (!task || task.status !== "running") {
              const reason = task ? "not_running" : "not_found";
              debugLogger6.info(
                `sessionTaskCancel skipped sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} reason=${reason} status=${task?.status ?? "missing"}`
              );
              return { cancelled: false, reason, status: task?.status };
            }
            config.getMonitorRegistry().cancel(taskId);
            debugLogger6.info(
              `sessionTaskCancel completed sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} status=${task.status}`
            );
            return { cancelled: true, status: task.status };
          }
          default: {
            const exhaustive = taskKind;
            throw new Error(`Unhandled task kind: ${exhaustive}`);
          }
        }
      }
      case SERVE_CONTROL_EXT_METHODS.sessionGoalClear: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const session = this.sessionOrThrow(sessionId);
        const config = session.getConfig();
        const cleared = unregisterGoalHook(config, sessionId);
        if (cleared) {
          session.emitGoalStatus({
            kind: "cleared",
            condition: cleared.condition,
            iterations: cleared.iterations,
            durationMs: Date.now() - cleared.setAt
          });
        }
        debugLogger6.info(
          `sessionGoalClear sessionId=${sessionId} cleared=${!!cleared} condition=${cleared?.condition ?? "(none)"}`
        );
        return {
          cleared: !!cleared,
          condition: cleared?.condition
        };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionContinue: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || sessionId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const session = this.sessionOrThrow(sessionId);
        const result = await session.continueLastTurn();
        debugLogger6.info(
          `sessionContinue sessionId=${sessionId} accepted=${result.accepted} interruption=${result.interruption}`
        );
        return result;
      }
      case SERVE_CONTROL_EXT_METHODS.workspaceMcpRuntimeAdd: {
        const name = params["name"];
        const config = params["config"];
        const originatorClientId = params["originatorClientId"];
        if (typeof name !== "string" || name.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing name"
          );
        }
        if (!isValidServerName(name)) {
          throw RequestError.invalidParams(
            void 0,
            "Server name must be \u2264256 chars, alphanumeric + underscore/hyphen, and not a reserved JS property name"
          );
        }
        if (!config || typeof config !== "object" || Array.isArray(config)) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing config"
          );
        }
        if (typeof originatorClientId !== "string" || originatorClientId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing originatorClientId"
          );
        }
        const manager = this.config.getToolRegistry()?.getMcpClientManager();
        if (!manager) {
          throw RequestError.internalError(
            void 0,
            "McpClientManager unavailable on this Config"
          );
        }
        try {
          const {
            trust: _trust,
            authProviderType: _auth,
            includeTools: _inc,
            excludeTools: _exc,
            cwd: _cwd,
            env: _env,
            oauth: _oauth,
            headers: _headers,
            type: _type,
            // Reverse tool channel marker (issue #5626, Phase 2). The parent
            // serve process stamps this on a client-hosted (extension) MCP
            // server's runtime config; it never reaches the transport itself.
            [CLIENT_MCP_OVER_WS_CONFIG_FLAG]: clientMcpOverWs,
            ...safeConfig
          } = config;
          if (clientMcpOverWs === true) {
            safeConfig["type"] = "sdk";
          }
          const result = await manager.addRuntimeMcpServer(
            name,
            safeConfig,
            originatorClientId
          );
          await Promise.all(
            this.getActiveSessions().map(async (session) => {
              const sessionManager = session.getConfig().getToolRegistry()?.getMcpClientManager();
              if (!sessionManager) return;
              try {
                await sessionManager.addRuntimeMcpServer(
                  name,
                  safeConfig,
                  originatorClientId
                );
              } catch (sessionErr) {
                debugLogger6.warn(
                  `workspaceMcpRuntimeAdd: failed to add runtime MCP server '${name}' to active session ${session.getConfig().getSessionId()}: ${sessionErr instanceof Error ? sessionErr.message : String(sessionErr)}`
                );
              }
            })
          );
          return result;
        } catch (err) {
          if (err instanceof McpBudgetWouldExceedError) {
            throw new RequestError(-32099, err.message, {
              errorKind: err.code,
              serverName: err.serverName
            });
          }
          if (err instanceof McpServerSpawnFailedError) {
            throw new RequestError(-32099, err.message, {
              errorKind: err.code,
              serverName: err.serverName,
              ...err.details
            });
          }
          if (err instanceof InvalidMcpConfigError) {
            throw new RequestError(-32099, err.message, {
              errorKind: err.code,
              serverName: err.serverName,
              reason: err.reason
            });
          }
          throw err;
        }
      }
      case SERVE_CONTROL_EXT_METHODS.workspaceMcpRuntimeRemove: {
        const name = params["name"];
        const originatorClientId = params["originatorClientId"];
        if (typeof name !== "string" || name.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing name"
          );
        }
        if (!isValidServerName(name)) {
          throw RequestError.invalidParams(
            void 0,
            "Server name must be \u2264256 chars, alphanumeric + underscore/hyphen, and not a reserved JS property name"
          );
        }
        if (typeof originatorClientId !== "string" || originatorClientId.length === 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing originatorClientId"
          );
        }
        const manager = this.config.getToolRegistry()?.getMcpClientManager();
        if (!manager) {
          throw RequestError.internalError(
            void 0,
            "McpClientManager unavailable on this Config"
          );
        }
        const result = await manager.removeRuntimeMcpServer(
          name,
          originatorClientId
        );
        await Promise.all(
          this.getActiveSessions().map(async (session) => {
            const sessionManager = session.getConfig().getToolRegistry()?.getMcpClientManager();
            if (!sessionManager) return;
            try {
              await sessionManager.removeRuntimeMcpServer(
                name,
                originatorClientId
              );
            } catch (sessionErr) {
              debugLogger6.warn(
                `workspaceMcpRuntimeRemove: failed to remove runtime MCP server '${name}' from active session ${session.getConfig().getSessionId()}: ${sessionErr instanceof Error ? sessionErr.message : String(sessionErr)}`
              );
            }
          })
        );
        return result;
      }
      case SERVE_CONTROL_EXT_METHODS.workspaceExtensionsRefresh: {
        const sessionId = params["sessionId"];
        const session = this.sessionOrThrow(sessionId);
        const extensionManager = session.getConfig().getExtensionManager();
        await extensionManager.refreshCache();
        try {
          await extensionManager.refreshTools();
        } catch (err) {
          debugLogger6.warn(
            `Extension tool refresh failed for session ${sessionId}: ${err instanceof Error ? err.message : String(err)}`
          );
        }
        await session.sendAvailableCommandsUpdate();
        return { ok: true };
      }
      case "deleteSession": {
        const sessionId = params["sessionId"];
        if (!sessionId || !SESSION_ID_RE.test(sessionId)) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const success = await runWithAcpRuntimeOutputDir(
          this.settings,
          cwd,
          async () => {
            const sessionService = new SessionService(cwd);
            return sessionService.removeSession(sessionId);
          }
        );
        return { success };
      }
      case "renameSession": {
        const sessionId = params["sessionId"];
        const title = params["title"];
        if (!sessionId || !SESSION_ID_RE.test(sessionId)) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        if (!title || typeof title !== "string") {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing title"
          );
        }
        if (title.length > SESSION_TITLE_MAX_LENGTH) {
          throw RequestError.invalidParams(
            void 0,
            `Title too long (max ${SESSION_TITLE_MAX_LENGTH} chars)`
          );
        }
        const liveRecording = this.sessions.get(sessionId)?.getConfig().getChatRecordingService();
        if (liveRecording) {
          const ok = liveRecording.recordCustomTitle(title, "manual");
          await liveRecording.flush();
          return { success: ok };
        }
        const success = await runWithAcpRuntimeOutputDir(
          this.settings,
          cwd,
          async () => {
            const sessionService = new SessionService(cwd);
            return sessionService.renameSession(sessionId, title);
          }
        );
        return { success };
      }
      case "rewindSession":
      case SERVE_CONTROL_EXT_METHODS.sessionRewind: {
        const sessionId = params["sessionId"];
        if (!sessionId || !SESSION_ID_RE.test(sessionId)) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const session = this.sessions.get(sessionId);
        if (!session) {
          throw RequestError.invalidParams(
            void 0,
            `Session not found for id: ${sessionId}`
          );
        }
        let turnIndex = params["targetTurnIndex"];
        const promptId = params["promptId"];
        if (promptId && (turnIndex === void 0 || turnIndex === null)) {
          const prefix = sessionId + "########";
          if (!promptId.startsWith(prefix)) {
            throw new RequestError(-32602, "Invalid promptId format", {
              errorKind: "invalid_rewind_target"
            });
          }
          const suffix = promptId.slice(prefix.length);
          if (!/^\d+$/.test(suffix)) {
            throw new RequestError(
              -32602,
              "Invalid promptId: non-numeric turn suffix",
              { errorKind: "invalid_rewind_target" }
            );
          }
          const fhs = session.getConfig().getFileHistoryService();
          const snapshots = fhs.getSnapshots();
          const snapshotIdx = snapshots.findIndex(
            (s) => s.promptId === promptId
          );
          if (snapshotIdx < 0) {
            throw new RequestError(
              -32602,
              "Snapshot not found for the given promptId",
              { errorKind: "invalid_rewind_target" }
            );
          }
          turnIndex = snapshotIdx;
        }
        if (!Number.isInteger(turnIndex) || turnIndex < 0) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing targetTurnIndex"
          );
        }
        const rewindFiles = params["rewindFiles"] !== false;
        const historyBeforeRewind = session.captureHistorySnapshot();
        let rewindResult;
        try {
          rewindResult = session.rewindToTurn(turnIndex, {
            rewindFiles
          });
        } catch (err) {
          if (err instanceof RequestError) {
            const msg = err.message;
            if (msg.includes("Cannot rewind while a prompt is running")) {
              throw new RequestError(err.code, msg, {
                errorKind: "session_busy"
              });
            }
            if (msg.includes("compressed or does not exist")) {
              throw new RequestError(err.code, msg, {
                errorKind: "invalid_rewind_target"
              });
            }
          }
          throw err;
        }
        let filesChanged = [];
        let filesFailed = [];
        if (rewindFiles && promptId) {
          const fhs = session.getConfig().getFileHistoryService();
          try {
            const fileResult = await fhs.rewind(promptId, true);
            filesChanged = fileResult.filesChanged;
            filesFailed = fileResult.filesFailed;
          } catch (err) {
            const reason = err instanceof Error ? err.message : String(err);
            debugLogger6.error(
              `[ACP] File-history rewind failed for session=${sessionId} promptId=${promptId}: ${reason}`
            );
            filesFailed = [`file-history-rewind: ${reason}`];
          }
        }
        return {
          success: true,
          historyBeforeRewind,
          ...rewindResult,
          filesChanged,
          filesFailed
        };
      }
      case "qwen/session/loadUpdates": {
        const sessionId = params["sessionId"];
        if (!sessionId || !SESSION_ID_RE.test(sessionId)) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const sessionData = await runWithAcpRuntimeOutputDir(
          this.settings,
          cwd,
          async () => {
            const sessionService = new SessionService(cwd);
            return sessionService.loadSession(sessionId);
          }
        );
        if (!sessionData?.conversation) {
          return { updates: [] };
        }
        const updates = [];
        const replayContext = {
          sessionId,
          config: this.config,
          sendUpdate: /* @__PURE__ */ __name(async (update) => {
            updates.push(update);
          }, "sendUpdate"),
          // Fresh accumulator for this replay: MessageEmitter advances it from
          // replayed usage metadata (tokens only — no per-turn durations) and
          // PlanEmitter snapshots it onto each todo update, so resumed sessions
          // recover per-task token spend (API time stays live-only).
          cumulativeUsage: {
            promptTokens: 0,
            cachedTokens: 0,
            candidateTokens: 0,
            apiTimeMs: 0
          }
        };
        let replayError;
        try {
          await new HistoryReplayer(replayContext).replay(
            sessionData.conversation.messages
          );
        } catch (error) {
          replayError = error instanceof Error ? error.message : String(error);
          debugLogger6.warn(
            "[loadUpdates] History replay failed for session %s (partial updates: %d):",
            sessionId,
            updates.length,
            error
          );
        }
        const updatesWithTopLevelTimestamps = updates.map((update) => {
          const record = update;
          const meta = record["_meta"];
          const timestamp = meta && typeof meta === "object" && !Array.isArray(meta) ? meta["timestamp"] : void 0;
          return typeof timestamp === "number" || typeof timestamp === "string" ? { ...record, timestamp } : record;
        });
        return {
          updates: updatesWithTopLevelTimestamps,
          startTime: sessionData.conversation.startTime,
          lastUpdated: sessionData.conversation.lastUpdated,
          // Signal to the client that replay aborted partway so it doesn't
          // render a truncated replay as the full conversation.
          ...replayError !== void 0 ? { partial: true, replayError } : {}
        };
      }
      case "restoreSessionHistory": {
        const sessionId = params["sessionId"];
        const history = params["history"];
        if (!sessionId || !SESSION_ID_RE.test(sessionId)) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        if (!Array.isArray(history)) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing history"
          );
        }
        const session = this.sessions.get(sessionId);
        if (!session) {
          throw RequestError.invalidParams(
            void 0,
            `Session not found for id: ${sessionId}`
          );
        }
        session.restoreHistory(history);
        return { success: true };
      }
      case "getAccountInfo": {
        const sessionId = params["sessionId"];
        const session = sessionId ? this.sessions.get(sessionId) : void 0;
        const config = session ? session.getConfig() : this.config;
        const cfg = config.getContentGeneratorConfig();
        return {
          authType: cfg?.authType ?? config.getAuthType() ?? null,
          model: cfg?.model ?? config.getModel() ?? null,
          baseUrl: cfg?.baseUrl ? sanitizeProviderBaseUrl(cfg.baseUrl) : null,
          apiKeyEnvKey: cfg?.apiKeyEnvKey ?? null
        };
      }
      case SERVE_CONTROL_EXT_METHODS.sessionBranch: {
        const sessionId = params["sessionId"];
        if (typeof sessionId !== "string" || !SESSION_ID_RE.test(sessionId)) {
          throw RequestError.invalidParams(
            void 0,
            "Invalid or missing sessionId"
          );
        }
        const name = params["name"];
        const sourceSession = this.sessions.get(sessionId);
        if (!sourceSession) {
          throw new RequestError(-32004, `Session not found: ${sessionId}`, {
            errorKind: "session_not_found",
            sessionId
          });
        }
        const recording = sourceSession.getConfig().getChatRecordingService();
        if (recording) {
          await recording.flush();
        }
        const newSessionId = randomUUID();
        return await runWithAcpRuntimeOutputDir(
          this.settings,
          cwd,
          async () => {
            const sessionService = new SessionService(cwd);
            await sessionService.forkSession(sessionId, newSessionId);
            let title;
            try {
              let baseName;
              if (typeof name === "string" && name.trim().length > 0) {
                baseName = name.trim();
              } else {
                const existingTitle = recording?.getCurrentCustomTitle();
                const stripped = existingTitle?.replace(/\s*\(Branch(?:\s+\d+)?\)\s*$/, "").trim();
                if (stripped && stripped.length > 0) {
                  baseName = stripped;
                } else {
                  baseName = sessionId.slice(0, 8);
                }
              }
              title = await computeUniqueBranchTitle(baseName, sessionService);
              const renamed = await sessionService.renameSession(
                newSessionId,
                title,
                "manual"
              );
              if (!renamed) {
                throw new RequestError(
                  -32603,
                  `Failed to set title on forked session ${newSessionId}`,
                  { errorKind: "internal", sessionId: newSessionId }
                );
              }
            } catch (err) {
              sessionService.removeSession(newSessionId).catch((rmErr) => {
                process.stderr.write(
                  `qwen serve: failed to clean up orphan session ${newSessionId}: ${rmErr instanceof Error ? rmErr.message : rmErr}
`
                );
              });
              throw err;
            }
            return { newSessionId, title, displayName: title };
          }
        );
      }
      case "qwen/settings/getCore": {
        const settings = loadSettings(cwd);
        this.settings = settings;
        return this.buildCoreSettings(settings, cwd);
      }
      case "qwen/settings/setCoreValue": {
        const key = params["key"];
        if (typeof key !== "string" || !QWEN_CORE_SETTING_KEYS.includes(key)) {
          throw RequestError.invalidParams(
            void 0,
            "Unsupported Qwen setting key"
          );
        }
        const settings = loadSettings(cwd);
        const settingKey = key;
        const normalizedValue = normalizeCoreSettingValue(
          settingKey,
          params["value"]
        );
        const scope = toSettingsScope(params["scope"]);
        settings.setValue(scope, key, normalizedValue);
        if (settingKey === "model.name") {
          settings.setValue(scope, "model.baseUrl", "");
        }
        if (settingKey === "general.outputLanguage" && typeof normalizedValue === "string" && scope === "User" /* User */) {
          updateOutputLanguageFile(normalizedValue);
        }
        this.settings = settings;
        return this.buildCoreSettings(settings, cwd);
      }
      case "qwen/settings/setMcpServer": {
        const name = params["name"];
        if (typeof name !== "string" || !name.trim()) {
          throw RequestError.invalidParams(
            void 0,
            "MCP server name is required"
          );
        }
        const settings = loadSettings(cwd);
        const settingScope = toSettingsScope(params["scope"]);
        const scope = settingScope === "Workspace" /* Workspace */ ? "workspace" : "user";
        const existing = readScopeSettings(settings, scope);
        const existingServers = toRecord(existing["mcpServers"]);
        const mcpServers = {
          ...existingServers,
          [name.trim()]: toStoredMcpServerConfig(
            restoreRedactedMcpSecrets(
              normalizeMcpServerConfig(params["server"]),
              toRecord(existingServers[name.trim()])
            )
          )
        };
        settings.setValue(settingScope, "mcpServers", mcpServers);
        this.settings = settings;
        return this.buildCoreSettings(settings, cwd);
      }
      case "qwen/settings/removeMcpServer": {
        const name = params["name"];
        if (typeof name !== "string" || !name.trim()) {
          throw RequestError.invalidParams(
            void 0,
            "MCP server name is required"
          );
        }
        const settings = loadSettings(cwd);
        const settingScope = toSettingsScope(params["scope"]);
        const scope = settingScope === "Workspace" /* Workspace */ ? "workspace" : "user";
        const existing = readScopeSettings(settings, scope);
        const mcpServers = { ...toRecord(existing["mcpServers"]) };
        delete mcpServers[name.trim()];
        settings.setValue(settingScope, "mcpServers", mcpServers);
        this.settings = settings;
        return this.buildCoreSettings(settings, cwd);
      }
      case "qwen/settings/setHook": {
        const event = params["event"];
        if (!isHookEvent(event)) {
          throw RequestError.invalidParams(void 0, "Invalid hook event");
        }
        const settings = loadSettings(cwd);
        const settingScope = toSettingsScope(params["scope"]);
        const scope = settingScope === "Workspace" /* Workspace */ ? "workspace" : "user";
        const existing = readScopeSettings(settings, scope);
        const hooksRoot = { ...toRecord(existing["hooks"]) };
        const eventHooks = Array.isArray(hooksRoot[event]) ? [...hooksRoot[event]] : [];
        const incomingHook = normalizeHookDefinition(params["hook"]);
        const index = params["index"];
        const isReplace = typeof index === "number" && Number.isInteger(index) && index >= 0 && index < eventHooks.length;
        const hook = restoreRedactedHookSecrets(
          incomingHook,
          isReplace ? toRecord(eventHooks[index]) : {}
        );
        if (isReplace) {
          eventHooks[index] = hook;
        } else {
          eventHooks.push(hook);
        }
        hooksRoot[event] = eventHooks;
        settings.setValue(settingScope, "hooks", hooksRoot);
        this.settings = settings;
        return this.buildCoreSettings(settings, cwd);
      }
      case "qwen/settings/removeHook": {
        const event = params["event"];
        if (!isHookEvent(event)) {
          throw RequestError.invalidParams(void 0, "Invalid hook event");
        }
        const index = params["index"];
        if (typeof index !== "number" || !Number.isInteger(index) || index < 0) {
          throw RequestError.invalidParams(void 0, "Invalid hook index");
        }
        const settings = loadSettings(cwd);
        const settingScope = toSettingsScope(params["scope"]);
        const scope = settingScope === "Workspace" /* Workspace */ ? "workspace" : "user";
        const existing = readScopeSettings(settings, scope);
        const hooksRoot = { ...toRecord(existing["hooks"]) };
        const eventHooks = Array.isArray(hooksRoot[event]) ? [...hooksRoot[event]] : [];
        if (index >= eventHooks.length) {
          throw RequestError.invalidParams(
            void 0,
            `Hook index ${index} out of range (event has ${eventHooks.length} hooks)`
          );
        }
        eventHooks.splice(index, 1);
        hooksRoot[event] = eventHooks;
        settings.setValue(settingScope, "hooks", hooksRoot);
        this.settings = settings;
        return this.buildCoreSettings(settings, cwd);
      }
      case "qwen/settings/setExtensionSetting": {
        const extensionId = params["extensionId"];
        const settingKey = params["settingKey"];
        const value = params["value"];
        if (typeof extensionId !== "string" || !extensionId) {
          throw RequestError.invalidParams(
            void 0,
            "extensionId is required"
          );
        }
        if (typeof settingKey !== "string" || !settingKey) {
          throw RequestError.invalidParams(void 0, "settingKey is required");
        }
        if (typeof value !== "string") {
          throw RequestError.invalidParams(void 0, "value must be a string");
        }
        const settings = loadSettings(cwd);
        const extensionManager = new ExtensionManager({
          workspaceDir: cwd,
          isWorkspaceTrusted: isWorkspaceTrusted(settings.merged).isTrusted ?? true,
          locale: getCurrentLanguage()
        });
        await extensionManager.refreshCache();
        const extension = extensionManager.getLoadedExtensions().find((item) => item.id === extensionId || item.name === extensionId);
        if (!extension) {
          throw RequestError.invalidParams(void 0, "Extension not found");
        }
        const extScope = toSettingsScope(params["scope"]) === "Workspace" /* Workspace */ ? "workspace" /* WORKSPACE */ : "user" /* USER */;
        await updateSetting(
          extension.config,
          extension.id,
          settingKey,
          async () => value,
          extScope
        );
        this.settings = settings;
        return this.buildCoreSettings(settings, cwd);
      }
      case "qwen/permissions/getSettings": {
        const settings = this.loadPermissionSettings(cwd);
        return buildPermissionSettings(settings);
      }
      case "qwen/permissions/setRules": {
        const scope = params["scope"];
        const ruleType = params["ruleType"];
        if (scope !== "user" && scope !== "workspace") {
          throw RequestError.invalidParams(
            void 0,
            'scope must be "user" or "workspace"'
          );
        }
        if (ruleType !== "allow" && ruleType !== "ask" && ruleType !== "deny") {
          throw RequestError.invalidParams(
            void 0,
            'ruleType must be "allow", "ask", or "deny"'
          );
        }
        const settings = this.loadPermissionSettings(cwd);
        const before = readPermissionRuleSet(settings.merged);
        const settingScope = scope === "workspace" ? "Workspace" /* Workspace */ : "User" /* User */;
        const scopeSettings = scope === "workspace" ? settings.workspace.settings : settings.user.settings;
        const existingRules = readPermissionRuleSet(scopeSettings)[ruleType];
        let rules;
        try {
          rules = normalizePermissionRules(params["rules"], {
            existingRules
          });
        } catch (error) {
          if (error instanceof PermissionRulesValidationError) {
            throw RequestError.invalidParams(void 0, error.message);
          }
          throw error;
        }
        settings.setValue(settingScope, `permissions.${ruleType}`, rules);
        const after = readPermissionRuleSet(settings.merged);
        this.syncLivePermissionManagers(before, after);
        return buildPermissionSettings(settings);
      }
      case SERVE_CONTROL_EXT_METHODS.workspaceReload: {
        const oldMerged = structuredClone(this.settings.merged);
        this.settings.reloadScopeFromDisk("User" /* User */);
        this.settings.reloadScopeFromDisk("Workspace" /* Workspace */);
        const newMerged = this.settings.merged;
        const envResult = reloadEnvironment(newMerged, cwd);
        const changed = diffSettingsKeys(oldMerged, newMerged);
        const envChanged = envResult.updatedKeys.length > 0 || envResult.removedKeys.length > 0;
        const sessions = [...this.sessions.entries()];
        const refreshed = [];
        const skipped = [];
        const results = await Promise.allSettled(
          sessions.map(async ([id, session]) => {
            if (!session.isIdle()) {
              skipped.push(id);
              return;
            }
            const config = session.getConfig();
            const authType = config.getAuthType();
            const providersChanged = changed.has("modelProviders") || changed.has("providerProtocol");
            if (providersChanged) {
              try {
                config.reloadModelProvidersConfig(
                  newMerged.modelProviders,
                  newMerged.providerProtocol ?? {}
                );
              } catch (err) {
                debugLogger6.warn(
                  `reload: reloadModelProvidersConfig failed for session ${id}: ${err}`
                );
              }
            }
            const newModelName = newMerged.model?.name;
            if (changed.has("model") && newModelName && newModelName !== config.getModel() && authType) {
              try {
                await config.switchModel(authType, newModelName);
              } catch (err) {
                debugLogger6.warn(
                  `reload: switchModel failed for session ${id}: ${err}`
                );
              }
            } else if ((providersChanged || envChanged) && authType) {
              try {
                await config.refreshAuth(authType);
              } catch (err) {
                debugLogger6.warn(
                  `reload: refreshAuth failed for session ${id}: ${err}`
                );
              }
            }
            if (changed.has("tools")) {
              const disabled = normalizeDisabledToolList(
                newMerged.tools?.disabled
              );
              config.setDisabledTools(new Set(disabled));
              const newMode = newMerged.tools?.approvalMode;
              if (newMode && APPROVAL_MODES.includes(newMode) && newMode !== config.getApprovalMode()) {
                try {
                  config.setApprovalMode(newMode);
                } catch (err) {
                  debugLogger6.warn(
                    `reload: setApprovalMode failed for session ${id}: ${err}`
                  );
                }
              }
            }
            try {
              await config.refreshHierarchicalMemory();
            } catch (err) {
              debugLogger6.warn(
                `reload: refreshHierarchicalMemory failed for session ${id}: ${err}`
              );
            }
            try {
              await config.getGeminiClient()?.refreshSystemInstruction();
            } catch (err) {
              debugLogger6.warn(
                `reload: refreshSystemInstruction failed for session ${id}: ${err}`
              );
            }
            refreshed.push(id);
          })
        );
        for (let i = 0; i < results.length; i++) {
          if (results[i].status === "rejected") {
            const reason = results[i].reason;
            debugLogger6.warn(
              `Session ${sessions[i][0]} reload failed: ${reason}`
            );
            skipped.push(sessions[i][0]);
          }
        }
        return {
          env: envResult,
          changedKeys: [...changed],
          sessionsRefreshed: refreshed,
          sessionsSkipped: skipped
        };
      }
      default:
        throw RequestError.methodNotFound(method);
    }
  }
  // --- private helpers ---
  /**
   * Reverse tool channel (issue #5626, Phase 2). Build the session
   * `McpClientManager`'s `sendSdkMcpMessage` callback. Client-hosted
   * (extension) MCP servers are registered SDK-type, so the manager routes
   * their JSON-RPC through this callback. We forward each frame UP to the
   * parent serve process via the `qwen/control/client_mcp/message` ext-method;
   * the parent's `BridgeClient.extMethod` hands it to the per-WS-connection
   * `ClientMcpRegistrar`, which carries it down the daemon WS to the extension
   * and returns the correlated response (the `payload` field). All SDK-type
   * servers in this session share one callback — the `serverName` argument
   * routes to the right client-hosted server in the parent.
   */
  buildClientMcpSender() {
    return (serverName, message) => deliverClientMcpMessage(this.connection, serverName, message);
  }
  async newSessionConfig(cwd, mcpServers, sessionId, resume) {
    this.settings = loadSettings(cwd);
    const sessionMcpServers = {};
    for (const server of mcpServers) {
      const stdioServer = toStdioServer(server);
      if (stdioServer) {
        const env = {};
        for (const { name: envName, value } of stdioServer.env) {
          env[envName] = value;
        }
        sessionMcpServers[stdioServer.name] = new MCPServerConfig(
          stdioServer.command,
          stdioServer.args,
          env,
          cwd
        );
        continue;
      }
      const sseServer = toSseServer(server);
      if (sseServer) {
        const headers = {};
        for (const { name: headerName, value } of sseServer.headers) {
          headers[headerName] = value;
        }
        sessionMcpServers[sseServer.name] = new MCPServerConfig(
          void 0,
          void 0,
          void 0,
          void 0,
          sseServer.url,
          void 0,
          Object.keys(headers).length > 0 ? headers : void 0
        );
        continue;
      }
      const httpServer = toHttpServer(server);
      if (httpServer) {
        const headers = {};
        for (const { name: headerName, value } of httpServer.headers) {
          headers[headerName] = value;
        }
        sessionMcpServers[httpServer.name] = new MCPServerConfig(
          void 0,
          void 0,
          void 0,
          void 0,
          void 0,
          httpServer.url,
          Object.keys(headers).length > 0 ? headers : void 0
        );
        continue;
      }
    }
    const settings = this.settings.merged;
    const argvForSession = {
      ...this.argv,
      ...resume ? { resume: sessionId } : { sessionId },
      continue: false
    };
    const config = await loadCliConfig(
      settings,
      argvForSession,
      cwd,
      // ACP sessions do not provide an extension override. Passing [] is a
      // truthy override and prevents default/argv extension commands from
      // loading, so leave it unset to preserve normal CLI behavior.
      void 0,
      // Pass separated hooks for proper source attribution
      {
        userHooks: this.settings.getUserHooks(),
        projectHooks: this.settings.getProjectHooks()
      },
      // CRITICAL: close over `this.settings` (LoadedSettings instance), NOT
      // over the local `settings` snapshot built above. `LoadedSettings.
      // setValue` replaces `_merged`, so a closure over the snapshot would
      // never see workspace toggles applied during the session. ACP/Zed
      // sessions otherwise leak persisted disabled skills into the first
      // <available_skills> at cold start.
      buildDisabledSkillNamesProvider(this.settings),
      sessionMcpServers
    );
    if (typeof config.enableFileCheckpointing === "function") {
      config.enableFileCheckpointing();
    }
    if (typeof this.config.getRuntimeMcpServers === "function" && typeof config.addRuntimeMcpServer === "function") {
      const bootstrapRuntimeMcpServers = this.config.getRuntimeMcpServers();
      for (const [runtimeServerName, runtimeServerConfig] of Object.entries(
        bootstrapRuntimeMcpServers
      )) {
        config.addRuntimeMcpServer(runtimeServerName, runtimeServerConfig);
      }
    }
    if (this.mcpPool !== void 0 && typeof config.setMcpTransportPool === "function") {
      config.setMcpTransportPool(this.mcpPool);
    }
    const wiredSessionId = typeof config.getSessionId === "function" ? config.getSessionId() : void 0;
    const skipPerSessionBudgetCallback = this.workspaceMcpBudget !== void 0;
    if (!skipPerSessionBudgetCallback && typeof config.setMcpBudgetEventCallback === "function" && wiredSessionId !== void 0) {
      const sid = wiredSessionId;
      config.setMcpBudgetEventCallback((event) => {
        void this.connection.extNotification("qwen/notify/session/mcp-budget-event", {
          v: 1,
          sessionId: sid,
          ...event
        }).catch((err) => {
          debugLogger6.debug(
            `MCP budget extNotification dropped (session=${sid}, kind=${event.kind}): ${err instanceof Error ? err.message : String(err)}`
          );
        });
      });
    }
    await config.initialize({
      // Reverse tool channel (issue #5626, Phase 2): bind the session
      // manager's SDK MCP callback to the `client_mcp/message` ext-method so a
      // client-hosted (extension) MCP server added at runtime reaches the
      // daemon WS. Servers that aren't client-hosted never use this callback
      // (the daemon only adds SDK-type runtime servers for client MCP).
      sendSdkMcpMessage: this.buildClientMcpSender()
    });
    void this.surfaceMcpFailuresWhenReady(config);
    return config;
  }
  async surfaceMcpFailuresWhenReady(config) {
    try {
      await config.waitForMcpReady();
    } catch (err) {
      debugLogger6.error(
        `MCP discovery readiness failed: ${err instanceof Error ? err.message : String(err)}`
      );
      return;
    }
    const failedMcpServers = typeof config.getFailedMcpServerNames === "function" ? config.getFailedMcpServerNames() : [];
    if (failedMcpServers.length > 0) {
      process.stderr.write(
        `Warning: MCP server(s) failed to start: ${failedMcpServers.join(", ")}. Continuing with built-in tools and any servers that did connect.
`
      );
    }
  }
  async ensureAuthenticated(config) {
    const selectedType = config.getModelsConfig().getCurrentAuthType();
    if (!selectedType) {
      throw RequestError.authRequired(
        { authMethods: pickAuthMethodsForAuthRequired() },
        "Use Qwen Code CLI to authenticate first."
      );
    }
    try {
      await config.refreshAuth(selectedType, true);
    } catch (e) {
      debugLogger6.error(`Authentication failed: ${e}`);
      throw RequestError.authRequired(
        {
          authMethods: pickAuthMethodsForAuthRequired(selectedType)
        },
        "Authentication failed: " + e.message
      );
    }
  }
  setupFileSystem(config) {
    if (!this.clientCapabilities?.fs) return;
    const acpFileSystemService = new AcpFileSystemService(
      this.connection,
      config.getSessionId(),
      this.clientCapabilities.fs,
      config.getFileSystemService(),
      {
        // SYNC: Mirrors ReadFileTool's default allowed local roots, including
        // auto-memory roots, so ACP-local read fallback follows the same policy.
        localReadRoots: [
          config.storage.getProjectTempDir(),
          path3.join(config.storage.getProjectDir(), "subagents"),
          Storage.getGlobalTempDir(),
          getAutoMemoryRoot(config.getTargetDir()),
          getUserAutoMemoryRoot(),
          ...config.storage.getUserSkillsDirs(),
          Storage.getUserExtensionsDir()
        ]
      }
    );
    config.setFileSystemService(acpFileSystemService);
  }
  async createAndStoreSession(config, sessionData, options = {}) {
    const sessionId = config.getSessionId();
    const geminiClient = config.getGeminiClient();
    const needsInitialize = !geminiClient.isInitialized();
    if (needsInitialize) {
      await geminiClient.initialize();
    }
    this.sessions.get(sessionId)?.dispose();
    const session = new Session(
      sessionId,
      config,
      this.connection,
      this.settings
    );
    this.sessions.set(sessionId, session);
    setTimeout(async () => {
      await session.sendAvailableCommandsUpdate();
    }, 0);
    if (sessionData?.fileHistorySnapshots?.length) {
      config.getFileHistoryService().restoreFromSnapshots(sessionData.fileHistorySnapshots);
    }
    if (sessionData?.conversation.messages) {
      config.getChatRecordingService()?.rebuildTurnBoundaries(sessionData.conversation.messages);
    }
    if (options.replayHistory !== false && sessionData?.conversation.messages) {
      await session.replayHistory(sessionData.conversation.messages);
    }
    session.installRewriter();
    session.startCronScheduler();
    return session;
  }
  buildAvailableModels(config) {
    const rawCurrentModelId = (config.getModel() || this.config.getModel() || "").trim();
    const currentAuthType = config.getAuthType();
    const allConfiguredModels = config.getAllConfiguredModels().filter(isMainSelectableModel);
    const activeRuntimeSnapshot = config.getActiveRuntimeModelSnapshot?.();
    const currentModelId = activeRuntimeSnapshot ? formatAcpModelId(
      activeRuntimeSnapshot.id,
      activeRuntimeSnapshot.authType
    ) : this.formatCurrentModelId(rawCurrentModelId, currentAuthType);
    const mappedAvailableModels = allConfiguredModels.map((model) => {
      const effectiveModelId = model.isRuntimeModel && model.runtimeSnapshotId ? model.runtimeSnapshotId : model.id;
      return {
        modelId: formatAcpModelId(effectiveModelId, model.authType),
        name: model.label,
        description: model.description ?? null,
        _meta: {
          contextLimit: model.contextWindowSize ?? tokenLimit(model.id)
        }
      };
    });
    return {
      currentModelId,
      availableModels: mappedAvailableModels
    };
  }
  buildModesData(config) {
    const currentApprovalMode = config.getApprovalMode();
    const availableModes = APPROVAL_MODES.map((mode) => ({
      id: mode,
      name: APPROVAL_MODE_INFO[mode].name,
      description: APPROVAL_MODE_INFO[mode].description
    }));
    return {
      currentModeId: currentApprovalMode,
      availableModes
    };
  }
  buildConfigOptions(config) {
    const currentApprovalMode = config.getApprovalMode();
    const allConfiguredModels = config.getAllConfiguredModels().filter(isMainSelectableModel);
    const rawCurrentModelId = (config.getModel() || "").trim();
    const currentAuthType = config.getAuthType?.();
    const activeRuntimeSnapshot = config.getActiveRuntimeModelSnapshot?.();
    const currentModelId = activeRuntimeSnapshot ? formatAcpModelId(
      activeRuntimeSnapshot.id,
      activeRuntimeSnapshot.authType
    ) : this.formatCurrentModelId(rawCurrentModelId, currentAuthType);
    const modeOptions = APPROVAL_MODES.map((mode) => ({
      value: mode,
      name: APPROVAL_MODE_INFO[mode].name,
      description: APPROVAL_MODE_INFO[mode].description
    }));
    const modeConfigOption = {
      id: "mode",
      name: "Mode",
      description: "Session permission mode",
      category: "mode",
      type: "select",
      currentValue: currentApprovalMode,
      options: modeOptions
    };
    const modelOptions = allConfiguredModels.map((model) => {
      const effectiveModelId = model.isRuntimeModel && model.runtimeSnapshotId ? model.runtimeSnapshotId : model.id;
      return {
        value: formatAcpModelId(effectiveModelId, model.authType),
        name: model.label,
        description: model.description ?? ""
      };
    });
    const modelConfigOption = {
      id: "model",
      name: "Model",
      description: "AI model to use",
      category: "model",
      type: "select",
      currentValue: currentModelId,
      options: modelOptions
    };
    return [modeConfigOption, modelConfigOption];
  }
  formatCurrentModelId(baseModelId, authType) {
    if (!baseModelId) return baseModelId;
    return authType ? formatAcpModelId(baseModelId, authType) : baseModelId;
  }
};
function isMainSelectableModel(model) {
  return model.fastOnly !== true && model.voiceOnly !== true;
}
__name(isMainSelectableModel, "isMainSelectableModel");
function diffSettingsKeys(oldMerged, newMerged) {
  const changed = /* @__PURE__ */ new Set();
  const allKeys = /* @__PURE__ */ new Set([
    ...Object.keys(oldMerged),
    ...Object.keys(newMerged)
  ]);
  for (const key of allKeys) {
    if (JSON.stringify(oldMerged[key]) !== JSON.stringify(newMerged[key])) {
      changed.add(key);
    }
  }
  return changed;
}
__name(diffSettingsKeys, "diffSettingsKeys");
export {
  AUTH_PREFLIGHT_ENV_KEYS,
  AUTH_PREFLIGHT_WAIVED_AUTH_TYPES,
  createWorkspaceMcpBudget,
  deliverClientMcpMessage,
  extractFilesFromTarGz,
  fetchAllowedGitHub,
  normalizeCoreSettingValue,
  runAcpAgent,
  toHttpServer,
  toSseServer,
  toStdioServer
};
/**
 * @license
 * Copyright 2025 Qwen Team
 * SPDX-License-Identifier: Apache-2.0
 */
/**
 * @license
 * Copyright 2025 Qwen
 * SPDX-License-Identifier: Apache-2.0
 */
/**
 * @license
 * Copyright 2026 Qwen Team
 * SPDX-License-Identifier: Apache-2.0
 */
