@tencent-ai/cloud-agent-sdk 0.2.12 → 0.2.13-next.06ef89d.20260310

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -323,9 +323,9 @@ const ToolOutputSchemas = {
323
323
  }),
324
324
  search_content: zod.z.object({
325
325
  type: zod.z.literal("search_content_result"),
326
- directory: zod.z.string(),
326
+ path: zod.z.string(),
327
327
  pattern: zod.z.string(),
328
- fileTypes: zod.z.string(),
328
+ glob: zod.z.string(),
329
329
  matches: zod.z.array(zod.z.object({
330
330
  filePath: zod.z.string(),
331
331
  content: zod.z.string(),
@@ -337,7 +337,7 @@ const ToolOutputSchemas = {
337
337
  totalCount: zod.z.number(),
338
338
  hasMore: zod.z.boolean(),
339
339
  offset: zod.z.number(),
340
- limit: zod.z.number(),
340
+ headLimit: zod.z.number(),
341
341
  contextBefore: zod.z.number(),
342
342
  contextAfter: zod.z.number(),
343
343
  contextAround: zod.z.number().optional(),
@@ -700,7 +700,8 @@ const ExtensionMethod = {
700
700
  CHECKPOINT: "_codebuddy.ai/checkpoint",
701
701
  USAGE: "_codebuddy.ai/usage",
702
702
  COMMAND: "_codebuddy.ai/command",
703
- AUTH_URL: "_codebuddy.ai/authUrl"
703
+ AUTH_URL: "_codebuddy.ai/authUrl",
704
+ FILE_HISTORY_SNAPSHOT: "_codebuddy.ai/file_history_snapshot"
704
705
  };
705
706
  /**
706
707
  * All known extension methods
@@ -711,7 +712,8 @@ const KNOWN_EXTENSIONS = [
711
712
  ExtensionMethod.CHECKPOINT,
712
713
  ExtensionMethod.USAGE,
713
714
  ExtensionMethod.COMMAND,
714
- ExtensionMethod.AUTH_URL
715
+ ExtensionMethod.AUTH_URL,
716
+ ExtensionMethod.FILE_HISTORY_SNAPSHOT
715
717
  ];
716
718
 
717
719
  //#endregion
@@ -762,7 +764,7 @@ function parseSSELine(line, currentEvent) {
762
764
  };
763
765
  }
764
766
  function streamableHttp(options) {
765
- const { endpoint, authToken, headers: customHeaders = {}, reconnect = {}, signal: externalSignal, fetch: customFetch = globalThis.fetch, onConnect, onDisconnect, onError, heartbeatTimeout = 6e4, postTimeout = 3e4, backpressure = {} } = options;
767
+ const { endpoint, authToken, headers: customHeaders = {}, reconnect = {}, signal: externalSignal, fetch: customFetch = globalThis.fetch, onConnect, onDisconnect, onError, heartbeatTimeout = 6e4, connectionTimeout = 3e4, postTimeout = 3e4, backpressure = {} } = options;
766
768
  const { enabled: reconnectEnabled = true, initialDelay = 1e3, maxDelay = 3e4, maxRetries = Infinity, jitter: jitterEnabled = true } = reconnect;
767
769
  const { highWaterMark = 100, lowWaterMark = 50, pauseTimeout = 5e3 } = backpressure;
768
770
  let connectionId;
@@ -973,11 +975,21 @@ function streamableHttp(options) {
973
975
  const headers = buildHeaders();
974
976
  headers["Accept"] = "text/event-stream";
975
977
  if (lastEventId) headers["Last-Event-ID"] = lastEventId;
976
- const response = await customFetch(endpoint, {
977
- method: "GET",
978
- headers,
979
- signal: combinedSignal
980
- });
978
+ const connectTimeoutMs = connectionTimeout > 0 ? connectionTimeout : 3e4;
979
+ const connectController = new AbortController();
980
+ const connectTimer = setTimeout(() => connectController.abort(), connectTimeoutMs);
981
+ if (externalSignal) externalSignal.addEventListener("abort", () => connectController.abort(), { once: true });
982
+ abortController.signal.addEventListener("abort", () => connectController.abort(), { once: true });
983
+ let response;
984
+ try {
985
+ response = await customFetch(endpoint, {
986
+ method: "GET",
987
+ headers,
988
+ signal: connectController.signal
989
+ });
990
+ } finally {
991
+ clearTimeout(connectTimer);
992
+ }
981
993
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
982
994
  const newConnectionId = response.headers.get("Acp-Connection-Id");
983
995
  if (!newConnectionId) throw new Error("Server did not return Acp-Connection-Id header");
@@ -1913,6 +1925,9 @@ var StreamableHttpClient = class {
1913
1925
  headers: this.options.headers,
1914
1926
  reconnect: this.options.reconnect,
1915
1927
  fetch: this.options.fetch,
1928
+ heartbeatTimeout: this.options.heartbeatTimeout,
1929
+ postTimeout: this.options.postTimeout,
1930
+ connectionTimeout: this.options.connectionTimeout,
1916
1931
  onConnect: (connectionId) => {
1917
1932
  this.options.logger?.debug(`Transport connected: ${connectionId}`);
1918
1933
  },
@@ -1987,10 +2002,6 @@ var StreamableHttpClient = class {
1987
2002
  },
1988
2003
  requestPermission: async (params) => this.handleRequestPermission(params),
1989
2004
  extNotification: async (method, params) => {
1990
- console.log("[ACP-Client] extNotification callback invoked:", {
1991
- method,
1992
- paramsKeys: Object.keys(params)
1993
- });
1994
2005
  await this.handleExtNotification(method, params);
1995
2006
  },
1996
2007
  extMethod: async (method, params) => this.handleExtMethod(method, params)
@@ -1998,10 +2009,15 @@ var StreamableHttpClient = class {
1998
2009
  }
1999
2010
  /**
2000
2011
  * Create a new session
2012
+ *
2013
+ * Retries on transient network errors (e.g., proxy connection reset)
2014
+ * since session/new is idempotent and safe to retry.
2001
2015
  */
2002
2016
  async createSession(cwd) {
2003
2017
  this.ensureInitialized("createSession");
2004
- try {
2018
+ const maxRetries = 2;
2019
+ let lastError;
2020
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
2005
2021
  const response = await this.connection.newSession({
2006
2022
  cwd,
2007
2023
  mcpServers: []
@@ -2009,8 +2025,16 @@ var StreamableHttpClient = class {
2009
2025
  this.options.logger?.info(`Session created: ${response.sessionId}`);
2010
2026
  return response;
2011
2027
  } catch (err) {
2012
- throw new SessionError(`Failed to create session: ${err instanceof Error ? err.message : String(err)}`, void 0, err instanceof Error ? err : void 0);
2028
+ lastError = err instanceof Error ? err : new Error(String(err));
2029
+ if (attempt < maxRetries && isRetryableNetworkError(err)) {
2030
+ const delay = 500 * Math.pow(2, attempt);
2031
+ this.options.logger?.warn(`session/new network error, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries}): ${lastError.message}`);
2032
+ await new Promise((resolve) => setTimeout(resolve, delay));
2033
+ continue;
2034
+ }
2035
+ throw new SessionError(`Failed to create session: ${lastError.message}`, void 0, lastError);
2013
2036
  }
2037
+ throw new SessionError(`Failed to create session: ${lastError?.message}`, void 0, lastError);
2014
2038
  }
2015
2039
  /**
2016
2040
  * Load an existing session
@@ -2234,6 +2258,18 @@ var StreamableHttpClient = class {
2234
2258
  if (this.state !== "initialized") throw new InvalidStateError(operation, this.state, ["initialized"]);
2235
2259
  }
2236
2260
  };
2261
+ /**
2262
+ * Check if an error is a retryable network-level error.
2263
+ * Only network failures (TypeError from fetch) are retried, NOT HTTP errors (4xx/5xx).
2264
+ */
2265
+ function isRetryableNetworkError(error) {
2266
+ if (error instanceof TypeError) return true;
2267
+ if (error instanceof Error) {
2268
+ const msg = error.message.toLowerCase();
2269
+ return msg.includes("failed to fetch") || msg.includes("fetch failed") || msg.includes("network request failed") || msg.includes("econnreset") || msg.includes("econnrefused") || msg.includes("socket hang up");
2270
+ }
2271
+ return false;
2272
+ }
2237
2273
 
2238
2274
  //#endregion
2239
2275
  //#region ../agent-provider/src/common/providers/cloud-agent-provider/cloud-connection.ts
@@ -2269,7 +2305,7 @@ var CloudAgentConnection = class {
2269
2305
  fetch: config.fetch,
2270
2306
  clientCapabilities: config.clientCapabilities,
2271
2307
  onSessionUpdate: (update) => {
2272
- if (!this._isStreaming) this.emit("sessionUpdate", update);
2308
+ if (!this._isStreaming && this.isOwnSessionNotification(update)) this.emit("sessionUpdate", update);
2273
2309
  },
2274
2310
  onArtifact: (artifact, event) => {
2275
2311
  console.log("[CloudConnection] onArtifact callback:", {
@@ -2283,10 +2319,41 @@ var CloudAgentConnection = class {
2283
2319
  },
2284
2320
  onUsageUpdate: (usage) => {
2285
2321
  this.emit("usageUpdate", usage);
2322
+ },
2323
+ onExtNotification: (method, params) => {
2324
+ console.log("[CloudConnection] Received extNotification:", {
2325
+ method,
2326
+ paramsKeys: Object.keys(params)
2327
+ });
2328
+ if (method === ExtensionMethod.COMMAND) {
2329
+ const action = params.action;
2330
+ const commandParams = params.params;
2331
+ console.log("[CloudConnection] Emitting command event:", {
2332
+ action,
2333
+ paramsKeys: commandParams ? Object.keys(commandParams) : []
2334
+ });
2335
+ this.emit("command", {
2336
+ action,
2337
+ params: commandParams
2338
+ });
2339
+ }
2286
2340
  }
2287
2341
  });
2288
2342
  this.setupEventForwarding();
2289
2343
  }
2344
+ /**
2345
+ * Check whether a notification belongs to this connection's own session.
2346
+ *
2347
+ * CloudConnection.createSession() overrides sessionId to agentId, so the
2348
+ * rest of the client stack uses agentId as the canonical session
2349
+ * identifier. Notifications whose sessionId differs from agentId
2350
+ * originate from sub-agent sessions running inside the same sandbox and
2351
+ * should be silently ignored at this layer — the adapter layer handles
2352
+ * sub-agent messages independently via parentToolUseId in _meta.
2353
+ */
2354
+ isOwnSessionNotification(notification) {
2355
+ return notification.sessionId === this.agentId;
2356
+ }
2290
2357
  setupEventForwarding() {
2291
2358
  this.client.on("connecting", () => {
2292
2359
  this.emit("connecting", void 0);
@@ -2411,7 +2478,7 @@ var CloudAgentConnection = class {
2411
2478
  }
2412
2479
  async createSession(params) {
2413
2480
  return {
2414
- ...await this.client.loadSession(this.agentId, this.cwd),
2481
+ ...await this.client.createSession(this.cwd),
2415
2482
  sessionId: this.agentId
2416
2483
  };
2417
2484
  }
@@ -2447,6 +2514,7 @@ var CloudAgentConnection = class {
2447
2514
  let resolveUpdate = null;
2448
2515
  let done = false;
2449
2516
  const listener = (update) => {
2517
+ if (!this.isOwnSessionNotification(update)) return;
2450
2518
  if (resolveUpdate) {
2451
2519
  resolveUpdate(update);
2452
2520
  resolveUpdate = null;
@@ -2529,6 +2597,16 @@ var CloudAgentConnection = class {
2529
2597
  get sessionConnectionInfo() {
2530
2598
  return this._sessionConnectionInfo;
2531
2599
  }
2600
+ async reportTelemetry(eventName, payload) {
2601
+ try {
2602
+ await this.client.extMethod("reportTelemetry", {
2603
+ eventName,
2604
+ payload
2605
+ });
2606
+ } catch (error) {
2607
+ console.warn("[CloudAgentConnection] reportTelemetry failed:", error);
2608
+ }
2609
+ }
2532
2610
  async extMethod(method, params) {
2533
2611
  return this.client.extMethod(method, params);
2534
2612
  }
@@ -3131,7 +3209,7 @@ var utils_default = {
3131
3209
  *
3132
3210
  * @returns {Error} The created error.
3133
3211
  */
3134
- function AxiosError(message, code, config, request, response) {
3212
+ function AxiosError$1(message, code, config, request, response) {
3135
3213
  Error.call(this);
3136
3214
  if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
3137
3215
  else this.stack = (/* @__PURE__ */ new Error()).stack;
@@ -3145,7 +3223,7 @@ function AxiosError(message, code, config, request, response) {
3145
3223
  this.status = response.status ? response.status : null;
3146
3224
  }
3147
3225
  }
3148
- utils_default.inherits(AxiosError, Error, { toJSON: function toJSON() {
3226
+ utils_default.inherits(AxiosError$1, Error, { toJSON: function toJSON() {
3149
3227
  return {
3150
3228
  message: this.message,
3151
3229
  name: this.name,
@@ -3160,7 +3238,7 @@ utils_default.inherits(AxiosError, Error, { toJSON: function toJSON() {
3160
3238
  status: this.status
3161
3239
  };
3162
3240
  } });
3163
- const prototype$1 = AxiosError.prototype;
3241
+ const prototype$1 = AxiosError$1.prototype;
3164
3242
  const descriptors = {};
3165
3243
  [
3166
3244
  "ERR_BAD_OPTION_VALUE",
@@ -3178,22 +3256,22 @@ const descriptors = {};
3178
3256
  ].forEach((code) => {
3179
3257
  descriptors[code] = { value: code };
3180
3258
  });
3181
- Object.defineProperties(AxiosError, descriptors);
3259
+ Object.defineProperties(AxiosError$1, descriptors);
3182
3260
  Object.defineProperty(prototype$1, "isAxiosError", { value: true });
3183
- AxiosError.from = (error, code, config, request, response, customProps) => {
3261
+ AxiosError$1.from = (error, code, config, request, response, customProps) => {
3184
3262
  const axiosError = Object.create(prototype$1);
3185
3263
  utils_default.toFlatObject(error, axiosError, function filter(obj) {
3186
3264
  return obj !== Error.prototype;
3187
3265
  }, (prop) => {
3188
3266
  return prop !== "isAxiosError";
3189
3267
  });
3190
- AxiosError.call(axiosError, error.message, code, config, request, response);
3268
+ AxiosError$1.call(axiosError, error.message, code, config, request, response);
3191
3269
  axiosError.cause = error;
3192
3270
  axiosError.name = error.name;
3193
3271
  customProps && Object.assign(axiosError, customProps);
3194
3272
  return axiosError;
3195
3273
  };
3196
- var AxiosError_default = AxiosError;
3274
+ var AxiosError_default = AxiosError$1;
3197
3275
 
3198
3276
  //#endregion
3199
3277
  //#region ../../node_modules/delayed-stream/lib/delayed_stream.js
@@ -11920,7 +11998,7 @@ const predicates = utils_default.toFlatObject(utils_default, {}, null, function
11920
11998
  *
11921
11999
  * @returns
11922
12000
  */
11923
- function toFormData(obj, formData, options) {
12001
+ function toFormData$1(obj, formData, options) {
11924
12002
  if (!utils_default.isObject(obj)) throw new TypeError("target must be an object");
11925
12003
  formData = formData || new (FormData_default || FormData)();
11926
12004
  options = utils_default.toFlatObject(options, {
@@ -11991,7 +12069,7 @@ function toFormData(obj, formData, options) {
11991
12069
  build(obj);
11992
12070
  return formData;
11993
12071
  }
11994
- var toFormData_default = toFormData;
12072
+ var toFormData_default = toFormData$1;
11995
12073
 
11996
12074
  //#endregion
11997
12075
  //#region ../agent-provider/node_modules/axios/lib/helpers/AxiosURLSearchParams.js
@@ -12515,7 +12593,7 @@ function buildAccessors(obj, header) {
12515
12593
  });
12516
12594
  });
12517
12595
  }
12518
- var AxiosHeaders = class {
12596
+ var AxiosHeaders$1 = class {
12519
12597
  constructor(headers) {
12520
12598
  headers && this.set(headers);
12521
12599
  }
@@ -12653,7 +12731,7 @@ var AxiosHeaders = class {
12653
12731
  return this;
12654
12732
  }
12655
12733
  };
12656
- AxiosHeaders.accessor([
12734
+ AxiosHeaders$1.accessor([
12657
12735
  "Content-Type",
12658
12736
  "Content-Length",
12659
12737
  "Accept",
@@ -12661,7 +12739,7 @@ AxiosHeaders.accessor([
12661
12739
  "User-Agent",
12662
12740
  "Authorization"
12663
12741
  ]);
12664
- utils_default.reduceDescriptors(AxiosHeaders.prototype, ({ value }, key) => {
12742
+ utils_default.reduceDescriptors(AxiosHeaders$1.prototype, ({ value }, key) => {
12665
12743
  let mapped = key[0].toUpperCase() + key.slice(1);
12666
12744
  return {
12667
12745
  get: () => value,
@@ -12670,8 +12748,8 @@ utils_default.reduceDescriptors(AxiosHeaders.prototype, ({ value }, key) => {
12670
12748
  }
12671
12749
  };
12672
12750
  });
12673
- utils_default.freezeMethods(AxiosHeaders);
12674
- var AxiosHeaders_default = AxiosHeaders;
12751
+ utils_default.freezeMethods(AxiosHeaders$1);
12752
+ var AxiosHeaders_default = AxiosHeaders$1;
12675
12753
 
12676
12754
  //#endregion
12677
12755
  //#region ../agent-provider/node_modules/axios/lib/core/transformData.js
@@ -12697,7 +12775,7 @@ function transformData(fns, response) {
12697
12775
 
12698
12776
  //#endregion
12699
12777
  //#region ../agent-provider/node_modules/axios/lib/cancel/isCancel.js
12700
- function isCancel(value) {
12778
+ function isCancel$1(value) {
12701
12779
  return !!(value && value.__CANCEL__);
12702
12780
  }
12703
12781
 
@@ -12712,12 +12790,12 @@ function isCancel(value) {
12712
12790
  *
12713
12791
  * @returns {CanceledError} The created error.
12714
12792
  */
12715
- function CanceledError(message, config, request) {
12793
+ function CanceledError$1(message, config, request) {
12716
12794
  AxiosError_default.call(this, message == null ? "canceled" : message, AxiosError_default.ERR_CANCELED, config, request);
12717
12795
  this.name = "CanceledError";
12718
12796
  }
12719
- utils_default.inherits(CanceledError, AxiosError_default, { __CANCEL__: true });
12720
- var CanceledError_default = CanceledError;
12797
+ utils_default.inherits(CanceledError$1, AxiosError_default, { __CANCEL__: true });
12798
+ var CanceledError_default = CanceledError$1;
12721
12799
 
12722
12800
  //#endregion
12723
12801
  //#region ../agent-provider/node_modules/axios/lib/core/settle.js
@@ -14119,7 +14197,9 @@ var require_follow_redirects = /* @__PURE__ */ __commonJSMin(((exports, module)
14119
14197
 
14120
14198
  //#endregion
14121
14199
  //#region ../agent-provider/node_modules/axios/lib/env/data.js
14122
- const VERSION = "1.10.0";
14200
+ var import_follow_redirects = /* @__PURE__ */ __toESM(require_follow_redirects(), 1);
14201
+ var import_proxy_from_env = /* @__PURE__ */ __toESM(require_proxy_from_env(), 1);
14202
+ const VERSION$1 = "1.10.0";
14123
14203
 
14124
14204
  //#endregion
14125
14205
  //#region ../agent-provider/node_modules/axios/lib/helpers/parseProtocol.js
@@ -14482,8 +14562,6 @@ const asyncDecorator = (fn) => (...args) => utils_default.asap(() => fn(...args)
14482
14562
 
14483
14563
  //#endregion
14484
14564
  //#region ../agent-provider/node_modules/axios/lib/adapters/http.js
14485
- var import_proxy_from_env = /* @__PURE__ */ __toESM(require_proxy_from_env(), 1);
14486
- var import_follow_redirects = /* @__PURE__ */ __toESM(require_follow_redirects(), 1);
14487
14565
  const zlibOptions = {
14488
14566
  flush: zlib.default.constants.Z_SYNC_FLUSH,
14489
14567
  finishFlush: zlib.default.constants.Z_SYNC_FLUSH
@@ -14649,7 +14727,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config) {
14649
14727
  }
14650
14728
  if (supportedProtocols.indexOf(protocol) === -1) return reject(new AxiosError_default("Unsupported protocol " + protocol, AxiosError_default.ERR_BAD_REQUEST, config));
14651
14729
  const headers = AxiosHeaders_default.from(config.headers).normalize();
14652
- headers.set("User-Agent", "axios/" + VERSION, false);
14730
+ headers.set("User-Agent", "axios/" + VERSION$1, false);
14653
14731
  const { onUploadProgress, onDownloadProgress } = config;
14654
14732
  const maxRate = config.maxRate;
14655
14733
  let maxUploadRate = void 0;
@@ -14659,7 +14737,7 @@ var http_default = isHttpAdapterSupported && function httpAdapter(config) {
14659
14737
  data = formDataToStream_default(data, (formHeaders) => {
14660
14738
  headers.set(formHeaders);
14661
14739
  }, {
14662
- tag: `axios-${VERSION}-boundary`,
14740
+ tag: `axios-${VERSION$1}-boundary`,
14663
14741
  boundary: userBoundary && userBoundary[1] || void 0
14664
14742
  });
14665
14743
  } else if (utils_default.isFormData(data) && utils_default.isFunction(data.getHeaders)) {
@@ -14924,7 +15002,7 @@ const headersToObject = (thing) => thing instanceof AxiosHeaders_default ? { ...
14924
15002
  *
14925
15003
  * @returns {Object} New object resulting from merging config2 to config1
14926
15004
  */
14927
- function mergeConfig(config1, config2) {
15005
+ function mergeConfig$1(config1, config2) {
14928
15006
  config2 = config2 || {};
14929
15007
  const config = {};
14930
15008
  function getMergedValue(target, source, prop, caseless) {
@@ -14990,7 +15068,7 @@ function mergeConfig(config1, config2) {
14990
15068
  //#endregion
14991
15069
  //#region ../agent-provider/node_modules/axios/lib/helpers/resolveConfig.js
14992
15070
  var resolveConfig_default = (config) => {
14993
- const newConfig = mergeConfig({}, config);
15071
+ const newConfig = mergeConfig$1({}, config);
14994
15072
  let { data, withXSRFToken, xsrfHeaderName, xsrfCookieName, headers, auth } = newConfig;
14995
15073
  newConfig.headers = headers = AxiosHeaders_default.from(headers);
14996
15074
  newConfig.url = buildURL(buildFullPath(newConfig.baseURL, newConfig.url, newConfig.allowAbsoluteUrls), config.params, config.paramsSerializer);
@@ -15421,7 +15499,7 @@ function dispatchRequest(config) {
15421
15499
  response.headers = AxiosHeaders_default.from(response.headers);
15422
15500
  return response;
15423
15501
  }, function onAdapterRejection(reason) {
15424
- if (!isCancel(reason)) {
15502
+ if (!isCancel$1(reason)) {
15425
15503
  throwIfCancellationRequested(config);
15426
15504
  if (reason && reason.response) {
15427
15505
  reason.response.data = transformData.call(config, config.transformResponse, reason.response);
@@ -15459,7 +15537,7 @@ const deprecatedWarnings = {};
15459
15537
  */
15460
15538
  validators$1.transitional = function transitional(validator, version, message) {
15461
15539
  function formatMessage(opt, desc) {
15462
- return "[Axios v" + VERSION + "] Transitional option '" + opt + "'" + desc + (message ? ". " + message : "");
15540
+ return "[Axios v" + VERSION$1 + "] Transitional option '" + opt + "'" + desc + (message ? ". " + message : "");
15463
15541
  }
15464
15542
  return (value, opt, opts) => {
15465
15543
  if (validator === false) throw new AxiosError_default(formatMessage(opt, " has been removed" + (version ? " in " + version : "")), AxiosError_default.ERR_DEPRECATED);
@@ -15516,7 +15594,7 @@ const validators = validator_default.validators;
15516
15594
  *
15517
15595
  * @return {Axios} A new instance of Axios
15518
15596
  */
15519
- var Axios = class {
15597
+ var Axios$1 = class {
15520
15598
  constructor(instanceConfig) {
15521
15599
  this.defaults = instanceConfig || {};
15522
15600
  this.interceptors = {
@@ -15553,7 +15631,7 @@ var Axios = class {
15553
15631
  config = config || {};
15554
15632
  config.url = configOrUrl;
15555
15633
  } else config = configOrUrl || {};
15556
- config = mergeConfig(this.defaults, config);
15634
+ config = mergeConfig$1(this.defaults, config);
15557
15635
  const { transitional, paramsSerializer, headers } = config;
15558
15636
  if (transitional !== void 0) validator_default.assertOptions(transitional, {
15559
15637
  silentJSONParsing: validators.transitional(validators.boolean),
@@ -15632,7 +15710,7 @@ var Axios = class {
15632
15710
  return promise;
15633
15711
  }
15634
15712
  getUri(config) {
15635
- config = mergeConfig(this.defaults, config);
15713
+ config = mergeConfig$1(this.defaults, config);
15636
15714
  return buildURL(buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls), config.params, config.paramsSerializer);
15637
15715
  }
15638
15716
  };
@@ -15642,8 +15720,8 @@ utils_default.forEach([
15642
15720
  "head",
15643
15721
  "options"
15644
15722
  ], function forEachMethodNoData(method) {
15645
- Axios.prototype[method] = function(url, config) {
15646
- return this.request(mergeConfig(config || {}, {
15723
+ Axios$1.prototype[method] = function(url, config) {
15724
+ return this.request(mergeConfig$1(config || {}, {
15647
15725
  method,
15648
15726
  url,
15649
15727
  data: (config || {}).data
@@ -15657,7 +15735,7 @@ utils_default.forEach([
15657
15735
  ], function forEachMethodWithData(method) {
15658
15736
  function generateHTTPMethod(isForm) {
15659
15737
  return function httpMethod(url, data, config) {
15660
- return this.request(mergeConfig(config || {}, {
15738
+ return this.request(mergeConfig$1(config || {}, {
15661
15739
  method,
15662
15740
  headers: isForm ? { "Content-Type": "multipart/form-data" } : {},
15663
15741
  url,
@@ -15665,10 +15743,10 @@ utils_default.forEach([
15665
15743
  }));
15666
15744
  };
15667
15745
  }
15668
- Axios.prototype[method] = generateHTTPMethod();
15669
- Axios.prototype[method + "Form"] = generateHTTPMethod(true);
15746
+ Axios$1.prototype[method] = generateHTTPMethod();
15747
+ Axios$1.prototype[method + "Form"] = generateHTTPMethod(true);
15670
15748
  });
15671
- var Axios_default = Axios;
15749
+ var Axios_default = Axios$1;
15672
15750
 
15673
15751
  //#endregion
15674
15752
  //#region ../agent-provider/node_modules/axios/lib/cancel/CancelToken.js
@@ -15679,7 +15757,7 @@ var Axios_default = Axios;
15679
15757
  *
15680
15758
  * @returns {CancelToken}
15681
15759
  */
15682
- var CancelToken = class CancelToken {
15760
+ var CancelToken$1 = class CancelToken$1 {
15683
15761
  constructor(executor) {
15684
15762
  if (typeof executor !== "function") throw new TypeError("executor must be a function.");
15685
15763
  let resolvePromise;
@@ -15751,14 +15829,14 @@ var CancelToken = class CancelToken {
15751
15829
  static source() {
15752
15830
  let cancel;
15753
15831
  return {
15754
- token: new CancelToken(function executor(c) {
15832
+ token: new CancelToken$1(function executor(c) {
15755
15833
  cancel = c;
15756
15834
  }),
15757
15835
  cancel
15758
15836
  };
15759
15837
  }
15760
15838
  };
15761
- var CancelToken_default = CancelToken;
15839
+ var CancelToken_default = CancelToken$1;
15762
15840
 
15763
15841
  //#endregion
15764
15842
  //#region ../agent-provider/node_modules/axios/lib/helpers/spread.js
@@ -15783,7 +15861,7 @@ var CancelToken_default = CancelToken;
15783
15861
  *
15784
15862
  * @returns {Function}
15785
15863
  */
15786
- function spread(callback) {
15864
+ function spread$1(callback) {
15787
15865
  return function wrap(arr) {
15788
15866
  return callback.apply(null, arr);
15789
15867
  };
@@ -15798,13 +15876,13 @@ function spread(callback) {
15798
15876
  *
15799
15877
  * @returns {boolean} True if the payload is an error thrown by Axios, otherwise false
15800
15878
  */
15801
- function isAxiosError(payload) {
15879
+ function isAxiosError$1(payload) {
15802
15880
  return utils_default.isObject(payload) && payload.isAxiosError === true;
15803
15881
  }
15804
15882
 
15805
15883
  //#endregion
15806
15884
  //#region ../agent-provider/node_modules/axios/lib/helpers/HttpStatusCode.js
15807
- const HttpStatusCode = {
15885
+ const HttpStatusCode$1 = {
15808
15886
  Continue: 100,
15809
15887
  SwitchingProtocols: 101,
15810
15888
  Processing: 102,
@@ -15869,10 +15947,10 @@ const HttpStatusCode = {
15869
15947
  NotExtended: 510,
15870
15948
  NetworkAuthenticationRequired: 511
15871
15949
  };
15872
- Object.entries(HttpStatusCode).forEach(([key, value]) => {
15873
- HttpStatusCode[value] = key;
15950
+ Object.entries(HttpStatusCode$1).forEach(([key, value]) => {
15951
+ HttpStatusCode$1[value] = key;
15874
15952
  });
15875
- var HttpStatusCode_default = HttpStatusCode;
15953
+ var HttpStatusCode_default = HttpStatusCode$1;
15876
15954
 
15877
15955
  //#endregion
15878
15956
  //#region ../agent-provider/node_modules/axios/lib/axios.js
@@ -15889,7 +15967,7 @@ function createInstance(defaultConfig) {
15889
15967
  utils_default.extend(instance, Axios_default.prototype, context, { allOwnKeys: true });
15890
15968
  utils_default.extend(instance, context, null, { allOwnKeys: true });
15891
15969
  instance.create = function create(instanceConfig) {
15892
- return createInstance(mergeConfig(defaultConfig, instanceConfig));
15970
+ return createInstance(mergeConfig$1(defaultConfig, instanceConfig));
15893
15971
  };
15894
15972
  return instance;
15895
15973
  }
@@ -15897,17 +15975,17 @@ const axios = createInstance(defaults_default);
15897
15975
  axios.Axios = Axios_default;
15898
15976
  axios.CanceledError = CanceledError_default;
15899
15977
  axios.CancelToken = CancelToken_default;
15900
- axios.isCancel = isCancel;
15901
- axios.VERSION = VERSION;
15978
+ axios.isCancel = isCancel$1;
15979
+ axios.VERSION = VERSION$1;
15902
15980
  axios.toFormData = toFormData_default;
15903
15981
  axios.AxiosError = AxiosError_default;
15904
15982
  axios.Cancel = axios.CanceledError;
15905
15983
  axios.all = function all(promises) {
15906
15984
  return Promise.all(promises);
15907
15985
  };
15908
- axios.spread = spread;
15909
- axios.isAxiosError = isAxiosError;
15910
- axios.mergeConfig = mergeConfig;
15986
+ axios.spread = spread$1;
15987
+ axios.isAxiosError = isAxiosError$1;
15988
+ axios.mergeConfig = mergeConfig$1;
15911
15989
  axios.AxiosHeaders = AxiosHeaders_default;
15912
15990
  axios.formToJSON = (thing) => formDataToJSON_default(utils_default.isHTMLForm(thing) ? new FormData(thing) : thing);
15913
15991
  axios.getAdapter = adapters_default.getAdapter;
@@ -15915,6 +15993,10 @@ axios.HttpStatusCode = HttpStatusCode_default;
15915
15993
  axios.default = axios;
15916
15994
  var axios_default = axios;
15917
15995
 
15996
+ //#endregion
15997
+ //#region ../agent-provider/node_modules/axios/index.js
15998
+ const { Axios, AxiosError, CanceledError, isCancel, CancelToken, VERSION, all, Cancel, isAxiosError, spread, toFormData, AxiosHeaders, HttpStatusCode, formToJSON, getAdapter, mergeConfig } = axios_default;
15999
+
15918
16000
  //#endregion
15919
16001
  //#region ../agent-provider/src/http/http-service.ts
15920
16002
  /**
@@ -15923,7 +16005,7 @@ var axios_default = axios;
15923
16005
  * 特性:
15924
16006
  * - 单例模式,全局唯一实例,延迟初始化(首次使用时自动创建)
15925
16007
  * - 支持拦截器注册(其他模块可注入 header)
15926
- * - 统一 401/403 处理
16008
+ * - 统一 401 处理(支持自动刷新 token 并重试)
15927
16009
  * - 自动携带凭证(withCredentials)
15928
16010
  * - 类型安全
15929
16011
  *
@@ -15963,6 +16045,8 @@ var HttpService = class HttpService {
15963
16045
  */
15964
16046
  constructor(config = {}) {
15965
16047
  this.unauthorizedCallbacks = /* @__PURE__ */ new Set();
16048
+ this.isRefreshing = false;
16049
+ this.refreshSubscribers = [];
15966
16050
  this.config = config;
15967
16051
  this.axiosInstance = axios_default.create({
15968
16052
  baseURL: config.baseURL?.replace(/\/$/, "") || "",
@@ -16003,18 +16087,54 @@ var HttpService = class HttpService {
16003
16087
  }, (error) => Promise.reject(error));
16004
16088
  }
16005
16089
  /**
16006
- * 注册默认响应拦截器(处理 401/403)
16090
+ * 注册默认响应拦截器(处理 401,支持自动重试)
16007
16091
  */
16008
16092
  registerDefaultResponseInterceptor() {
16009
- this.axiosInstance.interceptors.response.use((response) => response, (error) => {
16010
- if (error.response?.status === 401 || error.response?.status === 403) {
16011
- console.warn("[HttpService] Unauthorized (401/403), triggering callbacks");
16012
- this.triggerUnauthorizedCallbacks();
16093
+ this.axiosInstance.interceptors.response.use((response) => response, async (error) => {
16094
+ const originalRequest = error.config;
16095
+ if (error.response?.status === 401 && originalRequest && !originalRequest._retry) {
16096
+ if (originalRequest.url?.includes("/console/accounts")) {
16097
+ console.warn("[HttpService] Unauthorized 401 on refresh endpoint, not retrying");
16098
+ return Promise.reject(error);
16099
+ }
16100
+ console.warn("[HttpService] Unauthorized 401, attempting token refresh and retry");
16101
+ originalRequest._retry = true;
16102
+ if (this.isRefreshing) return new Promise((resolve, reject) => {
16103
+ this.refreshSubscribers.push((success) => {
16104
+ if (success) this.axiosInstance.request(originalRequest).then(resolve).catch(reject);
16105
+ else reject(error);
16106
+ });
16107
+ });
16108
+ this.isRefreshing = true;
16109
+ try {
16110
+ await this.triggerUnauthorizedCallbacks();
16111
+ this.onRefreshSuccess();
16112
+ return this.axiosInstance.request(originalRequest);
16113
+ } catch (refreshError) {
16114
+ this.onRefreshFailure();
16115
+ return Promise.reject(error);
16116
+ } finally {
16117
+ this.isRefreshing = false;
16118
+ }
16013
16119
  }
16014
16120
  return Promise.reject(error);
16015
16121
  });
16016
16122
  }
16017
16123
  /**
16124
+ * token 刷新成功,通知所有等待的请求
16125
+ */
16126
+ onRefreshSuccess() {
16127
+ this.refreshSubscribers.forEach((callback) => callback(true));
16128
+ this.refreshSubscribers = [];
16129
+ }
16130
+ /**
16131
+ * token 刷新失败,通知所有等待的请求
16132
+ */
16133
+ onRefreshFailure() {
16134
+ this.refreshSubscribers.forEach((callback) => callback(false));
16135
+ this.refreshSubscribers = [];
16136
+ }
16137
+ /**
16018
16138
  * 注册请求拦截器
16019
16139
  * @param onFulfilled 请求成功拦截器
16020
16140
  * @param onRejected 请求失败拦截器
@@ -16091,16 +16211,18 @@ var HttpService = class HttpService {
16091
16211
  this.unauthorizedCallbacks.delete(callback);
16092
16212
  }
16093
16213
  /**
16094
- * 触发所有 401 回调
16214
+ * 触发所有 401 回调并等待完成
16215
+ * @returns Promise,等待所有回调完成
16216
+ * @throws 如果任何回调失败,则抛出错误
16095
16217
  */
16096
- triggerUnauthorizedCallbacks() {
16097
- this.unauthorizedCallbacks.forEach((callback) => {
16098
- try {
16099
- callback();
16100
- } catch (error) {
16101
- console.error("[HttpService] Error in unauthorized callback:", error);
16102
- }
16103
- });
16218
+ async triggerUnauthorizedCallbacks() {
16219
+ const callbacks = Array.from(this.unauthorizedCallbacks);
16220
+ const failedResults = (await Promise.allSettled(callbacks.map((callback) => callback()))).filter((r) => r.status === "rejected");
16221
+ if (failedResults.length > 0) {
16222
+ const errors = failedResults.map((r) => r.reason);
16223
+ console.error("[HttpService] Some unauthorized callbacks failed:", errors);
16224
+ throw errors[0];
16225
+ }
16104
16226
  }
16105
16227
  /**
16106
16228
  * 更新 authToken
@@ -16212,6 +16334,7 @@ var AccountService = class {
16212
16334
  this.initPromise = null;
16213
16335
  this.initResolve = null;
16214
16336
  this.requestInterceptorId = null;
16337
+ this.crossTabBroadcaster = null;
16215
16338
  this.initPromise = new Promise((resolve) => {
16216
16339
  this.initResolve = resolve;
16217
16340
  });
@@ -16260,15 +16383,34 @@ var AccountService = class {
16260
16383
  this.initialized = true;
16261
16384
  this.initResolve?.(account);
16262
16385
  }
16263
- if (!wasInitialized || prev?.uid !== account?.uid) this.notifyListeners();
16386
+ if (!wasInitialized || prev?.uid !== account?.uid) {
16387
+ this.notifyListeners();
16388
+ if (account && this.crossTabBroadcaster) this.crossTabBroadcaster.broadcastLogin();
16389
+ }
16264
16390
  }
16265
16391
  /**
16266
16392
  * 清除账号(登出)
16393
+ * 先广播登出消息,再清除本地账号
16267
16394
  */
16268
16395
  clearAccount() {
16396
+ if (this.crossTabBroadcaster) this.crossTabBroadcaster.broadcastLogout();
16269
16397
  this.setAccount(null);
16270
16398
  }
16271
16399
  /**
16400
+ * 静默清除账号(不广播)
16401
+ * 用于收到其他标签页 logout 消息时,避免循环广播
16402
+ */
16403
+ clearAccountSilently() {
16404
+ this.setAccount(null);
16405
+ }
16406
+ /**
16407
+ * 设置跨标签页认证同步广播器
16408
+ * 应在应用初始化时由上层(如 agent-ui)调用
16409
+ */
16410
+ setCrossTabBroadcaster(broadcaster) {
16411
+ this.crossTabBroadcaster = broadcaster;
16412
+ }
16413
+ /**
16272
16414
  * 订阅账号变化
16273
16415
  * @param callback 变化时的回调函数
16274
16416
  * @returns 取消订阅函数
@@ -16333,6 +16475,114 @@ var AccountService = class {
16333
16475
  * 导出单例实例
16334
16476
  */
16335
16477
  const accountService = new AccountService();
16478
+ /**
16479
+ * 暴露给全局,供 Agent Manager 直接调用 setAccount 刷新 Widget 状态
16480
+ * 这是为了解决 IDE 环境中 IPC 事件无法直接触发 Widget 账号刷新的问题
16481
+ */
16482
+ if (typeof window !== "undefined") window.__genieAccountService = accountService;
16483
+
16484
+ //#endregion
16485
+ //#region ../agent-provider/src/common/utils/lru-cache.ts
16486
+ /**
16487
+ * LRU (Least Recently Used) Cache
16488
+ * 当缓存达到容量上限时,自动淘汰最久未使用的数据
16489
+ *
16490
+ * @template K - Key 类型
16491
+ * @template V - Value 类型
16492
+ */
16493
+ var LRUCache = class {
16494
+ /**
16495
+ * 创建 LRU 缓存实例
16496
+ * @param capacity - 缓存容量上限
16497
+ */
16498
+ constructor(capacity) {
16499
+ if (capacity <= 0) throw new Error("Cache capacity must be greater than 0");
16500
+ this.capacity = capacity;
16501
+ this.cache = /* @__PURE__ */ new Map();
16502
+ }
16503
+ /**
16504
+ * 获取缓存值
16505
+ * @param key - 键
16506
+ * @returns 值,如果不存在返回 undefined
16507
+ */
16508
+ get(key) {
16509
+ if (!this.cache.has(key)) return;
16510
+ const value = this.cache.get(key);
16511
+ this.cache.delete(key);
16512
+ this.cache.set(key, value);
16513
+ return value;
16514
+ }
16515
+ /**
16516
+ * 设置缓存值
16517
+ * @param key - 键
16518
+ * @param value - 值
16519
+ */
16520
+ set(key, value) {
16521
+ if (this.cache.has(key)) this.cache.delete(key);
16522
+ else if (this.cache.size >= this.capacity) {
16523
+ const firstKey = this.cache.keys().next().value;
16524
+ this.cache.delete(firstKey);
16525
+ }
16526
+ this.cache.set(key, value);
16527
+ }
16528
+ /**
16529
+ * 检查 key 是否存在
16530
+ * @param key - 键
16531
+ * @returns 是否存在
16532
+ */
16533
+ has(key) {
16534
+ return this.cache.has(key);
16535
+ }
16536
+ /**
16537
+ * 删除指定 key
16538
+ * @param key - 键
16539
+ * @returns 是否删除成功
16540
+ */
16541
+ delete(key) {
16542
+ return this.cache.delete(key);
16543
+ }
16544
+ /**
16545
+ * 清空缓存
16546
+ */
16547
+ clear() {
16548
+ this.cache.clear();
16549
+ }
16550
+ /**
16551
+ * 获取当前缓存大小
16552
+ * @returns 当前缓存的元素数量
16553
+ */
16554
+ get size() {
16555
+ return this.cache.size;
16556
+ }
16557
+ /**
16558
+ * 获取缓存容量
16559
+ * @returns 缓存容量上限
16560
+ */
16561
+ get maxSize() {
16562
+ return this.capacity;
16563
+ }
16564
+ /**
16565
+ * 获取所有 key
16566
+ * @returns key 数组
16567
+ */
16568
+ keys() {
16569
+ return Array.from(this.cache.keys());
16570
+ }
16571
+ /**
16572
+ * 获取所有 value
16573
+ * @returns value 数组
16574
+ */
16575
+ values() {
16576
+ return Array.from(this.cache.values());
16577
+ }
16578
+ /**
16579
+ * 遍历缓存
16580
+ * @param callback - 回调函数
16581
+ */
16582
+ forEach(callback) {
16583
+ this.cache.forEach((value, key) => callback(value, key));
16584
+ }
16585
+ };
16336
16586
 
16337
16587
  //#endregion
16338
16588
  //#region ../agent-provider/src/common/utils/concurrency.ts
@@ -16435,20 +16685,32 @@ var CosUploadService = class {
16435
16685
  * 上传单个文件到 COS
16436
16686
  *
16437
16687
  * @param file - 要上传的文件
16688
+ * @param abortSignal - 可选的 AbortSignal,用于取消上传
16438
16689
  * @returns 上传结果,包含访问 URL 或错误信息
16439
16690
  */
16440
- async uploadFile(file) {
16691
+ async uploadFile(file, abortSignal) {
16441
16692
  const filename = file.name;
16442
16693
  this.logger?.info(`[CosUploadService] Uploading file: ${filename}`);
16443
16694
  try {
16695
+ if (abortSignal?.aborted) return {
16696
+ success: false,
16697
+ error: "Upload cancelled",
16698
+ aborted: true
16699
+ };
16444
16700
  const objectKey = this.generateObjectKey(filename);
16445
16701
  this.logger?.debug(`[CosUploadService] Generated objectKey: ${objectKey}`);
16446
16702
  const presignedItem = (await this.getPresignedUrls([objectKey])).items[0];
16447
16703
  if (!presignedItem) throw new Error("No presigned URL item returned");
16704
+ if (abortSignal?.aborted) return {
16705
+ success: false,
16706
+ error: "Upload cancelled",
16707
+ aborted: true
16708
+ };
16448
16709
  const uploadResponse = await fetch(presignedItem.upload_url, {
16449
16710
  method: "PUT",
16450
16711
  body: file,
16451
- headers: { "Content-Type": file.type || "application/octet-stream" }
16712
+ headers: { "Content-Type": file.type || "application/octet-stream" },
16713
+ signal: abortSignal
16452
16714
  });
16453
16715
  if (!uploadResponse.ok) {
16454
16716
  const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
@@ -16462,6 +16724,11 @@ var CosUploadService = class {
16462
16724
  objectKey
16463
16725
  };
16464
16726
  } catch (error) {
16727
+ if (error instanceof Error && error.name === "AbortError") return {
16728
+ success: false,
16729
+ error: "Upload cancelled",
16730
+ aborted: true
16731
+ };
16465
16732
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
16466
16733
  this.logger?.error(`[CosUploadService] Upload failed: ${filename}`, error);
16467
16734
  return {
@@ -16476,14 +16743,25 @@ var CosUploadService = class {
16476
16743
  * 使用并发控制,限制同时上传的文件数量
16477
16744
  *
16478
16745
  * @param files - 要上传的文件数组
16746
+ * @param abortSignal - 可选的 AbortSignal,用于取消上传
16479
16747
  * @returns 所有文件的上传结果
16480
16748
  */
16481
- async uploadFiles(files) {
16749
+ async uploadFiles(files, abortSignal) {
16482
16750
  if (files.length === 0) return {
16483
16751
  success: true,
16484
16752
  urls: [],
16485
16753
  results: []
16486
16754
  };
16755
+ if (abortSignal?.aborted) return {
16756
+ success: false,
16757
+ error: "Upload cancelled",
16758
+ aborted: true,
16759
+ results: files.map(() => ({
16760
+ success: false,
16761
+ error: "Upload cancelled",
16762
+ aborted: true
16763
+ }))
16764
+ };
16487
16765
  this.logger?.info(`[CosUploadService] Uploading ${files.length} file(s) with concurrency ${this.uploadConcurrency}`);
16488
16766
  try {
16489
16767
  const fileInfos = files.map((file) => ({
@@ -16495,6 +16773,12 @@ var CosUploadService = class {
16495
16773
  this.logger?.debug(`[CosUploadService] Got ${presignedResponse.items.length} presigned URLs`);
16496
16774
  if (presignedResponse.items.length !== fileInfos.length) throw new Error(`Expected ${fileInfos.length} presigned URLs, got ${presignedResponse.items.length}`);
16497
16775
  const results = (await runWithConcurrencySettled(fileInfos.map(({ file }, index) => async () => {
16776
+ if (abortSignal?.aborted) return {
16777
+ success: false,
16778
+ error: "Upload cancelled",
16779
+ aborted: true,
16780
+ objectKey: fileInfos[index].objectKey
16781
+ };
16498
16782
  const presignedItem = presignedResponse.items[index];
16499
16783
  if (!presignedItem) return {
16500
16784
  success: false,
@@ -16505,7 +16789,8 @@ var CosUploadService = class {
16505
16789
  const uploadResponse = await fetch(presignedItem.upload_url, {
16506
16790
  method: "PUT",
16507
16791
  body: file,
16508
- headers: { "Content-Type": file.type || "application/octet-stream" }
16792
+ headers: { "Content-Type": file.type || "application/octet-stream" },
16793
+ signal: abortSignal
16509
16794
  });
16510
16795
  if (!uploadResponse.ok) {
16511
16796
  const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
@@ -16522,6 +16807,12 @@ var CosUploadService = class {
16522
16807
  objectKey: presignedItem.object_key
16523
16808
  };
16524
16809
  } catch (error) {
16810
+ if (error instanceof Error && error.name === "AbortError") return {
16811
+ success: false,
16812
+ error: "Upload cancelled",
16813
+ aborted: true,
16814
+ objectKey: presignedItem.object_key
16815
+ };
16525
16816
  return {
16526
16817
  success: false,
16527
16818
  error: error instanceof Error ? error.message : "Unknown error",
@@ -16575,35 +16866,35 @@ var CosUploadService = class {
16575
16866
  * E2B Filesystem Implementation
16576
16867
  *
16577
16868
  * Provides FilesResource implementation using E2B Sandbox SDK.
16578
- * Directly uses e2b SDK types.
16869
+ * Supports optional auto-reconnect on auth failure (401/403).
16579
16870
  *
16580
16871
  * @see https://e2b.dev/docs/filesystem/read-write
16581
16872
  * @see https://e2b.dev/docs/filesystem/watch
16582
16873
  */
16583
16874
  /**
16584
- * E2B Filesystem Implementation
16875
+ * E2B Filesystem
16585
16876
  *
16586
- * Wraps E2B Sandbox SDK's filesystem operations to implement FilesResource interface.
16877
+ * Wraps E2B Sandbox SDK's filesystem operations to implement FilesResource.
16878
+ * When `reconnectFn` is provided, automatically reconnects on auth errors.
16587
16879
  *
16588
16880
  * @example
16589
16881
  * ```typescript
16590
- * const fs = await E2BFilesystem.connect({
16591
- * sandboxId: 'sandbox-123',
16592
- * apiKey: 'e2b_xxx'
16593
- * });
16594
- *
16595
- * // Read/write files
16596
- * await fs.write('/test.txt', 'Hello World');
16597
- * const content = await fs.read('/test.txt');
16882
+ * // Basic usage (no auto-reconnect)
16883
+ * const fs = await E2BFilesystem.connect({ sandboxId: '...' });
16598
16884
  *
16599
- * // Watch for changes
16600
- * const handle = await fs.watchDir('/workspace', (event) => {
16601
- * console.log('File changed:', event);
16602
- * });
16885
+ * // With auto-reconnect on token expiry
16886
+ * const fs = await E2BFilesystem.connect({ sandboxId: '...' });
16887
+ * fs.setReconnectFn(async () => fetchFreshConnectionInfo());
16603
16888
  * ```
16604
16889
  */
16605
16890
  var E2BFilesystem = class E2BFilesystem {
16891
+ static {
16892
+ this.MIN_RECONNECT_INTERVAL_MS = 10 * 1e3;
16893
+ }
16606
16894
  constructor(sandbox) {
16895
+ this.isReconnecting = false;
16896
+ this.reconnectSubscribers = [];
16897
+ this.lastReconnectAt = 0;
16607
16898
  this.sandbox = sandbox;
16608
16899
  }
16609
16900
  /**
@@ -16619,38 +16910,111 @@ var E2BFilesystem = class E2BFilesystem {
16619
16910
  }));
16620
16911
  }
16621
16912
  /**
16913
+ * Set reconnect callback. When set, auth errors trigger automatic reconnect + retry.
16914
+ */
16915
+ setReconnectFn(fn) {
16916
+ this.reconnectFn = fn;
16917
+ }
16918
+ /**
16622
16919
  * Get the underlying E2B Sandbox instance
16623
16920
  */
16624
16921
  getSandbox() {
16625
16922
  return this.sandbox;
16626
16923
  }
16924
+ isAuthError(error) {
16925
+ if (!error || typeof error !== "object") return false;
16926
+ const err = error;
16927
+ if (err.status === 401 || err.status === 403) return true;
16928
+ if (err.statusCode === 401 || err.statusCode === 403) return true;
16929
+ if (err.response && typeof err.response === "object") {
16930
+ const resp = err.response;
16931
+ if (resp.status === 401 || resp.status === 403) return true;
16932
+ }
16933
+ if (typeof err.message === "string") {
16934
+ const msg = err.message.toLowerCase();
16935
+ if (msg.includes("unauthorized") || msg.includes("token expired") || msg.includes("authentication")) return true;
16936
+ }
16937
+ return false;
16938
+ }
16939
+ canAttemptReconnect() {
16940
+ return Date.now() - this.lastReconnectAt >= E2BFilesystem.MIN_RECONNECT_INTERVAL_MS;
16941
+ }
16942
+ /**
16943
+ * Reconnect with fresh credentials.
16944
+ * Only one reconnect in-flight at a time; concurrent callers share the result.
16945
+ */
16946
+ async reconnect() {
16947
+ if (this.isReconnecting) return new Promise((resolve, reject) => {
16948
+ this.reconnectSubscribers.push((success) => {
16949
+ if (success) resolve();
16950
+ else reject(/* @__PURE__ */ new Error("E2B sandbox reconnect failed"));
16951
+ });
16952
+ });
16953
+ this.isReconnecting = true;
16954
+ this.lastReconnectAt = Date.now();
16955
+ try {
16956
+ const info = await this.reconnectFn();
16957
+ this.sandbox = await e2b.Sandbox.connect(info.sandboxId, {
16958
+ domain: info.domain,
16959
+ apiUrl: info.apiUrl,
16960
+ requestTimeoutMs: info.requestTimeoutMs,
16961
+ debug: info.debug,
16962
+ headers: info.headers
16963
+ });
16964
+ this.reconnectSubscribers.forEach((cb) => cb(true));
16965
+ this.reconnectSubscribers = [];
16966
+ } catch (error) {
16967
+ this.reconnectSubscribers.forEach((cb) => cb(false));
16968
+ this.reconnectSubscribers = [];
16969
+ throw error;
16970
+ } finally {
16971
+ this.isReconnecting = false;
16972
+ }
16973
+ }
16974
+ /**
16975
+ * Execute an operation. If reconnectFn is set and an auth error occurs,
16976
+ * reconnect and retry once.
16977
+ */
16978
+ async exec(operation) {
16979
+ try {
16980
+ return await operation(this.sandbox.files);
16981
+ } catch (error) {
16982
+ if (this.reconnectFn && this.isAuthError(error) && this.canAttemptReconnect()) {
16983
+ await this.reconnect();
16984
+ return operation(this.sandbox.files);
16985
+ }
16986
+ throw error;
16987
+ }
16988
+ }
16627
16989
  read(path, opts) {
16628
- return this.sandbox.files.read(path, opts);
16990
+ return this.exec((f) => f.read(path, opts));
16629
16991
  }
16630
16992
  write(pathOrFiles, dataOrOpts, opts) {
16631
- if (Array.isArray(pathOrFiles)) return this.sandbox.files.write(pathOrFiles, dataOrOpts);
16632
- return this.sandbox.files.write(pathOrFiles, dataOrOpts, opts);
16993
+ return this.exec((f) => {
16994
+ if (Array.isArray(pathOrFiles)) return f.write(pathOrFiles, dataOrOpts);
16995
+ return f.write(pathOrFiles, dataOrOpts, opts);
16996
+ });
16633
16997
  }
16634
16998
  async list(path, opts) {
16635
- return this.sandbox.files.list(path, opts);
16999
+ return this.exec((f) => f.list(path, opts));
16636
17000
  }
16637
17001
  async exists(path, opts) {
16638
- return this.sandbox.files.exists(path, opts);
17002
+ return this.exec((f) => f.exists(path, opts));
16639
17003
  }
16640
17004
  async makeDir(path, opts) {
16641
- return this.sandbox.files.makeDir(path, opts);
17005
+ return this.exec((f) => f.makeDir(path, opts));
16642
17006
  }
16643
17007
  async remove(path, opts) {
16644
- return this.sandbox.files.remove(path, opts);
17008
+ return this.exec((f) => f.remove(path, opts));
16645
17009
  }
16646
17010
  async rename(oldPath, newPath, opts) {
16647
- return this.sandbox.files.rename(oldPath, newPath, opts);
17011
+ return this.exec((f) => f.rename(oldPath, newPath, opts));
16648
17012
  }
16649
17013
  async getInfo(path, opts) {
16650
- return this.sandbox.files.getInfo(path, opts);
17014
+ return this.exec((f) => f.getInfo(path, opts));
16651
17015
  }
16652
17016
  async watchDir(path, onEvent, opts) {
16653
- return this.sandbox.files.watchDir(path, onEvent, opts);
17017
+ return this.exec((f) => f.watchDir(path, onEvent, opts));
16654
17018
  }
16655
17019
  };
16656
17020
 
@@ -16802,8 +17166,11 @@ var CloudAgentProvider = class CloudAgentProvider {
16802
17166
  this.filesystemCache = /* @__PURE__ */ new Map();
16803
17167
  this.connectionCache = /* @__PURE__ */ new Map();
16804
17168
  this.eventListeners = /* @__PURE__ */ new Map();
17169
+ this.productConfigCache = null;
16805
17170
  this.options = options;
16806
17171
  this.logger = options.logger;
17172
+ this.marketplaceCache = new LRUCache(200);
17173
+ this.pluginCache = new LRUCache(2e3);
16807
17174
  if (options.endpoint) httpService.setBaseURL(options.endpoint);
16808
17175
  if (options.authToken) httpService.setAuthToken(options.authToken);
16809
17176
  if (options.headers && Object.keys(options.headers).length > 0) this.requestInterceptorId = httpService.registerRequestInterceptor((config) => {
@@ -16854,7 +17221,14 @@ var CloudAgentProvider = class CloudAgentProvider {
16854
17221
  const cached = this.filesystemCache.get(agentId);
16855
17222
  if (cached) return cached;
16856
17223
  const info = await this.getSandboxInfo(agentId);
16857
- const filesystem = createAgentFilesystem(await E2BFilesystem.connect(info));
17224
+ const e2bFilesystem = await E2BFilesystem.connect(info);
17225
+ e2bFilesystem.setReconnectFn(async () => {
17226
+ this.logger?.debug(`Reconnecting E2B sandbox for agent: ${agentId}`);
17227
+ const newInfo = await this.getSandboxInfo(agentId);
17228
+ this.logger?.debug(`E2B sandbox reconnected for agent: ${agentId}`);
17229
+ return newInfo;
17230
+ });
17231
+ const filesystem = createAgentFilesystem(e2bFilesystem);
16858
17232
  this.filesystemCache.set(agentId, filesystem);
16859
17233
  this.logger?.debug(`Created filesystem for agent: ${agentId}`);
16860
17234
  return filesystem;
@@ -16922,15 +17296,9 @@ var CloudAgentProvider = class CloudAgentProvider {
16922
17296
  const url = this.buildGetUrl("/console/as/conversations/", params);
16923
17297
  const apiResponse = await httpService.get(url);
16924
17298
  if (!apiResponse.data) throw new Error("No data in API response");
16925
- const agents = apiResponse.data.conversations.map((a) => this.toAgentState(a));
16926
- const pagination = apiResponse.data.pagination;
16927
- console.log("[CloudAgentProvider] API response:", {
16928
- agentsCount: agents.length,
16929
- pagination
16930
- });
16931
17299
  return {
16932
- agents,
16933
- pagination
17300
+ agents: apiResponse.data.conversations.map((a) => this.toAgentState(a)),
17301
+ pagination: apiResponse.data.pagination
16934
17302
  };
16935
17303
  } catch (error) {
16936
17304
  this.logger?.error("Failed to list agents:", error);
@@ -16940,13 +17308,21 @@ var CloudAgentProvider = class CloudAgentProvider {
16940
17308
  /**
16941
17309
  * Create a new conversation
16942
17310
  * POST {endpoint}/console/as/conversations
17311
+ * @param params - Session params containing cwd and optional configuration
16943
17312
  */
16944
- async create() {
17313
+ async create(params) {
16945
17314
  try {
16946
- const apiResponse = await httpService.post("/console/as/conversations/", {
16947
- prompt: "",
16948
- model: "deepseek-r1"
16949
- });
17315
+ const { options = {} } = params;
17316
+ const codebuddyMeta = options._meta?.["codebuddy.ai"];
17317
+ const tagsObj = options.tags || codebuddyMeta?.tags;
17318
+ const tagsArray = tagsObj ? Object.entries(tagsObj).map(([key, value]) => `${key}:${value}`) : void 0;
17319
+ const createPayload = {
17320
+ prompt: (options.prompt || "").slice(0, 100),
17321
+ model: options.model || "deepseek-r1",
17322
+ ...tagsArray && tagsArray.length > 0 ? { tags: tagsArray } : {}
17323
+ };
17324
+ console.log("[CloudAgentProvider] Creating conversation with payload:", createPayload);
17325
+ const apiResponse = await httpService.post("/console/as/conversations/", createPayload);
16950
17326
  if (!apiResponse.data) throw new Error("No data in API response");
16951
17327
  this.logger?.info(`Created conversation: ${apiResponse.data.id}`);
16952
17328
  return apiResponse.data.id;
@@ -16996,7 +17372,14 @@ var CloudAgentProvider = class CloudAgentProvider {
16996
17372
  } catch (error) {
16997
17373
  this.logger?.debug(`Failed to fetch conversation details for ${agentId}:`, error);
16998
17374
  }
16999
- if (this.connectionCache.get(endpoint)) this.connectionCache.delete(endpoint);
17375
+ const existingConnection = this.connectionCache.get(endpoint);
17376
+ if (existingConnection) {
17377
+ this.connectionCache.delete(endpoint);
17378
+ existingConnection.removeAllListeners();
17379
+ existingConnection.disconnect().catch((err) => {
17380
+ this.logger?.debug("Failed to disconnect old connection:", err);
17381
+ });
17382
+ }
17000
17383
  const clientCapabilities = {
17001
17384
  ...this.options.clientCapabilities,
17002
17385
  _meta: {
@@ -17116,6 +17499,35 @@ var CloudAgentProvider = class CloudAgentProvider {
17116
17499
  }
17117
17500
  }
17118
17501
  /**
17502
+ * Update conversation status by ID
17503
+ * POST {endpoint}/console/as/conversations/{agentId}
17504
+ *
17505
+ * @param agentId - Conversation ID to update
17506
+ * @param status - New status for the conversation
17507
+ * @returns PatchConversationResponse containing the updated conversation ID
17508
+ *
17509
+ * @example
17510
+ * ```typescript
17511
+ * const result = await provider.updateStatus('agent-123', 'completed');
17512
+ * console.log('Updated conversation status:', result.id);
17513
+ * ```
17514
+ */
17515
+ async updateStatus(agentId, status) {
17516
+ try {
17517
+ const body = { status };
17518
+ const apiResponse = await httpService.post(`/console/as/conversations/${agentId}`, body);
17519
+ if (!apiResponse.data) {
17520
+ this.logger?.info(`Updated conversation status: ${agentId} to "${status}"`);
17521
+ return { id: agentId };
17522
+ }
17523
+ this.logger?.info(`Updated conversation status: ${apiResponse.data.id} to "${status}"`);
17524
+ return apiResponse.data;
17525
+ } catch (error) {
17526
+ this.logger?.error(`Failed to update conversation status ${agentId}:`, error);
17527
+ throw error;
17528
+ }
17529
+ }
17530
+ /**
17119
17531
  * Get available models from product configuration
17120
17532
  *
17121
17533
  * GET {endpoint}/console/enterprises/{personal|enterpriseId}/models?repos[]={repo}
@@ -17146,9 +17558,13 @@ var CloudAgentProvider = class CloudAgentProvider {
17146
17558
  this.logger?.warn("[CloudAgentProvider] No data in config response, returning empty models");
17147
17559
  return [];
17148
17560
  }
17149
- const models = apiResponse.data.models ?? [];
17150
- this.logger?.info(`[CloudAgentProvider] Retrieved ${models.length} models from /console/enterprises API`);
17151
- return models.map((model) => ({
17561
+ this.productConfigCache = apiResponse.data;
17562
+ const productConfig = apiResponse.data;
17563
+ const allModels = productConfig.models ?? [];
17564
+ const cliModelIds = (productConfig.agents ?? []).find((agent) => agent.name === "cli")?.models ?? [];
17565
+ const filteredModels = cliModelIds.length > 0 ? allModels.filter((model) => cliModelIds.includes(model.id)) : allModels;
17566
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${filteredModels.length} models for cli agent (total: ${allModels.length})`);
17567
+ return filteredModels.map((model) => ({
17152
17568
  id: model.id,
17153
17569
  name: model.name ?? model.id,
17154
17570
  description: model.description,
@@ -17170,6 +17586,43 @@ var CloudAgentProvider = class CloudAgentProvider {
17170
17586
  }
17171
17587
  }
17172
17588
  /**
17589
+ * 获取产品部署类型(从缓存)
17590
+ * 需要先调用 getModels() 初始化缓存
17591
+ *
17592
+ * @returns 部署类型:'SaaS' | 'Cloud-Hosted' | 'Self-Hosted',默认为 'SaaS'
17593
+ */
17594
+ getDeploymentType() {
17595
+ return this.productConfigCache?.deploymentType ?? "SaaS";
17596
+ }
17597
+ /**
17598
+ * 获取 Credit 购买引导配置(从缓存)
17599
+ * 需要先调用 getModels() 初始化缓存
17600
+ *
17601
+ * @returns Credit 购买引导配置,key 为错误码。如果后端未返回,则使用默认配置
17602
+ */
17603
+ getCreditPurchaseActions() {
17604
+ return this.productConfigCache?.config?.creditPurchaseActions ?? {
17605
+ "14018": {
17606
+ labelZh: "获取 Credits",
17607
+ labelEn: "Get credits",
17608
+ url: "https://www.codebuddy.cn/profile/plan",
17609
+ showButton: true
17610
+ },
17611
+ "6004": {
17612
+ labelZh: "升级专业版",
17613
+ labelEn: "Upgrade to Pro",
17614
+ url: "https://www.codebuddy.cn/profile/plan",
17615
+ showButton: true
17616
+ },
17617
+ "6005": {
17618
+ labelZh: "升级专业版",
17619
+ labelEn: "Upgrade to Pro",
17620
+ url: "https://www.codebuddy.cn/profile/plan",
17621
+ showButton: true
17622
+ }
17623
+ };
17624
+ }
17625
+ /**
17173
17626
  * Generate a unique request ID
17174
17627
  */
17175
17628
  generateRequestId() {
@@ -17252,7 +17705,7 @@ var CloudAgentProvider = class CloudAgentProvider {
17252
17705
  /**
17253
17706
  * Upload files to cloud storage via COS presigned URL
17254
17707
  *
17255
- * @param params - files array (File objects in browser)
17708
+ * @param params - files array (File objects in browser), optional abortSignal
17256
17709
  * @returns Response with corresponding cloud URLs
17257
17710
  */
17258
17711
  async uploadFile(params) {
@@ -17262,12 +17715,13 @@ var CloudAgentProvider = class CloudAgentProvider {
17262
17715
  success: false,
17263
17716
  error: "No valid File objects provided"
17264
17717
  };
17265
- const result = await this.cosUploadService.uploadFiles(files);
17718
+ const result = await this.cosUploadService.uploadFiles(files, params.abortSignal);
17266
17719
  return {
17267
17720
  success: result.success,
17268
17721
  urls: result.urls,
17269
17722
  expireSeconds: result.expireSeconds,
17270
- error: result.error
17723
+ error: result.error,
17724
+ aborted: result.aborted
17271
17725
  };
17272
17726
  }
17273
17727
  /**
@@ -17309,20 +17763,22 @@ var CloudAgentProvider = class CloudAgentProvider {
17309
17763
  }
17310
17764
  /**
17311
17765
  * 获取支持的场景列表
17312
- * API 端点: GET /console/as/support/scenes
17766
+ * API 端点: GET /v2/as/support/scenes (不鉴权)
17313
17767
  * 用于 Welcome 页面的 QuickActions 快捷操作
17314
17768
  *
17769
+ * @param locale - 可选,语言环境(如 'zh-CN', 'en-US'),用于获取对应语言的场景数据
17315
17770
  * @returns Promise<SupportScene[]> 支持的场景列表
17316
17771
  */
17317
- async getSupportScenes() {
17772
+ async getSupportScenes(locale) {
17318
17773
  try {
17319
- const apiResponse = await httpService.get("/console/as/support/scenes");
17774
+ const url = this.buildGetUrl("/v2/as/support/scenes", locale ? { locale } : void 0);
17775
+ const apiResponse = await httpService.get(url);
17320
17776
  if (!apiResponse.data) {
17321
17777
  this.logger?.warn("[CloudAgentProvider] No data in support scenes response");
17322
17778
  return [];
17323
17779
  }
17324
17780
  const scenes = apiResponse.data.scenes || [];
17325
- this.logger?.info(`[CloudAgentProvider] Retrieved ${scenes.length} support scenes`);
17781
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${scenes.length} support scenes${locale ? ` for locale: ${locale}` : ""}`);
17326
17782
  return scenes;
17327
17783
  } catch (error) {
17328
17784
  this.logger?.error("[CloudAgentProvider] Failed to get support scenes:", error);
@@ -17338,7 +17794,8 @@ var CloudAgentProvider = class CloudAgentProvider {
17338
17794
  type: "cloud",
17339
17795
  status,
17340
17796
  createdAt: data.createdAt ? new Date(data.createdAt) : void 0,
17341
- capabilities: this.options.clientCapabilities
17797
+ capabilities: this.options.clientCapabilities,
17798
+ isUserDefinedTitle: data.isUserDefinedTitle
17342
17799
  };
17343
17800
  }
17344
17801
  /**
@@ -17354,61 +17811,504 @@ var CloudAgentProvider = class CloudAgentProvider {
17354
17811
  const queryString = searchParams.toString();
17355
17812
  return queryString ? `${path}?${queryString}` : path;
17356
17813
  }
17357
- };
17358
-
17359
- //#endregion
17360
- //#region ../agent-provider/src/common/providers/local-agent-provider/local-connection.ts
17361
- /**
17362
- * Local Agent Connection
17363
- * Wraps AcpJsonRpcClient to implement AgentConnection interface
17364
- *
17365
- * Uses IWidgetChannel for IPC communication with ExtensionHost
17366
- * Migrated from ipc-agent-provider for unified local agent access
17367
- */
17368
-
17369
- //#endregion
17370
- //#region ../agent-provider/src/common/client/session.ts
17371
- /**
17372
- * ActiveSessionImpl - Implements the ActiveSession interface
17373
- *
17374
- * This class wraps an AgentConnection and provides the session-centric API.
17375
- * It is created by SessionManager when creating or loading sessions.
17376
- *
17377
- * @example
17378
- * ```typescript
17379
- * // Created by client.sessions.new() or client.sessions.load()
17380
- * const session = await client.sessions.new({ cwd: '/workspace' });
17381
- *
17382
- * // Access agent state
17383
- * console.log(session.agentState.status);
17384
- *
17385
- * // Send prompt
17386
- * const response = await session.prompts.send({ content: 'Hello!' });
17387
- *
17388
- * // Cleanup
17389
- * session.disconnect();
17390
- * ```
17391
- */
17392
- var ActiveSessionImpl = class {
17393
17814
  /**
17394
- * Create an ActiveSessionImpl instance
17815
+ * 获取已安装插件列表
17816
+ * GET /console/as/user/plugins/installed
17817
+ */
17818
+ async getInstalledPlugins(forceRefresh) {
17819
+ try {
17820
+ const result = ((await httpService.get("/console/as/user/plugins/installed")).data?.plugins || []).map((p) => ({
17821
+ name: p.plugin_name,
17822
+ marketplaceName: p.marketplace_name,
17823
+ status: p.enabled ? "enabled" : "disabled",
17824
+ description: p.description,
17825
+ version: p.version,
17826
+ installScope: p.scope === "local" ? "project" : p.scope,
17827
+ installedScopes: [p.scope],
17828
+ installId: p.id
17829
+ }));
17830
+ result.forEach((plugin) => {
17831
+ this.pluginCache.set(plugin.name, plugin);
17832
+ });
17833
+ return result;
17834
+ } catch (error) {
17835
+ this.logger?.error("[CloudAgentProvider] getInstalledPlugins failed:", error);
17836
+ throw error;
17837
+ }
17838
+ }
17839
+ /**
17840
+ * 安装插件
17841
+ * POST /console/as/user/plugins/install
17395
17842
  *
17396
- * @param sessionId - Session ID
17397
- * @param agentId - Agent ID
17398
- * @param connection - Already connected AgentConnection
17399
- * @param options - Additional options
17843
+ * @param pluginNames - 插件名称数组
17844
+ * @param marketplaceNameOrId - 市场名称或 ID
17400
17845
  */
17401
- constructor(sessionId, agentId, connection, options = {}) {
17402
- this._availableCommands = [];
17403
- this.listeners = /* @__PURE__ */ new Map();
17404
- this.onceListeners = /* @__PURE__ */ new Map();
17405
- this._id = sessionId;
17406
- this._agentId = agentId;
17407
- this.connection = connection;
17408
- this.logger = options.logger;
17409
- this._getFilesystem = options.getFilesystem;
17410
- this._connectionInfo = options.connectionInfo;
17411
- this.setupConnectionEvents(connection);
17846
+ async installPlugins(pluginNames, marketplaceNameOrId, installScope, marketplaceSource, workspacePath) {
17847
+ try {
17848
+ const marketplaceId = await this.findMarketplaceId(marketplaceNameOrId);
17849
+ if (!marketplaceId) return {
17850
+ success: false,
17851
+ error: `Marketplace not found: ${marketplaceNameOrId}`
17852
+ };
17853
+ const failed = (await Promise.allSettled(pluginNames.map((pluginName) => httpService.post("/console/as/user/plugins/install", {
17854
+ plugin_name: pluginName,
17855
+ marketplace_id: marketplaceId,
17856
+ version: "latest"
17857
+ })))).filter((r) => r.status === "rejected");
17858
+ if (failed.length > 0) {
17859
+ const errors = failed.map((r) => r.reason?.message || "Unknown error");
17860
+ return {
17861
+ success: false,
17862
+ error: `安装失败 ${failed.length} 个插件: ${errors.join(", ")}`
17863
+ };
17864
+ }
17865
+ return { success: true };
17866
+ } catch (error) {
17867
+ return {
17868
+ success: false,
17869
+ error: this.extractErrorMessage(error)
17870
+ };
17871
+ }
17872
+ }
17873
+ /**
17874
+ * 卸载插件
17875
+ * POST /console/as/user/plugins/installed/:id/uninstall
17876
+ *
17877
+ * 完整链路:
17878
+ * CloudAgentProvider.uninstallPlugin()
17879
+ * -> HTTP POST /console/as/user/plugins/installed/:id/uninstall
17880
+ * -> agentserver: PluginInstallService.Uninstall()
17881
+ * -> 软删除 DB + 异步同步到活跃沙箱
17882
+ *
17883
+ * @param pluginName - 插件名称
17884
+ * @param marketplaceName - 市场名称(用于标识唯一插件)
17885
+ * @param scope - 卸载范围 ('user' | 'project' | 'project-local')
17886
+ */
17887
+ async uninstallPlugin(pluginName, marketplaceName, scope) {
17888
+ try {
17889
+ const installId = await this.findPluginInstallId(pluginName);
17890
+ if (!installId) return {
17891
+ success: false,
17892
+ error: `Plugin not found or not installed: ${pluginName}`
17893
+ };
17894
+ await httpService.post(`/console/as/user/plugins/installed/${installId}/uninstall`);
17895
+ return { success: true };
17896
+ } catch (error) {
17897
+ return {
17898
+ success: false,
17899
+ error: this.extractErrorMessage(error)
17900
+ };
17901
+ }
17902
+ }
17903
+ /**
17904
+ * 获取插件市场列表
17905
+ * GET /console/as/marketplace/sources
17906
+ */
17907
+ async getPluginMarketplaces(forceRefresh) {
17908
+ try {
17909
+ const result = ((await httpService.get("/console/as/marketplace/sources")).data?.sources || []).map((src) => ({
17910
+ id: src.id,
17911
+ name: src.name,
17912
+ type: this.mapSourceType(src.source_type),
17913
+ source: { url: src.url },
17914
+ description: src.name,
17915
+ isBuiltin: src.is_default
17916
+ }));
17917
+ result.forEach((m) => {
17918
+ const marketplaceInfo = {
17919
+ id: m.id,
17920
+ name: m.name
17921
+ };
17922
+ this.marketplaceCache.set(m.name, marketplaceInfo);
17923
+ this.marketplaceCache.set(m.id, marketplaceInfo);
17924
+ });
17925
+ return result;
17926
+ } catch (error) {
17927
+ this.logger?.error("[CloudAgentProvider] getPluginMarketplaces failed:", error);
17928
+ throw error;
17929
+ }
17930
+ }
17931
+ /**
17932
+ * 获取市场下的插件列表
17933
+ * - 有 searchText: GET /console/as/marketplace/plugins/search (跨所有市场搜索)
17934
+ * - 无 searchText: GET /console/as/marketplace/plugins (指定市场)
17935
+ *
17936
+ * @param marketplaceNameOrId - 市场名称或 ID(优先使用 ID,如果是名称则从缓存查询 ID)
17937
+ * @param forceRefresh - 是否强制刷新
17938
+ * @param searchText - 搜索关键字(如果提供,则跨所有市场搜索)
17939
+ */
17940
+ async getMarketplacePlugins(marketplaceNameOrId, forceRefresh, searchText) {
17941
+ try {
17942
+ if (searchText) return ((await httpService.get("/console/as/marketplace/plugins/search", { params: {
17943
+ q: searchText,
17944
+ page: 1,
17945
+ page_size: 100
17946
+ } })).data?.plugins || []).map((p) => this.mapPluginData(p));
17947
+ const sourceId = await this.findMarketplaceId(marketplaceNameOrId);
17948
+ if (!sourceId) {
17949
+ this.logger?.warn(`[CloudAgentProvider] Marketplace not found: ${marketplaceNameOrId}`);
17950
+ return [];
17951
+ }
17952
+ const params = {
17953
+ source_id: sourceId,
17954
+ page: 1,
17955
+ page_size: 100
17956
+ };
17957
+ return ((await httpService.get("/console/as/marketplace/plugins", { params })).data?.plugins || []).map((p) => this.mapPluginData(p));
17958
+ } catch (error) {
17959
+ this.logger?.error("[CloudAgentProvider] getMarketplacePlugins failed:", error);
17960
+ throw error;
17961
+ }
17962
+ }
17963
+ /**
17964
+ * 获取插件详情
17965
+ * GET /console/as/marketplace/plugins/:name/detail
17966
+ *
17967
+ * @param pluginName - 插件名称
17968
+ * @param marketplaceNameOrId - 市场名称或 ID(优先使用 ID,如果是名称则从缓存查询 ID)
17969
+ */
17970
+ async getPluginDetail(pluginName, marketplaceNameOrId) {
17971
+ try {
17972
+ const sourceId = await this.findMarketplaceId(marketplaceNameOrId);
17973
+ if (!sourceId) {
17974
+ this.logger?.warn(`[CloudAgentProvider] Marketplace not found: ${marketplaceNameOrId}`);
17975
+ return null;
17976
+ }
17977
+ const p = (await httpService.get(`/console/as/marketplace/plugins/${pluginName}/detail`, { params: { source_id: sourceId } })).data?.plugin;
17978
+ if (!p) return null;
17979
+ const capabilities = p.capabilities ? this.parseCapabilities(p.capabilities) : {};
17980
+ const tags = p.tags ? JSON.parse(p.tags) : [];
17981
+ return {
17982
+ name: p.name,
17983
+ marketplaceName: p.marketplace_name,
17984
+ description: p.description,
17985
+ version: p.version,
17986
+ iconUrl: p.icon_url,
17987
+ tags,
17988
+ installed: p.installed,
17989
+ status: p.enabled ? "enabled" : p.installed ? "disabled" : "not-installed",
17990
+ readme: p.readme,
17991
+ author: p.author,
17992
+ homepage: p.homepage,
17993
+ repositoryUrl: p.repository_url,
17994
+ license: p.license,
17995
+ ...capabilities
17996
+ };
17997
+ } catch (error) {
17998
+ this.logger?.error("[CloudAgentProvider] getPluginDetail failed:", error);
17999
+ throw error;
18000
+ }
18001
+ }
18002
+ /**
18003
+ * 添加插件市场
18004
+ * POST /console/as/marketplace/sources
18005
+ */
18006
+ async addPluginMarketplace(sourceUrl, name) {
18007
+ try {
18008
+ const body = {
18009
+ source_type: sourceUrl.startsWith("http") ? "url" : "github",
18010
+ url: sourceUrl,
18011
+ name: name || sourceUrl
18012
+ };
18013
+ const sourceData = (await httpService.post("/console/as/marketplace/sources", body)).data?.source;
18014
+ if (!sourceData) throw new Error("Invalid response from server");
18015
+ const marketplaceInfo = {
18016
+ id: sourceData.id,
18017
+ name: sourceData.name
18018
+ };
18019
+ this.marketplaceCache.set(sourceData.name, marketplaceInfo);
18020
+ this.marketplaceCache.set(sourceData.id, marketplaceInfo);
18021
+ return {
18022
+ success: true,
18023
+ marketplace: {
18024
+ id: sourceData.id,
18025
+ name: sourceData.name,
18026
+ type: this.mapSourceType(sourceData.source_type),
18027
+ source: { url: sourceData.url },
18028
+ isBuiltin: sourceData.is_default
18029
+ }
18030
+ };
18031
+ } catch (error) {
18032
+ return {
18033
+ success: false,
18034
+ error: this.extractErrorMessage(error)
18035
+ };
18036
+ }
18037
+ }
18038
+ /**
18039
+ * 删除插件市场
18040
+ * POST /console/as/marketplace/sources/:id/delete
18041
+ */
18042
+ async removePluginMarketplace(marketplaceNameOrId) {
18043
+ try {
18044
+ const marketplaceId = await this.findMarketplaceId(marketplaceNameOrId);
18045
+ if (!marketplaceId) return {
18046
+ success: false,
18047
+ error: `Marketplace not found: ${marketplaceNameOrId}`
18048
+ };
18049
+ await httpService.post(`/console/as/marketplace/sources/${marketplaceId}/delete`, {});
18050
+ return { success: true };
18051
+ } catch (error) {
18052
+ return {
18053
+ success: false,
18054
+ error: this.extractErrorMessage(error)
18055
+ };
18056
+ }
18057
+ }
18058
+ /**
18059
+ * 刷新插件市场
18060
+ * POST /console/as/marketplace/sources/:id/check-updates
18061
+ */
18062
+ async refreshPluginMarketplace(marketplaceNameOrId) {
18063
+ try {
18064
+ const marketplaceId = await this.findMarketplaceId(marketplaceNameOrId);
18065
+ if (!marketplaceId) return {
18066
+ success: false,
18067
+ error: `Marketplace not found: ${marketplaceNameOrId}`
18068
+ };
18069
+ return {
18070
+ success: true,
18071
+ plugins: (await httpService.post(`/console/as/marketplace/sources/${marketplaceId}/check-updates`, {})).data?.updated_plugins || []
18072
+ };
18073
+ } catch (error) {
18074
+ return {
18075
+ success: false,
18076
+ error: this.extractErrorMessage(error)
18077
+ };
18078
+ }
18079
+ }
18080
+ /**
18081
+ * 批量切换插件启用/禁用状态
18082
+ * POST /console/as/user/plugins/installed/:id/toggle
18083
+ */
18084
+ async batchTogglePlugins(request) {
18085
+ try {
18086
+ const results = await Promise.allSettled(request.items.map(async (item) => {
18087
+ const installId = await this.findPluginInstallId(item.pluginName);
18088
+ if (!installId) throw new Error(`Plugin not found or not installed: ${item.pluginName}`);
18089
+ const enabled = item.operation === "enable";
18090
+ await httpService.post(`/console/as/user/plugins/installed/${installId}/toggle`, { enabled });
18091
+ return item;
18092
+ }));
18093
+ const succeededPlugins = [];
18094
+ const failedPlugins = [];
18095
+ results.forEach((r, i) => {
18096
+ const item = request.items[i];
18097
+ if (r.status === "fulfilled") succeededPlugins.push(item);
18098
+ else failedPlugins.push({
18099
+ ...item,
18100
+ error: r.reason?.message || "Unknown error"
18101
+ });
18102
+ });
18103
+ return {
18104
+ success: failedPlugins.length === 0,
18105
+ succeededPlugins,
18106
+ failedPlugins
18107
+ };
18108
+ } catch (error) {
18109
+ return {
18110
+ success: false,
18111
+ succeededPlugins: [],
18112
+ failedPlugins: request.items.map((item) => ({
18113
+ ...item,
18114
+ error: this.extractErrorMessage(error)
18115
+ }))
18116
+ };
18117
+ }
18118
+ }
18119
+ /**
18120
+ * 将后端插件数据映射为前端格式
18121
+ */
18122
+ mapPluginData(p) {
18123
+ const capabilities = p.capabilities ? this.parseCapabilities(p.capabilities) : {};
18124
+ let tags = [];
18125
+ if (p.tags) {
18126
+ if (Array.isArray(p.tags)) tags = p.tags;
18127
+ else if (typeof p.tags === "string") try {
18128
+ const parsed = JSON.parse(p.tags);
18129
+ tags = Array.isArray(parsed) ? parsed : [parsed];
18130
+ } catch {
18131
+ tags = p.tags.split(",").map((t) => t.trim()).filter((t) => t);
18132
+ }
18133
+ }
18134
+ return {
18135
+ name: p.name,
18136
+ marketplaceName: p.marketplace_name,
18137
+ description: p.description,
18138
+ version: p.version,
18139
+ iconUrl: p.icon_url,
18140
+ tags,
18141
+ installed: p.installed,
18142
+ status: p.enabled ? "enabled" : p.installed ? "disabled" : "not-installed",
18143
+ installedScopes: p.installed ? [p.installed_scope] : [],
18144
+ ...capabilities
18145
+ };
18146
+ }
18147
+ /**
18148
+ * 从缓存中查找插件的 install_id
18149
+ * 如果缓存未命中,则调用 API 获取并缓存
18150
+ *
18151
+ * @param pluginName - 插件名称
18152
+ * @returns install_id 或 null
18153
+ */
18154
+ async findPluginInstallId(pluginName) {
18155
+ const cached = this.pluginCache.get(pluginName);
18156
+ if (cached) return cached.installId;
18157
+ await this.getInstalledPlugins();
18158
+ return this.pluginCache.get(pluginName)?.installId || null;
18159
+ }
18160
+ /**
18161
+ * 从缓存中查找 marketplace ID
18162
+ * 如果缓存未命中,则调用 API 获取并缓存
18163
+ */
18164
+ async findMarketplaceId(nameOrId) {
18165
+ const cached = this.marketplaceCache.get(nameOrId);
18166
+ if (cached) return cached.id;
18167
+ await this.getPluginMarketplaces();
18168
+ return this.marketplaceCache.get(nameOrId)?.id || null;
18169
+ }
18170
+ /**
18171
+ * 提取 API 错误信息
18172
+ * 从 AxiosError 中提取详细的错误信息,包括 HTTP 状态码、错误码和错误消息
18173
+ */
18174
+ extractErrorMessage(error) {
18175
+ if (error instanceof AxiosError) {
18176
+ const status = error.response?.status;
18177
+ const apiResponse = error.response?.data;
18178
+ const parts = [];
18179
+ if (status) parts.push(`HTTP ${status}`);
18180
+ if (apiResponse?.code) parts.push(`Code ${apiResponse.code}`);
18181
+ if (apiResponse?.msg) parts.push(apiResponse.msg);
18182
+ else if (error.message) parts.push(error.message);
18183
+ const errorMessage = parts.join(" - ");
18184
+ this.logger?.error("[CloudAgentProvider] API Error:", {
18185
+ status,
18186
+ code: apiResponse?.code,
18187
+ msg: apiResponse?.msg,
18188
+ requestId: apiResponse?.requestId,
18189
+ url: error.config?.url,
18190
+ method: error.config?.method
18191
+ });
18192
+ return errorMessage;
18193
+ }
18194
+ if (error instanceof Error) return error.message;
18195
+ return "Unknown error";
18196
+ }
18197
+ /**
18198
+ * 映射后端 source_type 到前端类型
18199
+ */
18200
+ mapSourceType(sourceType) {
18201
+ switch (sourceType) {
18202
+ case "github": return "github";
18203
+ case "official": return "custom";
18204
+ default: return "custom";
18205
+ }
18206
+ }
18207
+ /**
18208
+ * 解析 capabilities JSON 字符串
18209
+ */
18210
+ parseCapabilities(capabilitiesStr) {
18211
+ try {
18212
+ const cap = JSON.parse(capabilitiesStr);
18213
+ return {
18214
+ commands: cap.commands,
18215
+ skills: cap.skills,
18216
+ mcpServers: cap.mcp,
18217
+ agents: cap.agents,
18218
+ hooks: cap.hooks,
18219
+ rules: cap.rules
18220
+ };
18221
+ } catch {
18222
+ return {};
18223
+ }
18224
+ }
18225
+ /**
18226
+ * 上报 telemetry 事件(Cloud 模式)
18227
+ * 通过 HTTP POST 发送到 /v2/report
18228
+ * 注入用户信息和浏览器环境等公共字段
18229
+ */
18230
+ async reportTelemetry(eventName, payload) {
18231
+ try {
18232
+ const account = accountService.getAccount();
18233
+ const commonFields = {};
18234
+ if (account) {
18235
+ commonFields.userId = account.uid;
18236
+ commonFields.userNickname = account.nickname;
18237
+ if (account.enterpriseId) commonFields.enterpriseId = account.enterpriseId;
18238
+ if (account.enterpriseUserName) commonFields.username = account.enterpriseUserName;
18239
+ }
18240
+ if (typeof navigator !== "undefined") {
18241
+ commonFields.userAgent = navigator.userAgent;
18242
+ commonFields.os = navigator.platform;
18243
+ }
18244
+ const events = [{
18245
+ eventCode: eventName,
18246
+ timestamp: Date.now(),
18247
+ reportDelay: 0,
18248
+ ...commonFields,
18249
+ ...payload
18250
+ }];
18251
+ await httpService.post("/v2/report", events);
18252
+ } catch (error) {
18253
+ this.logger?.warn("reportTelemetry() failed:", error);
18254
+ }
18255
+ }
18256
+ };
18257
+
18258
+ //#endregion
18259
+ //#region ../agent-provider/src/common/providers/local-agent-provider/local-connection.ts
18260
+ /**
18261
+ * Local Agent Connection
18262
+ * Wraps AcpJsonRpcClient to implement AgentConnection interface
18263
+ *
18264
+ * Uses IWidgetChannel for IPC communication with ExtensionHost
18265
+ * Migrated from ipc-agent-provider for unified local agent access
18266
+ */
18267
+
18268
+ //#endregion
18269
+ //#region ../agent-provider/src/common/client/session.ts
18270
+ /**
18271
+ * ActiveSessionImpl - Implements the ActiveSession interface
18272
+ *
18273
+ * This class wraps an AgentConnection and provides the session-centric API.
18274
+ * It is created by SessionManager when creating or loading sessions.
18275
+ *
18276
+ * @example
18277
+ * ```typescript
18278
+ * // Created by client.sessions.new() or client.sessions.load()
18279
+ * const session = await client.sessions.new({ cwd: '/workspace' });
18280
+ *
18281
+ * // Access agent state
18282
+ * console.log(session.agentState.status);
18283
+ *
18284
+ * // Send prompt
18285
+ * const response = await session.prompts.send({ content: 'Hello!' });
18286
+ *
18287
+ * // Cleanup
18288
+ * session.disconnect();
18289
+ * ```
18290
+ */
18291
+ var ActiveSessionImpl = class {
18292
+ /**
18293
+ * Create an ActiveSessionImpl instance
18294
+ *
18295
+ * @param sessionId - Session ID
18296
+ * @param agentId - Agent ID
18297
+ * @param connection - Already connected AgentConnection
18298
+ * @param options - Additional options
18299
+ */
18300
+ constructor(sessionId, agentId, connection, options = {}) {
18301
+ this._availableCommands = [];
18302
+ this.listeners = /* @__PURE__ */ new Map();
18303
+ this.onceListeners = /* @__PURE__ */ new Map();
18304
+ this.connectionListeners = [];
18305
+ this._id = sessionId;
18306
+ this._agentId = agentId;
18307
+ this.connection = connection;
18308
+ this.logger = options.logger;
18309
+ this._getFilesystem = options.getFilesystem;
18310
+ this._connectionInfo = options.connectionInfo;
18311
+ this.setupConnectionEvents(connection);
17412
18312
  this.agent = this.createAgentOperations();
17413
18313
  this.prompts = this.createPromptsResource();
17414
18314
  this.artifacts = this.createArtifactsResource();
@@ -17427,6 +18327,18 @@ var ActiveSessionImpl = class {
17427
18327
  return this._agentId;
17428
18328
  }
17429
18329
  /**
18330
+ * Actual workspace path (set from newSession response _meta)
18331
+ */
18332
+ get cwd() {
18333
+ return this._cwd;
18334
+ }
18335
+ /**
18336
+ * Set actual workspace path (called by SessionManager after createSession)
18337
+ */
18338
+ setCwd(cwd) {
18339
+ this._cwd = cwd;
18340
+ }
18341
+ /**
17430
18342
  * Agent state (live connection state)
17431
18343
  * Returns LocalAgentState or CloudAgentState based on transport type
17432
18344
  */
@@ -17643,8 +18555,8 @@ var ActiveSessionImpl = class {
17643
18555
  * await session.setMode('architect');
17644
18556
  * ```
17645
18557
  */
17646
- async setMode(modeId) {
17647
- if (this._availableModes) {
18558
+ async setMode(modeId, skipAvailableChecker) {
18559
+ if (this._availableModes && !skipAvailableChecker) {
17648
18560
  if (!this._availableModes.some((m) => m.id === modeId)) {
17649
18561
  const availableIds = this._availableModes.map((m) => m.id).join(", ");
17650
18562
  throw new Error(`Invalid modeId: "${modeId}". Available modes: ${availableIds}`);
@@ -17667,6 +18579,7 @@ var ActiveSessionImpl = class {
17667
18579
  * ```
17668
18580
  */
17669
18581
  async setSessionModel(modelId) {
18582
+ this._currentModelId = modelId;
17670
18583
  await this.getConnectionOrThrow().setSessionModel(this._id, modelId);
17671
18584
  }
17672
18585
  /**
@@ -17744,11 +18657,23 @@ var ActiveSessionImpl = class {
17744
18657
  * Disconnect from the session/agent
17745
18658
  */
17746
18659
  disconnect() {
18660
+ this.removeConnectionListeners();
17747
18661
  this.connection.disconnect();
17748
18662
  this.removeAllListeners();
17749
18663
  this.logger?.info(`Session ${this._id}: Disconnected`);
17750
18664
  }
17751
18665
  /**
18666
+ * Detach the session from connection events without disconnecting the connection.
18667
+ * This should be called when the session is being replaced but the connection is shared.
18668
+ * Unlike disconnect(), this only removes event listeners without closing the connection.
18669
+ */
18670
+ detach() {
18671
+ this.logger?.info(`Session ${this._id}: Detaching from connection events`);
18672
+ this.removeConnectionListeners();
18673
+ this.removeAllListeners();
18674
+ this.logger?.info(`Session ${this._id}: Detached successfully`);
18675
+ }
18676
+ /**
17752
18677
  * Symbol.dispose for 'using' keyword support
17753
18678
  * Automatically disconnects and cleans up when session goes out of scope
17754
18679
  *
@@ -17767,60 +18692,85 @@ var ActiveSessionImpl = class {
17767
18692
  if (!this.connection.isInitialized) throw new Error(`Session ${this._id}: Connection not initialized.`);
17768
18693
  return this.connection;
17769
18694
  }
18695
+ /**
18696
+ * 在 connection 上注册 listener 并保存引用,便于 disconnect 时移除
18697
+ */
18698
+ addConnectionListener(connection, event, listener) {
18699
+ connection.on(event, listener);
18700
+ this.connectionListeners.push({
18701
+ event,
18702
+ listener
18703
+ });
18704
+ }
18705
+ /**
18706
+ * 从 connection 上移除所有本 session 注册的 listener
18707
+ */
18708
+ removeConnectionListeners() {
18709
+ for (const { event, listener } of this.connectionListeners) this.connection.off(event, listener);
18710
+ this.connectionListeners = [];
18711
+ }
17770
18712
  setupConnectionEvents(connection) {
17771
- connection.on("connected", () => {
18713
+ this.addConnectionListener(connection, "connected", () => {
17772
18714
  this.emit("connected", void 0);
17773
18715
  });
17774
- connection.on("disconnected", () => {
18716
+ this.addConnectionListener(connection, "disconnected", () => {
17775
18717
  this.emit("disconnected", void 0);
17776
18718
  });
17777
- connection.on("error", (error) => {
18719
+ this.addConnectionListener(connection, "error", (error) => {
17778
18720
  this.emit("error", error);
17779
18721
  });
17780
- connection.on("sessionUpdate", (update) => {
18722
+ this.addConnectionListener(connection, "sessionUpdate", (update) => {
18723
+ const notificationSessionId = update?.sessionId;
18724
+ if (notificationSessionId && notificationSessionId !== this._id) {
18725
+ console.log(`[RT-DEBUG][AgentMgr:Session] sessionUpdate SKIPPED: notifSessionId mismatch, notif=${notificationSessionId?.substring(0, 8)}, my=${this._id?.substring(0, 8)}`);
18726
+ return;
18727
+ }
17781
18728
  this.emit("sessionUpdate", update);
17782
18729
  });
17783
- connection.on("artifactCreated", (artifact) => {
17784
- console.log("[Session] Forwarding artifactCreated:", {
17785
- artifactUri: artifact.uri,
17786
- artifactType: artifact.type
17787
- });
18730
+ this.addConnectionListener(connection, "artifactCreated", (artifact) => {
18731
+ if (!this.shouldForwardArtifact(artifact)) return;
17788
18732
  this.emit("artifactCreated", artifact);
17789
18733
  });
17790
- connection.on("artifactUpdated", (artifact) => {
17791
- console.log("[Session] Forwarding artifactUpdated:", {
17792
- artifactUri: artifact.uri,
17793
- artifactType: artifact.type
17794
- });
18734
+ this.addConnectionListener(connection, "artifactUpdated", (artifact) => {
18735
+ if (!this.shouldForwardArtifact(artifact)) return;
17795
18736
  this.emit("artifactUpdated", artifact);
17796
18737
  });
17797
- connection.on("artifactDeleted", (artifact) => {
17798
- console.log("[Session] Forwarding artifactDeleted:", { artifactUri: artifact.uri });
18738
+ this.addConnectionListener(connection, "artifactDeleted", (artifact) => {
18739
+ if (!this.shouldForwardArtifact(artifact)) return;
17799
18740
  this.emit("artifactDeleted", artifact);
17800
18741
  });
17801
- connection.on("permissionRequest", (request) => {
18742
+ this.addConnectionListener(connection, "permissionRequest", (request) => {
17802
18743
  this.emit("permissionRequest", request);
17803
18744
  });
17804
- connection.on("questionRequest", (request) => {
18745
+ this.addConnectionListener(connection, "questionRequest", (request) => {
17805
18746
  this.emit("questionRequest", request);
17806
18747
  });
17807
- connection.on("questionCancelled", () => {
18748
+ this.addConnectionListener(connection, "questionCancelled", () => {
17808
18749
  this.prompts.cancel();
17809
18750
  });
17810
- connection.on("usageUpdate", (usage) => {
18751
+ this.addConnectionListener(connection, "usageUpdate", (usage) => {
17811
18752
  this.emit("usageUpdate", usage);
17812
18753
  });
17813
- connection.on("checkpointCreated", (checkpoint) => {
18754
+ this.addConnectionListener(connection, "checkpointCreated", (checkpoint) => {
18755
+ const originSessionId = checkpoint.__sessionId;
18756
+ if (originSessionId && originSessionId !== this._id) return;
17814
18757
  this.emit("checkpointCreated", checkpoint);
17815
18758
  });
17816
- connection.on("checkpointUpdated", (checkpoint) => {
18759
+ this.addConnectionListener(connection, "checkpointUpdated", (checkpoint) => {
18760
+ const originSessionId = checkpoint.__sessionId;
18761
+ if (originSessionId && originSessionId !== this._id) return;
17817
18762
  this.emit("checkpointUpdated", checkpoint);
17818
18763
  });
17819
- connection.on("command", (command) => {
17820
- console.log("[Session] Forwarding command:", {
17821
- action: command.action,
17822
- paramsKeys: command.params ? Object.keys(command.params) : []
17823
- });
18764
+ this.addConnectionListener(connection, "command", (command) => {
18765
+ const originSessionId = command.__sessionId;
18766
+ if (originSessionId && originSessionId !== this._id) {
18767
+ console.log("[Session] Command not forwarded:", {
18768
+ command,
18769
+ originSessionId,
18770
+ sessionId: this._id
18771
+ });
18772
+ return;
18773
+ }
17824
18774
  this.emit("command", command);
17825
18775
  });
17826
18776
  }
@@ -17830,19 +18780,38 @@ var ActiveSessionImpl = class {
17830
18780
  _meta: response._meta ?? void 0
17831
18781
  };
17832
18782
  }
18783
+ /**
18784
+ * 判断 artifact 是否应该转发给当前 session
18785
+ * - media 类型:按 cwd 路径隔离(同 cwd 下不同 session 可共享 media)
18786
+ * - 其余类型:按 __sessionId 严格隔离
18787
+ */
18788
+ shouldForwardArtifact(artifact) {
18789
+ const originSessionId = artifact.__sessionId;
18790
+ console.log("[Session] shouldForwardArtifact:", {
18791
+ artifact,
18792
+ originSessionId,
18793
+ sessionId: this._id,
18794
+ cwd: this.connection?.cwd
18795
+ });
18796
+ if (artifact.type === "media") {
18797
+ const cwd = this.connection?.cwd;
18798
+ const uri = artifact?.uri;
18799
+ if (cwd && uri) {
18800
+ const toPosix = (p) => p.replace(/\\/g, "/");
18801
+ const uriPath = toPosix(uri.replace(/^(?:file|agent):\/\//, ""));
18802
+ const posixCwd = toPosix(cwd);
18803
+ const normalizedCwd = posixCwd.endsWith("/") ? posixCwd : posixCwd + "/";
18804
+ return uriPath.startsWith(normalizedCwd);
18805
+ }
18806
+ }
18807
+ if (originSessionId && originSessionId !== this._id) return false;
18808
+ return true;
18809
+ }
17833
18810
  };
17834
18811
 
17835
18812
  //#endregion
17836
18813
  //#region ../agent-provider/src/common/client/session-manager.ts
17837
18814
  /**
17838
- * SessionManager - Manages session lifecycle and connections
17839
- *
17840
- * Provides the core implementation for session-centric API operations:
17841
- * - list() - Lists sessions (mapped from agents)
17842
- * - createSession() - Creates new session (auto-creates agent)
17843
- * - loadSession() - Loads existing session (finds agent by sessionId)
17844
- */
17845
- /**
17846
18815
  * SessionManager - Session lifecycle management
17847
18816
  *
17848
18817
  * This class manages the relationship between sessions and agents.
@@ -17892,7 +18861,8 @@ var SessionManager = class {
17892
18861
  createdAt: agent.createdAt,
17893
18862
  lastActivityAt: agent.updatedAt,
17894
18863
  cwd: agent.type === "local" ? agent.cwd : void 0,
17895
- isPlayground: agent.isPlayground
18864
+ isPlayground: agent.isPlayground,
18865
+ isUserDefinedTitle: agent.isUserDefinedTitle
17896
18866
  }));
17897
18867
  console.log("[SessionManager] Returning sessions:", {
17898
18868
  count: sessions.length,
@@ -17919,13 +18889,26 @@ var SessionManager = class {
17919
18889
  if (this.provider.create) {
17920
18890
  agentId = await this.provider.create(params);
17921
18891
  this.logger?.debug(`Created new agent: ${agentId}`);
18892
+ if (params.options?.onSessionPrepared) {
18893
+ const initialPrompt = params.options?.prompt;
18894
+ const initialTitle = initialPrompt?.slice(0, 50) || "";
18895
+ params.options.onSessionPrepared({
18896
+ id: agentId,
18897
+ agentId,
18898
+ name: initialTitle + (initialPrompt && initialPrompt.length > 50 ? "..." : ""),
18899
+ status: "connecting",
18900
+ cwd: params.cwd || "",
18901
+ createdAt: /* @__PURE__ */ new Date()
18902
+ });
18903
+ this.logger?.debug(`Called onSessionPrepared for: ${agentId}`);
18904
+ }
17922
18905
  } else throw new Error("Provider does not support creating agents. Use sessions.load() with an existing sessionId.");
17923
18906
  const connection = await this.provider.connect(agentId);
17924
18907
  this.logger?.debug(`Connected to agent: ${agentId}`);
17925
18908
  const response = await connection.createSession({
17926
- _meta: params._meta,
18909
+ _meta: params.options?._meta,
17927
18910
  cwd: params.cwd,
17928
- mcpServers: params.mcpServers
18911
+ mcpServers: params.options?.mcpServers
17929
18912
  });
17930
18913
  if (this.provider.registerSession) {
17931
18914
  this.provider.registerSession(response.sessionId, agentId);
@@ -17938,14 +18921,10 @@ var SessionManager = class {
17938
18921
  connectionInfo
17939
18922
  });
17940
18923
  session.setModes(response.modes?.availableModes, response.modes?.currentModeId);
17941
- if (response.models?.availableModels) {
17942
- const localModels = response.models.availableModels.map((m) => ({
17943
- id: m.modelId,
17944
- name: m.name,
17945
- description: m.description ?? void 0
17946
- }));
17947
- session.setModels(localModels, response.models?.currentModelId);
17948
- }
18924
+ const availableModels = this.extractAvailableModels(response);
18925
+ if (availableModels) session.setModels(availableModels, response.models?.currentModelId);
18926
+ const responseCwd = response._meta?.["codebuddy.ai"]?.cwd;
18927
+ if (responseCwd) session.setCwd(responseCwd);
17949
18928
  this.logger?.info(`Session created: ${response.sessionId}`);
17950
18929
  return session;
17951
18930
  }
@@ -17981,17 +18960,31 @@ var SessionManager = class {
17981
18960
  mcpServers: params.mcpServers
17982
18961
  });
17983
18962
  session.setModes(response.modes?.availableModes, response.modes?.currentModeId);
17984
- if (response.models?.availableModels) {
17985
- const localModels = response.models.availableModels.map((m) => ({
17986
- id: m.modelId,
17987
- name: m.name,
17988
- description: m.description ?? void 0
17989
- }));
17990
- session.setModels(localModels, response.models?.currentModelId);
17991
- }
18963
+ const availableModels = this.extractAvailableModels(response);
18964
+ if (availableModels) session.setModels(availableModels, response.models?.currentModelId);
17992
18965
  this.logger?.info(`Session loaded: ${params.sessionId}`);
17993
18966
  return session;
17994
18967
  }
18968
+ /**
18969
+ * 从 ACP response 中提取可用模型列表
18970
+ *
18971
+ * 优先级:
18972
+ * 1. response.models._meta?.['codebuddy.ai']?.availableModels - 包含完整的模型信息(字段名为 'id')
18973
+ * 2. response.models?.availableModels - 只包含基本信息(字段名为 'modelId')
18974
+ * 3. undefined - 都没有时返回 undefined
18975
+ *
18976
+ * @param response - ACP 响应对象
18977
+ * @returns ModelInfo[] | undefined
18978
+ */
18979
+ extractAvailableModels(response) {
18980
+ const metaModels = (response.models?._meta?.["codebuddy.ai"])?.availableModels;
18981
+ if (metaModels && Array.isArray(metaModels) && metaModels.length > 0) return metaModels;
18982
+ const availableModels = response.models?.availableModels;
18983
+ if (availableModels && Array.isArray(availableModels) && availableModels.length > 0) return availableModels.map((model) => ({
18984
+ ...model,
18985
+ ...model._meta?.["codebuddy.ai"] || {}
18986
+ }));
18987
+ }
17995
18988
  };
17996
18989
 
17997
18990
  //#endregion
@@ -18098,9 +19091,29 @@ var AgentClient = class {
18098
19091
  throw error;
18099
19092
  }
18100
19093
  },
18101
- move: async (sessionId) => {
18102
- this.logger?.debug("AgentClient.sessions.move called", { sessionId });
18103
- try {
19094
+ updateStatus: async (sessionId, status) => {
19095
+ this.logger?.debug("AgentClient.sessions.updateStatus called", {
19096
+ sessionId,
19097
+ status
19098
+ });
19099
+ try {
19100
+ if (this.provider.updateStatus) {
19101
+ const result = await this.provider.updateStatus(sessionId, status);
19102
+ this.logger?.info("Session status updated successfully", {
19103
+ sessionId,
19104
+ status
19105
+ });
19106
+ return result;
19107
+ }
19108
+ throw new Error("Provider does not support updateStatus method");
19109
+ } catch (error) {
19110
+ this.logger?.error("Failed to update session status", error);
19111
+ throw error;
19112
+ }
19113
+ },
19114
+ move: async (sessionId) => {
19115
+ this.logger?.debug("AgentClient.sessions.move called", { sessionId });
19116
+ try {
18104
19117
  if (this.provider.move) {
18105
19118
  const result = await this.provider.move(sessionId);
18106
19119
  this.logger?.info("Session moved successfully", { sessionId });
@@ -18145,6 +19158,100 @@ var AgentClient = class {
18145
19158
  return [];
18146
19159
  }
18147
19160
  },
19161
+ getAutomationSnapshot: async () => {
19162
+ try {
19163
+ if (this.provider?.getAutomationSnapshot) return await this.provider.getAutomationSnapshot();
19164
+ this.logger?.warn("Provider does not support getAutomationSnapshot");
19165
+ } catch (error) {
19166
+ this.logger?.error("Failed to get automation snapshot", error);
19167
+ }
19168
+ return {
19169
+ automations: [],
19170
+ inbox: [],
19171
+ runtimeState: {},
19172
+ updatedAt: Date.now()
19173
+ };
19174
+ },
19175
+ updateAutomation: async (payload) => {
19176
+ try {
19177
+ if (this.provider?.updateAutomation) return await this.provider.updateAutomation(payload);
19178
+ this.logger?.warn("Provider does not support updateAutomation");
19179
+ } catch (error) {
19180
+ this.logger?.error("Failed to update automation", error);
19181
+ return {
19182
+ success: false,
19183
+ message: error instanceof Error ? error.message : "Unknown error"
19184
+ };
19185
+ }
19186
+ return {
19187
+ success: false,
19188
+ message: "Provider does not support updateAutomation"
19189
+ };
19190
+ },
19191
+ deleteAutomation: async (id) => {
19192
+ try {
19193
+ if (this.provider?.deleteAutomation) return await this.provider.deleteAutomation(id);
19194
+ this.logger?.warn("Provider does not support deleteAutomation");
19195
+ } catch (error) {
19196
+ this.logger?.error("Failed to delete automation", error);
19197
+ return {
19198
+ success: false,
19199
+ message: error instanceof Error ? error.message : "Unknown error"
19200
+ };
19201
+ }
19202
+ return {
19203
+ success: false,
19204
+ message: "Provider does not support deleteAutomation"
19205
+ };
19206
+ },
19207
+ archiveAutomationInboxItem: async (itemId) => {
19208
+ try {
19209
+ if (this.provider?.archiveAutomationInboxItem) return await this.provider.archiveAutomationInboxItem(itemId);
19210
+ this.logger?.warn("Provider does not support archiveAutomationInboxItem");
19211
+ } catch (error) {
19212
+ this.logger?.error("Failed to archive automation inbox item", error);
19213
+ return {
19214
+ success: false,
19215
+ message: error instanceof Error ? error.message : "Unknown error"
19216
+ };
19217
+ }
19218
+ return {
19219
+ success: false,
19220
+ message: "Provider does not support archiveAutomationInboxItem"
19221
+ };
19222
+ },
19223
+ deleteAutomationInboxItem: async (itemId) => {
19224
+ try {
19225
+ if (this.provider?.deleteAutomationInboxItem) return await this.provider.deleteAutomationInboxItem(itemId);
19226
+ this.logger?.warn("Provider does not support deleteAutomationInboxItem");
19227
+ } catch (error) {
19228
+ this.logger?.error("Failed to delete automation inbox item", error);
19229
+ return {
19230
+ success: false,
19231
+ message: error instanceof Error ? error.message : "Unknown error"
19232
+ };
19233
+ }
19234
+ return {
19235
+ success: false,
19236
+ message: "Provider does not support deleteAutomationInboxItem"
19237
+ };
19238
+ },
19239
+ testAutomation: async (id) => {
19240
+ try {
19241
+ if (this.provider?.testAutomation) return await this.provider.testAutomation(id);
19242
+ this.logger?.warn("Provider does not support testAutomation");
19243
+ } catch (error) {
19244
+ this.logger?.error("Failed to test automation", error);
19245
+ return {
19246
+ success: false,
19247
+ message: error instanceof Error ? error.message : "Unknown error"
19248
+ };
19249
+ }
19250
+ return {
19251
+ success: false,
19252
+ message: "Provider does not support testAutomation"
19253
+ };
19254
+ },
18148
19255
  on: (event, handler) => {
18149
19256
  if (this.provider.on) this.provider.on(event, handler);
18150
19257
  else this.logger?.warn(`Provider does not support event registration: ${String(event)}`);
@@ -18188,164 +19295,517 @@ var AgentClient = class {
18188
19295
  error: "Provider does not support pickFile"
18189
19296
  };
18190
19297
  } catch (error) {
18191
- this.logger?.error("Failed to pick file", error);
18192
- return {
18193
- files: [],
18194
- canceled: true,
18195
- error: error instanceof Error ? error.message : "Unknown error"
18196
- };
19298
+ this.logger?.error("Failed to pick file", error);
19299
+ return {
19300
+ files: [],
19301
+ canceled: true,
19302
+ error: error instanceof Error ? error.message : "Unknown error"
19303
+ };
19304
+ }
19305
+ },
19306
+ pickFolder: async (params) => {
19307
+ try {
19308
+ if (this.provider && this.provider.pickFolder) {
19309
+ const result = await this.provider.pickFolder(params);
19310
+ this.logger?.info("Folder picker completed", {
19311
+ folderPaths: result.folderPaths,
19312
+ canceled: result.canceled
19313
+ });
19314
+ return result;
19315
+ }
19316
+ return {
19317
+ folderPaths: [],
19318
+ canceled: true,
19319
+ error: "Provider does not support pickFolder"
19320
+ };
19321
+ } catch (error) {
19322
+ this.logger?.error("Failed to pick folder", error);
19323
+ return {
19324
+ folderPaths: [],
19325
+ canceled: true,
19326
+ error: error instanceof Error ? error.message : "Unknown error"
19327
+ };
19328
+ }
19329
+ },
19330
+ uploadFile: async (params) => {
19331
+ try {
19332
+ if (this.provider && this.provider.uploadFile) {
19333
+ const result = await this.provider.uploadFile(params);
19334
+ this.logger?.info("File upload completed", {
19335
+ count: params.files.length,
19336
+ success: result.success
19337
+ });
19338
+ return result;
19339
+ }
19340
+ return {
19341
+ success: false,
19342
+ error: "Provider does not support uploadFile"
19343
+ };
19344
+ } catch (error) {
19345
+ this.logger?.error("Failed to upload file", error);
19346
+ return {
19347
+ success: false,
19348
+ error: error instanceof Error ? error.message : "Unknown error"
19349
+ };
19350
+ }
19351
+ },
19352
+ searchFile: async (params) => {
19353
+ try {
19354
+ if (this.provider && this.provider.searchFile) {
19355
+ const result = await this.provider.searchFile(params);
19356
+ this.logger?.info("File search completed", {
19357
+ resultCount: result.results.length,
19358
+ hasError: !!result.error
19359
+ });
19360
+ return result;
19361
+ }
19362
+ return {
19363
+ results: [],
19364
+ error: "Provider does not support searchFile"
19365
+ };
19366
+ } catch (error) {
19367
+ this.logger?.error("Failed to search file", error);
19368
+ return {
19369
+ results: [],
19370
+ error: error instanceof Error ? error.message : "Unknown error"
19371
+ };
19372
+ }
19373
+ },
19374
+ getSubagentList: async (params) => {
19375
+ try {
19376
+ if (this.provider && this.provider.getSubagentList) {
19377
+ const result = await this.provider.getSubagentList(params);
19378
+ this.logger?.info("Subagent list retrieved", {
19379
+ resultCount: result.results.length,
19380
+ hasError: !!result.error
19381
+ });
19382
+ return result;
19383
+ }
19384
+ return {
19385
+ results: [],
19386
+ error: "Provider does not support getSubagentList"
19387
+ };
19388
+ } catch (error) {
19389
+ this.logger?.error("Failed to get subagent list", error);
19390
+ return {
19391
+ results: [],
19392
+ error: error instanceof Error ? error.message : "Unknown error"
19393
+ };
19394
+ }
19395
+ },
19396
+ getSkillList: async (params) => {
19397
+ try {
19398
+ if (this.provider && this.provider.getSkillList) {
19399
+ const result = await this.provider.getSkillList(params);
19400
+ this.logger?.info("Skill list retrieved", {
19401
+ resultCount: result.results.length,
19402
+ hasError: !!result.error
19403
+ });
19404
+ return result;
19405
+ }
19406
+ return {
19407
+ results: [],
19408
+ error: "Provider does not support getSkillList"
19409
+ };
19410
+ } catch (error) {
19411
+ this.logger?.error("Failed to get skill list", error);
19412
+ return {
19413
+ results: [],
19414
+ error: error instanceof Error ? error.message : "Unknown error"
19415
+ };
19416
+ }
19417
+ },
19418
+ importSkill: async (params) => {
19419
+ try {
19420
+ if (this.provider && this.provider.importSkill) {
19421
+ const result = await this.provider.importSkill(params);
19422
+ this.logger?.info("Import skill completed", {
19423
+ success: result.success,
19424
+ hasError: !!result.error
19425
+ });
19426
+ return result;
19427
+ }
19428
+ return {
19429
+ success: false,
19430
+ error: "Provider does not support importSkill"
19431
+ };
19432
+ } catch (error) {
19433
+ this.logger?.error("Failed to import skill", error);
19434
+ return {
19435
+ success: false,
19436
+ error: error instanceof Error ? error.message : "Unknown error"
19437
+ };
19438
+ }
19439
+ },
19440
+ batchTogglePlugins: async (request) => {
19441
+ try {
19442
+ if (this.provider && this.provider.batchTogglePlugins) {
19443
+ const result = await this.provider.batchTogglePlugins(request);
19444
+ this.logger?.info("Batch toggle plugins completed", {
19445
+ succeededCount: result.succeededPlugins.length,
19446
+ failedCount: result.failedPlugins.length
19447
+ });
19448
+ return result;
19449
+ }
19450
+ return {
19451
+ success: false,
19452
+ succeededPlugins: [],
19453
+ failedPlugins: request.items.map((item) => ({
19454
+ ...item,
19455
+ error: "Provider does not support batchTogglePlugins"
19456
+ }))
19457
+ };
19458
+ } catch (error) {
19459
+ this.logger?.error("Failed to batch toggle plugins", error);
19460
+ return {
19461
+ success: false,
19462
+ succeededPlugins: [],
19463
+ failedPlugins: request.items.map((item) => ({
19464
+ ...item,
19465
+ error: error instanceof Error ? error.message : "Unknown error"
19466
+ }))
19467
+ };
19468
+ }
19469
+ },
19470
+ getInstalledPlugins: async (forceRefresh) => {
19471
+ try {
19472
+ if (this.provider && "getInstalledPlugins" in this.provider && typeof this.provider.getInstalledPlugins === "function") {
19473
+ const result = await this.provider.getInstalledPlugins(forceRefresh);
19474
+ this.logger?.info("Got installed plugins", { count: result?.length ?? 0 });
19475
+ return result;
19476
+ }
19477
+ this.logger?.warn("Provider does not support getInstalledPlugins");
19478
+ return [];
19479
+ } catch (error) {
19480
+ this.logger?.error("Failed to get installed plugins", error);
19481
+ return [];
19482
+ }
19483
+ },
19484
+ installPlugins: async (pluginNames, marketplaceName, installScope, marketplaceSource, workspacePath) => {
19485
+ try {
19486
+ if (this.provider && "installPlugins" in this.provider && typeof this.provider.installPlugins === "function") {
19487
+ const result = await this.provider.installPlugins(pluginNames, marketplaceName, installScope, marketplaceSource, workspacePath);
19488
+ this.logger?.info("Install plugins", {
19489
+ pluginNames,
19490
+ marketplaceName,
19491
+ success: result.success
19492
+ });
19493
+ return result;
19494
+ }
19495
+ this.logger?.warn("Provider does not support installPlugins");
19496
+ return {
19497
+ success: false,
19498
+ error: "Provider does not support installPlugins"
19499
+ };
19500
+ } catch (error) {
19501
+ this.logger?.error("Failed to install plugins", error);
19502
+ return {
19503
+ success: false,
19504
+ error: error instanceof Error ? error.message : "Unknown error"
19505
+ };
19506
+ }
19507
+ },
19508
+ uninstallPlugin: async (pluginName, marketplaceName, scope) => {
19509
+ try {
19510
+ if (this.provider && "uninstallPlugin" in this.provider && typeof this.provider.uninstallPlugin === "function") {
19511
+ const result = await this.provider.uninstallPlugin(pluginName, marketplaceName, scope);
19512
+ this.logger?.info("Uninstall plugin", {
19513
+ pluginName,
19514
+ marketplaceName,
19515
+ scope,
19516
+ success: result.success
19517
+ });
19518
+ return result;
19519
+ }
19520
+ this.logger?.warn("Provider does not support uninstallPlugin");
19521
+ return {
19522
+ success: false,
19523
+ error: "Provider does not support uninstallPlugin"
19524
+ };
19525
+ } catch (error) {
19526
+ this.logger?.error("Failed to uninstall plugin", error);
19527
+ return {
19528
+ success: false,
19529
+ error: error instanceof Error ? error.message : "Unknown error"
19530
+ };
19531
+ }
19532
+ },
19533
+ updatePlugin: async (pluginName, marketplaceName) => {
19534
+ try {
19535
+ if (this.provider && "updatePlugin" in this.provider && typeof this.provider.updatePlugin === "function") {
19536
+ const result = await this.provider.updatePlugin(pluginName, marketplaceName);
19537
+ this.logger?.info("Update plugin", {
19538
+ pluginName,
19539
+ marketplaceName,
19540
+ success: result.success
19541
+ });
19542
+ return result;
19543
+ }
19544
+ this.logger?.warn("Provider does not support updatePlugin");
19545
+ return {
19546
+ success: false,
19547
+ error: "Provider does not support updatePlugin"
19548
+ };
19549
+ } catch (error) {
19550
+ this.logger?.error("Failed to update plugin", error);
19551
+ return {
19552
+ success: false,
19553
+ error: error instanceof Error ? error.message : "Unknown error"
19554
+ };
19555
+ }
19556
+ },
19557
+ getPluginMarketplaces: async (forceRefresh) => {
19558
+ try {
19559
+ if (this.provider && "getPluginMarketplaces" in this.provider && typeof this.provider.getPluginMarketplaces === "function") {
19560
+ const result = await this.provider.getPluginMarketplaces(forceRefresh);
19561
+ this.logger?.info("Got plugin marketplaces", { count: result?.length ?? 0 });
19562
+ return result;
19563
+ }
19564
+ this.logger?.warn("Provider does not support getPluginMarketplaces");
19565
+ return [];
19566
+ } catch (error) {
19567
+ this.logger?.error("Failed to get plugin marketplaces", error);
19568
+ return [];
19569
+ }
19570
+ },
19571
+ getMarketplacePlugins: async (marketplaceName, forceRefresh, searchText) => {
19572
+ try {
19573
+ if (this.provider && "getMarketplacePlugins" in this.provider && typeof this.provider.getMarketplacePlugins === "function") {
19574
+ const result = await this.provider.getMarketplacePlugins(marketplaceName, forceRefresh, searchText);
19575
+ this.logger?.info("Got marketplace plugins", {
19576
+ marketplaceName,
19577
+ count: result?.length ?? 0
19578
+ });
19579
+ return result;
19580
+ }
19581
+ this.logger?.warn("Provider does not support getMarketplacePlugins");
19582
+ return [];
19583
+ } catch (error) {
19584
+ this.logger?.error("Failed to get marketplace plugins", error);
19585
+ return [];
18197
19586
  }
18198
19587
  },
18199
- pickFolder: async (params) => {
19588
+ getPluginDetail: async (pluginName, marketplaceName) => {
18200
19589
  try {
18201
- if (this.provider && this.provider.pickFolder) {
18202
- const result = await this.provider.pickFolder(params);
18203
- this.logger?.info("Folder picker completed", {
18204
- folderPaths: result.folderPaths,
18205
- canceled: result.canceled
19590
+ if (this.provider && "getPluginDetail" in this.provider && typeof this.provider.getPluginDetail === "function") {
19591
+ const result = await this.provider.getPluginDetail(pluginName, marketplaceName);
19592
+ this.logger?.info("Got plugin detail", {
19593
+ pluginName,
19594
+ marketplaceName
18206
19595
  });
18207
19596
  return result;
18208
19597
  }
18209
- return {
18210
- folderPaths: [],
18211
- canceled: true,
18212
- error: "Provider does not support pickFolder"
18213
- };
19598
+ this.logger?.warn("Provider does not support getPluginDetail");
19599
+ return null;
18214
19600
  } catch (error) {
18215
- this.logger?.error("Failed to pick folder", error);
18216
- return {
18217
- folderPaths: [],
18218
- canceled: true,
18219
- error: error instanceof Error ? error.message : "Unknown error"
18220
- };
19601
+ this.logger?.error("Failed to get plugin detail", error);
19602
+ return null;
18221
19603
  }
18222
19604
  },
18223
- uploadFile: async (params) => {
19605
+ addPluginMarketplace: async (source, name) => {
18224
19606
  try {
18225
- if (this.provider && this.provider.uploadFile) {
18226
- const result = await this.provider.uploadFile(params);
18227
- this.logger?.info("File upload completed", {
18228
- count: params.files.length,
19607
+ if (this.provider && "addPluginMarketplace" in this.provider && typeof this.provider.addPluginMarketplace === "function") {
19608
+ const result = await this.provider.addPluginMarketplace(source, name);
19609
+ this.logger?.info("Add plugin marketplace", {
19610
+ source,
19611
+ name,
18229
19612
  success: result.success
18230
19613
  });
18231
19614
  return result;
18232
19615
  }
19616
+ this.logger?.warn("Provider does not support addPluginMarketplace");
18233
19617
  return {
18234
19618
  success: false,
18235
- error: "Provider does not support uploadFile"
19619
+ error: "Provider does not support addPluginMarketplace"
18236
19620
  };
18237
19621
  } catch (error) {
18238
- this.logger?.error("Failed to upload file", error);
19622
+ this.logger?.error("Failed to add plugin marketplace", error);
18239
19623
  return {
18240
19624
  success: false,
18241
19625
  error: error instanceof Error ? error.message : "Unknown error"
18242
19626
  };
18243
19627
  }
18244
19628
  },
18245
- searchFile: async (params) => {
19629
+ removePluginMarketplace: async (marketplaceName) => {
18246
19630
  try {
18247
- if (this.provider && this.provider.searchFile) {
18248
- const result = await this.provider.searchFile(params);
18249
- this.logger?.info("File search completed", {
18250
- resultCount: result.results.length,
18251
- hasError: !!result.error
19631
+ if (this.provider && "removePluginMarketplace" in this.provider && typeof this.provider.removePluginMarketplace === "function") {
19632
+ const result = await this.provider.removePluginMarketplace(marketplaceName);
19633
+ this.logger?.info("Remove plugin marketplace", {
19634
+ marketplaceName,
19635
+ success: result.success
18252
19636
  });
18253
19637
  return result;
18254
19638
  }
19639
+ this.logger?.warn("Provider does not support removePluginMarketplace");
18255
19640
  return {
18256
- results: [],
18257
- error: "Provider does not support searchFile"
19641
+ success: false,
19642
+ error: "Provider does not support removePluginMarketplace"
18258
19643
  };
18259
19644
  } catch (error) {
18260
- this.logger?.error("Failed to search file", error);
19645
+ this.logger?.error("Failed to remove plugin marketplace", error);
18261
19646
  return {
18262
- results: [],
19647
+ success: false,
18263
19648
  error: error instanceof Error ? error.message : "Unknown error"
18264
19649
  };
18265
19650
  }
18266
19651
  },
18267
- batchTogglePlugins: async (request) => {
19652
+ refreshPluginMarketplace: async (marketplaceName) => {
18268
19653
  try {
18269
- if (this.provider && this.provider.batchTogglePlugins) {
18270
- const result = await this.provider.batchTogglePlugins(request);
18271
- this.logger?.info("Batch toggle plugins completed", {
18272
- succeededCount: result.succeededPlugins.length,
18273
- failedCount: result.failedPlugins.length
19654
+ if (this.provider && "refreshPluginMarketplace" in this.provider && typeof this.provider.refreshPluginMarketplace === "function") {
19655
+ const result = await this.provider.refreshPluginMarketplace(marketplaceName);
19656
+ this.logger?.info("Refresh plugin marketplace", {
19657
+ marketplaceName,
19658
+ success: result.success
18274
19659
  });
18275
19660
  return result;
18276
19661
  }
19662
+ this.logger?.warn("Provider does not support refreshPluginMarketplace");
18277
19663
  return {
18278
19664
  success: false,
18279
- succeededPlugins: [],
18280
- failedPlugins: request.items.map((item) => ({
18281
- ...item,
18282
- error: "Provider does not support batchTogglePlugins"
18283
- }))
19665
+ error: "Provider does not support refreshPluginMarketplace"
18284
19666
  };
18285
19667
  } catch (error) {
18286
- this.logger?.error("Failed to batch toggle plugins", error);
19668
+ this.logger?.error("Failed to refresh plugin marketplace", error);
18287
19669
  return {
18288
19670
  success: false,
18289
- succeededPlugins: [],
18290
- failedPlugins: request.items.map((item) => ({
18291
- ...item,
18292
- error: error instanceof Error ? error.message : "Unknown error"
18293
- }))
19671
+ error: error instanceof Error ? error.message : "Unknown error"
18294
19672
  };
18295
19673
  }
18296
19674
  },
18297
- getInstalledPlugins: async (forceRefresh) => {
19675
+ openFolderInNewWindow: async (folderPath) => {
18298
19676
  try {
18299
- if (this.provider && "getInstalledPlugins" in this.provider && typeof this.provider.getInstalledPlugins === "function") {
18300
- const result = await this.provider.getInstalledPlugins(forceRefresh);
18301
- this.logger?.info("Got installed plugins", { count: result?.length ?? 0 });
18302
- return result;
19677
+ if (this.provider && "openFolderInNewWindow" in this.provider && typeof this.provider.openFolderInNewWindow === "function") {
19678
+ await this.provider.openFolderInNewWindow(folderPath);
19679
+ this.logger?.info("Opened folder in new window", { folderPath });
19680
+ } else {
19681
+ this.logger?.warn("Provider does not support openFolderInNewWindow");
19682
+ throw new Error("Provider does not support openFolderInNewWindow");
18303
19683
  }
18304
- this.logger?.warn("Provider does not support getInstalledPlugins");
19684
+ } catch (error) {
19685
+ this.logger?.error("Failed to open folder in new window", error);
19686
+ throw error;
19687
+ }
19688
+ },
19689
+ getSupportScenes: async (locale) => {
19690
+ try {
19691
+ if (this.provider && "getSupportScenes" in this.provider && typeof this.provider.getSupportScenes === "function") return await this.provider.getSupportScenes(locale);
19692
+ this.logger?.warn("Provider does not support getSupportScenes");
18305
19693
  return [];
18306
19694
  } catch (error) {
18307
- this.logger?.error("Failed to get installed plugins", error);
19695
+ this.logger?.error("Failed to get support scenes", error);
18308
19696
  return [];
18309
19697
  }
18310
19698
  },
18311
- installPlugins: async (pluginNames, marketplaceName, installScope) => {
19699
+ getProductScenes: async (locale) => {
18312
19700
  try {
18313
- if (this.provider && "installPlugins" in this.provider && typeof this.provider.installPlugins === "function") {
18314
- const result = await this.provider.installPlugins(pluginNames, marketplaceName, installScope);
18315
- this.logger?.info("Install plugins", {
18316
- pluginNames,
18317
- marketplaceName,
18318
- success: result.success
18319
- });
19701
+ if (this.provider?.getProductScenes) {
19702
+ const result = await this.provider.getProductScenes(locale);
19703
+ this.logger?.info("Got product scenes", { count: result?.length ?? 0 });
18320
19704
  return result;
18321
19705
  }
18322
- this.logger?.warn("Provider does not support installPlugins");
18323
- return {
18324
- success: false,
18325
- error: "Provider does not support installPlugins"
18326
- };
19706
+ this.logger?.warn("Provider does not support getProductScenes");
19707
+ return [];
18327
19708
  } catch (error) {
18328
- this.logger?.error("Failed to install plugins", error);
18329
- return {
18330
- success: false,
18331
- error: error instanceof Error ? error.message : "Unknown error"
18332
- };
19709
+ this.logger?.error("Failed to get product scenes", error);
19710
+ return [];
18333
19711
  }
18334
19712
  },
18335
- getSupportScenes: async () => {
19713
+ getAvailableCommands: async (params) => {
18336
19714
  try {
18337
- if (this.provider && "getSupportScenes" in this.provider && typeof this.provider.getSupportScenes === "function") {
18338
- const result = await this.provider.getSupportScenes();
18339
- this.logger?.info("Got support scenes", { count: result?.length ?? 0 });
19715
+ if (this.provider && "getAvailableCommands" in this.provider && typeof this.provider.getAvailableCommands === "function") {
19716
+ const result = await this.provider.getAvailableCommands(params);
19717
+ this.logger?.info("Got available commands from provider", {
19718
+ sessionId: params?.sessionId ?? "(default)",
19719
+ count: result?.length ?? 0
19720
+ });
18340
19721
  return result;
18341
19722
  }
18342
- this.logger?.warn("Provider does not support getSupportScenes");
19723
+ this.logger?.warn("Provider does not support getAvailableCommands", { params });
18343
19724
  return [];
18344
19725
  } catch (error) {
18345
- this.logger?.error("Failed to get support scenes", error);
19726
+ this.logger?.error("Failed to get available commands", error);
18346
19727
  return [];
18347
19728
  }
18348
19729
  },
19730
+ reportTelemetry: async (eventName, payload) => {
19731
+ try {
19732
+ if (this.provider?.reportTelemetry) await this.provider.reportTelemetry(eventName, payload);
19733
+ else this.logger?.warn("Provider does not support reportTelemetry");
19734
+ } catch (error) {
19735
+ this.logger?.error("Failed to report telemetry", error);
19736
+ }
19737
+ },
19738
+ getProductConfiguration: async () => {
19739
+ try {
19740
+ if (this.provider?.getProductConfiguration) return await this.provider.getProductConfiguration();
19741
+ this.logger?.warn("Provider does not support getProductConfiguration");
19742
+ return {};
19743
+ } catch (error) {
19744
+ this.logger?.error("Failed to get product configuration", error);
19745
+ return {};
19746
+ }
19747
+ },
19748
+ getUserInfo: async () => {
19749
+ this.logger?.info("[AgentClient.sessions] getUserInfo() called");
19750
+ try {
19751
+ if (this.provider?.getUserInfo) {
19752
+ const result = await this.provider.getUserInfo();
19753
+ this.logger?.info("[AgentClient.sessions] getUserInfo() result:", JSON.stringify(result));
19754
+ return result;
19755
+ }
19756
+ this.logger?.warn("Provider does not support getUserInfo");
19757
+ return {};
19758
+ } catch (error) {
19759
+ this.logger?.error("Failed to get user info", error);
19760
+ return {};
19761
+ }
19762
+ },
19763
+ respondToSampling: async (sessionId, response) => {
19764
+ try {
19765
+ if (this.provider?.respondToSampling) {
19766
+ await this.provider.respondToSampling(sessionId, response);
19767
+ this.logger?.info("Responded to sampling request", {
19768
+ sessionId,
19769
+ requestId: response.id,
19770
+ approved: response.approved
19771
+ });
19772
+ } else this.logger?.warn("Provider does not support respondToSampling");
19773
+ } catch (error) {
19774
+ this.logger?.error("Failed to respond to sampling request", error);
19775
+ throw error;
19776
+ }
19777
+ },
19778
+ respondToRoots: async (sessionId, response) => {
19779
+ try {
19780
+ if (this.provider?.respondToRoots) {
19781
+ await this.provider.respondToRoots(sessionId, response);
19782
+ this.logger?.info("Responded to roots request", {
19783
+ sessionId,
19784
+ requestId: response.id,
19785
+ approved: response.approved
19786
+ });
19787
+ } else this.logger?.warn("Provider does not support respondToRoots");
19788
+ } catch (error) {
19789
+ this.logger?.error("Failed to respond to roots request", error);
19790
+ throw error;
19791
+ }
19792
+ },
19793
+ subscribeSamplingRequests: (serverName, callback) => {
19794
+ if (this.provider?.subscribeSamplingRequests) {
19795
+ this.logger?.info("Subscribing to sampling requests", { serverName });
19796
+ return this.provider.subscribeSamplingRequests(serverName, callback);
19797
+ }
19798
+ this.logger?.warn("Provider does not support subscribeSamplingRequests");
19799
+ return () => {};
19800
+ },
19801
+ subscribeRootsRequests: (serverName, callback) => {
19802
+ if (this.provider?.subscribeRootsRequests) {
19803
+ this.logger?.info("Subscribing to roots requests", { serverName });
19804
+ return this.provider.subscribeRootsRequests(serverName, callback);
19805
+ }
19806
+ this.logger?.warn("Provider does not support subscribeRootsRequests");
19807
+ return () => {};
19808
+ },
18349
19809
  models: this.createModelsResource()
18350
19810
  };
18351
19811
  }
@@ -18412,6 +19872,154 @@ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
18412
19872
  return AccountStatus;
18413
19873
  }({});
18414
19874
 
19875
+ //#endregion
19876
+ //#region ../agent-provider/src/backend/service/oauth-repository-service.ts
19877
+ /**
19878
+ * OAuth Repository Service
19879
+ *
19880
+ * 封装 OAuth 连接器相关的仓库和分支操作
19881
+ */
19882
+ /**
19883
+ * OAuth Repository Service
19884
+ *
19885
+ * 提供仓库和分支的查询操作
19886
+ */
19887
+ var OAuthRepositoryService = class {
19888
+ /**
19889
+ * 获取仓库分支列表
19890
+ * API 端点: GET /console/as/connector/oauth/{name}/branches
19891
+ *
19892
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
19893
+ * @param params 平台特定的查询参数
19894
+ * @param page 页码,从1开始,0表示不分页获取全部
19895
+ * @param perPage 每页数量,最大100
19896
+ * @returns Promise<OauthBranch[]> 分支列表
19897
+ *
19898
+ * @example
19899
+ * ```typescript
19900
+ * // GitHub
19901
+ * const branches = await service.getBranches('github', {
19902
+ * owner: 'CodeBuddy-Official-Account',
19903
+ * repo: 'CodeBuddyIDE'
19904
+ * });
19905
+ *
19906
+ * // Gongfeng
19907
+ * const branches = await service.getBranches('gongfeng', {
19908
+ * project_id: '1611499'
19909
+ * });
19910
+ *
19911
+ * // CNB
19912
+ * const branches = await service.getBranches('cnb', {
19913
+ * repo: 'genie/genie-ide'
19914
+ * });
19915
+ * ```
19916
+ */
19917
+ async getBranches(connector, params, page = 0, perPage = 100) {
19918
+ try {
19919
+ const url = `/console/as/connector/oauth/${connector}/branches?${this.buildBranchQueryParams(connector, params, page, perPage).toString()}`;
19920
+ console.log(`[OAuthRepositoryService] GET ${url}`);
19921
+ const apiResponse = await httpService.get(url);
19922
+ if (!apiResponse.data) {
19923
+ console.warn(`[OAuthRepositoryService] No data in branches response for ${connector}`);
19924
+ return [];
19925
+ }
19926
+ const branches = apiResponse.data.branches || [];
19927
+ console.log(`[OAuthRepositoryService] Retrieved ${branches.length} branches from ${connector}`);
19928
+ return branches;
19929
+ } catch (error) {
19930
+ console.error(`[OAuthRepositoryService] Failed to get branches from ${connector}:`, error);
19931
+ throw error;
19932
+ }
19933
+ }
19934
+ /**
19935
+ * 获取仓库列表
19936
+ * API 端点: GET /console/as/connector/oauth/{name}/repos
19937
+ *
19938
+ * Note: 由于工蜂原生支持的 Search 能力会匹配 path/name/description 部分,
19939
+ * 且不支持定制,不满足产品要求(只按 name 匹配),因此前端拉取全量数据后做筛选。
19940
+ *
19941
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
19942
+ * @param page 页码,从1开始,0表示不分页获取全部
19943
+ * - GitHub 只支持全量数据,必须传 0
19944
+ * - 工蜂和 CNB 依据前端逻辑而定
19945
+ * @param perPage 每页数量,最大100
19946
+ * @returns Promise<ListReposResponse> 仓库列表响应
19947
+ *
19948
+ * @example
19949
+ * ```typescript
19950
+ * // GitHub - 必须传 page=0 获取全量数据
19951
+ * const response = await service.getRepositories('github', 0, 100);
19952
+ * // response.github_repos 是 map: installation_id => repo[]
19953
+ *
19954
+ * // Gongfeng
19955
+ * const response = await service.getRepositories('gongfeng', 0, 100);
19956
+ * // response.gongfeng_repos 是数组
19957
+ *
19958
+ * // CNB
19959
+ * const response = await service.getRepositories('cnb', 0, 100);
19960
+ * // response.cnb_repos 是数组
19961
+ * ```
19962
+ */
19963
+ async getRepositories(connector, page = 0, perPage = 100) {
19964
+ try {
19965
+ const queryParams = new URLSearchParams();
19966
+ queryParams.append("page", String(page));
19967
+ queryParams.append("per_page", String(Math.min(perPage, 100)));
19968
+ const url = `/console/as/connector/oauth/${connector}/repos?${queryParams.toString()}`;
19969
+ console.log(`[OAuthRepositoryService] GET ${url}`);
19970
+ const apiResponse = await httpService.get(url);
19971
+ if (!apiResponse.data) {
19972
+ console.warn(`[OAuthRepositoryService] No data in repos response for ${connector}`);
19973
+ return {};
19974
+ }
19975
+ const response = apiResponse.data;
19976
+ this.logRepositoryCounts(response);
19977
+ return response;
19978
+ } catch (error) {
19979
+ console.error(`[OAuthRepositoryService] Failed to get repos from ${connector}:`, error);
19980
+ throw error;
19981
+ }
19982
+ }
19983
+ /**
19984
+ * 构建分支查询参数
19985
+ */
19986
+ buildBranchQueryParams(connector, params, page, perPage) {
19987
+ const queryParams = new URLSearchParams();
19988
+ queryParams.append("page", String(page));
19989
+ queryParams.append("per_page", String(Math.min(perPage, 100)));
19990
+ if (connector === "github") {
19991
+ const githubParams = params;
19992
+ if (!githubParams.owner || !githubParams.repo) throw new Error("GitHub requires owner and repo parameters");
19993
+ queryParams.append("owner", githubParams.owner);
19994
+ queryParams.append("repo", githubParams.repo);
19995
+ } else if (connector === "gongfeng") {
19996
+ const gongfengParams = params;
19997
+ if (!gongfengParams.project_id) throw new Error("Gongfeng requires project_id parameter");
19998
+ queryParams.append("project_id", gongfengParams.project_id);
19999
+ } else if (connector === "cnb") {
20000
+ const cnbParams = params;
20001
+ if (!cnbParams.repo) throw new Error("CNB requires repo parameter");
20002
+ queryParams.append("repo", cnbParams.repo);
20003
+ } else throw new Error(`Unknown connector: ${connector}`);
20004
+ return queryParams;
20005
+ }
20006
+ /**
20007
+ * 记录仓库数量日志
20008
+ */
20009
+ logRepositoryCounts(response) {
20010
+ if (response.github_repos) {
20011
+ const totalCount = Object.values(response.github_repos).reduce((sum, repos) => sum + repos.length, 0);
20012
+ console.log(`[OAuthRepositoryService] Retrieved ${totalCount} GitHub repos across ${Object.keys(response.github_repos).length} installations`);
20013
+ }
20014
+ if (response.gongfeng_repos) console.log(`[OAuthRepositoryService] Retrieved ${response.gongfeng_repos.length} Gongfeng repos`);
20015
+ if (response.cnb_repos) console.log(`[OAuthRepositoryService] Retrieved ${response.cnb_repos.length} CNB repos`);
20016
+ }
20017
+ };
20018
+ /**
20019
+ * OAuth Repository Service 单例实例
20020
+ */
20021
+ const oauthRepositoryService = new OAuthRepositoryService();
20022
+
18415
20023
  //#endregion
18416
20024
  //#region ../agent-provider/src/backend/backend-provider.ts
18417
20025
  /**
@@ -18420,28 +20028,24 @@ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
18420
20028
  * 封装与后端 API 的 HTTP 通信
18421
20029
  */
18422
20030
  /**
18423
- * 判断当前是否在 SSO 域名下
18424
- * SSO 域名格式: xxx.sso.copilot.tencent.com xxx.sso.codebuddy.cn
20031
+ * 判断当前账号是否是 SSO 账号
20032
+ * 通过 account.accountType === 'sso' 来判断,这种不行,因为未登录之前account 为空
18425
20033
  */
18426
- const isSSODomain = () => {
18427
- const { hostname } = window.location;
18428
- return hostname.includes(".sso.copilot") || hostname.includes("sso.codebuddy.cn") || hostname.includes(".sso.copilot-staging") || hostname.includes(".staging-sso.codebuddy.cn");
20034
+ const safeParseJSON = (jsonString) => {
20035
+ try {
20036
+ return JSON.parse(jsonString);
20037
+ } catch (error) {
20038
+ return {};
20039
+ }
18429
20040
  };
18430
20041
  /**
18431
- * 获取登录页面 URL
18432
- * - SSO 域名下需要跳转到对应的预发/生产域名
18433
- * - 非 SSO 域名直接使用当前域名
20042
+ * 根据路径获取完整 URL
20043
+ * - SSO 账号需要跳转到对应的预发/生产域名
20044
+ * - 非 SSO 账号直接使用当前域名
20045
+ * @param path 路径,如 '/login'、'/logout'、'/home' 等
20046
+ * @returns 完整的 URL 地址
18434
20047
  */
18435
- const getLoginUrl = () => {
18436
- const { hostname, protocol } = window.location;
18437
- if (isSSODomain()) {
18438
- const isCodebuddy = hostname.includes("codebuddy.cn");
18439
- const isStaging = hostname.includes("staging");
18440
- if (isCodebuddy) return isStaging ? `${protocol}//staging.codebuddy.cn/login` : `${protocol}//www.codebuddy.cn/login`;
18441
- else return isStaging ? `${protocol}//staging-copilot.tencent.com/login` : `${protocol}//copilot.tencent.com/login`;
18442
- }
18443
- return `${window.location.origin}/login`;
18444
- };
20048
+ const getFullUrl = (path) => `${window.location.origin}${path}`;
18445
20049
  /** 获取当前域名的账号选择页面 URL */
18446
20050
  const getSelectAccountUrl = () => `${window.location.origin}/login/select`;
18447
20051
  /** localStorage 中存储选中账号 ID 的 key */
@@ -18478,12 +20082,30 @@ var BackendProvider = class {
18478
20082
  constructor(config) {
18479
20083
  httpService.setBaseURL(config.baseUrl);
18480
20084
  if (config.authToken) httpService.setAuthToken(config.authToken);
18481
- httpService.onUnauthorized(() => {
18482
- console.log("[BackendProvider] User unauthorized (401/403), triggering logout");
18483
- this.logout().catch((error) => {
18484
- console.error("[BackendProvider] Logout failed in 401 handler:", error);
20085
+ httpService.onUnauthorized(() => this.handleUnauthorized());
20086
+ }
20087
+ /**
20088
+ * 处理 401 未授权错误
20089
+ * 先尝试刷新 token,失败后再执行登出流程
20090
+ *
20091
+ * @throws 如果 token 刷新失败,抛出错误通知 HttpService 不要重试
20092
+ */
20093
+ async handleUnauthorized() {
20094
+ console.log("[BackendProvider] User unauthorized (401), attempting token refresh first");
20095
+ try {
20096
+ if (await this.refreshToken()) {
20097
+ console.log("[BackendProvider] Token refresh successful after 401, user still logged in");
20098
+ return;
20099
+ }
20100
+ throw new Error("Token refresh returned null");
20101
+ } catch (error) {
20102
+ console.error("[BackendProvider] Token refresh failed after 401:", error);
20103
+ console.log("[BackendProvider] Token refresh failed, triggering logout");
20104
+ this.logout().catch((logoutError) => {
20105
+ console.error("[BackendProvider] Logout failed in 401 handler:", logoutError);
18485
20106
  });
18486
- });
20107
+ throw error;
20108
+ }
18487
20109
  }
18488
20110
  /**
18489
20111
  * 获取当前账号信息
@@ -18528,7 +20150,7 @@ var BackendProvider = class {
18528
20150
  return account;
18529
20151
  }
18530
20152
  const redirectUrl = encodeURIComponent(window.location.href);
18531
- window.location.href = `${getSelectAccountUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
20153
+ window.location.href = `${getSelectAccountUrl()}?platform=agents&state=0&redirect_uri=${redirectUrl}`;
18532
20154
  accountService.setAccount(null);
18533
20155
  return null;
18534
20156
  } catch (error) {
@@ -18551,7 +20173,8 @@ var BackendProvider = class {
18551
20173
  activeStatus: connector.active_status,
18552
20174
  displayName: connector.display_name,
18553
20175
  oauthClientId: connector.oauth_client_id,
18554
- oauthRedirectUrl: connector.oauth_redirect_url
20176
+ oauthRedirectUrl: connector.oauth_redirect_url,
20177
+ oauthAppName: connector.oauth_app_name
18555
20178
  })) };
18556
20179
  }
18557
20180
  throw result;
@@ -18633,7 +20256,8 @@ var BackendProvider = class {
18633
20256
  connectStatus: connector.connect_status,
18634
20257
  displayName: connector.display_name,
18635
20258
  oauthClientId: connector.oauth_client_id,
18636
- oauthRedirectUrl: connector.oauth_redirect_url
20259
+ oauthRedirectUrl: connector.oauth_redirect_url,
20260
+ oauthAppName: connector.oauth_app_name
18637
20261
  })) };
18638
20262
  }
18639
20263
  throw result;
@@ -18724,6 +20348,9 @@ var BackendProvider = class {
18724
20348
  PackageCode: void 0,
18725
20349
  name: ""
18726
20350
  };
20351
+ const productFeatures = typeof window !== "undefined" && window.PRODUCT_FEATURES ? safeParseJSON(window.PRODUCT_FEATURES) : {};
20352
+ console.log("[PRODUCT_FEATURES]", productFeatures);
20353
+ if (!productFeatures.billing) return defaultPlan;
18727
20354
  try {
18728
20355
  const now = /* @__PURE__ */ new Date();
18729
20356
  const futureDate = new Date(now.getTime() + 101 * 365 * 24 * 60 * 60 * 1e3);
@@ -18745,10 +20372,11 @@ var BackendProvider = class {
18745
20372
  if (!time) return 0;
18746
20373
  return new Date(time).getTime();
18747
20374
  };
18748
- const dailyCredits = [CommodityCode.free, CommodityCode.freeMon];
20375
+ const dailyCredits = [CommodityCode.free];
18749
20376
  const planResources = resources.map((r) => {
18750
20377
  const isDaily = dailyCredits.includes(r.PackageCode);
18751
20378
  const endTime = isDaily ? r.CycleEndTime : r.DeductionEndTime;
20379
+ const refreshAt = parseTime(r.CycleEndTime) + 1e3;
18752
20380
  return {
18753
20381
  id: r.ResourceId,
18754
20382
  name: isDaily ? "plan.addonCredits" : getPackageName(r.PackageCode),
@@ -18758,7 +20386,7 @@ var BackendProvider = class {
18758
20386
  used: Math.max(0, Number(r.CycleCapacitySizePrecise) - Number(r.CycleCapacityRemainPrecise)) || 0,
18759
20387
  left: Number(r.CycleCapacityRemainPrecise) || 0,
18760
20388
  expireAt: parseTime(endTime),
18761
- refreshAt: isDaily ? void 0 : parseTime(r.CycleEndTime)
20389
+ refreshAt: isDaily ? void 0 : refreshAt
18762
20390
  };
18763
20391
  }).sort((a, b) => {
18764
20392
  const getPriority = (code) => {
@@ -18766,10 +20394,11 @@ var BackendProvider = class {
18766
20394
  CommodityCode.proMon,
18767
20395
  CommodityCode.proMonPlus,
18768
20396
  CommodityCode.proYear,
20397
+ CommodityCode.freeMon,
18769
20398
  CommodityCode.extra
18770
20399
  ].includes(code)) return 1;
18771
20400
  if ([CommodityCode.gift, CommodityCode.activity].includes(code)) return 2;
18772
- if ([CommodityCode.free, CommodityCode.freeMon].includes(code)) return 3;
20401
+ if ([CommodityCode.free].includes(code)) return 3;
18773
20402
  return 4;
18774
20403
  };
18775
20404
  return getPriority(a.packageCode) - getPriority(b.packageCode);
@@ -18890,38 +20519,69 @@ var BackendProvider = class {
18890
20519
  */
18891
20520
  async login() {
18892
20521
  const redirectUrl = encodeURIComponent(window.location.href);
18893
- window.location.href = `${getLoginUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
20522
+ window.location.href = `${getFullUrl("/login")}?platform=agents&state=0&redirect_uri=${redirectUrl}`;
18894
20523
  }
18895
20524
  /**
18896
20525
  * 登出账号
18897
- * Web 环境: 通过 iframe 访问登出 URL 清除 cookie
20526
+ *
20527
+ * 策略:
20528
+ * - IOA 企业:用 iframe 走 SSO/SAML SLO 登出链路(涉及跨域重定向),通过轮询 iframe URL 变化检测完成
20529
+ * - 非 IOA 企业:直接用 httpService 请求 /console/logout,速度快
18898
20530
  */
18899
20531
  async logout() {
18900
- const url = `${httpService.getAxiosInstance().defaults.baseURL}/console/logout`;
20532
+ const account = accountService.getAccount();
20533
+ if (account?.enterpriseId && ["esoikz80kd8g", "etahzsqej0n4"].includes(account.enterpriseId)) await this.logoutViaIframe();
20534
+ else await this.logoutViaHttp();
20535
+ localStorage.removeItem(SELECTED_ACCOUNT_KEY);
20536
+ accountService.clearAccount();
20537
+ }
20538
+ /**
20539
+ * IOA 企业登出:通过 iframe 走 SSO/SAML SLO 登出链路
20540
+ * 轮询 iframe URL 变化检测完成,兜底超时 5 秒
20541
+ */
20542
+ async logoutViaIframe() {
20543
+ const logoutUrl = `${httpService.getAxiosInstance().defaults.baseURL}/console/logout`;
18901
20544
  try {
18902
20545
  await new Promise((resolve) => {
18903
20546
  const iframe = document.createElement("iframe");
18904
20547
  iframe.style.cssText = "position:fixed;top:-9999px;left:-9999px;width:1px;height:1px;border:none;";
18905
- iframe.src = url;
18906
- const timeout = setTimeout(() => {
18907
- cleanup();
18908
- resolve();
18909
- }, 5e3);
18910
- const cleanup = () => {
20548
+ iframe.src = logoutUrl;
20549
+ let pollTimer;
20550
+ let settled = false;
20551
+ const done = () => {
20552
+ if (settled) return;
20553
+ settled = true;
20554
+ clearInterval(pollTimer);
18911
20555
  clearTimeout(timeout);
18912
20556
  if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
18913
- };
18914
- iframe.onerror = () => {
18915
- cleanup();
18916
20557
  resolve();
18917
20558
  };
20559
+ let wasRedirecting = false;
20560
+ pollTimer = setInterval(() => {
20561
+ try {
20562
+ const href = iframe.contentWindow?.location?.href;
20563
+ if (wasRedirecting && href) done();
20564
+ } catch {
20565
+ wasRedirecting = true;
20566
+ }
20567
+ }, 100);
20568
+ const timeout = setTimeout(done, 5e3);
20569
+ iframe.onerror = done;
18918
20570
  document.body.appendChild(iframe);
18919
20571
  });
18920
20572
  } catch (error) {
18921
- console.error("[BackendProvider] logout failed:", error);
20573
+ console.error("[BackendProvider] logout via iframe failed:", error);
20574
+ }
20575
+ }
20576
+ /**
20577
+ * 非 IOA 企业登出:直接 HTTP 请求 /console/logout
20578
+ */
20579
+ async logoutViaHttp() {
20580
+ try {
20581
+ await httpService.get("/console/logout");
20582
+ } catch (error) {
20583
+ console.error("[BackendProvider] logout via http failed:", error);
18922
20584
  }
18923
- localStorage.removeItem(SELECTED_ACCOUNT_KEY);
18924
- accountService.clearAccount();
18925
20585
  }
18926
20586
  /**
18927
20587
  * 批量切换插件状态
@@ -18953,6 +20613,78 @@ var BackendProvider = class {
18953
20613
  return null;
18954
20614
  }
18955
20615
  }
20616
+ /**
20617
+ * 刷新 Token
20618
+ * 通过调用 getAccount 刷新 cookie,适用于 Cloud 场景下页面切换回来时刷新登录态
20619
+ * @returns Promise<Account | null> 刷新后的账号信息
20620
+ */
20621
+ async refreshToken() {
20622
+ console.log("[BackendProvider] Refreshing token...");
20623
+ try {
20624
+ const account = await this.getAccount();
20625
+ console.log("[BackendProvider] Token refreshed, account:", account?.uid);
20626
+ return account;
20627
+ } catch (error) {
20628
+ console.error("[BackendProvider] refreshToken failed:", error);
20629
+ return null;
20630
+ }
20631
+ }
20632
+ /**
20633
+ * 获取仓库分支列表
20634
+ * API 端点: GET /console/as/connector/oauth/{name}/branches
20635
+ *
20636
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
20637
+ * @param params 平台特定的查询参数
20638
+ * @param page 页码,从1开始,0表示不分页获取全部
20639
+ * @param perPage 每页数量,最大100
20640
+ * @returns Promise<OauthBranch[]> 分支列表
20641
+ */
20642
+ async getBranches(connector, params, page = 0, perPage = 100) {
20643
+ return oauthRepositoryService.getBranches(connector, params, page, perPage);
20644
+ }
20645
+ /**
20646
+ * 获取仓库列表
20647
+ * API 端点: GET /console/as/connector/oauth/{name}/repos
20648
+ *
20649
+ * Note: 由于工蜂原生支持的 Search 能力会匹配 path/name/description 部分,
20650
+ * 且不支持定制,不满足产品要求(只按 name 匹配),因此前端拉取全量数据后做筛选。
20651
+ *
20652
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
20653
+ * @param page 页码,从1开始,0表示不分页获取全部
20654
+ * - GitHub 只支持全量数据,必须传 0
20655
+ * - 工蜂和 CNB 依据前端逻辑而定
20656
+ * @param perPage 每页数量,最大100
20657
+ * @returns Promise<ListReposResponse> 仓库列表响应
20658
+ */
20659
+ async getRepositories(connector, page = 0, perPage = 100) {
20660
+ return oauthRepositoryService.getRepositories(connector, page, perPage);
20661
+ }
20662
+ /**
20663
+ * 保存待发送的输入内容到后端
20664
+ * API 端点: POST /api/v1/code-id
20665
+ */
20666
+ async savePendingInput(code) {
20667
+ try {
20668
+ const result = await httpService.post("/api/v1/code-id", { code });
20669
+ return result?.codeId || result?.data?.codeId || null;
20670
+ } catch (e) {
20671
+ console.warn("[BackendProvider] savePendingInput failed:", e);
20672
+ return null;
20673
+ }
20674
+ }
20675
+ /**
20676
+ * 从后端加载待发送的输入内容
20677
+ * API 端点: GET /api/v1/code?id=xxx
20678
+ */
20679
+ async loadPendingInput(codeId) {
20680
+ try {
20681
+ const result = await httpService.get(`/api/v1/code?id=${encodeURIComponent(codeId)}`);
20682
+ return result?.code || result?.data?.code || null;
20683
+ } catch (e) {
20684
+ console.warn("[BackendProvider] loadPendingInput failed:", e);
20685
+ return null;
20686
+ }
20687
+ }
18956
20688
  };
18957
20689
  /**
18958
20690
  * 创建 BackendProvider 实例
@@ -18992,6 +20724,7 @@ const BACKEND_REQUEST_TYPES = {
18992
20724
  GET_FILE: "backend:get-file",
18993
20725
  RELOAD_WINDOW: "backend:reload-window",
18994
20726
  CLOSE_AGENT_MANAGER: "backend:close-agent-manager",
20727
+ OPEN_EXTERNAL: "backend:open-external",
18995
20728
  BATCH_TOGGLE_PLUGINS: "backend:batch-toggle-plugins",
18996
20729
  GET_SUPPORT_SCENES: "backend:get-support-scenes"
18997
20730
  };
@@ -19040,12 +20773,14 @@ var IPCBackendProvider = class {
19040
20773
  */
19041
20774
  async getAccount() {
19042
20775
  this.log("Getting account via IPC");
20776
+ const startTime = performance.now();
19043
20777
  try {
19044
20778
  const account = await this.sendBackendRequest(BACKEND_REQUEST_TYPES.GET_ACCOUNT);
20779
+ this.log(`getAccount IPC completed in ${(performance.now() - startTime).toFixed(0)}ms`);
19045
20780
  accountService.setAccount(account);
19046
20781
  return account;
19047
20782
  } catch (error) {
19048
- this.log("Get account failed:", error);
20783
+ this.log(`getAccount IPC failed after ${(performance.now() - startTime).toFixed(0)}ms:`, error);
19049
20784
  accountService.setAccount(null);
19050
20785
  return null;
19051
20786
  }
@@ -19287,6 +21022,20 @@ var IPCBackendProvider = class {
19287
21022
  }
19288
21023
  }
19289
21024
  /**
21025
+ * 在外部浏览器中打开链接
21026
+ * IDE 环境: 通过 IPC 通知 IDE 使用 vscode.env.openExternal 打开 URL
21027
+ * @param url 要打开的 URL
21028
+ */
21029
+ async openExternal(url) {
21030
+ this.log("Opening external URL via IPC:", url);
21031
+ try {
21032
+ await this.sendBackendRequest(BACKEND_REQUEST_TYPES.OPEN_EXTERNAL, { url });
21033
+ } catch (error) {
21034
+ this.log("Open external request failed:", error);
21035
+ throw error;
21036
+ }
21037
+ }
21038
+ /**
19290
21039
  * 批量切换插件状态
19291
21040
  * IDE 环境: 通过 IPC 调用 Extension Host 的 PluginService
19292
21041
  */