@tencent-ai/cloud-agent-sdk 0.2.12 → 0.2.13-next.1828052.20260211

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)
@@ -2283,6 +2281,24 @@ var CloudAgentConnection = class {
2283
2281
  },
2284
2282
  onUsageUpdate: (usage) => {
2285
2283
  this.emit("usageUpdate", usage);
2284
+ },
2285
+ onExtNotification: (method, params) => {
2286
+ console.log("[CloudConnection] Received extNotification:", {
2287
+ method,
2288
+ paramsKeys: Object.keys(params)
2289
+ });
2290
+ if (method === ExtensionMethod.COMMAND) {
2291
+ const action = params.action;
2292
+ const commandParams = params.params;
2293
+ console.log("[CloudConnection] Emitting command event:", {
2294
+ action,
2295
+ paramsKeys: commandParams ? Object.keys(commandParams) : []
2296
+ });
2297
+ this.emit("command", {
2298
+ action,
2299
+ params: commandParams
2300
+ });
2301
+ }
2286
2302
  }
2287
2303
  });
2288
2304
  this.setupEventForwarding();
@@ -2411,7 +2427,7 @@ var CloudAgentConnection = class {
2411
2427
  }
2412
2428
  async createSession(params) {
2413
2429
  return {
2414
- ...await this.client.loadSession(this.agentId, this.cwd),
2430
+ ...await this.client.createSession(this.cwd),
2415
2431
  sessionId: this.agentId
2416
2432
  };
2417
2433
  }
@@ -2529,6 +2545,16 @@ var CloudAgentConnection = class {
2529
2545
  get sessionConnectionInfo() {
2530
2546
  return this._sessionConnectionInfo;
2531
2547
  }
2548
+ async reportTelemetry(eventName, payload) {
2549
+ try {
2550
+ await this.client.extMethod("reportTelemetry", {
2551
+ eventName,
2552
+ payload
2553
+ });
2554
+ } catch (error) {
2555
+ console.warn("[CloudAgentConnection] reportTelemetry failed:", error);
2556
+ }
2557
+ }
2532
2558
  async extMethod(method, params) {
2533
2559
  return this.client.extMethod(method, params);
2534
2560
  }
@@ -15923,7 +15949,7 @@ var axios_default = axios;
15923
15949
  * 特性:
15924
15950
  * - 单例模式,全局唯一实例,延迟初始化(首次使用时自动创建)
15925
15951
  * - 支持拦截器注册(其他模块可注入 header)
15926
- * - 统一 401/403 处理
15952
+ * - 统一 401 处理(支持自动刷新 token 并重试)
15927
15953
  * - 自动携带凭证(withCredentials)
15928
15954
  * - 类型安全
15929
15955
  *
@@ -15963,6 +15989,8 @@ var HttpService = class HttpService {
15963
15989
  */
15964
15990
  constructor(config = {}) {
15965
15991
  this.unauthorizedCallbacks = /* @__PURE__ */ new Set();
15992
+ this.isRefreshing = false;
15993
+ this.refreshSubscribers = [];
15966
15994
  this.config = config;
15967
15995
  this.axiosInstance = axios_default.create({
15968
15996
  baseURL: config.baseURL?.replace(/\/$/, "") || "",
@@ -16003,18 +16031,54 @@ var HttpService = class HttpService {
16003
16031
  }, (error) => Promise.reject(error));
16004
16032
  }
16005
16033
  /**
16006
- * 注册默认响应拦截器(处理 401/403)
16034
+ * 注册默认响应拦截器(处理 401,支持自动重试)
16007
16035
  */
16008
16036
  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();
16037
+ this.axiosInstance.interceptors.response.use((response) => response, async (error) => {
16038
+ const originalRequest = error.config;
16039
+ if (error.response?.status === 401 && originalRequest && !originalRequest._retry) {
16040
+ if (originalRequest.url?.includes("/console/accounts")) {
16041
+ console.warn("[HttpService] Unauthorized 401 on refresh endpoint, not retrying");
16042
+ return Promise.reject(error);
16043
+ }
16044
+ console.warn("[HttpService] Unauthorized 401, attempting token refresh and retry");
16045
+ originalRequest._retry = true;
16046
+ if (this.isRefreshing) return new Promise((resolve, reject) => {
16047
+ this.refreshSubscribers.push((success) => {
16048
+ if (success) this.axiosInstance.request(originalRequest).then(resolve).catch(reject);
16049
+ else reject(error);
16050
+ });
16051
+ });
16052
+ this.isRefreshing = true;
16053
+ try {
16054
+ await this.triggerUnauthorizedCallbacks();
16055
+ this.onRefreshSuccess();
16056
+ return this.axiosInstance.request(originalRequest);
16057
+ } catch (refreshError) {
16058
+ this.onRefreshFailure();
16059
+ return Promise.reject(error);
16060
+ } finally {
16061
+ this.isRefreshing = false;
16062
+ }
16013
16063
  }
16014
16064
  return Promise.reject(error);
16015
16065
  });
16016
16066
  }
16017
16067
  /**
16068
+ * token 刷新成功,通知所有等待的请求
16069
+ */
16070
+ onRefreshSuccess() {
16071
+ this.refreshSubscribers.forEach((callback) => callback(true));
16072
+ this.refreshSubscribers = [];
16073
+ }
16074
+ /**
16075
+ * token 刷新失败,通知所有等待的请求
16076
+ */
16077
+ onRefreshFailure() {
16078
+ this.refreshSubscribers.forEach((callback) => callback(false));
16079
+ this.refreshSubscribers = [];
16080
+ }
16081
+ /**
16018
16082
  * 注册请求拦截器
16019
16083
  * @param onFulfilled 请求成功拦截器
16020
16084
  * @param onRejected 请求失败拦截器
@@ -16091,16 +16155,18 @@ var HttpService = class HttpService {
16091
16155
  this.unauthorizedCallbacks.delete(callback);
16092
16156
  }
16093
16157
  /**
16094
- * 触发所有 401 回调
16158
+ * 触发所有 401 回调并等待完成
16159
+ * @returns Promise,等待所有回调完成
16160
+ * @throws 如果任何回调失败,则抛出错误
16095
16161
  */
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
- });
16162
+ async triggerUnauthorizedCallbacks() {
16163
+ const callbacks = Array.from(this.unauthorizedCallbacks);
16164
+ const failedResults = (await Promise.allSettled(callbacks.map((callback) => callback()))).filter((r) => r.status === "rejected");
16165
+ if (failedResults.length > 0) {
16166
+ const errors = failedResults.map((r) => r.reason);
16167
+ console.error("[HttpService] Some unauthorized callbacks failed:", errors);
16168
+ throw errors[0];
16169
+ }
16104
16170
  }
16105
16171
  /**
16106
16172
  * 更新 authToken
@@ -16212,6 +16278,7 @@ var AccountService = class {
16212
16278
  this.initPromise = null;
16213
16279
  this.initResolve = null;
16214
16280
  this.requestInterceptorId = null;
16281
+ this.crossTabBroadcaster = null;
16215
16282
  this.initPromise = new Promise((resolve) => {
16216
16283
  this.initResolve = resolve;
16217
16284
  });
@@ -16260,15 +16327,34 @@ var AccountService = class {
16260
16327
  this.initialized = true;
16261
16328
  this.initResolve?.(account);
16262
16329
  }
16263
- if (!wasInitialized || prev?.uid !== account?.uid) this.notifyListeners();
16330
+ if (!wasInitialized || prev?.uid !== account?.uid) {
16331
+ this.notifyListeners();
16332
+ if (account && this.crossTabBroadcaster) this.crossTabBroadcaster.broadcastLogin();
16333
+ }
16264
16334
  }
16265
16335
  /**
16266
16336
  * 清除账号(登出)
16337
+ * 先广播登出消息,再清除本地账号
16267
16338
  */
16268
16339
  clearAccount() {
16340
+ if (this.crossTabBroadcaster) this.crossTabBroadcaster.broadcastLogout();
16269
16341
  this.setAccount(null);
16270
16342
  }
16271
16343
  /**
16344
+ * 静默清除账号(不广播)
16345
+ * 用于收到其他标签页 logout 消息时,避免循环广播
16346
+ */
16347
+ clearAccountSilently() {
16348
+ this.setAccount(null);
16349
+ }
16350
+ /**
16351
+ * 设置跨标签页认证同步广播器
16352
+ * 应在应用初始化时由上层(如 agent-ui)调用
16353
+ */
16354
+ setCrossTabBroadcaster(broadcaster) {
16355
+ this.crossTabBroadcaster = broadcaster;
16356
+ }
16357
+ /**
16272
16358
  * 订阅账号变化
16273
16359
  * @param callback 变化时的回调函数
16274
16360
  * @returns 取消订阅函数
@@ -16333,6 +16419,11 @@ var AccountService = class {
16333
16419
  * 导出单例实例
16334
16420
  */
