@tencent-ai/cloud-agent-sdk 0.2.12 → 0.2.13-next.2bf8b02.20260209

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.mjs CHANGED
@@ -661,7 +661,8 @@ const ExtensionMethod = {
661
661
  CHECKPOINT: "_codebuddy.ai/checkpoint",
662
662
  USAGE: "_codebuddy.ai/usage",
663
663
  COMMAND: "_codebuddy.ai/command",
664
- AUTH_URL: "_codebuddy.ai/authUrl"
664
+ AUTH_URL: "_codebuddy.ai/authUrl",
665
+ FILE_HISTORY_SNAPSHOT: "_codebuddy.ai/file_history_snapshot"
665
666
  };
666
667
  /**
667
668
  * All known extension methods
@@ -672,7 +673,8 @@ const KNOWN_EXTENSIONS = [
672
673
  ExtensionMethod.CHECKPOINT,
673
674
  ExtensionMethod.USAGE,
674
675
  ExtensionMethod.COMMAND,
675
- ExtensionMethod.AUTH_URL
676
+ ExtensionMethod.AUTH_URL,
677
+ ExtensionMethod.FILE_HISTORY_SNAPSHOT
676
678
  ];
677
679
 
678
680
  //#endregion
@@ -2244,6 +2246,24 @@ var CloudAgentConnection = class {
2244
2246
  },
2245
2247
  onUsageUpdate: (usage) => {
2246
2248
  this.emit("usageUpdate", usage);
2249
+ },
2250
+ onExtNotification: (method, params) => {
2251
+ console.log("[CloudConnection] Received extNotification:", {
2252
+ method,
2253
+ paramsKeys: Object.keys(params)
2254
+ });
2255
+ if (method === ExtensionMethod.COMMAND) {
2256
+ const action = params.action;
2257
+ const commandParams = params.params;
2258
+ console.log("[CloudConnection] Emitting command event:", {
2259
+ action,
2260
+ paramsKeys: commandParams ? Object.keys(commandParams) : []
2261
+ });
2262
+ this.emit("command", {
2263
+ action,
2264
+ params: commandParams
2265
+ });
2266
+ }
2247
2267
  }
2248
2268
  });
2249
2269
  this.setupEventForwarding();
@@ -2372,7 +2392,7 @@ var CloudAgentConnection = class {
2372
2392
  }
2373
2393
  async createSession(params) {
2374
2394
  return {
2375
- ...await this.client.loadSession(this.agentId, this.cwd),
2395
+ ...await this.client.createSession(this.cwd),
2376
2396
  sessionId: this.agentId
2377
2397
  };
2378
2398
  }
@@ -2490,6 +2510,16 @@ var CloudAgentConnection = class {
2490
2510
  get sessionConnectionInfo() {
2491
2511
  return this._sessionConnectionInfo;
2492
2512
  }
2513
+ async reportTelemetry(eventName, payload) {
2514
+ try {
2515
+ await this.client.extMethod("reportTelemetry", {
2516
+ eventName,
2517
+ payload
2518
+ });
2519
+ } catch (error) {
2520
+ console.warn("[CloudAgentConnection] reportTelemetry failed:", error);
2521
+ }
2522
+ }
2493
2523
  async extMethod(method, params) {
2494
2524
  return this.client.extMethod(method, params);
2495
2525
  }
@@ -5253,7 +5283,7 @@ var axios_default = axios;
5253
5283
  * 特性:
5254
5284
  * - 单例模式,全局唯一实例,延迟初始化(首次使用时自动创建)
5255
5285
  * - 支持拦截器注册(其他模块可注入 header)
5256
- * - 统一 401/403 处理
5286
+ * - 统一 401 处理(支持自动刷新 token 并重试)
5257
5287
  * - 自动携带凭证(withCredentials)
5258
5288
  * - 类型安全
5259
5289
  *
@@ -5293,6 +5323,8 @@ var HttpService = class HttpService {
5293
5323
  */
