@blade-hq/agent-client 2612.0.0-beta.2 → 2612.0.0-beta.20

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.
Files changed (89) hide show
  1. package/README.md +203 -19
  2. package/dist/blade-client.d.ts +12 -1
  3. package/dist/{chunk-PWJJJATA.js → chunk-2VIJBVAC.js} +1676 -680
  4. package/dist/chunk-2VIJBVAC.js.map +1 -0
  5. package/dist/index.d.ts +12 -3
  6. package/dist/index.js +9 -1
  7. package/dist/internal.d.ts +8 -3
  8. package/dist/internal.js +9 -1
  9. package/dist/internal.js.map +1 -1
  10. package/dist/resources/chat-groups.d.ts +92 -0
  11. package/dist/resources/headless.d.ts +5 -0
  12. package/dist/resources/plugins.d.ts +1 -0
  13. package/dist/resources/sessions.d.ts +94 -11
  14. package/dist/schemas/context-attachments.d.ts +16 -0
  15. package/dist/schemas/event.d.ts +2 -0
  16. package/dist/schemas/message-utils.d.ts +3 -9
  17. package/dist/schemas/message.d.ts +18 -1
  18. package/dist/schemas/plugin-refs.d.ts +15 -0
  19. package/dist/schemas/projection.d.ts +31 -0
  20. package/dist/schemas/session.d.ts +2 -42
  21. package/dist/schemas/topic-assets.d.ts +7 -0
  22. package/dist/session/agent-session.d.ts +51 -7
  23. package/dist/session/background.d.ts +14 -0
  24. package/dist/session/events.d.ts +0 -1
  25. package/dist/session/hub.d.ts +3 -0
  26. package/dist/session/log-cursor.d.ts +14 -0
  27. package/dist/session/state.d.ts +2 -1
  28. package/dist/session/workflow.d.ts +10 -3
  29. package/dist/shared/projection/builder.d.ts +5 -0
  30. package/dist/shared/projection/helpers.d.ts +2 -0
  31. package/dist/shared/projection/index.d.ts +1 -0
  32. package/dist/shared/projection/workflow.d.ts +24 -0
  33. package/dist/tool-provider.d.ts +51 -0
  34. package/dist/types/socket-events.d.ts +177 -6
  35. package/node_modules/engine.io-client/build/cjs/globals.node.d.ts +2 -3
  36. package/node_modules/engine.io-client/build/cjs/index.d.ts +3 -3
  37. package/node_modules/engine.io-client/build/cjs/index.js +1 -4
  38. package/node_modules/engine.io-client/build/cjs/socket.d.ts +39 -82
  39. package/node_modules/engine.io-client/build/cjs/socket.js +151 -218
  40. package/node_modules/engine.io-client/build/cjs/transport.d.ts +1 -1
  41. package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.d.ts +4 -4
  42. package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.js +2 -2
  43. package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.node.js +17 -7
  44. package/node_modules/engine.io-client/build/cjs/transports/websocket.node.js +17 -7
  45. package/node_modules/engine.io-client/build/esm/globals.node.d.ts +2 -3
  46. package/node_modules/engine.io-client/build/esm/index.d.ts +3 -3
  47. package/node_modules/engine.io-client/build/esm/index.js +1 -2
  48. package/node_modules/engine.io-client/build/esm/socket.d.ts +39 -82
  49. package/node_modules/engine.io-client/build/esm/socket.js +141 -206
  50. package/node_modules/engine.io-client/build/esm/transport.d.ts +1 -1
  51. package/node_modules/engine.io-client/build/esm/transports/polling-xhr.d.ts +4 -4
  52. package/node_modules/engine.io-client/build/esm/transports/polling-xhr.js +2 -2
  53. package/node_modules/engine.io-client/build/esm-debug/globals.node.d.ts +2 -3
  54. package/node_modules/engine.io-client/build/esm-debug/index.d.ts +3 -3
  55. package/node_modules/engine.io-client/build/esm-debug/index.js +1 -2
  56. package/node_modules/engine.io-client/build/esm-debug/socket.d.ts +39 -82
  57. package/node_modules/engine.io-client/build/esm-debug/socket.js +150 -215
  58. package/node_modules/engine.io-client/build/esm-debug/transport.d.ts +1 -1
  59. package/node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.d.ts +4 -4
  60. package/node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.js +2 -2
  61. package/node_modules/engine.io-client/dist/engine.io.esm.min.js +3 -3
  62. package/node_modules/engine.io-client/dist/engine.io.esm.min.js.map +1 -1
  63. package/node_modules/engine.io-client/dist/engine.io.js +164 -239
  64. package/node_modules/engine.io-client/dist/engine.io.js.map +1 -1
  65. package/node_modules/engine.io-client/dist/engine.io.min.js +3 -3
  66. package/node_modules/engine.io-client/dist/engine.io.min.js.map +1 -1
  67. package/node_modules/engine.io-client/package.json +2 -2
  68. package/node_modules/socket.io-parser/build/cjs/binary.js +4 -1
  69. package/node_modules/socket.io-parser/build/cjs/index.d.ts +14 -4
  70. package/node_modules/socket.io-parser/build/cjs/index.js +18 -11
  71. package/node_modules/socket.io-parser/build/esm/binary.js +4 -1
  72. package/node_modules/socket.io-parser/build/esm/index.d.ts +14 -4
  73. package/node_modules/socket.io-parser/build/esm/index.js +14 -10
  74. package/node_modules/socket.io-parser/build/esm-debug/binary.js +4 -1
  75. package/node_modules/socket.io-parser/build/esm-debug/index.d.ts +14 -4
  76. package/node_modules/socket.io-parser/build/esm-debug/index.js +14 -10
  77. package/node_modules/socket.io-parser/package.json +1 -1
  78. package/node_modules/ws/index.js +15 -6
  79. package/node_modules/ws/lib/constants.js +1 -0
  80. package/node_modules/ws/lib/permessage-deflate.js +9 -7
  81. package/node_modules/ws/lib/receiver.js +37 -0
  82. package/node_modules/ws/lib/sender.js +6 -1
  83. package/node_modules/ws/lib/websocket-server.js +18 -6
  84. package/node_modules/ws/lib/websocket.js +50 -17
  85. package/node_modules/ws/package.json +8 -3
  86. package/node_modules/ws/wrapper.mjs +14 -1
  87. package/package.json +4 -4
  88. package/public-api.md +329 -26
  89. package/dist/chunk-PWJJJATA.js.map +0 -1
@@ -1,6 +1,219 @@
1
+ // src/rest.ts
2
+ var DOCS_URL = "https://blade-hq.github.io/public-skills/";
3
+ var BladeApiError = class extends Error {
4
+ response;
5
+ status;
6
+ statusText;
7
+ constructor(response, message) {
8
+ super(message ?? `\u8BF7\u6C42\u5931\u8D25\uFF08${response.status} ${response.statusText}\uFF09`);
9
+ this.name = "BladeApiError";
10
+ this.response = response;
11
+ this.status = response.status;
12
+ this.statusText = response.statusText;
13
+ }
14
+ };
15
+ async function extractErrorDetail(response) {
16
+ try {
17
+ const cloned = response.clone();
18
+ const contentType = cloned.headers.get("content-type") ?? "";
19
+ if (contentType.includes("application/json")) {
20
+ const data = await cloned.json();
21
+ const detail = data?.detail;
22
+ if (typeof detail === "string" && detail.trim()) return detail;
23
+ if (Array.isArray(detail) && detail.length > 0) {
24
+ const first = detail[0];
25
+ if (first && typeof first.msg === "string") return first.msg;
26
+ }
27
+ } else {
28
+ const text = (await cloned.text()).trim();
29
+ if (text) return text;
30
+ }
31
+ } catch {
32
+ }
33
+ return void 0;
34
+ }
35
+ function decorateAuthError(error) {
36
+ if (!(error instanceof BladeApiError)) return error;
37
+ if (error.status === 401) {
38
+ error.message = `${error.message}\uFF08\u672A\u767B\u5F55\u6216\u51ED\u8BC1\u5DF2\u5931\u6548\u3002\u6D4F\u89C8\u5668\u573A\u666F\u8C03\u7528 client.auth.login() \u5F39\u7A97\u767B\u5F55\uFF0C\u81EA\u52A8\u5316\u573A\u666F\u5728 new BladeClient({ token }) \u4F20\u5165 PAT\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
39
+ } else if (error.status === 403) {
40
+ error.message = `${error.message}\uFF08\u5F53\u524D\u8D26\u53F7\u65E0\u6743\u8BBF\u95EE\u8BE5\u8D44\u6E90\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
41
+ }
42
+ return error;
43
+ }
44
+ function buildBackendUrl(baseUrl, path) {
45
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
46
+ return new URL(path.startsWith("/") ? path.slice(1) : path, base);
47
+ }
48
+
49
+ // src/tool-provider.ts
50
+ var ToolProvider = class {
51
+ constructor(client, options, onDispose) {
52
+ this.client = client;
53
+ this.options = options;
54
+ this.onDispose = onDispose;
55
+ if (!/^[a-zA-Z0-9_-]{1,128}$/.test(options.providerId) || options.capability.length < 16 || options.capability.length > 4096) throw new Error("Invalid tool provider identity");
56
+ if (new Set(options.tools.map((t) => t.name)).size !== options.tools.length || options.tools.length > 256) throw new Error("Duplicate or excessive provider tools");
57
+ this.url = `blade-rpc://${options.providerId}`;
58
+ this.open();
59
+ }
60
+ client;
61
+ options;
62
+ onDispose;
63
+ url;
64
+ socket;
65
+ timer;
66
+ registrationTimer;
67
+ disposed = false;
68
+ registered = false;
69
+ attempts = 0;
70
+ calls = /* @__PURE__ */ new Map();
71
+ waiters = /* @__PURE__ */ new Set();
72
+ /** Resolves only after BA has authenticated and registered this connection. */
73
+ ready() {
74
+ if (this.disposed) return Promise.reject(new Error("Tool provider disposed"));
75
+ if (this.registered) return Promise.resolve();
76
+ return new Promise((resolve, reject) => {
77
+ const timer = setTimeout(() => {
78
+ this.waiters.delete(waiter);
79
+ reject(new Error("Tool provider registration timed out"));
80
+ }, 25e3);
81
+ const waiter = { resolve: () => {
82
+ clearTimeout(timer);
83
+ resolve();
84
+ }, reject: (e) => {
85
+ clearTimeout(timer);
86
+ reject(e);
87
+ } };
88
+ this.waiters.add(waiter);
89
+ });
90
+ }
91
+ reconnect() {
92
+ if (this.disposed) return;
93
+ clearTimeout(this.timer);
94
+ this.reset();
95
+ this.open();
96
+ }
97
+ reset() {
98
+ clearTimeout(this.registrationTimer);
99
+ this.registered = false;
100
+ const socket = this.socket;
101
+ this.socket = void 0;
102
+ socket?.close();
103
+ for (const controller of this.calls.values()) controller.abort();
104
+ this.calls.clear();
105
+ }
106
+ fail(error) {
107
+ for (const waiter of this.waiters) waiter.reject(error);
108
+ this.waiters.clear();
109
+ try {
110
+ this.options.onError?.(error);
111
+ } catch {
112
+ }
113
+ }
114
+ open() {
115
+ if (this.disposed) return;
116
+ const endpoint = buildBackendUrl(this.client.options.baseUrl, "/api/app-tools/connect");
117
+ endpoint.protocol = endpoint.protocol === "https:" ? "wss:" : "ws:";
118
+ let socket;
119
+ try {
120
+ const token = this.client.token;
121
+ if (!token) throw new Error("Tool provider requires a token");
122
+ const encoded = btoa(Array.from(new TextEncoder().encode(token), (byte) => String.fromCharCode(byte)).join("")).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
123
+ socket = new WebSocket(endpoint, ["blade-rpc", `blade-bearer.${encoded}`]);
124
+ } catch {
125
+ this.fail(new Error("Tool provider WebSocket unavailable"));
126
+ this.schedule();
127
+ return;
128
+ }
129
+ this.socket = socket;
130
+ this.registrationTimer = setTimeout(() => {
131
+ if (this.socket === socket) {
132
+ this.fail(new Error("Tool provider registration timed out"));
133
+ socket.close();
134
+ }
135
+ }, 15e3);
136
+ socket.addEventListener("open", () => {
137
+ if (this.socket !== socket) return;
138
+ socket.send(JSON.stringify({ jsonrpc: "2.0", id: "register", method: "provider/register", params: { providerId: this.options.providerId, capability: this.options.capability } }));
139
+ });
140
+ socket.addEventListener("message", (event) => {
141
+ if (this.socket !== socket || typeof event.data !== "string") return;
142
+ void this.receive(socket, event.data).catch(() => {
143
+ this.fail(new Error("Invalid application tool RPC message"));
144
+ socket.close();
145
+ });
146
+ });
147
+ socket.addEventListener("error", () => {
148
+ if (this.socket === socket) this.fail(new Error("Application tool connection failed"));
149
+ });
150
+ socket.addEventListener("close", () => {
151
+ if (this.socket !== socket) return;
152
+ this.reset();
153
+ this.fail(new Error("Application tool connection closed; unfinished calls will not be replayed"));
154
+ this.schedule();
155
+ });
156
+ }
157
+ schedule() {
158
+ if (this.disposed) return;
159
+ this.timer = setTimeout(() => this.open(), Math.min(1e4, 500 * 2 ** Math.min(this.attempts++, 5)));
160
+ }
161
+ async receive(socket, text) {
162
+ const message = JSON.parse(text);
163
+ if (message.jsonrpc !== "2.0") throw new Error("Invalid JSON-RPC envelope");
164
+ if (message.id === "register" && !message.method) {
165
+ clearTimeout(this.registrationTimer);
166
+ if (message.error || !message.result?.registered) {
167
+ this.fail(new Error(message.error?.message ?? "Tool provider registration rejected"));
168
+ socket.close();
169
+ return;
170
+ }
171
+ this.registered = true;
172
+ this.attempts = 0;
173
+ for (const waiter of this.waiters) waiter.resolve();
174
+ this.waiters.clear();
175
+ return;
176
+ }
177
+ if (!this.registered) throw new Error("Provider not registered");
178
+ if (message.method === "notifications/cancelled") {
179
+ this.calls.get(String(message.params?.requestId))?.abort();
180
+ return;
181
+ }
182
+ if (!message.id || typeof message.id !== "string" || this.calls.has(message.id)) throw new Error("Invalid RPC request ID");
183
+ const controller = new AbortController();
184
+ this.calls.set(message.id, controller);
185
+ let response = { jsonrpc: "2.0", id: message.id };
186
+ try {
187
+ if (message.method === "tools/list") {
188
+ response.result = { tools: this.options.tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })) };
189
+ } else if (message.method === "tools/call") {
190
+ const params = message.params;
191
+ const tool = this.options.tools.find((t) => t.name === params?.name);
192
+ if (!tool || typeof params?.sessionId !== "string" || !params.sessionId || !params.arguments || typeof params.arguments !== "object" || Array.isArray(params.arguments)) throw new Error("Invalid or unauthorized tool call");
193
+ response.result = await tool.handler(params.arguments, { sessionId: params.sessionId, signal: controller.signal });
194
+ } else {
195
+ response.error = { code: -32601, message: "Method not found" };
196
+ }
197
+ } catch {
198
+ response = { jsonrpc: "2.0", id: message.id, error: { code: -32e3, message: "Application tool execution failed" } };
199
+ } finally {
200
+ this.calls.delete(message.id);
201
+ }
202
+ if (this.socket === socket && socket.readyState === WebSocket.OPEN && !controller.signal.aborted) socket.send(JSON.stringify(response));
203
+ }
204
+ dispose() {
205
+ if (this.disposed) return;
206
+ this.disposed = true;
207
+ clearTimeout(this.timer);
208
+ this.reset();
209
+ this.fail(new Error("Tool provider disposed"));
210
+ this.onDispose();
211
+ }
212
+ };
213
+
1
214
  // src/version.ts
2
215
  var SDK_NAME = "agent-client";
3
- var SDK_VERSION = true ? "2612.0.0-beta.2" : "1.1.1";
216
+ var SDK_VERSION = true ? "2612.0.0-beta.20" : "1.1.1";
4
217
 
5
218
  // src/auth-login.ts
6
219
  function isAuthCallbackMessage(value) {
@@ -128,54 +341,6 @@ async function loginWithPopup(client, options = {}) {
128
341
  });
129
342
  }
130
343
 
131
- // src/rest.ts
132
- var DOCS_URL = "https://blade-hq.github.io/public-skills/";
133
- var BladeApiError = class extends Error {
134
- response;
135
- status;
136
- statusText;
137
- constructor(response, message) {
138
- super(message ?? `\u8BF7\u6C42\u5931\u8D25\uFF08${response.status} ${response.statusText}\uFF09`);
139
- this.name = "BladeApiError";
140
- this.response = response;
141
- this.status = response.status;
142
- this.statusText = response.statusText;
143
- }
144
- };
145
- async function extractErrorDetail(response) {
146
- try {
147
- const cloned = response.clone();
148
- const contentType = cloned.headers.get("content-type") ?? "";
149
- if (contentType.includes("application/json")) {
150
- const data = await cloned.json();
151
- const detail = data?.detail;
152
- if (typeof detail === "string" && detail.trim()) return detail;
153
- if (Array.isArray(detail) && detail.length > 0) {
154
- const first = detail[0];
155
- if (first && typeof first.msg === "string") return first.msg;
156
- }
157
- } else {
158
- const text = (await cloned.text()).trim();
159
- if (text) return text;
160
- }
161
- } catch {
162
- }
163
- return void 0;
164
- }
165
- function decorateAuthError(error) {
166
- if (!(error instanceof BladeApiError)) return error;
167
- if (error.status === 401) {
168
- error.message = `${error.message}\uFF08\u672A\u767B\u5F55\u6216\u51ED\u8BC1\u5DF2\u5931\u6548\u3002\u6D4F\u89C8\u5668\u573A\u666F\u8C03\u7528 client.auth.login() \u5F39\u7A97\u767B\u5F55\uFF0C\u81EA\u52A8\u5316\u573A\u666F\u5728 new BladeClient({ token }) \u4F20\u5165 PAT\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
169
- } else if (error.status === 403) {
170
- error.message = `${error.message}\uFF08\u5F53\u524D\u8D26\u53F7\u65E0\u6743\u8BBF\u95EE\u8BE5\u8D44\u6E90\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
171
- }
172
- return error;
173
- }
174
- function buildBackendUrl(baseUrl, path) {
175
- const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
176
- return new URL(path.startsWith("/") ? path.slice(1) : path, base);
177
- }
178
-
179
344
  // src/resources/auth.ts
