@tencent-ai/cloud-agent-sdk 0.2.12 → 0.2.13-next.136ad71.20260213

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
@@ -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
@@ -1987,10 +1989,6 @@ var StreamableHttpClient = class {
1987
1989
  },
1988
1990
  requestPermission: async (params) => this.handleRequestPermission(params),
1989
1991
  extNotification: async (method, params) => {
1990
- console.log("[ACP-Client] extNotification callback invoked:", {
1991
- method,
1992
- paramsKeys: Object.keys(params)
1993
- });
1994
1992
  await this.handleExtNotification(method, params);
1995
1993
  },
1996
1994
  extMethod: async (method, params) => this.handleExtMethod(method, params)
@@ -1998,10 +1996,15 @@ var StreamableHttpClient = class {
1998
1996
  }
1999
1997
  /**
2000
1998
  * Create a new session
1999
+ *
2000
+ * Retries on transient network errors (e.g., proxy connection reset)
2001
+ * since session/new is idempotent and safe to retry.
2001
2002
  */
2002
2003
  async createSession(cwd) {
2003
2004
  this.ensureInitialized("createSession");
2004
- try {
2005
+ const maxRetries = 2;
2006
+ let lastError;
2007
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
2005
2008
  const response = await this.connection.newSession({
2006
2009
  cwd,
2007
2010
  mcpServers: []
@@ -2009,8 +2012,16 @@ var StreamableHttpClient = class {
2009
2012
  this.options.logger?.info(`Session created: ${response.sessionId}`);
2010
2013
  return response;
2011
2014
  } 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);
2015
+ lastError = err instanceof Error ? err : new Error(String(err));
2016
+ if (attempt < maxRetries && isRetryableNetworkError(err)) {
2017
+ const delay = 500 * Math.pow(2, attempt);
2018
+ this.options.logger?.warn(`session/new network error, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries}): ${lastError.message}`);
2019
+ await new Promise((resolve) => setTimeout(resolve, delay));
2020
+ continue;
2021
+ }
2022
+ throw new SessionError(`Failed to create session: ${lastError.message}`, void 0, lastError);
2013
2023
  }
2024
+ throw new SessionError(`Failed to create session: ${lastError?.message}`, void 0, lastError);
2014
2025
  }
2015
2026
  /**
2016
2027
  * Load an existing session
@@ -2234,6 +2245,18 @@ var StreamableHttpClient = class {
2234
2245
  if (this.state !== "initialized") throw new InvalidStateError(operation, this.state, ["initialized"]);
2235
2246
  }
2236
2247
  };
2248
+ /**
2249
+ * Check if an error is a retryable network-level error.
2250
+ * Only network failures (TypeError from fetch) are retried, NOT HTTP errors (4xx/5xx).
2251
+ */
2252
+ function isRetryableNetworkError(error) {
2253
+ if (error instanceof TypeError) return true;
2254
+ if (error instanceof Error) {
2255
+ const msg = error.message.toLowerCase();
2256
+ 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");
2257
+ }
2258
+ return false;
2259
+ }
2237
2260
 
2238
2261
  //#endregion
2239
2262
  //#region ../agent-provider/src/common/providers/cloud-agent-provider/cloud-connection.ts
@@ -2283,6 +2306,24 @@ var CloudAgentConnection = class {
2283
2306
  },
2284
2307
  onUsageUpdate: (usage) => {
2285
2308
  this.emit("usageUpdate", usage);
2309
+ },
2310
+ onExtNotification: (method, params) => {
2311
+ console.log("[CloudConnection] Received extNotification:", {
2312
+ method,
2313
+ paramsKeys: Object.keys(params)
2314
+ });
2315
+ if (method === ExtensionMethod.COMMAND) {
2316
+ const action = params.action;
2317
+ const commandParams = params.params;
2318
+ console.log("[CloudConnection] Emitting command event:", {
2319
+ action,
2320
+ paramsKeys: commandParams ? Object.keys(commandParams) : []
2321
+ });
2322
+ this.emit("command", {
2323
+ action,
2324
+ params: commandParams
2325
+ });
2326
+ }
2286
2327
  }
2287
2328
  });
2288
2329
  this.setupEventForwarding();
@@ -2411,7 +2452,7 @@ var CloudAgentConnection = class {
2411
2452
  }
2412
2453
  async createSession(params) {
2413
2454
  return {
2414
- ...await this.client.loadSession(this.agentId, this.cwd),
2455
+ ...await this.client.createSession(this.cwd),
2415
2456
  sessionId: this.agentId
2416
2457
  };
2417
2458
  }
@@ -2529,6 +2570,16 @@ var CloudAgentConnection = class {
2529
2570
  get sessionConnectionInfo() {
2530
2571
  return this._sessionConnectionInfo;
2531
2572
  }
2573
+ async reportTelemetry(eventName, payload) {
2574
+ try {
2575
+ await this.client.extMethod("reportTelemetry", {
2576
+ eventName,
2577
+ payload
2578
+ });
2579
+ } catch (error) {
2580
+ console.warn("[CloudAgentConnection] reportTelemetry failed:", error);
2581
+ }
2582
+ }
2532
2583
  async extMethod(method, params) {
2533
2584
  return this.client.extMethod(method, params);
2534
2585
  }
@@ -15923,7 +15974,7 @@ var axios_default = axios;
15923
15974
  * 特性:
15924
15975
  * - 单例模式,全局唯一实例,延迟初始化(首次使用时自动创建)
15925
15976
  * - 支持拦截器注册(其他模块可注入 header)
15926
- * - 统一 401/403 处理
15977
+ * - 统一 401 处理(支持自动刷新 token 并重试)
15927
15978
  * - 自动携带凭证(withCredentials)
15928
15979
  * - 类型安全
15929
15980
  *
@@ -15963,6 +16014,8 @@ var HttpService = class HttpService {
15963
16014
  */
15964
16015
  constructor(config = {}) {
15965
16016
  this.unauthorizedCallbacks = /* @__PURE__ */ new Set();
16017
+ this.isRefreshing = false;
16018
+ this.refreshSubscribers = [];
15966
16019
  this.config = config;
15967
16020
  this.axiosInstance = axios_default.create({
15968
16021
  baseURL: config.baseURL?.replace(/\/$/, "") || "",
@@ -16003,18 +16056,54 @@ var HttpService = class HttpService {
16003
16056
  }, (error) => Promise.reject(error));
16004
16057
  }
16005
16058
  /**
16006
- * 注册默认响应拦截器(处理 401/403)
16059
+ * 注册默认响应拦截器(处理 401,支持自动重试)
16007
16060
  */
16008
16061
  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();
16062
+ this.axiosInstance.interceptors.response.use((response) => response, async (error) => {
16063
+ const originalRequest = error.config;
16064
+ if (error.response?.status === 401 && originalRequest && !originalRequest._retry) {
16065
+ if (originalRequest.url?.includes("/console/accounts")) {
16066
+ console.warn("[HttpService] Unauthorized 401 on refresh endpoint, not retrying");
16067
+ return Promise.reject(error);
16068
+ }
16069
+ console.warn("[HttpService] Unauthorized 401, attempting token refresh and retry");
16070
+ originalRequest._retry = true;
16071
+ if (this.isRefreshing) return new Promise((resolve, reject) => {
16072
+ this.refreshSubscribers.push((success) => {
16073
+ if (success) this.axiosInstance.request(originalRequest).then(resolve).catch(reject);
16074
+ else reject(error);
16075
+ });
16076
+ });
16077
+ this.isRefreshing = true;
16078
+ try {
16079
+ await this.triggerUnauthorizedCallbacks();
16080
+ this.onRefreshSuccess();
16081
+ return this.axiosInstance.request(originalRequest);
16082
+ } catch (refreshError) {
16083
+ this.onRefreshFailure();
16084
+ return Promise.reject(error);
16085
+ } finally {
16086
+ this.isRefreshing = false;
16087
+ }
16013
16088
  }
16014
16089
  return Promise.reject(error);
16015
16090
  });
16016
16091
  }
16017
16092
  /**
16093
+ * token 刷新成功,通知所有等待的请求
16094
+ */
16095
+ onRefreshSuccess() {
16096
+ this.refreshSubscribers.forEach((callback) => callback(true));
16097
+ this.refreshSubscribers = [];
16098
+ }
16099
+ /**
16100
+ * token 刷新失败,通知所有等待的请求
16101
+ */
16102
+ onRefreshFailure() {
16103
+ this.refreshSubscribers.forEach((callback) => callback(false));
16104
+ this.refreshSubscribers = [];
16105
+ }
16106
+ /**
16018
16107
  * 注册请求拦截器
16019
16108
  * @param onFulfilled 请求成功拦截器
16020
16109
  * @param onRejected 请求失败拦截器
@@ -16091,16 +16180,18 @@ var HttpService = class HttpService {
16091
16180
  this.unauthorizedCallbacks.delete(callback);
16092
16181
  }
16093
16182
  /**
16094
- * 触发所有 401 回调
16183
+ * 触发所有 401 回调并等待完成
16184
+ * @returns Promise,等待所有回调完成
16185
+ * @throws 如果任何回调失败,则抛出错误
16095
16186
  */
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
- });
16187
+ async triggerUnauthorizedCallbacks() {
16188
+ const callbacks = Array.from(this.unauthorizedCallbacks);
16189
+ const failedResults = (await Promise.allSettled(callbacks.map((callback) => callback()))).filter((r) => r.status === "rejected");
16190
+ if (failedResults.length > 0) {
16191
+ const errors = failedResults.map((r) => r.reason);
16192
+ console.error("[HttpService] Some unauthorized callbacks failed:", errors);
16193
+ throw errors[0];
16194
+ }
16104
16195
  }
