@blade-hq/agent-client 2610.0.0-beta.54 → 2610.0.0-beta.55

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/README.md CHANGED
@@ -174,6 +174,32 @@ await client.sessions.getSessionTurnsPage(id, { before: page.nextBefore! })
174
174
  会话插件可通过 `client.sessions.listSessionPlugins(id)` 查询,并用
175
175
  `client.sessions.setSessionPluginActivation(id, name, active)` 切换当前会话。列表项为 `SessionPlugin`;修改结果为 `SessionPluginActivation`,只返回选择和准备状态,安装事实需从列表获取。
176
176
 
177
+ 账号级目录是 `client.plugins.catalog()`(`PluginsResource` 是 `client.plugins`
178
+ 的类型)。它**不依赖任何会话**,返回 `PluginCatalogEntry[]`:`name`、`display_name`、
179
+ `icon`、`installed`。没有会话也要展示插件名称与图标的界面(例如首页的输入框连接器)
180
+ 用它,而不是先建一个会话。
181
+
182
+ `icon` 是不透明的缓存 token(当前是包内容摘要),**不是路径**:把它交给
183
+ `client.plugins.fetchIcon(name)` 取字节再转 object URL,不要拿它拼 URL,也不要把
184
+ `<img>` 指向接口路径(token 用户会拿到 401/403)。
185
+
186
+ ```ts
187
+ const { plugins } = await client.plugins.catalog()
188
+ // plugins: PluginCatalogEntry[]
189
+ // name 技术 ID:路径、授权、缓存与调用一律用它
190
+ // display_name 包声明的业务名;null 时界面回退到 name
191
+ // icon 包内图标的 opaque token;null 表示没有可用图标
192
+ // installed 当前用户是否安装了它
193
+
194
+ const blob = await client.plugins.fetchIcon(plugins[0].name)
195
+ const url = URL.createObjectURL(blob)
196
+ ```
197
+
198
+ 账号电脑目录是 `client.computers.listAccount()`,返回 `AccountComputerList`。它只回答
199
+ 账号事实(登记过哪些电脑、此刻在不在线,`AccountComputer`),**不含**"本次会话能不能
200
+ 用它"——那是会话接口 (`client.computers.list`) 的回答。两者混着说,用户就会把在线当成
201
+ 已授权。
202
+
177
203
  插件可以在包内 `cn.com.bladeai.agent/config.schema.json` 用标准 JSON Schema
178
204
  (Draft 2020-12)声明账号、token 或任意业务配置。声明了配置的插件用
179
205
  `client.sessions.getSessionPluginConfig(id, name)` 读取声明与当前值,用
@@ -706,7 +732,9 @@ HTML 一律判无效);`isInternalStatusToolUiCard` 识别内部「阶段进
706
732
  - **SDK 身份**:`SDK_NAME`、`SDK_VERSION`
707
733
  - **声明式会话**:`SessionDefinition`、`SolutionDefinition`、`SkillDefinition`、`SessionConfig`、`TextFile`、`SessionSetupError`、`SessionSetupStage`
708
734
  - **模型目录**:`ModelsResource`、`ModelCatalog`、`ModelOption`
709
- - **聊天项目资源(REST)**:`ChatProjectsResource`、`ChatProject`(含 `delete` 永久删除)
735
+ - **聊天分组资源(REST)**:`ChatGroupsResource`、`ChatGroup`(含 `delete` 永久删除);`client.chatGroups`。旧名 `ChatProjectsResource`、`ChatProject`、`client.chatProjects` 与 `chat_project_id` 字段已随分组改名一并移除,接入方请改用 `chatGroups` 与 `chat_group_id`
736
+ - **插件目录(REST,无会话)**:`PluginsResource`、`PluginCatalogEntry`(`client.plugins`)。首页这类还没有会话的界面用它读名称、业务名与图标 token
737
+ - **账号电脑目录**:`AccountComputer`、`AccountComputerList`(`client.computers.listAccount()`,只读账号事实,不含会话授权)
710
738
  - **全站访问统计(REST)**:`SiteVisitsResource`、`SiteVisitsSummary`(`client.siteVisits.record()` 上报一次页面访问、`client.siteVisits.summary()` 只读快照)。这两个操作走独立匿名传输:不带 bearer、不触发 401 登录刷新,只依赖匿名访客 Cookie,供内置 Web 同源使用。
