@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.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
@@ -2283,6 +2285,24 @@ var CloudAgentConnection = class {
2283
2285
  },
2284
2286
  onUsageUpdate: (usage) => {
2285
2287
  this.emit("usageUpdate", usage);
2288
+ },
2289
+ onExtNotification: (method, params) => {
2290
+ console.log("[CloudConnection] Received extNotification:", {
2291
+ method,
2292
+ paramsKeys: Object.keys(params)
2293
+ });
2294
+ if (method === ExtensionMethod.COMMAND) {
2295
+ const action = params.action;
2296
+ const commandParams = params.params;
2297
+ console.log("[CloudConnection] Emitting command event:", {
2298
+ action,
2299
+ paramsKeys: commandParams ? Object.keys(commandParams) : []
2300
+ });
2301
+ this.emit("command", {
2302
+ action,
2303
+ params: commandParams
2304
+ });
2305
+ }
2286
2306
  }
2287
2307
  });
2288
2308
  this.setupEventForwarding();
@@ -2411,7 +2431,7 @@ var CloudAgentConnection = class {
2411
2431
  }
2412
2432
  async createSession(params) {
2413
2433
  return {
2414
- ...await this.client.loadSession(this.agentId, this.cwd),
2434
+ ...await this.client.createSession(this.cwd),
2415
2435
  sessionId: this.agentId
2416
2436
  };
2417
2437
  }
@@ -2529,6 +2549,16 @@ var CloudAgentConnection = class {
2529
2549
  get sessionConnectionInfo() {
2530
2550
  return this._sessionConnectionInfo;
2531
2551
  }
2552
+ async reportTelemetry(eventName, payload) {
2553
+ try {
2554
+ await this.client.extMethod("reportTelemetry", {
2555
+ eventName,
2556
+ payload
2557
+ });
2558
+ } catch (error) {
2559
+ console.warn("[CloudAgentConnection] reportTelemetry failed:", error);
2560
+ }
2561
+ }
2532
2562
  async extMethod(method, params) {
2533
2563
  return this.client.extMethod(method, params);
2534
2564
  }
@@ -15923,7 +15953,7 @@ var axios_default = axios;
15923
15953
  * 特性:
15924
15954
  * - 单例模式,全局唯一实例,延迟初始化(首次使用时自动创建)
15925
15955
  * - 支持拦截器注册(其他模块可注入 header)
15926
- * - 统一 401/403 处理
15956
+ * - 统一 401 处理(支持自动刷新 token 并重试)
15927
15957
  * - 自动携带凭证(withCredentials)
15928
15958
  * - 类型安全
15929
15959
  *
@@ -15963,6 +15993,8 @@ var HttpService = class HttpService {
15963
15993
  */
15964
15994
  constructor(config = {}) {
15965
15995
  this.unauthorizedCallbacks = /* @__PURE__ */ new Set();
15996
+ this.isRefreshing = false;
15997
+ this.refreshSubscribers = [];
15966
15998
  this.config = config;
15967
15999
  this.axiosInstance = axios_default.create({
15968
16000
  baseURL: config.baseURL?.replace(/\/$/, "") || "",
@@ -16003,18 +16035,54 @@ var HttpService = class HttpService {
16003
16035
  }, (error) => Promise.reject(error));
16004
16036
  }
16005
16037
  /**
16006
- * 注册默认响应拦截器(处理 401/403)
16038
+ * 注册默认响应拦截器(处理 401,支持自动重试)
16007
16039
  */
16008
16040
  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();
16041
+ this.axiosInstance.interceptors.response.use((response) => response, async (error) => {
16042
+ const originalRequest = error.config;
16043
+ if (error.response?.status === 401 && originalRequest && !originalRequest._retry) {
16044
+ if (originalRequest.url?.includes("/console/accounts")) {
16045
+ console.warn("[HttpService] Unauthorized 401 on refresh endpoint, not retrying");
16046
+ return Promise.reject(error);
16047
+ }
16048
+ console.warn("[HttpService] Unauthorized 401, attempting token refresh and retry");
16049
+ originalRequest._retry = true;
16050
+ if (this.isRefreshing) return new Promise((resolve, reject) => {
16051
+ this.refreshSubscribers.push((success) => {
16052
+ if (success) this.axiosInstance.request(originalRequest).then(resolve).catch(reject);
16053
+ else reject(error);
16054
+ });
16055
+ });
16056
+ this.isRefreshing = true;
16057
+ try {
16058
+ await this.triggerUnauthorizedCallbacks();
16059
+ this.onRefreshSuccess();
16060
+ return this.axiosInstance.request(originalRequest);
16061
+ } catch (refreshError) {
16062
+ this.onRefreshFailure();
16063
+ return Promise.reject(error);
16064
+ } finally {
16065
+ this.isRefreshing = false;
16066
+ }
16013
16067
  }
16014
16068
  return Promise.reject(error);
16015
16069
  });
16016
16070
  }
16017
16071
  /**
16072
+ * token 刷新成功,通知所有等待的请求
16073
+ */
16074
+ onRefreshSuccess() {
16075
+ this.refreshSubscribers.forEach((callback) => callback(true));
16076
+ this.refreshSubscribers = [];
16077
+ }
16078
+ /**
16079
+ * token 刷新失败,通知所有等待的请求
16080
+ */
16081
+ onRefreshFailure() {
16082
+ this.refreshSubscribers.forEach((callback) => callback(false));
16083
+ this.refreshSubscribers = [];
16084
+ }
16085
+ /**
16018
16086
  * 注册请求拦截器
16019
16087
  * @param onFulfilled 请求成功拦截器
16020
16088
  * @param onRejected 请求失败拦截器
@@ -16091,16 +16159,18 @@ var HttpService = class HttpService {
16091
16159
  this.unauthorizedCallbacks.delete(callback);
16092
16160
  }
16093
16161
  /**
16094
- * 触发所有 401 回调
16162
+ * 触发所有 401 回调并等待完成
16163
+ * @returns Promise,等待所有回调完成
16164
+ * @throws 如果任何回调失败,则抛出错误
16095
16165
  */
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
- });
16166
+ async triggerUnauthorizedCallbacks() {
16167
+ const callbacks = Array.from(this.unauthorizedCallbacks);
16168
+ const failedResults = (await Promise.allSettled(callbacks.map((callback) => callback()))).filter((r) => r.status === "rejected");
16169
+ if (failedResults.length > 0) {
16170
+ const errors = failedResults.map((r) => r.reason);
16171
+ console.error("[HttpService] Some unauthorized callbacks failed:", errors);
16172
+ throw errors[0];
16173
+ }
16104
16174
  }
16105
16175
  /**
16106
16176
  * 更新 authToken
@@ -16212,6 +16282,7 @@ var AccountService = class {
16212
16282
  this.initPromise = null;
16213
16283
  this.initResolve = null;
16214
16284
  this.requestInterceptorId = null;
16285
+ this.crossTabBroadcaster = null;
16215
16286
  this.initPromise = new Promise((resolve) => {
16216
16287
  this.initResolve = resolve;
16217
16288
  });
@@ -16260,15 +16331,34 @@ var AccountService = class {
16260
16331
  this.initialized = true;
16261
16332
  this.initResolve?.(account);
16262
16333
  }
16263
- if (!wasInitialized || prev?.uid !== account?.uid) this.notifyListeners();
16334
+ if (!wasInitialized || prev?.uid !== account?.uid) {
16335
+ this.notifyListeners();
16336
+ if (account && this.crossTabBroadcaster) this.crossTabBroadcaster.broadcastLogin();
16337
+ }
16264
16338
  }
16265
16339
  /**
16266
16340
  * 清除账号(登出)
16341
+ * 先广播登出消息,再清除本地账号
16267
16342
  */