5294
5324
  constructor(config = {}) {
5295
5325
  this.unauthorizedCallbacks = /* @__PURE__ */ new Set();
5326
+ this.isRefreshing = false;
5327
+ this.refreshSubscribers = [];
5296
5328
  this.config = config;
5297
5329
  this.axiosInstance = axios_default.create({
5298
5330
  baseURL: config.baseURL?.replace(/\/$/, "") || "",
@@ -5333,18 +5365,54 @@ var HttpService = class HttpService {
5333
5365
  }, (error) => Promise.reject(error));
5334
5366
  }
5335
5367
  /**
5336
- * 注册默认响应拦截器(处理 401/403)
5368
+ * 注册默认响应拦截器(处理 401,支持自动重试)
5337
5369
  */
5338
5370
  registerDefaultResponseInterceptor() {
5339
- this.axiosInstance.interceptors.response.use((response) => response, (error) => {
5340
- if (error.response?.status === 401 || error.response?.status === 403) {
5341
- console.warn("[HttpService] Unauthorized (401/403), triggering callbacks");
5342
- this.triggerUnauthorizedCallbacks();
5371
+ this.axiosInstance.interceptors.response.use((response) => response, async (error) => {
5372
+ const originalRequest = error.config;
5373
+ if (error.response?.status === 401 && originalRequest && !originalRequest._retry) {
5374
+ if (originalRequest.url?.includes("/console/accounts")) {
5375
+ console.warn("[HttpService] Unauthorized 401 on refresh endpoint, not retrying");
5376
+ return Promise.reject(error);
5377
+ }
5378
+ console.warn("[HttpService] Unauthorized 401, attempting token refresh and retry");
5379
+ originalRequest._retry = true;
5380
+ if (this.isRefreshing) return new Promise((resolve, reject) => {
5381
+ this.refreshSubscribers.push((success) => {
5382
+ if (success) this.axiosInstance.request(originalRequest).then(resolve).catch(reject);
5383
+ else reject(error);
5384
+ });
5385
+ });
5386
+ this.isRefreshing = true;
5387
+ try {
5388
+ await this.triggerUnauthorizedCallbacks();
5389
+ this.onRefreshSuccess();
5390
+ return this.axiosInstance.request(originalRequest);
5391
+ } catch (refreshError) {
5392
+ this.onRefreshFailure();
5393
+ return Promise.reject(error);
5394
+ } finally {
5395
+ this.isRefreshing = false;
5396
+ }
5343
5397
  }
5344
5398
  return Promise.reject(error);
5345
5399
  });
5346
5400
  }
5347
5401
  /**
5402
+ * token 刷新成功,通知所有等待的请求
5403
+ */
5404
+ onRefreshSuccess() {
5405
+ this.refreshSubscribers.forEach((callback) => callback(true));
5406
+ this.refreshSubscribers = [];
5407
+ }
5408
+ /**
5409
+ * token 刷新失败,通知所有等待的请求
5410
+ */
5411
+ onRefreshFailure() {
5412
+ this.refreshSubscribers.forEach((callback) => callback(false));
5413
+ this.refreshSubscribers = [];
5414
+ }
5415
+ /**
5348
5416
  * 注册请求拦截器
5349
5417
  * @param onFulfilled 请求成功拦截器
5350
5418
  * @param onRejected 请求失败拦截器
@@ -5421,16 +5489,18 @@ var HttpService = class HttpService {
5421
5489
  this.unauthorizedCallbacks.delete(callback);
5422
5490
  }
5423
5491
  /**
5424
- * 触发所有 401 回调
5492
+ * 触发所有 401 回调并等待完成
5493
+ * @returns Promise,等待所有回调完成
5494
+ * @throws 如果任何回调失败,则抛出错误
5425
5495
  */
5426
- triggerUnauthorizedCallbacks() {
5427
- this.unauthorizedCallbacks.forEach((callback) => {
5428
- try {
5429
- callback();
5430
- } catch (error) {
5431
- console.error("[HttpService] Error in unauthorized callback:", error);
5432
- }
5433
- });
5496
+ async triggerUnauthorizedCallbacks() {
5497
+ const callbacks = Array.from(this.unauthorizedCallbacks);
5498
+ const failedResults = (await Promise.allSettled(callbacks.map((callback) => callback()))).filter((r) => r.status === "rejected");
5499
+ if (failedResults.length > 0) {
5500
+ const errors = failedResults.map((r) => r.reason);
5501
+ console.error("[HttpService] Some unauthorized callbacks failed:", errors);
5502
+ throw errors[0];
5503
+ }
5434
5504
  }
5435
5505
  /**
5436
5506
  * 更新 authToken
@@ -5542,6 +5612,7 @@ var AccountService = class {
5542
5612
  this.initPromise = null;
5543
5613
  this.initResolve = null;
5544
5614
  this.requestInterceptorId = null;
5615
+ this.crossTabBroadcaster = null;
5545
5616
  this.initPromise = new Promise((resolve) => {
5546
5617
  this.initResolve = resolve;
5547
5618
  });
@@ -5590,15 +5661,34 @@ var AccountService = class {
5590
5661
  this.initialized = true;
5591
5662
  this.initResolve?.(account);
5592
5663
  }
5593
- if (!wasInitialized || prev?.uid !== account?.uid) this.notifyListeners();
5664
+ if (!wasInitialized || prev?.uid !== account?.uid) {
5665
+ this.notifyListeners();
5666
+ if (account && this.crossTabBroadcaster) this.crossTabBroadcaster.broadcastLogin();
5667
+ }
5594
5668
  }
5595
5669
  /**
5596
5670
  * 清除账号(登出)
5671
+ * 先广播登出消息,再清除本地账号
5597
5672
  */
5598
5673
  clearAccount() {
5674
+ if (this.crossTabBroadcaster) this.crossTabBroadcaster.broadcastLogout();
5675
+ this.setAccount(null);
5676
+ }
5677
+ /**
5678
+ * 静默清除账号(不广播)
5679
+ * 用于收到其他标签页 logout 消息时,避免循环广播
5680
+ */
5681
+ clearAccountSilently() {
5599
5682
  this.setAccount(null);
5600
5683
  }
5601
5684
  /**
5685
+ * 设置跨标签页认证同步广播器
5686
+ * 应在应用初始化时由上层(如 agent-ui)调用
5687
+ */
5688
+ setCrossTabBroadcaster(broadcaster) {
5689
+ this.crossTabBroadcaster = broadcaster;
5690
+ }
5691
+ /**
5602
5692
  * 订阅账号变化
5603
5693
  * @param callback 变化时的回调函数
5604
5694
  * @returns 取消订阅函数
@@ -5663,6 +5753,11 @@ var AccountService = class {
5663
5753
  * 导出单例实例
5664
5754
  */
5665
5755
  const accountService = new AccountService();
5756
+ /**
5757
+ * 暴露给全局,供 Agent Manager 直接调用 setAccount 刷新 Widget 状态
5758
+ * 这是为了解决 IDE 环境中 IPC 事件无法直接触发 Widget 账号刷新的问题
5759
+ */
5760
+ if (typeof window !== "undefined") window.__genieAccountService = accountService;
5666
5761
 
5667
5762
  //#endregion
5668
5763
  //#region ../agent-provider/src/common/utils/concurrency.ts
@@ -5765,20 +5860,32 @@ var CosUploadService = class {
5765
5860
  * 上传单个文件到 COS
5766
5861
  *
5767
5862
  * @param file - 要上传的文件
5863
+ * @param abortSignal - 可选的 AbortSignal,用于取消上传
5768
5864
  * @returns 上传结果,包含访问 URL 或错误信息
5769
5865
  */
5770
- async uploadFile(file) {
5866
+ async uploadFile(file, abortSignal) {
5771
5867
  const filename = file.name;
5772
5868
  this.logger?.info(`[CosUploadService] Uploading file: ${filename}`);
5773
5869
  try {
5870
+ if (abortSignal?.aborted) return {
5871
+ success: false,
5872
+ error: "Upload cancelled",
5873
+ aborted: true
5874
+ };
5774
5875
  const objectKey = this.generateObjectKey(filename);
5775
5876
  this.logger?.debug(`[CosUploadService] Generated objectKey: ${objectKey}`);
5776
5877
  const presignedItem = (await this.getPresignedUrls([objectKey])).items[0];
5777
5878
  if (!presignedItem) throw new Error("No presigned URL item returned");
5879
+ if (abortSignal?.aborted) return {
5880
+ success: false,
5881
+ error: "Upload cancelled",
5882
+ aborted: true
5883
+ };
5778
5884
  const uploadResponse = await fetch(presignedItem.upload_url, {
5779
5885
  method: "PUT",
5780
5886
  body: file,
5781
- headers: { "Content-Type": file.type || "application/octet-stream" }
5887
+ headers: { "Content-Type": file.type || "application/octet-stream" },
5888
+ signal: abortSignal
5782
5889
  });
5783
5890
  if (!uploadResponse.ok) {
5784
5891
  const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
@@ -5792,6 +5899,11 @@ var CosUploadService = class {
5792
5899
  objectKey
5793
5900
  };
5794
5901
  } catch (error) {
5902
+ if (error instanceof Error && error.name === "AbortError") return {
5903
+ success: false,
5904
+ error: "Upload cancelled",
5905
+ aborted: true
5906
+ };
5795
5907
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
5796
5908
  this.logger?.error(`[CosUploadService] Upload failed: ${filename}`, error);
5797
5909
  return {
@@ -5806,14 +5918,25 @@ var CosUploadService = class {
5806
5918
  * 使用并发控制,限制同时上传的文件数量
5807
5919
  *
5808
5920
  * @param files - 要上传的文件数组
5921
+ * @param abortSignal - 可选的 AbortSignal,用于取消上传
5809
5922
  * @returns 所有文件的上传结果
5810
5923
  */
5811
- async uploadFiles(files) {
5924
+ async uploadFiles(files, abortSignal) {
5812
5925
  if (files.length === 0) return {
5813
5926
  success: true,
5814
5927
  urls: [],
5815
5928
  results: []
5816
5929
  };
5930
+ if (abortSignal?.aborted) return {
5931
+ success: false,
5932
+ error: "Upload cancelled",
5933
+ aborted: true,
5934
+ results: files.map(() => ({
5935
+ success: false,
5936
+ error: "Upload cancelled",
5937
+ aborted: true
5938
+ }))
5939
+ };
5817
5940
  this.logger?.info(`[CosUploadService] Uploading ${files.length} file(s) with concurrency ${this.uploadConcurrency}`);
5818
5941
  try {
5819
5942
  const fileInfos = files.map((file) => ({
@@ -5825,6 +5948,12 @@ var CosUploadService = class {
5825
5948
  this.logger?.debug(`[CosUploadService] Got ${presignedResponse.items.length} presigned URLs`);
5826
5949
  if (presignedResponse.items.length !== fileInfos.length) throw new Error(`Expected ${fileInfos.length} presigned URLs, got ${presignedResponse.items.length}`);
5827
5950
  const results = (await runWithConcurrencySettled(fileInfos.map(({ file }, index) => async () => {
5951
+ if (abortSignal?.aborted) return {
5952
+ success: false,
5953
+ error: "Upload cancelled",
5954
+ aborted: true,
5955
+ objectKey: fileInfos[index].objectKey
5956
+ };
5828
5957
  const presignedItem = presignedResponse.items[index];
5829
5958
  if (!presignedItem) return {
5830
5959
  success: false,
@@ -5835,7 +5964,8 @@ var CosUploadService = class {
5835
5964
  const uploadResponse = await fetch(presignedItem.upload_url, {
5836
5965
  method: "PUT",
5837
5966
  body: file,
5838
- headers: { "Content-Type": file.type || "application/octet-stream" }
5967
+ headers: { "Content-Type": file.type || "application/octet-stream" },
5968
+ signal: abortSignal
5839
5969
  });
5840
5970
  if (!uploadResponse.ok) {
5841
5971
  const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
@@ -5852,6 +5982,12 @@ var CosUploadService = class {
5852
5982
  objectKey: presignedItem.object_key
5853
5983
  };
5854
5984
  } catch (error) {
5985
+ if (error instanceof Error && error.name === "AbortError") return {
5986
+ success: false,
5987
+ error: "Upload cancelled",
5988
+ aborted: true,
5989
+ objectKey: presignedItem.object_key
5990
+ };
5855
5991
  return {
5856
5992
  success: false,
5857
5993
  error: error instanceof Error ? error.message : "Unknown error",
@@ -6252,15 +6388,9 @@ var CloudAgentProvider = class CloudAgentProvider {
6252
6388
  const url = this.buildGetUrl("/console/as/conversations/", params);
6253
6389
  const apiResponse = await httpService.get(url);
6254
6390
  if (!apiResponse.data) throw new Error("No data in API response");
6255
- const agents = apiResponse.data.conversations.map((a) => this.toAgentState(a));
6256
- const pagination = apiResponse.data.pagination;
6257
- console.log("[CloudAgentProvider] API response:", {
6258
- agentsCount: agents.length,
6259
- pagination
6260
- });
6261
6391
  return {
6262
- agents,
6263
- pagination
6392
+ agents: apiResponse.data.conversations.map((a) => this.toAgentState(a)),
6393
+ pagination: apiResponse.data.pagination
6264
6394
  };
6265
6395
  } catch (error) {
6266
6396
  this.logger?.error("Failed to list agents:", error);
@@ -6270,13 +6400,21 @@ var CloudAgentProvider = class CloudAgentProvider {
6270
6400
  /**
6271
6401
  * Create a new conversation
6272
6402
  * POST {endpoint}/console/as/conversations
6403
+ * @param params - Session params containing cwd and optional configuration
6273
6404
  */
6274
- async create() {
6405
+ async create(params) {
6275
6406
  try {
6276
- const apiResponse = await httpService.post("/console/as/conversations/", {
6277
- prompt: "",
6278
- model: "deepseek-r1"
6279
- });
6407
+ const { options = {} } = params;
6408
+ const codebuddyMeta = options._meta?.["codebuddy.ai"];
6409
+ const tagsObj = options.tags || codebuddyMeta?.tags;
6410
+ const tagsArray = tagsObj ? Object.entries(tagsObj).map(([key, value]) => `${key}:${value}`) : void 0;
6411
+ const createPayload = {
6412
+ prompt: (options.prompt || "").slice(0, 100),
6413
+ model: options.model || "deepseek-r1",
6414
+ ...tagsArray && tagsArray.length > 0 ? { tags: tagsArray } : {}
6415
+ };
6416
+ console.log("[CloudAgentProvider] Creating conversation with payload:", createPayload);
6417
+ const apiResponse = await httpService.post("/console/as/conversations/", createPayload);
6280
6418
  if (!apiResponse.data) throw new Error("No data in API response");
6281
6419
  this.logger?.info(`Created conversation: ${apiResponse.data.id}`);
6282
6420
  return apiResponse.data.id;
@@ -6476,9 +6614,12 @@ var CloudAgentProvider = class CloudAgentProvider {
6476
6614
  this.logger?.warn("[CloudAgentProvider] No data in config response, returning empty models");
6477
6615
  return [];
6478
6616
  }
6479
- const models = apiResponse.data.models ?? [];
6480
- this.logger?.info(`[CloudAgentProvider] Retrieved ${models.length} models from /console/enterprises API`);
6481
- return models.map((model) => ({
6617
+ const productConfig = apiResponse.data;
6618
+ const allModels = productConfig.models ?? [];
6619
+ const cliModelIds = (productConfig.agents ?? []).find((agent) => agent.name === "cli")?.models ?? [];
6620
+ const filteredModels = cliModelIds.length > 0 ? allModels.filter((model) => cliModelIds.includes(model.id)) : allModels;
6621
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${filteredModels.length} models for cli agent (total: ${allModels.length})`);
6622
+ return filteredModels.map((model) => ({
6482
6623
  id: model.id,
6483
6624
  name: model.name ?? model.id,
6484
6625
  description: model.description,
@@ -6582,7 +6723,7 @@ var CloudAgentProvider = class CloudAgentProvider {
6582
6723
  /**
6583
6724
  * Upload files to cloud storage via COS presigned URL
6584
6725
  *
6585
- * @param params - files array (File objects in browser)
6726
+ * @param params - files array (File objects in browser), optional abortSignal
6586
6727
  * @returns Response with corresponding cloud URLs
6587
6728
  */
6588
6729
  async uploadFile(params) {
@@ -6592,12 +6733,13 @@ var CloudAgentProvider = class CloudAgentProvider {
6592
6733
  success: false,
6593
6734
  error: "No valid File objects provided"
6594
6735
  };
6595
- const result = await this.cosUploadService.uploadFiles(files);
6736
+ const result = await this.cosUploadService.uploadFiles(files, params.abortSignal);
6596
6737
  return {
6597
6738
  success: result.success,
6598
6739
  urls: result.urls,
6599
6740
  expireSeconds: result.expireSeconds,
6600
- error: result.error
6741
+ error: result.error,
6742
+ aborted: result.aborted
6601
6743
  };
6602
6744
  }
6603
6745
  /**
@@ -6639,20 +6781,22 @@ var CloudAgentProvider = class CloudAgentProvider {
6639
6781
  }
6640
6782
  /**
6641
6783
  * 获取支持的场景列表
6642
- * API 端点: GET /console/as/support/scenes
6784
+ * API 端点: GET /v2/as/support/scenes (不鉴权)
6643
6785
  * 用于 Welcome 页面的 QuickActions 快捷操作
6644
6786
  *
6787
+ * @param locale - 可选,语言环境(如 'zh-CN', 'en-US'),用于获取对应语言的场景数据
6645
6788
  * @returns Promise<SupportScene[]> 支持的场景列表
6646
6789
  */
6647
- async getSupportScenes() {
6790
+ async getSupportScenes(locale) {
6648
6791
  try {
6649
- const apiResponse = await httpService.get("/console/as/support/scenes");
6792
+ const url = this.buildGetUrl("/v2/as/support/scenes", locale ? { locale } : void 0);
6793
+ const apiResponse = await httpService.get(url);
6650
6794
  if (!apiResponse.data) {
6651
6795
  this.logger?.warn("[CloudAgentProvider] No data in support scenes response");
6652
6796
  return [];
6653
6797
  }
6654
6798
  const scenes = apiResponse.data.scenes || [];
6655
- this.logger?.info(`[CloudAgentProvider] Retrieved ${scenes.length} support scenes`);
6799
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${scenes.length} support scenes${locale ? ` for locale: ${locale}` : ""}`);
6656
6800
  return scenes;
6657
6801
  } catch (error) {
6658
6802
  this.logger?.error("[CloudAgentProvider] Failed to get support scenes:", error);
@@ -6668,7 +6812,8 @@ var CloudAgentProvider = class CloudAgentProvider {
6668
6812
  type: "cloud",
6669
6813
  status,
6670
6814
  createdAt: data.createdAt ? new Date(data.createdAt) : void 0,
6671
- capabilities: this.options.clientCapabilities
6815
+ capabilities: this.options.clientCapabilities,
6816
+ isUserDefinedTitle: data.isUserDefinedTitle
6672
6817
  };
6673
6818
  }
6674
6819
  /**
@@ -6684,6 +6829,23 @@ var CloudAgentProvider = class CloudAgentProvider {
6684
6829
  const queryString = searchParams.toString();
6685
6830
  return queryString ? `${path}?${queryString}` : path;
6686
6831
  }
6832
+ /**
6833
+ * 上报 telemetry 事件(Cloud 模式)
6834
+ * 通过 HTTP POST 发送到 /v2/report
6835
+ */
6836
+ async reportTelemetry(eventName, payload) {
6837
+ try {
6838
+ const events = [{
6839
+ eventCode: eventName,
6840
+ timestamp: Date.now(),
6841
+ reportDelay: 0,
6842
+ ...payload
6843
+ }];
6844
+ await httpService.post("/v2/report", events);
6845
+ } catch (error) {
6846
+ this.logger?.warn("reportTelemetry() failed:", error);
6847
+ }
6848
+ }
6687
6849
  };
6688
6850
 
6689
6851
  //#endregion
@@ -6722,6 +6884,7 @@ var ActiveSessionImpl = class {
6722
6884
  this._availableCommands = [];
6723
6885
  this.listeners = /* @__PURE__ */ new Map();
6724
6886
  this.onceListeners = /* @__PURE__ */ new Map();
6887
+ this.connectionListeners = [];
6725
6888
  this._id = sessionId;
6726
6889
  this._agentId = agentId;
6727
6890
  this.connection = connection;
@@ -6747,6 +6910,18 @@ var ActiveSessionImpl = class {
6747
6910
  return this._agentId;
6748
6911
  }
6749
6912
  /**
6913
+ * Actual workspace path (set from newSession response _meta)
6914
+ */
6915
+ get cwd() {
6916
+ return this._cwd;
6917
+ }
6918
+ /**
6919
+ * Set actual workspace path (called by SessionManager after createSession)
6920
+ */
6921
+ setCwd(cwd) {
6922
+ this._cwd = cwd;
6923
+ }
6924
+ /**
6750
6925
  * Agent state (live connection state)
6751
6926
  * Returns LocalAgentState or CloudAgentState based on transport type
6752
6927
  */
@@ -6963,8 +7138,8 @@ var ActiveSessionImpl = class {
6963
7138
  * await session.setMode('architect');
6964
7139
  * ```
6965
7140
  */
6966
- async setMode(modeId) {
6967
- if (this._availableModes) {
7141
+ async setMode(modeId, skipAvailableChecker) {
7142
+ if (this._availableModes && !skipAvailableChecker) {
6968
7143
  if (!this._availableModes.some((m) => m.id === modeId)) {
6969
7144
  const availableIds = this._availableModes.map((m) => m.id).join(", ");
6970
7145
  throw new Error(`Invalid modeId: "${modeId}". Available modes: ${availableIds}`);
@@ -7064,6 +7239,7 @@ var ActiveSessionImpl = class {
7064
7239
  * Disconnect from the session/agent
7065
7240
  */
7066
7241
  disconnect() {
7242
+ this.removeConnectionListeners();
7067
7243
  this.connection.disconnect();
7068
7244
  this.removeAllListeners();
7069
7245
  this.logger?.info(`Session ${this._id}: Disconnected`);
@@ -7087,60 +7263,80 @@ var ActiveSessionImpl = class {
7087
7263
  if (!this.connection.isInitialized) throw new Error(`Session ${this._id}: Connection not initialized.`);
7088
7264
  return this.connection;
7089
7265
  }
7266
+ /**
7267
+ * 在 connection 上注册 listener 并保存引用,便于 disconnect 时移除
7268
+ */
7269
+ addConnectionListener(connection, event, listener) {
7270
+ connection.on(event, listener);
7271
+ this.connectionListeners.push({
7272
+ event,
7273
+ listener
7274
+ });
7275
+ }
7276
+ /**
7277
+ * 从 connection 上移除所有本 session 注册的 listener
7278
+ */
7279
+ removeConnectionListeners() {
7280
+ for (const { event, listener } of this.connectionListeners) this.connection.off(event, listener);
7281
+ this.connectionListeners = [];
7282
+ }
7090
7283
  setupConnectionEvents(connection) {
7091
- connection.on("connected", () => {
7284
+ this.addConnectionListener(connection, "connected", () => {
7092
7285
  this.emit("connected", void 0);
7093
7286
  });
7094
- connection.on("disconnected", () => {
7287
+ this.addConnectionListener(connection, "disconnected", () => {
7095
7288
  this.emit("disconnected", void 0);
7096
7289
  });
7097
- connection.on("error", (error) => {
7290
+ this.addConnectionListener(connection, "error", (error) => {
7098
7291
  this.emit("error", error);
7099
7292
  });
7100
- connection.on("sessionUpdate", (update) => {
7293
+ this.addConnectionListener(connection, "sessionUpdate", (update) => {
7101
7294
  this.emit("sessionUpdate", update);
7102
7295
  });
7103
- connection.on("artifactCreated", (artifact) => {
7104
- console.log("[Session] Forwarding artifactCreated:", {
7105
- artifactUri: artifact.uri,
7106
- artifactType: artifact.type
7107
- });
7296
+ this.addConnectionListener(connection, "artifactCreated", (artifact) => {
7297
+ if (!this.shouldForwardArtifact(artifact)) return;
7108
7298
  this.emit("artifactCreated", artifact);
7109
7299
  });
7110
- connection.on("artifactUpdated", (artifact) => {
7111
- console.log("[Session] Forwarding artifactUpdated:", {
7112
- artifactUri: artifact.uri,
7113
- artifactType: artifact.type
7114
- });
7300
+ this.addConnectionListener(connection, "artifactUpdated", (artifact) => {
7301
+ if (!this.shouldForwardArtifact(artifact)) return;
7115
7302
  this.emit("artifactUpdated", artifact);
7116
7303
  });
7117
- connection.on("artifactDeleted", (artifact) => {
7118
- console.log("[Session] Forwarding artifactDeleted:", { artifactUri: artifact.uri });
7304
+ this.addConnectionListener(connection, "artifactDeleted", (artifact) => {
7305
+ if (!this.shouldForwardArtifact(artifact)) return;
7119
7306
  this.emit("artifactDeleted", artifact);
7120
7307
  });
7121
- connection.on("permissionRequest", (request) => {
7308
+ this.addConnectionListener(connection, "permissionRequest", (request) => {
7122
7309
  this.emit("permissionRequest", request);
7123
7310
  });
7124
- connection.on("questionRequest", (request) => {
7311
+ this.addConnectionListener(connection, "questionRequest", (request) => {
7125
7312
  this.emit("questionRequest", request);
7126
7313
  });
7127
- connection.on("questionCancelled", () => {
7314
+ this.addConnectionListener(connection, "questionCancelled", () => {
7128
7315
  this.prompts.cancel();
7129
7316
  });
7130
- connection.on("usageUpdate", (usage) => {
7317
+ this.addConnectionListener(connection, "usageUpdate", (usage) => {
7131
7318
  this.emit("usageUpdate", usage);
7132
7319
  });
7133
- connection.on("checkpointCreated", (checkpoint) => {
7320
+ this.addConnectionListener(connection, "checkpointCreated", (checkpoint) => {
7321
+ const originSessionId = checkpoint.__sessionId;
7322
+ if (originSessionId && originSessionId !== this._id) return;
7134
7323
  this.emit("checkpointCreated", checkpoint);
7135
7324
  });
7136
- connection.on("checkpointUpdated", (checkpoint) => {
7325
+ this.addConnectionListener(connection, "checkpointUpdated", (checkpoint) => {
7326
+ const originSessionId = checkpoint.__sessionId;
7327
+ if (originSessionId && originSessionId !== this._id) return;
7137
7328
  this.emit("checkpointUpdated", checkpoint);
7138
7329
  });
7139
- connection.on("command", (command) => {
7140
- console.log("[Session] Forwarding command:", {
7141
- action: command.action,
7142
- paramsKeys: command.params ? Object.keys(command.params) : []
7143
- });
7330
+ this.addConnectionListener(connection, "command", (command) => {
7331
+ const originSessionId = command.__sessionId;
7332
+ if (originSessionId && originSessionId !== this._id) {
7333
+ console.log("[Session] Command not forwarded:", {
7334
+ command,
7335
+ originSessionId,
7336
+ sessionId: this._id
7337
+ });
7338
+ return;
7339
+ }
7144
7340
  this.emit("command", command);
7145
7341
  });
7146
7342
  }
@@ -7150,19 +7346,38 @@ var ActiveSessionImpl = class {
7150
7346
  _meta: response._meta ?? void 0
7151
7347
  };
7152
7348
  }
7349
+ /**
7350
+ * 判断 artifact 是否应该转发给当前 session
7351
+ * - media 类型:按 cwd 路径隔离(同 cwd 下不同 session 可共享 media)
7352
+ * - 其余类型:按 __sessionId 严格隔离
7353
+ */
7354
+ shouldForwardArtifact(artifact) {
7355
+ const originSessionId = artifact.__sessionId;
7356
+ console.log("[Session] shouldForwardArtifact:", {
7357
+ artifact,
7358
+ originSessionId,
7359
+ sessionId: this._id,
7360
+ cwd: this.connection?.cwd
7361
+ });
7362
+ if (artifact.type === "media") {
7363
+ const cwd = this.connection?.cwd;
7364
+ const uri = artifact?.uri;
7365
+ if (cwd && uri) {
7366
+ const toPosix = (p) => p.replace(/\\/g, "/");
7367
+ const uriPath = toPosix(uri.replace(/^(?:file|agent):\/\//, ""));
7368
+ const posixCwd = toPosix(cwd);
7369
+ const normalizedCwd = posixCwd.endsWith("/") ? posixCwd : posixCwd + "/";
7370
+ return uriPath.startsWith(normalizedCwd);
7371
+ }
7372
+ }
7373
+ if (originSessionId && originSessionId !== this._id) return false;
7374
+ return true;
7375
+ }
7153
7376
  };
7154
7377
 
7155
7378
  //#endregion
7156
7379
  //#region ../agent-provider/src/common/client/session-manager.ts
7157
7380
  /**
7158
- * SessionManager - Manages session lifecycle and connections
7159
- *
7160
- * Provides the core implementation for session-centric API operations:
7161
- * - list() - Lists sessions (mapped from agents)
7162
- * - createSession() - Creates new session (auto-creates agent)
7163
- * - loadSession() - Loads existing session (finds agent by sessionId)
7164
- */
7165
- /**
7166
7381
  * SessionManager - Session lifecycle management
7167
7382
  *
7168
7383
  * This class manages the relationship between sessions and agents.
@@ -7212,7 +7427,8 @@ var SessionManager = class {
7212
7427
  createdAt: agent.createdAt,
7213
7428
  lastActivityAt: agent.updatedAt,
7214
7429
  cwd: agent.type === "local" ? agent.cwd : void 0,
7215
- isPlayground: agent.isPlayground
7430
+ isPlayground: agent.isPlayground,
7431
+ isUserDefinedTitle: agent.isUserDefinedTitle
7216
7432
  }));
7217
7433
  console.log("[SessionManager] Returning sessions:", {
7218
7434
  count: sessions.length,
@@ -7239,13 +7455,26 @@ var SessionManager = class {
7239
7455
  if (this.provider.create) {
7240
7456
  agentId = await this.provider.create(params);
7241
7457
  this.logger?.debug(`Created new agent: ${agentId}`);
7458
+ if (params.options?.onSessionPrepared) {
7459
+ const initialPrompt = params.options?.prompt;
7460
+ const initialTitle = initialPrompt?.slice(0, 50) || "";
7461
+ params.options.onSessionPrepared({
7462
+ id: agentId,
7463
+ agentId,
7464
+ name: initialTitle + (initialPrompt && initialPrompt.length > 50 ? "..." : ""),
7465
+ status: "connecting",
7466
+ cwd: params.cwd || "",
7467
+ createdAt: /* @__PURE__ */ new Date()
7468
+ });
7469
+ this.logger?.debug(`Called onSessionPrepared for: ${agentId}`);
7470
+ }
7242
7471
  } else throw new Error("Provider does not support creating agents. Use sessions.load() with an existing sessionId.");
7243
7472
  const connection = await this.provider.connect(agentId);
7244
7473
  this.logger?.debug(`Connected to agent: ${agentId}`);
7245
7474
  const response = await connection.createSession({
7246
- _meta: params._meta,
7475
+ _meta: params.options?._meta,
7247
7476
  cwd: params.cwd,
7248
- mcpServers: params.mcpServers
7477
+ mcpServers: params.options?.mcpServers
7249
7478
  });
7250
7479
  if (this.provider.registerSession) {
7251
7480
  this.provider.registerSession(response.sessionId, agentId);
@@ -7258,14 +7487,10 @@ var SessionManager = class {
7258
7487
  connectionInfo
7259
7488
  });
7260
7489
  session.setModes(response.modes?.availableModes, response.modes?.currentModeId);
7261
- if (response.models?.availableModels) {
7262
- const localModels = response.models.availableModels.map((m) => ({
7263
- id: m.modelId,
7264
- name: m.name,
7265
- description: m.description ?? void 0
7266
- }));
7267
- session.setModels(localModels, response.models?.currentModelId);
7268
- }
7490
+ const availableModels = this.extractAvailableModels(response);
7491
+ if (availableModels) session.setModels(availableModels, response.models?.currentModelId);
7492
+ const responseCwd = response._meta?.["codebuddy.ai"]?.cwd;
7493
+ if (responseCwd) session.setCwd(responseCwd);
7269
7494
  this.logger?.info(`Session created: ${response.sessionId}`);
7270
7495
  return session;
7271
7496
  }
@@ -7301,17 +7526,31 @@ var SessionManager = class {
7301
7526
  mcpServers: params.mcpServers
7302
7527
  });
7303
7528
  session.setModes(response.modes?.availableModes, response.modes?.currentModeId);
7304
- if (response.models?.availableModels) {
7305
- const localModels = response.models.availableModels.map((m) => ({
7306
- id: m.modelId,
7307
- name: m.name,
7308
- description: m.description ?? void 0
7309
- }));
7310
- session.setModels(localModels, response.models?.currentModelId);
7311
- }
7529
+ const availableModels = this.extractAvailableModels(response);
7530
+ if (availableModels) session.setModels(availableModels, response.models?.currentModelId);
7312
7531
  this.logger?.info(`Session loaded: ${params.sessionId}`);
7313
7532
  return session;
7314
7533
  }
7534
+ /**
7535
+ * 从 ACP response 中提取可用模型列表
7536
+ *
7537
+ * 优先级:
7538
+ * 1. response.models._meta?.['codebuddy.ai']?.availableModels - 包含完整的模型信息(字段名为 'id')
7539
+ * 2. response.models?.availableModels - 只包含基本信息(字段名为 'modelId')
7540
+ * 3. undefined - 都没有时返回 undefined
7541
+ *
7542
+ * @param response - ACP 响应对象
7543
+ * @returns ModelInfo[] | undefined
7544
+ */
7545
+ extractAvailableModels(response) {
7546
+ const metaModels = (response.models?._meta?.["codebuddy.ai"])?.availableModels;
7547
+ if (metaModels && Array.isArray(metaModels) && metaModels.length > 0) return metaModels;
7548
+ const availableModels = response.models?.availableModels;
7549
+ if (availableModels && Array.isArray(availableModels) && availableModels.length > 0) return availableModels.map((model) => ({
7550
+ ...model,
7551
+ ...model._meta?.["codebuddy.ai"] || {}
7552
+ }));
7553
+ }
7315
7554
  };
7316
7555
 
7317
7556
  //#endregion
@@ -7584,6 +7823,28 @@ var AgentClient = class {
7584
7823
  };
7585
7824
  }
7586
7825
  },
7826
+ getSubagentList: async (params) => {
7827
+ try {
7828
+ if (this.provider && this.provider.getSubagentList) {
7829
+ const result = await this.provider.getSubagentList(params);
7830
+ this.logger?.info("Subagent list retrieved", {
7831
+ resultCount: result.results.length,
7832
+ hasError: !!result.error
7833
+ });
7834
+ return result;
7835
+ }
7836
+ return {
7837
+ results: [],
7838
+ error: "Provider does not support getSubagentList"
7839
+ };
7840
+ } catch (error) {
7841
+ this.logger?.error("Failed to get subagent list", error);
7842
+ return {
7843
+ results: [],
7844
+ error: error instanceof Error ? error.message : "Unknown error"
7845
+ };
7846
+ }
7847
+ },
7587
7848
  batchTogglePlugins: async (request) => {
7588
7849
  try {
7589
7850
  if (this.provider && this.provider.batchTogglePlugins) {
@@ -7628,10 +7889,10 @@ var AgentClient = class {
7628
7889
  return [];
7629
7890
  }
7630
7891
  },
7631
- installPlugins: async (pluginNames, marketplaceName, installScope) => {
7892
+ installPlugins: async (pluginNames, marketplaceName, installScope, marketplaceSource, workspacePath) => {
7632
7893
  try {
7633
7894
  if (this.provider && "installPlugins" in this.provider && typeof this.provider.installPlugins === "function") {
7634
- const result = await this.provider.installPlugins(pluginNames, marketplaceName, installScope);
7895
+ const result = await this.provider.installPlugins(pluginNames, marketplaceName, installScope, marketplaceSource, workspacePath);
7635
7896
  this.logger?.info("Install plugins", {
7636
7897
  pluginNames,
7637
7898
  marketplaceName,
@@ -7652,13 +7913,9 @@ var AgentClient = class {
7652
7913
  };
7653
7914
  }
7654
7915
  },
7655
- getSupportScenes: async () => {
7916
+ getSupportScenes: async (locale) => {
7656
7917
  try {
7657
- if (this.provider && "getSupportScenes" in this.provider && typeof this.provider.getSupportScenes === "function") {
7658
- const result = await this.provider.getSupportScenes();
7659
- this.logger?.info("Got support scenes", { count: result?.length ?? 0 });
7660
- return result;
7661
- }
7918
+ if (this.provider && "getSupportScenes" in this.provider && typeof this.provider.getSupportScenes === "function") return await this.provider.getSupportScenes(locale);
7662
7919
  this.logger?.warn("Provider does not support getSupportScenes");
7663
7920
  return [];
7664
7921
  } catch (error) {
@@ -7666,6 +7923,94 @@ var AgentClient = class {
7666
7923
  return [];
7667
7924
  }
7668
7925
  },
7926
+ getProductScenes: async (locale) => {
7927
+ try {
7928
+ const hasMethod = this.provider && "getProductScenes" in this.provider;
7929
+ const isFunction = hasMethod && typeof this.provider.getProductScenes === "function";
7930
+ this.logger?.warn(`[getProductScenes] provider=${!!this.provider}, hasMethod=${hasMethod}, isFunction=${isFunction}, providerType=${this.provider?.constructor?.name}`);
7931
+ if (isFunction) {
7932
+ const result = await this.provider.getProductScenes(locale);
7933
+ this.logger?.warn(`[getProductScenes] got ${result?.length ?? 0} scenes`);
7934
+ return result;
7935
+ }
7936
+ this.logger?.warn("Provider does not support getProductScenes");
7937
+ return [];
7938
+ } catch (error) {
7939
+ this.logger?.error("Failed to get product scenes", error);
7940
+ return [];
7941
+ }
7942
+ },
7943
+ getAvailableCommands: async (params) => {
7944
+ try {
7945
+ if (this.provider && "getAvailableCommands" in this.provider && typeof this.provider.getAvailableCommands === "function") {
7946
+ const result = await this.provider.getAvailableCommands(params);
7947
+ this.logger?.info("Got available commands from provider", {
7948
+ sessionId: params?.sessionId ?? "(default)",
7949
+ count: result?.length ?? 0
7950
+ });
7951
+ return result;
7952
+ }
7953
+ this.logger?.warn("Provider does not support getAvailableCommands", { params });
7954
+ return [];
7955
+ } catch (error) {
7956
+ this.logger?.error("Failed to get available commands", error);
7957
+ return [];
7958
+ }
7959
+ },
7960
+ reportTelemetry: async (eventName, payload) => {
7961
+ try {
7962
+ if (this.provider?.reportTelemetry) await this.provider.reportTelemetry(eventName, payload);
7963
+ else this.logger?.warn("Provider does not support reportTelemetry");
7964
+ } catch (error) {
7965
+ this.logger?.error("Failed to report telemetry", error);
7966
+ }
7967
+ },
7968
+ respondToSampling: async (sessionId, response) => {
7969
+ try {
7970
+ if (this.provider?.respondToSampling) {
7971
+ await this.provider.respondToSampling(sessionId, response);
7972
+ this.logger?.info("Responded to sampling request", {
7973
+ sessionId,
7974
+ requestId: response.id,
7975
+ approved: response.approved
7976
+ });
7977
+ } else this.logger?.warn("Provider does not support respondToSampling");
7978
+ } catch (error) {
7979
+ this.logger?.error("Failed to respond to sampling request", error);
7980
+ throw error;
7981
+ }
7982
+ },
7983
+ respondToRoots: async (sessionId, response) => {
7984
+ try {
7985
+ if (this.provider?.respondToRoots) {
7986
+ await this.provider.respondToRoots(sessionId, response);
7987
+ this.logger?.info("Responded to roots request", {
7988
+ sessionId,
7989
+ requestId: response.id,
7990
+ approved: response.approved
7991
+ });
7992
+ } else this.logger?.warn("Provider does not support respondToRoots");
7993
+ } catch (error) {
7994
+ this.logger?.error("Failed to respond to roots request", error);
7995
+ throw error;
7996
+ }
7997
+ },
7998
+ subscribeSamplingRequests: (serverName, callback) => {
7999
+ if (this.provider?.subscribeSamplingRequests) {
8000
+ this.logger?.info("Subscribing to sampling requests", { serverName });
8001
+ return this.provider.subscribeSamplingRequests(serverName, callback);
8002
+ }
8003
+ this.logger?.warn("Provider does not support subscribeSamplingRequests");
8004
+ return () => {};
8005
+ },
8006
+ subscribeRootsRequests: (serverName, callback) => {
8007
+ if (this.provider?.subscribeRootsRequests) {
8008
+ this.logger?.info("Subscribing to roots requests", { serverName });
8009
+ return this.provider.subscribeRootsRequests(serverName, callback);
8010
+ }
8011
+ this.logger?.warn("Provider does not support subscribeRootsRequests");
8012
+ return () => {};
8013
+ },
7669
8014
  models: this.createModelsResource()
7670
8015
  };
7671
8016
  }
@@ -7732,6 +8077,154 @@ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
7732
8077
  return AccountStatus;
7733
8078
  }({});
7734
8079
 
8080
+ //#endregion
8081
+ //#region ../agent-provider/src/backend/service/oauth-repository-service.ts
8082
+ /**
8083
+ * OAuth Repository Service
8084
+ *
8085
+ * 封装 OAuth 连接器相关的仓库和分支操作
8086
+ */
8087
+ /**
8088
+ * OAuth Repository Service
8089
+ *
8090
+ * 提供仓库和分支的查询操作
8091
+ */
8092
+ var OAuthRepositoryService = class {
8093
+ /**
8094
+ * 获取仓库分支列表
8095
+ * API 端点: GET /console/as/connector/oauth/{name}/branches
8096
+ *
8097
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
8098
+ * @param params 平台特定的查询参数
8099
+ * @param page 页码,从1开始,0表示不分页获取全部
8100
+ * @param perPage 每页数量,最大100
8101
+ * @returns Promise<OauthBranch[]> 分支列表
8102
+ *
8103
+ * @example
8104
+ * ```typescript
8105
+ * // GitHub
8106
+ * const branches = await service.getBranches('github', {
8107
+ * owner: 'CodeBuddy-Official-Account',
8108
+ * repo: 'CodeBuddyIDE'
8109
+ * });
8110
+ *
8111
+ * // Gongfeng
8112
+ * const branches = await service.getBranches('gongfeng', {
8113
+ * project_id: '1611499'
8114
+ * });
8115
+ *
8116
+ * // CNB
8117
+ * const branches = await service.getBranches('cnb', {
8118
+ * repo: 'genie/genie-ide'
8119
+ * });
8120
+ * ```
8121
+ */
8122
+ async getBranches(connector, params, page = 0, perPage = 100) {
8123
+ try {
8124
+ const url = `/console/as/connector/oauth/${connector}/branches?${this.buildBranchQueryParams(connector, params, page, perPage).toString()}`;
8125
+ console.log(`[OAuthRepositoryService] GET ${url}`);
8126
+ const apiResponse = await httpService.get(url);
8127
+ if (!apiResponse.data) {
8128
+ console.warn(`[OAuthRepositoryService] No data in branches response for ${connector}`);
8129
+ return [];
8130
+ }
8131
+ const branches = apiResponse.data.branches || [];
8132
+ console.log(`[OAuthRepositoryService] Retrieved ${branches.length} branches from ${connector}`);
8133
+ return branches;
8134
+ } catch (error) {
8135
+ console.error(`[OAuthRepositoryService] Failed to get branches from ${connector}:`, error);
8136
+ throw error;
8137
+ }
8138
+ }
8139
+ /**
8140
+ * 获取仓库列表
8141
+ * API 端点: GET /console/as/connector/oauth/{name}/repos
8142
+ *
8143
+ * Note: 由于工蜂原生支持的 Search 能力会匹配 path/name/description 部分,
8144
+ * 且不支持定制,不满足产品要求(只按 name 匹配),因此前端拉取全量数据后做筛选。
8145
+ *
8146
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
8147
+ * @param page 页码,从1开始,0表示不分页获取全部
8148
+ * - GitHub 只支持全量数据,必须传 0
8149
+ * - 工蜂和 CNB 依据前端逻辑而定
8150
+ * @param perPage 每页数量,最大100
8151
+ * @returns Promise<ListReposResponse> 仓库列表响应
8152
+ *
8153
+ * @example
8154
+ * ```typescript
8155
+ * // GitHub - 必须传 page=0 获取全量数据
8156
+ * const response = await service.getRepositories('github', 0, 100);
8157
+ * // response.github_repos 是 map: installation_id => repo[]
8158
+ *
8159
+ * // Gongfeng
8160
+ * const response = await service.getRepositories('gongfeng', 0, 100);
8161
+ * // response.gongfeng_repos 是数组
8162
+ *
8163
+ * // CNB
8164
+ * const response = await service.getRepositories('cnb', 0, 100);
8165
+ * // response.cnb_repos 是数组
8166
+ * ```
8167
+ */
8168
+ async getRepositories(connector, page = 0, perPage = 100) {
8169
+ try {
8170
+ const queryParams = new URLSearchParams();
8171
+ queryParams.append("page", String(page));
8172
+ queryParams.append("per_page", String(Math.min(perPage, 100)));
8173
+ const url = `/console/as/connector/oauth/${connector}/repos?${queryParams.toString()}`;
8174
+ console.log(`[OAuthRepositoryService] GET ${url}`);
8175
+ const apiResponse = await httpService.get(url);
8176
+ if (!apiResponse.data) {
8177
+ console.warn(`[OAuthRepositoryService] No data in repos response for ${connector}`);
8178
+ return {};
8179
+ }
8180
+ const response = apiResponse.data;
8181
+ this.logRepositoryCounts(response);
8182
+ return response;
8183
+ } catch (error) {
8184
+ console.error(`[OAuthRepositoryService] Failed to get repos from ${connector}:`, error);
8185
+ throw error;
8186
+ }
8187
+ }
8188
+ /**
8189
+ * 构建分支查询参数
8190
+ */
8191
+ buildBranchQueryParams(connector, params, page, perPage) {
8192
+ const queryParams = new URLSearchParams();
8193
+ queryParams.append("page", String(page));
8194
+ queryParams.append("per_page", String(Math.min(perPage, 100)));
8195
+ if (connector === "github") {
8196
+ const githubParams = params;
8197
+ if (!githubParams.owner || !githubParams.repo) throw new Error("GitHub requires owner and repo parameters");
8198
+ queryParams.append("owner", githubParams.owner);
8199
+ queryParams.append("repo", githubParams.repo);
8200
+ } else if (connector === "gongfeng") {
8201
+ const gongfengParams = params;
8202
+ if (!gongfengParams.project_id) throw new Error("Gongfeng requires project_id parameter");
8203
+ queryParams.append("project_id", gongfengParams.project_id);
8204
+ } else if (connector === "cnb") {
8205
+ const cnbParams = params;
8206
+ if (!cnbParams.repo) throw new Error("CNB requires repo parameter");
8207
+ queryParams.append("repo", cnbParams.repo);
8208
+ } else throw new Error(`Unknown connector: ${connector}`);
8209
+ return queryParams;
8210
+ }
8211
+ /**
8212
+ * 记录仓库数量日志
8213
+ */
8214
+ logRepositoryCounts(response) {
8215
+ if (response.github_repos) {
8216
+ const totalCount = Object.values(response.github_repos).reduce((sum, repos) => sum + repos.length, 0);
8217
+ console.log(`[OAuthRepositoryService] Retrieved ${totalCount} GitHub repos across ${Object.keys(response.github_repos).length} installations`);
8218
+ }
8219
+ if (response.gongfeng_repos) console.log(`[OAuthRepositoryService] Retrieved ${response.gongfeng_repos.length} Gongfeng repos`);
8220
+ if (response.cnb_repos) console.log(`[OAuthRepositoryService] Retrieved ${response.cnb_repos.length} CNB repos`);
8221
+ }
8222
+ };
8223
+ /**
8224
+ * OAuth Repository Service 单例实例
8225
+ */
8226
+ const oauthRepositoryService = new OAuthRepositoryService();
8227
+
7735
8228
  //#endregion
7736
8229
  //#region ../agent-provider/src/backend/backend-provider.ts
7737
8230
  /**
@@ -7740,28 +8233,24 @@ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
7740
8233
  * 封装与后端 API 的 HTTP 通信
7741
8234
  */
7742
8235
  /**
7743
- * 判断当前是否在 SSO 域名下
7744
- * SSO 域名格式: xxx.sso.copilot.tencent.com xxx.sso.codebuddy.cn
8236
+ * 判断当前账号是否是 SSO 账号
8237
+ * 通过 account.accountType === 'sso' 来判断,这种不行,因为未登录之前account 为空
7745
8238
  */
7746
- const isSSODomain = () => {
7747
- const { hostname } = window.location;
7748
- return hostname.includes(".sso.copilot") || hostname.includes("sso.codebuddy.cn") || hostname.includes(".sso.copilot-staging") || hostname.includes(".staging-sso.codebuddy.cn");
8239
+ const safeParseJSON = (jsonString) => {
8240
+ try {
8241
+ return JSON.parse(jsonString);
8242
+ } catch (error) {
8243
+ return {};
8244
+ }
7749
8245
  };
7750
8246
  /**
7751
- * 获取登录页面 URL
7752
- * - SSO 域名下需要跳转到对应的预发/生产域名
7753
- * - 非 SSO 域名直接使用当前域名
8247
+ * 根据路径获取完整 URL
8248
+ * - SSO 账号需要跳转到对应的预发/生产域名
8249
+ * - 非 SSO 账号直接使用当前域名
8250
+ * @param path 路径,如 '/login'、'/logout'、'/home' 等
8251
+ * @returns 完整的 URL 地址
7754
8252
  */
7755
- const getLoginUrl = () => {
7756
- const { hostname, protocol } = window.location;
7757
- if (isSSODomain()) {
7758
- const isCodebuddy = hostname.includes("codebuddy.cn");
7759
- const isStaging = hostname.includes("staging");
7760
- if (isCodebuddy) return isStaging ? `${protocol}//staging.codebuddy.cn/login` : `${protocol}//www.codebuddy.cn/login`;
7761
- else return isStaging ? `${protocol}//staging-copilot.tencent.com/login` : `${protocol}//copilot.tencent.com/login`;
7762
- }
7763
- return `${window.location.origin}/login`;
7764
- };
8253
+ const getFullUrl = (path) => `${window.location.origin}${path}`;
7765
8254
  /** 获取当前域名的账号选择页面 URL */
7766
8255
  const getSelectAccountUrl = () => `${window.location.origin}/login/select`;
7767
8256
  /** localStorage 中存储选中账号 ID 的 key */
@@ -7798,12 +8287,30 @@ var BackendProvider = class {
7798
8287
  constructor(config) {
7799
8288
  httpService.setBaseURL(config.baseUrl);
7800
8289
  if (config.authToken) httpService.setAuthToken(config.authToken);
7801
- httpService.onUnauthorized(() => {
7802
- console.log("[BackendProvider] User unauthorized (401/403), triggering logout");
7803
- this.logout().catch((error) => {
7804
- console.error("[BackendProvider] Logout failed in 401 handler:", error);
8290
+ httpService.onUnauthorized(() => this.handleUnauthorized());
8291
+ }
8292
+ /**
8293
+ * 处理 401 未授权错误
8294
+ * 先尝试刷新 token,失败后再执行登出流程
8295
+ *
8296
+ * @throws 如果 token 刷新失败,抛出错误通知 HttpService 不要重试
8297
+ */
8298
+ async handleUnauthorized() {
8299
+ console.log("[BackendProvider] User unauthorized (401), attempting token refresh first");
8300
+ try {
8301
+ if (await this.refreshToken()) {
8302
+ console.log("[BackendProvider] Token refresh successful after 401, user still logged in");
8303
+ return;
8304
+ }
8305
+ throw new Error("Token refresh returned null");
8306
+ } catch (error) {
8307
+ console.error("[BackendProvider] Token refresh failed after 401:", error);
8308
+ console.log("[BackendProvider] Token refresh failed, triggering logout");
8309
+ this.logout().catch((logoutError) => {
8310
+ console.error("[BackendProvider] Logout failed in 401 handler:", logoutError);
7805
8311
  });
7806
- });
8312
+ throw error;
8313
+ }
7807
8314
  }
7808
8315
  /**
7809
8316
  * 获取当前账号信息
@@ -7848,7 +8355,7 @@ var BackendProvider = class {
7848
8355
  return account;
7849
8356
  }
7850
8357
  const redirectUrl = encodeURIComponent(window.location.href);
7851
- window.location.href = `${getSelectAccountUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
8358
+ window.location.href = `${getSelectAccountUrl()}?platform=agents&state=0&redirect_uri=${redirectUrl}`;
7852
8359
  accountService.setAccount(null);
7853
8360
  return null;
7854
8361
  } catch (error) {
@@ -7871,7 +8378,8 @@ var BackendProvider = class {
7871
8378
  activeStatus: connector.active_status,
7872
8379
  displayName: connector.display_name,
7873
8380
  oauthClientId: connector.oauth_client_id,
7874
- oauthRedirectUrl: connector.oauth_redirect_url
8381
+ oauthRedirectUrl: connector.oauth_redirect_url,
8382
+ oauthAppName: connector.oauth_app_name
7875
8383
  })) };
7876
8384
  }
7877
8385
  throw result;
@@ -7953,7 +8461,8 @@ var BackendProvider = class {
7953
8461
  connectStatus: connector.connect_status,
7954
8462
  displayName: connector.display_name,
7955
8463
  oauthClientId: connector.oauth_client_id,
7956
- oauthRedirectUrl: connector.oauth_redirect_url
8464
+ oauthRedirectUrl: connector.oauth_redirect_url,
8465
+ oauthAppName: connector.oauth_app_name
7957
8466
  })) };
7958
8467
  }
7959
8468
  throw result;
@@ -8044,6 +8553,9 @@ var BackendProvider = class {
8044
8553
  PackageCode: void 0,
8045
8554
  name: ""
8046
8555
  };
8556
+ const productFeatures = typeof window !== "undefined" && window.PRODUCT_FEATURES ? safeParseJSON(window.PRODUCT_FEATURES) : {};
8557
+ console.log("[PRODUCT_FEATURES]", productFeatures);
8558
+ if (!productFeatures.billing) return defaultPlan;
8047
8559
  try {
8048
8560
  const now = /* @__PURE__ */ new Date();
8049
8561
  const futureDate = new Date(now.getTime() + 101 * 365 * 24 * 60 * 60 * 1e3);
@@ -8065,7 +8577,7 @@ var BackendProvider = class {
8065
8577
  if (!time) return 0;
8066
8578
  return new Date(time).getTime();
8067
8579
  };
8068
- const dailyCredits = [CommodityCode.free, CommodityCode.freeMon];
8580
+ const dailyCredits = [CommodityCode.free];
8069
8581
  const planResources = resources.map((r) => {
8070
8582
  const isDaily = dailyCredits.includes(r.PackageCode);
8071
8583
  const endTime = isDaily ? r.CycleEndTime : r.DeductionEndTime;
@@ -8086,10 +8598,11 @@ var BackendProvider = class {
8086
8598
  CommodityCode.proMon,
8087
8599
  CommodityCode.proMonPlus,
8088
8600
  CommodityCode.proYear,
8601
+ CommodityCode.freeMon,
8089
8602
  CommodityCode.extra
8090
8603
  ].includes(code)) return 1;
8091
8604
  if ([CommodityCode.gift, CommodityCode.activity].includes(code)) return 2;
8092
- if ([CommodityCode.free, CommodityCode.freeMon].includes(code)) return 3;
8605
+ if ([CommodityCode.free].includes(code)) return 3;
8093
8606
  return 4;
8094
8607
  };
8095
8608
  return getPriority(a.packageCode) - getPriority(b.packageCode);
@@ -8210,7 +8723,7 @@ var BackendProvider = class {
8210
8723
  */
8211
8724
  async login() {
8212
8725
  const redirectUrl = encodeURIComponent(window.location.href);
8213
- window.location.href = `${getLoginUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
8726
+ window.location.href = `${getFullUrl("/login")}?platform=agents&state=0&redirect_uri=${redirectUrl}`;
8214
8727
  }
8215
8728
  /**
8216
8729
  * 登出账号
@@ -8273,6 +8786,52 @@ var BackendProvider = class {
8273
8786
  return null;
8274
8787
  }
8275
8788
  }
8789
+ /**
8790
+ * 刷新 Token
8791
+ * 通过调用 getAccount 刷新 cookie,适用于 Cloud 场景下页面切换回来时刷新登录态
8792
+ * @returns Promise<Account | null> 刷新后的账号信息
8793
+ */
8794
+ async refreshToken() {
8795
+ console.log("[BackendProvider] Refreshing token...");
8796
+ try {
8797
+ const account = await this.getAccount();
8798
+ console.log("[BackendProvider] Token refreshed, account:", account?.uid);
8799
+ return account;
8800
+ } catch (error) {
8801
+ console.error("[BackendProvider] refreshToken failed:", error);
8802
+ return null;
8803
+ }
8804
+ }
8805
+ /**
8806
+ * 获取仓库分支列表
8807
+ * API 端点: GET /console/as/connector/oauth/{name}/branches
8808
+ *
8809
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
8810
+ * @param params 平台特定的查询参数
8811
+ * @param page 页码,从1开始,0表示不分页获取全部
8812
+ * @param perPage 每页数量,最大100
8813
+ * @returns Promise<OauthBranch[]> 分支列表
8814
+ */
8815
+ async getBranches(connector, params, page = 0, perPage = 100) {
8816
+ return oauthRepositoryService.getBranches(connector, params, page, perPage);
8817
+ }
8818
+ /**
8819
+ * 获取仓库列表
8820
+ * API 端点: GET /console/as/connector/oauth/{name}/repos
8821
+ *
8822
+ * Note: 由于工蜂原生支持的 Search 能力会匹配 path/name/description 部分,
8823
+ * 且不支持定制,不满足产品要求(只按 name 匹配),因此前端拉取全量数据后做筛选。
8824
+ *
8825
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
8826
+ * @param page 页码,从1开始,0表示不分页获取全部
8827
+ * - GitHub 只支持全量数据,必须传 0
8828
+ * - 工蜂和 CNB 依据前端逻辑而定
8829
+ * @param perPage 每页数量,最大100
8830
+ * @returns Promise<ListReposResponse> 仓库列表响应
8831
+ */
8832
+ async getRepositories(connector, page = 0, perPage = 100) {
8833
+ return oauthRepositoryService.getRepositories(connector, page, perPage);
8834
+ }
8276
8835
  };
8277
8836
  /**
8278
8837
  * 创建 BackendProvider 实例
@@ -8312,6 +8871,7 @@ const BACKEND_REQUEST_TYPES = {
8312
8871
  GET_FILE: "backend:get-file",
8313
8872
  RELOAD_WINDOW: "backend:reload-window",
8314
8873
  CLOSE_AGENT_MANAGER: "backend:close-agent-manager",
8874
+ OPEN_EXTERNAL: "backend:open-external",
8315
8875
  BATCH_TOGGLE_PLUGINS: "backend:batch-toggle-plugins",
8316
8876
  GET_SUPPORT_SCENES: "backend:get-support-scenes"
8317
8877
  };
@@ -8607,6 +9167,20 @@ var IPCBackendProvider = class {
8607
9167
  }
8608
9168
  }
8609
9169
  /**
9170
+ * 在外部浏览器中打开链接
9171
+ * IDE 环境: 通过 IPC 通知 IDE 使用 vscode.env.openExternal 打开 URL
9172
+ * @param url 要打开的 URL
9173
+ */
9174
+ async openExternal(url) {
9175
+ this.log("Opening external URL via IPC:", url);
9176
+ try {
9177
+ await this.sendBackendRequest(BACKEND_REQUEST_TYPES.OPEN_EXTERNAL, { url });
9178
+ } catch (error) {
9179
+ this.log("Open external request failed:", error);
9180
+ throw error;
9181
+ }
9182
+ }
9183
+ /**
8610
9184
  * 批量切换插件状态
8611
9185
  * IDE 环境: 通过 IPC 调用 Extension Host 的 PluginService
8612
9186
  */