16105
16196
  /**
16106
16197
  * 更新 authToken
@@ -16212,6 +16303,7 @@ var AccountService = class {
16212
16303
  this.initPromise = null;
16213
16304
  this.initResolve = null;
16214
16305
  this.requestInterceptorId = null;
16306
+ this.crossTabBroadcaster = null;
16215
16307
  this.initPromise = new Promise((resolve) => {
16216
16308
  this.initResolve = resolve;
16217
16309
  });
@@ -16260,15 +16352,34 @@ var AccountService = class {
16260
16352
  this.initialized = true;
16261
16353
  this.initResolve?.(account);
16262
16354
  }
16263
- if (!wasInitialized || prev?.uid !== account?.uid) this.notifyListeners();
16355
+ if (!wasInitialized || prev?.uid !== account?.uid) {
16356
+ this.notifyListeners();
16357
+ if (account && this.crossTabBroadcaster) this.crossTabBroadcaster.broadcastLogin();
16358
+ }
16264
16359
  }
16265
16360
  /**
16266
16361
  * 清除账号(登出)
16362
+ * 先广播登出消息,再清除本地账号
16267
16363
  */
16268
16364
  clearAccount() {
16365
+ if (this.crossTabBroadcaster) this.crossTabBroadcaster.broadcastLogout();
16269
16366
  this.setAccount(null);
16270
16367
  }
16271
16368
  /**
16369
+ * 静默清除账号(不广播)
16370
+ * 用于收到其他标签页 logout 消息时,避免循环广播
16371
+ */
16372
+ clearAccountSilently() {
16373
+ this.setAccount(null);
16374
+ }
16375
+ /**
16376
+ * 设置跨标签页认证同步广播器
16377
+ * 应在应用初始化时由上层(如 agent-ui)调用
16378
+ */
16379
+ setCrossTabBroadcaster(broadcaster) {
16380
+ this.crossTabBroadcaster = broadcaster;
16381
+ }
16382
+ /**
16272
16383
  * 订阅账号变化
16273
16384
  * @param callback 变化时的回调函数
16274
16385
  * @returns 取消订阅函数
@@ -16333,6 +16444,11 @@ var AccountService = class {
16333
16444
  * 导出单例实例
16334
16445
  */
16335
16446
  const accountService = new AccountService();
16447
+ /**
16448
+ * 暴露给全局,供 Agent Manager 直接调用 setAccount 刷新 Widget 状态
16449
+ * 这是为了解决 IDE 环境中 IPC 事件无法直接触发 Widget 账号刷新的问题
16450
+ */
16451
+ if (typeof window !== "undefined") window.__genieAccountService = accountService;
16336
16452
 
16337
16453
  //#endregion
16338
16454
  //#region ../agent-provider/src/common/utils/concurrency.ts