16268
16343
  clearAccount() {
16344
+ if (this.crossTabBroadcaster) this.crossTabBroadcaster.broadcastLogout();
16345
+ this.setAccount(null);
16346
+ }
16347
+ /**
16348
+ * 静默清除账号(不广播)
16349
+ * 用于收到其他标签页 logout 消息时,避免循环广播
16350
+ */
16351
+ clearAccountSilently() {
16269
16352
  this.setAccount(null);
16270
16353
  }
16271
16354
  /**
16355
+ * 设置跨标签页认证同步广播器
16356
+ * 应在应用初始化时由上层(如 agent-ui)调用
16357
+ */
16358
+ setCrossTabBroadcaster(broadcaster) {
16359
+ this.crossTabBroadcaster = broadcaster;
16360
+ }
16361
+ /**
16272
16362
  * 订阅账号变化
16273
16363
  * @param callback 变化时的回调函数
16274
16364
  * @returns 取消订阅函数
@@ -16333,6 +16423,11 @@ var AccountService = class {
16333
16423
  * 导出单例实例
16334
16424
  */
16335
16425
  const accountService = new AccountService();
16426
+ /**
16427
+ * 暴露给全局,供 Agent Manager 直接调用 setAccount 刷新 Widget 状态
16428
+ * 这是为了解决 IDE 环境中 IPC 事件无法直接触发 Widget 账号刷新的问题
16429
+ */
16430
+ if (typeof window !== "undefined") window.__genieAccountService = accountService;
16336
16431
 
16337
16432
  //#endregion
16338
16433
  //#region ../agent-provider/src/common/utils/concurrency.ts