180
345
  var AuthResource = class {
181
346
  constructor(client) {
@@ -227,404 +392,167 @@ function isRecord(value) {
227
392
  return typeof value === "object" && value !== null && !Array.isArray(value);
228
393
  }
229
394
 
230
- // src/resources/headless.ts
231
- var HeadlessError = class extends Error {
232
- constructor(message, detail) {
233
- super(message);
234
- this.detail = detail;
235
- this.name = "HeadlessError";
395
+ // src/session/log-cursor.ts
396
+ function positive(value) {
397
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
398
+ }
399
+ var LogCursor = class {
400
+ sequence = 0;
401
+ epoch = 0;
402
+ deltas = /* @__PURE__ */ new Map();
403
+ subscription() {
404
+ return this.sequence > 0 ? { after_log_seq: this.sequence } : {};
405
+ }
406
+ consume(events) {
407
+ const accepted = [];
408
+ let reload = false;
409
+ for (const event of events) {
410
+ if (event.type === "log:retract") {
411
+ this.deltas.clear();
412
+ reload = true;
413
+ continue;
414
+ }
415
+ if (event.type === "log:record") {
416
+ if (!positive(event.log_seq) || event.log_seq <= this.sequence) continue;
417
+ const nested = event.payload.events;
418
+ if (!Array.isArray(nested)) continue;
419
+ if (positive(event.payload.epoch) && event.payload.epoch !== this.epoch) {
420
+ this.epoch = event.payload.epoch;
421
+ this.deltas.clear();
422
+ reload = true;
423
+ }
424
+ accepted.push(...nested);
425
+ if (["step_end", "run_end", "truncate"].includes(String(event.payload.kind))) {
426
+ for (const step of this.deltas.keys()) if (step <= event.log_seq) this.deltas.delete(step);
427
+ }
428
+ this.sequence = event.log_seq;
429
+ continue;
430
+ }
431
+ if (event.type === "llm:stream:reset" && positive(event.step_log_seq)) {
432
+ this.deltas.delete(event.step_log_seq);
433
+ }
434
+ if (positive(event.step_log_seq) && positive(event.delta_seq)) {
435
+ if (event.delta_seq <= (this.deltas.get(event.step_log_seq) ?? 0)) continue;
436
+ this.deltas.set(event.step_log_seq, event.delta_seq);
437
+ }
438
+ if (event.type === "log:delta" && Array.isArray(event.payload.events)) {
439
+ accepted.push(...event.payload.events);
440
+ } else {
441
+ accepted.push(event);
442
+ }
443
+ }
444
+ return { events: accepted, reload };
236
445
  }
237
- detail;
238
446
  };
239
- var SYSTEM_ERROR_CHAT_END_GRACE_MS = 1e3;
240
- var HeadlessResource = class {
241
- constructor(client) {
242
- this.client = client;
447
+
448
+ // src/schemas/context-attachments.ts
449
+ function snapshotContextAttachments(value) {
450
+ const snapshot2 = JSON.parse(JSON.stringify(value));
451
+ if (!Array.isArray(snapshot2) || snapshot2.length > 20) throw new Error("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u63D0\u4F9B 20 \u4EFD\u8D44\u6599");
452
+ const ids = /* @__PURE__ */ new Set();
453
+ const validText = (text, limit) => typeof text === "string" && [...text].length > 0 && [...text].length <= limit;
454
+ for (const item of snapshot2) {
455
+ if (!item || !validText(item.id, 128) || !validText(item.label, 512) || !validText(item.content, 64e3) || ids.has(item.id)) throw new Error("\u9644\u52A0\u8D44\u6599\u683C\u5F0F\u9519\u8BEF\u6216 ID \u91CD\u590D");
456
+ ids.add(item.id);
457
+ if (item.data != null && (typeof item.data !== "object" || Array.isArray(item.data))) throw new Error("\u8D44\u6599\u6570\u636E\u5FC5\u987B\u662F JSON \u5BF9\u8C61");
458
+ if (item.files != null && (!Array.isArray(item.files) || item.files.length > 20)) throw new Error("\u8D44\u6599\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF");
459
+ for (const file of item.files ?? []) {
460
+ if (!validText(file.name, 512)) throw new Error("\u8D44\u6599\u6587\u4EF6\u540D\u683C\u5F0F\u9519\u8BEF");
461
+ if (file.session_id == null && file.path == null) continue;
462
+ if (!validText(file.session_id, 128) || !validText(file.path, 2048) || /[\\:]/.test(file.path) || file.path.replace(/^\//, "").split("/").some((part) => !part || part === "." || part === "..")) throw new Error("\u8D44\u6599\u6587\u4EF6\u5FC5\u987B\u4F7F\u7528\u6709\u6548\u7684\u4F1A\u8BDD\u6587\u4EF6\u8DEF\u5F84");
463
+ }
464
+ }
465
+ if (new TextEncoder().encode(JSON.stringify(snapshot2)).length > 256 * 1024) throw new Error("\u6D88\u606F\u9644\u52A0\u8D44\u6599\u4E0D\u80FD\u8D85\u8FC7 256 KiB");
466
+ return snapshot2;
467
+ }
468
+ function normalizeContextAttachments(value) {
469
+ return Array.isArray(value) ? value : [];
470
+ }
471
+
472
+ // src/schemas/plugin-refs.ts
473
+ function normalizePluginRefs(value) {
474
+ if (!Array.isArray(value)) return [];
475
+ const seen = /* @__PURE__ */ new Set();
476
+ return value.flatMap((item) => {
477
+ if (!item || typeof item !== "object" || item.provider !== "plugin" || ![item.plugin, item.server, item.id, item.label].every((part) => typeof part === "string" && part)) return [];
478
+ const key = JSON.stringify([item.plugin, item.server, item.id]);
479
+ if (seen.has(key)) return [];
480
+ seen.add(key);
481
+ return [{ provider: "plugin", plugin: item.plugin, server: item.server, id: item.id, label: item.label }];
482
+ });
483
+ }
484
+ function normalizeMessageRefs(value) {
485
+ if (!Array.isArray(value)) return [];
486
+ const seen = /* @__PURE__ */ new Set();
487
+ return value.flatMap((item) => {
488
+ if (!item || typeof item !== "object") return [];
489
+ const ref = item.provider === "skill" && typeof item.id === "string" && item.id && typeof item.label === "string" && item.label ? { provider: "skill", id: item.id, label: item.label } : normalizePluginRefs([item])[0];
490
+ if (!ref) return [];
491
+ const key = ref.provider === "skill" ? JSON.stringify([ref.provider, ref.id]) : JSON.stringify([ref.provider, ref.plugin, ref.server, ref.id]);
492
+ if (seen.has(key)) return [];
493
+ seen.add(key);
494
+ return [ref];
495
+ });
496
+ }
497
+
498
+ // src/schemas/topic-assets.ts
499
+ function normalizeTopicAssetRefs(value) {
500
+ if (!Array.isArray(value)) return [];
501
+ const seen = /* @__PURE__ */ new Set();
502
+ return value.flatMap((item) => {
503
+ if (!item || typeof item !== "object" || typeof item.topic_id !== "string" || !item.topic_id || typeof item.path !== "string" || !item.path) return [];
504
+ const key = JSON.stringify([item.topic_id, item.path]);
505
+ if (seen.has(key)) return [];
506
+ seen.add(key);
507
+ return [{ topic_id: item.topic_id, path: item.path }];
508
+ });
509
+ }
510
+
511
+ // src/shared/projection/helpers.ts
512
+ function upsertToolCall(state, toolCallId, toolName, displayName, args) {
513
+ if (!toolCallId) return;
514
+ let found = false;
515
+ for (let i = 0; i < state.toolCalls.length; i++) {
516
+ if (state.toolCalls[i].id !== toolCallId) continue;
517
+ const tc = state.toolCalls[i];
518
+ state.toolCalls[i] = {
519
+ ...tc,
520
+ tool_name: toolName || tc.tool_name,
521
+ display_name: displayName || tc.display_name,
522
+ arguments: args || tc.arguments
523
+ };
524
+ found = true;
525
+ break;
243
526
  }
244
- client;
245
- async run(prompt, options = {}) {
246
- const { session_id } = await this.client.sessions.createSessionWithRequest({
247
- intent: prompt,
248
- model: options.model ?? null
527
+ if (!found) {
528
+ state.toolCalls.push({
529
+ id: toolCallId,
530
+ tool_name: toolName,
531
+ display_name: displayName,
532
+ arguments: args,
533
+ status: "pending"
249
534
  });
250
- return this.runTracked(session_id, prompt, options, false);
251
535
  }
252
- async runInSession(sessionId, prompt, options = {}) {
253
- return this.runTracked(sessionId, prompt, options, false);
536
+ let blockFound = false;
537
+ for (let i = 0; i < state.blocks.length; i++) {
538
+ const b = state.blocks[i];
539
+ if (b.type !== "tool_use" || b.tool_call_id !== toolCallId) continue;
540
+ state.blocks[i] = {
541
+ ...b,
542
+ content: args || b.content,
543
+ tool_name: toolName || b.tool_name,
544
+ display_name: displayName || b.display_name
545
+ };
546
+ blockFound = true;
547
+ break;
254
548
  }
255
- async runWithSession(prompt, options = {}) {
256
- const { session_id } = await this.client.sessions.createSessionWithRequest({
257
- intent: prompt,
258
- model: options.model ?? null
259
- });
260
- return this.runTracked(session_id, prompt, options, true);
261
- }
262
- async runTracked(sessionId, prompt, options, returnEnvelope) {
263
- const timeoutMs = options.timeoutMs ?? 3e5;
264
- const session_id = sessionId;
265
- const socket = this.client.socket();
266
- await ensureSocketConnected(socket, timeoutMs, { session_id });
267
- return new Promise((resolve, reject) => {
268
- let settled = false;
269
- let stopSent = false;
270
- let started = false;
271
- let chatRunId = null;
272
- let systemErrorTimeout = null;
273
- const timeout = setTimeout(() => {
274
- fail(new HeadlessError(`headless run timed out after ${timeoutMs}ms`, { session_id, chat_run_id: chatRunId }));
275
- }, timeoutMs);
276
- const cleanup = (sendStop) => {
277
- clearTimeout(timeout);
278
- if (systemErrorTimeout) {
279
- clearTimeout(systemErrorTimeout);
280
- systemErrorTimeout = null;
281
- }
282
- socket.off("chat:start", onChatStart);
283
- socket.off("chat:end", onChatEnd);
284
- socket.off("system:error", onSystemError);
285
- if (sendStop && !stopSent) {
286
- stopSent = true;
287
- socket.emit("chat:stop", { session_id }, () => void 0);
288
- }
289
- socket.emit("session:unsubscribe", { session_id });
290
- };
291
- const finish = (value) => {
292
- if (settled) return;
293
- settled = true;
294
- cleanup(false);
295
- if (returnEnvelope) {
296
- resolve({ session_id, chat_run_id: chatRunId, result: value });
297
- return;
298
- }
299
- resolve(value);
300
- };
301
- const fail = (error) => {
302
- if (settled) return;
303
- settled = true;
304
- cleanup(true);
305
- reject(error);
306
- };
307
- const failWithoutStop = (error) => {
308
- if (settled) return;
309
- settled = true;
310
- cleanup(false);
311
- reject(error);
312
- };
313
- const rejectAfterChatEnd = (error) => {
314
- if (settled) return;
315
- settled = true;
316
- cleanup(false);
317
- reject(error);
318
- };
319
- function onChatStart(payload) {
320
- if (payload.session_id && payload.session_id !== session_id) return;
321
- started = true;
322
- }
323
- function onSystemError(payload) {
324
- if (payload.session_id !== session_id) return;
325
- if (!started) {
326
- failWithoutStop(new HeadlessError(payload.message || "headless run failed", {
327
- session_id,
328
- chat_run_id: chatRunId,
329
- event: payload
330
- }));
331
- return;
332
- }
333
- if (systemErrorTimeout) {
334
- clearTimeout(systemErrorTimeout);
335
- }
336
- systemErrorTimeout = setTimeout(() => {
337
- systemErrorTimeout = null;
338
- fail(new HeadlessError(payload.message || "headless run failed", {
339
- session_id,
340
- chat_run_id: chatRunId,
341
- event: payload
342
- }));
343
- }, SYSTEM_ERROR_CHAT_END_GRACE_MS);
344
- }
345
- function onChatEnd(payload) {
346
- if (payload.session_id && payload.session_id !== session_id) return;
347
- chatRunId = typeof payload.chat_run_id === "string" ? payload.chat_run_id : chatRunId;
348
- const result = payload.result;
349
- if (payload.status === "failed") {
350
- rejectAfterChatEnd(new HeadlessError(errorMessageFromResult(result) ?? "headless run failed", {
351
- session_id,
352
- chat_run_id: chatRunId,
353
- event: payload
354
- }));
355
- return;
356
- }
357
- try {
358
- finish(normalizeResult(result, Boolean(options.schema)));
359
- } catch (error) {
360
- rejectAfterChatEnd(error);
361
- }
362
- }
363
- socket.on("chat:start", onChatStart);
364
- socket.on("chat:end", onChatEnd);
365
- socket.on("system:error", onSystemError);
366
- socket.emit("session:subscribe", {
367
- session_id,
368
- event_format: "raw"
369
- });
370
- socket.emit(
371
- "chat:send",
372
- {
373
- session_id,
374
- message: prompt,
375
- headless: true,
376
- output_schema: options.schema ?? null,
377
- model: options.model ?? null
378
- },
379
- (response) => {
380
- if (response.status === "accepted") return;
381
- rejectAfterChatEnd(
382
- new HeadlessError(response.message ?? "headless run was not accepted", response)
383
- );
384
- }
385
- );
386
- });
387
- }
388
- };
389
- function normalizeResult(result, hasSchema) {
390
- if (!isRecord(result)) {
391
- throw new HeadlessError("headless run finished without a result", result);
392
- }
393
- const errMsg = typeof result.err_msg === "string" ? result.err_msg : "";
394
- if (errMsg) {
395
- throw new HeadlessError(errMsg, result);
396
- }
397
- if (!hasSchema) {
398
- const text = result.result;
399
- return typeof text === "string" ? text : String(text ?? "");
400
- }
401
- const { err_msg: _errMsg, ...businessResult } = result;
402
- return businessResult;
403
- }
404
- function errorMessageFromResult(result) {
405
- if (isRecord(result) && typeof result.err_msg === "string" && result.err_msg) {
406
- return result.err_msg;
407
- }
408
- return null;
409
- }
410
- function ensureSocketConnected(socket, timeoutMs, detail) {
411
- if (socket.connected) {
412
- return Promise.resolve();
413
- }
414
- return new Promise((resolve, reject) => {
415
- let settled = false;
416
- const timeout = setTimeout(() => {
417
- finish(() => reject(new HeadlessError(`socket connection timed out after ${timeoutMs}ms`, detail)));
418
- }, timeoutMs);
419
- const onConnect = () => finish(resolve);
420
- const onConnectError = (error) => {
421
- finish(() => reject(new HeadlessError("socket connection failed", { ...detail, event: error })));
422
- };
423
- const finish = (callback) => {
424
- if (settled) return;
425
- settled = true;
426
- clearTimeout(timeout);
427
- socket.off("connect", onConnect);
428
- socket.off("connect_error", onConnectError);
429
- callback();
430
- };
431
- socket.on("connect", onConnect);
432
- socket.on("connect_error", onConnectError);
433
- socket.connect();
434
- });
435
- }
436
-
437
- // src/resources/computers.ts
438
- function computerState(computer) {
439
- if (computer.is_primary) return "primary";
440
- if (computer.enabled) return "enabled";
441
- return computer.online ? "idle" : "offline";
442
- }
443
- function computerOS(computer) {
444
- const os = (computer.os ?? "").toLowerCase();
445
- if (os.startsWith("darwin") || os.startsWith("mac")) return "macos";
446
- if (os.startsWith("windows")) return "windows";
447
- if (os.startsWith("linux")) return "linux";
448
- return "unknown";
449
- }
450
- function computerPlatformLabel(computer) {
451
- const os = (computer.os ?? "").trim();
452
- const arch = (computer.arch ?? "").trim();
453
- if (os && arch) return `${os}/${arch}`;
454
- return os || arch || "";
455
- }
456
- function computerDaemonVersion(computer) {
457
- return (computer.daemon_version ?? "").trim();
458
- }
459
- function canToggleComputer(computer) {
460
- return !computer.is_primary;
461
- }
462
- function sortComputers(computers) {
463
- return [...computers].sort((a, b) => {
464
- const byCreated = (a.created_at ?? "").localeCompare(b.created_at ?? "");
465
- if (byCreated !== 0) return byCreated;
466
- return a.label.localeCompare(b.label);
467
- });
468
- }
469
- var ComputersResource = class {
470
- constructor(client) {
471
- this.client = client;
472
- }
473
- client;
474
- /**
475
- * 列出当前账号登记过的全部电脑及其在线状态。
476
- *
477
- * 首页还没有会话,也要能看到同一份清单——判定各写一份必然分叉,一边的兜底会把
478
- * 另一边的错误盖住(#1597)。所以账号事实只从这一个接口读。
479
- */
480
- async listAccount() {
481
- const data = await this.client.json("GET", "/api/daemon/instances");
482
- return {
483
- computers: (data.instances ?? []).map((instance) => ({
484
- ...instance,
485
- online: Boolean(instance.online)
486
- }))
487
- };
488
- }
489
- /**
490
- * 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
491
- * 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
492
- */
493
- list(sessionId) {
494
- return this.client.json(
495
- "GET",
496
- `/api/sessions/${encodeURIComponent(sessionId)}/computers`
497
- );
498
- }
499
- /**
500
- * 启动或停止一台电脑。只改这次会话的可用范围,不会碰远程机器,
501
- * 电脑离线时同样能登记,等它上线就直接可用。
502
- */
503
- setEnabled(sessionId, computer, enabled) {
504
- return this.client.json(
505
- "PUT",
506
- `/api/sessions/${encodeURIComponent(sessionId)}/computers/${encodeURIComponent(computer)}/enabled`,
507
- { enabled }
508
- );
509
- }
510
- };
511
-
512
- // src/resources/models.ts
513
- var ModelsResource = class {
514
- constructor(client) {
515
- this.client = client;
516
- }
517
- client;
518
- list() {
519
- return this.client.json("GET", "/api/config/models");
520
- }
521
- };
522
-
523
- // src/schemas/session.ts
524
- import { type } from "arktype";
525
- var SessionStatus = type(
526
- "'created' | 'running' | 'completed' | 'failed' | 'interrupted' | 'waiting_for_input'"
527
- );
528
- var DEFAULT_REPLAY_SPEED = 5;
529
- var SessionInfo = type({
530
- id: "string",
531
- intent: "string",
532
- status: SessionStatus,
533
- created_at: "string",
534
- updated_at: "string",
535
- "shared?": "boolean",
536
- "memory_enabled?": "boolean",
537
- "is_persistent?": "boolean",
538
- "initiator?": "'ui' | 'sdk' | 'unknown' | null",
539
- "is_headless?": "boolean",
540
- "viewer_role?": "'owner' | 'viewer'",
541
- "template_id?": "string | null",
542
- "model?": "string | null",
543
- "enable_thinking?": "boolean | null",
544
- "solution_id?": "string | null",
545
- "solution_ref?": "unknown",
546
- "biz_role_id?": "string | null",
547
- "solution?": "unknown",
548
- "plan_summary?": "string | null",
549
- "primary_skill_id?": "string | null",
550
- // ship-attack v2:后端 engine.set_bound_skill 写入的"绑定 skill id"。
551
- // 未绑定 / 非 ship-attack session 为 null。
552
- "bound_skill_id?": "string | null",
553
- "replay_state?": "unknown",
554
- "is_pinned?": "boolean",
555
- "pinned_at?": "string | null",
556
- "is_example?": "boolean",
557
- "ports?": "unknown",
558
- "disable_tools?": "string[]",
559
- "runtime_type?": "string | null",
560
- "daemon_id?": "string | null",
561
- "agent_runtime_id?": "string | null",
562
- "workspace_path?": "string | null",
563
- "ba_version?": "string | null",
564
- "sandbox_version?": "string | null",
565
- "chat_group_id?": "string | null",
566
- "match?": "unknown"
567
- });
568
-
569
- // src/session/definition.ts
570
- var SessionSetupError = class extends Error {
571
- constructor(message, stage, sessionId, options) {
572
- super(message);
573
- this.stage = stage;
574
- this.sessionId = sessionId;
575
- this.cause = options?.cause;
576
- }
577
- stage;
578
- sessionId;
579
- name = "SessionSetupError";
580
- cause;
581
- };
582
-
583
- // src/shared/projection/helpers.ts
584
- function upsertToolCall(state, toolCallId, toolName, displayName, args) {
585
- if (!toolCallId) return;
586
- let found = false;
587
- for (let i = 0; i < state.toolCalls.length; i++) {
588
- if (state.toolCalls[i].id !== toolCallId) continue;
589
- const tc = state.toolCalls[i];
590
- state.toolCalls[i] = {
591
- ...tc,
592
- tool_name: toolName || tc.tool_name,
593
- display_name: displayName || tc.display_name,
594
- arguments: args || tc.arguments
595
- };
596
- found = true;
597
- break;
598
- }
599
- if (!found) {
600
- state.toolCalls.push({
601
- id: toolCallId,
602
- tool_name: toolName,
603
- display_name: displayName,
604
- arguments: args,
605
- status: "pending"
606
- });
607
- }
608
- let blockFound = false;
609
- for (let i = 0; i < state.blocks.length; i++) {
610
- const b = state.blocks[i];
611
- if (b.type !== "tool_use" || b.tool_call_id !== toolCallId) continue;
612
- state.blocks[i] = {
613
- ...b,
614
- content: args || b.content,
615
- tool_name: toolName || b.tool_name,
616
- display_name: displayName || b.display_name
617
- };
618
- blockFound = true;
619
- break;
620
- }
621
- if (!blockFound) {
622
- state.blocks.push({
623
- type: "tool_use",
624
- content: args,
625
- tool_call_id: toolCallId,
626
- tool_name: toolName || null,
627
- display_name: displayName || null
549
+ if (!blockFound) {
550
+ state.blocks.push({
551
+ type: "tool_use",
552
+ content: args,
553
+ tool_call_id: toolCallId,
554
+ tool_name: toolName || null,
555
+ display_name: displayName || null
628
556
  });
629
557
  }
630
558
  }
@@ -1115,6 +1043,10 @@ function normalizeChildPause(pausePayload) {
1115
1043
  }
1116
1044
  return { pauseToolData, childLoopName, childToolCallId, description };
1117
1045
  }
1046
+ function attachToolEvidence(call, evidence) {
1047
+ if (!call) return;
1048
+ call.evidence = call.status === "done" && Array.isArray(evidence) ? evidence : void 0;
1049
+ }
1118
1050
 
1119
1051
  // src/schemas/message.ts
1120
1052
  function inferToolStatus(resultStr) {
@@ -1316,7 +1248,7 @@ function acknowledgeTurnEvents(acknowledge) {
1316
1248
  }
1317
1249
  function hasChatRunEvent(events) {
1318
1250
  return events.some(
1319
- (event) => event.type !== "llm:first_token:delivered" && event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
1251
+ (event) => event.type !== "llm:first_token:delivered" && event.type !== "session:post_chat_followup" && event.type !== "loop:tool_bridge" && !event.type.startsWith("compaction:")
1320
1252
  );
1321
1253
  }
1322
1254
  function acceptedPostChatFollowupCompletesLatestRun(events, updates) {
@@ -1556,6 +1488,19 @@ var ClientProjectionBuilder = class {
1556
1488
  this.seq = 0;
1557
1489
  this.syntheticCounter = 0;
1558
1490
  }
1491
+ /** A durable snapshot supersedes only this turn's temporary overlay. */
1492
+ acceptDurableTurn(turn) {
1493
+ this.seedSequence(turn.sequence);
1494
+ if (!isConversationTurn(turn)) return;
1495
+ const active = this.activeTurns.get(turn.loop_id);
1496
+ if (active?.turnId === turn.turn_id) this.activeTurns.delete(turn.loop_id);
1497
+ this.turnsById.set(turn.turn_id, turn);
1498
+ const latest = this.latestTurns.get(turn.loop_id);
1499
+ if ((!active || active.turnId === turn.turn_id) && (!latest || latest.turn_id === turn.turn_id || turn.sequence >= latest.sequence)) {
1500
+ this.latestTurns.set(turn.loop_id, turn);
1501
+ this.lastTurnIds.set(turn.loop_id, turn.turn_id);
1502
+ }
1503
+ }
1559
1504
  seedSequence(maxSeq) {
1560
1505
  if (maxSeq > this.seq) {
1561
1506
  this.seq = maxSeq;
@@ -1802,6 +1747,7 @@ var ClientProjectionBuilder = class {
1802
1747
  onUserMessage(_loopId, payload) {
1803
1748
  const entryId = String(payload.entry_id ?? this.nextTurnId("user"));
1804
1749
  const displayContent = payload.display_content ?? payload.content ?? "";
1750
+ const refs = normalizeMessageRefs(payload.refs);
1805
1751
  const turn = {
1806
1752
  id: entryId,
1807
1753
  sequence: this.nextSeq(),
@@ -1811,6 +1757,9 @@ var ClientProjectionBuilder = class {
1811
1757
  role: "user",
1812
1758
  status: "completed",
1813
1759
  blocks: [{ type: "text", content: displayContent }],
1760
+ ...refs.length ? { refs } : {},
1761
+ context_attachments: normalizeContextAttachments(payload.context_attachments),
1762
+ topic_asset_refs: normalizeTopicAssetRefs(payload.topic_asset_refs),
1814
1763
  tool_calls: [],
1815
1764
  model: null,
1816
1765
  usage: null,
@@ -1945,6 +1894,7 @@ var ClientProjectionBuilder = class {
1945
1894
  const sl = sourceLoopFor(state.loopId, this.loopDescriptions);
1946
1895
  const startedAt = typeof payload.started_at === "string" && payload.started_at ? payload.started_at : null;
1947
1896
  applyToolResult(state, toolCallId, content, status, durationMs, ranInParallel, sl, startedAt);
1897
+ attachToolEvidence(state.toolCalls.find((call) => call.id === toolCallId), payload.evidence);
1948
1898
  const pendingChild = [...this.pendingChildPauses.values()].find(
1949
1899
  (pause) => pause.parentLoopName === loopId && pause.parentToolCallId === toolCallId
1950
1900
  );
@@ -2466,6 +2416,7 @@ function projectHistory(entries) {
2466
2416
  if (owner && toolCall) {
2467
2417
  toolCall.status = message._error ? "error" : inferToolStatus(String(message.content ?? ""));
2468
2418
  toolCall.result = message.content ?? "";
2419
+ attachToolEvidence(toolCall, message._evidence);
2469
2420
  const duration = numberOrNull(message._duration_ms);
2470
2421
  if (duration !== null) toolCall.duration_ms = duration;
2471
2422
  if (typeof message._started_at === "string") {
@@ -2493,6 +2444,7 @@ function projectHistory(entries) {
2493
2444
  loop_id: loopId,
2494
2445
  kind: "message",
2495
2446
  role,
2447
+ client_request_id: stringOrNull(entry.client_request_id),
2496
2448
  status: message._error ? "failed" : message._interrupted ? "interrupted" : "completed",
2497
2449
  blocks,
2498
2450
  tool_calls: toolCalls,
@@ -2509,6 +2461,11 @@ function projectHistory(entries) {
2509
2461
  // root Agent 工具调用兜底配对,工作流 fork 没有,子智能体卡会整体消失)。
2510
2462
  parent_fork_tool_call_id: stringOrNull(message._parent_fork_tool_call_id)
2511
2463
  };
2464
+ if (role === "user") {
2465
+ const refs = normalizeMessageRefs(message.refs);
2466
+ if (refs.length) turn.refs = refs;
2467
+ turn.context_attachments = normalizeContextAttachments(message.context_attachments);
2468
+ }
2512
2469
  const isNativeToolEntry = role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.some(
2513
2470
  (toolCall) => isRecord(toolCall) && typeof toolCall.provider === "string"
2514
2471
  );
@@ -2566,6 +2523,33 @@ function projectHistory(entries) {
2566
2523
  }
2567
2524
  continue;
2568
2525
  }
2526
+ if (kind === "workflow_step") {
2527
+ const data = entry.data ?? {};
2528
+ const toolCallId = String(data.tool_call_id ?? "");
2529
+ const owner = toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
2530
+ if (owner && toolCallId) {
2531
+ const index = owner.tool_calls.findIndex((call) => call.id === toolCallId);
2532
+ if (index >= 0) {
2533
+ const call = owner.tool_calls[index];
2534
+ owner.tool_calls[index] = {
2535
+ ...call,
2536
+ workflow_steps: [
2537
+ ...call.workflow_steps ?? [],
2538
+ {
2539
+ step_id: String(data.step_id ?? ""),
2540
+ desc: String(data.desc ?? ""),
2541
+ step_kind: String(data.step_kind ?? "step"),
2542
+ status: String(data.status ?? "done"),
2543
+ output: String(data.output ?? ""),
2544
+ ...typeof data.seq === "number" ? { seq: data.seq } : {},
2545
+ ...typeof data.log_seq === "number" ? { log_seq: data.log_seq } : {}
2546
+ }
2547
+ ]
2548
+ };
2549
+ }
2550
+ }
2551
+ continue;
2552
+ }
2569
2553
  if (kind === "mode_change") {
2570
2554
  turns.push(markerTurn(id, ++sequence, loopId, "mode_change", entry.data ?? {}));
2571
2555
  } else if (kind === "workspace_change") {
@@ -2728,117 +2712,654 @@ function projectHistory(entries) {
2728
2712
  pauseArguments.questions = resultArguments.questions;
2729
2713
  }
2730
2714
  }
2731
- applyAskUserPauseToTurn(
2732
- rootAssistant,
2715
+ applyAskUserPauseToTurn(
2716
+ rootAssistant,
2717
+ {
2718
+ name: toolCall.tool_name,
2719
+ tool_call_id: toolCall.id,
2720
+ arguments: pauseArguments
2721
+ },
2722
+ toolCall.id,
2723
+ "",
2724
+ "",
2725
+ (name) => name
2726
+ );
2727
+ }
2728
+ }
2729
+ return turns;
2730
+ }
2731
+ function parseToolArguments(value) {
2732
+ if (!value) return {};
2733
+ try {
2734
+ const parsed = JSON.parse(value);
2735
+ return isRecord(parsed) ? parsed : {};
2736
+ } catch {
2737
+ return {};
2738
+ }
2739
+ }
2740
+ function buildToolCalls(value) {
2741
+ if (!Array.isArray(value)) return [];
2742
+ return value.filter(isRecord).map((raw) => {
2743
+ const fn = isRecord(raw.function) ? raw.function : {};
2744
+ const name = String(fn.name ?? "");
2745
+ return {
2746
+ id: String(raw.id ?? ""),
2747
+ tool_name: name,
2748
+ display_name: String(raw.display_name ?? name),
2749
+ arguments: typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments ?? {}),
2750
+ status: "running",
2751
+ result: null
2752
+ };
2753
+ });
2754
+ }
2755
+ function buildBlocks(message, toolCalls) {
2756
+ const displayContent = message.role === "user" ? message._display_content : void 0;
2757
+ const stored = message._blocks;
2758
+ const blocks = [];
2759
+ const storedToolCallIds = /* @__PURE__ */ new Set();
2760
+ if (displayContent !== void 0) {
2761
+ blocks.push({ type: "text", content: displayContent });
2762
+ } else if (Array.isArray(stored)) {
2763
+ for (const raw of stored) {
2764
+ if (!isRecord(raw)) continue;
2765
+ const type2 = String(raw.type ?? "");
2766
+ if (type2 === "thinking") blocks.push({ type: type2, content: raw.thinking ?? raw.content ?? "" });
2767
+ if (type2 === "text") blocks.push({ type: type2, content: raw.text ?? raw.content ?? "" });
2768
+ if (type2 === "tool_call") {
2769
+ const toolCallId = String(raw.id ?? "");
2770
+ const toolCall = toolCalls.find((candidate) => candidate.id === toolCallId);
2771
+ if (!toolCall) continue;
2772
+ storedToolCallIds.add(toolCallId);
2773
+ blocks.push({
2774
+ type: "tool_use",
2775
+ content: toolCall.arguments,
2776
+ tool_call_id: toolCall.id,
2777
+ tool_name: toolCall.tool_name,
2778
+ display_name: toolCall.display_name
2779
+ });
2780
+ }
2781
+ }
2782
+ } else if (message.content !== void 0 && message.content !== "") {
2783
+ blocks.push({ type: "text", content: message.content });
2784
+ }
2785
+ for (const toolCall of toolCalls) {
2786
+ if (storedToolCallIds.has(toolCall.id)) continue;
2787
+ blocks.push({
2788
+ type: "tool_use",
2789
+ content: toolCall.arguments,
2790
+ tool_call_id: toolCall.id,
2791
+ tool_name: toolCall.tool_name,
2792
+ display_name: toolCall.display_name
2793
+ });
2794
+ }
2795
+ return blocks;
2796
+ }
2797
+ function markerTurn(id, sequence, loopId, type2, content, toolCallId = null) {
2798
+ return {
2799
+ id,
2800
+ sequence,
2801
+ turn_id: id,
2802
+ loop_id: loopId,
2803
+ kind: type2,
2804
+ role: "system",
2805
+ status: "completed",
2806
+ blocks: [{ type: type2, content, tool_call_id: toolCallId }],
2807
+ tool_calls: [],
2808
+ model: null,
2809
+ usage: null,
2810
+ duration_ms: 0
2811
+ };
2812
+ }
2813
+ function stringOrNull(value) {
2814
+ return typeof value === "string" ? value : null;
2815
+ }
2816
+ function numberOrNull(value) {
2817
+ return typeof value === "number" && value >= 0 ? value : null;
2818
+ }
2819
+ function findLatestAssistant(turns, loopId) {
2820
+ for (let index = turns.length - 1; index >= 0; index -= 1) {
2821
+ const turn = turns[index];
2822
+ if (turn?.role === "assistant" && turn.loop_id === loopId) return turn;
2823
+ }
2824
+ return void 0;
2825
+ }
2826
+
2827
+ // src/shared/projection/workflow.ts
2828
+ var STEP_KINDS = /* @__PURE__ */ new Set(["agent", "step", "parallel", "choice", "end"]);
2829
+ function workflowEntriesFromSteps(steps) {
2830
+ const items = [];
2831
+ const ordered = steps.every((step) => typeof step.seq === "number");
2832
+ steps.forEach((step, index) => {
2833
+ const base = ordered ? step.seq : index * 2;
2834
+ const stepKind = step.step_kind && STEP_KINDS.has(step.step_kind) ? step.step_kind : "step";
2835
+ items.push({
2836
+ key: base,
2837
+ entry: { kind: "step", stepId: step.step_id, desc: step.desc ?? "", stepKind, status: step.status }
2838
+ });
2839
+ const logKey = ordered && typeof step.log_seq === "number" ? step.log_seq : base + 0.5;
2840
+ const output = step.output ?? "";
2841
+ if (step.status === "done" && stepKind === "step") {
2842
+ const shown = output.length <= 300 ? output : `${output.slice(0, 300)}\u2026(\u5171${output.length}\u5B57)`;
2843
+ items.push({ key: logKey, entry: { kind: "log", text: ` \u2514 \u8F93\u51FA: ${shown || "(\u7A7A)"}`, stepId: step.step_id } });
2844
+ }
2845
+ if (step.status === "error" && output) {
2846
+ items.push({ key: logKey, entry: { kind: "log", text: `\u5931\u8D25: ${output}`, stepId: step.step_id } });
2847
+ }
2848
+ });
2849
+ return items.sort((a, b) => a.key - b.key).map((item) => item.entry);
2850
+ }
2851
+ function workflowRunCancelled(steps) {
2852
+ return steps.some((step) => step.status === "cancelled") && !steps.some((step) => step.status === "error");
2853
+ }
2854
+
2855
+ // src/resources/headless.ts
2856
+ var HeadlessError = class extends Error {
2857
+ constructor(message, detail) {
2858
+ super(message);
2859
+ this.detail = detail;
2860
+ this.name = "HeadlessError";
2861
+ }
2862
+ detail;
2863
+ };
2864
+ var SYSTEM_ERROR_CHAT_END_GRACE_MS = 1e3;
2865
+ var CLEANUP_ACK_TIMEOUT_MS = 35e3;
2866
+ var HeadlessResource = class {
2867
+ constructor(client) {
2868
+ this.client = client;
2869
+ }
2870
+ client;
2871
+ subscriptions = /* @__PURE__ */ new Map();
2872
+ async run(prompt, options = {}) {
2873
+ const { session_id } = await this.client.sessions.createSessionWithRequest({
2874
+ intent: prompt,
2875
+ model: options.model ?? null
2876
+ });
2877
+ return this.runTracked(session_id, prompt, options, false);
2878
+ }
2879
+ async runInSession(sessionId, prompt, options = {}) {
2880
+ return this.runTracked(sessionId, prompt, options, false);
2881
+ }
2882
+ async runWithSession(prompt, options = {}) {
2883
+ const { session_id } = await this.client.sessions.createSessionWithRequest({
2884
+ intent: prompt,
2885
+ model: options.model ?? null
2886
+ });
2887
+ return this.runTracked(session_id, prompt, options, true);
2888
+ }
2889
+ async runTracked(sessionId, prompt, options, returnEnvelope) {
2890
+ if ((options.refs?.length ?? 0) > 20) throw new HeadlessError("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u9009\u62E9 20 \u4E2A\u5F15\u7528");
2891
+ if ((options.topicAssetRefs?.length ?? 0) > 20) throw new HeadlessError("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u5F15\u7528 20 \u4E2A\u4E3B\u9898\u8D44\u4EA7");
2892
+ const timeoutMs = options.timeoutMs ?? 3e5;
2893
+ const session_id = sessionId;
2894
+ const socket = this.client.socket();
2895
+ await ensureSocketConnected(socket, timeoutMs, { session_id });
2896
+ const cursor = new LogCursor();
2897
+ const subscriptions = this.subscriptions.get(session_id) ?? /* @__PURE__ */ new Set();
2898
+ subscriptions.add(cursor);
2899
+ this.subscriptions.set(session_id, subscriptions);
2900
+ return new Promise((resolve, reject) => {
2901
+ let settled = false;
2902
+ let cleanupPromise;
2903
+ let stopAttempt;
2904
+ let handleLateAck;
2905
+ let acknowledgeSend = () => void 0;
2906
+ const sendAck = new Promise((resolveAck) => {
2907
+ acknowledgeSend = resolveAck;
2908
+ });
2909
+ let started = false;
2910
+ let chatRunId = null;
2911
+ let systemErrorTimeout = null;
2912
+ const beforeAck = /* @__PURE__ */ new Map();
2913
+ const timeout = setTimeout(() => {
2914
+ fail(new HeadlessError(`headless run timed out after ${timeoutMs}ms`, { session_id, chat_run_id: chatRunId }));
2915
+ }, timeoutMs);
2916
+ const stopOwnedRun = (runId, deadline) => {
2917
+ stopAttempt ??= (async () => {
2918
+ const stopped = await waitForCleanupAck(socket, deadline, (ack2) => {
2919
+ socket.emit("chat:stop", { session_id, chat_run_id: runId }, ack2);
2920
+ });
2921
+ if (stopped.status !== "received") return { ...stopped, phase: "stop_ack", chat_run_id: runId };
2922
+ const ack = stopped.ack;
2923
+ const confirmed = isRecord(ack) && ack.ok !== false && (!Object.hasOwn(ack, "chat_run_id") || ack.chat_run_id === runId) && typeof ack.status === "string" && ["stopping", "stopped", "idle", "done", "interrupted"].includes(ack.status);
2924
+ return {
2925
+ status: confirmed ? "confirmed" : "unconfirmed",
2926
+ phase: "stop_ack",
2927
+ chat_run_id: runId,
2928
+ ...confirmed ? {} : { reason: "invalid_stop_ack" },
2929
+ ack
2930
+ };
2931
+ })().finally(releaseSubscription).catch((error) => ({
2932
+ status: "unconfirmed",
2933
+ phase: "stop_ack",
2934
+ chat_run_id: runId,
2935
+ reason: "cleanup_failed",
2936
+ error
2937
+ }));
2938
+ return stopAttempt;
2939
+ };
2940
+ const cancelRun = async (deadline) => {
2941
+ if (chatRunId === null) {
2942
+ const sent = await waitForCleanupAck(socket, deadline, (ack) => {
2943
+ void sendAck.then(ack);
2944
+ });
2945
+ if (sent.status !== "received") return { ...sent, phase: "send_ack", chat_run_id: null };
2946
+ const runId = acceptedRunId(sent.ack);
2947
+ if (runId === null) {
2948
+ return { status: "unconfirmed", phase: "send_ack", chat_run_id: null, reason: "no_accepted_run", ack: sent.ack };
2949
+ }
2950
+ chatRunId = runId;
2951
+ }
2952
+ const attempt = stopOwnedRun(chatRunId, deadline);
2953
+ const stopped = await waitForCleanupAck(socket, deadline, (ack) => {
2954
+ void attempt.then(ack);
2955
+ });
2956
+ return stopped.status === "received" ? stopped.ack : { ...stopped, phase: "stop_ack", chat_run_id: chatRunId };
2957
+ };
2958
+ const releaseSubscription = () => {
2959
+ const current = this.subscriptions.get(session_id);
2960
+ current?.delete(cursor);
2961
+ if (current?.size === 0) {
2962
+ this.subscriptions.delete(session_id);
2963
+ socket.emit("session:unsubscribe", { session_id });
2964
+ }
2965
+ };
2966
+ const reportDelivery = (error, delivery) => {
2967
+ let detailUpdated = true;
2968
+ try {
2969
+ if (error instanceof HeadlessError && isRecord(error.detail)) error.detail.stop_delivery = delivery;
2970
+ } catch {
2971
+ detailUpdated = false;
2972
+ }
2973
+ if (delivery.status !== "confirmed" || !detailUpdated) {
2974
+ console.warn(delivery.status !== "confirmed" ? "[agent-client] headless stop delivery unconfirmed; the run may still be active" : "[agent-client] late headless stop delivery confirmed; error detail is read-only", { session_id, ...delivery });
2975
+ }
2976
+ };
2977
+ const cleanup = (error) => {
2978
+ cleanupPromise ??= (async () => {
2979
+ clearTimeout(timeout);
2980
+ if (systemErrorTimeout) clearTimeout(systemErrorTimeout);
2981
+ systemErrorTimeout = null;
2982
+ socket.off("chat:start", onChatStart);
2983
+ socket.off("chat:end", onChatEnd);
2984
+ socket.off("system:error", onSystemError);
2985
+ socket.off("turn:events", onTurnEvents);
2986
+ socket.off("connect", subscribe);
2987
+ beforeAck.clear();
2988
+ if (error !== void 0) {
2989
+ const deadline = Date.now() + CLEANUP_ACK_TIMEOUT_MS;
2990
+ handleLateAck = (ack) => {
2991
+ const runId = acceptedRunId(ack);
2992
+ if (runId === null || !socket.connected || stopAttempt) return;
2993
+ const late = Date.now() >= deadline;
2994
+ const current = this.subscriptions.get(session_id) ?? /* @__PURE__ */ new Set();
2995
+ current.add(cursor);
2996
+ this.subscriptions.set(session_id, current);
2997
+ void stopOwnedRun(runId, late ? Date.now() + CLEANUP_ACK_TIMEOUT_MS : deadline).then((delivery) => {
2998
+ if (late) reportDelivery(error, { ...delivery, late: true });
2999
+ }).catch((error2) => {
3000
+ console.warn("[agent-client] late headless stop cleanup failed", { session_id, chat_run_id: runId, error: error2 });
3001
+ });
3002
+ };
3003
+ reportDelivery(error, await cancelRun(deadline));
3004
+ }
3005
+ if (!stopAttempt) releaseSubscription();
3006
+ })();
3007
+ return cleanupPromise;
3008
+ };
3009
+ const finish = (value) => {
3010
+ if (settled) return;
3011
+ settled = true;
3012
+ void cleanup().then(() => resolve(returnEnvelope ? { session_id, chat_run_id: chatRunId, result: value } : value), reject);
3013
+ };
3014
+ const rejectRun = (error, sendStop = false) => {
3015
+ if (settled) return;
3016
+ settled = true;
3017
+ void cleanup(sendStop ? error : void 0).then(() => reject(error), () => reject(error));
3018
+ };
3019
+ const fail = (error) => rejectRun(error, true);
3020
+ const accept = (event) => {
3021
+ if (settled) return;
3022
+ const payload = event.payload;
3023
+ if (event.type === "chat:start") {
3024
+ started = true;
3025
+ return;
3026
+ }
3027
+ if (event.type === "system:error") {
3028
+ const error = new HeadlessError(String(payload.message || "headless run failed"), {
3029
+ session_id,
3030
+ chat_run_id: chatRunId,
3031
+ event: payload
3032
+ });
3033
+ if (!started) {
3034
+ rejectRun(error);
3035
+ } else {
3036
+ if (systemErrorTimeout) clearTimeout(systemErrorTimeout);
3037
+ systemErrorTimeout = setTimeout(() => fail(error), SYSTEM_ERROR_CHAT_END_GRACE_MS);
3038
+ }
3039
+ return;
3040
+ }
3041
+ if (event.type !== "chat:end") return;
3042
+ if (payload.status !== "completed") {
3043
+ rejectRun(new HeadlessError(errorMessageFromResult(payload.result) ?? `headless run ${String(payload.status || "failed")}`, {
3044
+ session_id,
3045
+ chat_run_id: chatRunId,
3046
+ event: payload
3047
+ }));
3048
+ return;
3049
+ }
3050
+ try {
3051
+ finish(normalizeResult(payload.result, Boolean(options.schema)));
3052
+ } catch (error) {
3053
+ rejectRun(error);
3054
+ }
3055
+ };
3056
+ const observe = (type2, payload) => {
3057
+ if (settled || payload.session_id && payload.session_id !== session_id) return;
3058
+ const runId = payload.chat_run_id;
3059
+ const event = { type: type2, payload };
3060
+ if (typeof runId !== "string" || !runId) {
3061
+ if (!runId && chatRunId === null && type2 === "system:error" && payload.session_id === session_id && !beforeAck.has("")) {
3062
+ beforeAck.set("", [event]);
3063
+ }
3064
+ return;
3065
+ }
3066
+ if (chatRunId === null) {
3067
+ const pending = beforeAck.get(runId) ?? [];
3068
+ pending.push(event);
3069
+ beforeAck.set(runId, pending);
3070
+ } else if (runId === chatRunId) {
3071
+ accept(event);
3072
+ }
3073
+ };
3074
+ function onChatStart(payload) {
3075
+ observe("chat:start", payload);
3076
+ }
3077
+ function onChatEnd(payload) {
3078
+ observe("chat:end", payload);
3079
+ }
3080
+ function onSystemError(payload) {
3081
+ observe("system:error", payload);
3082
+ }
3083
+ function onTurnEvents(data, acknowledge) {
3084
+ acknowledgeTurnEvents(acknowledge);
3085
+ if (data.session_id !== session_id) return;
3086
+ for (const event of cursor.consume(data.events.map((event2) => ({
3087
+ ...event2,
3088
+ log_seq: event2.log_seq ?? void 0,
3089
+ step_log_seq: event2.step_log_seq ?? void 0,
3090
+ delta_seq: event2.delta_seq ?? void 0
3091
+ }))).events) {
3092
+ if (["chat:start", "chat:end", "system:error"].includes(event.type)) observe(event.type, event.payload);
3093
+ }
3094
+ }
3095
+ function subscribe() {
3096
+ if (settled) return;
3097
+ const after = Math.min(...[...subscriptions].map((item) => item.subscription().after_log_seq ?? 0));
3098
+ socket.emit("session:subscribe", {
3099
+ session_id,
3100
+ event_format: "raw",
3101
+ ...after > 0 ? { after_log_seq: after } : {}
3102
+ });
3103
+ }
3104
+ socket.on("chat:start", onChatStart);
3105
+ socket.on("chat:end", onChatEnd);
3106
+ socket.on("system:error", onSystemError);
3107
+ socket.on("turn:events", onTurnEvents);
3108
+ socket.on("connect", subscribe);
3109
+ subscribe();
3110
+ socket.emit(
3111
+ "chat:send",
2733
3112
  {
2734
- name: toolCall.tool_name,
2735
- tool_call_id: toolCall.id,
2736
- arguments: pauseArguments
3113
+ session_id,
3114
+ message: prompt,
3115
+ headless: true,
3116
+ output_schema: options.schema ?? null,
3117
+ model: options.model ?? null,
3118
+ refs: options.refs,
3119
+ topic_asset_refs: options.topicAssetRefs
2737
3120
  },
2738
- toolCall.id,
2739
- "",
2740
- "",
2741
- (name) => name
3121
+ (response) => {
3122
+ acknowledgeSend(response);
3123
+ if (settled) {
3124
+ handleLateAck?.(response);
3125
+ return;
3126
+ }
3127
+ if (response?.status !== "accepted") {
3128
+ const validationError = beforeAck.get("")?.[0];
3129
+ if ((response == null || isRecord(response) && Object.keys(response).length === 0) && validationError) {
3130
+ accept(validationError);
3131
+ return;
3132
+ }
3133
+ rejectRun(new HeadlessError(response?.message ?? "headless run was not accepted", response));
3134
+ return;
3135
+ }
3136
+ if (typeof response.chat_run_id !== "string" || !response.chat_run_id) {
3137
+ rejectRun(new HeadlessError("headless run acknowledgement has no run ID", response));
3138
+ return;
3139
+ }
3140
+ chatRunId = response.chat_run_id;
3141
+ const pending = beforeAck.get(chatRunId) ?? [];
3142
+ beforeAck.clear();
3143
+ for (const event of pending) accept(event);
3144
+ }
2742
3145
  );
2743
- }
3146
+ });
2744
3147
  }
2745
- return turns;
3148
+ };
3149
+ function normalizeResult(result, hasSchema) {
3150
+ if (!isRecord(result)) {
3151
+ throw new HeadlessError("headless run finished without a result", result);
3152
+ }
3153
+ const errMsg = typeof result.err_msg === "string" ? result.err_msg : "";
3154
+ if (errMsg) {
3155
+ throw new HeadlessError(errMsg, result);
3156
+ }
3157
+ if (!hasSchema) {
3158
+ const text = result.result;
3159
+ return typeof text === "string" ? text : String(text ?? "");
3160
+ }
3161
+ const { err_msg: _errMsg, ...businessResult } = result;
3162
+ return businessResult;
2746
3163
  }
2747
- function parseToolArguments(value) {
2748
- if (!value) return {};
2749
- try {
2750
- const parsed = JSON.parse(value);
2751
- return isRecord(parsed) ? parsed : {};
2752
- } catch {
2753
- return {};
3164
+ function errorMessageFromResult(result) {
3165
+ if (isRecord(result) && typeof result.err_msg === "string" && result.err_msg) {
3166
+ return result.err_msg;
2754
3167
  }
3168
+ return null;
2755
3169
  }
2756
- function buildToolCalls(value) {
2757
- if (!Array.isArray(value)) return [];
2758
- return value.filter(isRecord).map((raw) => {
2759
- const fn = isRecord(raw.function) ? raw.function : {};
2760
- const name = String(fn.name ?? "");
2761
- return {
2762
- id: String(raw.id ?? ""),
2763
- tool_name: name,
2764
- display_name: String(raw.display_name ?? name),
2765
- arguments: typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments ?? {}),
2766
- status: "running",
2767
- result: null
3170
+ function ensureSocketConnected(socket, timeoutMs, detail) {
3171
+ if (socket.connected) {
3172
+ return Promise.resolve();
3173
+ }
3174
+ return new Promise((resolve, reject) => {
3175
+ let settled = false;
3176
+ const timeout = setTimeout(() => {
3177
+ finish(() => reject(new HeadlessError(`socket connection timed out after ${timeoutMs}ms`, detail)));
3178
+ }, timeoutMs);
3179
+ const onConnect = () => finish(resolve);
3180
+ const onConnectError = (error) => {
3181
+ finish(() => reject(new HeadlessError("socket connection failed", { ...detail, event: error })));
3182
+ };
3183
+ const finish = (callback) => {
3184
+ if (settled) return;
3185
+ settled = true;
3186
+ clearTimeout(timeout);
3187
+ socket.off("connect", onConnect);
3188
+ socket.off("connect_error", onConnectError);
3189
+ callback();
2768
3190
  };
3191
+ socket.on("connect", onConnect);
3192
+ socket.on("connect_error", onConnectError);
3193
+ socket.connect();
2769
3194
  });
2770
3195
  }
2771
- function buildBlocks(message, toolCalls) {
2772
- const displayContent = message.role === "user" ? message._display_content : void 0;
2773
- const stored = message._blocks;
2774
- const blocks = [];
2775
- const storedToolCallIds = /* @__PURE__ */ new Set();
2776
- if (displayContent !== void 0) {
2777
- blocks.push({ type: "text", content: displayContent });
2778
- } else if (Array.isArray(stored)) {
2779
- for (const raw of stored) {
2780
- if (!isRecord(raw)) continue;
2781
- const type2 = String(raw.type ?? "");
2782
- if (type2 === "thinking") blocks.push({ type: type2, content: raw.thinking ?? raw.content ?? "" });
2783
- if (type2 === "text") blocks.push({ type: type2, content: raw.text ?? raw.content ?? "" });
2784
- if (type2 === "tool_call") {
2785
- const toolCallId = String(raw.id ?? "");
2786
- const toolCall = toolCalls.find((candidate) => candidate.id === toolCallId);
2787
- if (!toolCall) continue;
2788
- storedToolCallIds.add(toolCallId);
2789
- blocks.push({
2790
- type: "tool_use",
2791
- content: toolCall.arguments,
2792
- tool_call_id: toolCall.id,
2793
- tool_name: toolCall.tool_name,
2794
- display_name: toolCall.display_name
2795
- });
2796
- }
3196
+ function acceptedRunId(ack) {
3197
+ return isRecord(ack) && ack.status === "accepted" && typeof ack.chat_run_id === "string" && ack.chat_run_id ? ack.chat_run_id : null;
3198
+ }
3199
+ function waitForCleanupAck(socket, deadline, request) {
3200
+ if (!socket.connected) return Promise.resolve({ status: "unconfirmed", reason: "disconnected" });
3201
+ if (Date.now() >= deadline) return Promise.resolve({ status: "unconfirmed", reason: "ack_timeout" });
3202
+ return new Promise((resolve) => {
3203
+ const timer = setTimeout(() => finish({ status: "unconfirmed", reason: "ack_timeout" }), Math.max(0, deadline - Date.now()));
3204
+ let finished = false;
3205
+ const finish = (outcome) => {
3206
+ if (finished) return;
3207
+ finished = true;
3208
+ clearTimeout(timer);
3209
+ socket.off("disconnect", onDisconnect);
3210
+ resolve(outcome);
3211
+ };
3212
+ const onDisconnect = () => finish({ status: "unconfirmed", reason: "disconnected" });
3213
+ socket.on("disconnect", onDisconnect);
3214
+ try {
3215
+ request((ack) => finish({ status: "received", ack }));
3216
+ } catch (error) {
3217
+ finish({ status: "unconfirmed", reason: "emit_failed", error });
2797
3218
  }
2798
- } else if (message.content !== void 0 && message.content !== "") {
2799
- blocks.push({ type: "text", content: message.content });
2800
- }
2801
- for (const toolCall of toolCalls) {
2802
- if (storedToolCallIds.has(toolCall.id)) continue;
2803
- blocks.push({
2804
- type: "tool_use",
2805
- content: toolCall.arguments,
2806
- tool_call_id: toolCall.id,
2807
- tool_name: toolCall.tool_name,
2808
- display_name: toolCall.display_name
2809
- });
2810
- }
2811
- return blocks;
3219
+ });
2812
3220
  }
2813
- function markerTurn(id, sequence, loopId, type2, content, toolCallId = null) {
2814
- return {
2815
- id,
2816
- sequence,
2817
- turn_id: id,
2818
- loop_id: loopId,
2819
- kind: type2,
2820
- role: "system",
2821
- status: "completed",
2822
- blocks: [{ type: type2, content, tool_call_id: toolCallId }],
2823
- tool_calls: [],
2824
- model: null,
2825
- usage: null,
2826
- duration_ms: 0
2827
- };
3221
+
3222
+ // src/resources/computers.ts
3223
+ function computerState(computer) {
3224
+ if (computer.is_primary) return "primary";
3225
+ if (computer.enabled) return "enabled";
3226
+ return computer.online ? "idle" : "offline";
2828
3227
  }
2829
- function stringOrNull(value) {
2830
- return typeof value === "string" ? value : null;
3228
+ function computerOS(computer) {
3229
+ const os = (computer.os ?? "").toLowerCase();
3230
+ if (os.startsWith("darwin") || os.startsWith("mac")) return "macos";
3231
+ if (os.startsWith("windows")) return "windows";
3232
+ if (os.startsWith("linux")) return "linux";
3233
+ return "unknown";
2831
3234
  }
2832
- function numberOrNull(value) {
2833
- return typeof value === "number" && value >= 0 ? value : null;
3235
+ function computerPlatformLabel(computer) {
3236
+ const os = (computer.os ?? "").trim();
3237
+ const arch = (computer.arch ?? "").trim();
3238
+ if (os && arch) return `${os}/${arch}`;
3239
+ return os || arch || "";
2834
3240
  }
2835
- function findLatestAssistant(turns, loopId) {
2836
- for (let index = turns.length - 1; index >= 0; index -= 1) {
2837
- const turn = turns[index];
2838
- if (turn?.role === "assistant" && turn.loop_id === loopId) return turn;
3241
+ function computerDaemonVersion(computer) {
3242
+ return (computer.daemon_version ?? "").trim();
3243
+ }
3244
+ function canToggleComputer(computer) {
3245
+ return !computer.is_primary;
3246
+ }
3247
+ function sortComputers(computers) {
3248
+ return [...computers].sort((a, b) => {
3249
+ const byCreated = (a.created_at ?? "").localeCompare(b.created_at ?? "");
3250
+ if (byCreated !== 0) return byCreated;
3251
+ return a.label.localeCompare(b.label);
3252
+ });
3253
+ }
3254
+ var ComputersResource = class {
3255
+ constructor(client) {
3256
+ this.client = client;
3257
+ }
3258
+ client;
3259
+ /**
3260
+ * 列出当前账号登记过的全部电脑及其在线状态。
3261
+ *
3262
+ * 首页还没有会话,也要能看到同一份清单——判定各写一份必然分叉,一边的兜底会把
3263
+ * 另一边的错误盖住(#1597)。所以账号事实只从这一个接口读。
3264
+ */
3265
+ async listAccount() {
3266
+ const data = await this.client.json("GET", "/api/daemon/instances");
3267
+ return {
3268
+ computers: (data.instances ?? []).map((instance) => ({
3269
+ ...instance,
3270
+ online: Boolean(instance.online)
3271
+ }))
3272
+ };
3273
+ }
3274
+ /**
3275
+ * 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
3276
+ * 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
3277
+ */
3278
+ list(sessionId) {
3279
+ return this.client.json(
3280
+ "GET",
3281
+ `/api/sessions/${encodeURIComponent(sessionId)}/computers`
3282
+ );
3283
+ }
3284
+ /**
3285
+ * 启动或停止一台电脑。只改这次会话的可用范围,不会碰远程机器,
3286
+ * 电脑离线时同样能登记,等它上线就直接可用。
3287
+ */
3288
+ setEnabled(sessionId, computer, enabled) {
3289
+ return this.client.json(
3290
+ "PUT",
3291
+ `/api/sessions/${encodeURIComponent(sessionId)}/computers/${encodeURIComponent(computer)}/enabled`,
3292
+ { enabled }
3293
+ );
3294
+ }
3295
+ };
3296
+
3297
+ // src/resources/models.ts
3298
+ var ModelsResource = class {
3299
+ constructor(client) {
3300
+ this.client = client;
3301
+ }
3302
+ client;
3303
+ list() {
3304
+ return this.client.json("GET", "/api/config/models");
2839
3305
  }
2840
- return void 0;
2841
- }
3306
+ };
3307
+
3308
+ // src/schemas/session.ts
3309
+ import { type } from "arktype";
3310
+ var SessionStatus = type(
3311
+ "'created' | 'running' | 'completed' | 'failed' | 'interrupted' | 'waiting_for_input'"
3312
+ );
3313
+ var DEFAULT_REPLAY_SPEED = 5;
3314
+ var SessionInfo = type({
3315
+ id: "string",
3316
+ intent: "string",
3317
+ status: SessionStatus,
3318
+ created_at: "string",
3319
+ updated_at: "string",
3320
+ "shared?": "boolean",
3321
+ "anonymous?": "boolean",
3322
+ "memory_enabled?": "boolean",
3323
+ "is_persistent?": "boolean",
3324
+ "initiator?": "'ui' | 'sdk' | 'unknown' | null",
3325
+ "is_headless?": "boolean",
3326
+ "viewer_role?": "'owner' | 'viewer'",
3327
+ "model?": "string | null",
3328
+ "enable_thinking?": "boolean | null",
3329
+ "solution_id?": "string | null",
3330
+ "solution_ref?": "unknown",
3331
+ "biz_role_id?": "string | null",
3332
+ "solution?": "unknown",
3333
+ "plan_summary?": "string | null",
3334
+ "replay_state?": "unknown",
3335
+ "is_pinned?": "boolean",
3336
+ "pinned_at?": "string | null",
3337
+ "is_example?": "boolean",
3338
+ "ports?": "unknown",
3339
+ "disable_tools?": "string[]",
3340
+ "runtime_type?": "string | null",
3341
+ "daemon_id?": "string | null",
3342
+ "agent_runtime_id?": "string | null",
3343
+ "workspace_path?": "string | null",
3344
+ "ba_version?": "string | null",
3345
+ "sandbox_version?": "string | null",
3346
+ "chat_group_id?": "string | null",
3347
+ "match?": "unknown"
3348
+ });
3349
+
3350
+ // src/session/definition.ts
3351
+ var SessionSetupError = class extends Error {
3352
+ constructor(message, stage, sessionId, options) {
3353
+ super(message);
3354
+ this.stage = stage;
3355
+ this.sessionId = sessionId;
3356
+ this.cause = options?.cause;
3357
+ }
3358
+ stage;
3359
+ sessionId;
3360
+ name = "SessionSetupError";
3361
+ cause;
3362
+ };
2842
3363
 
2843
3364
  // src/resources/sessions.ts
2844
3365
  function isEntryGoneError(error) {
@@ -2849,16 +3370,18 @@ function toCreateSessionPayload(request) {
2849
3370
  const solutionRef = request.solution_ref ?? externalSolutionRef(request.solution_id);
2850
3371
  return {
2851
3372
  intent: request.intent ?? "",
2852
- template_id: request.template_id,
3373
+ initiator: request.initiator ?? "sdk",
3374
+ anonymous: request.anonymous,
2853
3375
  solution_id: solutionRef ? void 0 : request.solution_id,
2854
3376
  solution_ref: solutionRef,
2855
3377
  biz_role_id: request.biz_role_id ?? null,
2856
3378
  model: request.model ?? null,
2857
3379
  enable_thinking: request.enable_thinking ?? null,
2858
3380
  memory_enabled: request.memory_enabled ?? null,
2859
- primary_skill_id: request.primary_skill_id ?? null,
2860
3381
  env: request.env,
2861
3382
  disable_tools: request.disable_tools ?? null,
3383
+ mcp_sources: request.mcp_sources,
3384
+ skip_confirmations: request.skip_confirmations,
2862
3385
  session_solution_asset_id: request.session_solution_asset_id ?? null,
2863
3386
  solution_snapshot_session_id: request.solution_snapshot_session_id ?? null,
2864
3387
  compaction_ratio: request.compaction_ratio ?? null,
@@ -2985,18 +3508,21 @@ var SessionsResource = class {
2985
3508
  *
2986
3509
  * Secrets are never returned: `write_only` lists the pointers that hold one.
2987
3510
  */
2988
- getSessionPluginConfig(sessionId, name, init) {
2989
- return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config`, init);
3511
+ getSessionPluginConfig(sessionId, name, init, scope = "personal") {
3512
+ return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config?scope=${scope}`, init);
2990
3513
  }
2991
3514
  /**
2992
- * Publish explicit set/remove operations to the user's keychain file.
3515
+ * Save explicit set/remove operations to encrypted server storage.
2993
3516
  *
2994
3517
  * Fields absent from both lists keep their stored value, so a form never has
2995
- * to round-trip a secret. Rejections leave the previous file untouched; the
2996
- * call never restarts MCP servers.
3518
+ * to round-trip a secret. Rejections preserve the previous values; the
3519
+ * shared settings restart only the affected hosted plugin.
2997
3520
  */
2998
- updateSessionPluginConfig(sessionId, name, update, init) {
2999
- return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config`, { method: "PUT", body: JSON.stringify(update), ...init });
3521
+ updateSessionPluginConfig(sessionId, name, update, init, scope = "personal") {
3522
+ return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config?scope=${scope}`, { method: "PUT", body: JSON.stringify(update), ...init });
3523
+ }
3524
+ restartSessionPluginHost(sessionId, name) {
3525
+ return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/host`, { method: "POST" });
3000
3526
  }
3001
3527
  /** 创建新会话并直接连接,返回可收发消息的 AgentSession。 */
3002
3528
  async create(request = {}) {
@@ -3004,7 +3530,7 @@ var SessionsResource = class {
3004
3530
  return this.createDeclared(request);
3005
3531
  }
3006
3532
  const { session_id } = await this.createSessionWithRequest(request);
3007
- return this.connect(session_id);
3533
+ return request.anonymous ? this.client.forAnonymousSession(session_id).sessions.connect(session_id) : this.connect(session_id);
3008
3534
  }
3009
3535
  async createDeclared(definition) {
3010
3536
  const { intent, solution, skills = [], config } = definition;
@@ -3091,7 +3617,6 @@ var SessionsResource = class {
3091
3617
  fetchSessionsPage({
3092
3618
  limit,
3093
3619
  offset,
3094
- template_id_prefix,
3095
3620
  solution_id,
3096
3621
  solution_registry_id,
3097
3622
  q,
@@ -3100,7 +3625,6 @@ var SessionsResource = class {
3100
3625
  const searchParams = new URLSearchParams();
3101
3626
  searchParams.set("limit", String(limit));
3102
3627
  searchParams.set("offset", String(offset));
3103
- if (template_id_prefix) searchParams.set("template_id_prefix", template_id_prefix);
3104
3628
  if (solution_registry_id) {
3105
3629
  searchParams.set("solution_registry_id", solution_registry_id);
3106
3630
  } else if (solution_id) {
@@ -3111,12 +3635,12 @@ var SessionsResource = class {
3111
3635
  if (chat_group_id) searchParams.set("chat_group_id", chat_group_id);
3112
3636
  return this.client.json("GET", `/api/sessions?${searchParams.toString()}`);
3113
3637
  }
3114
- async listSessions(template_id_prefix, solution_id) {
3638
+ async listSessions(solution_id) {
3115
3639
  const pageSize = 100;
3116
3640
  const sessions = [];
3117
3641
  let offset = 0;
3118
3642
  while (true) {
3119
- const page = await this.fetchSessionsPage({ limit: pageSize, offset, template_id_prefix, solution_id });
3643
+ const page = await this.fetchSessionsPage({ limit: pageSize, offset, solution_id });
3120
3644
  sessions.push(...page.items);
3121
3645
  if (sessions.length >= page.total || page.items.length === 0) return sessions;
3122
3646
  offset += page.items.length;
@@ -3133,8 +3657,8 @@ var SessionsResource = class {
3133
3657
  if (params.limit !== void 0) searchParams.set("limit", String(params.limit));
3134
3658
  return this.client.json("GET", `/api/search?${searchParams.toString()}`);
3135
3659
  }
3136
- createSession(intent, template_id, primary_skill_id) {
3137
- return this.createSessionWithRequest({ intent, template_id, primary_skill_id });
3660
+ createSession(intent) {
3661
+ return this.createSessionWithRequest({ intent });
3138
3662
  }
3139
3663
  createSessionWithRequest(request) {
3140
3664
  return this.client.json("POST", "/api/sessions", toCreateSessionPayload(request));
@@ -3152,6 +3676,13 @@ var SessionsResource = class {
3152
3676
  updateSession(sessionId, payload) {
3153
3677
  return this.client.json("PATCH", `/api/sessions/${sessionId}`, payload);
3154
3678
  }
3679
+ getSessionConfiguration(sessionId, init) {
3680
+ return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/configuration`, init);
3681
+ }
3682
+ /** Updates atomically between turns. A running/handoff session returns HTTP 409. */
3683
+ updateSessionConfiguration(sessionId, request) {
3684
+ return this.client.json("PATCH", `/api/sessions/${encodeURIComponent(sessionId)}/configuration`, request);
3685
+ }
3155
3686
  setSessionEnv(sessionId, env) {
3156
3687
  return this.client.json("PUT", `/api/sessions/${sessionId}/env`, { env });
3157
3688
  }
@@ -3289,13 +3820,25 @@ var SessionsResource = class {
3289
3820
  }
3290
3821
  async rewindSession(sessionId, checkpointId) {
3291
3822
  const result = await this.checkoutSession(sessionId, checkpointId, "before");
3292
- return { id: result.id, content: result.content };
3823
+ return {
3824
+ id: result.id,
3825
+ content: result.content,
3826
+ ...result.history_committed !== void 0 ? { history_committed: result.history_committed } : {},
3827
+ ...result.file_checkpoint_cleanup_failed !== void 0 ? { file_checkpoint_cleanup_failed: result.file_checkpoint_cleanup_failed } : {},
3828
+ ...result.warning !== void 0 ? { warning: result.warning } : {}
3829
+ };
3293
3830
  }
3294
3831
  switchBranch(sessionId, checkpointId) {
3295
3832
  return this.client.json("POST", `/api/sessions/${sessionId}/switch-branch`, {
3296
3833
  checkpoint_id: checkpointId
3297
3834
  });
3298
3835
  }
3836
+ listAnonymousSessions() {
3837
+ return this.client.json("GET", "/api/anonymous-sessions");
3838
+ }
3839
+ deleteAnonymousSession(sessionId) {
3840
+ return this.client.json("DELETE", `/api/anonymous-sessions/${encodeURIComponent(sessionId)}`);
3841
+ }
3299
3842
  deleteSession(sessionId, options) {
3300
3843
  const query = options?.deleteWorkspace ? "?delete_workspace=true" : "";
3301
3844
  return this.client.json("DELETE", `/api/sessions/${sessionId}${query}`);
@@ -3543,6 +4086,9 @@ var ChatGroupsResource = class {
3543
4086
  this.client = client;
3544
4087
  }
3545
4088
  client;
4089
+ path(groupId) {
4090
+ return `/api/chat-groups/${encodeURIComponent(groupId)}`;
4091
+ }
3546
4092
  list() {
3547
4093
  return this.client.json("GET", "/api/chat-groups");
3548
4094
  }
@@ -3550,11 +4096,124 @@ var ChatGroupsResource = class {
3550
4096
  return this.client.json("POST", "/api/chat-groups", payload);
3551
4097
  }
3552
4098
  update(groupId, payload) {
3553
- return this.client.json("PATCH", `/api/chat-groups/${encodeURIComponent(groupId)}`, payload);
4099
+ return this.client.json("PATCH", this.path(groupId), payload);
3554
4100
  }
3555
4101
  delete(groupId, options) {
3556
4102
  const query = options?.deleteWorkspace ? "?delete_workspace=true" : "";
3557
- return this.client.json("DELETE", `/api/chat-groups/${encodeURIComponent(groupId)}${query}`);
4103
+ return this.client.json("DELETE", `${this.path(groupId)}${query}`);
4104
+ }
4105
+ listMembers(groupId) {
4106
+ return this.client.json("GET", `${this.path(groupId)}/members`);
4107
+ }
4108
+ addMembers(groupId, userIds, role) {
4109
+ return this.client.json("POST", `${this.path(groupId)}/members`, { user_ids: userIds, role });
4110
+ }
4111
+ updateMember(groupId, userId, role) {
4112
+ return this.client.json("PATCH", `${this.path(groupId)}/members/${encodeURIComponent(userId)}`, { role });
4113
+ }
4114
+ removeMember(groupId, userId) {
4115
+ return this.client.json("DELETE", `${this.path(groupId)}/members/${encodeURIComponent(userId)}`);
4116
+ }
4117
+ listTasks(groupId) {
4118
+ return this.client.json("GET", `${this.path(groupId)}/tasks`);
4119
+ }
4120
+ async createTask(groupId, payload) {
4121
+ try {
4122
+ return await this.client.json("POST", `${this.path(groupId)}/tasks`, payload);
4123
+ } catch (error) {
4124
+ if (error instanceof BladeApiError && error.status === 502) {
4125
+ const data = await error.response.clone().json().catch(() => null);
4126
+ if (typeof data?.detail?.message === "string") error.message = data.detail.message;
4127
+ }
4128
+ throw error;
4129
+ }
4130
+ }
4131
+ updateTask(groupId, taskId, payload) {
4132
+ return this.client.json("PATCH", `${this.path(groupId)}/tasks/${encodeURIComponent(taskId)}`, payload);
4133
+ }
4134
+ deleteTask(groupId, taskId) {
4135
+ return this.client.json("DELETE", `${this.path(groupId)}/tasks/${encodeURIComponent(taskId)}`);
4136
+ }
4137
+ listAssets(groupId) {
4138
+ return this.client.json("GET", `${this.path(groupId)}/assets`);
4139
+ }
4140
+ async readAsset(groupId, path, signal) {
4141
+ const response = await this.client.fetch(
4142
+ "GET",
4143
+ `${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`,
4144
+ { signal }
4145
+ );
4146
+ return response.blob();
4147
+ }
4148
+ /** Read at most the first 1 MiB. Full downloads continue to use readAsset. */
4149
+ async readAssetPreview(groupId, path, signal) {
4150
+ const limit = 1024 * 1024;
4151
+ let response;
4152
+ try {
4153
+ response = await this.client.fetch(
4154
+ "GET",
4155
+ `${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`,
4156
+ { signal, headers: { Range: `bytes=0-${limit - 1}` } }
4157
+ );
4158
+ } catch (error) {
4159
+ if (error instanceof BladeApiError && error.status === 416 && error.response.headers.get("content-range") === "bytes */0") {
4160
+ await error.response.body?.cancel();
4161
+ return { blob: new Blob(), truncated: false };
4162
+ }
4163
+ throw error;
4164
+ }
4165
+ const reader = response.body?.getReader();
4166
+ if (!reader) return { blob: new Blob(), truncated: false };
4167
+ const chunks = [];
4168
+ let bytes = 0;
4169
+ let ended = false;
4170
+ let overflow = false;
4171
+ try {
4172
+ while (bytes < limit) {
4173
+ signal?.throwIfAborted();
4174
+ const { done, value } = await reader.read();
4175
+ if (done) {
4176
+ ended = true;
4177
+ break;
4178
+ }
4179
+ const remaining = limit - bytes;
4180
+ overflow = value.byteLength > remaining;
4181
+ const chunk = value.slice(0, remaining);
4182
+ chunks.push(chunk);
4183
+ bytes += chunk.byteLength;
4184
+ }
4185
+ signal?.throwIfAborted();
4186
+ } finally {
4187
+ if (!ended) await reader.cancel().catch(() => void 0);
4188
+ reader.releaseLock();
4189
+ }
4190
+ const range = response.headers.get("content-range")?.match(/^bytes 0-\d+\/(\d+)$/);
4191
+ const length = response.headers.get("content-length");
4192
+ const total = range ? Number(range[1]) : response.status === 200 && length && !response.headers.has("content-encoding") ? Number(length) : null;
4193
+ return {
4194
+ blob: new Blob(chunks, { type: response.headers.get("content-type") ?? "" }),
4195
+ truncated: overflow || (total !== null && Number.isFinite(total) ? total > bytes : !ended)
4196
+ };
4197
+ }
4198
+ async uploadAsset(groupId, file, path, message = "") {
4199
+ const form = new FormData();
4200
+ form.append("file", file);
4201
+ form.append("message", message);
4202
+ const response = await this.client.formData(
4203
+ "POST",
4204
+ `${this.path(groupId)}/assets/upload?${new URLSearchParams({ path })}`,
4205
+ form
4206
+ );
4207
+ return response.json();
4208
+ }
4209
+ createAssetFolder(groupId, path) {
4210
+ return this.client.json("POST", `${this.path(groupId)}/assets/folders`, { path });
4211
+ }
4212
+ deleteAsset(groupId, path) {
4213
+ return this.client.json("DELETE", `${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`);
4214
+ }
4215
+ submitSessionAsset(payload) {
4216
+ return this.client.json("POST", "/api/topic-agent/files/submit", payload);
3558
4217
  }
3559
4218
  };
3560
4219
 
@@ -3595,6 +4254,34 @@ var SiteVisitsResource = class {
3595
4254
  }
3596
4255
  };
3597
4256
 
4257
+ // src/session/background.ts
4258
+ var BACKGROUND_STATUSES = [
4259
+ "starting",
4260
+ "running",
4261
+ "cancelling",
4262
+ "succeeded",
4263
+ "failed",
4264
+ "cancelled",
4265
+ "lost"
4266
+ ];
4267
+ function normalizeBackgroundStatus(value) {
4268
+ return BACKGROUND_STATUSES.includes(value) ? value : "running";
4269
+ }
4270
+ function completedBackgroundTask(task) {
4271
+ const taskId = typeof task.id === "string" ? task.id : typeof task.task_id === "string" ? task.task_id : "";
4272
+ if (!taskId) return null;
4273
+ return {
4274
+ id: taskId,
4275
+ command: typeof task.command === "string" ? task.command : "",
4276
+ description: typeof task.description === "string" ? task.description : void 0,
4277
+ status: normalizeBackgroundStatus(task.status),
4278
+ failureReason: typeof task.failure_reason === "string" ? task.failure_reason : void 0,
4279
+ exitCode: typeof task.exit_code === "number" || task.exit_code === null ? task.exit_code : void 0,
4280
+ elapsedSeconds: typeof task.elapsed_seconds === "number" ? task.elapsed_seconds : 0,
4281
+ output: Array.isArray(task.output_preview) && task.output_preview.length > 0 ? task.output_preview.join("\n") : void 0
4282
+ };
4283
+ }
4284
+
3598
4285
  // src/shared/auth-busy.ts
3599
4286
  var DEFAULT_RETRY_AFTER_SECONDS = 1;
3600
4287
  var MAX_TIMER_DELAY_MS = 2147483647;
@@ -4011,15 +4698,8 @@ function contentPreview(content, maxLen = 80) {
4011
4698
  const text = getTextContent(content).trim();
4012
4699
  return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
4013
4700
  }
4014
- function transformSlashCommand(skillName, rawInput, { local = true, installed = true } = {}) {
4015
- const prompt = rawInput.replace(`<skill>${skillName}</skill>`, "").trim();
4016
- if (local) {
4017
- return prompt ? `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1
4018
- ${prompt}` : `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1`;
4019
- }
4020
- const steps = installed ? `\u8BF7\u5148\u8FD0\u884C blade hub skill use ${skillName} \u52A0\u8F7D\u6280\u80FD` : `\u8BE5\u6280\u80FD\u5C1A\u672A\u5B89\u88C5\u3002\u8BF7\u5148\u8FD0\u884C blade hub skill install ${skillName} \u5B89\u88C5\uFF0C\u518D\u8FD0\u884C blade hub skill use ${skillName} \u52A0\u8F7D`;
4021
- return prompt ? `${steps}\uFF0C\u7136\u540E\u5B8C\u6210\u4EFB\u52A1\uFF1A
4022
- ${prompt}` : `${steps}\uFF0C\u7136\u540E\u4F7F\u7528\u5B83\u5B8C\u6210\u4EFB\u52A1`;
4701
+ function transformSlashCommand(skillName, rawInput, _availability = {}) {
4702
+ return rawInput.replace(`<skill>${skillName}</skill>`, `@${skillName}`).trim();
4023
4703
  }
4024
4704
  var ATTACHMENT_TAG_RE = /\[附件:\s*([^\]]+)\](?:[ \t]*已上传到工作区路径:[ \t]*([^\r\n]+))?/g;
4025
4705
  var CONTEXT_PART_RE = /^\[上下文:\s*([^\]]+)\]\n([\s\S]*)$/;
@@ -4147,7 +4827,13 @@ function workflowEntriesFromOutput(output) {
4147
4827
  }
4148
4828
  function workflowNameFromArguments(argumentsJson) {
4149
4829
  try {
4150
- const name = JSON.parse(argumentsJson)?.name;
4830
+ const args = JSON.parse(argumentsJson);
4831
+ const skillId = typeof args?.skill_id === "string" ? args.skill_id.trim() : "";
4832
+ if (skillId) {
4833
+ const flow = typeof args?.flow === "string" ? args.flow.trim() : "";
4834
+ return `\u6280\u80FD\u300C${skillId}\u300D${flow && flow !== "main" ? ` \xB7 \u6D41\u7A0B ${flow}` : ""}`;
4835
+ }
4836
+ const name = args?.name;
4151
4837
  return typeof name === "string" ? name : "";
4152
4838
  } catch {
4153
4839
  return "";
@@ -4171,6 +4857,15 @@ function applyWorkflowEvent(current, toolCallId, type2, payload) {
4171
4857
  }
4172
4858
  const base = current ?? { toolCallId, name: "", status: "running", entries: [] };
4173
4859
  if (type2 === "workflow:step") {
4860
+ const settled = asText(payload.status);
4861
+ const stepId = asText(payload.step_id);
4862
+ if (stepId && isFinished(settled)) {
4863
+ const index = base.entries.findIndex((entry) => entry.kind === "step" && entry.stepId === stepId);
4864
+ if (index === -1) return null;
4865
+ const entries = [...base.entries];
4866
+ entries[index] = { ...entries[index], status: settled };
4867
+ return { ...base, entries };
4868
+ }
4174
4869
  return {
4175
4870
  ...base,
4176
4871
  entries: [
@@ -4185,7 +4880,9 @@ function applyWorkflowEvent(current, toolCallId, type2, payload) {
4185
4880
  };
4186
4881
  }
4187
4882
  if (type2 === "workflow:log") {
4188
- return { ...base, entries: [...base.entries, { kind: "log", text: asText(payload.text) }] };
4883
+ const stepId = asText(payload.step_id);
4884
+ const entry = stepId ? { kind: "log", text: asText(payload.text), stepId } : { kind: "log", text: asText(payload.text) };
4885
+ return { ...base, entries: [...base.entries, entry] };
4189
4886
  }
4190
4887
  if (type2 === "workflow:run:end") {
4191
4888
  const status = asText(payload.status);
@@ -4207,10 +4904,10 @@ function rebuildWorkflowRuns(turns, previous) {
4207
4904
  const name = workflowNameFromArguments(toolCall.arguments);
4208
4905
  const prior = previous[toolCall.id];
4209
4906
  if (isFinished(toolCall.status)) {
4210
- const entries = workflowEntriesFromOutput(
4211
- typeof toolCall.result === "string" ? toolCall.result.trim() : ""
4212
- );
4213
- runs[toolCall.id] = prior && prior.status === toolCall.status && prior.name === name && JSON.stringify(prior.entries) === JSON.stringify(entries) ? prior : { toolCallId: toolCall.id, name, status: toolCall.status, entries };
4907
+ const steps = toolCall.workflow_steps ?? [];
4908
+ const entries = steps.length > 0 ? workflowEntriesFromSteps(steps) : workflowEntriesFromOutput(typeof toolCall.result === "string" ? toolCall.result.trim() : "");
4909
+ const status = toolCall.status === "error" && workflowRunCancelled(steps) ? "cancelled" : toolCall.status;
4910
+ runs[toolCall.id] = prior && prior.status === status && prior.name === name && JSON.stringify(prior.entries) === JSON.stringify(entries) ? prior : { toolCallId: toolCall.id, name, status, entries };
4214
4911
  continue;
4215
4912
  }
4216
4913
  runs[toolCall.id] = prior && prior.name === name ? prior : { toolCallId: toolCall.id, name, status: prior?.status ?? "running", entries: prior?.entries ?? [] };
@@ -4461,7 +5158,9 @@ function projectionToMessage(turn) {
4461
5158
  display_name: toolCall.display_name,
4462
5159
  arguments: toolCall.arguments,
4463
5160
  result: toolCall.result ?? void 0,
5161
+ ...toolCall.evidence ? { evidence: toolCall.evidence } : {},
4464
5162
  pending_question_ref: toolCall.pending_question_ref ?? void 0,
5163
+ workflow_steps: toolCall.workflow_steps ?? void 0,
4465
5164
  status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending",
4466
5165
  ...typeof toolCall.duration_ms === "number" ? { duration_ms: toolCall.duration_ms } : {}
4467
5166
  })) : void 0;
@@ -4476,10 +5175,14 @@ function projectionToMessage(turn) {
4476
5175
  ...toolCalls ? { tool_calls: toolCalls } : {},
4477
5176
  loop_name: turn.loop_id,
4478
5177
  entry_id: turn.turn_id,
5178
+ ...turn.client_request_id ? { client_request_id: turn.client_request_id } : {},
4479
5179
  status: turn.status,
4480
5180
  ...typeof turn.duration_ms === "number" ? { duration_ms: turn.duration_ms } : {},
4481
5181
  ...turn.started_at ? { timestamp: turn.started_at } : {},
4482
- ...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {}
5182
+ ...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {},
5183
+ ...turn.refs?.length ? { refs: turn.refs } : {},
5184
+ ...turn.context_attachments?.length ? { context_attachments: turn.context_attachments } : {},
5185
+ ...turn.topic_asset_refs?.length ? { topic_asset_refs: turn.topic_asset_refs } : {}
4483
5186
  };
4484
5187
  }
4485
5188
  var messageProjectionCache = /* @__PURE__ */ new WeakMap();
@@ -4716,7 +5419,7 @@ function withQueue(state, snapshot2) {
4716
5419
  return { ...state, queue: snapshot2 };
4717
5420
  }
4718
5421
  var localTurnCounter = 0;
4719
- function addUserMessage(state, content, clientRequestId) {
5422
+ function addUserMessage(state, content, clientRequestId, topicAssetRefs, refs, contextAttachments) {
4720
5423
  localTurnCounter += 1;
4721
5424
  const turnId = `local-user-${clientRequestId ?? `${Date.now()}-${localTurnCounter}`}`;
4722
5425
  const optimisticTurn = {
@@ -4727,6 +5430,9 @@ function addUserMessage(state, content, clientRequestId) {
4727
5430
  // client_render_id(= 乐观 turn 自己的 turn_id)保持同一个值,
4728
5431
  // 消费方才能拿它当"确认前后不变"的稳定标识使用。
4729
5432
  client_render_id: turnId,
5433
+ ...refs ? { refs } : {},
5434
+ ...contextAttachments?.length ? { context_attachments: contextAttachments } : {},
5435
+ ...topicAssetRefs?.length ? { topic_asset_refs: topicAssetRefs } : {},
4730
5436
  loop_id: "root",
4731
5437
  role: "user",
4732
5438
  status: "streaming",
@@ -4910,6 +5616,9 @@ function sessionStatusFromChatEnd(status) {
4910
5616
  if (status === "failed") return "failed";
4911
5617
  return "completed";
4912
5618
  }
5619
+ function isCompactionFinished(type2) {
5620
+ return type2 === "compaction:end" || type2 === "compaction:error" || type2 === "compaction:abort";
5621
+ }
4913
5622
  function extractTerminalChatEndStatus(events) {
4914
5623
  for (let i = events.length - 1; i >= 0; i--) {
4915
5624
  const event = events[i];
@@ -4920,6 +5629,7 @@ function extractTerminalChatEndStatus(events) {
4920
5629
  return null;
4921
5630
  }
4922
5631
  var AgentSession = class _AgentSession {
5632
+ logCursor = new LogCursor();
4923
5633
  sessionId;
4924
5634
  state;
4925
5635
  /** status 每变化一次加一(单调递增)。异步读取会话详情时据此判断请求期间状态是否变过,避免 running→终态→running 这类来回被误认为「没变」。 */
@@ -4932,6 +5642,7 @@ var AgentSession = class _AgentSession {
4932
5642
  emitter = new Emitter();
4933
5643
  commands = new CommandRegistry();
4934
5644
  runtime;
5645
+ activeReplayPlaybackId = null;
4935
5646
  projectionBuilder = new ClientProjectionBuilder();
4936
5647
  pendingTurnPatches = /* @__PURE__ */ new Map();
4937
5648
  patchFlushHandle = null;
@@ -4970,7 +5681,10 @@ var AgentSession = class _AgentSession {
4970
5681
  bufferedCommandDeliveries = null;
4971
5682
  pendingReplayMessage = null;
4972
5683
  pendingReplayMode;
4973
- pendingReplayKbIds;
5684
+ pendingReplayRequestId;
5685
+ pendingReplayAttachments;
5686
+ pendingReplayRefs;
5687
+ pendingReplayTopicAssetRefs;
4974
5688
  recentChatEndAt = 0;
4975
5689
  /**
4976
5690
  * 最近一次已经通过 `error` 事件投递过的服务端失败(`message` + `code`)。
@@ -4990,12 +5704,24 @@ var AgentSession = class _AgentSession {
4990
5704
  disposed = false;
4991
5705
  getAppContext = null;
4992
5706
  sendPending = false;
4993
- manualCompactionPending = false;
5707
+ manualCompactionRequest = null;
5708
+ /** Reload requests from retracts/epoch changes; done only when a reload started after the latest one succeeds. */
5709
+ epochReloadRequested = 0;
5710
+ epochReloadDone = 0;
5711
+ epochReloadRunning = false;
4994
5712
  /** 手动整理的在途标记,同时写进状态(界面据此保持「正在整理」,见 SessionState.compactionPending)。 */
4995
- setManualCompactionPending(pending) {
4996
- this.manualCompactionPending = pending;
5713
+ setManualCompactionRequest(request) {
5714
+ this.manualCompactionRequest = request;
5715
+ const pending = request !== null;
4997
5716
  if (this.state.compactionPending !== pending) this.update((s) => ({ ...s, compactionPending: pending }));
4998
5717
  }
5718
+ handleManualCompactionFinished(event) {
5719
+ const request = this.manualCompactionRequest;
5720
+ const runId = event.payload.run_id;
5721
+ if (!request || !isCompactionFinished(event.type) || typeof runId !== "string" || !runId) return;
5722
+ if (request.runId === null) request.finishedBeforeAck.add(runId);
5723
+ else if (request.runId === runId) this.setManualCompactionRequest(null);
5724
+ }
4999
5725
  /** 最近一次还在等服务端结果的发送请求 id;排队中断的 chat:end 据此判断回滚是否过期。 */
5000
5726
  pendingChatRequestId = null;
5001
5727
  /** 会话详情给出的运行时类型;"native" 时启用原生智能体的投影规则(见 _handleTurnEvents / _handleChatEnd)。 */
@@ -5153,6 +5879,11 @@ var AgentSession = class _AgentSession {
5153
5879
  * chat:end 会让界面停在「运行中」)。
5154
5880
  */
5155
5881
  async reloadHistory() {
5882
+ const replaced = await this.reloadHistoryWindow();
5883
+ await this.refreshRunningState().catch(() => void 0);
5884
+ return replaced;
5885
+ }
5886
+ async reloadHistoryWindow() {
5156
5887
  if (this.disposed) return false;
5157
5888
  this.invalidateTimeline();
5158
5889
  this.invalidateOlderPaging();
@@ -5162,9 +5893,7 @@ var AgentSession = class _AgentSession {
5162
5893
  loadingOlder: false,
5163
5894
  timelineRevision: state.timelineRevision + 1
5164
5895
  }));
5165
- const replaced = await this.replaceFromHistory();
5166
- await this.refreshRunningState().catch(() => void 0);
5167
- return replaced;
5896
+ return this.replaceFromHistory();
5168
5897
  }
5169
5898
  /**
5170
5899
  * 向服务端确认会话此刻是否在运行,校准流式状态(残留的「运行中」会被清掉)。只在已加入房间时
@@ -5255,10 +5984,78 @@ var AgentSession = class _AgentSession {
5255
5984
  this.getAppContext = options.getContext;
5256
5985
  return attachment;
5257
5986
  }
5987
+ /** Prepare a durable text input without sending any network request. */
5988
+ prepareSend(content, options = {}) {
5989
+ if (!content.trim()) throw new Error("\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A");
5990
+ if (Object.keys(options).some((key) => !["mode", "model", "thinkingOverride", "reasoningLevel", "contextAttachments"].includes(key))) {
5991
+ throw new Error("\u53EF\u9760\u6587\u672C\u53D1\u9001\u4E0D\u652F\u6301\u8FD9\u4E9B\u53C2\u6570\uFF0C\u8BF7\u4F7F\u7528 send()");
5992
+ }
5993
+ const snapshot2 = JSON.parse(JSON.stringify(options));
5994
+ if (snapshot2.contextAttachments) snapshot2.contextAttachments = snapshotContextAttachments(snapshot2.contextAttachments);
5995
+ return { sessionId: this.sessionId, requestId: createClientRequestId(), content, options: snapshot2 };
5996
+ }
5997
+ /** Durably enqueue an ordinary text input. Retries must reuse the saved request. */
5998
+ async sendPrepared(request) {
5999
+ if (request.sessionId !== this.sessionId) throw new Error("\u53D1\u9001\u8BF7\u6C42\u4E0D\u5C5E\u4E8E\u5F53\u524D\u4F1A\u8BDD");
6000
+ try {
6001
+ this.runtime.ensureConnected();
6002
+ const response = await this.runtime.emitWithAck("chat:append", {
6003
+ session_id: this.sessionId,
6004
+ client_request_id: request.requestId,
6005
+ message: request.content,
6006
+ prepared_options: {
6007
+ mode: request.options.mode,
6008
+ model: request.options.model,
6009
+ thinking_override: request.options.thinkingOverride,
6010
+ reasoning_level: request.options.reasoningLevel,
6011
+ ...request.options.contextAttachments ? { context_attachments: request.options.contextAttachments } : {}
6012
+ }
6013
+ }, QUEUE_ACK_TIMEOUT_MS);
6014
+ const result = parseQueueAck(response, this.sessionId);
6015
+ if (result.snapshot) this.update((state) => withQueue(state, result.snapshot));
6016
+ if (!result.ok) return {
6017
+ requestId: request.requestId,
6018
+ status: response?.status === "conflict" ? "rejected" : "unknown",
6019
+ message: result.message ?? void 0
6020
+ };
6021
+ const delivery = response?.delivery;
6022
+ if (!isRecord(delivery) || delivery.status !== "accepted") return { requestId: request.requestId, status: "unknown" };
6023
+ return {
6024
+ requestId: request.requestId,
6025
+ status: "accepted",
6026
+ itemId: typeof delivery.item_id === "string" ? delivery.item_id : void 0,
6027
+ queueStatus: typeof delivery.queue_status === "string" ? delivery.queue_status : void 0,
6028
+ executionId: typeof delivery.execution_id === "string" ? delivery.execution_id : null
6029
+ };
6030
+ } catch {
6031
+ return { requestId: request.requestId, status: "unknown" };
6032
+ }
6033
+ }
6034
+ /** Owner-authorized lookup; receipts remain until the session is deleted. */
6035
+ async getDelivery(requestId) {
6036
+ try {
6037
+ this.runtime.ensureConnected();
6038
+ const response = await this.runtime.emitWithAck("chat:queue:request", {
6039
+ session_id: this.sessionId,
6040
+ client_request_id: requestId
6041
+ }, QUEUE_ACK_TIMEOUT_MS);
6042
+ if (response?.status === "accepted") return {
6043
+ requestId,
6044
+ status: "accepted",
6045
+ itemId: typeof response.item_id === "string" ? response.item_id : void 0,
6046
+ queueStatus: typeof response.queue_status === "string" ? response.queue_status : void 0,
6047
+ executionId: typeof response.execution_id === "string" ? response.execution_id : null
6048
+ };
6049
+ if (response?.status === "not_found") return { requestId, status: "not_found" };
6050
+ return { requestId, status: "unknown", message: typeof response?.message === "string" ? response.message : void 0 };
6051
+ } catch {
6052
+ return { requestId, status: "unknown" };
6053
+ }
6054
+ }
5258
6055
  /** 发送一条消息。字符串或多模态内容均可。 */
5259
6056
  async send(content, options = {}) {
5260
6057
  this.runtime.ensureConnected();
5261
- if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.sendPending || this.manualCompactionPending) {
6058
+ if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.sendPending || this.manualCompactionRequest) {
5262
6059
  this.update(
5263
6060
  (s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002")
5264
6061
  );
@@ -5388,8 +6185,9 @@ var AgentSession = class _AgentSession {
5388
6185
  }
5389
6186
  }
5390
6187
  async sendUnlocked(content, options) {
6188
+ const contextAttachments = options.contextAttachments ? snapshotContextAttachments(options.contextAttachments) : void 0;
5391
6189
  await this.ensureIdleBeforeSend();
5392
- if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.manualCompactionPending) {
6190
+ if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.manualCompactionRequest) {
5393
6191
  this.update(
5394
6192
  (s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002")
5395
6193
  );
@@ -5424,7 +6222,7 @@ var AgentSession = class _AgentSession {
5424
6222
  let optimisticAskAnswer;
5425
6223
  let previousAskAnswer;
5426
6224
  this.update((state) => {
5427
- const next = addUserMessage(state, content, clientRequestId);
6225
+ const next = addUserMessage(state, content, clientRequestId, options.topicAssetRefs, options.refs, contextAttachments);
5428
6226
  optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
5429
6227
  return next;
5430
6228
  });
@@ -5442,7 +6240,10 @@ var AgentSession = class _AgentSession {
5442
6240
  if (!options.replayDecision) {
5443
6241
  this.pendingReplayMessage = content;
5444
6242
  this.pendingReplayMode = options.mode;
5445
- this.pendingReplayKbIds = options.kbIds;
6243
+ this.pendingReplayRequestId = clientRequestId;
6244
+ this.pendingReplayAttachments = contextAttachments;
6245
+ this.pendingReplayRefs = options.refs;
6246
+ this.pendingReplayTopicAssetRefs = options.topicAssetRefs;
5446
6247
  }
5447
6248
  this.pendingChatRequestId = clientRequestId;
5448
6249
  const payload = {
@@ -5457,7 +6258,9 @@ var AgentSession = class _AgentSession {
5457
6258
  whatif: options.whatif,
5458
6259
  replay_decision: options.replayDecision,
5459
6260
  app_context: appContext,
5460
- kb_ids: options.kbIds,
6261
+ context_attachments: contextAttachments,
6262
+ refs: options.refs,
6263
+ topic_asset_refs: options.topicAssetRefs,
5461
6264
  // 运行中提交不必由客户端挑命令:服务端按会话运行状态决定直接执行还是入队。
5462
6265
  queue_when_running: options.queueWhenRunning === true
5463
6266
  };
@@ -5541,7 +6344,10 @@ var AgentSession = class _AgentSession {
5541
6344
  }
5542
6345
  this.pendingReplayMessage = null;
5543
6346
  this.pendingReplayMode = void 0;
5544
- this.pendingReplayKbIds = void 0;
6347
+ this.pendingReplayRequestId = void 0;
6348
+ this.pendingReplayAttachments = void 0;
6349
+ this.pendingReplayRefs = void 0;
6350
+ this.pendingReplayTopicAssetRefs = void 0;
5545
6351
  this.update((current) => {
5546
6352
  const filtered = optimisticTurnId ? current.turns.filter((turn) => turn.turn_id !== optimisticTurnId) : current.turns;
5547
6353
  let next = optimisticTurnId ? withTurns({ ...current, turns: filtered }, filtered) : current;
@@ -5563,22 +6369,62 @@ var AgentSession = class _AgentSession {
5563
6369
  }
5564
6370
  }
5565
6371
  /** 停止当前回复。 */
5566
- stop() {
5567
- return this.runtime.emitWithAck("chat:stop", { session_id: this.sessionId }, 35e3).then((response) => {
5568
- const result = response ?? {};
5569
- if (result.status !== "accepted") this.setManualCompactionPending(false);
5570
- return result;
5571
- });
6372
+ async stop() {
6373
+ const request = this.manualCompactionRequest;
6374
+ if (request) {
6375
+ const ack = await request.ack;
6376
+ if (ack.status !== "accepted") return ack;
6377
+ if (this.manualCompactionRequest !== request) return { status: "idle" };
6378
+ return await this.runtime.emitWithAck("chat:stop", {
6379
+ session_id: this.sessionId,
6380
+ chat_run_id: request.runId
6381
+ }, 35e3) ?? {};
6382
+ }
6383
+ const response = await this.runtime.emitWithAck("chat:stop", { session_id: this.sessionId }, 35e3) ?? {};
6384
+ const cancelled = response.cancelled_client_request_ids;
6385
+ if (response.status === "stopped" && Array.isArray(cancelled) && this.pendingChatRequestId !== null && cancelled.includes(this.pendingChatRequestId)) {
6386
+ this.pendingChatRequestId = null;
6387
+ this.update((state) => ({ ...withoutOptimisticUserTurns(state, cancelled), isStreaming: false }));
6388
+ this.applyDesiredStreamTokens();
6389
+ }
6390
+ return response;
5572
6391
  }
5573
6392
  /** 手动触发上下文压缩。 */
5574
6393
  compact() {
5575
- if (this.state.isStreaming || this.state.activeCompaction || this.manualCompactionPending) {
6394
+ if (this.state.isStreaming || this.state.activeCompaction || this.manualCompactionRequest) {
5576
6395
  return Promise.resolve({
5577
6396
  status: "error",
5578
6397
  message: "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002"
5579
6398
  });
5580
6399
  }
5581
- this.setManualCompactionPending(true);
6400
+ const request = {
6401
+ runId: null,
6402
+ finishedBeforeAck: /* @__PURE__ */ new Set(),
6403
+ ack: Promise.resolve().then(() => this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4)).then((response) => {
6404
+ const result = response ?? {};
6405
+ if (result.status !== "accepted") {
6406
+ if (this.manualCompactionRequest === request) this.setManualCompactionRequest(null);
6407
+ return result;
6408
+ }
6409
+ if (typeof result.run_id !== "string" || !result.run_id) {
6410
+ throw new AgentSessionError("\u6574\u7406\u8BF7\u6C42\u786E\u8BA4\u7F3A\u5C11 run_id\uFF0C\u65E0\u6CD5\u786E\u8BA4\u8FD0\u884C\u72B6\u6001");
6411
+ }
6412
+ request.runId = result.run_id;
6413
+ if (request.finishedBeforeAck.has(request.runId) && this.manualCompactionRequest === request) {
6414
+ this.setManualCompactionRequest(null);
6415
+ }
6416
+ request.finishedBeforeAck.clear();
6417
+ return result;
6418
+ }).catch((error) => {
6419
+ if (this.manualCompactionRequest === request) this.setManualCompactionRequest(null);
6420
+ throw error;
6421
+ }).finally(() => {
6422
+ if ((!this.manualCompactionRequest || this.manualCompactionRequest === request) && this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
6423
+ this.update((state) => ({ ...state, activeCompaction: null }));
6424
+ }
6425
+ })
6426
+ };
6427
+ this.setManualCompactionRequest(request);
5582
6428
  this.update((state) => ({
5583
6429
  ...state,
5584
6430
  activeCompaction: {
@@ -5588,18 +6434,7 @@ var AgentSession = class _AgentSession {
5588
6434
  trigger: "manual"
5589
6435
  }
5590
6436
  }));
5591
- return this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4).then((response) => {
5592
- const result = response ?? {};
5593
- if (result.status !== "accepted") this.setManualCompactionPending(false);
5594
- return result;
5595
- }).catch((error) => {
5596
- this.setManualCompactionPending(false);
5597
- throw error;
5598
- }).finally(() => {
5599
- if (this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
5600
- this.update((state) => ({ ...state, activeCompaction: null }));
5601
- }
5602
- });
6437
+ return request.ack;
5603
6438
  }
5604
6439
  /**
5605
6440
  * 把一段业务数据作为附件放进聊天输入框(比如地图上选中的点位)。
@@ -5799,14 +6634,86 @@ var AgentSession = class _AgentSession {
5799
6634
  _handleTurnEnd(turn) {
5800
6635
  this.applyTurn(turn);
5801
6636
  }
6637
+ runEpochReload() {
6638
+ if (this.disposed || this.epochReloadRunning || this.epochReloadDone >= this.epochReloadRequested) return;
6639
+ const target = this.epochReloadRequested;
6640
+ this.epochReloadRunning = true;
6641
+ void this.reloadHistoryWindow().then((replaced) => {
6642
+ if (replaced) this.epochReloadDone = Math.max(this.epochReloadDone, target);
6643
+ }).catch(() => void 0).finally(() => {
6644
+ this.epochReloadRunning = false;
6645
+ if (this.epochReloadRequested > target) this.runEpochReload();
6646
+ });
6647
+ }
5802
6648
  /** @internal 历史回放通道(event_format: raw)。 */
5803
- _handleTurnEvents(events, replayed = false) {
5804
- if (!events.length) return;
5805
- if (events.some(
5806
- (event) => event.type === "compaction:end" || event.type === "compaction:error" || event.type === "compaction:abort"
5807
- )) {
5808
- this.setManualCompactionPending(false);
6649
+ _handleTurnEvents(incoming, replayed = false) {
6650
+ const delivery = this.logCursor.consume(incoming);
6651
+ let events = delivery.events;
6652
+ if (delivery.reload) {
6653
+ this.activeReplayPlaybackId = null;
6654
+ this.projectionBuilder.reset();
6655
+ this.pendingTurnPatches.clear();
6656
+ this.epochReloadRequested += 1;
6657
+ }
6658
+ this.runEpochReload();
6659
+ for (const event of events) {
6660
+ const playbackId = typeof event.payload.playback_id === "string" ? event.payload.playback_id : null;
6661
+ if (event.type === "chat:start") this.activeReplayPlaybackId = null;
6662
+ if (event.type === "replay:start" && playbackId) {
6663
+ this.activeReplayPlaybackId = playbackId;
6664
+ this._handleChatStart();
6665
+ } else if (event.type === "replay:state") {
6666
+ const status = event.payload.status;
6667
+ if (event.payload.reconciled === true && typeof status === "string") {
6668
+ if (status === "running") this._handleChatStart();
6669
+ else {
6670
+ this.activeReplayPlaybackId = null;
6671
+ this.finishStreaming(status);
6672
+ }
6673
+ }
6674
+ this._handleSessionUpdated(event.payload);
6675
+ } else if (event.type === "replay:end" && playbackId && (!this.activeReplayPlaybackId || this.activeReplayPlaybackId === playbackId)) {
6676
+ this.activeReplayPlaybackId = null;
6677
+ const status = event.payload.status === "interrupted" ? "interrupted" : event.payload.status === "failed" ? "failed" : "completed";
6678
+ this._handleChatEnd({ status });
6679
+ }
6680
+ if (event.type === "log:notification") {
6681
+ const notification = event.payload.event;
6682
+ if (isCompactionFinished(notification.type)) {
6683
+ this.handleManualCompactionFinished(notification);
6684
+ } else if (notification.type === "artifact:created" || notification.type === "artifact:updated") {
6685
+ this._handleArtifact(notification.payload, notification.type === "artifact:created" ? "created" : "updated", replayed);
6686
+ } else if (notification.type === "workspace:changed") {
6687
+ const change = notification.payload.workspace_change;
6688
+ const paths = Array.isArray(notification.payload.paths) ? notification.payload.paths.filter((path) => typeof path === "string") : [];
6689
+ this.emitter.emit("workspaceChanged", {
6690
+ ...paths.length ? { paths } : {},
6691
+ ...typeof change?.to === "string" && change.to ? { workspacePath: change.to } : {}
6692
+ });
6693
+ } else if (notification.type === "task:updated" && Array.isArray(notification.payload.tasks)) {
6694
+ this._handleTaskUpdated(notification.payload.tasks);
6695
+ } else if (notification.type === "bg:tasks_completed" && Array.isArray(notification.payload.tasks)) {
6696
+ for (const task of notification.payload.tasks) {
6697
+ const completed = completedBackgroundTask(task);
6698
+ if (completed) this._handleBackgroundTask(completed);
6699
+ }
6700
+ } else {
6701
+ this.applyWorkflowEvents([notification]);
6702
+ }
6703
+ }
6704
+ if (event.type === "turn:snapshot") {
6705
+ const turn = event.payload.turn;
6706
+ this.projectionBuilder.acceptDurableTurn(turn);
6707
+ this.pendingTurnPatches.delete(turn.turn_id);
6708
+ this.applyTurn(
6709
+ turn,
6710
+ replayed ? this.subscriptionReplaySource ?? "bootstrap_replay" : "live"
6711
+ );
6712
+ }
5809
6713
  }
6714
+ events = events.filter((event) => event.type !== "turn:snapshot" && event.type !== "log:notification" && !event.type.startsWith("replay:"));
6715
+ if (!events.length) return;
6716
+ for (const event of events) this.handleManualCompactionFinished(event);
5810
6717
  this.dispatchRawCommands(events);
5811
6718
  const terminalChatEndStatus = extractTerminalChatEndStatus(events);
5812
6719
  if (!terminalChatEndStatus && hasChatRunEvent(events) && this.state.status !== "waiting_for_input") {
@@ -5949,7 +6856,6 @@ var AgentSession = class _AgentSession {
5949
6856
  intent: data.intent,
5950
6857
  status: data.status,
5951
6858
  model: data.model,
5952
- boundSkillId: data.bound_skill_id,
5953
6859
  replayState: data.replay_state
5954
6860
  });
5955
6861
  }
@@ -5972,10 +6878,16 @@ var AgentSession = class _AgentSession {
5972
6878
  _handleReplayMismatch(data) {
5973
6879
  const message = this.pendingReplayMessage ?? data.actual_message ?? "";
5974
6880
  const mode = this.pendingReplayMode;
5975
- const kbIds = this.pendingReplayKbIds;
6881
+ const requestId = this.pendingReplayRequestId ?? createClientRequestId();
6882
+ const contextAttachments = this.pendingReplayAttachments;
6883
+ const refs = this.pendingReplayRefs;
6884
+ const topicAssetRefs = this.pendingReplayTopicAssetRefs;
5976
6885
  this.pendingReplayMessage = null;
5977
6886
  this.pendingReplayMode = void 0;
5978
- this.pendingReplayKbIds = void 0;
6887
+ this.pendingReplayRequestId = void 0;
6888
+ this.pendingReplayAttachments = void 0;
6889
+ this.pendingReplayRefs = void 0;
6890
+ this.pendingReplayTopicAssetRefs = void 0;
5979
6891
  const respond = (decision) => {
5980
6892
  void this.runtime.emitWithAck(
5981
6893
  "chat:send",
@@ -5984,7 +6896,10 @@ var AgentSession = class _AgentSession {
5984
6896
  message,
5985
6897
  mode,
5986
6898
  replay_decision: decision,
5987
- kb_ids: kbIds
6899
+ client_request_id: requestId,
6900
+ refs,
6901
+ topic_asset_refs: topicAssetRefs,
6902
+ context_attachments: contextAttachments
5988
6903
  },
5989
6904
  3e4
5990
6905
  ).then((response) => {
@@ -6045,8 +6960,20 @@ var AgentSession = class _AgentSession {
6045
6960
  this.emitter.emit("backgroundTask", task);
6046
6961
  }
6047
6962
  /** @internal */
6048
- _handleArtifact(event) {
6049
- this.emitter.emit("artifact", event);
6963
+ _handleArtifact(data, kind, replayed = false) {
6964
+ const filePath = typeof data.file_path === "string" ? data.file_path : "";
6965
+ if (!filePath) return;
6966
+ this._handleWorkspaceFilesChanged(filePath);
6967
+ if (replayed) return;
6968
+ this.emitter.emit("artifact", {
6969
+ filePath,
6970
+ contentType: typeof data.content_type === "string" ? data.content_type : "",
6971
+ title: typeof data.title === "string" ? data.title : filePath,
6972
+ content: typeof data.content === "string" ? data.content : "",
6973
+ oldString: typeof data.old_string === "string" ? data.old_string : void 0,
6974
+ newString: typeof data.new_string === "string" ? data.new_string : void 0,
6975
+ kind
6976
+ });
6050
6977
  }
6051
6978
  /** @internal 加入房间的应答里带 is_running,据此校准流式状态。 */
6052
6979
  _applyRunningState(isRunning, liveRevisionAtJoin = this.liveRevision) {
@@ -6248,6 +7175,7 @@ var AgentSession = class _AgentSession {
6248
7175
  display_name: toolCall.display_name,
6249
7176
  arguments: toolCall.arguments,
6250
7177
  result: toolCall.result ?? void 0,
7178
+ ...toolCall.evidence ? { evidence: toolCall.evidence } : {},
6251
7179
  status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending"
6252
7180
  };
6253
7181
  if (!this.seenToolCallIds.has(toolCall.id)) {
@@ -6514,6 +7442,7 @@ var AgentSession = class _AgentSession {
6514
7442
  session_id: this.sessionId,
6515
7443
  event_format: "raw",
6516
7444
  resume: true,
7445
+ ...this.logCursor.subscription(),
6517
7446
  ...streamTokens === null ? {} : { internal_stream_tokens: streamTokens }
6518
7447
  },
6519
7448
  5e3
@@ -6547,18 +7476,6 @@ var AgentSession = class _AgentSession {
6547
7476
  };
6548
7477
 
6549
7478
  // src/session/hub.ts
6550
- var BACKGROUND_STATUSES = [
6551
- "starting",
6552
- "running",
6553
- "cancelling",
6554
- "succeeded",
6555
- "failed",
6556
- "cancelled",
6557
- "lost"
6558
- ];
6559
- function normalizeBackgroundStatus(value) {
6560
- return BACKGROUND_STATUSES.includes(value) ? value : "running";
6561
- }
6562
7479
  var SessionHub = class {
6563
7480
  client;
6564
7481
  sessions = /* @__PURE__ */ new Map();
@@ -6576,6 +7493,8 @@ var SessionHub = class {
6576
7493
  streamTokens = null;
6577
7494
  /** 状态订阅(mode: "status")的持有计数。 */
6578
7495
  statusWatchCounts = /* @__PURE__ */ new Map();
7496
+ statusLogCursors = /* @__PURE__ */ new Map();
7497
+ replayPlaybackIds = /* @__PURE__ */ new Map();
6579
7498
  /** 启动连接门控:扣住期间只登记「需要连接」,放开后连一次。 */
6580
7499
  connectionHeld = false;
6581
7500
  connectDeferred = false;
@@ -6627,6 +7546,7 @@ var SessionHub = class {
6627
7546
  return;
6628
7547
  }
6629
7548
  this.statusWatchCounts.delete(sessionId);
7549
+ this.statusLogCursors.delete(sessionId);
6630
7550
  if (!this.sessions.has(sessionId)) {
6631
7551
  ;
6632
7552
  this.client.socket().emit("session:unsubscribe", { session_id: sessionId });
@@ -6753,6 +7673,7 @@ var SessionHub = class {
6753
7673
  this.authBusyReconnect.cancel();
6754
7674
  this.bootstrapPending.clear();
6755
7675
  this.statusWatchCounts.clear();
7676
+ this.statusLogCursors.clear();
6756
7677
  for (const session of [...this.sessions.values()]) {
6757
7678
  this.forceRelease(session);
6758
7679
  }
@@ -6814,17 +7735,32 @@ var SessionHub = class {
6814
7735
  ;
6815
7736
  this.client.socket().emit("session:unsubscribe", { session_id: session.sessionId });
6816
7737
  if (this.statusWatchCounts.has(session.sessionId)) this.emitStatusSubscribe([session.sessionId]);
7738
+ else this.statusLogCursors.delete(session.sessionId);
7739
+ if (this.client.options.anonymousSessionId && !this.needsConnection()) this.disconnect();
6817
7740
  return true;
6818
7741
  }
7742
+ statusCursor(sessionId) {
7743
+ let cursor = this.statusLogCursors.get(sessionId);
7744
+ if (!cursor) {
7745
+ cursor = new LogCursor();
7746
+ this.statusLogCursors.set(sessionId, cursor);
7747
+ }
7748
+ return cursor;
7749
+ }
6819
7750
  emitStatusSubscribe(sessionIds) {
6820
7751
  if (sessionIds.length === 0) return;
6821
7752
  const socket = this.client.socket();
7753
+ const payload = (sessionId) => ({
7754
+ session_id: sessionId,
7755
+ mode: "status",
7756
+ ...this.statusCursor(sessionId).subscription()
7757
+ });
6822
7758
  if (sessionIds.length === 1) {
6823
- socket.emit("session:subscribe", { session_id: sessionIds[0], mode: "status" });
7759
+ socket.emit("session:subscribe", payload(sessionIds[0]));
6824
7760
  return;
6825
7761
  }
6826
7762
  socket.emit("session:subscribe_batch", {
6827
- sessions: sessionIds.map((sessionId) => ({ session_id: sessionId, mode: "status" }))
7763
+ sessions: sessionIds.map(payload)
6828
7764
  });
6829
7765
  }
6830
7766
  /** 不管还有几方持有都立即释放(启动失败、切换身份)。 */
@@ -6922,6 +7858,49 @@ var SessionHub = class {
6922
7858
  "turn:events",
6923
7859
  (data, acknowledge) => {
6924
7860
  acknowledgeTurnEvents(acknowledge);
7861
+ if (data.session_id && (this.statusWatchCounts.has(data.session_id) || this.sessions.has(data.session_id)) && data.events?.some((event) => event.type === "log:record")) {
7862
+ const delivery = this.statusCursor(data.session_id).consume(data.events ?? []);
7863
+ for (const event of delivery.events) {
7864
+ if (event.type === "chat:start") {
7865
+ this.replayPlaybackIds.delete(data.session_id);
7866
+ this.hubEmitter.emit("runStarted", { sessionId: data.session_id });
7867
+ } else if (event.type === "chat:end") {
7868
+ this.hubEmitter.emit("runEnded", {
7869
+ sessionId: data.session_id,
7870
+ status: typeof event.payload.status === "string" ? event.payload.status : void 0,
7871
+ queued: false,
7872
+ replayed: data.replayed === true,
7873
+ ...typeof event.payload.chat_run_id === "string" ? { chatRunId: event.payload.chat_run_id } : {}
7874
+ });
7875
+ }
7876
+ }
7877
+ }
7878
+ if (data.session_id) {
7879
+ for (const event of data.events ?? []) {
7880
+ if (event.type === "log:retract") this.replayPlaybackIds.delete(data.session_id);
7881
+ if (event.type === "replay:state") {
7882
+ this.hubEmitter.emit("sessionUpdated", { sessionId: data.session_id, data: event.payload });
7883
+ }
7884
+ const playbackId = event.payload.playback_id;
7885
+ if (typeof playbackId !== "string") continue;
7886
+ if (event.type === "replay:start") {
7887
+ if (this.replayPlaybackIds.get(data.session_id) !== playbackId) {
7888
+ this.replayPlaybackIds.set(data.session_id, playbackId);
7889
+ this.hubEmitter.emit("runStarted", { sessionId: data.session_id });
7890
+ }
7891
+ } else if (event.type === "replay:end") {
7892
+ const current = this.replayPlaybackIds.get(data.session_id);
7893
+ if (current && current !== playbackId) continue;
7894
+ this.replayPlaybackIds.delete(data.session_id);
7895
+ this.hubEmitter.emit("runEnded", {
7896
+ sessionId: data.session_id,
7897
+ status: typeof event.payload.status === "string" ? event.payload.status : "completed",
7898
+ queued: false,
7899
+ replayed: true
7900
+ });
7901
+ }
7902
+ }
7903
+ }
6925
7904
  for (const session of this.route(data.session_id)) {
6926
7905
  session._handleTurnEvents(data.events ?? [], data.replayed === true);
6927
7906
  }
@@ -7039,40 +8018,16 @@ var SessionHub = class {
7039
8018
  (data) => {
7040
8019
  for (const session of this.route(data.session_id)) {
7041
8020
  for (const task of data.tasks ?? []) {
7042
- const taskId = typeof task.id === "string" ? task.id : typeof task.task_id === "string" ? task.task_id : "";
7043
- if (!taskId) continue;
7044
- session._handleBackgroundTask({
7045
- id: taskId,
7046
- command: typeof task.command === "string" ? task.command : "",
7047
- description: typeof task.description === "string" ? task.description : void 0,
7048
- // Pass the server's status through unchanged. Collapsing it here
7049
- // made subscribers see a successful-looking "done" for tasks the
7050
- // REST API correctly reported as cancelled or lost.
7051
- status: normalizeBackgroundStatus(task.status),
7052
- failureReason: typeof task.failure_reason === "string" ? task.failure_reason : void 0,
7053
- exitCode: typeof task.exit_code === "number" || task.exit_code === null ? task.exit_code : void 0,
7054
- elapsedSeconds: typeof task.elapsed_seconds === "number" ? task.elapsed_seconds : 0,
7055
- output: Array.isArray(task.output_preview) && task.output_preview.length > 0 ? task.output_preview.join("\n") : void 0
7056
- });
8021
+ const completed = completedBackgroundTask(task);
8022
+ if (completed) session._handleBackgroundTask(completed);
7057
8023
  }
7058
8024
  }
7059
8025
  }
7060
8026
  );
7061
8027
  }
7062
8028
  routeArtifact(data, kind) {
7063
- const filePath = typeof data.file_path === "string" ? data.file_path : "";
7064
- if (!filePath) return;
7065
8029
  for (const session of this.route(data.session_id)) {
7066
- session._handleWorkspaceFilesChanged(filePath);
7067
- session._handleArtifact({
7068
- filePath,
7069
- contentType: typeof data.content_type === "string" ? data.content_type : "",
7070
- title: typeof data.title === "string" ? data.title : filePath,
7071
- content: typeof data.content === "string" ? data.content : "",
7072
- oldString: typeof data.old_string === "string" ? data.old_string : void 0,
7073
- newString: typeof data.new_string === "string" ? data.new_string : void 0,
7074
- kind
7075
- });
8030
+ session._handleArtifact(data, kind);
7076
8031
  }
7077
8032
  }
7078
8033
  };
@@ -7103,12 +8058,14 @@ function createSocket(options) {
7103
8058
 
7104
8059
  // src/blade-client.ts
7105
8060
  var REFRESH_PATH = "/api/auth/refresh";
7106
- var BladeClient = class {
8061
+ var BladeClient = class _BladeClient {
8062
+ anonymousClients = /* @__PURE__ */ new Map();
8063
+ toolProviders = /* @__PURE__ */ new Set();
7107
8064
  refreshPromise = null;
7108
8065
  socketInstance = null;
7109
8066
  socketReplacementListeners = /* @__PURE__ */ new Set();
7110
8067
  /** login() / setToken() 热挂载的令牌,优先级高于构造参数。 */
7111
- runtimeToken = null;
8068
+ runtimeToken = void 0;
7112
8069
  options;
7113
8070
  auth;
7114
8071
  headless;
@@ -7126,6 +8083,9 @@ var BladeClient = class {
7126
8083
  /** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
7127
8084
  hub;
7128
8085
  constructor(options) {
8086
+ if (options.anonymousSessionId !== void 0 && !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(options.anonymousSessionId)) {
8087
+ throw new Error("\u533F\u540D\u4F1A\u8BDD ID \u5FC5\u987B\u662F UUID v4");
8088
+ }
7129
8089
  this.options = {
7130
8090
  ...options,
7131
8091
  baseUrl: normalizeBaseUrl(options.baseUrl)
@@ -7139,10 +8099,16 @@ var BladeClient = class {
7139
8099
  this.chatGroups = new ChatGroupsResource(this);
7140
8100
  this.siteVisits = new SiteVisitsResource(this);
7141
8101
  this.hub = new SessionHub(this);
7142
- if (this.options.token === void 0) {
7143
- this.runtimeToken = readStoredToken(this.options.baseUrl);
8102
+ if (this.options.token === void 0 && !this.options.anonymousSessionId) {
8103
+ this.runtimeToken = readStoredToken(this.options.baseUrl) ?? void 0;
7144
8104
  }
7145
8105
  }
8106
+ /** Backend application tools, callable over an outbound WebSocket. */
8107
+ connectToolProvider(options) {
8108
+ const provider = new ToolProvider(this, options, () => this.toolProviders.delete(provider));
8109
+ this.toolProviders.add(provider);
8110
+ return provider;
8111
+ }
7146
8112
  /**
7147
8113
  * 弹窗授权登录:用户在弹出的授权页点击"许可授权"后自动取得 PAT,
7148
8114
  * REST 与 Socket.IO 立即生效。只在浏览器可用。
@@ -7162,7 +8128,12 @@ var BladeClient = class {
7162
8128
  const previousToken = this.resolveToken();
7163
8129
  this.runtimeToken = token;
7164
8130
  const nextToken = this.resolveToken();
8131
+ if (token === null) for (const provider of this.toolProviders) provider.dispose();
7165
8132
  if (previousToken !== nextToken) {
8133
+ for (const provider of this.toolProviders) {
8134
+ if (nextToken) provider.reconnect();
8135
+ else provider.dispose();
8136
+ }
7166
8137
  this.hub.disposeAll();
7167
8138
  }
7168
8139
  writeStoredToken(this.options.baseUrl, token, this.options.tokenStorage ?? "local");
@@ -7195,12 +8166,27 @@ var BladeClient = class {
7195
8166
  get token() {
7196
8167
  return this.resolveRestToken();
7197
8168
  }
8169
+ /** 获取只访问指定匿名会话的客户端;创建仍由已认证的原客户端完成。 */
8170
+ forAnonymousSession(sessionId) {
8171
+ let client = this.anonymousClients.get(sessionId);
8172
+ if (!client) {
8173
+ client = new _BladeClient({ baseUrl: this.options.baseUrl, anonymousSessionId: sessionId, fetchImpl: this.options.fetchImpl, commandTransport: this.options.commandTransport });
8174
+ this.anonymousClients.set(sessionId, client);
8175
+ }
8176
+ return client;
8177
+ }
7198
8178
  setBaseUrl(baseUrl) {
7199
8179
  const nextBaseUrl = normalizeBaseUrl(baseUrl);
7200
8180
  if (nextBaseUrl === this.options.baseUrl) {
7201
8181
  return;
7202
8182
  }
8183
+ for (const provider of this.toolProviders) provider.dispose();
7203
8184
  this.hub.disposeAll();
8185
+ for (const client of this.anonymousClients.values()) {
8186
+ client.hub.disposeAll();
8187
+ client.hub.disconnect();
8188
+ }
8189
+ this.anonymousClients.clear();
7204
8190
  this.options.baseUrl = nextBaseUrl;
7205
8191
  if (this.socketInstance) {
7206
8192
  this.socketInstance.removeAllListeners();
@@ -7240,7 +8226,10 @@ var BladeClient = class {
7240
8226
  else resolve(ack ?? {});
7241
8227
  });
7242
8228
  });
7243
- return this.options.commandTransport?.(event, payload, send) ?? send();
8229
+ const transport = this.options.commandTransport;
8230
+ if (!transport) return send();
8231
+ const handled = this.options.anonymousSessionId ? transport(event, payload, send, { anonymousSessionId: this.options.anonymousSessionId }) : transport(event, payload, send);
8232
+ return handled ?? send();
7244
8233
  }
7245
8234
  async json(method, path, body) {
7246
8235
  const response = await this.fetch(method, path, {
@@ -7383,6 +8372,7 @@ var BladeClient = class {
7383
8372
  }
7384
8373
  }
7385
8374
  async tryRefresh() {
8375
+ if (this.options.anonymousSessionId) return false;
7386
8376
  if (this.refreshPromise) {
7387
8377
  return this.refreshPromise;
7388
8378
  }
@@ -7417,14 +8407,16 @@ var BladeClient = class {
7417
8407
  return this.resolveRestToken();
7418
8408
  }
7419
8409
  resolveRestToken() {
8410
+ if (this.options.anonymousSessionId) return `ba-anonymous-${this.options.anonymousSessionId}`;
7420
8411
  return this.resolveToken();
7421
8412
  }
7422
8413
  resolveSocketToken() {
7423
- if (this.runtimeToken) return this.runtimeToken;
8414
+ if (this.options.anonymousSessionId) return `ba-anonymous-${this.options.anonymousSessionId}`;
8415
+ if (this.runtimeToken !== void 0) return this.runtimeToken;
7424
8416
  return resolveTokenOption(this.options.socketToken ?? this.options.token);
7425
8417
  }
7426
8418
  resolveToken() {
7427
- if (this.runtimeToken) return this.runtimeToken;
8419
+ if (this.runtimeToken !== void 0) return this.runtimeToken;
7428
8420
  return resolveTokenOption(this.options.token);
7429
8421
  }
7430
8422
  formDataWithUploadProgress(method, path, form, options, isRetry = false) {
@@ -7522,22 +8514,14 @@ function parseXhrHeaders(rawHeaders) {
7522
8514
  }
7523
8515
 
7524
8516
  export {
8517
+ BladeApiError,
8518
+ ToolProvider,
7525
8519
  SDK_NAME,
7526
8520
  SDK_VERSION,
7527
- BladeApiError,
7528
8521
  isRecord,
7529
- computerState,
7530
- computerOS,
7531
- computerPlatformLabel,
7532
- computerDaemonVersion,
7533
- canToggleComputer,
7534
- sortComputers,
7535
- ComputersResource,
7536
- ModelsResource,
7537
- SessionStatus,
7538
- DEFAULT_REPLAY_SPEED,
7539
- SessionInfo,
7540
- SessionSetupError,
8522
+ normalizePluginRefs,
8523
+ normalizeMessageRefs,
8524
+ normalizeTopicAssetRefs,
7541
8525
  inferToolStatus,
7542
8526
  contextProjectionData,
7543
8527
  getContextDisplayState,
@@ -7555,6 +8539,18 @@ export {
7555
8539
  isAppDevToolUiCard,
7556
8540
  collectInlineToolUiCards,
7557
8541
  collectPreviewToolUiCards,
8542
+ computerState,
8543
+ computerOS,
8544
+ computerPlatformLabel,
8545
+ computerDaemonVersion,
8546
+ canToggleComputer,
8547
+ sortComputers,
8548
+ ComputersResource,
8549
+ ModelsResource,
8550
+ SessionStatus,
8551
+ DEFAULT_REPLAY_SPEED,
8552
+ SessionInfo,
8553
+ SessionSetupError,
7558
8554
  isEntryGoneError,
7559
8555
  PluginsResource,
7560
8556
  ChatGroupsResource,
@@ -7604,4 +8600,4 @@ export {
7604
8600
  createSocket,
7605
8601
  BladeClient
7606
8602
  };
7607
- //# sourceMappingURL=chunk-PWJJJATA.js.map
8603
+ //# sourceMappingURL=chunk-2VIJBVAC.js.map