711
739
  - **会话资源(REST)**:`SessionsResource`、`CreateSessionRequest`、`ImportSessionOptions`、`AppCliDefinition`、`AppCliAttachment`、`AttachAppOptions`、`PaginatedSessionsResult`、`GlobalSearchResult`、`GlobalSearchResultItem`、`GlobalSearchConversationResult`、`GlobalSearchFileResult`、`SessionHistory`、`SessionContextStats`、`ResultFeedback`、`ResultFeedbackReason`、`ShareLinkResult`、`FileEntry`、`UploadFileEntry`、`UploadFilesOptions`、`SessionProfile`、`SessionDetail`、`SessionInfo`、`SessionStatus`、`SessionPortMapping`、`ModeId`、`TemplateId`、`PrimarySkillSnapshot`、`PrimarySkillParallelMode`
712
740
  - **会话回放**:`ReplayState`、`ReplaySpeed`、`ReplayPreview`、`ReplaySnapshot`、`toReplaySnapshot`、`DEFAULT_REPLAY_SPEED`
@@ -5,7 +5,8 @@ import { HeadlessResource } from "./resources/headless";
5
5
  import { ComputersResource } from "./resources/computers";
6
6
  import { ModelsResource } from "./resources/models";
7
7
  import { SessionsResource } from "./resources/sessions";
8
- import { ChatProjectsResource } from "./resources/chat-projects";
8
+ import { PluginsResource } from "./resources/plugins";
9
+ import { ChatGroupsResource } from "./resources/chat-groups";
9
10
  import { SiteVisitsResource } from "./resources/site-visits";
10
11
  import { SessionHub } from "./session/hub";
11
12
  import { type TypedSocket } from "./socket";