@@ -16435,20 +16551,32 @@ var CosUploadService = class {
16435
16551
  * 上传单个文件到 COS
16436
16552
  *
16437
16553
  * @param file - 要上传的文件
16554
+ * @param abortSignal - 可选的 AbortSignal,用于取消上传
16438
16555
  * @returns 上传结果,包含访问 URL 或错误信息
16439
16556
  */
16440
- async uploadFile(file) {
16557
+ async uploadFile(file, abortSignal) {
16441
16558
  const filename = file.name;
16442
16559
  this.logger?.info(`[CosUploadService] Uploading file: ${filename}`);
16443
16560
  try {
16561
+ if (abortSignal?.aborted) return {
16562
+ success: false,
16563
+ error: "Upload cancelled",
16564
+ aborted: true
16565
+ };
16444
16566
  const objectKey = this.generateObjectKey(filename);
16445
16567
  this.logger?.debug(`[CosUploadService] Generated objectKey: ${objectKey}`);
16446
16568
  const presignedItem = (await this.getPresignedUrls([objectKey])).items[0];
16447
16569
  if (!presignedItem) throw new Error("No presigned URL item returned");
16570
+ if (abortSignal?.aborted) return {
16571
+ success: false,
16572
+ error: "Upload cancelled",
16573
+ aborted: true
16574
+ };
16448
16575
  const uploadResponse = await fetch(presignedItem.upload_url, {
16449
16576
  method: "PUT",
16450
16577
  body: file,
16451
- headers: { "Content-Type": file.type || "application/octet-stream" }
16578
+ headers: { "Content-Type": file.type || "application/octet-stream" },
16579
+ signal: abortSignal
16452
16580
  });
16453
16581
  if (!uploadResponse.ok) {
16454
16582
  const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
@@ -16462,6 +16590,11 @@ var CosUploadService = class {
16462
16590
  objectKey
16463
16591
  };
16464
16592
  } catch (error) {
16593
+ if (error instanceof Error && error.name === "AbortError") return {
16594
+ success: false,
16595
+ error: "Upload cancelled",
16596
+ aborted: true
16597
+ };
16465
16598
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
16466
16599
  this.logger?.error(`[CosUploadService] Upload failed: ${filename}`, error);
16467
16600
  return {
@@ -16476,14 +16609,25 @@ var CosUploadService = class {
16476
16609
  * 使用并发控制,限制同时上传的文件数量
16477
16610
  *
16478
16611
  * @param files - 要上传的文件数组
16612
+ * @param abortSignal - 可选的 AbortSignal,用于取消上传
16479
16613
  * @returns 所有文件的上传结果
16480
16614
  */
16481
- async uploadFiles(files) {
16615
+ async uploadFiles(files, abortSignal) {
16482
16616
  if (files.length === 0) return {
16483
16617
  success: true,
16484
16618
  urls: [],
16485
16619
  results: []
16486
16620
  };
16621
+ if (abortSignal?.aborted) return {
16622
+ success: false,
16623
+ error: "Upload cancelled",
16624
+ aborted: true,
16625
+ results: files.map(() => ({
16626
+ success: false,
16627
+ error: "Upload cancelled",
16628
+ aborted: true
16629
+ }))
16630
+ };
16487
16631
  this.logger?.info(`[CosUploadService] Uploading ${files.length} file(s) with concurrency ${this.uploadConcurrency}`);
16488
16632
  try {
16489
16633
  const fileInfos = files.map((file) => ({
@@ -16495,6 +16639,12 @@ var CosUploadService = class {
16495
16639
  this.logger?.debug(`[CosUploadService] Got ${presignedResponse.items.length} presigned URLs`);
16496
16640
  if (presignedResponse.items.length !== fileInfos.length) throw new Error(`Expected ${fileInfos.length} presigned URLs, got ${presignedResponse.items.length}`);
16497
16641
  const results = (await runWithConcurrencySettled(fileInfos.map(({ file }, index) => async () => {
16642
+ if (abortSignal?.aborted) return {
16643
+ success: false,
16644
+ error: "Upload cancelled",
16645
+ aborted: true,
16646
+ objectKey: fileInfos[index].objectKey
16647
+ };
16498
16648
  const presignedItem = presignedResponse.items[index];
16499
16649
  if (!presignedItem) return {
16500
16650
  success: false,
@@ -16505,7 +16655,8 @@ var CosUploadService = class {
16505
16655
  const uploadResponse = await fetch(presignedItem.upload_url, {
16506
16656
  method: "PUT",
16507
16657
  body: file,
16508
- headers: { "Content-Type": file.type || "application/octet-stream" }
16658
+ headers: { "Content-Type": file.type || "application/octet-stream" },
16659
+ signal: abortSignal
16509
16660
  });
16510
16661
  if (!uploadResponse.ok) {
16511
16662
  const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
@@ -16522,6 +16673,12 @@ var CosUploadService = class {
16522
16673
  objectKey: presignedItem.object_key
16523
16674
  };
16524
16675
  } catch (error) {
16676
+ if (error instanceof Error && error.name === "AbortError") return {
16677
+ success: false,
16678
+ error: "Upload cancelled",
16679
+ aborted: true,
16680
+ objectKey: presignedItem.object_key
16681
+ };
16525
16682
  return {
16526
16683
  success: false,
16527
16684
  error: error instanceof Error ? error.message : "Unknown error",
@@ -16922,15 +17079,9 @@ var CloudAgentProvider = class CloudAgentProvider {
16922
17079
  const url = this.buildGetUrl("/console/as/conversations/", params);
16923
17080
  const apiResponse = await httpService.get(url);
16924
17081
  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
17082
  return {
16932
- agents,
16933
- pagination
17083
+ agents: apiResponse.data.conversations.map((a) => this.toAgentState(a)),
17084
+ pagination: apiResponse.data.pagination
16934
17085
  };
16935
17086
  } catch (error) {
16936
17087
  this.logger?.error("Failed to list agents:", error);
@@ -16940,13 +17091,21 @@ var CloudAgentProvider = class CloudAgentProvider {
16940
17091
  /**
16941
17092
  * Create a new conversation
16942
17093
  * POST {endpoint}/console/as/conversations
17094
+ * @param params - Session params containing cwd and optional configuration
16943
17095
  */
16944
- async create() {
17096
+ async create(params) {
16945
17097
  try {
16946
- const apiResponse = await httpService.post("/console/as/conversations/", {
16947
- prompt: "",
16948
- model: "deepseek-r1"
16949
- });
17098
+ const { options = {} } = params;
17099
+ const codebuddyMeta = options._meta?.["codebuddy.ai"];
17100
+ const tagsObj = options.tags || codebuddyMeta?.tags;
17101
+ const tagsArray = tagsObj ? Object.entries(tagsObj).map(([key, value]) => `${key}:${value}`) : void 0;
17102
+ const createPayload = {
17103
+ prompt: (options.prompt || "").slice(0, 100),
17104
+ model: options.model || "deepseek-r1",
17105
+ ...tagsArray && tagsArray.length > 0 ? { tags: tagsArray } : {}
17106
+ };
17107
+ console.log("[CloudAgentProvider] Creating conversation with payload:", createPayload);
17108
+ const apiResponse = await httpService.post("/console/as/conversations/", createPayload);
16950
17109
  if (!apiResponse.data) throw new Error("No data in API response");
16951
17110
  this.logger?.info(`Created conversation: ${apiResponse.data.id}`);
16952
17111
  return apiResponse.data.id;
@@ -17116,6 +17275,35 @@ var CloudAgentProvider = class CloudAgentProvider {
17116
17275
  }
17117
17276
  }
17118
17277
  /**
17278
+ * Update conversation status by ID
17279
+ * POST {endpoint}/console/as/conversations/{agentId}
17280
+ *
17281
+ * @param agentId - Conversation ID to update
17282
+ * @param status - New status for the conversation
17283
+ * @returns PatchConversationResponse containing the updated conversation ID
17284
+ *
17285
+ * @example
17286
+ * ```typescript
17287
+ * const result = await provider.updateStatus('agent-123', 'completed');
17288
+ * console.log('Updated conversation status:', result.id);
17289
+ * ```
17290
+ */
17291
+ async updateStatus(agentId, status) {
17292
+ try {
17293
+ const body = { status };
17294
+ const apiResponse = await httpService.post(`/console/as/conversations/${agentId}`, body);
17295
+ if (!apiResponse.data) {
17296
+ this.logger?.info(`Updated conversation status: ${agentId} to "${status}"`);
17297
+ return { id: agentId };
17298
+ }
17299
+ this.logger?.info(`Updated conversation status: ${apiResponse.data.id} to "${status}"`);
17300
+ return apiResponse.data;
17301
+ } catch (error) {
17302
+ this.logger?.error(`Failed to update conversation status ${agentId}:`, error);
17303
+ throw error;
17304
+ }
17305
+ }
17306
+ /**
17119
17307
  * Get available models from product configuration
17120
17308
  *
17121
17309
  * GET {endpoint}/console/enterprises/{personal|enterpriseId}/models?repos[]={repo}
@@ -17146,9 +17334,12 @@ var CloudAgentProvider = class CloudAgentProvider {
17146
17334
  this.logger?.warn("[CloudAgentProvider] No data in config response, returning empty models");
17147
17335
  return [];
17148
17336
  }
17149
- const models = apiResponse.data.models ?? [];
17150
- this.logger?.info(`[CloudAgentProvider] Retrieved ${models.length} models from /console/enterprises API`);
17151
- return models.map((model) => ({
17337
+ const productConfig = apiResponse.data;
17338
+ const allModels = productConfig.models ?? [];
17339
+ const cliModelIds = (productConfig.agents ?? []).find((agent) => agent.name === "cli")?.models ?? [];
17340
+ const filteredModels = cliModelIds.length > 0 ? allModels.filter((model) => cliModelIds.includes(model.id)) : allModels;
17341
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${filteredModels.length} models for cli agent (total: ${allModels.length})`);
17342
+ return filteredModels.map((model) => ({
17152
17343
  id: model.id,
17153
17344
  name: model.name ?? model.id,
17154
17345
  description: model.description,
@@ -17252,7 +17443,7 @@ var CloudAgentProvider = class CloudAgentProvider {
17252
17443
  /**
17253
17444
  * Upload files to cloud storage via COS presigned URL
17254
17445
  *
17255
- * @param params - files array (File objects in browser)
17446
+ * @param params - files array (File objects in browser), optional abortSignal
17256
17447
  * @returns Response with corresponding cloud URLs
17257
17448
  */
17258
17449
  async uploadFile(params) {
@@ -17262,12 +17453,13 @@ var CloudAgentProvider = class CloudAgentProvider {
17262
17453
  success: false,
17263
17454
  error: "No valid File objects provided"
17264
17455
  };
17265
- const result = await this.cosUploadService.uploadFiles(files);
17456
+ const result = await this.cosUploadService.uploadFiles(files, params.abortSignal);
17266
17457
  return {
17267
17458
  success: result.success,
17268
17459
  urls: result.urls,
17269
17460
  expireSeconds: result.expireSeconds,
17270
- error: result.error
17461
+ error: result.error,
17462
+ aborted: result.aborted
17271
17463
  };
17272
17464
  }
17273
17465
  /**
@@ -17309,20 +17501,22 @@ var CloudAgentProvider = class CloudAgentProvider {
17309
17501
  }
17310
17502
  /**
17311
17503
  * 获取支持的场景列表
17312
- * API 端点: GET /console/as/support/scenes
17504
+ * API 端点: GET /v2/as/support/scenes (不鉴权)
17313
17505
  * 用于 Welcome 页面的 QuickActions 快捷操作
17314
17506
  *
17507
+ * @param locale - 可选,语言环境(如 'zh-CN', 'en-US'),用于获取对应语言的场景数据
17315
17508
  * @returns Promise<SupportScene[]> 支持的场景列表
17316
17509
  */
17317
- async getSupportScenes() {
17510
+ async getSupportScenes(locale) {
17318
17511
  try {
17319
- const apiResponse = await httpService.get("/console/as/support/scenes");
17512
+ const url = this.buildGetUrl("/v2/as/support/scenes", locale ? { locale } : void 0);
17513
+ const apiResponse = await httpService.get(url);
17320
17514
  if (!apiResponse.data) {
17321
17515
  this.logger?.warn("[CloudAgentProvider] No data in support scenes response");
17322
17516
  return [];
17323
17517
  }
17324
17518
  const scenes = apiResponse.data.scenes || [];
17325
- this.logger?.info(`[CloudAgentProvider] Retrieved ${scenes.length} support scenes`);
17519
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${scenes.length} support scenes${locale ? ` for locale: ${locale}` : ""}`);
17326
17520
  return scenes;
17327
17521
  } catch (error) {
17328
17522
  this.logger?.error("[CloudAgentProvider] Failed to get support scenes:", error);
@@ -17338,7 +17532,8 @@ var CloudAgentProvider = class CloudAgentProvider {
17338
17532
  type: "cloud",
17339
17533
  status,
17340
17534
  createdAt: data.createdAt ? new Date(data.createdAt) : void 0,
17341
- capabilities: this.options.clientCapabilities
17535
+ capabilities: this.options.clientCapabilities,
17536
+ isUserDefinedTitle: data.isUserDefinedTitle
17342
17537
  };
17343
17538
  }
17344
17539
  /**
@@ -17354,6 +17549,37 @@ var CloudAgentProvider = class CloudAgentProvider {
17354
17549
  const queryString = searchParams.toString();
17355
17550
  return queryString ? `${path}?${queryString}` : path;
17356
17551
  }
17552
+ /**
17553
+ * 上报 telemetry 事件(Cloud 模式)
17554
+ * 通过 HTTP POST 发送到 /v2/report
17555
+ * 注入用户信息和浏览器环境等公共字段
17556
+ */
17557
+ async reportTelemetry(eventName, payload) {
17558
+ try {
17559
+ const account = accountService.getAccount();
17560
+ const commonFields = {};
17561
+ if (account) {
17562
+ commonFields.userId = account.uid;
17563
+ commonFields.userNickname = account.nickname;
17564
+ if (account.enterpriseId) commonFields.enterpriseId = account.enterpriseId;
17565
+ if (account.enterpriseUserName) commonFields.username = account.enterpriseUserName;
17566
+ }
17567
+ if (typeof navigator !== "undefined") {
17568
+ commonFields.userAgent = navigator.userAgent;
17569
+ commonFields.os = navigator.platform;
17570
+ }
17571
+ const events = [{
17572
+ eventCode: eventName,
17573
+ timestamp: Date.now(),
17574
+ reportDelay: 0,
17575
+ ...commonFields,
17576
+ ...payload
17577
+ }];
17578
+ await httpService.post("/v2/report", events);
17579
+ } catch (error) {
17580
+ this.logger?.warn("reportTelemetry() failed:", error);
17581
+ }
17582
+ }
17357
17583
  };
17358
17584
 
17359
17585
  //#endregion
@@ -17402,6 +17628,7 @@ var ActiveSessionImpl = class {
17402
17628
  this._availableCommands = [];
17403
17629
  this.listeners = /* @__PURE__ */ new Map();
17404
17630
  this.onceListeners = /* @__PURE__ */ new Map();
17631
+ this.connectionListeners = [];
17405
17632
  this._id = sessionId;
17406
17633
  this._agentId = agentId;
17407
17634
  this.connection = connection;
@@ -17427,6 +17654,18 @@ var ActiveSessionImpl = class {
17427
17654
  return this._agentId;
17428
17655
  }
17429
17656
  /**
17657
+ * Actual workspace path (set from newSession response _meta)
17658
+ */
17659
+ get cwd() {
17660
+ return this._cwd;
17661
+ }
17662
+ /**
17663
+ * Set actual workspace path (called by SessionManager after createSession)
17664
+ */
17665
+ setCwd(cwd) {
17666
+ this._cwd = cwd;
17667
+ }
17668
+ /**
17430
17669
  * Agent state (live connection state)
17431
17670
  * Returns LocalAgentState or CloudAgentState based on transport type
17432
17671
  */
@@ -17643,8 +17882,8 @@ var ActiveSessionImpl = class {
17643
17882
  * await session.setMode('architect');
17644
17883
  * ```
17645
17884
  */
17646
- async setMode(modeId) {
17647
- if (this._availableModes) {
17885
+ async setMode(modeId, skipAvailableChecker) {
17886
+ if (this._availableModes && !skipAvailableChecker) {
17648
17887
  if (!this._availableModes.some((m) => m.id === modeId)) {
17649
17888
  const availableIds = this._availableModes.map((m) => m.id).join(", ");
17650
17889
  throw new Error(`Invalid modeId: "${modeId}". Available modes: ${availableIds}`);
@@ -17744,6 +17983,7 @@ var ActiveSessionImpl = class {
17744
17983
  * Disconnect from the session/agent
17745
17984
  */
17746
17985
  disconnect() {
17986
+ this.removeConnectionListeners();
17747
17987
  this.connection.disconnect();
17748
17988
  this.removeAllListeners();
17749
17989
  this.logger?.info(`Session ${this._id}: Disconnected`);
@@ -17767,60 +18007,80 @@ var ActiveSessionImpl = class {
17767
18007
  if (!this.connection.isInitialized) throw new Error(`Session ${this._id}: Connection not initialized.`);
17768
18008
  return this.connection;
17769
18009
  }
18010
+ /**
18011
+ * 在 connection 上注册 listener 并保存引用,便于 disconnect 时移除
18012
+ */
18013
+ addConnectionListener(connection, event, listener) {
18014
+ connection.on(event, listener);
18015
+ this.connectionListeners.push({
18016
+ event,
18017
+ listener
18018
+ });
18019
+ }
18020
+ /**
18021
+ * 从 connection 上移除所有本 session 注册的 listener
18022
+ */
18023
+ removeConnectionListeners() {
18024
+ for (const { event, listener } of this.connectionListeners) this.connection.off(event, listener);
18025
+ this.connectionListeners = [];
18026
+ }
17770
18027
  setupConnectionEvents(connection) {
17771
- connection.on("connected", () => {
18028
+ this.addConnectionListener(connection, "connected", () => {
17772
18029
  this.emit("connected", void 0);
17773
18030
  });
17774
- connection.on("disconnected", () => {
18031
+ this.addConnectionListener(connection, "disconnected", () => {
17775
18032
  this.emit("disconnected", void 0);
17776
18033
  });
17777
- connection.on("error", (error) => {
18034
+ this.addConnectionListener(connection, "error", (error) => {
17778
18035
  this.emit("error", error);
17779
18036
  });
17780
- connection.on("sessionUpdate", (update) => {
18037
+ this.addConnectionListener(connection, "sessionUpdate", (update) => {
17781
18038
  this.emit("sessionUpdate", update);
17782
18039
  });
17783
- connection.on("artifactCreated", (artifact) => {
17784
- console.log("[Session] Forwarding artifactCreated:", {
17785
- artifactUri: artifact.uri,
17786
- artifactType: artifact.type
17787
- });
18040
+ this.addConnectionListener(connection, "artifactCreated", (artifact) => {
18041
+ if (!this.shouldForwardArtifact(artifact)) return;
17788
18042
  this.emit("artifactCreated", artifact);
17789
18043
  });
17790
- connection.on("artifactUpdated", (artifact) => {
17791
- console.log("[Session] Forwarding artifactUpdated:", {
17792
- artifactUri: artifact.uri,
17793
- artifactType: artifact.type
17794
- });
18044
+ this.addConnectionListener(connection, "artifactUpdated", (artifact) => {
18045
+ if (!this.shouldForwardArtifact(artifact)) return;
17795
18046
  this.emit("artifactUpdated", artifact);
17796
18047
  });
17797
- connection.on("artifactDeleted", (artifact) => {
17798
- console.log("[Session] Forwarding artifactDeleted:", { artifactUri: artifact.uri });
18048
+ this.addConnectionListener(connection, "artifactDeleted", (artifact) => {
18049
+ if (!this.shouldForwardArtifact(artifact)) return;
17799
18050
  this.emit("artifactDeleted", artifact);
17800
18051
  });
17801
- connection.on("permissionRequest", (request) => {
18052
+ this.addConnectionListener(connection, "permissionRequest", (request) => {
17802
18053
  this.emit("permissionRequest", request);
17803
18054
  });
17804
- connection.on("questionRequest", (request) => {
18055
+ this.addConnectionListener(connection, "questionRequest", (request) => {
17805
18056
  this.emit("questionRequest", request);
17806
18057
  });
17807
- connection.on("questionCancelled", () => {
18058
+ this.addConnectionListener(connection, "questionCancelled", () => {
17808
18059
  this.prompts.cancel();
17809
18060
  });
17810
- connection.on("usageUpdate", (usage) => {
18061
+ this.addConnectionListener(connection, "usageUpdate", (usage) => {
17811
18062
  this.emit("usageUpdate", usage);
17812
18063
  });
17813
- connection.on("checkpointCreated", (checkpoint) => {
18064
+ this.addConnectionListener(connection, "checkpointCreated", (checkpoint) => {
18065
+ const originSessionId = checkpoint.__sessionId;
18066
+ if (originSessionId && originSessionId !== this._id) return;
17814
18067
  this.emit("checkpointCreated", checkpoint);
17815
18068
  });
17816
- connection.on("checkpointUpdated", (checkpoint) => {
18069
+ this.addConnectionListener(connection, "checkpointUpdated", (checkpoint) => {
18070
+ const originSessionId = checkpoint.__sessionId;
18071
+ if (originSessionId && originSessionId !== this._id) return;
17817
18072
  this.emit("checkpointUpdated", checkpoint);
17818
18073
  });
17819
- connection.on("command", (command) => {
17820
- console.log("[Session] Forwarding command:", {
17821
- action: command.action,
17822
- paramsKeys: command.params ? Object.keys(command.params) : []
17823
- });
18074
+ this.addConnectionListener(connection, "command", (command) => {
18075
+ const originSessionId = command.__sessionId;
18076
+ if (originSessionId && originSessionId !== this._id) {
18077
+ console.log("[Session] Command not forwarded:", {
18078
+ command,
18079
+ originSessionId,
18080
+ sessionId: this._id
18081
+ });
18082
+ return;
18083
+ }
17824
18084
  this.emit("command", command);
17825
18085
  });
17826
18086
  }
@@ -17830,19 +18090,38 @@ var ActiveSessionImpl = class {
17830
18090
  _meta: response._meta ?? void 0
17831
18091
  };
17832
18092
  }
18093
+ /**
18094
+ * 判断 artifact 是否应该转发给当前 session
18095
+ * - media 类型:按 cwd 路径隔离(同 cwd 下不同 session 可共享 media)
18096
+ * - 其余类型:按 __sessionId 严格隔离
18097
+ */
18098
+ shouldForwardArtifact(artifact) {
18099
+ const originSessionId = artifact.__sessionId;
18100
+ console.log("[Session] shouldForwardArtifact:", {
18101
+ artifact,
18102
+ originSessionId,
18103
+ sessionId: this._id,
18104
+ cwd: this.connection?.cwd
18105
+ });
18106
+ if (artifact.type === "media") {
18107
+ const cwd = this.connection?.cwd;
18108
+ const uri = artifact?.uri;
18109
+ if (cwd && uri) {
18110
+ const toPosix = (p) => p.replace(/\\/g, "/");
18111
+ const uriPath = toPosix(uri.replace(/^(?:file|agent):\/\//, ""));
18112
+ const posixCwd = toPosix(cwd);
18113
+ const normalizedCwd = posixCwd.endsWith("/") ? posixCwd : posixCwd + "/";
18114
+ return uriPath.startsWith(normalizedCwd);
18115
+ }
18116
+ }
18117
+ if (originSessionId && originSessionId !== this._id) return false;
18118
+ return true;
18119
+ }
17833
18120
  };
17834
18121
 
17835
18122
  //#endregion
17836
18123
  //#region ../agent-provider/src/common/client/session-manager.ts
17837
18124
  /**
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
18125
  * SessionManager - Session lifecycle management
17847
18126
  *
17848
18127
  * This class manages the relationship between sessions and agents.
@@ -17892,7 +18171,8 @@ var SessionManager = class {
17892
18171
  createdAt: agent.createdAt,
17893
18172
  lastActivityAt: agent.updatedAt,
17894
18173
  cwd: agent.type === "local" ? agent.cwd : void 0,
17895
- isPlayground: agent.isPlayground
18174
+ isPlayground: agent.isPlayground,
18175
+ isUserDefinedTitle: agent.isUserDefinedTitle
17896
18176
  }));
17897
18177
  console.log("[SessionManager] Returning sessions:", {
17898
18178
  count: sessions.length,
@@ -17919,13 +18199,26 @@ var SessionManager = class {
17919
18199
  if (this.provider.create) {
17920
18200
  agentId = await this.provider.create(params);
17921
18201
  this.logger?.debug(`Created new agent: ${agentId}`);
18202
+ if (params.options?.onSessionPrepared) {
18203
+ const initialPrompt = params.options?.prompt;
18204
+ const initialTitle = initialPrompt?.slice(0, 50) || "";
18205
+ params.options.onSessionPrepared({
18206
+ id: agentId,
18207
+ agentId,
18208
+ name: initialTitle + (initialPrompt && initialPrompt.length > 50 ? "..." : ""),
18209
+ status: "connecting",
18210
+ cwd: params.cwd || "",
18211
+ createdAt: /* @__PURE__ */ new Date()
18212
+ });
18213
+ this.logger?.debug(`Called onSessionPrepared for: ${agentId}`);
18214
+ }
17922
18215
  } else throw new Error("Provider does not support creating agents. Use sessions.load() with an existing sessionId.");
17923
18216
  const connection = await this.provider.connect(agentId);
17924
18217
  this.logger?.debug(`Connected to agent: ${agentId}`);
17925
18218
  const response = await connection.createSession({
17926
- _meta: params._meta,
18219
+ _meta: params.options?._meta,
17927
18220
  cwd: params.cwd,
17928
- mcpServers: params.mcpServers
18221
+ mcpServers: params.options?.mcpServers
17929
18222
  });
17930
18223
  if (this.provider.registerSession) {
17931
18224
  this.provider.registerSession(response.sessionId, agentId);
@@ -17938,14 +18231,10 @@ var SessionManager = class {
17938
18231
  connectionInfo
17939
18232
  });
17940
18233
  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
- }
18234
+ const availableModels = this.extractAvailableModels(response);
18235
+ if (availableModels) session.setModels(availableModels, response.models?.currentModelId);
18236
+ const responseCwd = response._meta?.["codebuddy.ai"]?.cwd;
18237
+ if (responseCwd) session.setCwd(responseCwd);
17949
18238
  this.logger?.info(`Session created: ${response.sessionId}`);
17950
18239
  return session;
17951
18240
  }
@@ -17981,17 +18270,31 @@ var SessionManager = class {
17981
18270
  mcpServers: params.mcpServers
17982
18271
  });
17983
18272
  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
- }
18273
+ const availableModels = this.extractAvailableModels(response);
18274
+ if (availableModels) session.setModels(availableModels, response.models?.currentModelId);
17992
18275
  this.logger?.info(`Session loaded: ${params.sessionId}`);
17993
18276
  return session;
17994
18277
  }
18278
+ /**
18279
+ * 从 ACP response 中提取可用模型列表
18280
+ *
18281
+ * 优先级:
18282
+ * 1. response.models._meta?.['codebuddy.ai']?.availableModels - 包含完整的模型信息(字段名为 'id')
18283
+ * 2. response.models?.availableModels - 只包含基本信息(字段名为 'modelId')
18284
+ * 3. undefined - 都没有时返回 undefined
18285
+ *
18286
+ * @param response - ACP 响应对象
18287
+ * @returns ModelInfo[] | undefined
18288
+ */
18289
+ extractAvailableModels(response) {
18290
+ const metaModels = (response.models?._meta?.["codebuddy.ai"])?.availableModels;
18291
+ if (metaModels && Array.isArray(metaModels) && metaModels.length > 0) return metaModels;
18292
+ const availableModels = response.models?.availableModels;
18293
+ if (availableModels && Array.isArray(availableModels) && availableModels.length > 0) return availableModels.map((model) => ({
18294
+ ...model,
18295
+ ...model._meta?.["codebuddy.ai"] || {}
18296
+ }));
18297
+ }
17995
18298
  };
17996
18299
 
17997
18300
  //#endregion
@@ -18098,6 +18401,26 @@ var AgentClient = class {
18098
18401
  throw error;
18099
18402
  }
18100
18403
  },
18404
+ updateStatus: async (sessionId, status) => {
18405
+ this.logger?.debug("AgentClient.sessions.updateStatus called", {
18406
+ sessionId,
18407
+ status
18408
+ });
18409
+ try {
18410
+ if (this.provider.updateStatus) {
18411
+ const result = await this.provider.updateStatus(sessionId, status);
18412
+ this.logger?.info("Session status updated successfully", {
18413
+ sessionId,
18414
+ status
18415
+ });
18416
+ return result;
18417
+ }
18418
+ throw new Error("Provider does not support updateStatus method");
18419
+ } catch (error) {
18420
+ this.logger?.error("Failed to update session status", error);
18421
+ throw error;
18422
+ }
18423
+ },
18101
18424
  move: async (sessionId) => {
18102
18425
  this.logger?.debug("AgentClient.sessions.move called", { sessionId });
18103
18426
  try {
@@ -18264,6 +18587,72 @@ var AgentClient = class {
18264
18587
  };
18265
18588
  }
18266
18589
  },
18590
+ getSubagentList: async (params) => {
18591
+ try {
18592
+ if (this.provider && this.provider.getSubagentList) {
18593
+ const result = await this.provider.getSubagentList(params);
18594
+ this.logger?.info("Subagent list retrieved", {
18595
+ resultCount: result.results.length,
18596
+ hasError: !!result.error
18597
+ });
18598
+ return result;
18599
+ }
18600
+ return {
18601
+ results: [],
18602
+ error: "Provider does not support getSubagentList"
18603
+ };
18604
+ } catch (error) {
18605
+ this.logger?.error("Failed to get subagent list", error);
18606
+ return {
18607
+ results: [],
18608
+ error: error instanceof Error ? error.message : "Unknown error"
18609
+ };
18610
+ }
18611
+ },
18612
+ getSkillList: async (params) => {
18613
+ try {
18614
+ if (this.provider && this.provider.getSkillList) {
18615
+ const result = await this.provider.getSkillList(params);
18616
+ this.logger?.info("Skill list retrieved", {
18617
+ resultCount: result.results.length,
18618
+ hasError: !!result.error
18619
+ });
18620
+ return result;
18621
+ }
18622
+ return {
18623
+ results: [],
18624
+ error: "Provider does not support getSkillList"
18625
+ };
18626
+ } catch (error) {
18627
+ this.logger?.error("Failed to get skill list", error);
18628
+ return {
18629
+ results: [],
18630
+ error: error instanceof Error ? error.message : "Unknown error"
18631
+ };
18632
+ }
18633
+ },
18634
+ importSkill: async (params) => {
18635
+ try {
18636
+ if (this.provider && this.provider.importSkill) {
18637
+ const result = await this.provider.importSkill(params);
18638
+ this.logger?.info("Import skill completed", {
18639
+ success: result.success,
18640
+ hasError: !!result.error
18641
+ });
18642
+ return result;
18643
+ }
18644
+ return {
18645
+ success: false,
18646
+ error: "Provider does not support importSkill"
18647
+ };
18648
+ } catch (error) {
18649
+ this.logger?.error("Failed to import skill", error);
18650
+ return {
18651
+ success: false,
18652
+ error: error instanceof Error ? error.message : "Unknown error"
18653
+ };
18654
+ }
18655
+ },
18267
18656
  batchTogglePlugins: async (request) => {
18268
18657
  try {
18269
18658
  if (this.provider && this.provider.batchTogglePlugins) {
@@ -18308,10 +18697,10 @@ var AgentClient = class {
18308
18697
  return [];
18309
18698
  }
18310
18699
  },
18311
- installPlugins: async (pluginNames, marketplaceName, installScope) => {
18700
+ installPlugins: async (pluginNames, marketplaceName, installScope, marketplaceSource, workspacePath) => {
18312
18701
  try {
18313
18702
  if (this.provider && "installPlugins" in this.provider && typeof this.provider.installPlugins === "function") {
18314
- const result = await this.provider.installPlugins(pluginNames, marketplaceName, installScope);
18703
+ const result = await this.provider.installPlugins(pluginNames, marketplaceName, installScope, marketplaceSource, workspacePath);
18315
18704
  this.logger?.info("Install plugins", {
18316
18705
  pluginNames,
18317
18706
  marketplaceName,
@@ -18332,13 +18721,9 @@ var AgentClient = class {
18332
18721
  };
18333
18722
  }
18334
18723
  },
18335
- getSupportScenes: async () => {
18724
+ getSupportScenes: async (locale) => {
18336
18725
  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 });
18340
- return result;
18341
- }
18726
+ if (this.provider && "getSupportScenes" in this.provider && typeof this.provider.getSupportScenes === "function") return await this.provider.getSupportScenes(locale);
18342
18727
  this.logger?.warn("Provider does not support getSupportScenes");
18343
18728
  return [];
18344
18729
  } catch (error) {
@@ -18346,6 +18731,91 @@ var AgentClient = class {
18346
18731
  return [];
18347
18732
  }
18348
18733
  },
18734
+ getProductScenes: async (locale) => {
18735
+ try {
18736
+ if (this.provider?.getProductScenes) {
18737
+ const result = await this.provider.getProductScenes(locale);
18738
+ this.logger?.info("Got product scenes", { count: result?.length ?? 0 });
18739
+ return result;
18740
+ }
18741
+ this.logger?.warn("Provider does not support getProductScenes");
18742
+ return [];
18743
+ } catch (error) {
18744
+ this.logger?.error("Failed to get product scenes", error);
18745
+ return [];
18746
+ }
18747
+ },
18748
+ getAvailableCommands: async (params) => {
18749
+ try {
18750
+ if (this.provider && "getAvailableCommands" in this.provider && typeof this.provider.getAvailableCommands === "function") {
18751
+ const result = await this.provider.getAvailableCommands(params);
18752
+ this.logger?.info("Got available commands from provider", {
18753
+ sessionId: params?.sessionId ?? "(default)",
18754
+ count: result?.length ?? 0
18755
+ });
18756
+ return result;
18757
+ }
18758
+ this.logger?.warn("Provider does not support getAvailableCommands", { params });
18759
+ return [];
18760
+ } catch (error) {
18761
+ this.logger?.error("Failed to get available commands", error);
18762
+ return [];
18763
+ }
18764
+ },
18765
+ reportTelemetry: async (eventName, payload) => {
18766
+ try {
18767
+ if (this.provider?.reportTelemetry) await this.provider.reportTelemetry(eventName, payload);
18768
+ else this.logger?.warn("Provider does not support reportTelemetry");
18769
+ } catch (error) {
18770
+ this.logger?.error("Failed to report telemetry", error);
18771
+ }
18772
+ },
18773
+ respondToSampling: async (sessionId, response) => {
18774
+ try {
18775
+ if (this.provider?.respondToSampling) {
18776
+ await this.provider.respondToSampling(sessionId, response);
18777
+ this.logger?.info("Responded to sampling request", {
18778
+ sessionId,
18779
+ requestId: response.id,
18780
+ approved: response.approved
18781
+ });
18782
+ } else this.logger?.warn("Provider does not support respondToSampling");
18783
+ } catch (error) {
18784
+ this.logger?.error("Failed to respond to sampling request", error);
18785
+ throw error;
18786
+ }
18787
+ },
18788
+ respondToRoots: async (sessionId, response) => {
18789
+ try {
18790
+ if (this.provider?.respondToRoots) {
18791
+ await this.provider.respondToRoots(sessionId, response);
18792
+ this.logger?.info("Responded to roots request", {
18793
+ sessionId,
18794
+ requestId: response.id,
18795
+ approved: response.approved
18796
+ });
18797
+ } else this.logger?.warn("Provider does not support respondToRoots");
18798
+ } catch (error) {
18799
+ this.logger?.error("Failed to respond to roots request", error);
18800
+ throw error;
18801
+ }
18802
+ },
18803
+ subscribeSamplingRequests: (serverName, callback) => {
18804
+ if (this.provider?.subscribeSamplingRequests) {
18805
+ this.logger?.info("Subscribing to sampling requests", { serverName });
18806
+ return this.provider.subscribeSamplingRequests(serverName, callback);
18807
+ }
18808
+ this.logger?.warn("Provider does not support subscribeSamplingRequests");
18809
+ return () => {};
18810
+ },
18811
+ subscribeRootsRequests: (serverName, callback) => {
18812
+ if (this.provider?.subscribeRootsRequests) {
18813
+ this.logger?.info("Subscribing to roots requests", { serverName });
18814
+ return this.provider.subscribeRootsRequests(serverName, callback);
18815
+ }
18816
+ this.logger?.warn("Provider does not support subscribeRootsRequests");
18817
+ return () => {};
18818
+ },
18349
18819
  models: this.createModelsResource()
18350
18820
  };
18351
18821
  }
@@ -18412,6 +18882,154 @@ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
18412
18882
  return AccountStatus;
18413
18883
  }({});
18414
18884
 
18885
+ //#endregion
18886
+ //#region ../agent-provider/src/backend/service/oauth-repository-service.ts
18887
+ /**
18888
+ * OAuth Repository Service
18889
+ *
18890
+ * 封装 OAuth 连接器相关的仓库和分支操作
18891
+ */
18892
+ /**
18893
+ * OAuth Repository Service
18894
+ *
18895
+ * 提供仓库和分支的查询操作
18896
+ */
18897
+ var OAuthRepositoryService = class {
18898
+ /**
18899
+ * 获取仓库分支列表
18900
+ * API 端点: GET /console/as/connector/oauth/{name}/branches
18901
+ *
18902
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
18903
+ * @param params 平台特定的查询参数
18904
+ * @param page 页码,从1开始,0表示不分页获取全部
18905
+ * @param perPage 每页数量,最大100
18906
+ * @returns Promise<OauthBranch[]> 分支列表
18907
+ *
18908
+ * @example
18909
+ * ```typescript
18910
+ * // GitHub
18911
+ * const branches = await service.getBranches('github', {
18912
+ * owner: 'CodeBuddy-Official-Account',
18913
+ * repo: 'CodeBuddyIDE'
18914
+ * });
18915
+ *
18916
+ * // Gongfeng
18917
+ * const branches = await service.getBranches('gongfeng', {
18918
+ * project_id: '1611499'
18919
+ * });
18920
+ *
18921
+ * // CNB
18922
+ * const branches = await service.getBranches('cnb', {
18923
+ * repo: 'genie/genie-ide'
18924
+ * });
18925
+ * ```
18926
+ */
18927
+ async getBranches(connector, params, page = 0, perPage = 100) {
18928
+ try {
18929
+ const url = `/console/as/connector/oauth/${connector}/branches?${this.buildBranchQueryParams(connector, params, page, perPage).toString()}`;
18930
+ console.log(`[OAuthRepositoryService] GET ${url}`);
18931
+ const apiResponse = await httpService.get(url);
18932
+ if (!apiResponse.data) {
18933
+ console.warn(`[OAuthRepositoryService] No data in branches response for ${connector}`);
18934
+ return [];
18935
+ }
18936
+ const branches = apiResponse.data.branches || [];
18937
+ console.log(`[OAuthRepositoryService] Retrieved ${branches.length} branches from ${connector}`);
18938
+ return branches;
18939
+ } catch (error) {
18940
+ console.error(`[OAuthRepositoryService] Failed to get branches from ${connector}:`, error);
18941
+ throw error;
18942
+ }
18943
+ }
18944
+ /**
18945
+ * 获取仓库列表
18946
+ * API 端点: GET /console/as/connector/oauth/{name}/repos
18947
+ *
18948
+ * Note: 由于工蜂原生支持的 Search 能力会匹配 path/name/description 部分,
18949
+ * 且不支持定制,不满足产品要求(只按 name 匹配),因此前端拉取全量数据后做筛选。
18950
+ *
18951
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
18952
+ * @param page 页码,从1开始,0表示不分页获取全部
18953
+ * - GitHub 只支持全量数据,必须传 0
18954
+ * - 工蜂和 CNB 依据前端逻辑而定
18955
+ * @param perPage 每页数量,最大100
18956
+ * @returns Promise<ListReposResponse> 仓库列表响应
18957
+ *
18958
+ * @example
18959
+ * ```typescript
18960
+ * // GitHub - 必须传 page=0 获取全量数据
18961
+ * const response = await service.getRepositories('github', 0, 100);
18962
+ * // response.github_repos 是 map: installation_id => repo[]
18963
+ *
18964
+ * // Gongfeng
18965
+ * const response = await service.getRepositories('gongfeng', 0, 100);
18966
+ * // response.gongfeng_repos 是数组
18967
+ *
18968
+ * // CNB
18969
+ * const response = await service.getRepositories('cnb', 0, 100);
18970
+ * // response.cnb_repos 是数组
18971
+ * ```
18972
+ */
18973
+ async getRepositories(connector, page = 0, perPage = 100) {
18974
+ try {
18975
+ const queryParams = new URLSearchParams();
18976
+ queryParams.append("page", String(page));
18977
+ queryParams.append("per_page", String(Math.min(perPage, 100)));
18978
+ const url = `/console/as/connector/oauth/${connector}/repos?${queryParams.toString()}`;
18979
+ console.log(`[OAuthRepositoryService] GET ${url}`);
18980
+ const apiResponse = await httpService.get(url);
18981
+ if (!apiResponse.data) {
18982
+ console.warn(`[OAuthRepositoryService] No data in repos response for ${connector}`);
18983
+ return {};
18984
+ }
18985
+ const response = apiResponse.data;
18986
+ this.logRepositoryCounts(response);
18987
+ return response;
18988
+ } catch (error) {
18989
+ console.error(`[OAuthRepositoryService] Failed to get repos from ${connector}:`, error);
18990
+ throw error;
18991
+ }
18992
+ }
18993
+ /**
18994
+ * 构建分支查询参数
18995
+ */
18996
+ buildBranchQueryParams(connector, params, page, perPage) {
18997
+ const queryParams = new URLSearchParams();
18998
+ queryParams.append("page", String(page));
18999
+ queryParams.append("per_page", String(Math.min(perPage, 100)));
19000
+ if (connector === "github") {
19001
+ const githubParams = params;
19002
+ if (!githubParams.owner || !githubParams.repo) throw new Error("GitHub requires owner and repo parameters");
19003
+ queryParams.append("owner", githubParams.owner);
19004
+ queryParams.append("repo", githubParams.repo);
19005
+ } else if (connector === "gongfeng") {
19006
+ const gongfengParams = params;
19007
+ if (!gongfengParams.project_id) throw new Error("Gongfeng requires project_id parameter");
19008
+ queryParams.append("project_id", gongfengParams.project_id);
19009
+ } else if (connector === "cnb") {
19010
+ const cnbParams = params;
19011
+ if (!cnbParams.repo) throw new Error("CNB requires repo parameter");
19012
+ queryParams.append("repo", cnbParams.repo);
19013
+ } else throw new Error(`Unknown connector: ${connector}`);
19014
+ return queryParams;
19015
+ }
19016
+ /**
19017
+ * 记录仓库数量日志
19018
+ */
19019
+ logRepositoryCounts(response) {
19020
+ if (response.github_repos) {
19021
+ const totalCount = Object.values(response.github_repos).reduce((sum, repos) => sum + repos.length, 0);
19022
+ console.log(`[OAuthRepositoryService] Retrieved ${totalCount} GitHub repos across ${Object.keys(response.github_repos).length} installations`);
19023
+ }
19024
+ if (response.gongfeng_repos) console.log(`[OAuthRepositoryService] Retrieved ${response.gongfeng_repos.length} Gongfeng repos`);
19025
+ if (response.cnb_repos) console.log(`[OAuthRepositoryService] Retrieved ${response.cnb_repos.length} CNB repos`);
19026
+ }
19027
+ };
19028
+ /**
19029
+ * OAuth Repository Service 单例实例
19030
+ */
19031
+ const oauthRepositoryService = new OAuthRepositoryService();
19032
+
18415
19033
  //#endregion
18416
19034
  //#region ../agent-provider/src/backend/backend-provider.ts
18417
19035
  /**
@@ -18420,28 +19038,24 @@ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
18420
19038
  * 封装与后端 API 的 HTTP 通信
18421
19039
  */
18422
19040
  /**
18423
- * 判断当前是否在 SSO 域名下
18424
- * SSO 域名格式: xxx.sso.copilot.tencent.com xxx.sso.codebuddy.cn
19041
+ * 判断当前账号是否是 SSO 账号
19042
+ * 通过 account.accountType === 'sso' 来判断,这种不行,因为未登录之前account 为空
18425
19043
  */
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");
19044
+ const safeParseJSON = (jsonString) => {
19045
+ try {
19046
+ return JSON.parse(jsonString);
19047
+ } catch (error) {
19048
+ return {};
19049
+ }
18429
19050
  };
18430
19051
  /**
18431
- * 获取登录页面 URL
18432
- * - SSO 域名下需要跳转到对应的预发/生产域名
18433
- * - 非 SSO 域名直接使用当前域名
19052
+ * 根据路径获取完整 URL
19053
+ * - SSO 账号需要跳转到对应的预发/生产域名
19054
+ * - 非 SSO 账号直接使用当前域名
19055
+ * @param path 路径,如 '/login'、'/logout'、'/home' 等
19056
+ * @returns 完整的 URL 地址
18434
19057
  */
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
- };
19058
+ const getFullUrl = (path) => `${window.location.origin}${path}`;
18445
19059
  /** 获取当前域名的账号选择页面 URL */
18446
19060
  const getSelectAccountUrl = () => `${window.location.origin}/login/select`;
18447
19061
  /** localStorage 中存储选中账号 ID 的 key */
@@ -18478,12 +19092,30 @@ var BackendProvider = class {
18478
19092
  constructor(config) {
18479
19093
  httpService.setBaseURL(config.baseUrl);
18480
19094
  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);
19095
+ httpService.onUnauthorized(() => this.handleUnauthorized());
19096
+ }
19097
+ /**
19098
+ * 处理 401 未授权错误
19099
+ * 先尝试刷新 token,失败后再执行登出流程
19100
+ *
19101
+ * @throws 如果 token 刷新失败,抛出错误通知 HttpService 不要重试
19102
+ */
19103
+ async handleUnauthorized() {
19104
+ console.log("[BackendProvider] User unauthorized (401), attempting token refresh first");
19105
+ try {
19106
+ if (await this.refreshToken()) {
19107
+ console.log("[BackendProvider] Token refresh successful after 401, user still logged in");
19108
+ return;
19109
+ }
19110
+ throw new Error("Token refresh returned null");
19111
+ } catch (error) {
19112
+ console.error("[BackendProvider] Token refresh failed after 401:", error);
19113
+ console.log("[BackendProvider] Token refresh failed, triggering logout");
19114
+ this.logout().catch((logoutError) => {
19115
+ console.error("[BackendProvider] Logout failed in 401 handler:", logoutError);
18485
19116
  });
18486
- });
19117
+ throw error;
19118
+ }
18487
19119
  }
18488
19120
  /**
18489
19121
  * 获取当前账号信息
@@ -18528,7 +19160,7 @@ var BackendProvider = class {
18528
19160
  return account;
18529
19161
  }
18530
19162
  const redirectUrl = encodeURIComponent(window.location.href);
18531
- window.location.href = `${getSelectAccountUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
19163
+ window.location.href = `${getSelectAccountUrl()}?platform=agents&state=0&redirect_uri=${redirectUrl}`;
18532
19164
  accountService.setAccount(null);
18533
19165
  return null;
18534
19166
  } catch (error) {
@@ -18551,7 +19183,8 @@ var BackendProvider = class {
18551
19183
  activeStatus: connector.active_status,
18552
19184
  displayName: connector.display_name,
18553
19185
  oauthClientId: connector.oauth_client_id,
18554
- oauthRedirectUrl: connector.oauth_redirect_url
19186
+ oauthRedirectUrl: connector.oauth_redirect_url,
19187
+ oauthAppName: connector.oauth_app_name
18555
19188
  })) };
18556
19189
  }
18557
19190
  throw result;
@@ -18633,7 +19266,8 @@ var BackendProvider = class {
18633
19266
  connectStatus: connector.connect_status,
18634
19267
  displayName: connector.display_name,
18635
19268
  oauthClientId: connector.oauth_client_id,
18636
- oauthRedirectUrl: connector.oauth_redirect_url
19269
+ oauthRedirectUrl: connector.oauth_redirect_url,
19270
+ oauthAppName: connector.oauth_app_name
18637
19271
  })) };
18638
19272
  }
18639
19273
  throw result;
@@ -18724,6 +19358,9 @@ var BackendProvider = class {
18724
19358
  PackageCode: void 0,
18725
19359
  name: ""
18726
19360
  };
19361
+ const productFeatures = typeof window !== "undefined" && window.PRODUCT_FEATURES ? safeParseJSON(window.PRODUCT_FEATURES) : {};
19362
+ console.log("[PRODUCT_FEATURES]", productFeatures);
19363
+ if (!productFeatures.billing) return defaultPlan;
18727
19364
  try {
18728
19365
  const now = /* @__PURE__ */ new Date();
18729
19366
  const futureDate = new Date(now.getTime() + 101 * 365 * 24 * 60 * 60 * 1e3);
@@ -18745,7 +19382,7 @@ var BackendProvider = class {
18745
19382
  if (!time) return 0;
18746
19383
  return new Date(time).getTime();
18747
19384
  };
18748
- const dailyCredits = [CommodityCode.free, CommodityCode.freeMon];
19385
+ const dailyCredits = [CommodityCode.free];
18749
19386
  const planResources = resources.map((r) => {
18750
19387
  const isDaily = dailyCredits.includes(r.PackageCode);
18751
19388
  const endTime = isDaily ? r.CycleEndTime : r.DeductionEndTime;
@@ -18766,10 +19403,11 @@ var BackendProvider = class {
18766
19403
  CommodityCode.proMon,
18767
19404
  CommodityCode.proMonPlus,
18768
19405
  CommodityCode.proYear,
19406
+ CommodityCode.freeMon,
18769
19407
  CommodityCode.extra
18770
19408
  ].includes(code)) return 1;
18771
19409
  if ([CommodityCode.gift, CommodityCode.activity].includes(code)) return 2;
18772
- if ([CommodityCode.free, CommodityCode.freeMon].includes(code)) return 3;
19410
+ if ([CommodityCode.free].includes(code)) return 3;
18773
19411
  return 4;
18774
19412
  };
18775
19413
  return getPriority(a.packageCode) - getPriority(b.packageCode);
@@ -18890,38 +19528,69 @@ var BackendProvider = class {
18890
19528
  */
18891
19529
  async login() {
18892
19530
  const redirectUrl = encodeURIComponent(window.location.href);
18893
- window.location.href = `${getLoginUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
19531
+ window.location.href = `${getFullUrl("/login")}?platform=agents&state=0&redirect_uri=${redirectUrl}`;
18894
19532
  }
18895
19533
  /**
18896
19534
  * 登出账号
18897
- * Web 环境: 通过 iframe 访问登出 URL 清除 cookie
19535
+ *
19536
+ * 策略:
19537
+ * - IOA 企业:用 iframe 走 SSO/SAML SLO 登出链路(涉及跨域重定向),通过轮询 iframe URL 变化检测完成
19538
+ * - 非 IOA 企业:直接用 httpService 请求 /console/logout,速度快
18898
19539
  */
18899
19540
  async logout() {
18900
- const url = `${httpService.getAxiosInstance().defaults.baseURL}/console/logout`;
19541
+ const account = accountService.getAccount();
19542
+ if (account?.enterpriseId && ["esoikz80kd8g", "etahzsqej0n4"].includes(account.enterpriseId)) await this.logoutViaIframe();
19543
+ else await this.logoutViaHttp();
19544
+ localStorage.removeItem(SELECTED_ACCOUNT_KEY);
19545
+ accountService.clearAccount();
19546
+ }
19547
+ /**
19548
+ * IOA 企业登出:通过 iframe 走 SSO/SAML SLO 登出链路
19549
+ * 轮询 iframe URL 变化检测完成,兜底超时 5 秒
19550
+ */
19551
+ async logoutViaIframe() {
19552
+ const logoutUrl = `${httpService.getAxiosInstance().defaults.baseURL}/console/logout`;
18901
19553
  try {
18902
19554
  await new Promise((resolve) => {
18903
19555
  const iframe = document.createElement("iframe");
18904
19556
  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 = () => {
19557
+ iframe.src = logoutUrl;
19558
+ let pollTimer;
19559
+ let settled = false;
19560
+ const done = () => {
19561
+ if (settled) return;
19562
+ settled = true;
19563
+ clearInterval(pollTimer);
18911
19564
  clearTimeout(timeout);
18912
19565
  if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
18913
- };
18914
- iframe.onerror = () => {
18915
- cleanup();
18916
19566
  resolve();
18917
19567
  };
19568
+ let wasRedirecting = false;
19569
+ pollTimer = setInterval(() => {
19570
+ try {
19571
+ const href = iframe.contentWindow?.location?.href;
19572
+ if (wasRedirecting && href) done();
19573
+ } catch {
19574
+ wasRedirecting = true;
19575
+ }
19576
+ }, 100);
19577
+ const timeout = setTimeout(done, 5e3);
19578
+ iframe.onerror = done;
18918
19579
  document.body.appendChild(iframe);
18919
19580
  });
18920
19581
  } catch (error) {
18921
- console.error("[BackendProvider] logout failed:", error);
19582
+ console.error("[BackendProvider] logout via iframe failed:", error);
19583
+ }
19584
+ }
19585
+ /**
19586
+ * 非 IOA 企业登出:直接 HTTP 请求 /console/logout
19587
+ */
19588
+ async logoutViaHttp() {
19589
+ try {
19590
+ await httpService.get("/console/logout");
19591
+ } catch (error) {
19592
+ console.error("[BackendProvider] logout via http failed:", error);
18922
19593
  }
18923
- localStorage.removeItem(SELECTED_ACCOUNT_KEY);
18924
- accountService.clearAccount();
18925
19594
  }
18926
19595
  /**
18927
19596
  * 批量切换插件状态
@@ -18953,6 +19622,52 @@ var BackendProvider = class {
18953
19622
  return null;
18954
19623
  }
18955
19624
  }
19625
+ /**
19626
+ * 刷新 Token
19627
+ * 通过调用 getAccount 刷新 cookie,适用于 Cloud 场景下页面切换回来时刷新登录态
19628
+ * @returns Promise<Account | null> 刷新后的账号信息
19629
+ */
19630
+ async refreshToken() {
19631
+ console.log("[BackendProvider] Refreshing token...");
19632
+ try {
19633
+ const account = await this.getAccount();
19634
+ console.log("[BackendProvider] Token refreshed, account:", account?.uid);
19635
+ return account;
19636
+ } catch (error) {
19637
+ console.error("[BackendProvider] refreshToken failed:", error);
19638
+ return null;
19639
+ }
19640
+ }
19641
+ /**
19642
+ * 获取仓库分支列表
19643
+ * API 端点: GET /console/as/connector/oauth/{name}/branches
19644
+ *
19645
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
19646
+ * @param params 平台特定的查询参数
19647
+ * @param page 页码,从1开始,0表示不分页获取全部
19648
+ * @param perPage 每页数量,最大100
19649
+ * @returns Promise<OauthBranch[]> 分支列表
19650
+ */
19651
+ async getBranches(connector, params, page = 0, perPage = 100) {
19652
+ return oauthRepositoryService.getBranches(connector, params, page, perPage);
19653
+ }
19654
+ /**
19655
+ * 获取仓库列表
19656
+ * API 端点: GET /console/as/connector/oauth/{name}/repos
19657
+ *
19658
+ * Note: 由于工蜂原生支持的 Search 能力会匹配 path/name/description 部分,
19659
+ * 且不支持定制,不满足产品要求(只按 name 匹配),因此前端拉取全量数据后做筛选。
19660
+ *
19661
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
19662
+ * @param page 页码,从1开始,0表示不分页获取全部
19663
+ * - GitHub 只支持全量数据,必须传 0
19664
+ * - 工蜂和 CNB 依据前端逻辑而定
19665
+ * @param perPage 每页数量,最大100
19666
+ * @returns Promise<ListReposResponse> 仓库列表响应
19667
+ */
19668
+ async getRepositories(connector, page = 0, perPage = 100) {
19669
+ return oauthRepositoryService.getRepositories(connector, page, perPage);
19670
+ }
18956
19671
  };
18957
19672
  /**
18958
19673
  * 创建 BackendProvider 实例
@@ -18992,6 +19707,7 @@ const BACKEND_REQUEST_TYPES = {
18992
19707
  GET_FILE: "backend:get-file",
18993
19708
  RELOAD_WINDOW: "backend:reload-window",
18994
19709
  CLOSE_AGENT_MANAGER: "backend:close-agent-manager",
19710
+ OPEN_EXTERNAL: "backend:open-external",
18995
19711
  BATCH_TOGGLE_PLUGINS: "backend:batch-toggle-plugins",
18996
19712
  GET_SUPPORT_SCENES: "backend:get-support-scenes"
18997
19713
  };
@@ -19287,6 +20003,20 @@ var IPCBackendProvider = class {
19287
20003
  }
19288
20004
  }
19289
20005
  /**
20006
+ * 在外部浏览器中打开链接
20007
+ * IDE 环境: 通过 IPC 通知 IDE 使用 vscode.env.openExternal 打开 URL
20008
+ * @param url 要打开的 URL
20009
+ */
20010
+ async openExternal(url) {
20011
+ this.log("Opening external URL via IPC:", url);
20012
+ try {
20013
+ await this.sendBackendRequest(BACKEND_REQUEST_TYPES.OPEN_EXTERNAL, { url });
20014
+ } catch (error) {
20015
+ this.log("Open external request failed:", error);
20016
+ throw error;
20017
+ }
20018
+ }
20019
+ /**
19290
20020
  * 批量切换插件状态
19291
20021
  * IDE 环境: 通过 IPC 调用 Extension Host 的 PluginService
19292
20022
  */