16335
16421
  const accountService = new AccountService();
16422
+ /**
16423
+ * 暴露给全局,供 Agent Manager 直接调用 setAccount 刷新 Widget 状态
16424
+ * 这是为了解决 IDE 环境中 IPC 事件无法直接触发 Widget 账号刷新的问题
16425
+ */
16426
+ if (typeof window !== "undefined") window.__genieAccountService = accountService;
16336
16427
 
16337
16428
  //#endregion
16338
16429
  //#region ../agent-provider/src/common/utils/concurrency.ts
@@ -16435,20 +16526,32 @@ var CosUploadService = class {
16435
16526
  * 上传单个文件到 COS
16436
16527
  *
16437
16528
  * @param file - 要上传的文件
16529
+ * @param abortSignal - 可选的 AbortSignal,用于取消上传
16438
16530
  * @returns 上传结果,包含访问 URL 或错误信息
16439
16531
  */
16440
- async uploadFile(file) {
16532
+ async uploadFile(file, abortSignal) {
16441
16533
  const filename = file.name;
16442
16534
  this.logger?.info(`[CosUploadService] Uploading file: ${filename}`);
16443
16535
  try {
16536
+ if (abortSignal?.aborted) return {
16537
+ success: false,
16538
+ error: "Upload cancelled",
16539
+ aborted: true
16540
+ };
16444
16541
  const objectKey = this.generateObjectKey(filename);
16445
16542
  this.logger?.debug(`[CosUploadService] Generated objectKey: ${objectKey}`);
16446
16543
  const presignedItem = (await this.getPresignedUrls([objectKey])).items[0];
16447
16544
  if (!presignedItem) throw new Error("No presigned URL item returned");
16545
+ if (abortSignal?.aborted) return {
16546
+ success: false,
16547
+ error: "Upload cancelled",
16548
+ aborted: true
16549
+ };
16448
16550
  const uploadResponse = await fetch(presignedItem.upload_url, {
16449
16551
  method: "PUT",
16450
16552
  body: file,
16451
- headers: { "Content-Type": file.type || "application/octet-stream" }
16553
+ headers: { "Content-Type": file.type || "application/octet-stream" },
16554
+ signal: abortSignal
16452
16555
  });
16453
16556
  if (!uploadResponse.ok) {
16454
16557
  const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
@@ -16462,6 +16565,11 @@ var CosUploadService = class {
16462
16565
  objectKey
16463
16566
  };
16464
16567
  } catch (error) {
16568
+ if (error instanceof Error && error.name === "AbortError") return {
16569
+ success: false,
16570
+ error: "Upload cancelled",
16571
+ aborted: true
16572
+ };
16465
16573
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
16466
16574
  this.logger?.error(`[CosUploadService] Upload failed: ${filename}`, error);
16467
16575
  return {
@@ -16476,14 +16584,25 @@ var CosUploadService = class {
16476
16584
  * 使用并发控制,限制同时上传的文件数量
16477
16585
  *
16478
16586
  * @param files - 要上传的文件数组
16587
+ * @param abortSignal - 可选的 AbortSignal,用于取消上传
16479
16588
  * @returns 所有文件的上传结果
16480
16589
  */
16481
- async uploadFiles(files) {
16590
+ async uploadFiles(files, abortSignal) {
16482
16591
  if (files.length === 0) return {
16483
16592
  success: true,
16484
16593
  urls: [],
16485
16594
  results: []
16486
16595
  };
16596
+ if (abortSignal?.aborted) return {
16597
+ success: false,
16598
+ error: "Upload cancelled",
16599
+ aborted: true,
16600
+ results: files.map(() => ({
16601
+ success: false,
16602
+ error: "Upload cancelled",
16603
+ aborted: true
16604
+ }))
16605
+ };
16487
16606
  this.logger?.info(`[CosUploadService] Uploading ${files.length} file(s) with concurrency ${this.uploadConcurrency}`);
16488
16607
  try {
16489
16608
  const fileInfos = files.map((file) => ({
@@ -16495,6 +16614,12 @@ var CosUploadService = class {
16495
16614
  this.logger?.debug(`[CosUploadService] Got ${presignedResponse.items.length} presigned URLs`);
16496
16615
  if (presignedResponse.items.length !== fileInfos.length) throw new Error(`Expected ${fileInfos.length} presigned URLs, got ${presignedResponse.items.length}`);
16497
16616
  const results = (await runWithConcurrencySettled(fileInfos.map(({ file }, index) => async () => {
16617
+ if (abortSignal?.aborted) return {
16618
+ success: false,
16619
+ error: "Upload cancelled",
16620
+ aborted: true,
16621
+ objectKey: fileInfos[index].objectKey
16622
+ };
16498
16623
  const presignedItem = presignedResponse.items[index];
16499
16624
  if (!presignedItem) return {
16500
16625
  success: false,
@@ -16505,7 +16630,8 @@ var CosUploadService = class {
16505
16630
  const uploadResponse = await fetch(presignedItem.upload_url, {
16506
16631
  method: "PUT",
16507
16632
  body: file,
16508
- headers: { "Content-Type": file.type || "application/octet-stream" }
16633
+ headers: { "Content-Type": file.type || "application/octet-stream" },
16634
+ signal: abortSignal
16509
16635
  });
16510
16636
  if (!uploadResponse.ok) {
16511
16637
  const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
@@ -16522,6 +16648,12 @@ var CosUploadService = class {
16522
16648
  objectKey: presignedItem.object_key
16523
16649
  };
16524
16650
  } catch (error) {
16651
+ if (error instanceof Error && error.name === "AbortError") return {
16652
+ success: false,
16653
+ error: "Upload cancelled",
16654
+ aborted: true,
16655
+ objectKey: presignedItem.object_key
16656
+ };
16525
16657
  return {
16526
16658
  success: false,
16527
16659
  error: error instanceof Error ? error.message : "Unknown error",
@@ -16922,15 +17054,9 @@ var CloudAgentProvider = class CloudAgentProvider {
16922
17054
  const url = this.buildGetUrl("/console/as/conversations/", params);
16923
17055
  const apiResponse = await httpService.get(url);
16924
17056
  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
17057
  return {
16932
- agents,
16933
- pagination
17058
+ agents: apiResponse.data.conversations.map((a) => this.toAgentState(a)),
17059
+ pagination: apiResponse.data.pagination
16934
17060
  };
16935
17061
  } catch (error) {
16936
17062
  this.logger?.error("Failed to list agents:", error);
@@ -16940,13 +17066,21 @@ var CloudAgentProvider = class CloudAgentProvider {
16940
17066
  /**
16941
17067
  * Create a new conversation
16942
17068
  * POST {endpoint}/console/as/conversations
17069
+ * @param params - Session params containing cwd and optional configuration
16943
17070
  */
16944
- async create() {
17071
+ async create(params) {
16945
17072
  try {
16946
- const apiResponse = await httpService.post("/console/as/conversations/", {
16947
- prompt: "",
16948
- model: "deepseek-r1"
16949
- });
17073
+ const { options = {} } = params;
17074
+ const codebuddyMeta = options._meta?.["codebuddy.ai"];
17075
+ const tagsObj = options.tags || codebuddyMeta?.tags;
17076
+ const tagsArray = tagsObj ? Object.entries(tagsObj).map(([key, value]) => `${key}:${value}`) : void 0;
17077
+ const createPayload = {
17078
+ prompt: (options.prompt || "").slice(0, 100),
17079
+ model: options.model || "deepseek-r1",
17080
+ ...tagsArray && tagsArray.length > 0 ? { tags: tagsArray } : {}
17081
+ };
17082
+ console.log("[CloudAgentProvider] Creating conversation with payload:", createPayload);
17083
+ const apiResponse = await httpService.post("/console/as/conversations/", createPayload);
16950
17084
  if (!apiResponse.data) throw new Error("No data in API response");
16951
17085
  this.logger?.info(`Created conversation: ${apiResponse.data.id}`);
16952
17086
  return apiResponse.data.id;
@@ -17116,6 +17250,35 @@ var CloudAgentProvider = class CloudAgentProvider {
17116
17250
  }
17117
17251
  }
17118
17252
  /**
17253
+ * Update conversation status by ID
17254
+ * POST {endpoint}/console/as/conversations/{agentId}
17255
+ *
17256
+ * @param agentId - Conversation ID to update
17257
+ * @param status - New status for the conversation
17258
+ * @returns PatchConversationResponse containing the updated conversation ID
17259
+ *
17260
+ * @example
17261
+ * ```typescript
17262
+ * const result = await provider.updateStatus('agent-123', 'completed');
17263
+ * console.log('Updated conversation status:', result.id);
17264
+ * ```
17265
+ */
17266
+ async updateStatus(agentId, status) {
17267
+ try {
17268
+ const body = { status };
17269
+ const apiResponse = await httpService.post(`/console/as/conversations/${agentId}`, body);
17270
+ if (!apiResponse.data) {
17271
+ this.logger?.info(`Updated conversation status: ${agentId} to "${status}"`);
17272
+ return { id: agentId };
17273
+ }
17274
+ this.logger?.info(`Updated conversation status: ${apiResponse.data.id} to "${status}"`);
17275
+ return apiResponse.data;
17276
+ } catch (error) {
17277
+ this.logger?.error(`Failed to update conversation status ${agentId}:`, error);
17278
+ throw error;
17279
+ }
17280
+ }
17281
+ /**
17119
17282
  * Get available models from product configuration
17120
17283
  *
17121
17284
  * GET {endpoint}/console/enterprises/{personal|enterpriseId}/models?repos[]={repo}
@@ -17146,9 +17309,12 @@ var CloudAgentProvider = class CloudAgentProvider {
17146
17309
  this.logger?.warn("[CloudAgentProvider] No data in config response, returning empty models");
17147
17310
  return [];
17148
17311
  }
17149
- const models = apiResponse.data.models ?? [];
17150
- this.logger?.info(`[CloudAgentProvider] Retrieved ${models.length} models from /console/enterprises API`);
17151
- return models.map((model) => ({
17312
+ const productConfig = apiResponse.data;
17313
+ const allModels = productConfig.models ?? [];
17314
+ const cliModelIds = (productConfig.agents ?? []).find((agent) => agent.name === "cli")?.models ?? [];
17315
+ const filteredModels = cliModelIds.length > 0 ? allModels.filter((model) => cliModelIds.includes(model.id)) : allModels;
17316
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${filteredModels.length} models for cli agent (total: ${allModels.length})`);
17317
+ return filteredModels.map((model) => ({
17152
17318
  id: model.id,
17153
17319
  name: model.name ?? model.id,
17154
17320
  description: model.description,
@@ -17252,7 +17418,7 @@ var CloudAgentProvider = class CloudAgentProvider {
17252
17418
  /**
17253
17419
  * Upload files to cloud storage via COS presigned URL
17254
17420
  *
17255
- * @param params - files array (File objects in browser)
17421
+ * @param params - files array (File objects in browser), optional abortSignal
17256
17422
  * @returns Response with corresponding cloud URLs
17257
17423
  */
17258
17424
  async uploadFile(params) {
@@ -17262,12 +17428,13 @@ var CloudAgentProvider = class CloudAgentProvider {
17262
17428
  success: false,
17263
17429
  error: "No valid File objects provided"
17264
17430
  };
17265
- const result = await this.cosUploadService.uploadFiles(files);
17431
+ const result = await this.cosUploadService.uploadFiles(files, params.abortSignal);
17266
17432
  return {
17267
17433
  success: result.success,
17268
17434
  urls: result.urls,
17269
17435
  expireSeconds: result.expireSeconds,
17270
- error: result.error
17436
+ error: result.error,
17437
+ aborted: result.aborted
17271
17438
  };
17272
17439
  }
17273
17440
  /**
@@ -17309,20 +17476,22 @@ var CloudAgentProvider = class CloudAgentProvider {
17309
17476
  }
17310
17477
  /**
17311
17478
  * 获取支持的场景列表
17312
- * API 端点: GET /console/as/support/scenes
17479
+ * API 端点: GET /v2/as/support/scenes (不鉴权)
17313
17480
  * 用于 Welcome 页面的 QuickActions 快捷操作
17314
17481
  *
17482
+ * @param locale - 可选,语言环境(如 'zh-CN', 'en-US'),用于获取对应语言的场景数据
17315
17483
  * @returns Promise<SupportScene[]> 支持的场景列表
17316
17484
  */
17317
- async getSupportScenes() {
17485
+ async getSupportScenes(locale) {
17318
17486
  try {
17319
- const apiResponse = await httpService.get("/console/as/support/scenes");
17487
+ const url = this.buildGetUrl("/v2/as/support/scenes", locale ? { locale } : void 0);
17488
+ const apiResponse = await httpService.get(url);
17320
17489
  if (!apiResponse.data) {
17321
17490
  this.logger?.warn("[CloudAgentProvider] No data in support scenes response");
17322
17491
  return [];
17323
17492
  }
17324
17493
  const scenes = apiResponse.data.scenes || [];
17325
- this.logger?.info(`[CloudAgentProvider] Retrieved ${scenes.length} support scenes`);
17494
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${scenes.length} support scenes${locale ? ` for locale: ${locale}` : ""}`);
17326
17495
  return scenes;
17327
17496
  } catch (error) {
17328
17497
  this.logger?.error("[CloudAgentProvider] Failed to get support scenes:", error);
@@ -17338,7 +17507,8 @@ var CloudAgentProvider = class CloudAgentProvider {
17338
17507
  type: "cloud",
17339
17508
  status,
17340
17509
  createdAt: data.createdAt ? new Date(data.createdAt) : void 0,
17341
- capabilities: this.options.clientCapabilities
17510
+ capabilities: this.options.clientCapabilities,
17511
+ isUserDefinedTitle: data.isUserDefinedTitle
17342
17512
  };
17343
17513
  }
17344
17514
  /**
@@ -17354,6 +17524,37 @@ var CloudAgentProvider = class CloudAgentProvider {
17354
17524
  const queryString = searchParams.toString();
17355
17525
  return queryString ? `${path}?${queryString}` : path;
17356
17526
  }
17527
+ /**
17528
+ * 上报 telemetry 事件(Cloud 模式)
17529
+ * 通过 HTTP POST 发送到 /v2/report
17530
+ * 注入用户信息和浏览器环境等公共字段
17531
+ */
17532
+ async reportTelemetry(eventName, payload) {
17533
+ try {
17534
+ const account = accountService.getAccount();
17535
+ const commonFields = {};
17536
+ if (account) {
17537
+ commonFields.userId = account.uid;
17538
+ commonFields.userNickname = account.nickname;
17539
+ if (account.enterpriseId) commonFields.enterpriseId = account.enterpriseId;
17540
+ if (account.enterpriseUserName) commonFields.username = account.enterpriseUserName;
17541
+ }
17542
+ if (typeof navigator !== "undefined") {
17543
+ commonFields.userAgent = navigator.userAgent;
17544
+ commonFields.os = navigator.platform;
17545
+ }
17546
+ const events = [{
17547
+ eventCode: eventName,
17548
+ timestamp: Date.now(),
17549
+ reportDelay: 0,
17550
+ ...commonFields,
17551
+ ...payload
17552
+ }];
17553
+ await httpService.post("/v2/report", events);
17554
+ } catch (error) {
17555
+ this.logger?.warn("reportTelemetry() failed:", error);
17556
+ }
17557
+ }
17357
17558
  };
17358
17559
 
17359
17560
  //#endregion
@@ -17402,6 +17603,7 @@ var ActiveSessionImpl = class {
17402
17603
  this._availableCommands = [];
17403
17604
  this.listeners = /* @__PURE__ */ new Map();
17404
17605
  this.onceListeners = /* @__PURE__ */ new Map();
17606
+ this.connectionListeners = [];
17405
17607
  this._id = sessionId;
17406
17608
  this._agentId = agentId;
17407
17609
  this.connection = connection;
@@ -17427,6 +17629,18 @@ var ActiveSessionImpl = class {
17427
17629
  return this._agentId;
17428
17630
  }
17429
17631
  /**
17632
+ * Actual workspace path (set from newSession response _meta)
17633
+ */
17634
+ get cwd() {
17635
+ return this._cwd;
17636
+ }
17637
+ /**
17638
+ * Set actual workspace path (called by SessionManager after createSession)
17639
+ */
17640
+ setCwd(cwd) {
17641
+ this._cwd = cwd;
17642
+ }
17643
+ /**
17430
17644
  * Agent state (live connection state)
17431
17645
  * Returns LocalAgentState or CloudAgentState based on transport type
17432
17646
  */
@@ -17643,8 +17857,8 @@ var ActiveSessionImpl = class {
17643
17857
  * await session.setMode('architect');
17644
17858
  * ```
17645
17859
  */
17646
- async setMode(modeId) {
17647
- if (this._availableModes) {
17860
+ async setMode(modeId, skipAvailableChecker) {
17861
+ if (this._availableModes && !skipAvailableChecker) {
17648
17862
  if (!this._availableModes.some((m) => m.id === modeId)) {
17649
17863
  const availableIds = this._availableModes.map((m) => m.id).join(", ");
17650
17864
  throw new Error(`Invalid modeId: "${modeId}". Available modes: ${availableIds}`);
@@ -17744,6 +17958,7 @@ var ActiveSessionImpl = class {
17744
17958
  * Disconnect from the session/agent
17745
17959
  */
17746
17960
  disconnect() {
17961
+ this.removeConnectionListeners();
17747
17962
  this.connection.disconnect();
17748
17963
  this.removeAllListeners();
17749
17964
  this.logger?.info(`Session ${this._id}: Disconnected`);
@@ -17767,60 +17982,80 @@ var ActiveSessionImpl = class {
17767
17982
  if (!this.connection.isInitialized) throw new Error(`Session ${this._id}: Connection not initialized.`);
17768
17983
  return this.connection;
17769
17984
  }
17985
+ /**
17986
+ * 在 connection 上注册 listener 并保存引用,便于 disconnect 时移除
17987
+ */
17988
+ addConnectionListener(connection, event, listener) {
17989
+ connection.on(event, listener);
17990
+ this.connectionListeners.push({
17991
+ event,
17992
+ listener
17993
+ });
17994
+ }
17995
+ /**
17996
+ * 从 connection 上移除所有本 session 注册的 listener
17997
+ */
17998
+ removeConnectionListeners() {
17999
+ for (const { event, listener } of this.connectionListeners) this.connection.off(event, listener);
18000
+ this.connectionListeners = [];
18001
+ }
17770
18002
  setupConnectionEvents(connection) {
17771
- connection.on("connected", () => {
18003
+ this.addConnectionListener(connection, "connected", () => {
17772
18004
  this.emit("connected", void 0);
17773
18005
  });
17774
- connection.on("disconnected", () => {
18006
+ this.addConnectionListener(connection, "disconnected", () => {
17775
18007
  this.emit("disconnected", void 0);
17776
18008
  });
17777
- connection.on("error", (error) => {
18009
+ this.addConnectionListener(connection, "error", (error) => {
17778
18010
  this.emit("error", error);
17779
18011
  });
17780
- connection.on("sessionUpdate", (update) => {
18012
+ this.addConnectionListener(connection, "sessionUpdate", (update) => {
17781
18013
  this.emit("sessionUpdate", update);
17782
18014
  });
17783
- connection.on("artifactCreated", (artifact) => {
17784
- console.log("[Session] Forwarding artifactCreated:", {
17785
- artifactUri: artifact.uri,
17786
- artifactType: artifact.type
17787
- });
18015
+ this.addConnectionListener(connection, "artifactCreated", (artifact) => {
18016
+ if (!this.shouldForwardArtifact(artifact)) return;
17788
18017
  this.emit("artifactCreated", artifact);
17789
18018
  });
17790
- connection.on("artifactUpdated", (artifact) => {
17791
- console.log("[Session] Forwarding artifactUpdated:", {
17792
- artifactUri: artifact.uri,
17793
- artifactType: artifact.type
17794
- });
18019
+ this.addConnectionListener(connection, "artifactUpdated", (artifact) => {
18020
+ if (!this.shouldForwardArtifact(artifact)) return;
17795
18021
  this.emit("artifactUpdated", artifact);
17796
18022
  });
17797
- connection.on("artifactDeleted", (artifact) => {
17798
- console.log("[Session] Forwarding artifactDeleted:", { artifactUri: artifact.uri });
18023
+ this.addConnectionListener(connection, "artifactDeleted", (artifact) => {
18024
+ if (!this.shouldForwardArtifact(artifact)) return;
17799
18025
  this.emit("artifactDeleted", artifact);
17800
18026
  });
17801
- connection.on("permissionRequest", (request) => {
18027
+ this.addConnectionListener(connection, "permissionRequest", (request) => {
17802
18028
  this.emit("permissionRequest", request);
17803
18029
  });
17804
- connection.on("questionRequest", (request) => {
18030
+ this.addConnectionListener(connection, "questionRequest", (request) => {
17805
18031
  this.emit("questionRequest", request);
17806
18032
  });
17807
- connection.on("questionCancelled", () => {
18033
+ this.addConnectionListener(connection, "questionCancelled", () => {
17808
18034
  this.prompts.cancel();
17809
18035
  });
17810
- connection.on("usageUpdate", (usage) => {
18036
+ this.addConnectionListener(connection, "usageUpdate", (usage) => {
17811
18037
  this.emit("usageUpdate", usage);
17812
18038
  });
17813
- connection.on("checkpointCreated", (checkpoint) => {
18039
+ this.addConnectionListener(connection, "checkpointCreated", (checkpoint) => {
18040
+ const originSessionId = checkpoint.__sessionId;
18041
+ if (originSessionId && originSessionId !== this._id) return;
17814
18042
  this.emit("checkpointCreated", checkpoint);
17815
18043
  });
17816
- connection.on("checkpointUpdated", (checkpoint) => {
18044
+ this.addConnectionListener(connection, "checkpointUpdated", (checkpoint) => {
18045
+ const originSessionId = checkpoint.__sessionId;
18046
+ if (originSessionId && originSessionId !== this._id) return;
17817
18047
  this.emit("checkpointUpdated", checkpoint);
17818
18048
  });
17819
- connection.on("command", (command) => {
17820
- console.log("[Session] Forwarding command:", {
17821
- action: command.action,
17822
- paramsKeys: command.params ? Object.keys(command.params) : []
17823
- });
18049
+ this.addConnectionListener(connection, "command", (command) => {
18050
+ const originSessionId = command.__sessionId;
18051
+ if (originSessionId && originSessionId !== this._id) {
18052
+ console.log("[Session] Command not forwarded:", {
18053
+ command,
18054
+ originSessionId,
18055
+ sessionId: this._id
18056
+ });
18057
+ return;
18058
+ }
17824
18059
  this.emit("command", command);
17825
18060
  });
17826
18061
  }
@@ -17830,19 +18065,38 @@ var ActiveSessionImpl = class {
17830
18065
  _meta: response._meta ?? void 0
17831
18066
  };
17832
18067
  }
18068
+ /**
18069
+ * 判断 artifact 是否应该转发给当前 session
18070
+ * - media 类型:按 cwd 路径隔离(同 cwd 下不同 session 可共享 media)
18071
+ * - 其余类型:按 __sessionId 严格隔离
18072
+ */
18073
+ shouldForwardArtifact(artifact) {
18074
+ const originSessionId = artifact.__sessionId;
18075
+ console.log("[Session] shouldForwardArtifact:", {
18076
+ artifact,
18077
+ originSessionId,
18078
+ sessionId: this._id,
18079
+ cwd: this.connection?.cwd
18080
+ });
18081
+ if (artifact.type === "media") {
18082
+ const cwd = this.connection?.cwd;
18083
+ const uri = artifact?.uri;
18084
+ if (cwd && uri) {
18085
+ const toPosix = (p) => p.replace(/\\/g, "/");
18086
+ const uriPath = toPosix(uri.replace(/^(?:file|agent):\/\//, ""));
18087
+ const posixCwd = toPosix(cwd);
18088
+ const normalizedCwd = posixCwd.endsWith("/") ? posixCwd : posixCwd + "/";
18089
+ return uriPath.startsWith(normalizedCwd);
18090
+ }
18091
+ }
18092
+ if (originSessionId && originSessionId !== this._id) return false;
18093
+ return true;
18094
+ }
17833
18095
  };
17834
18096
 
17835
18097
  //#endregion
17836
18098
  //#region ../agent-provider/src/common/client/session-manager.ts
17837
18099
  /**
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
18100
  * SessionManager - Session lifecycle management
17847
18101
  *
17848
18102
  * This class manages the relationship between sessions and agents.
@@ -17892,7 +18146,8 @@ var SessionManager = class {
17892
18146
  createdAt: agent.createdAt,
17893
18147
  lastActivityAt: agent.updatedAt,
17894
18148
  cwd: agent.type === "local" ? agent.cwd : void 0,
17895
- isPlayground: agent.isPlayground
18149
+ isPlayground: agent.isPlayground,
18150
+ isUserDefinedTitle: agent.isUserDefinedTitle
17896
18151
  }));
17897
18152
  console.log("[SessionManager] Returning sessions:", {
17898
18153
  count: sessions.length,
@@ -17919,13 +18174,26 @@ var SessionManager = class {
17919
18174
  if (this.provider.create) {
17920
18175
  agentId = await this.provider.create(params);
17921
18176
  this.logger?.debug(`Created new agent: ${agentId}`);
18177
+ if (params.options?.onSessionPrepared) {
18178
+ const initialPrompt = params.options?.prompt;
18179
+ const initialTitle = initialPrompt?.slice(0, 50) || "";
18180
+ params.options.onSessionPrepared({
18181
+ id: agentId,
18182
+ agentId,
18183
+ name: initialTitle + (initialPrompt && initialPrompt.length > 50 ? "..." : ""),
18184
+ status: "connecting",
18185
+ cwd: params.cwd || "",
18186
+ createdAt: /* @__PURE__ */ new Date()
18187
+ });
18188
+ this.logger?.debug(`Called onSessionPrepared for: ${agentId}`);
18189
+ }
17922
18190
  } else throw new Error("Provider does not support creating agents. Use sessions.load() with an existing sessionId.");
17923
18191
  const connection = await this.provider.connect(agentId);
17924
18192
  this.logger?.debug(`Connected to agent: ${agentId}`);
17925
18193
  const response = await connection.createSession({
17926
- _meta: params._meta,
18194
+ _meta: params.options?._meta,
17927
18195
  cwd: params.cwd,
17928
- mcpServers: params.mcpServers
18196
+ mcpServers: params.options?.mcpServers
17929
18197
  });
17930
18198
  if (this.provider.registerSession) {
17931
18199
  this.provider.registerSession(response.sessionId, agentId);
@@ -17938,14 +18206,10 @@ var SessionManager = class {
17938
18206
  connectionInfo
17939
18207
  });
17940
18208
  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
- }
18209
+ const availableModels = this.extractAvailableModels(response);
18210
+ if (availableModels) session.setModels(availableModels, response.models?.currentModelId);
18211
+ const responseCwd = response._meta?.["codebuddy.ai"]?.cwd;
18212
+ if (responseCwd) session.setCwd(responseCwd);
17949
18213
  this.logger?.info(`Session created: ${response.sessionId}`);
17950
18214
  return session;
17951
18215
  }
@@ -17981,17 +18245,31 @@ var SessionManager = class {
17981
18245
  mcpServers: params.mcpServers
17982
18246
  });
17983
18247
  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
- }
18248
+ const availableModels = this.extractAvailableModels(response);
18249
+ if (availableModels) session.setModels(availableModels, response.models?.currentModelId);
17992
18250
  this.logger?.info(`Session loaded: ${params.sessionId}`);
17993
18251
  return session;
17994
18252
  }
18253
+ /**
18254
+ * 从 ACP response 中提取可用模型列表
18255
+ *
18256
+ * 优先级:
18257
+ * 1. response.models._meta?.['codebuddy.ai']?.availableModels - 包含完整的模型信息(字段名为 'id')
18258
+ * 2. response.models?.availableModels - 只包含基本信息(字段名为 'modelId')
18259
+ * 3. undefined - 都没有时返回 undefined
18260
+ *
18261
+ * @param response - ACP 响应对象
18262
+ * @returns ModelInfo[] | undefined
18263
+ */
18264
+ extractAvailableModels(response) {
18265
+ const metaModels = (response.models?._meta?.["codebuddy.ai"])?.availableModels;
18266
+ if (metaModels && Array.isArray(metaModels) && metaModels.length > 0) return metaModels;
18267
+ const availableModels = response.models?.availableModels;
18268
+ if (availableModels && Array.isArray(availableModels) && availableModels.length > 0) return availableModels.map((model) => ({
18269
+ ...model,
18270
+ ...model._meta?.["codebuddy.ai"] || {}
18271
+ }));
18272
+ }
17995
18273
  };
17996
18274
 
17997
18275
  //#endregion
@@ -18098,6 +18376,26 @@ var AgentClient = class {
18098
18376
  throw error;
18099
18377
  }
18100
18378
  },
18379
+ updateStatus: async (sessionId, status) => {
18380
+ this.logger?.debug("AgentClient.sessions.updateStatus called", {
18381
+ sessionId,
18382
+ status
18383
+ });
18384
+ try {
18385
+ if (this.provider.updateStatus) {
18386
+ const result = await this.provider.updateStatus(sessionId, status);
18387
+ this.logger?.info("Session status updated successfully", {
18388
+ sessionId,
18389
+ status
18390
+ });
18391
+ return result;
18392
+ }
18393
+ throw new Error("Provider does not support updateStatus method");
18394
+ } catch (error) {
18395
+ this.logger?.error("Failed to update session status", error);
18396
+ throw error;
18397
+ }
18398
+ },
18101
18399
  move: async (sessionId) => {
18102
18400
  this.logger?.debug("AgentClient.sessions.move called", { sessionId });
18103
18401
  try {
@@ -18264,6 +18562,72 @@ var AgentClient = class {
18264
18562
  };
18265
18563
  }
18266
18564
  },
18565
+ getSubagentList: async (params) => {
18566
+ try {
18567
+ if (this.provider && this.provider.getSubagentList) {
18568
+ const result = await this.provider.getSubagentList(params);
18569
+ this.logger?.info("Subagent list retrieved", {
18570
+ resultCount: result.results.length,
18571
+ hasError: !!result.error
18572
+ });
18573
+ return result;
18574
+ }
18575
+ return {
18576
+ results: [],
18577
+ error: "Provider does not support getSubagentList"
18578
+ };
18579
+ } catch (error) {
18580
+ this.logger?.error("Failed to get subagent list", error);
18581
+ return {
18582
+ results: [],
18583
+ error: error instanceof Error ? error.message : "Unknown error"
18584
+ };
18585
+ }
18586
+ },
18587
+ getSkillList: async (params) => {
18588
+ try {
18589
+ if (this.provider && this.provider.getSkillList) {
18590
+ const result = await this.provider.getSkillList(params);
18591
+ this.logger?.info("Skill list retrieved", {
18592
+ resultCount: result.results.length,
18593
+ hasError: !!result.error
18594
+ });
18595
+ return result;
18596
+ }
18597
+ return {
18598
+ results: [],
18599
+ error: "Provider does not support getSkillList"
18600
+ };
18601
+ } catch (error) {
18602
+ this.logger?.error("Failed to get skill list", error);
18603
+ return {
18604
+ results: [],
18605
+ error: error instanceof Error ? error.message : "Unknown error"
18606
+ };
18607
+ }
18608
+ },
18609
+ importSkill: async (params) => {
18610
+ try {
18611
+ if (this.provider && this.provider.importSkill) {
18612
+ const result = await this.provider.importSkill(params);
18613
+ this.logger?.info("Import skill completed", {
18614
+ success: result.success,
18615
+ hasError: !!result.error
18616
+ });
18617
+ return result;
18618
+ }
18619
+ return {
18620
+ success: false,
18621
+ error: "Provider does not support importSkill"
18622
+ };
18623
+ } catch (error) {
18624
+ this.logger?.error("Failed to import skill", error);
18625
+ return {
18626
+ success: false,
18627
+ error: error instanceof Error ? error.message : "Unknown error"
18628
+ };
18629
+ }
18630
+ },
18267
18631
  batchTogglePlugins: async (request) => {
18268
18632
  try {
18269
18633
  if (this.provider && this.provider.batchTogglePlugins) {
@@ -18308,10 +18672,10 @@ var AgentClient = class {
18308
18672
  return [];
18309
18673
  }
18310
18674
  },
18311
- installPlugins: async (pluginNames, marketplaceName, installScope) => {
18675
+ installPlugins: async (pluginNames, marketplaceName, installScope, marketplaceSource, workspacePath) => {
18312
18676
  try {
18313
18677
  if (this.provider && "installPlugins" in this.provider && typeof this.provider.installPlugins === "function") {
18314
- const result = await this.provider.installPlugins(pluginNames, marketplaceName, installScope);
18678
+ const result = await this.provider.installPlugins(pluginNames, marketplaceName, installScope, marketplaceSource, workspacePath);
18315
18679
  this.logger?.info("Install plugins", {
18316
18680
  pluginNames,
18317
18681
  marketplaceName,
@@ -18332,13 +18696,9 @@ var AgentClient = class {
18332
18696
  };
18333
18697
  }
18334
18698
  },
18335
- getSupportScenes: async () => {
18699
+ getSupportScenes: async (locale) => {
18336
18700
  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
- }
18701
+ if (this.provider && "getSupportScenes" in this.provider && typeof this.provider.getSupportScenes === "function") return await this.provider.getSupportScenes(locale);
18342
18702
  this.logger?.warn("Provider does not support getSupportScenes");
18343
18703
  return [];
18344
18704
  } catch (error) {
@@ -18346,6 +18706,91 @@ var AgentClient = class {
18346
18706
  return [];
18347
18707
  }
18348
18708
  },
18709
+ getProductScenes: async (locale) => {
18710
+ try {
18711
+ if (this.provider?.getProductScenes) {
18712
+ const result = await this.provider.getProductScenes(locale);
18713
+ this.logger?.info("Got product scenes", { count: result?.length ?? 0 });
18714
+ return result;
18715
+ }
18716
+ this.logger?.warn("Provider does not support getProductScenes");
18717
+ return [];
18718
+ } catch (error) {
18719
+ this.logger?.error("Failed to get product scenes", error);
18720
+ return [];
18721
+ }
18722
+ },
18723
+ getAvailableCommands: async (params) => {
18724
+ try {
18725
+ if (this.provider && "getAvailableCommands" in this.provider && typeof this.provider.getAvailableCommands === "function") {
18726
+ const result = await this.provider.getAvailableCommands(params);
18727
+ this.logger?.info("Got available commands from provider", {
18728
+ sessionId: params?.sessionId ?? "(default)",
18729
+ count: result?.length ?? 0
18730
+ });
18731
+ return result;
18732
+ }
18733
+ this.logger?.warn("Provider does not support getAvailableCommands", { params });
18734
+ return [];
18735
+ } catch (error) {
18736
+ this.logger?.error("Failed to get available commands", error);
18737
+ return [];
18738
+ }
18739
+ },
18740
+ reportTelemetry: async (eventName, payload) => {
18741
+ try {
18742
+ if (this.provider?.reportTelemetry) await this.provider.reportTelemetry(eventName, payload);
18743
+ else this.logger?.warn("Provider does not support reportTelemetry");
18744
+ } catch (error) {
18745
+ this.logger?.error("Failed to report telemetry", error);
18746
+ }
18747
+ },
18748
+ respondToSampling: async (sessionId, response) => {
18749
+ try {
18750
+ if (this.provider?.respondToSampling) {
18751
+ await this.provider.respondToSampling(sessionId, response);
18752
+ this.logger?.info("Responded to sampling request", {
18753
+ sessionId,
18754
+ requestId: response.id,
18755
+ approved: response.approved
18756
+ });
18757
+ } else this.logger?.warn("Provider does not support respondToSampling");
18758
+ } catch (error) {
18759
+ this.logger?.error("Failed to respond to sampling request", error);
18760
+ throw error;
18761
+ }
18762
+ },
18763
+ respondToRoots: async (sessionId, response) => {
18764
+ try {
18765
+ if (this.provider?.respondToRoots) {
18766
+ await this.provider.respondToRoots(sessionId, response);
18767
+ this.logger?.info("Responded to roots request", {
18768
+ sessionId,
18769
+ requestId: response.id,
18770
+ approved: response.approved
18771
+ });
18772
+ } else this.logger?.warn("Provider does not support respondToRoots");
18773
+ } catch (error) {
18774
+ this.logger?.error("Failed to respond to roots request", error);
18775
+ throw error;
18776
+ }
18777
+ },
18778
+ subscribeSamplingRequests: (serverName, callback) => {
18779
+ if (this.provider?.subscribeSamplingRequests) {
18780
+ this.logger?.info("Subscribing to sampling requests", { serverName });
18781
+ return this.provider.subscribeSamplingRequests(serverName, callback);
18782
+ }
18783
+ this.logger?.warn("Provider does not support subscribeSamplingRequests");
18784
+ return () => {};
18785
+ },
18786
+ subscribeRootsRequests: (serverName, callback) => {
18787
+ if (this.provider?.subscribeRootsRequests) {
18788
+ this.logger?.info("Subscribing to roots requests", { serverName });
18789
+ return this.provider.subscribeRootsRequests(serverName, callback);
18790
+ }
18791
+ this.logger?.warn("Provider does not support subscribeRootsRequests");
18792
+ return () => {};
18793
+ },
18349
18794
  models: this.createModelsResource()
18350
18795
  };
18351
18796
  }
@@ -18412,6 +18857,154 @@ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
18412
18857
  return AccountStatus;
18413
18858
  }({});
18414
18859
 
18860
+ //#endregion
18861
+ //#region ../agent-provider/src/backend/service/oauth-repository-service.ts
18862
+ /**
18863
+ * OAuth Repository Service
18864
+ *
18865
+ * 封装 OAuth 连接器相关的仓库和分支操作
18866
+ */
18867
+ /**
18868
+ * OAuth Repository Service
18869
+ *
18870
+ * 提供仓库和分支的查询操作
18871
+ */
18872
+ var OAuthRepositoryService = class {
18873
+ /**
18874
+ * 获取仓库分支列表
18875
+ * API 端点: GET /console/as/connector/oauth/{name}/branches
18876
+ *
18877
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
18878
+ * @param params 平台特定的查询参数
18879
+ * @param page 页码,从1开始,0表示不分页获取全部
18880
+ * @param perPage 每页数量,最大100
18881
+ * @returns Promise<OauthBranch[]> 分支列表
18882
+ *
18883
+ * @example
18884
+ * ```typescript
18885
+ * // GitHub
18886
+ * const branches = await service.getBranches('github', {
18887
+ * owner: 'CodeBuddy-Official-Account',
18888
+ * repo: 'CodeBuddyIDE'
18889
+ * });
18890
+ *
18891
+ * // Gongfeng
18892
+ * const branches = await service.getBranches('gongfeng', {
18893
+ * project_id: '1611499'
18894
+ * });
18895
+ *
18896
+ * // CNB
18897
+ * const branches = await service.getBranches('cnb', {
18898
+ * repo: 'genie/genie-ide'
18899
+ * });
18900
+ * ```
18901
+ */
18902
+ async getBranches(connector, params, page = 0, perPage = 100) {
18903
+ try {
18904
+ const url = `/console/as/connector/oauth/${connector}/branches?${this.buildBranchQueryParams(connector, params, page, perPage).toString()}`;
18905
+ console.log(`[OAuthRepositoryService] GET ${url}`);
18906
+ const apiResponse = await httpService.get(url);
18907
+ if (!apiResponse.data) {
18908
+ console.warn(`[OAuthRepositoryService] No data in branches response for ${connector}`);
18909
+ return [];
18910
+ }
18911
+ const branches = apiResponse.data.branches || [];
18912
+ console.log(`[OAuthRepositoryService] Retrieved ${branches.length} branches from ${connector}`);
18913
+ return branches;
18914
+ } catch (error) {
18915
+ console.error(`[OAuthRepositoryService] Failed to get branches from ${connector}:`, error);
18916
+ throw error;
18917
+ }
18918
+ }
18919
+ /**
18920
+ * 获取仓库列表
18921
+ * API 端点: GET /console/as/connector/oauth/{name}/repos
18922
+ *
18923
+ * Note: 由于工蜂原生支持的 Search 能力会匹配 path/name/description 部分,
18924
+ * 且不支持定制,不满足产品要求(只按 name 匹配),因此前端拉取全量数据后做筛选。
18925
+ *
18926
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
18927
+ * @param page 页码,从1开始,0表示不分页获取全部
18928
+ * - GitHub 只支持全量数据,必须传 0
18929
+ * - 工蜂和 CNB 依据前端逻辑而定
18930
+ * @param perPage 每页数量,最大100
18931
+ * @returns Promise<ListReposResponse> 仓库列表响应
18932
+ *
18933
+ * @example
18934
+ * ```typescript
18935
+ * // GitHub - 必须传 page=0 获取全量数据
18936
+ * const response = await service.getRepositories('github', 0, 100);
18937
+ * // response.github_repos 是 map: installation_id => repo[]
18938
+ *
18939
+ * // Gongfeng
18940
+ * const response = await service.getRepositories('gongfeng', 0, 100);
18941
+ * // response.gongfeng_repos 是数组
18942
+ *
18943
+ * // CNB
18944
+ * const response = await service.getRepositories('cnb', 0, 100);
18945
+ * // response.cnb_repos 是数组
18946
+ * ```
18947
+ */
18948
+ async getRepositories(connector, page = 0, perPage = 100) {
18949
+ try {
18950
+ const queryParams = new URLSearchParams();
18951
+ queryParams.append("page", String(page));
18952
+ queryParams.append("per_page", String(Math.min(perPage, 100)));
18953
+ const url = `/console/as/connector/oauth/${connector}/repos?${queryParams.toString()}`;
18954
+ console.log(`[OAuthRepositoryService] GET ${url}`);
18955
+ const apiResponse = await httpService.get(url);
18956
+ if (!apiResponse.data) {
18957
+ console.warn(`[OAuthRepositoryService] No data in repos response for ${connector}`);
18958
+ return {};
18959
+ }
18960
+ const response = apiResponse.data;
18961
+ this.logRepositoryCounts(response);
18962
+ return response;
18963
+ } catch (error) {
18964
+ console.error(`[OAuthRepositoryService] Failed to get repos from ${connector}:`, error);
18965
+ throw error;
18966
+ }
18967
+ }
18968
+ /**
18969
+ * 构建分支查询参数
18970
+ */
18971
+ buildBranchQueryParams(connector, params, page, perPage) {
18972
+ const queryParams = new URLSearchParams();
18973
+ queryParams.append("page", String(page));
18974
+ queryParams.append("per_page", String(Math.min(perPage, 100)));
18975
+ if (connector === "github") {
18976
+ const githubParams = params;
18977
+ if (!githubParams.owner || !githubParams.repo) throw new Error("GitHub requires owner and repo parameters");
18978
+ queryParams.append("owner", githubParams.owner);
18979
+ queryParams.append("repo", githubParams.repo);
18980
+ } else if (connector === "gongfeng") {
18981
+ const gongfengParams = params;
18982
+ if (!gongfengParams.project_id) throw new Error("Gongfeng requires project_id parameter");
18983
+ queryParams.append("project_id", gongfengParams.project_id);
18984
+ } else if (connector === "cnb") {
18985
+ const cnbParams = params;
18986
+ if (!cnbParams.repo) throw new Error("CNB requires repo parameter");
18987
+ queryParams.append("repo", cnbParams.repo);
18988
+ } else throw new Error(`Unknown connector: ${connector}`);
18989
+ return queryParams;
18990
+ }
18991
+ /**
18992
+ * 记录仓库数量日志
18993
+ */
18994
+ logRepositoryCounts(response) {
18995
+ if (response.github_repos) {
18996
+ const totalCount = Object.values(response.github_repos).reduce((sum, repos) => sum + repos.length, 0);
18997
+ console.log(`[OAuthRepositoryService] Retrieved ${totalCount} GitHub repos across ${Object.keys(response.github_repos).length} installations`);
18998
+ }
18999
+ if (response.gongfeng_repos) console.log(`[OAuthRepositoryService] Retrieved ${response.gongfeng_repos.length} Gongfeng repos`);
19000
+ if (response.cnb_repos) console.log(`[OAuthRepositoryService] Retrieved ${response.cnb_repos.length} CNB repos`);
19001
+ }
19002
+ };
19003
+ /**
19004
+ * OAuth Repository Service 单例实例
19005
+ */
19006
+ const oauthRepositoryService = new OAuthRepositoryService();
19007
+
18415
19008
  //#endregion
18416
19009
  //#region ../agent-provider/src/backend/backend-provider.ts
18417
19010
  /**
@@ -18420,28 +19013,24 @@ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
18420
19013
  * 封装与后端 API 的 HTTP 通信
18421
19014
  */
18422
19015
  /**
18423
- * 判断当前是否在 SSO 域名下
18424
- * SSO 域名格式: xxx.sso.copilot.tencent.com xxx.sso.codebuddy.cn
19016
+ * 判断当前账号是否是 SSO 账号
19017
+ * 通过 account.accountType === 'sso' 来判断,这种不行,因为未登录之前account 为空
18425
19018
  */
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");
19019
+ const safeParseJSON = (jsonString) => {
19020
+ try {
19021
+ return JSON.parse(jsonString);
19022
+ } catch (error) {
19023
+ return {};
19024
+ }
18429
19025
  };
18430
19026
  /**
18431
- * 获取登录页面 URL
18432
- * - SSO 域名下需要跳转到对应的预发/生产域名
18433
- * - 非 SSO 域名直接使用当前域名
19027
+ * 根据路径获取完整 URL
19028
+ * - SSO 账号需要跳转到对应的预发/生产域名
19029
+ * - 非 SSO 账号直接使用当前域名
19030
+ * @param path 路径,如 '/login'、'/logout'、'/home' 等
19031
+ * @returns 完整的 URL 地址
18434
19032
  */
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
- };
19033
+ const getFullUrl = (path) => `${window.location.origin}${path}`;
18445
19034
  /** 获取当前域名的账号选择页面 URL */
18446
19035
  const getSelectAccountUrl = () => `${window.location.origin}/login/select`;
18447
19036
  /** localStorage 中存储选中账号 ID 的 key */
@@ -18478,12 +19067,30 @@ var BackendProvider = class {
18478
19067
  constructor(config) {
18479
19068
  httpService.setBaseURL(config.baseUrl);
18480
19069
  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);
19070
+ httpService.onUnauthorized(() => this.handleUnauthorized());
19071
+ }
19072
+ /**
19073
+ * 处理 401 未授权错误
19074
+ * 先尝试刷新 token,失败后再执行登出流程
19075
+ *
19076
+ * @throws 如果 token 刷新失败,抛出错误通知 HttpService 不要重试
19077
+ */
19078
+ async handleUnauthorized() {
19079
+ console.log("[BackendProvider] User unauthorized (401), attempting token refresh first");
19080
+ try {
19081
+ if (await this.refreshToken()) {
19082
+ console.log("[BackendProvider] Token refresh successful after 401, user still logged in");
19083
+ return;
19084
+ }
19085
+ throw new Error("Token refresh returned null");
19086
+ } catch (error) {
19087
+ console.error("[BackendProvider] Token refresh failed after 401:", error);
19088
+ console.log("[BackendProvider] Token refresh failed, triggering logout");
19089
+ this.logout().catch((logoutError) => {
19090
+ console.error("[BackendProvider] Logout failed in 401 handler:", logoutError);
18485
19091
  });
18486
- });
19092
+ throw error;
19093
+ }
18487
19094
  }
18488
19095
  /**
18489
19096
  * 获取当前账号信息
@@ -18528,7 +19135,7 @@ var BackendProvider = class {
18528
19135
  return account;
18529
19136
  }
18530
19137
  const redirectUrl = encodeURIComponent(window.location.href);
18531
- window.location.href = `${getSelectAccountUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
19138
+ window.location.href = `${getSelectAccountUrl()}?platform=agents&state=0&redirect_uri=${redirectUrl}`;
18532
19139
  accountService.setAccount(null);
18533
19140
  return null;
18534
19141
  } catch (error) {
@@ -18551,7 +19158,8 @@ var BackendProvider = class {
18551
19158
  activeStatus: connector.active_status,
18552
19159
  displayName: connector.display_name,
18553
19160
  oauthClientId: connector.oauth_client_id,
18554
- oauthRedirectUrl: connector.oauth_redirect_url
19161
+ oauthRedirectUrl: connector.oauth_redirect_url,
19162
+ oauthAppName: connector.oauth_app_name
18555
19163
  })) };
18556
19164
  }
18557
19165
  throw result;
@@ -18633,7 +19241,8 @@ var BackendProvider = class {
18633
19241
  connectStatus: connector.connect_status,
18634
19242
  displayName: connector.display_name,
18635
19243
  oauthClientId: connector.oauth_client_id,
18636
- oauthRedirectUrl: connector.oauth_redirect_url
19244
+ oauthRedirectUrl: connector.oauth_redirect_url,
19245
+ oauthAppName: connector.oauth_app_name
18637
19246
  })) };
18638
19247
  }
18639
19248
  throw result;
@@ -18724,6 +19333,9 @@ var BackendProvider = class {
18724
19333
  PackageCode: void 0,
18725
19334
  name: ""
18726
19335
  };
19336
+ const productFeatures = typeof window !== "undefined" && window.PRODUCT_FEATURES ? safeParseJSON(window.PRODUCT_FEATURES) : {};
19337
+ console.log("[PRODUCT_FEATURES]", productFeatures);
19338
+ if (!productFeatures.billing) return defaultPlan;
18727
19339
  try {
18728
19340
  const now = /* @__PURE__ */ new Date();
18729
19341
  const futureDate = new Date(now.getTime() + 101 * 365 * 24 * 60 * 60 * 1e3);
@@ -18745,7 +19357,7 @@ var BackendProvider = class {
18745
19357
  if (!time) return 0;
18746
19358
  return new Date(time).getTime();
18747
19359
  };
18748
- const dailyCredits = [CommodityCode.free, CommodityCode.freeMon];
19360
+ const dailyCredits = [CommodityCode.free];
18749
19361
  const planResources = resources.map((r) => {
18750
19362
  const isDaily = dailyCredits.includes(r.PackageCode);
18751
19363
  const endTime = isDaily ? r.CycleEndTime : r.DeductionEndTime;
@@ -18766,10 +19378,11 @@ var BackendProvider = class {
18766
19378
  CommodityCode.proMon,
18767
19379
  CommodityCode.proMonPlus,
18768
19380
  CommodityCode.proYear,
19381
+ CommodityCode.freeMon,
18769
19382
  CommodityCode.extra
18770
19383
  ].includes(code)) return 1;
18771
19384
  if ([CommodityCode.gift, CommodityCode.activity].includes(code)) return 2;
18772
- if ([CommodityCode.free, CommodityCode.freeMon].includes(code)) return 3;
19385
+ if ([CommodityCode.free].includes(code)) return 3;
18773
19386
  return 4;
18774
19387
  };
18775
19388
  return getPriority(a.packageCode) - getPriority(b.packageCode);
@@ -18890,38 +19503,69 @@ var BackendProvider = class {
18890
19503
  */
18891
19504
  async login() {
18892
19505
  const redirectUrl = encodeURIComponent(window.location.href);
18893
- window.location.href = `${getLoginUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
19506
+ window.location.href = `${getFullUrl("/login")}?platform=agents&state=0&redirect_uri=${redirectUrl}`;
18894
19507
  }
18895
19508
  /**
18896
19509
  * 登出账号
18897
- * Web 环境: 通过 iframe 访问登出 URL 清除 cookie
19510
+ *
19511
+ * 策略:
19512
+ * - IOA 企业:用 iframe 走 SSO/SAML SLO 登出链路(涉及跨域重定向),通过轮询 iframe URL 变化检测完成
19513
+ * - 非 IOA 企业:直接用 httpService 请求 /console/logout,速度快
18898
19514
  */
18899
19515
  async logout() {
18900
- const url = `${httpService.getAxiosInstance().defaults.baseURL}/console/logout`;
19516
+ const account = accountService.getAccount();
19517
+ if (account?.enterpriseId && ["esoikz80kd8g", "etahzsqej0n4"].includes(account.enterpriseId)) await this.logoutViaIframe();
19518
+ else await this.logoutViaHttp();
19519
+ localStorage.removeItem(SELECTED_ACCOUNT_KEY);
19520
+ accountService.clearAccount();
19521
+ }
19522
+ /**
19523
+ * IOA 企业登出:通过 iframe 走 SSO/SAML SLO 登出链路
19524
+ * 轮询 iframe URL 变化检测完成,兜底超时 5 秒
19525
+ */
19526
+ async logoutViaIframe() {
19527
+ const logoutUrl = `${httpService.getAxiosInstance().defaults.baseURL}/console/logout`;
18901
19528
  try {
18902
19529
  await new Promise((resolve) => {
18903
19530
  const iframe = document.createElement("iframe");
18904
19531
  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 = () => {
19532
+ iframe.src = logoutUrl;
19533
+ let pollTimer;
19534
+ let settled = false;
19535
+ const done = () => {
19536
+ if (settled) return;
19537
+ settled = true;
19538
+ clearInterval(pollTimer);
18911
19539
  clearTimeout(timeout);
18912
19540
  if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
18913
- };
18914
- iframe.onerror = () => {
18915
- cleanup();
18916
19541
  resolve();
18917
19542
  };
19543
+ let wasRedirecting = false;
19544
+ pollTimer = setInterval(() => {
19545
+ try {
19546
+ const href = iframe.contentWindow?.location?.href;
19547
+ if (wasRedirecting && href) done();
19548
+ } catch {
19549
+ wasRedirecting = true;
19550
+ }
19551
+ }, 100);
19552
+ const timeout = setTimeout(done, 5e3);
19553
+ iframe.onerror = done;
18918
19554
  document.body.appendChild(iframe);
18919
19555
  });
18920
19556
  } catch (error) {
18921
- console.error("[BackendProvider] logout failed:", error);
19557
+ console.error("[BackendProvider] logout via iframe failed:", error);
19558
+ }
19559
+ }
19560
+ /**
19561
+ * 非 IOA 企业登出:直接 HTTP 请求 /console/logout
19562
+ */
19563
+ async logoutViaHttp() {
19564
+ try {
19565
+ await httpService.get("/console/logout");
19566
+ } catch (error) {
19567
+ console.error("[BackendProvider] logout via http failed:", error);
18922
19568
  }
18923
- localStorage.removeItem(SELECTED_ACCOUNT_KEY);
18924
- accountService.clearAccount();
18925
19569
  }
18926
19570
  /**
18927
19571
  * 批量切换插件状态
@@ -18953,6 +19597,52 @@ var BackendProvider = class {
18953
19597
  return null;
18954
19598
  }
18955
19599
  }
19600
+ /**
19601
+ * 刷新 Token
19602
+ * 通过调用 getAccount 刷新 cookie,适用于 Cloud 场景下页面切换回来时刷新登录态
19603
+ * @returns Promise<Account | null> 刷新后的账号信息
19604
+ */
19605
+ async refreshToken() {
19606
+ console.log("[BackendProvider] Refreshing token...");
19607
+ try {
19608
+ const account = await this.getAccount();
19609
+ console.log("[BackendProvider] Token refreshed, account:", account?.uid);
19610
+ return account;
19611
+ } catch (error) {
19612
+ console.error("[BackendProvider] refreshToken failed:", error);
19613
+ return null;
19614
+ }
19615
+ }
19616
+ /**
19617
+ * 获取仓库分支列表
19618
+ * API 端点: GET /console/as/connector/oauth/{name}/branches
19619
+ *
19620
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
19621
+ * @param params 平台特定的查询参数
19622
+ * @param page 页码,从1开始,0表示不分页获取全部
19623
+ * @param perPage 每页数量,最大100
19624
+ * @returns Promise<OauthBranch[]> 分支列表
19625
+ */
19626
+ async getBranches(connector, params, page = 0, perPage = 100) {
19627
+ return oauthRepositoryService.getBranches(connector, params, page, perPage);
19628
+ }
19629
+ /**
19630
+ * 获取仓库列表
19631
+ * API 端点: GET /console/as/connector/oauth/{name}/repos
19632
+ *
19633
+ * Note: 由于工蜂原生支持的 Search 能力会匹配 path/name/description 部分,
19634
+ * 且不支持定制,不满足产品要求(只按 name 匹配),因此前端拉取全量数据后做筛选。
19635
+ *
19636
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
19637
+ * @param page 页码,从1开始,0表示不分页获取全部
19638
+ * - GitHub 只支持全量数据,必须传 0
19639
+ * - 工蜂和 CNB 依据前端逻辑而定
19640
+ * @param perPage 每页数量,最大100
19641
+ * @returns Promise<ListReposResponse> 仓库列表响应
19642
+ */
19643
+ async getRepositories(connector, page = 0, perPage = 100) {
19644
+ return oauthRepositoryService.getRepositories(connector, page, perPage);
19645
+ }
18956
19646
  };
18957
19647
  /**
18958
19648
  * 创建 BackendProvider 实例
@@ -18992,6 +19682,7 @@ const BACKEND_REQUEST_TYPES = {
18992
19682
  GET_FILE: "backend:get-file",
18993
19683
  RELOAD_WINDOW: "backend:reload-window",
18994
19684
  CLOSE_AGENT_MANAGER: "backend:close-agent-manager",
19685
+ OPEN_EXTERNAL: "backend:open-external",
18995
19686
  BATCH_TOGGLE_PLUGINS: "backend:batch-toggle-plugins",
18996
19687
  GET_SUPPORT_SCENES: "backend:get-support-scenes"
18997
19688
  };
@@ -19287,6 +19978,20 @@ var IPCBackendProvider = class {
19287
19978
  }
19288
19979
  }
19289
19980
  /**
19981
+ * 在外部浏览器中打开链接
19982
+ * IDE 环境: 通过 IPC 通知 IDE 使用 vscode.env.openExternal 打开 URL
19983
+ * @param url 要打开的 URL
19984
+ */
19985
+ async openExternal(url) {
19986
+ this.log("Opening external URL via IPC:", url);
19987
+ try {
19988
+ await this.sendBackendRequest(BACKEND_REQUEST_TYPES.OPEN_EXTERNAL, { url });
19989
+ } catch (error) {
19990
+ this.log("Open external request failed:", error);
19991
+ throw error;
19992
+ }
19993
+ }
19994
+ /**
19290
19995
  * 批量切换插件状态
19291
19996
  * IDE 环境: 通过 IPC 调用 Extension Host 的 PluginService
19292
19997
  */