@@ -39,7 +40,9 @@ export declare class BladeClient {
39
40
  readonly computers: ComputersResource;
40
41
  readonly models: ModelsResource;
41
42
  readonly sessions: SessionsResource;
42
- readonly chatProjects: ChatProjectsResource;
43
+ /** 按用户读取的插件目录与图标;没有会话时也能用。 */
44
+ readonly plugins: PluginsResource;
45
+ readonly chatGroups: ChatGroupsResource;
43
46
  /**
44
47
  * 全站访问统计:匿名读写,不带 bearer 也不触发登录刷新。
45
48
  * 只有内置 Web 的同源页面使用,第三方宿主页面不会被自动统计。
package/dist/index.d.ts CHANGED
@@ -28,14 +28,16 @@ export type { AuthResource, ExchangeCodeParams, ExchangeCodeResult, ProvidersRes
28
28
  export type { HeadlessResource } from "./resources/headless";
29
29
  export { ComputersResource } from "./resources/computers";
30
30
  export { canToggleComputer, computerDaemonVersion, computerOS, computerPlatformLabel, computerState, sortComputers, } from "./resources/computers";
31
- export type { ComputerOS, ComputerState, SessionComputer, SessionComputerList, } from "./resources/computers";
31
+ export type { AccountComputer, AccountComputerList, ComputerOS, ComputerState, SessionComputer, SessionComputerList, } from "./resources/computers";
32
32
  export { ModelsResource } from "./resources/models";
33
33
  export type { ModelCatalog, ModelOption } from "./resources/models";
34
34
  export type { SessionsResource } from "./resources/sessions";
35
35
  export type { SessionPlugin, SessionPluginActivation } from "./resources/sessions";
36
36
  export type { SessionPluginConfigError, SessionPluginConfigSet, SessionPluginConfigState, SessionPluginConfigStateName, SessionPluginConfigSummary, SessionPluginConfigUpdate, } from "./resources/sessions";
37
- export { ChatProjectsResource } from "./resources/chat-projects";
38
- export type { ChatProject } from "./resources/chat-projects";
37
+ export { ChatGroupsResource } from "./resources/chat-groups";
38
+ export type { ChatGroup } from "./resources/chat-groups";
39
+ export { PluginsResource } from "./resources/plugins";
40
+ export type { PluginCatalogEntry } from "./resources/plugins";
39
41
  export type { SiteVisitsResource, SiteVisitsSummary } from "./resources/site-visits";
40
42
  export { hasChatRunEvent } from "./shared/projection";
41
43
  export type { CreateSessionRequest, AppCliAttachment, AppCliDefinition, FileEntry, ImportSessionOptions, PaginatedSessionsResult, GlobalSearchConversationResult, GlobalSearchFileResult, GlobalSearchResult, GlobalSearchResultItem, ResultFeedback, ResultFeedbackReason, HistoricalCommand, SessionContextStats, SessionHistory, SessionTurnsPage, ShareLinkResult, UploadFileEntry, UploadFilesOptions, } from "./resources/sessions";
package/dist/index.js CHANGED
@@ -467,6 +467,21 @@ var ComputersResource = class {
467
467
  this.client = client;
468
468
  }
469
469
  client;
470
+ /**
471
+ * 列出当前账号登记过的全部电脑及其在线状态。
472
+ *
473
+ * 首页还没有会话,也要能看到同一份清单——判定各写一份必然分叉,一边的兜底会把
474
+ * 另一边的错误盖住(#1597)。所以账号事实只从这一个接口读。
475
+ */
476
+ async listAccount() {
477
+ const data = await this.client.json("GET", "/api/daemon/instances");
478
+ return {
479
+ computers: (data.instances ?? []).map((instance) => ({
480
+ ...instance,
481
+ online: Boolean(instance.online)
482
+ }))
483
+ };
484
+ }
470
485
  /**
471
486
  * 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
472
487
  * 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
@@ -542,7 +557,7 @@ var SessionInfo = type({
542
557
  "workspace_path?": "string | null",
543
558
  "ba_version?": "string | null",
544
559
  "sandbox_version?": "string | null",
545
- "chat_project_id?": "string | null",
560
+ "chat_group_id?": "string | null",
546
561
  "match?": "unknown"
547
562
  });
548
563
 
@@ -608,7 +623,7 @@ function upsertToolCall(state, toolCallId, toolName, displayName, args) {
608
623
  });
609
624
  }
610
625
  }
611
- function applyToolResult(state, toolCallId, result, status, durationMs, ranInParallel, sourceLoop) {
626
+ function applyToolResult(state, toolCallId, result, status, durationMs, ranInParallel, sourceLoop, startedAt = null) {
612
627
  let toolName = "";
613
628
  let displayName = "";
614
629
  let finalResult = result;
@@ -621,6 +636,7 @@ function applyToolResult(state, toolCallId, result, status, durationMs, ranInPar
621
636
  const update = { status, result: finalResult };
622
637
  if (durationMs != null) update.duration_ms = durationMs;
623
638
  if (ranInParallel != null) update.ran_in_parallel = ranInParallel;
639
+ if (startedAt != null) update.started_at = startedAt;
624
640
  state.toolCalls[i] = { ...tc, ...update };
625
641
  toolName = tc.tool_name;
626
642
  displayName = tc.display_name;
@@ -1070,6 +1086,9 @@ function createTurnState(turnId, loopId, opts = {}) {
1070
1086
  firstTokenMs: null,
1071
1087
  streamDurationMs: null,
1072
1088
  firstTokenDeliveryMs: null,
1089
+ modelStartedAt: null,
1090
+ modelDurationMs: null,
1091
+ modelStartedAtReported: false,
1073
1092
  memoryRefs: opts.memoryRefs ?? null,
1074
1093
  parentForkToolCallId: null
1075
1094
  };
@@ -1087,11 +1106,13 @@ function snapshot(state, sequence, status) {
1087
1106
  tool_calls: [...state.toolCalls],
1088
1107
  model: state.model,
1089
1108
  usage: state.usage ? { ...state.usage } : null,
1090
- duration_ms: Math.max(0, Date.now() - state.startedAt),
1109
+ duration_ms: state.modelDurationMs,
1091
1110
  first_token_ms: state.firstTokenMs,
1092
1111
  stream_duration_ms: state.streamDurationMs,
1093
1112
  first_token_delivery_ms: state.firstTokenDeliveryMs,
1094
- started_at: new Date(state.startedAt).toISOString(),
1113
+ started_at: state.modelStartedAt ?? new Date(state.startedAt).toISOString(),
1114
+ // 没有 payload 上报时用的是 loop:turn 的浏览器事件时间,标记出来供瀑布区分。
1115
+ has_reported_timing: state.modelStartedAtReported,
1095
1116
  context_window: state.contextWindow,
1096
1117
  memory_refs: state.memoryRefs,
1097
1118
  parent_fork_tool_call_id: state.parentForkToolCallId
@@ -1312,6 +1333,8 @@ var ClientProjectionBuilder = class {
1312
1333
  latestTurns = /* @__PURE__ */ new Map();
1313
1334
  turnsById = /* @__PURE__ */ new Map();
1314
1335
  loopDescriptions = /* @__PURE__ */ new Map();
1336
+ /** loop_name → 正在运行的子智能体起点,agent:end 结算并挂到父工具调用上。 */
1337
+ activeSubagentRuns = /* @__PURE__ */ new Map();
1315
1338
  pendingChildPauses = /* @__PURE__ */ new Map();
1316
1339
  pendingMemoryRefs = /* @__PURE__ */ new Map();
1317
1340
  activeCompactions = /* @__PURE__ */ new Map();
@@ -1455,6 +1478,7 @@ var ClientProjectionBuilder = class {
1455
1478
  this.latestTurns.clear();
1456
1479
  this.turnsById.clear();
1457
1480
  this.loopDescriptions.clear();
1481
+ this.activeSubagentRuns.clear();
1458
1482
  this.pendingChildPauses.clear();
1459
1483
  this.pendingMemoryRefs.clear();
1460
1484
  this.activeCompactions.clear();
@@ -1596,6 +1620,13 @@ var ClientProjectionBuilder = class {
1596
1620
  onAgentStart(loopId, payload) {
1597
1621
  const description = optStr(payload.description);
1598
1622
  if (description) this.loopDescriptions.set(loopId, description);
1623
+ const subagentStartedAt = optStr(payload.started_at);
1624
+ if (subagentStartedAt) {
1625
+ this.activeSubagentRuns.set(loopId, {
1626
+ runId: optStr(payload.run_id) ?? loopId,
1627
+ startedAt: subagentStartedAt
1628
+ });
1629
+ }
1599
1630
  const turn = buildSystemNotificationTurn(
1600
1631
  this.nextSeq(),
1601
1632
  this.nextTurnId("agent:start"),
@@ -1797,6 +1828,13 @@ var ClientProjectionBuilder = class {
1797
1828
  if (typeof payload.stream_duration_ms === "number" && payload.stream_duration_ms >= 0) {
1798
1829
  state.streamDurationMs = Math.round(payload.stream_duration_ms);
1799
1830
  }
1831
+ if (typeof payload.started_at === "string" && payload.started_at) {
1832
+ state.modelStartedAt = payload.started_at;
1833
+ state.modelStartedAtReported = true;
1834
+ }
1835
+ if (typeof payload.duration_ms === "number" && payload.duration_ms >= 0) {
1836
+ state.modelDurationMs = Math.round(payload.duration_ms);
1837
+ }
1800
1838
  }
1801
1839
  onToolResultDone(state, loopId, payload) {
1802
1840
  const toolCallId = String(payload.tool_call_id ?? "");
@@ -1807,7 +1845,8 @@ var ClientProjectionBuilder = class {
1807
1845
  const durationMs = typeof rawDuration === "number" && rawDuration >= 0 ? Math.round(rawDuration) : null;
1808
1846
  const ranInParallel = typeof payload.ran_in_parallel === "boolean" ? payload.ran_in_parallel : null;
1809
1847
  const sl = sourceLoopFor(state.loopId, this.loopDescriptions);
1810
- applyToolResult(state, toolCallId, content, status, durationMs, ranInParallel, sl);
1848
+ const startedAt = typeof payload.started_at === "string" && payload.started_at ? payload.started_at : null;
1849
+ applyToolResult(state, toolCallId, content, status, durationMs, ranInParallel, sl, startedAt);
1811
1850
  const isNativeProviderResult = typeof payload.provider === "string" && payload.provider !== "";
1812
1851
  if (!isNativeProviderResult && this.shouldFinalizeAfterToolResults(state)) {
1813
1852
  return this.finalize(loopId, "completed");
@@ -1963,6 +2002,7 @@ var ClientProjectionBuilder = class {
1963
2002
  }
1964
2003
  onAgentEnd(state, loopId, payload) {
1965
2004
  const ok = payload.ok !== false;
2005
+ this.settleSubagentRun(loopId, payload);
1966
2006
  const status = ok ? "done" : "error";
1967
2007
  const title = `Agent: ${payload.description ?? payload.skill_id ?? "agent"}`;
1968
2008
  const detail = optStr(payload.error);
@@ -1989,6 +2029,32 @@ var ClientProjectionBuilder = class {
1989
2029
  );
1990
2030
  return [{ kind: "upsert", turn }];
1991
2031
  }
2032
+ settleSubagentRun(loopId, payload) {
2033
+ const active = this.activeSubagentRuns.get(loopId);
2034
+ this.activeSubagentRuns.delete(loopId);
2035
+ const parentToolCallId = optStr(payload.parent_fork_tool_call_id);
2036
+ if (!parentToolCallId) return;
2037
+ const rawDuration = payload.run_duration_ms;
2038
+ const durationMs = typeof rawDuration === "number" && rawDuration >= 0 ? Math.round(rawDuration) : null;
2039
+ const rawStatus = optStr(payload.run_status);
2040
+ const status = rawStatus === "failed" || rawStatus === "cancelled" || rawStatus === "paused" || rawStatus === "incomplete" ? rawStatus : payload.ok === false ? "failed" : "completed";
2041
+ const run = {
2042
+ run_id: active?.runId ?? optStr(payload.run_id) ?? loopId,
2043
+ scope: "subagent",
2044
+ loop_id: loopId,
2045
+ started_at: active?.startedAt ?? optStr(payload.run_started_at),
2046
+ duration_ms: durationMs,
2047
+ status
2048
+ };
2049
+ const seen = /* @__PURE__ */ new Set();
2050
+ for (const turn of [...this.latestTurns.values(), ...this.turnsById.values()]) {
2051
+ if (seen.has(turn)) continue;
2052
+ seen.add(turn);
2053
+ const toolCall = turn.tool_calls.find((item) => item.id === parentToolCallId);
2054
+ if (!toolCall) continue;
2055
+ toolCall.subagent_runs = [...toolCall.subagent_runs ?? [], run];
2056
+ }
2057
+ }
1992
2058
  dropIncompleteToolCalls(state) {
1993
2059
  if (state.responseDone) return;
1994
2060
  state.toolCalls = [];
@@ -2129,6 +2195,32 @@ function projectHistory(entries) {
2129
2195
  if (target && duration !== null) target.first_token_delivery_ms = duration;
2130
2196
  continue;
2131
2197
  }
2198
+ if (kind === "run_timing" && isRecord(entry.data)) {
2199
+ const data = entry.data;
2200
+ if (data.scope === "subagent") {
2201
+ const parentToolCallId = String(data.parent_fork_tool_call_id ?? "");
2202
+ const duration = numberOrNull(data.duration_ms);
2203
+ const rawStatus = String(data.status ?? "completed");
2204
+ const status = rawStatus === "failed" || rawStatus === "cancelled" || rawStatus === "paused" || rawStatus === "incomplete" ? rawStatus : "completed";
2205
+ for (const turn of turns) {
2206
+ const toolCall = turn.tool_calls.find((item) => item.id === parentToolCallId);
2207
+ if (!toolCall) continue;
2208
+ toolCall.subagent_runs = [
2209
+ ...toolCall.subagent_runs ?? [],
2210
+ {
2211
+ run_id: String(data.run_id ?? ""),
2212
+ scope: "subagent",
2213
+ loop_id: loopId,
2214
+ started_at: typeof data.started_at === "string" ? data.started_at : null,
2215
+ duration_ms: duration,
2216
+ status
2217
+ }
2218
+ ];
2219
+ break;
2220
+ }
2221
+ }
2222
+ continue;
2223
+ }
2132
2224
  if (kind === "post_chat_followup" && isRecord(entry.data)) {
2133
2225
  const assistantEntryId = String(entry.data.assistant_entry_id ?? "");
2134
2226
  if (latestRootMessage?.role === "assistant" && latestRootMessage.turn_id === assistantEntryId) {
@@ -2157,6 +2249,9 @@ function projectHistory(entries) {
2157
2249
  toolCall.result = message.content ?? "";
2158
2250
  const duration = numberOrNull(message._duration_ms);
2159
2251
  if (duration !== null) toolCall.duration_ms = duration;
2252
+ if (typeof message._started_at === "string") {
2253
+ toolCall.started_at = message._started_at;
2254
+ }
2160
2255
  if (message._ran_in_parallel === true) toolCall.ran_in_parallel = true;
2161
2256
  owner.blocks.push({
2162
2257
  type: "tool_result",
@@ -2181,10 +2276,12 @@ function projectHistory(entries) {
2181
2276
  tool_calls: toolCalls,
2182
2277
  model: stringOrNull(message.model),
2183
2278
  usage: isRecord(message._usage) ? { ...message._usage } : null,
2184
- duration_ms: numberOrZero(message._duration_ms),
2279
+ // 模型请求区间来自持久化字段;旧会话缺失时为 null,不用落盘时刻冒充(#2581 A1)。
2280
+ duration_ms: numberOrNull(message._duration_ms),
2185
2281
  first_token_ms: numberOrNull(message._first_token_ms),
2186
2282
  stream_duration_ms: numberOrNull(message._stream_duration_ms),
2187
- started_at: typeof entry.timestamp === "string" ? entry.timestamp : void 0,
2283
+ started_at: typeof message._started_at === "string" ? message._started_at : typeof entry.timestamp === "string" ? entry.timestamp : void 0,
2284
+ has_reported_timing: typeof message._started_at === "string",
2188
2285
  // fork 子会话的父锚点:历史里落在消息级 _parent_fork_tool_call_id。丢掉它,
2189
2286
  // 刷新后工作流步骤 fork 就配不回 workflow:run 工具调用(普通 Agent fork 有
2190
2287
  // root Agent 工具调用兜底配对,工作流 fork 没有,子智能体卡会整体消失)。
@@ -2201,10 +2298,15 @@ function projectHistory(entries) {
2201
2298
  previousTurn.status = turn.status;
2202
2299
  previousTurn.model = turn.model ?? previousTurn.model;
2203
2300
  previousTurn.usage = turn.usage ?? previousTurn.usage;
2204
- previousTurn.duration_ms = turn.duration_ms || previousTurn.duration_ms;
2301
+ if (turn.duration_ms) previousTurn.duration_ms = turn.duration_ms;
2205
2302
  previousTurn.first_token_ms = turn.first_token_ms ?? previousTurn.first_token_ms;
2206
2303
  previousTurn.stream_duration_ms = turn.stream_duration_ms ?? previousTurn.stream_duration_ms;
2207
- if (turn.started_at) previousTurn.started_at = turn.started_at;
2304
+ if (turn.has_reported_timing) {
2305
+ previousTurn.started_at = turn.started_at;
2306
+ previousTurn.has_reported_timing = true;
2307
+ } else if (!previousTurn.has_reported_timing) {
2308
+ previousTurn.started_at = previousTurn.started_at ?? turn.started_at;
2309
+ }
2208
2310
  previousTurn.id = turn.id;
2209
2311
  previousTurn.turn_id = turn.turn_id;
2210
2312
  if (isNativeToolEntry) nativeToolTurns.add(previousTurn);
@@ -2482,9 +2584,6 @@ function markerTurn(id, sequence, loopId, type3, content, toolCallId = null) {
2482
2584
  function stringOrNull(value) {
2483
2585
  return typeof value === "string" ? value : null;
2484
2586
  }
2485
- function numberOrZero(value) {
2486
- return typeof value === "number" ? value : 0;
2487
- }
2488
2587
  function numberOrNull(value) {
2489
2588
  return typeof value === "number" && value >= 0 ? value : null;
2490
2589
  }
@@ -2517,7 +2616,7 @@ function toCreateSessionPayload(request) {
2517
2616
  ...request.runtime_type ? { runtime_type: request.runtime_type } : {},
2518
2617
  ...request.daemon_id ? { daemon_id: request.daemon_id } : {},
2519
2618
  ...request.agent_runtime_id ? { agent_runtime_id: request.agent_runtime_id } : {},
2520
- ...request.chat_project_id ? { chat_project_id: request.chat_project_id } : {}
2619
+ ...request.chat_group_id ? { chat_group_id: request.chat_group_id } : {}
2521
2620
  };
2522
2621
  }
2523
2622
  var builtinSolutionIds = /* @__PURE__ */ new Set([
@@ -2602,6 +2701,18 @@ var SessionsResource = class {
2602
2701
  sessionPluginIconUrl(sessionId, name) {
2603
2702
  return this.client.buildAuthedUrl(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/icon`);
2604
2703
  }
2704
+ /**
2705
+ * 准备一个插件的配置:把包准备好并读出用户的配置契约,**不启用**它。
2706
+ *
2707
+ * 开启插件前必须先走这一步:缺配置时用户只会看到配置表单,服务端不会先把
2708
+ * active 持久化成 true 再靠补偿关掉。它不修改会话的插件选择、不连接 MCP。
2709
+ */
2710
+ prepareSessionPluginConfig(sessionId, name, init) {
2711
+ return this.client.jsonFromInit(
2712
+ `/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}:prepare-config`,
2713
+ { method: "POST", ...init }
2714
+ );
2715
+ }
2605
2716
  /**
2606
2717
  * Read a plugin's configuration contract and the user's stored values.
2607
2718
  *
@@ -2717,7 +2828,7 @@ var SessionsResource = class {
2717
2828
  solution_id,
2718
2829
  solution_registry_id,
2719
2830
  q,
2720
- chat_project_id
2831
+ chat_group_id
2721
2832
  }) {
2722
2833
  const searchParams = new URLSearchParams();
2723
2834
  searchParams.set("limit", String(limit));
@@ -2730,7 +2841,7 @@ var SessionsResource = class {
2730
2841
  }
2731
2842
  const trimmedQuery = q?.trim();
2732
2843
  if (trimmedQuery) searchParams.set("q", trimmedQuery);
2733
- if (chat_project_id) searchParams.set("chat_project_id", chat_project_id);
2844
+ if (chat_group_id) searchParams.set("chat_group_id", chat_group_id);
2734
2845
  return this.client.json("GET", `/api/sessions?${searchParams.toString()}`);
2735
2846
  }
2736
2847
  async listSessions(template_id_prefix, solution_id) {
@@ -3115,23 +3226,48 @@ function profileToCreateSessionRequest(profile) {
3115
3226
  };
3116
3227
  }
3117
3228
 
3118
- // src/resources/chat-projects.ts
3119
- var ChatProjectsResource = class {
3229
+ // src/resources/plugins.ts
3230
+ var PluginsResource = class {
3231
+ constructor(client) {
3232
+ this.client = client;
3233
+ }
3234
+ client;
3235
+ /**
3236
+ * 列出当前用户安装的插件及其展示信息。
3237
+ *
3238
+ * 服务端可能为了补齐名称/图标惰性获取包内容,但绝不激活插件、不连 MCP;
3239
+ * 单个插件的展示信息读不出来只会降级成占位,不会让整份目录失败。
3240
+ */
3241
+ catalog(init) {
3242
+ return this.client.jsonFromInit("/api/plugins/catalog", init);
3243
+ }
3244
+ /** 取一个插件的包内图标字节(用户级,不需要会话)。 */
3245
+ fetchIcon(name) {
3246
+ return this.client.blob("GET", `/api/plugins/${encodeURIComponent(name)}/icon`);
3247
+ }
3248
+ /** 带凭据的图标地址;能直接用 blob URL 的地方优先用 `fetchIcon`。 */
3249
+ iconUrl(name) {
3250
+ return this.client.buildAuthedUrl(`/api/plugins/${encodeURIComponent(name)}/icon`);
3251
+ }
3252
+ };
3253
+
3254
+ // src/resources/chat-groups.ts
3255
+ var ChatGroupsResource = class {
3120
3256
  constructor(client) {
3121
3257
  this.client = client;
3122
3258
  }
3123
3259
  client;
3124
3260
  list() {
3125
- return this.client.json("GET", "/api/chat-projects");
3261
+ return this.client.json("GET", "/api/chat-groups");
3126
3262
  }
3127
3263
  create(payload) {
3128
- return this.client.json("POST", "/api/chat-projects", payload);
3264
+ return this.client.json("POST", "/api/chat-groups", payload);
3129
3265
  }
3130
- update(projectId, payload) {
3131
- return this.client.json("PATCH", `/api/chat-projects/${encodeURIComponent(projectId)}`, payload);
3266
+ update(groupId, payload) {
3267
+ return this.client.json("PATCH", `/api/chat-groups/${encodeURIComponent(groupId)}`, payload);
3132
3268
  }
3133
- delete(projectId) {
3134
- return this.client.json("DELETE", `/api/chat-projects/${encodeURIComponent(projectId)}`);
3269
+ delete(groupId) {
3270
+ return this.client.json("DELETE", `/api/chat-groups/${encodeURIComponent(groupId)}`);
3135
3271
  }
3136
3272
  };
3137
3273
 
@@ -5934,7 +6070,9 @@ var BladeClient = class {
5934
6070
  computers;
5935
6071
  models;
5936
6072
  sessions;
5937
- chatProjects;
6073
+ /** 按用户读取的插件目录与图标;没有会话时也能用。 */
6074
+ plugins;
6075
+ chatGroups;
5938
6076
  /**
5939
6077
  * 全站访问统计:匿名读写,不带 bearer 也不触发登录刷新。
5940
6078
  * 只有内置 Web 的同源页面使用,第三方宿主页面不会被自动统计。
@@ -5952,7 +6090,8 @@ var BladeClient = class {
5952
6090
  this.computers = new ComputersResource(this);
5953
6091
  this.models = new ModelsResource(this);
5954
6092
  this.sessions = new SessionsResource(this);
5955
- this.chatProjects = new ChatProjectsResource(this);
6093
+ this.plugins = new PluginsResource(this);
6094
+ this.chatGroups = new ChatGroupsResource(this);
5956
6095
  this.siteVisits = new SiteVisitsResource(this);
5957
6096
  this.hub = new SessionHub(this);
5958
6097
  if (this.options.token === void 0) {
@@ -6421,7 +6560,7 @@ function resolveServiceUrl(endpoints, name, path = "") {
6421
6560
 
6422
6561
  // src/version.ts
6423
6562
  var SDK_NAME = "agent-client";
6424
- var SDK_VERSION = true ? "2610.0.0-beta.54" : "1.1.1";
6563
+ var SDK_VERSION = true ? "2610.0.0-beta.55" : "1.1.1";
6425
6564
 
6426
6565
  // src/commands/protocol.ts
6427
6566
  function isCommandEnvelope(value) {
@@ -6526,7 +6665,7 @@ export {
6526
6665
  AuthBusyReconnect,
6527
6666
  BladeApiError,
6528
6667
  BladeClient,
6529
- ChatProjectsResource,
6668
+ ChatGroupsResource,
6530
6669
  ClientProjectionBuilder,
6531
6670
  ComputersResource,
6532
6671
  DEFAULT_REPLAY_SPEED,
@@ -6534,6 +6673,7 @@ export {
6534
6673
  EMPTY_SESSION_QUEUE,
6535
6674
  LayoutType,
6536
6675
  ModelsResource,
6676
+ PluginsResource,
6537
6677
  PollingBackoff,
6538
6678
  SDK_NAME,
6539
6679
  SDK_VERSION,