@@ -16435,20 +16530,32 @@ var CosUploadService = class {
16435
16530
  * 上传单个文件到 COS
16436
16531
  *
16437
16532
  * @param file - 要上传的文件
16533
+ * @param abortSignal - 可选的 AbortSignal,用于取消上传
16438
16534
  * @returns 上传结果,包含访问 URL 或错误信息
16439
16535
  */
16440
- async uploadFile(file) {
16536
+ async uploadFile(file, abortSignal) {
16441
16537
  const filename = file.name;
16442
16538
  this.logger?.info(`[CosUploadService] Uploading file: ${filename}`);
16443
16539
  try {
16540
+ if (abortSignal?.aborted) return {
16541
+ success: false,
16542
+ error: "Upload cancelled",
16543
+ aborted: true
16544
+ };
16444
16545
  const objectKey = this.generateObjectKey(filename);
16445
16546
  this.logger?.debug(`[CosUploadService] Generated objectKey: ${objectKey}`);
16446
16547
  const presignedItem = (await this.getPresignedUrls([objectKey])).items[0];
16447
16548
  if (!presignedItem) throw new Error("No presigned URL item returned");
16549
+ if (abortSignal?.aborted) return {
16550
+ success: false,
16551
+ error: "Upload cancelled",
16552
+ aborted: true
16553
+ };
16448
16554
  const uploadResponse = await fetch(presignedItem.upload_url, {
16449
16555
  method: "PUT",
16450
16556
  body: file,
16451
- headers: { "Content-Type": file.type || "application/octet-stream" }
16557
+ headers: { "Content-Type": file.type || "application/octet-stream" },
16558
+ signal: abortSignal
16452
16559
  });
16453
16560
  if (!uploadResponse.ok) {
16454
16561
  const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
@@ -16462,6 +16569,11 @@ var CosUploadService = class {
16462
16569
  objectKey
16463
16570
  };
16464
16571
  } catch (error) {
16572
+ if (error instanceof Error && error.name === "AbortError") return {
16573
+ success: false,
16574
+ error: "Upload cancelled",
16575
+ aborted: true
16576
+ };
16465
16577
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
16466
16578
  this.logger?.error(`[CosUploadService] Upload failed: ${filename}`, error);
16467
16579
  return {
@@ -16476,14 +16588,25 @@ var CosUploadService = class {
16476
16588
  * 使用并发控制,限制同时上传的文件数量
16477
16589
  *
16478
16590
  * @param files - 要上传的文件数组
16591
+ * @param abortSignal - 可选的 AbortSignal,用于取消上传
16479
16592
  * @returns 所有文件的上传结果
16480
16593
  */
16481
- async uploadFiles(files) {
16594
+ async uploadFiles(files, abortSignal) {
16482
16595
  if (files.length === 0) return {
16483
16596
  success: true,
16484
16597
  urls: [],
16485
16598
  results: []
16486
16599
  };
16600
+ if (abortSignal?.aborted) return {
16601
+ success: false,
16602
+ error: "Upload cancelled",
16603
+ aborted: true,
16604
+ results: files.map(() => ({
16605
+ success: false,
16606
+ error: "Upload cancelled",
16607
+ aborted: true
16608
+ }))
16609
+ };
16487
16610
  this.logger?.info(`[CosUploadService] Uploading ${files.length} file(s) with concurrency ${this.uploadConcurrency}`);
16488
16611
  try {
16489
16612
  const fileInfos = files.map((file) => ({
@@ -16495,6 +16618,12 @@ var CosUploadService = class {
16495
16618
  this.logger?.debug(`[CosUploadService] Got ${presignedResponse.items.length} presigned URLs`);
16496
16619
  if (presignedResponse.items.length !== fileInfos.length) throw new Error(`Expected ${fileInfos.length} presigned URLs, got ${presignedResponse.items.length}`);
16497
16620
  const results = (await runWithConcurrencySettled(fileInfos.map(({ file }, index) => async () => {
16621
+ if (abortSignal?.aborted) return {
16622
+ success: false,
16623
+ error: "Upload cancelled",
16624
+ aborted: true,
16625
+ objectKey: fileInfos[index].objectKey
16626
+ };
16498
16627
  const presignedItem = presignedResponse.items[index];
16499
16628
  if (!presignedItem) return {
16500
16629
  success: false,
@@ -16505,7 +16634,8 @@ var CosUploadService = class {
16505
16634
  const uploadResponse = await fetch(presignedItem.upload_url, {
16506
16635
  method: "PUT",
16507
16636
  body: file,
16508
- headers: { "Content-Type": file.type || "application/octet-stream" }
16637
+ headers: { "Content-Type": file.type || "application/octet-stream" },
16638
+ signal: abortSignal
16509
16639
  });
16510
16640
  if (!uploadResponse.ok) {
16511
16641
  const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
@@ -16522,6 +16652,12 @@ var CosUploadService = class {
16522
16652
  objectKey: presignedItem.object_key
16523
16653
  };
16524
16654
  } catch (error) {
16655
+ if (error instanceof Error && error.name === "AbortError") return {
16656
+ success: false,
16657
+ error: "Upload cancelled",
16658
+ aborted: true,
16659
+ objectKey: presignedItem.object_key
16660
+ };
16525
16661
  return {
16526
16662
  success: false,
16527
16663
  error: error instanceof Error ? error.message : "Unknown error",
@@ -16922,15 +17058,9 @@ var CloudAgentProvider = class CloudAgentProvider {
16922
17058
  const url = this.buildGetUrl("/console/as/conversations/", params);
16923
17059
  const apiResponse = await httpService.get(url);
16924
17060
  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
17061
  return {
16932
- agents,
16933
- pagination
17062
+ agents: apiResponse.data.conversations.map((a) => this.toAgentState(a)),
17063
+ pagination: apiResponse.data.pagination
16934
17064
  };
16935
17065
  } catch (error) {
16936
17066
  this.logger?.error("Failed to list agents:", error);
@@ -16940,13 +17070,21 @@ var CloudAgentProvider = class CloudAgentProvider {
16940
17070
  /**
16941
17071
  * Create a new conversation
16942
17072
  * POST {endpoint}/console/as/conversations
17073
+ * @param params - Session params containing cwd and optional configuration
16943
17074
  */
16944
- async create() {
17075
+ async create(params) {
16945
17076
  try {
16946
- const apiResponse = await httpService.post("/console/as/conversations/", {
16947
- prompt: "",
16948
- model: "deepseek-r1"
16949
- });
17077
+ const { options = {} } = params;
17078
+ const codebuddyMeta = options._meta?.["codebuddy.ai"];
17079
+ const tagsObj = options.tags || codebuddyMeta?.tags;
17080
+ const tagsArray = tagsObj ? Object.entries(tagsObj).map(([key, value]) => `${key}:${value}`) : void 0;
17081
+ const createPayload = {
17082
+ prompt: (options.prompt || "").slice(0, 100),
17083
+ model: options.model || "deepseek-r1",
17084
+ ...tagsArray && tagsArray.length > 0 ? { tags: tagsArray } : {}
17085
+ };
17086
+ console.log("[CloudAgentProvider] Creating conversation with payload:", createPayload);
17087
+ const apiResponse = await httpService.post("/console/as/conversations/", createPayload);
16950
17088
  if (!apiResponse.data) throw new Error("No data in API response");
16951
17089
  this.logger?.info(`Created conversation: ${apiResponse.data.id}`);
16952
17090
  return apiResponse.data.id;
@@ -17146,9 +17284,12 @@ var CloudAgentProvider = class CloudAgentProvider {
17146
17284
  this.logger?.warn("[CloudAgentProvider] No data in config response, returning empty models");
17147
17285
  return [];
17148
17286
  }
17149
- const models = apiResponse.data.models ?? [];
17150
- this.logger?.info(`[CloudAgentProvider] Retrieved ${models.length} models from /console/enterprises API`);
17151
- return models.map((model) => ({
17287
+ const productConfig = apiResponse.data;
17288
+ const allModels = productConfig.models ?? [];
17289
+ const cliModelIds = (productConfig.agents ?? []).find((agent) => agent.name === "cli")?.models ?? [];
17290
+ const filteredModels = cliModelIds.length > 0 ? allModels.filter((model) => cliModelIds.includes(model.id)) : allModels;
17291
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${filteredModels.length} models for cli agent (total: ${allModels.length})`);
17292
+ return filteredModels.map((model) => ({
17152
17293
  id: model.id,
17153
17294
  name: model.name ?? model.id,
17154
17295
  description: model.description,
@@ -17252,7 +17393,7 @@ var CloudAgentProvider = class CloudAgentProvider {
17252
17393
  /**
17253
17394
  * Upload files to cloud storage via COS presigned URL
17254
17395
  *
17255
- * @param params - files array (File objects in browser)
17396
+ * @param params - files array (File objects in browser), optional abortSignal
17256
17397
  * @returns Response with corresponding cloud URLs
17257
17398
  */
17258
17399
  async uploadFile(params) {
@@ -17262,12 +17403,13 @@ var CloudAgentProvider = class CloudAgentProvider {
17262
17403
  success: false,
17263
17404
  error: "No valid File objects provided"
17264
17405
  };
17265
- const result = await this.cosUploadService.uploadFiles(files);
17406
+ const result = await this.cosUploadService.uploadFiles(files, params.abortSignal);
17266
17407
  return {
17267
17408
  success: result.success,
17268
17409
  urls: result.urls,
17269
17410
  expireSeconds: result.expireSeconds,
17270
- error: result.error
17411
+ error: result.error,
17412
+ aborted: result.aborted
17271
17413
  };
17272
17414
  }
17273
17415
  /**
@@ -17309,20 +17451,22 @@ var CloudAgentProvider = class CloudAgentProvider {
17309
17451
  }
17310
17452
  /**
17311
17453
  * 获取支持的场景列表
17312
- * API 端点: GET /console/as/support/scenes
17454
+ * API 端点: GET /v2/as/support/scenes (不鉴权)
17313
17455
  * 用于 Welcome 页面的 QuickActions 快捷操作
17314
17456
  *
17457
+ * @param locale - 可选,语言环境(如 'zh-CN', 'en-US'),用于获取对应语言的场景数据
17315
17458
  * @returns Promise<SupportScene[]> 支持的场景列表
17316
17459
  */
17317
- async getSupportScenes() {
17460
+ async getSupportScenes(locale) {
17318
17461
  try {
17319
- const apiResponse = await httpService.get("/console/as/support/scenes");
17462
+ const url = this.buildGetUrl("/v2/as/support/scenes", locale ? { locale } : void 0);
17463
+ const apiResponse = await httpService.get(url);
17320
17464
  if (!apiResponse.data) {
17321
17465
  this.logger?.warn("[CloudAgentProvider] No data in support scenes response");
17322
17466
  return [];
17323
17467
  }
17324
17468
  const scenes = apiResponse.data.scenes || [];
17325
- this.logger?.info(`[CloudAgentProvider] Retrieved ${scenes.length} support scenes`);
17469
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${scenes.length} support scenes${locale ? ` for locale: ${locale}` : ""}`);
17326
17470
  return scenes;
17327
17471
  } catch (error) {
17328
17472
  this.logger?.error("[CloudAgentProvider] Failed to get support scenes:", error);
@@ -17338,7 +17482,8 @@ var CloudAgentProvider = class CloudAgentProvider {
17338
17482
  type: "cloud",
17339
17483
  status,
17340
17484
  createdAt: data.createdAt ? new Date(data.createdAt) : void 0,
17341
- capabilities: this.options.clientCapabilities
17485
+ capabilities: this.options.clientCapabilities,
17486
+ isUserDefinedTitle: data.isUserDefinedTitle
17342
17487
  };
17343
17488
  }
17344
17489
  /**
@@ -17354,6 +17499,23 @@ var CloudAgentProvider = class CloudAgentProvider {
17354
17499
  const queryString = searchParams.toString();
17355
17500
  return queryString ? `${path}?${queryString}` : path;
17356
17501
  }
17502
+ /**
17503
+ * 上报 telemetry 事件(Cloud 模式)
17504
+ * 通过 HTTP POST 发送到 /v2/report
17505
+ */
17506
+ async reportTelemetry(eventName, payload) {
17507
+ try {
17508
+ const events = [{
17509
+ eventCode: eventName,
17510
+ timestamp: Date.now(),
17511
+ reportDelay: 0,
17512
+ ...payload
17513
+ }];
17514
+ await httpService.post("/v2/report", events);
17515
+ } catch (error) {
17516
+ this.logger?.warn("reportTelemetry() failed:", error);
17517
+ }
17518
+ }
17357
17519
  };
17358
17520
 
17359
17521
  //#endregion
@@ -17402,6 +17564,7 @@ var ActiveSessionImpl = class {
17402
17564
  this._availableCommands = [];
17403
17565
  this.listeners = /* @__PURE__ */ new Map();
17404
17566
  this.onceListeners = /* @__PURE__ */ new Map();
17567
+ this.connectionListeners = [];
17405
17568
  this._id = sessionId;
17406
17569
  this._agentId = agentId;
17407
17570
  this.connection = connection;
@@ -17427,6 +17590,18 @@ var ActiveSessionImpl = class {
17427
17590
  return this._agentId;
17428
17591
  }
17429
17592
  /**
17593
+ * Actual workspace path (set from newSession response _meta)
17594
+ */
17595
+ get cwd() {
17596
+ return this._cwd;
17597
+ }
17598
+ /**
17599
+ * Set actual workspace path (called by SessionManager after createSession)
17600
+ */
17601
+ setCwd(cwd) {
17602
+ this._cwd = cwd;
17603
+ }
17604
+ /**
17430
17605
  * Agent state (live connection state)
17431
17606
  * Returns LocalAgentState or CloudAgentState based on transport type
17432
17607
  */
@@ -17643,8 +17818,8 @@ var ActiveSessionImpl = class {
17643
17818
  * await session.setMode('architect');
17644
17819
  * ```
17645
17820
  */
17646
- async setMode(modeId) {
17647
- if (this._availableModes) {
17821
+ async setMode(modeId, skipAvailableChecker) {
17822
+ if (this._availableModes && !skipAvailableChecker) {
17648
17823
  if (!this._availableModes.some((m) => m.id === modeId)) {
17649
17824
  const availableIds = this._availableModes.map((m) => m.id).join(", ");
17650
17825
  throw new Error(`Invalid modeId: "${modeId}". Available modes: ${availableIds}`);
@@ -17744,6 +17919,7 @@ var ActiveSessionImpl = class {
17744
17919
  * Disconnect from the session/agent
17745
17920
  */
17746
17921
  disconnect() {
17922
+ this.removeConnectionListeners();
17747
17923
  this.connection.disconnect();
17748
17924
  this.removeAllListeners();
17749
17925
  this.logger?.info(`Session ${this._id}: Disconnected`);
@@ -17767,60 +17943,80 @@ var ActiveSessionImpl = class {
17767
17943
  if (!this.connection.isInitialized) throw new Error(`Session ${this._id}: Connection not initialized.`);
17768
17944
  return this.connection;
17769
17945
  }
17946
+ /**
17947
+ * 在 connection 上注册 listener 并保存引用,便于 disconnect 时移除
17948
+ */
17949
+ addConnectionListener(connection, event, listener) {
17950
+ connection.on(event, listener);
17951
+ this.connectionListeners.push({
17952
+ event,
17953
+ listener
17954
+ });
17955
+ }
17956
+ /**
17957
+ * 从 connection 上移除所有本 session 注册的 listener
17958
+ */
17959
+ removeConnectionListeners() {
17960
+ for (const { event, listener } of this.connectionListeners) this.connection.off(event, listener);
17961
+ this.connectionListeners = [];
17962
+ }
17770
17963
  setupConnectionEvents(connection) {
17771
- connection.on("connected", () => {
17964
+ this.addConnectionListener(connection, "connected", () => {
17772
17965
  this.emit("connected", void 0);
17773
17966
  });
17774
- connection.on("disconnected", () => {
17967
+ this.addConnectionListener(connection, "disconnected", () => {
17775
17968
  this.emit("disconnected", void 0);
17776
17969
  });
17777
- connection.on("error", (error) => {
17970
+ this.addConnectionListener(connection, "error", (error) => {
17778
17971
  this.emit("error", error);
17779
17972
  });
17780
- connection.on("sessionUpdate", (update) => {
17973
+ this.addConnectionListener(connection, "sessionUpdate", (update) => {
17781
17974
  this.emit("sessionUpdate", update);
17782
17975
  });
17783
- connection.on("artifactCreated", (artifact) => {
17784
- console.log("[Session] Forwarding artifactCreated:", {
17785
- artifactUri: artifact.uri,
17786
- artifactType: artifact.type
17787
- });
17976
+ this.addConnectionListener(connection, "artifactCreated", (artifact) => {
17977
+ if (!this.shouldForwardArtifact(artifact)) return;
17788
17978
  this.emit("artifactCreated", artifact);
17789
17979
  });
17790
- connection.on("artifactUpdated", (artifact) => {
17791
- console.log("[Session] Forwarding artifactUpdated:", {
17792
- artifactUri: artifact.uri,
17793
- artifactType: artifact.type
17794
- });
17980
+ this.addConnectionListener(connection, "artifactUpdated", (artifact) => {
17981
+ if (!this.shouldForwardArtifact(artifact)) return;
17795
17982
  this.emit("artifactUpdated", artifact);
17796
17983
  });
17797
- connection.on("artifactDeleted", (artifact) => {
17798
- console.log("[Session] Forwarding artifactDeleted:", { artifactUri: artifact.uri });
17984
+ this.addConnectionListener(connection, "artifactDeleted", (artifact) => {
17985
+ if (!this.shouldForwardArtifact(artifact)) return;
17799
17986
  this.emit("artifactDeleted", artifact);
17800
17987
  });
17801
- connection.on("permissionRequest", (request) => {
17988
+ this.addConnectionListener(connection, "permissionRequest", (request) => {
17802
17989
  this.emit("permissionRequest", request);
17803
17990
  });
17804
- connection.on("questionRequest", (request) => {
17991
+ this.addConnectionListener(connection, "questionRequest", (request) => {
17805
17992
  this.emit("questionRequest", request);
17806
17993
  });
17807
- connection.on("questionCancelled", () => {
17994
+ this.addConnectionListener(connection, "questionCancelled", () => {
17808
17995
  this.prompts.cancel();
17809
17996
  });
17810
- connection.on("usageUpdate", (usage) => {
17997
+ this.addConnectionListener(connection, "usageUpdate", (usage) => {
17811
17998
  this.emit("usageUpdate", usage);
17812
17999
  });
17813
- connection.on("checkpointCreated", (checkpoint) => {
18000
+ this.addConnectionListener(connection, "checkpointCreated", (checkpoint) => {
18001
+ const originSessionId = checkpoint.__sessionId;
18002
+ if (originSessionId && originSessionId !== this._id) return;
17814
18003
  this.emit("checkpointCreated", checkpoint);
17815
18004
  });
17816
- connection.on("checkpointUpdated", (checkpoint) => {
18005
+ this.addConnectionListener(connection, "checkpointUpdated", (checkpoint) => {
18006
+ const originSessionId = checkpoint.__sessionId;
18007
+ if (originSessionId && originSessionId !== this._id) return;
17817
18008
  this.emit("checkpointUpdated", checkpoint);
17818
18009
  });
17819
- connection.on("command", (command) => {
17820
- console.log("[Session] Forwarding command:", {
17821
- action: command.action,
17822
- paramsKeys: command.params ? Object.keys(command.params) : []
17823
- });
18010
+ this.addConnectionListener(connection, "command", (command) => {
18011
+ const originSessionId = command.__sessionId;
18012
+ if (originSessionId && originSessionId !== this._id) {
18013
+ console.log("[Session] Command not forwarded:", {
18014
+ command,
18015
+ originSessionId,
18016
+ sessionId: this._id
18017
+ });
18018
+ return;
18019
+ }
17824
18020
  this.emit("command", command);
17825
18021
  });
17826
18022
  }
@@ -17830,19 +18026,38 @@ var ActiveSessionImpl = class {
17830
18026
  _meta: response._meta ?? void 0
17831
18027
  };
17832
18028
  }
18029
+ /**
18030
+ * 判断 artifact 是否应该转发给当前 session
18031
+ * - media 类型:按 cwd 路径隔离(同 cwd 下不同 session 可共享 media)
18032
+ * - 其余类型:按 __sessionId 严格隔离
18033
+ */
18034
+ shouldForwardArtifact(artifact) {
18035
+ const originSessionId = artifact.__sessionId;
18036
+ console.log("[Session] shouldForwardArtifact:", {
18037
+ artifact,
18038
+ originSessionId,
18039
+ sessionId: this._id,
18040
+ cwd: this.connection?.cwd
18041
+ });
18042
+ if (artifact.type === "media") {
18043
+ const cwd = this.connection?.cwd;
18044
+ const uri = artifact?.uri;
18045
+ if (cwd && uri) {
18046
+ const toPosix = (p) => p.replace(/\\/g, "/");
18047
+ const uriPath = toPosix(uri.replace(/^(?:file|agent):\/\//, ""));
18048
+ const posixCwd = toPosix(cwd);
18049
+ const normalizedCwd = posixCwd.endsWith("/") ? posixCwd : posixCwd + "/";
18050
+ return uriPath.startsWith(normalizedCwd);
18051
+ }
18052
+ }
18053
+ if (originSessionId && originSessionId !== this._id) return false;
18054
+ return true;
18055
+ }
17833
18056
  };
17834
18057
 
17835
18058
  //#endregion
17836
18059
  //#region ../agent-provider/src/common/client/session-manager.ts
17837
18060
  /**
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
18061
  * SessionManager - Session lifecycle management
17847
18062
  *
17848
18063
  * This class manages the relationship between sessions and agents.
@@ -17892,7 +18107,8 @@ var SessionManager = class {
17892
18107
  createdAt: agent.createdAt,
17893
18108
  lastActivityAt: agent.updatedAt,
17894
18109
  cwd: agent.type === "local" ? agent.cwd : void 0,
17895
- isPlayground: agent.isPlayground
18110
+ isPlayground: agent.isPlayground,
18111
+ isUserDefinedTitle: agent.isUserDefinedTitle
17896
18112
  }));
17897
18113
  console.log("[SessionManager] Returning sessions:", {
17898
18114
  count: sessions.length,
@@ -17919,13 +18135,26 @@ var SessionManager = class {
17919
18135
  if (this.provider.create) {
17920
18136
  agentId = await this.provider.create(params);
17921
18137
  this.logger?.debug(`Created new agent: ${agentId}`);
18138
+ if (params.options?.onSessionPrepared) {
18139
+ const initialPrompt = params.options?.prompt;
18140
+ const initialTitle = initialPrompt?.slice(0, 50) || "";
18141
+ params.options.onSessionPrepared({
18142
+ id: agentId,
18143
+ agentId,
18144
+ name: initialTitle + (initialPrompt && initialPrompt.length > 50 ? "..." : ""),
18145
+ status: "connecting",
18146
+ cwd: params.cwd || "",
18147
+ createdAt: /* @__PURE__ */ new Date()
18148
+ });
18149
+ this.logger?.debug(`Called onSessionPrepared for: ${agentId}`);
18150
+ }
17922
18151
  } else throw new Error("Provider does not support creating agents. Use sessions.load() with an existing sessionId.");
17923
18152
  const connection = await this.provider.connect(agentId);
17924
18153
  this.logger?.debug(`Connected to agent: ${agentId}`);
17925
18154
  const response = await connection.createSession({
17926
- _meta: params._meta,
18155
+ _meta: params.options?._meta,
17927
18156
  cwd: params.cwd,
17928
- mcpServers: params.mcpServers
18157
+ mcpServers: params.options?.mcpServers
17929
18158
  });
17930
18159
  if (this.provider.registerSession) {
17931
18160
  this.provider.registerSession(response.sessionId, agentId);
@@ -17938,14 +18167,10 @@ var SessionManager = class {
17938
18167
  connectionInfo
17939
18168
  });
17940
18169
  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
- }
18170
+ const availableModels = this.extractAvailableModels(response);
18171
+ if (availableModels) session.setModels(availableModels, response.models?.currentModelId);
18172
+ const responseCwd = response._meta?.["codebuddy.ai"]?.cwd;
18173
+ if (responseCwd) session.setCwd(responseCwd);
17949
18174
  this.logger?.info(`Session created: ${response.sessionId}`);
17950
18175
  return session;
17951
18176
  }
@@ -17981,17 +18206,31 @@ var SessionManager = class {
17981
18206
  mcpServers: params.mcpServers
17982
18207
  });
17983
18208
  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
- }
18209
+ const availableModels = this.extractAvailableModels(response);
18210
+ if (availableModels) session.setModels(availableModels, response.models?.currentModelId);
17992
18211
  this.logger?.info(`Session loaded: ${params.sessionId}`);
17993
18212
  return session;
17994
18213
  }
18214
+ /**
18215
+ * 从 ACP response 中提取可用模型列表
18216
+ *
18217
+ * 优先级:
18218
+ * 1. response.models._meta?.['codebuddy.ai']?.availableModels - 包含完整的模型信息(字段名为 'id')
18219
+ * 2. response.models?.availableModels - 只包含基本信息(字段名为 'modelId')
18220
+ * 3. undefined - 都没有时返回 undefined
18221
+ *
18222
+ * @param response - ACP 响应对象
18223
+ * @returns ModelInfo[] | undefined
18224
+ */
18225
+ extractAvailableModels(response) {
18226
+ const metaModels = (response.models?._meta?.["codebuddy.ai"])?.availableModels;
18227
+ if (metaModels && Array.isArray(metaModels) && metaModels.length > 0) return metaModels;
18228
+ const availableModels = response.models?.availableModels;
18229
+ if (availableModels && Array.isArray(availableModels) && availableModels.length > 0) return availableModels.map((model) => ({
18230
+ ...model,
18231
+ ...model._meta?.["codebuddy.ai"] || {}
18232
+ }));
18233
+ }
17995
18234
  };
17996
18235
 
17997
18236
  //#endregion
@@ -18264,6 +18503,28 @@ var AgentClient = class {
18264
18503
  };
18265
18504
  }
18266
18505
  },
18506
+ getSubagentList: async (params) => {
18507
+ try {
18508
+ if (this.provider && this.provider.getSubagentList) {
18509
+ const result = await this.provider.getSubagentList(params);
18510
+ this.logger?.info("Subagent list retrieved", {
18511
+ resultCount: result.results.length,
18512
+ hasError: !!result.error
18513
+ });
18514
+ return result;
18515
+ }
18516
+ return {
18517
+ results: [],
18518
+ error: "Provider does not support getSubagentList"
18519
+ };
18520
+ } catch (error) {
18521
+ this.logger?.error("Failed to get subagent list", error);
18522
+ return {
18523
+ results: [],
18524
+ error: error instanceof Error ? error.message : "Unknown error"
18525
+ };
18526
+ }
18527
+ },
18267
18528
  batchTogglePlugins: async (request) => {
18268
18529
  try {
18269
18530
  if (this.provider && this.provider.batchTogglePlugins) {
@@ -18308,10 +18569,10 @@ var AgentClient = class {
18308
18569
  return [];
18309
18570
  }
18310
18571
  },
18311
- installPlugins: async (pluginNames, marketplaceName, installScope) => {
18572
+ installPlugins: async (pluginNames, marketplaceName, installScope, marketplaceSource, workspacePath) => {
18312
18573
  try {
18313
18574
  if (this.provider && "installPlugins" in this.provider && typeof this.provider.installPlugins === "function") {
18314
- const result = await this.provider.installPlugins(pluginNames, marketplaceName, installScope);
18575
+ const result = await this.provider.installPlugins(pluginNames, marketplaceName, installScope, marketplaceSource, workspacePath);
18315
18576
  this.logger?.info("Install plugins", {
18316
18577
  pluginNames,
18317
18578
  marketplaceName,
@@ -18332,13 +18593,9 @@ var AgentClient = class {
18332
18593
  };
18333
18594
  }
18334
18595
  },
18335
- getSupportScenes: async () => {
18596
+ getSupportScenes: async (locale) => {
18336
18597
  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
- }
18598
+ if (this.provider && "getSupportScenes" in this.provider && typeof this.provider.getSupportScenes === "function") return await this.provider.getSupportScenes(locale);
18342
18599
  this.logger?.warn("Provider does not support getSupportScenes");
18343
18600
  return [];
18344
18601
  } catch (error) {
@@ -18346,6 +18603,94 @@ var AgentClient = class {
18346
18603
  return [];
18347
18604
  }
18348
18605
  },
18606
+ getProductScenes: async (locale) => {
18607
+ try {
18608
+ const hasMethod = this.provider && "getProductScenes" in this.provider;
18609
+ const isFunction = hasMethod && typeof this.provider.getProductScenes === "function";
18610
+ this.logger?.warn(`[getProductScenes] provider=${!!this.provider}, hasMethod=${hasMethod}, isFunction=${isFunction}, providerType=${this.provider?.constructor?.name}`);
18611
+ if (isFunction) {
18612
+ const result = await this.provider.getProductScenes(locale);
18613
+ this.logger?.warn(`[getProductScenes] got ${result?.length ?? 0} scenes`);
18614
+ return result;
18615
+ }
18616
+ this.logger?.warn("Provider does not support getProductScenes");
18617
+ return [];
18618
+ } catch (error) {
18619
+ this.logger?.error("Failed to get product scenes", error);
18620
+ return [];
18621
+ }
18622
+ },
18623
+ getAvailableCommands: async (params) => {
18624
+ try {
18625
+ if (this.provider && "getAvailableCommands" in this.provider && typeof this.provider.getAvailableCommands === "function") {
18626
+ const result = await this.provider.getAvailableCommands(params);
18627
+ this.logger?.info("Got available commands from provider", {
18628
+ sessionId: params?.sessionId ?? "(default)",
18629
+ count: result?.length ?? 0
18630
+ });
18631
+ return result;
18632
+ }
18633
+ this.logger?.warn("Provider does not support getAvailableCommands", { params });
18634
+ return [];
18635
+ } catch (error) {
18636
+ this.logger?.error("Failed to get available commands", error);
18637
+ return [];
18638
+ }
18639
+ },
18640
+ reportTelemetry: async (eventName, payload) => {
18641
+ try {
18642
+ if (this.provider?.reportTelemetry) await this.provider.reportTelemetry(eventName, payload);
18643
+ else this.logger?.warn("Provider does not support reportTelemetry");
18644
+ } catch (error) {
18645
+ this.logger?.error("Failed to report telemetry", error);
18646
+ }
18647
+ },
18648
+ respondToSampling: async (sessionId, response) => {
18649
+ try {
18650
+ if (this.provider?.respondToSampling) {
18651
+ await this.provider.respondToSampling(sessionId, response);
18652
+ this.logger?.info("Responded to sampling request", {
18653
+ sessionId,
18654
+ requestId: response.id,
18655
+ approved: response.approved
18656
+ });
18657
+ } else this.logger?.warn("Provider does not support respondToSampling");
18658
+ } catch (error) {
18659
+ this.logger?.error("Failed to respond to sampling request", error);
18660
+ throw error;
18661
+ }
18662
+ },
18663
+ respondToRoots: async (sessionId, response) => {
18664
+ try {
18665
+ if (this.provider?.respondToRoots) {
18666
+ await this.provider.respondToRoots(sessionId, response);
18667
+ this.logger?.info("Responded to roots request", {
18668
+ sessionId,
18669
+ requestId: response.id,
18670
+ approved: response.approved
18671
+ });
18672
+ } else this.logger?.warn("Provider does not support respondToRoots");
18673
+ } catch (error) {
18674
+ this.logger?.error("Failed to respond to roots request", error);
18675
+ throw error;
18676
+ }
18677
+ },
18678
+ subscribeSamplingRequests: (serverName, callback) => {
18679
+ if (this.provider?.subscribeSamplingRequests) {
18680
+ this.logger?.info("Subscribing to sampling requests", { serverName });
18681
+ return this.provider.subscribeSamplingRequests(serverName, callback);
18682
+ }
18683
+ this.logger?.warn("Provider does not support subscribeSamplingRequests");
18684
+ return () => {};
18685
+ },
18686
+ subscribeRootsRequests: (serverName, callback) => {
18687
+ if (this.provider?.subscribeRootsRequests) {
18688
+ this.logger?.info("Subscribing to roots requests", { serverName });
18689
+ return this.provider.subscribeRootsRequests(serverName, callback);
18690
+ }
18691
+ this.logger?.warn("Provider does not support subscribeRootsRequests");
18692
+ return () => {};
18693
+ },
18349
18694
  models: this.createModelsResource()
18350
18695
  };
18351
18696
  }
@@ -18412,6 +18757,154 @@ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
18412
18757
  return AccountStatus;
18413
18758
  }({});
18414
18759
 
18760
+ //#endregion
18761
+ //#region ../agent-provider/src/backend/service/oauth-repository-service.ts
18762
+ /**
18763
+ * OAuth Repository Service
18764
+ *
18765
+ * 封装 OAuth 连接器相关的仓库和分支操作
18766
+ */
18767
+ /**
18768
+ * OAuth Repository Service
18769
+ *
18770
+ * 提供仓库和分支的查询操作
18771
+ */
18772
+ var OAuthRepositoryService = class {
18773
+ /**
18774
+ * 获取仓库分支列表
18775
+ * API 端点: GET /console/as/connector/oauth/{name}/branches
18776
+ *
18777
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
18778
+ * @param params 平台特定的查询参数
18779
+ * @param page 页码,从1开始,0表示不分页获取全部
18780
+ * @param perPage 每页数量,最大100
18781
+ * @returns Promise<OauthBranch[]> 分支列表
18782
+ *
18783
+ * @example
18784
+ * ```typescript
18785
+ * // GitHub
18786
+ * const branches = await service.getBranches('github', {
18787
+ * owner: 'CodeBuddy-Official-Account',
18788
+ * repo: 'CodeBuddyIDE'
18789
+ * });
18790
+ *
18791
+ * // Gongfeng
18792
+ * const branches = await service.getBranches('gongfeng', {
18793
+ * project_id: '1611499'
18794
+ * });
18795
+ *
18796
+ * // CNB
18797
+ * const branches = await service.getBranches('cnb', {
18798
+ * repo: 'genie/genie-ide'
18799
+ * });
18800
+ * ```
18801
+ */
18802
+ async getBranches(connector, params, page = 0, perPage = 100) {
18803
+ try {
18804
+ const url = `/console/as/connector/oauth/${connector}/branches?${this.buildBranchQueryParams(connector, params, page, perPage).toString()}`;
18805
+ console.log(`[OAuthRepositoryService] GET ${url}`);
18806
+ const apiResponse = await httpService.get(url);
18807
+ if (!apiResponse.data) {
18808
+ console.warn(`[OAuthRepositoryService] No data in branches response for ${connector}`);
18809
+ return [];
18810
+ }
18811
+ const branches = apiResponse.data.branches || [];
18812
+ console.log(`[OAuthRepositoryService] Retrieved ${branches.length} branches from ${connector}`);
18813
+ return branches;
18814
+ } catch (error) {
18815
+ console.error(`[OAuthRepositoryService] Failed to get branches from ${connector}:`, error);
18816
+ throw error;
18817
+ }
18818
+ }
18819
+ /**
18820
+ * 获取仓库列表
18821
+ * API 端点: GET /console/as/connector/oauth/{name}/repos
18822
+ *
18823
+ * Note: 由于工蜂原生支持的 Search 能力会匹配 path/name/description 部分,
18824
+ * 且不支持定制,不满足产品要求(只按 name 匹配),因此前端拉取全量数据后做筛选。
18825
+ *
18826
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
18827
+ * @param page 页码,从1开始,0表示不分页获取全部
18828
+ * - GitHub 只支持全量数据,必须传 0
18829
+ * - 工蜂和 CNB 依据前端逻辑而定
18830
+ * @param perPage 每页数量,最大100
18831
+ * @returns Promise<ListReposResponse> 仓库列表响应
18832
+ *
18833
+ * @example
18834
+ * ```typescript
18835
+ * // GitHub - 必须传 page=0 获取全量数据
18836
+ * const response = await service.getRepositories('github', 0, 100);
18837
+ * // response.github_repos 是 map: installation_id => repo[]
18838
+ *
18839
+ * // Gongfeng
18840
+ * const response = await service.getRepositories('gongfeng', 0, 100);
18841
+ * // response.gongfeng_repos 是数组
18842
+ *
18843
+ * // CNB
18844
+ * const response = await service.getRepositories('cnb', 0, 100);
18845
+ * // response.cnb_repos 是数组
18846
+ * ```
18847
+ */
18848
+ async getRepositories(connector, page = 0, perPage = 100) {
18849
+ try {
18850
+ const queryParams = new URLSearchParams();
18851
+ queryParams.append("page", String(page));
18852
+ queryParams.append("per_page", String(Math.min(perPage, 100)));
18853
+ const url = `/console/as/connector/oauth/${connector}/repos?${queryParams.toString()}`;
18854
+ console.log(`[OAuthRepositoryService] GET ${url}`);
18855
+ const apiResponse = await httpService.get(url);
18856
+ if (!apiResponse.data) {
18857
+ console.warn(`[OAuthRepositoryService] No data in repos response for ${connector}`);
18858
+ return {};
18859
+ }
18860
+ const response = apiResponse.data;
18861
+ this.logRepositoryCounts(response);
18862
+ return response;
18863
+ } catch (error) {
18864
+ console.error(`[OAuthRepositoryService] Failed to get repos from ${connector}:`, error);
18865
+ throw error;
18866
+ }
18867
+ }
18868
+ /**
18869
+ * 构建分支查询参数
18870
+ */
18871
+ buildBranchQueryParams(connector, params, page, perPage) {
18872
+ const queryParams = new URLSearchParams();
18873
+ queryParams.append("page", String(page));
18874
+ queryParams.append("per_page", String(Math.min(perPage, 100)));
18875
+ if (connector === "github") {
18876
+ const githubParams = params;
18877
+ if (!githubParams.owner || !githubParams.repo) throw new Error("GitHub requires owner and repo parameters");
18878
+ queryParams.append("owner", githubParams.owner);
18879
+ queryParams.append("repo", githubParams.repo);
18880
+ } else if (connector === "gongfeng") {
18881
+ const gongfengParams = params;
18882
+ if (!gongfengParams.project_id) throw new Error("Gongfeng requires project_id parameter");
18883
+ queryParams.append("project_id", gongfengParams.project_id);
18884
+ } else if (connector === "cnb") {
18885
+ const cnbParams = params;
18886
+ if (!cnbParams.repo) throw new Error("CNB requires repo parameter");
18887
+ queryParams.append("repo", cnbParams.repo);
18888
+ } else throw new Error(`Unknown connector: ${connector}`);
18889
+ return queryParams;
18890
+ }
18891
+ /**
18892
+ * 记录仓库数量日志
18893
+ */
18894
+ logRepositoryCounts(response) {
18895
+ if (response.github_repos) {
18896
+ const totalCount = Object.values(response.github_repos).reduce((sum, repos) => sum + repos.length, 0);
18897
+ console.log(`[OAuthRepositoryService] Retrieved ${totalCount} GitHub repos across ${Object.keys(response.github_repos).length} installations`);
18898
+ }
18899
+ if (response.gongfeng_repos) console.log(`[OAuthRepositoryService] Retrieved ${response.gongfeng_repos.length} Gongfeng repos`);
18900
+ if (response.cnb_repos) console.log(`[OAuthRepositoryService] Retrieved ${response.cnb_repos.length} CNB repos`);
18901
+ }
18902
+ };
18903
+ /**
18904
+ * OAuth Repository Service 单例实例
18905
+ */
18906
+ const oauthRepositoryService = new OAuthRepositoryService();
18907
+
18415
18908
  //#endregion
18416
18909
  //#region ../agent-provider/src/backend/backend-provider.ts
18417
18910
  /**
@@ -18420,28 +18913,24 @@ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
18420
18913
  * 封装与后端 API 的 HTTP 通信
18421
18914
  */
18422
18915
  /**
18423
- * 判断当前是否在 SSO 域名下
18424
- * SSO 域名格式: xxx.sso.copilot.tencent.com xxx.sso.codebuddy.cn
18916
+ * 判断当前账号是否是 SSO 账号
18917
+ * 通过 account.accountType === 'sso' 来判断,这种不行,因为未登录之前account 为空
18425
18918
  */
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");
18919
+ const safeParseJSON = (jsonString) => {
18920
+ try {
18921
+ return JSON.parse(jsonString);
18922
+ } catch (error) {
18923
+ return {};
18924
+ }
18429
18925
  };
18430
18926
  /**
18431
- * 获取登录页面 URL
18432
- * - SSO 域名下需要跳转到对应的预发/生产域名
18433
- * - 非 SSO 域名直接使用当前域名
18927
+ * 根据路径获取完整 URL
18928
+ * - SSO 账号需要跳转到对应的预发/生产域名
18929
+ * - 非 SSO 账号直接使用当前域名
18930
+ * @param path 路径,如 '/login'、'/logout'、'/home' 等
18931
+ * @returns 完整的 URL 地址
18434
18932
  */
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
- };
18933
+ const getFullUrl = (path) => `${window.location.origin}${path}`;
18445
18934
  /** 获取当前域名的账号选择页面 URL */
18446
18935
  const getSelectAccountUrl = () => `${window.location.origin}/login/select`;
18447
18936
  /** localStorage 中存储选中账号 ID 的 key */
@@ -18478,12 +18967,30 @@ var BackendProvider = class {
18478
18967
  constructor(config) {
18479
18968
  httpService.setBaseURL(config.baseUrl);
18480
18969
  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);
18970
+ httpService.onUnauthorized(() => this.handleUnauthorized());
18971
+ }
18972
+ /**
18973
+ * 处理 401 未授权错误
18974
+ * 先尝试刷新 token,失败后再执行登出流程
18975
+ *
18976
+ * @throws 如果 token 刷新失败,抛出错误通知 HttpService 不要重试
18977
+ */
18978
+ async handleUnauthorized() {
18979
+ console.log("[BackendProvider] User unauthorized (401), attempting token refresh first");
18980
+ try {
18981
+ if (await this.refreshToken()) {
18982
+ console.log("[BackendProvider] Token refresh successful after 401, user still logged in");
18983
+ return;
18984
+ }
18985
+ throw new Error("Token refresh returned null");
18986
+ } catch (error) {
18987
+ console.error("[BackendProvider] Token refresh failed after 401:", error);
18988
+ console.log("[BackendProvider] Token refresh failed, triggering logout");
18989
+ this.logout().catch((logoutError) => {
18990
+ console.error("[BackendProvider] Logout failed in 401 handler:", logoutError);
18485
18991
  });
18486
- });
18992
+ throw error;
18993
+ }
18487
18994
  }
18488
18995
  /**
18489
18996
  * 获取当前账号信息
@@ -18528,7 +19035,7 @@ var BackendProvider = class {
18528
19035
  return account;
18529
19036
  }
18530
19037
  const redirectUrl = encodeURIComponent(window.location.href);
18531
- window.location.href = `${getSelectAccountUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
19038
+ window.location.href = `${getSelectAccountUrl()}?platform=agents&state=0&redirect_uri=${redirectUrl}`;
18532
19039
  accountService.setAccount(null);
18533
19040
  return null;
18534
19041
  } catch (error) {
@@ -18551,7 +19058,8 @@ var BackendProvider = class {
18551
19058
  activeStatus: connector.active_status,
18552
19059
  displayName: connector.display_name,
18553
19060
  oauthClientId: connector.oauth_client_id,
18554
- oauthRedirectUrl: connector.oauth_redirect_url
19061
+ oauthRedirectUrl: connector.oauth_redirect_url,
19062
+ oauthAppName: connector.oauth_app_name
18555
19063
  })) };
18556
19064
  }
18557
19065
  throw result;
@@ -18633,7 +19141,8 @@ var BackendProvider = class {
18633
19141
  connectStatus: connector.connect_status,
18634
19142
  displayName: connector.display_name,
18635
19143
  oauthClientId: connector.oauth_client_id,
18636
- oauthRedirectUrl: connector.oauth_redirect_url
19144
+ oauthRedirectUrl: connector.oauth_redirect_url,
19145
+ oauthAppName: connector.oauth_app_name
18637
19146
  })) };
18638
19147
  }
18639
19148
  throw result;
@@ -18724,6 +19233,9 @@ var BackendProvider = class {
18724
19233
  PackageCode: void 0,
18725
19234
  name: ""
18726
19235
  };
19236
+ const productFeatures = typeof window !== "undefined" && window.PRODUCT_FEATURES ? safeParseJSON(window.PRODUCT_FEATURES) : {};
19237
+ console.log("[PRODUCT_FEATURES]", productFeatures);
19238
+ if (!productFeatures.billing) return defaultPlan;
18727
19239
  try {
18728
19240
  const now = /* @__PURE__ */ new Date();
18729
19241
  const futureDate = new Date(now.getTime() + 101 * 365 * 24 * 60 * 60 * 1e3);
@@ -18745,7 +19257,7 @@ var BackendProvider = class {
18745
19257
  if (!time) return 0;
18746
19258
  return new Date(time).getTime();
18747
19259
  };
18748
- const dailyCredits = [CommodityCode.free, CommodityCode.freeMon];
19260
+ const dailyCredits = [CommodityCode.free];
18749
19261
  const planResources = resources.map((r) => {
18750
19262
  const isDaily = dailyCredits.includes(r.PackageCode);
18751
19263
  const endTime = isDaily ? r.CycleEndTime : r.DeductionEndTime;
@@ -18766,10 +19278,11 @@ var BackendProvider = class {
18766
19278
  CommodityCode.proMon,
18767
19279
  CommodityCode.proMonPlus,
18768
19280
  CommodityCode.proYear,
19281
+ CommodityCode.freeMon,
18769
19282
  CommodityCode.extra
18770
19283
  ].includes(code)) return 1;
18771
19284
  if ([CommodityCode.gift, CommodityCode.activity].includes(code)) return 2;
18772
- if ([CommodityCode.free, CommodityCode.freeMon].includes(code)) return 3;
19285
+ if ([CommodityCode.free].includes(code)) return 3;
18773
19286
  return 4;
18774
19287
  };
18775
19288
  return getPriority(a.packageCode) - getPriority(b.packageCode);
@@ -18890,7 +19403,7 @@ var BackendProvider = class {
18890
19403
  */
18891
19404
  async login() {
18892
19405
  const redirectUrl = encodeURIComponent(window.location.href);
18893
- window.location.href = `${getLoginUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
19406
+ window.location.href = `${getFullUrl("/login")}?platform=agents&state=0&redirect_uri=${redirectUrl}`;
18894
19407
  }
18895
19408
  /**
18896
19409
  * 登出账号
@@ -18953,6 +19466,52 @@ var BackendProvider = class {
18953
19466
  return null;
18954
19467
  }
18955
19468
  }
19469
+ /**
19470
+ * 刷新 Token
19471
+ * 通过调用 getAccount 刷新 cookie,适用于 Cloud 场景下页面切换回来时刷新登录态
19472
+ * @returns Promise<Account | null> 刷新后的账号信息
19473
+ */
19474
+ async refreshToken() {
19475
+ console.log("[BackendProvider] Refreshing token...");
19476
+ try {
19477
+ const account = await this.getAccount();
19478
+ console.log("[BackendProvider] Token refreshed, account:", account?.uid);
19479
+ return account;
19480
+ } catch (error) {
19481
+ console.error("[BackendProvider] refreshToken failed:", error);
19482
+ return null;
19483
+ }
19484
+ }
19485
+ /**
19486
+ * 获取仓库分支列表
19487
+ * API 端点: GET /console/as/connector/oauth/{name}/branches
19488
+ *
19489
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
19490
+ * @param params 平台特定的查询参数
19491
+ * @param page 页码,从1开始,0表示不分页获取全部
19492
+ * @param perPage 每页数量,最大100
19493
+ * @returns Promise<OauthBranch[]> 分支列表
19494
+ */
19495
+ async getBranches(connector, params, page = 0, perPage = 100) {
19496
+ return oauthRepositoryService.getBranches(connector, params, page, perPage);
19497
+ }
19498
+ /**
19499
+ * 获取仓库列表
19500
+ * API 端点: GET /console/as/connector/oauth/{name}/repos
19501
+ *
19502
+ * Note: 由于工蜂原生支持的 Search 能力会匹配 path/name/description 部分,
19503
+ * 且不支持定制,不满足产品要求(只按 name 匹配),因此前端拉取全量数据后做筛选。
19504
+ *
19505
+ * @param connector 连接器名称 ('github' | 'gongfeng' | 'cnb')
19506
+ * @param page 页码,从1开始,0表示不分页获取全部
19507
+ * - GitHub 只支持全量数据,必须传 0
19508
+ * - 工蜂和 CNB 依据前端逻辑而定
19509
+ * @param perPage 每页数量,最大100
19510
+ * @returns Promise<ListReposResponse> 仓库列表响应
19511
+ */
19512
+ async getRepositories(connector, page = 0, perPage = 100) {
19513
+ return oauthRepositoryService.getRepositories(connector, page, perPage);
19514
+ }
18956
19515
  };
18957
19516
  /**
18958
19517
  * 创建 BackendProvider 实例
@@ -18992,6 +19551,7 @@ const BACKEND_REQUEST_TYPES = {
18992
19551
  GET_FILE: "backend:get-file",
18993
19552
  RELOAD_WINDOW: "backend:reload-window",
18994
19553
  CLOSE_AGENT_MANAGER: "backend:close-agent-manager",
19554
+ OPEN_EXTERNAL: "backend:open-external",
18995
19555
  BATCH_TOGGLE_PLUGINS: "backend:batch-toggle-plugins",
18996
19556
  GET_SUPPORT_SCENES: "backend:get-support-scenes"
18997
19557
  };
@@ -19287,6 +19847,20 @@ var IPCBackendProvider = class {
19287
19847
  }
19288
19848
  }
19289
19849
  /**
19850
+ * 在外部浏览器中打开链接
19851
+ * IDE 环境: 通过 IPC 通知 IDE 使用 vscode.env.openExternal 打开 URL
19852
+ * @param url 要打开的 URL
19853
+ */
19854
+ async openExternal(url) {
19855
+ this.log("Opening external URL via IPC:", url);
19856
+ try {
19857
+ await this.sendBackendRequest(BACKEND_REQUEST_TYPES.OPEN_EXTERNAL, { url });
19858
+ } catch (error) {
19859
+ this.log("Open external request failed:", error);
19860
+ throw error;
19861
+ }
19862
+ }
19863
+ /**
19290
19864
  * 批量切换插件状态
19291
19865
  * IDE 环境: 通过 IPC 调用 Extension Host 的 PluginService
19292
19866
  */