@blade-hq/agent-client 2612.0.0-beta.3 → 2612.0.0-beta.30

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 (96) hide show
  1. package/README.md +229 -28
  2. package/dist/application-client.d.ts +19 -0
  3. package/dist/blade-client.d.ts +18 -9
  4. package/dist/{chunk-6PM3EDZ7.js → chunk-FTGPJ7VW.js} +2387 -987
  5. package/dist/chunk-FTGPJ7VW.js.map +1 -0
  6. package/dist/index.d.ts +14 -3
  7. package/dist/index.js +13 -1
  8. package/dist/internal.d.ts +10 -4
  9. package/dist/internal.js +17 -3
  10. package/dist/internal.js.map +1 -1
  11. package/dist/resources/auth.d.ts +3 -1
  12. package/dist/resources/chat-groups.d.ts +92 -0
  13. package/dist/resources/computers.d.ts +1 -6
  14. package/dist/resources/headless.d.ts +5 -0
  15. package/dist/resources/models.d.ts +6 -1
  16. package/dist/resources/plugins.d.ts +1 -0
  17. package/dist/resources/sandbox.d.ts +19 -0
  18. package/dist/resources/sessions.d.ts +85 -15
  19. package/dist/schemas/context-attachments.d.ts +18 -0
  20. package/dist/schemas/event.d.ts +2 -0
  21. package/dist/schemas/message-utils.d.ts +3 -9
  22. package/dist/schemas/message.d.ts +18 -1
  23. package/dist/schemas/plugin-refs.d.ts +15 -0
  24. package/dist/schemas/projection.d.ts +31 -0
  25. package/dist/schemas/session.d.ts +2 -48
  26. package/dist/schemas/topic-assets.d.ts +7 -0
  27. package/dist/server-clock.d.ts +2 -0
  28. package/dist/session/agent-session.d.ts +51 -98
  29. package/dist/session/background.d.ts +14 -0
  30. package/dist/session/events.d.ts +0 -1
  31. package/dist/session/hub.d.ts +3 -24
  32. package/dist/session/log-cursor.d.ts +14 -0
  33. package/dist/session/state.d.ts +2 -1
  34. package/dist/session/workflow.d.ts +10 -3
  35. package/dist/shared/projection/builder.d.ts +5 -2
  36. package/dist/shared/projection/helpers.d.ts +2 -0
  37. package/dist/shared/projection/index.d.ts +1 -0
  38. package/dist/shared/projection/workflow.d.ts +24 -0
  39. package/dist/tool-provider.d.ts +51 -0
  40. package/dist/types/socket-events.d.ts +183 -8
  41. package/dist/version.d.ts +0 -1
  42. package/node_modules/engine.io-client/build/cjs/globals.node.d.ts +2 -3
  43. package/node_modules/engine.io-client/build/cjs/index.d.ts +3 -3
  44. package/node_modules/engine.io-client/build/cjs/index.js +1 -4
  45. package/node_modules/engine.io-client/build/cjs/socket.d.ts +39 -82
  46. package/node_modules/engine.io-client/build/cjs/socket.js +151 -218
  47. package/node_modules/engine.io-client/build/cjs/transport.d.ts +1 -1
  48. package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.d.ts +4 -4
  49. package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.js +2 -2
  50. package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.node.js +17 -7
  51. package/node_modules/engine.io-client/build/cjs/transports/websocket.node.js +17 -7
  52. package/node_modules/engine.io-client/build/esm/globals.node.d.ts +2 -3
  53. package/node_modules/engine.io-client/build/esm/index.d.ts +3 -3
  54. package/node_modules/engine.io-client/build/esm/index.js +1 -2
  55. package/node_modules/engine.io-client/build/esm/socket.d.ts +39 -82
  56. package/node_modules/engine.io-client/build/esm/socket.js +141 -206
  57. package/node_modules/engine.io-client/build/esm/transport.d.ts +1 -1
  58. package/node_modules/engine.io-client/build/esm/transports/polling-xhr.d.ts +4 -4
  59. package/node_modules/engine.io-client/build/esm/transports/polling-xhr.js +2 -2
  60. package/node_modules/engine.io-client/build/esm-debug/globals.node.d.ts +2 -3
  61. package/node_modules/engine.io-client/build/esm-debug/index.d.ts +3 -3
  62. package/node_modules/engine.io-client/build/esm-debug/index.js +1 -2
  63. package/node_modules/engine.io-client/build/esm-debug/socket.d.ts +39 -82
  64. package/node_modules/engine.io-client/build/esm-debug/socket.js +150 -215
  65. package/node_modules/engine.io-client/build/esm-debug/transport.d.ts +1 -1
  66. package/node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.d.ts +4 -4
  67. package/node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.js +2 -2
  68. package/node_modules/engine.io-client/dist/engine.io.esm.min.js +3 -3
  69. package/node_modules/engine.io-client/dist/engine.io.esm.min.js.map +1 -1
  70. package/node_modules/engine.io-client/dist/engine.io.js +164 -239
  71. package/node_modules/engine.io-client/dist/engine.io.js.map +1 -1
  72. package/node_modules/engine.io-client/dist/engine.io.min.js +3 -3
  73. package/node_modules/engine.io-client/dist/engine.io.min.js.map +1 -1
  74. package/node_modules/engine.io-client/package.json +2 -2
  75. package/node_modules/socket.io-parser/build/cjs/binary.js +4 -1
  76. package/node_modules/socket.io-parser/build/cjs/index.d.ts +14 -4
  77. package/node_modules/socket.io-parser/build/cjs/index.js +18 -11
  78. package/node_modules/socket.io-parser/build/esm/binary.js +4 -1
  79. package/node_modules/socket.io-parser/build/esm/index.d.ts +14 -4
  80. package/node_modules/socket.io-parser/build/esm/index.js +14 -10
  81. package/node_modules/socket.io-parser/build/esm-debug/binary.js +4 -1
  82. package/node_modules/socket.io-parser/build/esm-debug/index.d.ts +14 -4
  83. package/node_modules/socket.io-parser/build/esm-debug/index.js +14 -10
  84. package/node_modules/socket.io-parser/package.json +1 -1
  85. package/node_modules/ws/index.js +15 -6
  86. package/node_modules/ws/lib/constants.js +1 -0
  87. package/node_modules/ws/lib/permessage-deflate.js +9 -7
  88. package/node_modules/ws/lib/receiver.js +37 -0
  89. package/node_modules/ws/lib/sender.js +6 -1
  90. package/node_modules/ws/lib/websocket-server.js +18 -6
  91. package/node_modules/ws/lib/websocket.js +50 -17
  92. package/node_modules/ws/package.json +8 -3
  93. package/node_modules/ws/wrapper.mjs +14 -1
  94. package/package.json +4 -4
  95. package/public-api.md +356 -38
  96. package/dist/chunk-6PM3EDZ7.js.map +0 -1
@@ -1,6 +1,230 @@
1
+ // src/resources/sandbox.ts
2
+ var SandboxResource = class {
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ client;
7
+ execute(input) {
8
+ return this.client.json("POST", "/api/sandbox/execute", input);
9
+ }
10
+ };
11
+
12
+ // src/rest.ts
13
+ var DOCS_URL = "https://blade-hq.github.io/public-skills/";
14
+ var BladeApiError = class extends Error {
15
+ response;
16
+ status;
17
+ statusText;
18
+ constructor(response, message) {
19
+ super(message ?? `\u8BF7\u6C42\u5931\u8D25\uFF08${response.status} ${response.statusText}\uFF09`);
20
+ this.name = "BladeApiError";
21
+ this.response = response;
22
+ this.status = response.status;
23
+ this.statusText = response.statusText;
24
+ }
25
+ };
26
+ async function extractErrorDetail(response) {
27
+ try {
28
+ const cloned = response.clone();
29
+ const contentType = cloned.headers.get("content-type") ?? "";
30
+ if (contentType.includes("application/json")) {
31
+ const data = await cloned.json();
32
+ const detail = data?.detail;
33
+ if (typeof detail === "string" && detail.trim()) return detail;
34
+ if (Array.isArray(detail) && detail.length > 0) {
35
+ const first = detail[0];
36
+ if (first && typeof first.msg === "string") return first.msg;
37
+ }
38
+ } else {
39
+ const text = (await cloned.text()).trim();
40
+ if (text) return text;
41
+ }
42
+ } catch {
43
+ }
44
+ return void 0;
45
+ }
46
+ function decorateAuthError(error) {
47
+ if (!(error instanceof BladeApiError)) return error;
48
+ if (error.status === 401) {
49
+ 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`;
50
+ } else if (error.status === 403) {
51
+ 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`;
52
+ }
53
+ return error;
54
+ }
55
+ function buildBackendUrl(baseUrl, path) {
56
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
57
+ return new URL(path.startsWith("/") ? path.slice(1) : path, base);
58
+ }
59
+
60
+ // src/tool-provider.ts
61
+ var ToolProvider = class {
62
+ constructor(client, options, onDispose) {
63
+ this.client = client;
64
+ this.options = options;
65
+ this.onDispose = onDispose;
66
+ 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");
67
+ 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");
68
+ this.url = `blade-rpc://${options.providerId}`;
69
+ this.open();
70
+ }
71
+ client;
72
+ options;
73
+ onDispose;
74
+ url;
75
+ socket;
76
+ timer;
77
+ registrationTimer;
78
+ disposed = false;
79
+ registered = false;
80
+ attempts = 0;
81
+ calls = /* @__PURE__ */ new Map();
82
+ waiters = /* @__PURE__ */ new Set();
83
+ /** Resolves only after BA has authenticated and registered this connection. */
84
+ ready() {
85
+ if (this.disposed) return Promise.reject(new Error("Tool provider disposed"));
86
+ if (this.registered) return Promise.resolve();
87
+ return new Promise((resolve, reject) => {
88
+ const timer = setTimeout(() => {
89
+ this.waiters.delete(waiter);
90
+ reject(new Error("Tool provider registration timed out"));
91
+ }, 25e3);
92
+ const waiter = { resolve: () => {
93
+ clearTimeout(timer);
94
+ resolve();
95
+ }, reject: (e) => {
96
+ clearTimeout(timer);
97
+ reject(e);
98
+ } };
99
+ this.waiters.add(waiter);
100
+ });
101
+ }
102
+ reconnect() {
103
+ if (this.disposed) return;
104
+ clearTimeout(this.timer);
105
+ this.reset();
106
+ this.open();
107
+ }
108
+ reset() {
109
+ clearTimeout(this.registrationTimer);
110
+ this.registered = false;
111
+ const socket = this.socket;
112
+ this.socket = void 0;
113
+ socket?.close();
114
+ for (const controller of this.calls.values()) controller.abort();
115
+ this.calls.clear();
116
+ }
117
+ fail(error) {
118
+ for (const waiter of this.waiters) waiter.reject(error);
119
+ this.waiters.clear();
120
+ try {
121
+ this.options.onError?.(error);
122
+ } catch {
123
+ }
124
+ }
125
+ open() {
126
+ if (this.disposed) return;
127
+ const endpoint = buildBackendUrl(this.client.options.baseUrl, "/api/app-tools/connect");
128
+ endpoint.protocol = endpoint.protocol === "https:" ? "wss:" : "ws:";
129
+ let socket;
130
+ try {
131
+ const token = this.client.token;
132
+ if (!token) throw new Error("Tool provider requires a token");
133
+ const encoded = btoa(Array.from(new TextEncoder().encode(token), (byte) => String.fromCharCode(byte)).join("")).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
134
+ socket = this.client.options.applicationTransport?.provider() ?? new WebSocket(endpoint, ["blade-rpc", `blade-bearer.${encoded}`]);
135
+ } catch {
136
+ this.fail(new Error("Tool provider WebSocket unavailable"));
137
+ this.schedule();
138
+ return;
139
+ }
140
+ this.socket = socket;
141
+ this.registrationTimer = setTimeout(() => {
142
+ if (this.socket === socket) {
143
+ this.fail(new Error("Tool provider registration timed out"));
144
+ socket.close();
145
+ }
146
+ }, 15e3);
147
+ socket.addEventListener("open", () => {
148
+ if (this.socket !== socket) return;
149
+ socket.send(JSON.stringify({ jsonrpc: "2.0", id: "register", method: "provider/register", params: { providerId: this.options.providerId, capability: this.options.capability } }));
150
+ });
151
+ socket.addEventListener("message", (event) => {
152
+ if (this.socket !== socket || typeof event.data !== "string") return;
153
+ void this.receive(socket, event.data).catch(() => {
154
+ this.fail(new Error("Invalid application tool RPC message"));
155
+ socket.close();
156
+ });
157
+ });
158
+ socket.addEventListener("error", () => {
159
+ if (this.socket === socket) this.fail(new Error("Application tool connection failed"));
160
+ });
161
+ socket.addEventListener("close", () => {
162
+ if (this.socket !== socket) return;
163
+ this.reset();
164
+ this.fail(new Error("Application tool connection closed; unfinished calls will not be replayed"));
165
+ this.schedule();
166
+ });
167
+ }
168
+ schedule() {
169
+ if (this.disposed) return;
170
+ this.timer = setTimeout(() => this.open(), Math.min(1e4, 500 * 2 ** Math.min(this.attempts++, 5)));
171
+ }
172
+ async receive(socket, text) {
173
+ const message = JSON.parse(text);
174
+ if (message.jsonrpc !== "2.0") throw new Error("Invalid JSON-RPC envelope");
175
+ if (message.id === "register" && !message.method) {
176
+ clearTimeout(this.registrationTimer);
177
+ if (message.error || !message.result?.registered) {
178
+ this.fail(new Error(message.error?.message ?? "Tool provider registration rejected"));
179
+ socket.close();
180
+ return;
181
+ }
182
+ this.registered = true;
183
+ this.attempts = 0;
184
+ for (const waiter of this.waiters) waiter.resolve();
185
+ this.waiters.clear();
186
+ return;
187
+ }
188
+ if (!this.registered) throw new Error("Provider not registered");
189
+ if (message.method === "notifications/cancelled") {
190
+ this.calls.get(String(message.params?.requestId))?.abort();
191
+ return;
192
+ }
193
+ if (!message.id || typeof message.id !== "string" || this.calls.has(message.id)) throw new Error("Invalid RPC request ID");
194
+ const controller = new AbortController();
195
+ this.calls.set(message.id, controller);
196
+ let response = { jsonrpc: "2.0", id: message.id };
197
+ try {
198
+ if (message.method === "tools/list") {
199
+ response.result = { tools: this.options.tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })) };
200
+ } else if (message.method === "tools/call") {
201
+ const params = message.params;
202
+ const tool = this.options.tools.find((t) => t.name === params?.name);
203
+ 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");
204
+ response.result = await tool.handler(params.arguments, { sessionId: params.sessionId, signal: controller.signal });
205
+ } else {
206
+ response.error = { code: -32601, message: "Method not found" };
207
+ }
208
+ } catch {
209
+ response = { jsonrpc: "2.0", id: message.id, error: { code: -32e3, message: "Application tool execution failed" } };
210
+ } finally {
211
+ this.calls.delete(message.id);
212
+ }
213
+ if (this.socket === socket && socket.readyState === WebSocket.OPEN && !controller.signal.aborted) socket.send(JSON.stringify(response));
214
+ }
215
+ dispose() {
216
+ if (this.disposed) return;
217
+ this.disposed = true;
218
+ clearTimeout(this.timer);
219
+ this.reset();
220
+ this.fail(new Error("Tool provider disposed"));
221
+ this.onDispose();
222
+ }
223
+ };
224
+
1
225
  // src/version.ts
2
226
  var SDK_NAME = "agent-client";
3
- var SDK_VERSION = true ? "2612.0.0-beta.3" : "1.1.1";
227
+ var SDK_VERSION = true ? "2612.0.0-beta.30" : "1.1.1";
4
228
 
5
229
  // src/auth-login.ts
6
230
  function isAuthCallbackMessage(value) {
@@ -128,54 +352,6 @@ async function loginWithPopup(client, options = {}) {
128
352
  });
129
353
  }
130
354
 
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
355
  // src/resources/auth.ts
180
356
  var AuthResource = class {
181
357
  constructor(client) {
@@ -214,8 +390,8 @@ var AuthResource = class {
214
390
  getProviders() {
215
391
  return this.client.json("GET", "/api/auth/providers");
216
392
  }
217
- getMe() {
218
- return this.client.json("GET", "/api/auth/me");
393
+ getMe(options = {}) {
394
+ return this.client.jsonFromInit("/api/auth/me", { signal: options.signal });
219
395
  }
220
396
  logout() {
221
397
  return this.client.json("POST", "/api/auth/logout");
@@ -227,404 +403,168 @@ function isRecord(value) {
227
403
  return typeof value === "object" && value !== null && !Array.isArray(value);
228
404
  }
229
405
 
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";
406
+ // src/session/log-cursor.ts
407
+ function positive(value) {
408
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
409
+ }
410
+ var LogCursor = class {
411
+ sequence = 0;
412
+ epoch = 0;
413
+ deltas = /* @__PURE__ */ new Map();
414
+ subscription() {
415
+ return this.sequence > 0 ? { after_log_seq: this.sequence } : {};
416
+ }
417
+ consume(events) {
418
+ const accepted = [];
419
+ let reload = false;
420
+ for (const event of events) {
421
+ if (event.type === "log:retract") {
422
+ this.deltas.clear();
423
+ reload = true;
424
+ continue;
425
+ }
426
+ if (event.type === "log:record") {
427
+ if (!positive(event.log_seq) || event.log_seq <= this.sequence) continue;
428
+ const nested = event.payload.events;
429
+ if (!Array.isArray(nested)) continue;
430
+ if (positive(event.payload.epoch) && event.payload.epoch !== this.epoch) {
431
+ this.epoch = event.payload.epoch;
432
+ this.deltas.clear();
433
+ reload = true;
434
+ }
435
+ accepted.push(...nested);
436
+ if (["step_end", "run_end", "truncate"].includes(String(event.payload.kind))) {
437
+ for (const step of this.deltas.keys()) if (step <= event.log_seq) this.deltas.delete(step);
438
+ }
439
+ this.sequence = event.log_seq;
440
+ continue;
441
+ }
442
+ if (event.type === "llm:stream:reset" && positive(event.step_log_seq)) {
443
+ this.deltas.delete(event.step_log_seq);
444
+ }
445
+ if (positive(event.step_log_seq) && positive(event.delta_seq)) {
446
+ if (event.delta_seq <= (this.deltas.get(event.step_log_seq) ?? 0)) continue;
447
+ this.deltas.set(event.step_log_seq, event.delta_seq);
448
+ }
449
+ if (event.type === "log:delta" && Array.isArray(event.payload.events)) {
450
+ accepted.push(...event.payload.events);
451
+ } else {
452
+ accepted.push(event);
453
+ }
454
+ }
455
+ return { events: accepted, reload };
236
456
  }
237
- detail;
238
457
  };
239
- var SYSTEM_ERROR_CHAT_END_GRACE_MS = 1e3;
240
- var HeadlessResource = class {
241
- constructor(client) {
242
- this.client = client;
458
+
459
+ // src/schemas/context-attachments.ts
460
+ function snapshotContextAttachments(value) {
461
+ const snapshot2 = JSON.parse(JSON.stringify(value));
462
+ if (!Array.isArray(snapshot2) || snapshot2.length > 20) throw new Error("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u63D0\u4F9B 20 \u4EFD\u8D44\u6599");
463
+ const ids = /* @__PURE__ */ new Set();
464
+ const validText = (text, limit) => typeof text === "string" && [...text].length > 0 && [...text].length <= limit;
465
+ for (const item of snapshot2) {
466
+ 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");
467
+ ids.add(item.id);
468
+ if (item.update_format !== void 0 && item.update_format !== "snapshot" && item.update_format !== "diff") throw new Error("\u8D44\u6599\u66F4\u65B0\u683C\u5F0F\u65E0\u6548");
469
+ 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");
470
+ if (item.files != null && (!Array.isArray(item.files) || item.files.length > 20)) throw new Error("\u8D44\u6599\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF");
471
+ for (const file of item.files ?? []) {
472
+ if (!validText(file.name, 512)) throw new Error("\u8D44\u6599\u6587\u4EF6\u540D\u683C\u5F0F\u9519\u8BEF");
473
+ if (file.session_id == null && file.path == null) continue;
474
+ 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");
475
+ }
476
+ }
477
+ 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");
478
+ return snapshot2;
479
+ }
480
+ function normalizeContextAttachments(value) {
481
+ return Array.isArray(value) ? value : [];
482
+ }
483
+
484
+ // src/schemas/plugin-refs.ts
485
+ function normalizePluginRefs(value) {
486
+ if (!Array.isArray(value)) return [];
487
+ const seen = /* @__PURE__ */ new Set();
488
+ return value.flatMap((item) => {
489
+ if (!item || typeof item !== "object" || item.provider !== "plugin" || ![item.plugin, item.server, item.id, item.label].every((part) => typeof part === "string" && part)) return [];
490
+ const key = JSON.stringify([item.plugin, item.server, item.id]);
491
+ if (seen.has(key)) return [];
492
+ seen.add(key);
493
+ return [{ provider: "plugin", plugin: item.plugin, server: item.server, id: item.id, label: item.label }];
494
+ });
495
+ }
496
+ function normalizeMessageRefs(value) {
497
+ if (!Array.isArray(value)) return [];
498
+ const seen = /* @__PURE__ */ new Set();
499
+ return value.flatMap((item) => {
500
+ if (!item || typeof item !== "object") return [];
501
+ 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];
502
+ if (!ref) return [];
503
+ const key = ref.provider === "skill" ? JSON.stringify([ref.provider, ref.id]) : JSON.stringify([ref.provider, ref.plugin, ref.server, ref.id]);
504
+ if (seen.has(key)) return [];
505
+ seen.add(key);
506
+ return [ref];
507
+ });
508
+ }
509
+
510
+ // src/schemas/topic-assets.ts
511
+ function normalizeTopicAssetRefs(value) {
512
+ if (!Array.isArray(value)) return [];
513
+ const seen = /* @__PURE__ */ new Set();
514
+ return value.flatMap((item) => {
515
+ if (!item || typeof item !== "object" || typeof item.topic_id !== "string" || !item.topic_id || typeof item.path !== "string" || !item.path) return [];
516
+ const key = JSON.stringify([item.topic_id, item.path]);
517
+ if (seen.has(key)) return [];
518
+ seen.add(key);
519
+ return [{ topic_id: item.topic_id, path: item.path }];
520
+ });
521
+ }
522
+
523
+ // src/shared/projection/helpers.ts
524
+ function upsertToolCall(state, toolCallId, toolName, displayName, args) {
525
+ if (!toolCallId) return;
526
+ let found = false;
527
+ for (let i = 0; i < state.toolCalls.length; i++) {
528
+ if (state.toolCalls[i].id !== toolCallId) continue;
529
+ const tc = state.toolCalls[i];
530
+ state.toolCalls[i] = {
531
+ ...tc,
532
+ tool_name: toolName || tc.tool_name,
533
+ display_name: displayName || tc.display_name,
534
+ arguments: args || tc.arguments
535
+ };
536
+ found = true;
537
+ break;
243
538
  }
244
- client;
245
- async run(prompt, options = {}) {
246
- const { session_id } = await this.client.sessions.createSessionWithRequest({
247
- intent: prompt,
248
- model: options.model ?? null
539
+ if (!found) {
540
+ state.toolCalls.push({
541
+ id: toolCallId,
542
+ tool_name: toolName,
543
+ display_name: displayName,
544
+ arguments: args,
545
+ status: "pending"
249
546
  });
250
- return this.runTracked(session_id, prompt, options, false);
251
547
  }
252
- async runInSession(sessionId, prompt, options = {}) {
253
- return this.runTracked(sessionId, prompt, options, false);
548
+ let blockFound = false;
549
+ for (let i = 0; i < state.blocks.length; i++) {
550
+ const b = state.blocks[i];
551
+ if (b.type !== "tool_use" || b.tool_call_id !== toolCallId) continue;
552
+ state.blocks[i] = {
553
+ ...b,
554
+ content: args || b.content,
555
+ tool_name: toolName || b.tool_name,
556
+ display_name: displayName || b.display_name
557
+ };
558
+ blockFound = true;
559
+ break;
254
560
  }
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
561
+ if (!blockFound) {
562
+ state.blocks.push({
563
+ type: "tool_use",
564
+ content: args,
565
+ tool_call_id: toolCallId,
566
+ tool_name: toolName || null,
567
+ display_name: displayName || null
628
568
  });
629
569
  }
630
570
  }
@@ -941,14 +881,14 @@ function applyAskUserPauseToTurn(turn, pauseToolData, toolCallId, childLoopName,
941
881
  }
942
882
  function spacedJson(value) {
943
883
  try {
944
- const encode = (item) => {
945
- if (Array.isArray(item)) return `[${item.map(encode).join(", ")}]`;
884
+ const encode2 = (item) => {
885
+ if (Array.isArray(item)) return `[${item.map(encode2).join(", ")}]`;
946
886
  if (item && typeof item === "object") {
947
- return `{${Object.entries(item).map(([key, part]) => `${JSON.stringify(key)}: ${encode(part)}`).join(", ")}}`;
887
+ return `{${Object.entries(item).map(([key, part]) => `${JSON.stringify(key)}: ${encode2(part)}`).join(", ")}}`;
948
888
  }
949
889
  return JSON.stringify(item) ?? "null";
950
890
  };
951
- return encode(JSON.parse(value));
891
+ return encode2(JSON.parse(value));
952
892
  } catch {
953
893
  return value;
954
894
  }
@@ -1115,6 +1055,10 @@ function normalizeChildPause(pausePayload) {
1115
1055
  }
1116
1056
  return { pauseToolData, childLoopName, childToolCallId, description };
1117
1057
  }
1058
+ function attachToolEvidence(call, evidence) {
1059
+ if (!call) return;
1060
+ call.evidence = call.status === "done" && Array.isArray(evidence) ? evidence : void 0;
1061
+ }
1118
1062
 
1119
1063
  // src/schemas/message.ts
1120
1064
  function inferToolStatus(resultStr) {
@@ -1173,8 +1117,8 @@ function snapshot(state, sequence, status) {
1173
1117
  first_token_ms: state.firstTokenMs,
1174
1118
  stream_duration_ms: state.streamDurationMs,
1175
1119
  first_token_delivery_ms: state.firstTokenDeliveryMs,
1176
- started_at: state.modelStartedAt ?? new Date(state.startedAt).toISOString(),
1177
- // 没有 payload 上报时用的是 loop:turn 的浏览器事件时间,标记出来供瀑布区分。
1120
+ started_at: state.modelStartedAt,
1121
+ // 未上报时不伪造服务器起点;浏览器时钟不能与服务器时钟混用。
1178
1122
  has_reported_timing: state.modelStartedAtReported,
1179
1123
  context_window: state.contextWindow,
1180
1124
  memory_refs: state.memoryRefs,
@@ -1316,7 +1260,7 @@ function acknowledgeTurnEvents(acknowledge) {
1316
1260
  }
1317
1261
  function hasChatRunEvent(events) {
1318
1262
  return events.some(
1319
- (event) => event.type !== "llm:first_token:delivered" && event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
1263
+ (event) => event.type !== "llm:first_token:delivered" && event.type !== "session:post_chat_followup" && event.type !== "loop:tool_bridge" && !event.type.startsWith("compaction:")
1320
1264
  );
1321
1265
  }
1322
1266
  function acceptedPostChatFollowupCompletesLatestRun(events, updates) {
@@ -1400,10 +1344,8 @@ var ClientProjectionBuilder = class {
1400
1344
  latestTurns = /* @__PURE__ */ new Map();
1401
1345
  turnsById = /* @__PURE__ */ new Map();
1402
1346
  loopDescriptions = /* @__PURE__ */ new Map();
1403
- /** loop_name → 正在运行的子智能体起点,agent:end 结算并挂到父工具调用上。 */
1404
1347
  activeSubagentRuns = /* @__PURE__ */ new Map();
1405
1348
  pendingChildPauses = /* @__PURE__ */ new Map();
1406
- /** 作答后子回路续跑,最终回复就是随后落盘的父 Agent 工具结果。 */
1407
1349
  answeredChildParents = /* @__PURE__ */ new Map();
1408
1350
  pendingMemoryRefs = /* @__PURE__ */ new Map();
1409
1351
  activeCompactions = /* @__PURE__ */ new Map();
@@ -1556,6 +1498,19 @@ var ClientProjectionBuilder = class {
1556
1498
  this.seq = 0;
1557
1499
  this.syntheticCounter = 0;
1558
1500
  }
1501
+ /** A durable snapshot supersedes only this turn's temporary overlay. */
1502
+ acceptDurableTurn(turn) {
1503
+ this.seedSequence(turn.sequence);
1504
+ if (!isConversationTurn(turn)) return;
1505
+ const active = this.activeTurns.get(turn.loop_id);
1506
+ if (active?.turnId === turn.turn_id) this.activeTurns.delete(turn.loop_id);
1507
+ this.turnsById.set(turn.turn_id, turn);
1508
+ const latest = this.latestTurns.get(turn.loop_id);
1509
+ if ((!active || active.turnId === turn.turn_id) && (!latest || latest.turn_id === turn.turn_id || turn.sequence >= latest.sequence)) {
1510
+ this.latestTurns.set(turn.loop_id, turn);
1511
+ this.lastTurnIds.set(turn.loop_id, turn.turn_id);
1512
+ }
1513
+ }
1559
1514
  seedSequence(maxSeq) {
1560
1515
  if (maxSeq > this.seq) {
1561
1516
  this.seq = maxSeq;
@@ -1576,7 +1531,6 @@ var ClientProjectionBuilder = class {
1576
1531
  this.latestTurns = latestTurns;
1577
1532
  this.turnsById = turnsById;
1578
1533
  }
1579
- // --- Event handlers ---
1580
1534
  onMemoryInject(loopId, payload) {
1581
1535
  const refs = payload.memory_refs;
1582
1536
  const turnId = String(payload.turn_id ?? "");
@@ -1802,6 +1756,7 @@ var ClientProjectionBuilder = class {
1802
1756
  onUserMessage(_loopId, payload) {
1803
1757
  const entryId = String(payload.entry_id ?? this.nextTurnId("user"));
1804
1758
  const displayContent = payload.display_content ?? payload.content ?? "";
1759
+ const refs = normalizeMessageRefs(payload.refs);
1805
1760
  const turn = {
1806
1761
  id: entryId,
1807
1762
  sequence: this.nextSeq(),
@@ -1811,6 +1766,9 @@ var ClientProjectionBuilder = class {
1811
1766
  role: "user",
1812
1767
  status: "completed",
1813
1768
  blocks: [{ type: "text", content: displayContent }],
1769
+ ...refs.length ? { refs } : {},
1770
+ context_attachments: normalizeContextAttachments(payload.context_attachments),
1771
+ topic_asset_refs: normalizeTopicAssetRefs(payload.topic_asset_refs),
1814
1772
  tool_calls: [],
1815
1773
  model: null,
1816
1774
  usage: null,
@@ -1838,7 +1796,6 @@ var ClientProjectionBuilder = class {
1838
1796
  this.latestTurns.set("root", turn);
1839
1797
  return [{ kind: "upsert", turn }];
1840
1798
  }
1841
- // --- Delta / streaming events ---
1842
1799
  onDelta(state, blockType, _toolCallId, content) {
1843
1800
  if (!content) return null;
1844
1801
  appendTextBlock(state, blockType, content);
@@ -1890,10 +1847,7 @@ var ClientProjectionBuilder = class {
1890
1847
  if (!applyResponseBlockOrder(state, payload.response_blocks) && "content" in payload) {
1891
1848
  replaceTextContent(state, completedResponseText(payload.content));
1892
1849
  }
1893
- const isNativeProviderResponse = typeof payload.provider === "string" && payload.provider !== "";
1894
- if (state.toolCalls.length === 0 || isNativeProviderResponse && state.toolCalls.every(
1895
- (toolCall) => toolCall.status !== "pending" && toolCall.status !== "awaiting_answer"
1896
- )) {
1850
+ if (state.toolCalls.length === 0) {
1897
1851
  const childParent = this.answeredChildParents.get(loopId);
1898
1852
  if (childParent) {
1899
1853
  this.answeredChildParents.delete(loopId);
@@ -1910,6 +1864,7 @@ var ClientProjectionBuilder = class {
1910
1864
  return this.syncPatch(state);
1911
1865
  }
1912
1866
  applyFirstTokenDelivery(state, payload) {
1867
+ this.applyStreamTiming(state, payload);
1913
1868
  const firstTokenMs = payload.first_token_ms;
1914
1869
  if (state.firstTokenMs === null && typeof firstTokenMs === "number" && firstTokenMs >= 0) {
1915
1870
  state.firstTokenMs = Math.round(firstTokenMs);
@@ -1938,13 +1893,14 @@ var ClientProjectionBuilder = class {
1938
1893
  const toolCallId = String(payload.tool_call_id ?? "");
1939
1894
  const streamedResult = state.toolCalls.find((toolCall) => toolCall.id === toolCallId)?.result;
1940
1895
  const content = payload.content_from_deltas === true ? streamedResult ?? "" : payload.content ?? "";
1941
- const status = payload.is_error === true ? "error" : toolStatusFromResult(content);
1896
+ const status = payload.is_error === true ? "error" : payload._tool_pending === true ? "pending" : toolStatusFromResult(content);
1942
1897
  const rawDuration = payload.duration_ms;
1943
1898
  const durationMs = typeof rawDuration === "number" && rawDuration >= 0 ? Math.round(rawDuration) : null;
1944
1899
  const ranInParallel = typeof payload.ran_in_parallel === "boolean" ? payload.ran_in_parallel : null;
1945
1900
  const sl = sourceLoopFor(state.loopId, this.loopDescriptions);
1946
1901
  const startedAt = typeof payload.started_at === "string" && payload.started_at ? payload.started_at : null;
1947
1902
  applyToolResult(state, toolCallId, content, status, durationMs, ranInParallel, sl, startedAt);
1903
+ attachToolEvidence(state.toolCalls.find((call) => call.id === toolCallId), payload.evidence);
1948
1904
  const pendingChild = [...this.pendingChildPauses.values()].find(
1949
1905
  (pause) => pause.parentLoopName === loopId && pause.parentToolCallId === toolCallId
1950
1906
  );
@@ -1958,8 +1914,7 @@ var ClientProjectionBuilder = class {
1958
1914
  this.displayNameResolver
1959
1915
  );
1960
1916
  }
1961
- const isNativeProviderResult = typeof payload.provider === "string" && payload.provider !== "";
1962
- if (!isNativeProviderResult && this.shouldFinalizeAfterToolResults(state)) {
1917
+ if (this.shouldFinalizeAfterToolResults(state)) {
1963
1918
  return this.finalize(loopId, "completed");
1964
1919
  }
1965
1920
  return this.syncPatch(state);
@@ -2218,7 +2173,6 @@ var ClientProjectionBuilder = class {
2218
2173
  const turn = buildCompactionTurn(this.nextSeq(), compactionId, loopId, status, current);
2219
2174
  return [{ kind: "upsert", turn }];
2220
2175
  }
2221
- // --- Internal helpers ---
2222
2176
  syncPatch(state) {
2223
2177
  const turn = snapshot(state, this.nextSeq(), "streaming");
2224
2178
  this.latestTurns.set(state.loopId, turn);
@@ -2398,7 +2352,6 @@ function projectHistory(entries) {
2398
2352
  const pendingChildPauses = /* @__PURE__ */ new Map();
2399
2353
  const childPauseEntries = [];
2400
2354
  const answeredToolCallIds = /* @__PURE__ */ new Set();
2401
- const nativeToolTurns = /* @__PURE__ */ new Set();
2402
2355
  const turnEntryAliases = /* @__PURE__ */ new Map();
2403
2356
  let latestRootMessage = null;
2404
2357
  let latestRootAssistant = null;
@@ -2464,19 +2417,18 @@ function projectHistory(entries) {
2464
2417
  const owner = toolOwners.get(toolCallId);
2465
2418
  const toolCall = owner?.tool_calls.find((item) => item.id === toolCallId);
2466
2419
  if (owner && toolCall) {
2467
- toolCall.status = message._error ? "error" : inferToolStatus(String(message.content ?? ""));
2420
+ toolCall.status = message._error ? "error" : message._tool_pending === true ? "pending" : inferToolStatus(String(message.content ?? ""));
2468
2421
  toolCall.result = message.content ?? "";
2422
+ attachToolEvidence(toolCall, message._evidence);
2469
2423
  const duration = numberOrNull(message._duration_ms);
2470
2424
  if (duration !== null) toolCall.duration_ms = duration;
2471
2425
  if (typeof message._started_at === "string") {
2472
2426
  toolCall.started_at = message._started_at;
2473
2427
  }
2474
2428
  if (message._ran_in_parallel === true) toolCall.ran_in_parallel = true;
2475
- owner.blocks.push({
2476
- type: "tool_result",
2477
- content: message.content ?? "",
2478
- tool_call_id: toolCallId
2479
- });
2429
+ const existing = owner.blocks.find((block) => block.type === "tool_result" && block.tool_call_id === toolCallId);
2430
+ if (existing) existing.content = message.content ?? "";
2431
+ else owner.blocks.push({ type: "tool_result", content: message.content ?? "", tool_call_id: toolCallId });
2480
2432
  }
2481
2433
  continue;
2482
2434
  }
@@ -2493,56 +2445,29 @@ function projectHistory(entries) {
2493
2445
  loop_id: loopId,
2494
2446
  kind: "message",
2495
2447
  role,
2448
+ client_request_id: stringOrNull(entry.client_request_id),
2496
2449
  status: message._error ? "failed" : message._interrupted ? "interrupted" : "completed",
2497
2450
  blocks,
2498
2451
  tool_calls: toolCalls,
2499
2452
  model: stringOrNull(message.model),
2500
2453
  usage: isRecord(message._usage) ? { ...message._usage } : null,
2501
- // 模型请求区间来自持久化字段;旧会话缺失时为 null,不用落盘时刻冒充(#2581 A1)。
2502
2454
  duration_ms: numberOrNull(message._duration_ms),
2503
2455
  first_token_ms: numberOrNull(message._first_token_ms),
2504
2456
  stream_duration_ms: numberOrNull(message._stream_duration_ms),
2505
2457
  started_at: typeof message._started_at === "string" ? message._started_at : typeof entry.timestamp === "string" ? entry.timestamp : void 0,
2506
2458
  has_reported_timing: typeof message._started_at === "string",
2507
- // fork 子会话的父锚点:历史里落在消息级 _parent_fork_tool_call_id。丢掉它,
2508
- // 刷新后工作流步骤 fork 就配不回 workflow:run 工具调用(普通 Agent fork 有
2509
- // root Agent 工具调用兜底配对,工作流 fork 没有,子智能体卡会整体消失)。
2510
2459
  parent_fork_tool_call_id: stringOrNull(message._parent_fork_tool_call_id)
2511
2460
  };
2512
- const isNativeToolEntry = role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.some(
2513
- (toolCall) => isRecord(toolCall) && typeof toolCall.provider === "string"
2514
- );
2515
- const previousTurn = turns[turns.length - 1];
2516
- if (role === "assistant" && previousTurn?.role === "assistant" && previousTurn.loop_id === loopId && nativeToolTurns.has(previousTurn)) {
2517
- nativeToolTurns.delete(previousTurn);
2518
- previousTurn.blocks.push(...turn.blocks);
2519
- previousTurn.tool_calls.push(...turn.tool_calls);
2520
- previousTurn.status = turn.status;
2521
- previousTurn.model = turn.model ?? previousTurn.model;
2522
- previousTurn.usage = turn.usage ?? previousTurn.usage;
2523
- if (turn.duration_ms) previousTurn.duration_ms = turn.duration_ms;
2524
- previousTurn.first_token_ms = turn.first_token_ms ?? previousTurn.first_token_ms;
2525
- previousTurn.stream_duration_ms = turn.stream_duration_ms ?? previousTurn.stream_duration_ms;
2526
- if (turn.has_reported_timing) {
2527
- previousTurn.started_at = turn.started_at;
2528
- previousTurn.has_reported_timing = true;
2529
- } else if (!previousTurn.has_reported_timing) {
2530
- previousTurn.started_at = previousTurn.started_at ?? turn.started_at;
2531
- }
2532
- previousTurn.id = turn.id;
2533
- previousTurn.turn_id = turn.turn_id;
2534
- if (isNativeToolEntry) nativeToolTurns.add(previousTurn);
2535
- turnEntryAliases.set(id, previousTurn);
2536
- if (loopId === "root") latestRootMessage = previousTurn;
2537
- latestAssistantByLoop.set(loopId, previousTurn);
2538
- continue;
2461
+ if (role === "user") {
2462
+ const refs = normalizeMessageRefs(message.refs);
2463
+ if (refs.length) turn.refs = refs;
2464
+ turn.context_attachments = normalizeContextAttachments(message.context_attachments);
2539
2465
  }
2540
2466
  if (loopId === "root" && (role === "user" || role === "assistant")) {
2541
2467
  latestRootMessage = turn;
2542
2468
  }
2543
2469
  turns.push(turn);
2544
2470
  turnEntryAliases.set(id, turn);
2545
- if (isNativeToolEntry) nativeToolTurns.add(turn);
2546
2471
  if (role === "assistant") latestAssistantByLoop.set(loopId, turn);
2547
2472
  if (loopId === "root") {
2548
2473
  if (role === "assistant") latestRootAssistant = turn;
@@ -2558,7 +2483,7 @@ function projectHistory(entries) {
2558
2483
  if (owner && toolCallId) {
2559
2484
  const rawContent = data.content;
2560
2485
  const content = typeof rawContent === "string" ? rawContent : Array.isArray(rawContent) ? rawContent : String(rawContent ?? "");
2561
- const status = data.is_error === true ? "error" : inferToolStatus(typeof content === "string" ? content : JSON.stringify(content));
2486
+ const status = data.is_error === true ? "error" : data._tool_pending === true ? "pending" : inferToolStatus(typeof content === "string" ? content : JSON.stringify(content));
2562
2487
  setToolCallStatus(owner, toolCallId, status, content, null);
2563
2488
  if (!owner.blocks.some((block) => block.type === "tool_result" && block.tool_call_id === toolCallId)) {
2564
2489
  owner.blocks.push({ type: "tool_result", content, tool_call_id: toolCallId });
@@ -2566,6 +2491,33 @@ function projectHistory(entries) {
2566
2491
  }
2567
2492
  continue;
2568
2493
  }
2494
+ if (kind === "workflow_step") {
2495
+ const data = entry.data ?? {};
2496
+ const toolCallId = String(data.tool_call_id ?? "");
2497
+ const owner = toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
2498
+ if (owner && toolCallId) {
2499
+ const index = owner.tool_calls.findIndex((call) => call.id === toolCallId);
2500
+ if (index >= 0) {
2501
+ const call = owner.tool_calls[index];
2502
+ owner.tool_calls[index] = {
2503
+ ...call,
2504
+ workflow_steps: [
2505
+ ...call.workflow_steps ?? [],
2506
+ {
2507
+ step_id: String(data.step_id ?? ""),
2508
+ desc: String(data.desc ?? ""),
2509
+ step_kind: String(data.step_kind ?? "step"),
2510
+ status: String(data.status ?? "done"),
2511
+ output: String(data.output ?? ""),
2512
+ ...typeof data.seq === "number" ? { seq: data.seq } : {},
2513
+ ...typeof data.log_seq === "number" ? { log_seq: data.log_seq } : {}
2514
+ }
2515
+ ]
2516
+ };
2517
+ }
2518
+ }
2519
+ continue;
2520
+ }
2569
2521
  if (kind === "mode_change") {
2570
2522
  turns.push(markerTurn(id, ++sequence, loopId, "mode_change", entry.data ?? {}));
2571
2523
  } else if (kind === "workspace_change") {
@@ -2616,7 +2568,7 @@ function projectHistory(entries) {
2616
2568
  let content = isRecord(entry.message) ? entry.message.content : void 0;
2617
2569
  if (kind === "tool_result_archive") {
2618
2570
  const archivedCount = Array.isArray(data.archived_files) ? data.archived_files.length : 0;
2619
- content = `<compaction-summary>
2571
+ content = typeof data.summary === "string" && data.summary ? data.summary : `<compaction-summary>
2620
2572
  \u5DF2\u5F52\u6863 ${archivedCount} \u4E2A\u5927\u578B\u5DE5\u5177\u7ED3\u679C\u3002
2621
2573
  </compaction-summary>`;
2622
2574
  }
@@ -2683,162 +2635,692 @@ function projectHistory(entries) {
2683
2635
  } else {
2684
2636
  setToolCallStatus(latestAssistantByLoop.get(loopId), toolCallId, "done", null, null);
2685
2637
  }
2686
- turns.push(
2687
- markerTurn(
2688
- id,
2689
- ++sequence,
2690
- loopId,
2691
- "ask_user_answer",
2692
- data,
2693
- toolCallId || null
2694
- )
2695
- );
2696
- }
2697
- }
2698
- for (const { childToolCallId, pauseToolData, description } of childPauseEntries) {
2699
- const pending = pendingChildPauses.get(childToolCallId);
2700
- if (!pending) continue;
2701
- applyAskUserPauseToTurn(
2702
- latestAssistantByLoop.get(pending.childLoopId),
2703
- pauseToolData,
2704
- childToolCallId,
2705
- pending.childLoopId,
2706
- description,
2707
- (name) => name
2708
- );
2709
- applyChildPauseToParent(
2710
- latestAssistantByLoop.get(pending.parentLoopId),
2711
- pending.parentToolCallId,
2712
- pending.childLoopId,
2713
- childToolCallId,
2714
- description,
2715
- (name) => name
2716
- );
2717
- }
2718
- const rootAssistant = latestRootAssistant;
2719
- if (rootAssistant) {
2720
- for (const toolCall of rootAssistant.tool_calls) {
2721
- if (answeredToolCallIds.has(toolCall.id)) continue;
2722
- if (toolCall.tool_name.split(":").pop() !== "AskUserQuestion") continue;
2723
- if (toolCall.status === "error" || toolCall.status === "cancelled") continue;
2724
- const pauseArguments = parseToolArguments(toolCall.arguments);
2725
- if (typeof toolCall.result === "string") {
2726
- const resultArguments = parseToolArguments(toolCall.result);
2727
- if (Array.isArray(resultArguments.questions)) {
2728
- pauseArguments.questions = resultArguments.questions;
2638
+ turns.push(
2639
+ markerTurn(
2640
+ id,
2641
+ ++sequence,
2642
+ loopId,
2643
+ "ask_user_answer",
2644
+ data,
2645
+ toolCallId || null
2646
+ )
2647
+ );
2648
+ }
2649
+ }
2650
+ for (const { childToolCallId, pauseToolData, description } of childPauseEntries) {
2651
+ const pending = pendingChildPauses.get(childToolCallId);
2652
+ if (!pending) continue;
2653
+ applyAskUserPauseToTurn(
2654
+ latestAssistantByLoop.get(pending.childLoopId),
2655
+ pauseToolData,
2656
+ childToolCallId,
2657
+ pending.childLoopId,
2658
+ description,
2659
+ (name) => name
2660
+ );
2661
+ applyChildPauseToParent(
2662
+ latestAssistantByLoop.get(pending.parentLoopId),
2663
+ pending.parentToolCallId,
2664
+ pending.childLoopId,
2665
+ childToolCallId,
2666
+ description,
2667
+ (name) => name
2668
+ );
2669
+ }
2670
+ const rootAssistant = latestRootAssistant;
2671
+ if (rootAssistant) {
2672
+ for (const toolCall of rootAssistant.tool_calls) {
2673
+ if (answeredToolCallIds.has(toolCall.id)) continue;
2674
+ if (toolCall.tool_name.split(":").pop() !== "AskUserQuestion") continue;
2675
+ if (toolCall.status === "error" || toolCall.status === "cancelled") continue;
2676
+ const pauseArguments = parseToolArguments(toolCall.arguments);
2677
+ if (typeof toolCall.result === "string") {
2678
+ const resultArguments = parseToolArguments(toolCall.result);
2679
+ if (Array.isArray(resultArguments.questions)) {
2680
+ pauseArguments.questions = resultArguments.questions;
2681
+ }
2682
+ }
2683
+ applyAskUserPauseToTurn(
2684
+ rootAssistant,
2685
+ {
2686
+ name: toolCall.tool_name,
2687
+ tool_call_id: toolCall.id,
2688
+ arguments: pauseArguments
2689
+ },
2690
+ toolCall.id,
2691
+ "",
2692
+ "",
2693
+ (name) => name
2694
+ );
2695
+ }
2696
+ }
2697
+ return turns;
2698
+ }
2699
+ function parseToolArguments(value) {
2700
+ if (!value) return {};
2701
+ try {
2702
+ const parsed = JSON.parse(value);
2703
+ return isRecord(parsed) ? parsed : {};
2704
+ } catch {
2705
+ return {};
2706
+ }
2707
+ }
2708
+ function buildToolCalls(value) {
2709
+ if (!Array.isArray(value)) return [];
2710
+ return value.filter(isRecord).map((raw) => {
2711
+ const fn = isRecord(raw.function) ? raw.function : {};
2712
+ const name = String(fn.name ?? "");
2713
+ return {
2714
+ id: String(raw.id ?? ""),
2715
+ tool_name: name,
2716
+ display_name: String(raw.display_name ?? name),
2717
+ arguments: typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments ?? {}),
2718
+ status: "running",
2719
+ result: null
2720
+ };
2721
+ });
2722
+ }
2723
+ function buildBlocks(message, toolCalls) {
2724
+ const displayContent = message.role === "user" ? message._display_content : void 0;
2725
+ const stored = message._blocks;
2726
+ const blocks = [];
2727
+ const storedToolCallIds = /* @__PURE__ */ new Set();
2728
+ if (displayContent !== void 0) {
2729
+ blocks.push({ type: "text", content: displayContent });
2730
+ } else if (Array.isArray(stored)) {
2731
+ for (const raw of stored) {
2732
+ if (!isRecord(raw)) continue;
2733
+ const type2 = String(raw.type ?? "");
2734
+ if (type2 === "thinking") blocks.push({ type: type2, content: raw.thinking ?? raw.content ?? "" });
2735
+ if (type2 === "text") blocks.push({ type: type2, content: raw.text ?? raw.content ?? "" });
2736
+ if (type2 === "tool_call") {
2737
+ const toolCallId = String(raw.id ?? "");
2738
+ const toolCall = toolCalls.find((candidate) => candidate.id === toolCallId);
2739
+ if (!toolCall) continue;
2740
+ storedToolCallIds.add(toolCallId);
2741
+ blocks.push({
2742
+ type: "tool_use",
2743
+ content: toolCall.arguments,
2744
+ tool_call_id: toolCall.id,
2745
+ tool_name: toolCall.tool_name,
2746
+ display_name: toolCall.display_name
2747
+ });
2748
+ }
2749
+ }
2750
+ } else if (message.content !== void 0 && message.content !== "") {
2751
+ blocks.push({ type: "text", content: message.content });
2752
+ }
2753
+ for (const toolCall of toolCalls) {
2754
+ if (storedToolCallIds.has(toolCall.id)) continue;
2755
+ blocks.push({
2756
+ type: "tool_use",
2757
+ content: toolCall.arguments,
2758
+ tool_call_id: toolCall.id,
2759
+ tool_name: toolCall.tool_name,
2760
+ display_name: toolCall.display_name
2761
+ });
2762
+ }
2763
+ return blocks;
2764
+ }
2765
+ function markerTurn(id, sequence, loopId, type2, content, toolCallId = null) {
2766
+ return {
2767
+ id,
2768
+ sequence,
2769
+ turn_id: id,
2770
+ loop_id: loopId,
2771
+ kind: type2,
2772
+ role: "system",
2773
+ status: "completed",
2774
+ blocks: [{ type: type2, content, tool_call_id: toolCallId }],
2775
+ tool_calls: [],
2776
+ model: null,
2777
+ usage: null,
2778
+ duration_ms: 0
2779
+ };
2780
+ }
2781
+ function stringOrNull(value) {
2782
+ return typeof value === "string" ? value : null;
2783
+ }
2784
+ function numberOrNull(value) {
2785
+ return typeof value === "number" && value >= 0 ? value : null;
2786
+ }
2787
+ function findLatestAssistant(turns, loopId) {
2788
+ for (let index = turns.length - 1; index >= 0; index -= 1) {
2789
+ const turn = turns[index];
2790
+ if (turn?.role === "assistant" && turn.loop_id === loopId) return turn;
2791
+ }
2792
+ return void 0;
2793
+ }
2794
+
2795
+ // src/shared/projection/workflow.ts
2796
+ var STEP_KINDS = /* @__PURE__ */ new Set(["agent", "step", "parallel", "choice", "end"]);
2797
+ function workflowEntriesFromSteps(steps) {
2798
+ const items = [];
2799
+ const ordered = steps.every((step) => typeof step.seq === "number");
2800
+ steps.forEach((step, index) => {
2801
+ const base = ordered ? step.seq : index * 2;
2802
+ const stepKind = step.step_kind && STEP_KINDS.has(step.step_kind) ? step.step_kind : "step";
2803
+ items.push({
2804
+ key: base,
2805
+ entry: { kind: "step", stepId: step.step_id, desc: step.desc ?? "", stepKind, status: step.status }
2806
+ });
2807
+ const logKey = ordered && typeof step.log_seq === "number" ? step.log_seq : base + 0.5;
2808
+ const output = step.output ?? "";
2809
+ if (step.status === "done" && stepKind === "step") {
2810
+ const shown = output.length <= 300 ? output : `${output.slice(0, 300)}\u2026(\u5171${output.length}\u5B57)`;
2811
+ items.push({ key: logKey, entry: { kind: "log", text: ` \u2514 \u8F93\u51FA: ${shown || "(\u7A7A)"}`, stepId: step.step_id } });
2812
+ }
2813
+ if (step.status === "error" && output) {
2814
+ items.push({ key: logKey, entry: { kind: "log", text: `\u5931\u8D25: ${output}`, stepId: step.step_id } });
2815
+ }
2816
+ });
2817
+ return items.sort((a, b) => a.key - b.key).map((item) => item.entry);
2818
+ }
2819
+ function workflowRunCancelled(steps) {
2820
+ return steps.some((step) => step.status === "cancelled") && !steps.some((step) => step.status === "error");
2821
+ }
2822
+
2823
+ // src/resources/headless.ts
2824
+ var HeadlessError = class extends Error {
2825
+ constructor(message, detail) {
2826
+ super(message);
2827
+ this.detail = detail;
2828
+ this.name = "HeadlessError";
2829
+ }
2830
+ detail;
2831
+ };
2832
+ var SYSTEM_ERROR_CHAT_END_GRACE_MS = 1e3;
2833
+ var CLEANUP_ACK_TIMEOUT_MS = 35e3;
2834
+ var HeadlessResource = class {
2835
+ constructor(client) {
2836
+ this.client = client;
2837
+ }
2838
+ client;
2839
+ subscriptions = /* @__PURE__ */ new Map();
2840
+ async run(prompt, options = {}) {
2841
+ const { session_id } = await this.client.sessions.createSessionWithRequest({
2842
+ intent: prompt,
2843
+ model: options.model ?? null
2844
+ });
2845
+ return this.runTracked(session_id, prompt, options, false);
2846
+ }
2847
+ async runInSession(sessionId, prompt, options = {}) {
2848
+ return this.runTracked(sessionId, prompt, options, false);
2849
+ }
2850
+ async runWithSession(prompt, options = {}) {
2851
+ const { session_id } = await this.client.sessions.createSessionWithRequest({
2852
+ intent: prompt,
2853
+ model: options.model ?? null
2854
+ });
2855
+ return this.runTracked(session_id, prompt, options, true);
2856
+ }
2857
+ async runTracked(sessionId, prompt, options, returnEnvelope) {
2858
+ if ((options.refs?.length ?? 0) > 20) throw new HeadlessError("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u9009\u62E9 20 \u4E2A\u5F15\u7528");
2859
+ if ((options.topicAssetRefs?.length ?? 0) > 20) throw new HeadlessError("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u5F15\u7528 20 \u4E2A\u4E3B\u9898\u8D44\u4EA7");
2860
+ const timeoutMs = options.timeoutMs ?? 3e5;
2861
+ const session_id = sessionId;
2862
+ const socket = this.client.socket();
2863
+ await ensureSocketConnected(socket, timeoutMs, { session_id });
2864
+ const cursor = new LogCursor();
2865
+ const subscriptions = this.subscriptions.get(session_id) ?? /* @__PURE__ */ new Set();
2866
+ subscriptions.add(cursor);
2867
+ this.subscriptions.set(session_id, subscriptions);
2868
+ return new Promise((resolve, reject) => {
2869
+ let settled = false;
2870
+ let cleanupPromise;
2871
+ let stopAttempt;
2872
+ let handleLateAck;
2873
+ let acknowledgeSend = () => void 0;
2874
+ const sendAck = new Promise((resolveAck) => {
2875
+ acknowledgeSend = resolveAck;
2876
+ });
2877
+ let started = false;
2878
+ let chatRunId = null;
2879
+ let systemErrorTimeout = null;
2880
+ const beforeAck = /* @__PURE__ */ new Map();
2881
+ const timeout = setTimeout(() => {
2882
+ fail(new HeadlessError(`headless run timed out after ${timeoutMs}ms`, { session_id, chat_run_id: chatRunId }));
2883
+ }, timeoutMs);
2884
+ const stopOwnedRun = (runId, deadline) => {
2885
+ stopAttempt ??= (async () => {
2886
+ const stopped = await waitForCleanupAck(socket, deadline, (ack2) => {
2887
+ socket.emit("chat:stop", { session_id, chat_run_id: runId }, ack2);
2888
+ });
2889
+ if (stopped.status !== "received") return { ...stopped, phase: "stop_ack", chat_run_id: runId };
2890
+ const ack = stopped.ack;
2891
+ const confirmed = isRecord(ack) && ack.ok !== false && ack.chat_run_id === runId && typeof ack.status === "string" && ["stopping", "stopped", "idle", "done", "interrupted"].includes(ack.status);
2892
+ return {
2893
+ status: confirmed ? "confirmed" : "unconfirmed",
2894
+ phase: "stop_ack",
2895
+ chat_run_id: runId,
2896
+ ...confirmed ? {} : { reason: "invalid_stop_ack" },
2897
+ ack
2898
+ };
2899
+ })().finally(releaseSubscription).catch((error) => ({
2900
+ status: "unconfirmed",
2901
+ phase: "stop_ack",
2902
+ chat_run_id: runId,
2903
+ reason: "cleanup_failed",
2904
+ error
2905
+ }));
2906
+ return stopAttempt;
2907
+ };
2908
+ const cancelRun = async (deadline) => {
2909
+ if (chatRunId === null) {
2910
+ const sent = await waitForCleanupAck(socket, deadline, (ack) => {
2911
+ void sendAck.then(ack);
2912
+ });
2913
+ if (sent.status !== "received") return { ...sent, phase: "send_ack", chat_run_id: null };
2914
+ const runId = acceptedRunId(sent.ack);
2915
+ if (runId === null) {
2916
+ return { status: "unconfirmed", phase: "send_ack", chat_run_id: null, reason: "no_accepted_run", ack: sent.ack };
2917
+ }
2918
+ chatRunId = runId;
2919
+ }
2920
+ const attempt = stopOwnedRun(chatRunId, deadline);
2921
+ const stopped = await waitForCleanupAck(socket, deadline, (ack) => {
2922
+ void attempt.then(ack);
2923
+ });
2924
+ return stopped.status === "received" ? stopped.ack : { ...stopped, phase: "stop_ack", chat_run_id: chatRunId };
2925
+ };
2926
+ const releaseSubscription = () => {
2927
+ const current = this.subscriptions.get(session_id);
2928
+ current?.delete(cursor);
2929
+ if (current?.size === 0) {
2930
+ this.subscriptions.delete(session_id);
2931
+ socket.emit("session:unsubscribe", { session_id });
2932
+ }
2933
+ };
2934
+ const reportDelivery = (error, delivery) => {
2935
+ let detailUpdated = true;
2936
+ try {
2937
+ if (error instanceof HeadlessError && isRecord(error.detail)) error.detail.stop_delivery = delivery;
2938
+ } catch {
2939
+ detailUpdated = false;
2940
+ }
2941
+ if (delivery.status !== "confirmed" || !detailUpdated) {
2942
+ 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 });
2943
+ }
2944
+ };
2945
+ const cleanup = (error) => {
2946
+ cleanupPromise ??= (async () => {
2947
+ clearTimeout(timeout);
2948
+ if (systemErrorTimeout) clearTimeout(systemErrorTimeout);
2949
+ systemErrorTimeout = null;
2950
+ socket.off("chat:start", onChatStart);
2951
+ socket.off("chat:end", onChatEnd);
2952
+ socket.off("system:error", onSystemError);
2953
+ socket.off("turn:events", onTurnEvents);
2954
+ socket.off("connect", subscribe);
2955
+ beforeAck.clear();
2956
+ if (error !== void 0) {
2957
+ const deadline = Date.now() + CLEANUP_ACK_TIMEOUT_MS;
2958
+ handleLateAck = (ack) => {
2959
+ const runId = acceptedRunId(ack);
2960
+ if (runId === null || !socket.connected || stopAttempt) return;
2961
+ const late = Date.now() >= deadline;
2962
+ const current = this.subscriptions.get(session_id) ?? /* @__PURE__ */ new Set();
2963
+ current.add(cursor);
2964
+ this.subscriptions.set(session_id, current);
2965
+ void stopOwnedRun(runId, late ? Date.now() + CLEANUP_ACK_TIMEOUT_MS : deadline).then((delivery) => {
2966
+ if (late) reportDelivery(error, { ...delivery, late: true });
2967
+ }).catch((error2) => {
2968
+ console.warn("[agent-client] late headless stop cleanup failed", { session_id, chat_run_id: runId, error: error2 });
2969
+ });
2970
+ };
2971
+ reportDelivery(error, await cancelRun(deadline));
2972
+ }
2973
+ if (!stopAttempt) releaseSubscription();
2974
+ })();
2975
+ return cleanupPromise;
2976
+ };
2977
+ const finish = (value) => {
2978
+ if (settled) return;
2979
+ settled = true;
2980
+ void cleanup().then(() => resolve(returnEnvelope ? { session_id, chat_run_id: chatRunId, result: value } : value), reject);
2981
+ };
2982
+ const rejectRun = (error, sendStop = false) => {
2983
+ if (settled) return;
2984
+ settled = true;
2985
+ void cleanup(sendStop ? error : void 0).then(() => reject(error), () => reject(error));
2986
+ };
2987
+ const fail = (error) => rejectRun(error, true);
2988
+ const accept = (event) => {
2989
+ if (settled) return;
2990
+ const payload = event.payload;
2991
+ if (event.type === "chat:start") {
2992
+ started = true;
2993
+ return;
2994
+ }
2995
+ if (event.type === "system:error") {
2996
+ const error = new HeadlessError(String(payload.message || "headless run failed"), {
2997
+ session_id,
2998
+ chat_run_id: chatRunId,
2999
+ event: payload
3000
+ });
3001
+ if (!started) {
3002
+ rejectRun(error);
3003
+ } else {
3004
+ if (systemErrorTimeout) clearTimeout(systemErrorTimeout);
3005
+ systemErrorTimeout = setTimeout(() => fail(error), SYSTEM_ERROR_CHAT_END_GRACE_MS);
3006
+ }
3007
+ return;
3008
+ }
3009
+ if (event.type !== "chat:end") return;
3010
+ if (payload.status !== "completed") {
3011
+ rejectRun(new HeadlessError(errorMessageFromResult(payload.result) ?? `headless run ${String(payload.status || "failed")}`, {
3012
+ session_id,
3013
+ chat_run_id: chatRunId,
3014
+ event: payload
3015
+ }));
3016
+ return;
3017
+ }
3018
+ try {
3019
+ finish(normalizeResult(payload.result, Boolean(options.schema)));
3020
+ } catch (error) {
3021
+ rejectRun(error);
3022
+ }
3023
+ };
3024
+ const observe = (type2, payload) => {
3025
+ if (settled || payload.session_id && payload.session_id !== session_id) return;
3026
+ const runId = payload.chat_run_id;
3027
+ const event = { type: type2, payload };
3028
+ if (typeof runId !== "string" || !runId) {
3029
+ if (!runId && chatRunId === null && type2 === "system:error" && payload.session_id === session_id && !beforeAck.has("")) {
3030
+ beforeAck.set("", [event]);
3031
+ }
3032
+ return;
3033
+ }
3034
+ if (chatRunId === null) {
3035
+ const pending = beforeAck.get(runId) ?? [];
3036
+ pending.push(event);
3037
+ beforeAck.set(runId, pending);
3038
+ } else if (runId === chatRunId) {
3039
+ accept(event);
3040
+ }
3041
+ };
3042
+ function onChatStart(payload) {
3043
+ observe("chat:start", payload);
3044
+ }
3045
+ function onChatEnd(payload) {
3046
+ observe("chat:end", payload);
3047
+ }
3048
+ function onSystemError(payload) {
3049
+ observe("system:error", payload);
3050
+ }
3051
+ function onTurnEvents(data, acknowledge) {
3052
+ acknowledgeTurnEvents(acknowledge);
3053
+ if (data.session_id !== session_id) return;
3054
+ for (const event of cursor.consume(data.events.map((event2) => ({
3055
+ ...event2,
3056
+ log_seq: event2.log_seq ?? void 0,
3057
+ step_log_seq: event2.step_log_seq ?? void 0,
3058
+ delta_seq: event2.delta_seq ?? void 0
3059
+ }))).events) {
3060
+ if (["chat:start", "chat:end", "system:error"].includes(event.type)) observe(event.type, event.payload);
2729
3061
  }
2730
3062
  }
2731
- applyAskUserPauseToTurn(
2732
- rootAssistant,
3063
+ function subscribe() {
3064
+ if (settled) return;
3065
+ const after = Math.min(...[...subscriptions].map((item) => item.subscription().after_log_seq ?? 0));
3066
+ socket.emit("session:subscribe", {
3067
+ session_id,
3068
+ event_format: "raw",
3069
+ ...after > 0 ? { after_log_seq: after } : {}
3070
+ });
3071
+ }
3072
+ socket.on("chat:start", onChatStart);
3073
+ socket.on("chat:end", onChatEnd);
3074
+ socket.on("system:error", onSystemError);
3075
+ socket.on("turn:events", onTurnEvents);
3076
+ socket.on("connect", subscribe);
3077
+ subscribe();
3078
+ socket.emit(
3079
+ "chat:send",
2733
3080
  {
2734
- name: toolCall.tool_name,
2735
- tool_call_id: toolCall.id,
2736
- arguments: pauseArguments
3081
+ session_id,
3082
+ message: prompt,
3083
+ headless: true,
3084
+ output_schema: options.schema ?? null,
3085
+ model: options.model ?? null,
3086
+ refs: options.refs,
3087
+ topic_asset_refs: options.topicAssetRefs
2737
3088
  },
2738
- toolCall.id,
2739
- "",
2740
- "",
2741
- (name) => name
3089
+ (response) => {
3090
+ acknowledgeSend(response);
3091
+ if (settled) {
3092
+ handleLateAck?.(response);
3093
+ return;
3094
+ }
3095
+ if (response?.status !== "accepted") {
3096
+ const validationError = beforeAck.get("")?.[0];
3097
+ if ((response == null || isRecord(response) && Object.keys(response).length === 0) && validationError) {
3098
+ accept(validationError);
3099
+ return;
3100
+ }
3101
+ rejectRun(new HeadlessError(response?.message ?? "headless run was not accepted", response));
3102
+ return;
3103
+ }
3104
+ if (typeof response.chat_run_id !== "string" || !response.chat_run_id) {
3105
+ rejectRun(new HeadlessError("headless run acknowledgement has no run ID", response));
3106
+ return;
3107
+ }
3108
+ chatRunId = response.chat_run_id;
3109
+ const pending = beforeAck.get(chatRunId) ?? [];
3110
+ beforeAck.clear();
3111
+ for (const event of pending) accept(event);
3112
+ }
2742
3113
  );
2743
- }
3114
+ });
2744
3115
  }
2745
- return turns;
3116
+ };
3117
+ function normalizeResult(result, hasSchema) {
3118
+ if (!isRecord(result)) {
3119
+ throw new HeadlessError("headless run finished without a result", result);
3120
+ }
3121
+ const errMsg = typeof result.err_msg === "string" ? result.err_msg : "";
3122
+ if (errMsg) {
3123
+ throw new HeadlessError(errMsg, result);
3124
+ }
3125
+ if (!hasSchema) {
3126
+ const text = result.result;
3127
+ return typeof text === "string" ? text : String(text ?? "");
3128
+ }
3129
+ const { err_msg: _errMsg, ...businessResult } = result;
3130
+ return businessResult;
2746
3131
  }
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 {};
3132
+ function errorMessageFromResult(result) {
3133
+ if (isRecord(result) && typeof result.err_msg === "string" && result.err_msg) {
3134
+ return result.err_msg;
2754
3135
  }
3136
+ return null;
2755
3137
  }
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
3138
+ function ensureSocketConnected(socket, timeoutMs, detail) {
3139
+ if (socket.connected) {
3140
+ return Promise.resolve();
3141
+ }
3142
+ return new Promise((resolve, reject) => {
3143
+ let settled = false;
3144
+ const timeout = setTimeout(() => {
3145
+ finish(() => reject(new HeadlessError(`socket connection timed out after ${timeoutMs}ms`, detail)));
3146
+ }, timeoutMs);
3147
+ const onConnect = () => finish(resolve);
3148
+ const onConnectError = (error) => {
3149
+ finish(() => reject(new HeadlessError("socket connection failed", { ...detail, event: error })));
3150
+ };
3151
+ const finish = (callback) => {
3152
+ if (settled) return;
3153
+ settled = true;
3154
+ clearTimeout(timeout);
3155
+ socket.off("connect", onConnect);
3156
+ socket.off("connect_error", onConnectError);
3157
+ callback();
2768
3158
  };
3159
+ socket.on("connect", onConnect);
3160
+ socket.on("connect_error", onConnectError);
3161
+ socket.connect();
2769
3162
  });
2770
3163
  }
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
- }
3164
+ function acceptedRunId(ack) {
3165
+ return isRecord(ack) && ack.status === "accepted" && typeof ack.chat_run_id === "string" && ack.chat_run_id ? ack.chat_run_id : null;
3166
+ }
3167
+ function waitForCleanupAck(socket, deadline, request) {
3168
+ if (!socket.connected) return Promise.resolve({ status: "unconfirmed", reason: "disconnected" });
3169
+ if (Date.now() >= deadline) return Promise.resolve({ status: "unconfirmed", reason: "ack_timeout" });
3170
+ return new Promise((resolve) => {
3171
+ const timer = setTimeout(() => finish({ status: "unconfirmed", reason: "ack_timeout" }), Math.max(0, deadline - Date.now()));
3172
+ let finished = false;
3173
+ const finish = (outcome) => {
3174
+ if (finished) return;
3175
+ finished = true;
3176
+ clearTimeout(timer);
3177
+ socket.off("disconnect", onDisconnect);
3178
+ resolve(outcome);
3179
+ };
3180
+ const onDisconnect = () => finish({ status: "unconfirmed", reason: "disconnected" });
3181
+ socket.on("disconnect", onDisconnect);
3182
+ try {
3183
+ request((ack) => finish({ status: "received", ack }));
3184
+ } catch (error) {
3185
+ finish({ status: "unconfirmed", reason: "emit_failed", error });
2797
3186
  }
2798
- } else if (message.content !== void 0 && message.content !== "") {
2799
- blocks.push({ type: "text", content: message.content });
3187
+ });
3188
+ }
3189
+
3190
+ // src/resources/computers.ts
3191
+ function computerState(computer) {
3192
+ if (computer.enabled) return "enabled";
3193
+ return computer.online ? "idle" : "offline";
3194
+ }
3195
+ function computerOS(computer) {
3196
+ const os = (computer.os ?? "").toLowerCase();
3197
+ if (os.startsWith("darwin") || os.startsWith("mac")) return "macos";
3198
+ if (os.startsWith("windows")) return "windows";
3199
+ if (os.startsWith("linux")) return "linux";
3200
+ return "unknown";
3201
+ }
3202
+ function computerPlatformLabel(computer) {
3203
+ const os = (computer.os ?? "").trim();
3204
+ const arch = (computer.arch ?? "").trim();
3205
+ if (os && arch) return `${os}/${arch}`;
3206
+ return os || arch || "";
3207
+ }
3208
+ function computerDaemonVersion(computer) {
3209
+ return (computer.daemon_version ?? "").trim();
3210
+ }
3211
+ function sortComputers(computers) {
3212
+ return [...computers].sort((a, b) => {
3213
+ const byCreated = (a.created_at ?? "").localeCompare(b.created_at ?? "");
3214
+ if (byCreated !== 0) return byCreated;
3215
+ return a.label.localeCompare(b.label);
3216
+ });
3217
+ }
3218
+ var ComputersResource = class {
3219
+ constructor(client) {
3220
+ this.client = client;
3221
+ }
3222
+ client;
3223
+ /**
3224
+ * 列出当前账号登记过的全部电脑及其在线状态。
3225
+ *
3226
+ * 首页还没有会话,也要能看到同一份清单——判定各写一份必然分叉,一边的兜底会把
3227
+ * 另一边的错误盖住(#1597)。所以账号事实只从这一个接口读。
3228
+ */
3229
+ async listAccount() {
3230
+ const data = await this.client.json("GET", "/api/daemon/instances");
3231
+ return {
3232
+ computers: (data.instances ?? []).map((instance) => ({
3233
+ ...instance,
3234
+ online: Boolean(instance.online)
3235
+ }))
3236
+ };
3237
+ }
3238
+ /**
3239
+ * 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
3240
+ * 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
3241
+ */
3242
+ list(sessionId) {
3243
+ return this.client.json(
3244
+ "GET",
3245
+ `/api/sessions/${encodeURIComponent(sessionId)}/computers`
3246
+ );
3247
+ }
3248
+ /**
3249
+ * 启动或停止一台电脑。只改这次会话的可用范围,不会碰远程机器,
3250
+ * 电脑离线时同样能登记,等它上线就直接可用。
3251
+ */
3252
+ setEnabled(sessionId, computer, enabled) {
3253
+ return this.client.json(
3254
+ "PUT",
3255
+ `/api/sessions/${encodeURIComponent(sessionId)}/computers/${encodeURIComponent(computer)}/enabled`,
3256
+ { enabled }
3257
+ );
2800
3258
  }
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
- });
3259
+ };
3260
+
3261
+ // src/resources/models.ts
3262
+ var ModelsResource = class {
3263
+ constructor(client) {
3264
+ this.client = client;
2810
3265
  }
2811
- return blocks;
2812
- }
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
- };
2828
- }
2829
- function stringOrNull(value) {
2830
- return typeof value === "string" ? value : null;
2831
- }
2832
- function numberOrNull(value) {
2833
- return typeof value === "number" && value >= 0 ? value : null;
2834
- }
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;
3266
+ client;
3267
+ list(options = {}) {
3268
+ return this.client.jsonFromInit("/api/config/models", { signal: options.signal });
2839
3269
  }
2840
- return void 0;
2841
- }
3270
+ };
3271
+
3272
+ // src/schemas/session.ts
3273
+ import { type } from "arktype";
3274
+ var SessionStatus = type(
3275
+ "'created' | 'running' | 'completed' | 'failed' | 'interrupted' | 'waiting_for_input'"
3276
+ );
3277
+ var DEFAULT_REPLAY_SPEED = 5;
3278
+ var SessionInfo = type({
3279
+ id: "string",
3280
+ intent: "string",
3281
+ status: SessionStatus,
3282
+ created_at: "string",
3283
+ updated_at: "string",
3284
+ "shared?": "boolean",
3285
+ "anonymous?": "boolean",
3286
+ "memory_enabled?": "boolean",
3287
+ "is_persistent?": "boolean",
3288
+ "initiator?": "'ui' | 'sdk' | 'unknown' | null",
3289
+ "is_headless?": "boolean",
3290
+ "viewer_role?": "'owner' | 'viewer'",
3291
+ "model?": "string | null",
3292
+ "enable_thinking?": "boolean | null",
3293
+ "solution_id?": "string | null",
3294
+ "solution_ref?": "unknown",
3295
+ "biz_role_id?": "string | null",
3296
+ "solution?": "unknown",
3297
+ "plan_summary?": "string | null",
3298
+ "replay_state?": "unknown",
3299
+ "is_pinned?": "boolean",
3300
+ "pinned_at?": "string | null",
3301
+ "is_example?": "boolean",
3302
+ "ports?": "unknown",
3303
+ "disable_tools?": "string[]",
3304
+ "workspace_path?": "string | null",
3305
+ "ba_version?": "string | null",
3306
+ "sandbox_version?": "string | null",
3307
+ "chat_group_id?": "string | null",
3308
+ "match?": "unknown"
3309
+ });
3310
+
3311
+ // src/session/definition.ts
3312
+ var SessionSetupError = class extends Error {
3313
+ constructor(message, stage, sessionId, options) {
3314
+ super(message);
3315
+ this.stage = stage;
3316
+ this.sessionId = sessionId;
3317
+ this.cause = options?.cause;
3318
+ }
3319
+ stage;
3320
+ sessionId;
3321
+ name = "SessionSetupError";
3322
+ cause;
3323
+ };
2842
3324
 
2843
3325
  // src/resources/sessions.ts
2844
3326
  function isEntryGoneError(error) {
@@ -2849,14 +3331,14 @@ function toCreateSessionPayload(request) {
2849
3331
  const solutionRef = request.solution_ref ?? externalSolutionRef(request.solution_id);
2850
3332
  return {
2851
3333
  intent: request.intent ?? "",
2852
- template_id: request.template_id,
3334
+ initiator: request.initiator ?? "sdk",
3335
+ anonymous: request.anonymous,
2853
3336
  solution_id: solutionRef ? void 0 : request.solution_id,
2854
3337
  solution_ref: solutionRef,
2855
3338
  biz_role_id: request.biz_role_id ?? null,
2856
3339
  model: request.model ?? null,
2857
3340
  enable_thinking: request.enable_thinking ?? null,
2858
3341
  memory_enabled: request.memory_enabled ?? null,
2859
- primary_skill_id: request.primary_skill_id ?? null,
2860
3342
  env: request.env,
2861
3343
  disable_tools: request.disable_tools ?? null,
2862
3344
  mcp_sources: request.mcp_sources,
@@ -2864,9 +3346,6 @@ function toCreateSessionPayload(request) {
2864
3346
  session_solution_asset_id: request.session_solution_asset_id ?? null,
2865
3347
  solution_snapshot_session_id: request.solution_snapshot_session_id ?? null,
2866
3348
  compaction_ratio: request.compaction_ratio ?? null,
2867
- ...request.runtime_type ? { runtime_type: request.runtime_type } : {},
2868
- ...request.daemon_id ? { daemon_id: request.daemon_id } : {},
2869
- ...request.agent_runtime_id ? { agent_runtime_id: request.agent_runtime_id } : {},
2870
3349
  ...request.chat_group_id ? { chat_group_id: request.chat_group_id } : {}
2871
3350
  };
2872
3351
  }
@@ -2987,18 +3466,21 @@ var SessionsResource = class {
2987
3466
  *
2988
3467
  * Secrets are never returned: `write_only` lists the pointers that hold one.
2989
3468
  */
2990
- getSessionPluginConfig(sessionId, name, init) {
2991
- return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config`, init);
3469
+ getSessionPluginConfig(sessionId, name, init, scope = "personal") {
3470
+ return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config?scope=${scope}`, init);
2992
3471
  }
2993
3472
  /**
2994
- * Publish explicit set/remove operations to the user's keychain file.
3473
+ * Save explicit set/remove operations to encrypted server storage.
2995
3474
  *
2996
3475
  * Fields absent from both lists keep their stored value, so a form never has
2997
- * to round-trip a secret. Rejections leave the previous file untouched; the
2998
- * call never restarts MCP servers.
3476
+ * to round-trip a secret. Rejections preserve the previous values; the
3477
+ * shared settings restart only the affected hosted plugin.
2999
3478
  */
3000
- updateSessionPluginConfig(sessionId, name, update, init) {
3001
- return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config`, { method: "PUT", body: JSON.stringify(update), ...init });
3479
+ updateSessionPluginConfig(sessionId, name, update, init, scope = "personal") {
3480
+ return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config?scope=${scope}`, { method: "PUT", body: JSON.stringify(update), ...init });
3481
+ }
3482
+ restartSessionPluginHost(sessionId, name) {
3483
+ return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/host`, { method: "POST" });
3002
3484
  }
3003
3485
  /** 创建新会话并直接连接,返回可收发消息的 AgentSession。 */
3004
3486
  async create(request = {}) {
@@ -3006,7 +3488,7 @@ var SessionsResource = class {
3006
3488
  return this.createDeclared(request);
3007
3489
  }
3008
3490
  const { session_id } = await this.createSessionWithRequest(request);
3009
- return this.connect(session_id);
3491
+ return request.anonymous ? this.client.forAnonymousSession(session_id).sessions.connect(session_id) : this.connect(session_id);
3010
3492
  }
3011
3493
  async createDeclared(definition) {
3012
3494
  const { intent, solution, skills = [], config } = definition;
@@ -3093,7 +3575,6 @@ var SessionsResource = class {
3093
3575
  fetchSessionsPage({
3094
3576
  limit,
3095
3577
  offset,
3096
- template_id_prefix,
3097
3578
  solution_id,
3098
3579
  solution_registry_id,
3099
3580
  q,
@@ -3102,7 +3583,6 @@ var SessionsResource = class {
3102
3583
  const searchParams = new URLSearchParams();
3103
3584
  searchParams.set("limit", String(limit));
3104
3585
  searchParams.set("offset", String(offset));
3105
- if (template_id_prefix) searchParams.set("template_id_prefix", template_id_prefix);
3106
3586
  if (solution_registry_id) {
3107
3587
  searchParams.set("solution_registry_id", solution_registry_id);
3108
3588
  } else if (solution_id) {
@@ -3113,12 +3593,12 @@ var SessionsResource = class {
3113
3593
  if (chat_group_id) searchParams.set("chat_group_id", chat_group_id);
3114
3594
  return this.client.json("GET", `/api/sessions?${searchParams.toString()}`);
3115
3595
  }
3116
- async listSessions(template_id_prefix, solution_id) {
3596
+ async listSessions(solution_id) {
3117
3597
  const pageSize = 100;
3118
3598
  const sessions = [];
3119
3599
  let offset = 0;
3120
3600
  while (true) {
3121
- const page = await this.fetchSessionsPage({ limit: pageSize, offset, template_id_prefix, solution_id });
3601
+ const page = await this.fetchSessionsPage({ limit: pageSize, offset, solution_id });
3122
3602
  sessions.push(...page.items);
3123
3603
  if (sessions.length >= page.total || page.items.length === 0) return sessions;
3124
3604
  offset += page.items.length;
@@ -3135,8 +3615,8 @@ var SessionsResource = class {
3135
3615
  if (params.limit !== void 0) searchParams.set("limit", String(params.limit));
3136
3616
  return this.client.json("GET", `/api/search?${searchParams.toString()}`);
3137
3617
  }
3138
- createSession(intent, template_id, primary_skill_id) {
3139
- return this.createSessionWithRequest({ intent, template_id, primary_skill_id });
3618
+ createSession(intent) {
3619
+ return this.createSessionWithRequest({ intent });
3140
3620
  }
3141
3621
  createSessionWithRequest(request) {
3142
3622
  return this.client.json("POST", "/api/sessions", toCreateSessionPayload(request));
@@ -3154,6 +3634,13 @@ var SessionsResource = class {
3154
3634
  updateSession(sessionId, payload) {
3155
3635
  return this.client.json("PATCH", `/api/sessions/${sessionId}`, payload);
3156
3636
  }
3637
+ getSessionConfiguration(sessionId, init) {
3638
+ return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/configuration`, init);
3639
+ }
3640
+ /** Updates atomically between turns. A running/handoff session returns HTTP 409. */
3641
+ updateSessionConfiguration(sessionId, request) {
3642
+ return this.client.json("PATCH", `/api/sessions/${encodeURIComponent(sessionId)}/configuration`, request);
3643
+ }
3157
3644
  setSessionEnv(sessionId, env) {
3158
3645
  return this.client.json("PUT", `/api/sessions/${sessionId}/env`, { env });
3159
3646
  }
@@ -3291,13 +3778,25 @@ var SessionsResource = class {
3291
3778
  }
3292
3779
  async rewindSession(sessionId, checkpointId) {
3293
3780
  const result = await this.checkoutSession(sessionId, checkpointId, "before");
3294
- return { id: result.id, content: result.content };
3781
+ return {
3782
+ id: result.id,
3783
+ content: result.content,
3784
+ ...result.history_committed !== void 0 ? { history_committed: result.history_committed } : {},
3785
+ ...result.file_checkpoint_cleanup_failed !== void 0 ? { file_checkpoint_cleanup_failed: result.file_checkpoint_cleanup_failed } : {},
3786
+ ...result.warning !== void 0 ? { warning: result.warning } : {}
3787
+ };
3295
3788
  }
3296
3789
  switchBranch(sessionId, checkpointId) {
3297
3790
  return this.client.json("POST", `/api/sessions/${sessionId}/switch-branch`, {
3298
3791
  checkpoint_id: checkpointId
3299
3792
  });
3300
3793
  }
3794
+ listAnonymousSessions() {
3795
+ return this.client.json("GET", "/api/anonymous-sessions");
3796
+ }
3797
+ deleteAnonymousSession(sessionId) {
3798
+ return this.client.json("DELETE", `/api/anonymous-sessions/${encodeURIComponent(sessionId)}`);
3799
+ }
3301
3800
  deleteSession(sessionId, options) {
3302
3801
  const query = options?.deleteWorkspace ? "?delete_workspace=true" : "";
3303
3802
  return this.client.json("DELETE", `/api/sessions/${sessionId}${query}`);
@@ -3545,6 +4044,9 @@ var ChatGroupsResource = class {
3545
4044
  this.client = client;
3546
4045
  }
3547
4046
  client;
4047
+ path(groupId) {
4048
+ return `/api/chat-groups/${encodeURIComponent(groupId)}`;
4049
+ }
3548
4050
  list() {
3549
4051
  return this.client.json("GET", "/api/chat-groups");
3550
4052
  }
@@ -3552,11 +4054,124 @@ var ChatGroupsResource = class {
3552
4054
  return this.client.json("POST", "/api/chat-groups", payload);
3553
4055
  }
3554
4056
  update(groupId, payload) {
3555
- return this.client.json("PATCH", `/api/chat-groups/${encodeURIComponent(groupId)}`, payload);
4057
+ return this.client.json("PATCH", this.path(groupId), payload);
3556
4058
  }
3557
4059
  delete(groupId, options) {
3558
4060
  const query = options?.deleteWorkspace ? "?delete_workspace=true" : "";
3559
- return this.client.json("DELETE", `/api/chat-groups/${encodeURIComponent(groupId)}${query}`);
4061
+ return this.client.json("DELETE", `${this.path(groupId)}${query}`);
4062
+ }
4063
+ listMembers(groupId) {
4064
+ return this.client.json("GET", `${this.path(groupId)}/members`);
4065
+ }
4066
+ addMembers(groupId, userIds, role) {
4067
+ return this.client.json("POST", `${this.path(groupId)}/members`, { user_ids: userIds, role });
4068
+ }
4069
+ updateMember(groupId, userId, role) {
4070
+ return this.client.json("PATCH", `${this.path(groupId)}/members/${encodeURIComponent(userId)}`, { role });
4071
+ }
4072
+ removeMember(groupId, userId) {
4073
+ return this.client.json("DELETE", `${this.path(groupId)}/members/${encodeURIComponent(userId)}`);
4074
+ }
4075
+ listTasks(groupId) {
4076
+ return this.client.json("GET", `${this.path(groupId)}/tasks`);
4077
+ }
4078
+ async createTask(groupId, payload) {
4079
+ try {
4080
+ return await this.client.json("POST", `${this.path(groupId)}/tasks`, payload);
4081
+ } catch (error) {
4082
+ if (error instanceof BladeApiError && error.status === 502) {
4083
+ const data = await error.response.clone().json().catch(() => null);
4084
+ if (typeof data?.detail?.message === "string") error.message = data.detail.message;
4085
+ }
4086
+ throw error;
4087
+ }
4088
+ }
4089
+ updateTask(groupId, taskId, payload) {
4090
+ return this.client.json("PATCH", `${this.path(groupId)}/tasks/${encodeURIComponent(taskId)}`, payload);
4091
+ }
4092
+ deleteTask(groupId, taskId) {
4093
+ return this.client.json("DELETE", `${this.path(groupId)}/tasks/${encodeURIComponent(taskId)}`);
4094
+ }
4095
+ listAssets(groupId) {
4096
+ return this.client.json("GET", `${this.path(groupId)}/assets`);
4097
+ }
4098
+ async readAsset(groupId, path, signal) {
4099
+ const response = await this.client.fetch(
4100
+ "GET",
4101
+ `${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`,
4102
+ { signal }
4103
+ );
4104
+ return response.blob();
4105
+ }
4106
+ /** Read at most the first 1 MiB. Full downloads continue to use readAsset. */
4107
+ async readAssetPreview(groupId, path, signal) {
4108
+ const limit = 1024 * 1024;
4109
+ let response;
4110
+ try {
4111
+ response = await this.client.fetch(
4112
+ "GET",
4113
+ `${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`,
4114
+ { signal, headers: { Range: `bytes=0-${limit - 1}` } }
4115
+ );
4116
+ } catch (error) {
4117
+ if (error instanceof BladeApiError && error.status === 416 && error.response.headers.get("content-range") === "bytes */0") {
4118
+ await error.response.body?.cancel();
4119
+ return { blob: new Blob(), truncated: false };
4120
+ }
4121
+ throw error;
4122
+ }
4123
+ const reader = response.body?.getReader();
4124
+ if (!reader) return { blob: new Blob(), truncated: false };
4125
+ const chunks = [];
4126
+ let bytes = 0;
4127
+ let ended = false;
4128
+ let overflow = false;
4129
+ try {
4130
+ while (bytes < limit) {
4131
+ signal?.throwIfAborted();
4132
+ const { done, value } = await reader.read();
4133
+ if (done) {
4134
+ ended = true;
4135
+ break;
4136
+ }
4137
+ const remaining = limit - bytes;
4138
+ overflow = value.byteLength > remaining;
4139
+ const chunk = value.slice(0, remaining);
4140
+ chunks.push(chunk);
4141
+ bytes += chunk.byteLength;
4142
+ }
4143
+ signal?.throwIfAborted();
4144
+ } finally {
4145
+ if (!ended) await reader.cancel().catch(() => void 0);
4146
+ reader.releaseLock();
4147
+ }
4148
+ const range = response.headers.get("content-range")?.match(/^bytes 0-\d+\/(\d+)$/);
4149
+ const length = response.headers.get("content-length");
4150
+ const total = range ? Number(range[1]) : response.status === 200 && length && !response.headers.has("content-encoding") ? Number(length) : null;
4151
+ return {
4152
+ blob: new Blob(chunks, { type: response.headers.get("content-type") ?? "" }),
4153
+ truncated: overflow || (total !== null && Number.isFinite(total) ? total > bytes : !ended)
4154
+ };
4155
+ }
4156
+ async uploadAsset(groupId, file, path, message = "") {
4157
+ const form = new FormData();
4158
+ form.append("file", file);
4159
+ form.append("message", message);
4160
+ const response = await this.client.formData(
4161
+ "POST",
4162
+ `${this.path(groupId)}/assets/upload?${new URLSearchParams({ path })}`,
4163
+ form
4164
+ );
4165
+ return response.json();
4166
+ }
4167
+ createAssetFolder(groupId, path) {
4168
+ return this.client.json("POST", `${this.path(groupId)}/assets/folders`, { path });
4169
+ }
4170
+ deleteAsset(groupId, path) {
4171
+ return this.client.json("DELETE", `${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`);
4172
+ }
4173
+ submitSessionAsset(payload) {
4174
+ return this.client.json("POST", "/api/topic-agent/files/submit", payload);
3560
4175
  }
3561
4176
  };
3562
4177
 
@@ -3583,7 +4198,6 @@ var SiteVisitsResource = class {
3583
4198
  try {
3584
4199
  const response = await (this.client.options.fetchImpl ?? fetch)(url.toString(), {
3585
4200
  method,
3586
- // 访客 Cookie 是唯一的去重依据,必须随请求往返。
3587
4201
  credentials: "include",
3588
4202
  signal: controller.signal
3589
4203
  });
@@ -3597,6 +4211,48 @@ var SiteVisitsResource = class {
3597
4211
  }
3598
4212
  };
3599
4213
 
4214
+ // src/server-clock.ts
4215
+ var clocks = /* @__PURE__ */ new WeakMap();
4216
+ function syncServerTime(client, timestamp, receivedAt = performance.now()) {
4217
+ if (typeof timestamp !== "string") return;
4218
+ const serverTime = Date.parse(timestamp);
4219
+ if (!Number.isFinite(serverTime)) return;
4220
+ const current = getServerTime(client, receivedAt);
4221
+ clocks.set(client, { serverTime: Math.max(serverTime, current ?? serverTime), receivedAt });
4222
+ }
4223
+ function getServerTime(client, now = performance.now()) {
4224
+ const clock = client ? clocks.get(client) : void 0;
4225
+ return clock ? clock.serverTime + Math.max(0, now - clock.receivedAt) : null;
4226
+ }
4227
+
4228
+ // src/session/background.ts
4229
+ var BACKGROUND_STATUSES = [
4230
+ "starting",
4231
+ "running",
4232
+ "cancelling",
4233
+ "succeeded",
4234
+ "failed",
4235
+ "cancelled",
4236
+ "lost"
4237
+ ];
4238
+ function normalizeBackgroundStatus(value) {
4239
+ return BACKGROUND_STATUSES.includes(value) ? value : "running";
4240
+ }
4241
+ function completedBackgroundTask(task) {
4242
+ const taskId = typeof task.id === "string" ? task.id : typeof task.task_id === "string" ? task.task_id : "";
4243
+ if (!taskId) return null;
4244
+ return {
4245
+ id: taskId,
4246
+ command: typeof task.command === "string" ? task.command : "",
4247
+ description: typeof task.description === "string" ? task.description : void 0,
4248
+ status: normalizeBackgroundStatus(task.status),
4249
+ failureReason: typeof task.failure_reason === "string" ? task.failure_reason : void 0,
4250
+ exitCode: typeof task.exit_code === "number" || task.exit_code === null ? task.exit_code : void 0,
4251
+ elapsedSeconds: typeof task.elapsed_seconds === "number" ? task.elapsed_seconds : 0,
4252
+ output: Array.isArray(task.output_preview) && task.output_preview.length > 0 ? task.output_preview.join("\n") : void 0
4253
+ };
4254
+ }
4255
+
3600
4256
  // src/shared/auth-busy.ts
3601
4257
  var DEFAULT_RETRY_AFTER_SECONDS = 1;
3602
4258
  var MAX_TIMER_DELAY_MS = 2147483647;
@@ -4013,15 +4669,8 @@ function contentPreview(content, maxLen = 80) {
4013
4669
  const text = getTextContent(content).trim();
4014
4670
  return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
4015
4671
  }
4016
- function transformSlashCommand(skillName, rawInput, { local = true, installed = true } = {}) {
4017
- const prompt = rawInput.replace(`<skill>${skillName}</skill>`, "").trim();
4018
- if (local) {
4019
- return prompt ? `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1
4020
- ${prompt}` : `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1`;
4021
- }
4022
- 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`;
4023
- return prompt ? `${steps}\uFF0C\u7136\u540E\u5B8C\u6210\u4EFB\u52A1\uFF1A
4024
- ${prompt}` : `${steps}\uFF0C\u7136\u540E\u4F7F\u7528\u5B83\u5B8C\u6210\u4EFB\u52A1`;
4672
+ function transformSlashCommand(skillName, rawInput, _availability = {}) {
4673
+ return rawInput.replace(`<skill>${skillName}</skill>`, `@${skillName}`).trim();
4025
4674
  }
4026
4675
  var ATTACHMENT_TAG_RE = /\[附件:\s*([^\]]+)\](?:[ \t]*已上传到工作区路径:[ \t]*([^\r\n]+))?/g;
4027
4676
  var CONTEXT_PART_RE = /^\[上下文:\s*([^\]]+)\]\n([\s\S]*)$/;
@@ -4149,7 +4798,13 @@ function workflowEntriesFromOutput(output) {
4149
4798
  }
4150
4799
  function workflowNameFromArguments(argumentsJson) {
4151
4800
  try {
4152
- const name = JSON.parse(argumentsJson)?.name;
4801
+ const args = JSON.parse(argumentsJson);
4802
+ const skillId = typeof args?.skill_id === "string" ? args.skill_id.trim() : "";
4803
+ if (skillId) {
4804
+ const flow = typeof args?.flow === "string" ? args.flow.trim() : "";
4805
+ return `\u6280\u80FD\u300C${skillId}\u300D${flow && flow !== "main" ? ` \xB7 \u6D41\u7A0B ${flow}` : ""}`;
4806
+ }
4807
+ const name = args?.name;
4153
4808
  return typeof name === "string" ? name : "";
4154
4809
  } catch {
4155
4810
  return "";
@@ -4173,6 +4828,15 @@ function applyWorkflowEvent(current, toolCallId, type2, payload) {
4173
4828
  }
4174
4829
  const base = current ?? { toolCallId, name: "", status: "running", entries: [] };
4175
4830
  if (type2 === "workflow:step") {
4831
+ const settled = asText(payload.status);
4832
+ const stepId = asText(payload.step_id);
4833
+ if (stepId && isFinished(settled)) {
4834
+ const index = base.entries.findIndex((entry) => entry.kind === "step" && entry.stepId === stepId);
4835
+ if (index === -1) return null;
4836
+ const entries = [...base.entries];
4837
+ entries[index] = { ...entries[index], status: settled };
4838
+ return { ...base, entries };
4839
+ }
4176
4840
  return {
4177
4841
  ...base,
4178
4842
  entries: [
@@ -4187,7 +4851,9 @@ function applyWorkflowEvent(current, toolCallId, type2, payload) {
4187
4851
  };
4188
4852
  }
4189
4853
  if (type2 === "workflow:log") {
4190
- return { ...base, entries: [...base.entries, { kind: "log", text: asText(payload.text) }] };
4854
+ const stepId = asText(payload.step_id);
4855
+ const entry = stepId ? { kind: "log", text: asText(payload.text), stepId } : { kind: "log", text: asText(payload.text) };
4856
+ return { ...base, entries: [...base.entries, entry] };
4191
4857
  }
4192
4858
  if (type2 === "workflow:run:end") {
4193
4859
  const status = asText(payload.status);
@@ -4209,10 +4875,10 @@ function rebuildWorkflowRuns(turns, previous) {
4209
4875
  const name = workflowNameFromArguments(toolCall.arguments);
4210
4876
  const prior = previous[toolCall.id];
4211
4877
  if (isFinished(toolCall.status)) {
4212
- const entries = workflowEntriesFromOutput(
4213
- typeof toolCall.result === "string" ? toolCall.result.trim() : ""
4214
- );
4215
- 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 };
4878
+ const steps = toolCall.workflow_steps ?? [];
4879
+ const entries = steps.length > 0 ? workflowEntriesFromSteps(steps) : workflowEntriesFromOutput(typeof toolCall.result === "string" ? toolCall.result.trim() : "");
4880
+ const status = toolCall.status === "error" && workflowRunCancelled(steps) ? "cancelled" : toolCall.status;
4881
+ runs[toolCall.id] = prior && prior.status === status && prior.name === name && JSON.stringify(prior.entries) === JSON.stringify(entries) ? prior : { toolCallId: toolCall.id, name, status, entries };
4216
4882
  continue;
4217
4883
  }
4218
4884
  runs[toolCall.id] = prior && prior.name === name ? prior : { toolCallId: toolCall.id, name, status: prior?.status ?? "running", entries: prior?.entries ?? [] };
@@ -4463,7 +5129,9 @@ function projectionToMessage(turn) {
4463
5129
  display_name: toolCall.display_name,
4464
5130
  arguments: toolCall.arguments,
4465
5131
  result: toolCall.result ?? void 0,
5132
+ ...toolCall.evidence ? { evidence: toolCall.evidence } : {},
4466
5133
  pending_question_ref: toolCall.pending_question_ref ?? void 0,
5134
+ workflow_steps: toolCall.workflow_steps ?? void 0,
4467
5135
  status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending",
4468
5136
  ...typeof toolCall.duration_ms === "number" ? { duration_ms: toolCall.duration_ms } : {}
4469
5137
  })) : void 0;
@@ -4478,10 +5146,14 @@ function projectionToMessage(turn) {
4478
5146
  ...toolCalls ? { tool_calls: toolCalls } : {},
4479
5147
  loop_name: turn.loop_id,
4480
5148
  entry_id: turn.turn_id,
5149
+ ...turn.client_request_id ? { client_request_id: turn.client_request_id } : {},
4481
5150
  status: turn.status,
4482
5151
  ...typeof turn.duration_ms === "number" ? { duration_ms: turn.duration_ms } : {},
4483
5152
  ...turn.started_at ? { timestamp: turn.started_at } : {},
4484
- ...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {}
5153
+ ...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {},
5154
+ ...turn.refs?.length ? { refs: turn.refs } : {},
5155
+ ...turn.context_attachments?.length ? { context_attachments: turn.context_attachments } : {},
5156
+ ...turn.topic_asset_refs?.length ? { topic_asset_refs: turn.topic_asset_refs } : {}
4485
5157
  };
4486
5158
  }
4487
5159
  var messageProjectionCache = /* @__PURE__ */ new WeakMap();
@@ -4708,8 +5380,6 @@ function withTurns(state, turns) {
4708
5380
  activeCompaction,
4709
5381
  askAnswers,
4710
5382
  mode: latestMode ?? state.mode,
4711
- // 历史恢复时 chat:end(paused) 可能已不在 socket replay 缓存中;
4712
- // awaiting_answer 是投影里的持久权威状态,必须据此恢复问答交互。
4713
5383
  status: isWaitingForInput ? "waiting_for_input" : state.status
4714
5384
  };
4715
5385
  }
@@ -4718,17 +5388,17 @@ function withQueue(state, snapshot2) {
4718
5388
  return { ...state, queue: snapshot2 };
4719
5389
  }
4720
5390
  var localTurnCounter = 0;
4721
- function addUserMessage(state, content, clientRequestId) {
5391
+ function addUserMessage(state, content, clientRequestId, topicAssetRefs, refs, contextAttachments) {
4722
5392
  localTurnCounter += 1;
4723
5393
  const turnId = `local-user-${clientRequestId ?? `${Date.now()}-${localTurnCounter}`}`;
4724
5394
  const optimisticTurn = {
4725
5395
  id: turnId,
4726
5396
  sequence: Math.max(0, ...state.turns.map((turn) => turn.sequence)) + 1,
4727
5397
  turn_id: turnId,
4728
- // 乐观态就自赋,跟服务端确认后 reconcileUserTurns 补上的
4729
- // client_render_id(= 乐观 turn 自己的 turn_id)保持同一个值,
4730
- // 消费方才能拿它当"确认前后不变"的稳定标识使用。
4731
5398
  client_render_id: turnId,
5399
+ ...refs ? { refs } : {},
5400
+ ...contextAttachments?.length ? { context_attachments: contextAttachments } : {},
5401
+ ...topicAssetRefs?.length ? { topic_asset_refs: topicAssetRefs } : {},
4732
5402
  loop_id: "root",
4733
5403
  role: "user",
4734
5404
  status: "streaming",
@@ -4912,6 +5582,9 @@ function sessionStatusFromChatEnd(status) {
4912
5582
  if (status === "failed") return "failed";
4913
5583
  return "completed";
4914
5584
  }
5585
+ function isCompactionFinished(type2) {
5586
+ return type2 === "compaction:end" || type2 === "compaction:error" || type2 === "compaction:abort";
5587
+ }
4915
5588
  function extractTerminalChatEndStatus(events) {
4916
5589
  for (let i = events.length - 1; i >= 0; i--) {
4917
5590
  const event = events[i];
@@ -4922,11 +5595,10 @@ function extractTerminalChatEndStatus(events) {
4922
5595
  return null;
4923
5596
  }
4924
5597
  var AgentSession = class _AgentSession {
5598
+ logCursor = new LogCursor();
4925
5599
  sessionId;
4926
5600
  state;
4927
- /** status 每变化一次加一(单调递增)。异步读取会话详情时据此判断请求期间状态是否变过,避免 running→终态→running 这类来回被误认为「没变」。 */
4928
5601
  statusRevision = 0;
4929
- /** 实时事件写入 status 时调用:值不变也要记一次,才能识别请求期间收到过更新的状态。 */
4930
5602
  noteLiveStatus() {
4931
5603
  this.statusRevision += 1;
4932
5604
  }
@@ -4934,21 +5606,17 @@ var AgentSession = class _AgentSession {
4934
5606
  emitter = new Emitter();
4935
5607
  commands = new CommandRegistry();
4936
5608
  runtime;
5609
+ activeReplayPlaybackId = null;
4937
5610
  projectionBuilder = new ClientProjectionBuilder();
4938
5611
  pendingTurnPatches = /* @__PURE__ */ new Map();
4939
5612
  patchFlushHandle = null;
4940
5613
  patchFlushCancel = null;
4941
5614
  joined = false;
4942
5615
  joinPending = null;
4943
- // bootstrap 与每次 reconnect 共用一条订阅生命周期队列。它们都会重置 join
4944
- // 状态并临时设置 replay source,禁止并发,否则较早操作的 finally 会清掉
4945
- // 较晚操作仍在使用的来源上下文。
4946
5616
  subscriptionQueue = Promise.resolve();
4947
5617
  replaceQueue = Promise.resolve();
4948
5618
  timelineEpoch = 0;
4949
- /** 定位请求的流水号:被后一次选择取代的那次定位不再提交窗口(见 locateEntry)。 */
4950
5619
  locateToken = 0;
4951
- /** 在途那次定位的请求句柄。**只**服务定位:权威刷新不共用它(见 cancelPendingLocate)。 */
4952
5620
  locateAbort = null;
4953
5621
  pagingGeneration = 0;
4954
5622
  liveRevision = 0;
@@ -4957,14 +5625,7 @@ var AgentSession = class _AgentSession {
4957
5625
  pendingOptimisticTurnIds = /* @__PURE__ */ new Set();
4958
5626
  olderPending = null;
4959
5627
  replaceInFlight = 0;
4960
- // 回放控制请求排成一条队列。连着点"5x"再点"退出回放"时,并发发出去的两个
4961
- // 请求响应可能乱序回来,晚到的旧值会把已经退出的会话又显示成回放中;用
4962
- // 序号丢弃过期响应又会在失败时留下中间态。串行执行就没有这些情况:每个
4963
- // 响应都是当时最新的,失败也只影响自己。
4964
5628
  replayQueue = Promise.resolve();
4965
- // Historical snapshots and live commands share one delivery barrier. A
4966
- // repair closes the barrier before joining, snapshots the raw command log
4967
- // after the join, then releases buffered commands in socket arrival order.
4968
5629
  commandRestoreQueue = Promise.resolve();
4969
5630
  commandRecoveryGeneration = 0;
4970
5631
  commandRestoreRetryAttempt = 0;
@@ -4972,42 +5633,35 @@ var AgentSession = class _AgentSession {
4972
5633
  bufferedCommandDeliveries = null;
4973
5634
  pendingReplayMessage = null;
4974
5635
  pendingReplayMode;
4975
- pendingReplayKbIds;
5636
+ pendingReplayRequestId;
5637
+ pendingReplayAttachments;
5638
+ pendingReplayRefs;
5639
+ pendingReplayTopicAssetRefs;
4976
5640
  recentChatEndAt = 0;
4977
- /**
4978
- * 最近一次已经通过 `error` 事件投递过的服务端失败(`message` + `code`)。
4979
- *
4980
- * `chat:send` 被代理时服务端会先发 `system:error`、再用同一条失败回 ack,两条
4981
- * 通道都会到达这里:前者走 `_handleSystemError`,后者走发送失败的回滚。不去重
4982
- * 的话调用方会收到两次同一个 `error`(重复提示、重试逻辑跑两遍)。
4983
- *
4984
- * 带代际边界,只认「本次 `emitWithAck` 等待期间」投递的那一条:否则队列/停止
4985
- * 失败留下的标记会一直挂着,之后一次无关的订阅失败只是文案码相同就被当成
4986
- * 重复而静音。用单调递增的代际号而不是时间戳,同一毫秒内的两条也不会混淆。
4987
- */
4988
5641
  lastDeliveredSystemError = null;
4989
- /** 每次投递 `system:error` 递增,用于判定某条失败是否属于本次等待。 */
4990
5642
  systemErrorGeneration = 0;
4991
5643
  subscriptionReplaySource = null;
4992
5644
  disposed = false;
4993
5645
  getAppContext = null;
4994
5646
  sendPending = false;
4995
- manualCompactionPending = false;
4996
- /** 手动整理的在途标记,同时写进状态(界面据此保持「正在整理」,见 SessionState.compactionPending)。 */
4997
- setManualCompactionPending(pending) {
4998
- this.manualCompactionPending = pending;
5647
+ manualCompactionRequest = null;
5648
+ epochReloadRequested = 0;
5649
+ epochReloadDone = 0;
5650
+ epochReloadRunning = false;
5651
+ setManualCompactionRequest(request) {
5652
+ this.manualCompactionRequest = request;
5653
+ const pending = request !== null;
4999
5654
  if (this.state.compactionPending !== pending) this.update((s) => ({ ...s, compactionPending: pending }));
5000
5655
  }
5001
- /** 最近一次还在等服务端结果的发送请求 id;排队中断的 chat:end 据此判断回滚是否过期。 */
5656
+ handleManualCompactionFinished(event) {
5657
+ const request = this.manualCompactionRequest;
5658
+ const runId = event.payload.run_id;
5659
+ if (!request || !isCompactionFinished(event.type) || typeof runId !== "string" || !runId) return;
5660
+ if (request.runId === null) request.finishedBeforeAck.add(runId);
5661
+ else if (request.runId === runId) this.setManualCompactionRequest(null);
5662
+ }
5002
5663
  pendingChatRequestId = null;
5003
- /** 会话详情给出的运行时类型;"native" 时启用原生智能体的投影规则(见 _handleTurnEvents / _handleChatEnd)。 */
5004
- runtimeType = null;
5005
- /** workflow_run_id → 触发它的 workflow:run 工具调用 id(只有 run:start 事件带工具调用 id)。 */
5006
5664
  workflowRunToolCalls = /* @__PURE__ */ new Map();
5007
- /**
5008
- * 第一方 Web 的精简模式传输优化:订阅时是否要 token 级事件。null 表示宿主没设置,订阅时不带这个字段。
5009
- * `desiredStreamTokens` 是宿主最新的选择;运行中不切换,等这一轮结束再重订阅。
5010
- */
5011
5665
  desiredStreamTokens = null;
5012
5666
  joinedStreamTokens = null;
5013
5667
  deliveredCommandKeys = /* @__PURE__ */ new Set();
@@ -5045,11 +5699,8 @@ var AgentSession = class _AgentSession {
5045
5699
  emitWithAck: async () => readonlyError(),
5046
5700
  ensureConnected: readonlyError,
5047
5701
  fetchTurnsPage: async () => ({ turns, nextBefore: null, currentMode: "executing" }),
5048
- // 静态会话(分享页)没有会话目录接口,宿主用已加载的历史兜底条目。
5049
5702
  fetchTurnIndex: async () => ({ sessionId, items: [] }),
5050
5703
  fetchCommands: async () => [],
5051
- // 静态历史没有回放身份可言,给个空详情即可,不要抛错——_bootstrap
5052
- // 不会跑到这里,但保持只读语义一致。
5053
5704
  fetchSessionInfo: async () => ({ replay_state: null }),
5054
5705
  updateReplay: async () => readonlyError(),
5055
5706
  listDir: async () => readonlyError(),
@@ -5068,7 +5719,6 @@ var AgentSession = class _AgentSession {
5068
5719
  };
5069
5720
  return session;
5070
5721
  }
5071
- // ---------------------------------------------------------------- 状态订阅
5072
5722
  /** 返回当前会话快照。引用稳定:状态未变化时返回同一对象。 */
5073
5723
  getState() {
5074
5724
  return this.state;
@@ -5155,6 +5805,11 @@ var AgentSession = class _AgentSession {
5155
5805
  * chat:end 会让界面停在「运行中」)。
5156
5806
  */
5157
5807
  async reloadHistory() {
5808
+ const replaced = await this.reloadHistoryWindow();
5809
+ await this.refreshRunningState().catch(() => void 0);
5810
+ return replaced;
5811
+ }
5812
+ async reloadHistoryWindow() {
5158
5813
  if (this.disposed) return false;
5159
5814
  this.invalidateTimeline();
5160
5815
  this.invalidateOlderPaging();
@@ -5164,9 +5819,7 @@ var AgentSession = class _AgentSession {
5164
5819
  loadingOlder: false,
5165
5820
  timelineRevision: state.timelineRevision + 1
5166
5821
  }));
5167
- const replaced = await this.replaceFromHistory();
5168
- await this.refreshRunningState().catch(() => void 0);
5169
- return replaced;
5822
+ return this.replaceFromHistory();
5170
5823
  }
5171
5824
  /**
5172
5825
  * 向服务端确认会话此刻是否在运行,校准流式状态(残留的「运行中」会被清掉)。只在已加入房间时
@@ -5187,11 +5840,6 @@ var AgentSession = class _AgentSession {
5187
5840
  if (this.disposed || !response || response.ok === false || typeof response.is_running !== "boolean") return;
5188
5841
  this._applyRunningState(response.is_running, liveRevision);
5189
5842
  }
5190
- /**
5191
- * timeline 被整体改写(rewind / checkout / 释放):推进纪元,并把在途定位请求一并中止
5192
- * ——它已经属于旧 timeline,却还占着替换队列,会让改写后的权威窗口排在后面等它跑完。
5193
- * 作废号仍然保留:中止只是让它更早结束。
5194
- */
5195
5843
  invalidateTimeline() {
5196
5844
  this.timelineEpoch += 1;
5197
5845
  this.locateAbort?.abort();
@@ -5229,7 +5877,6 @@ var AgentSession = class _AgentSession {
5229
5877
  update(fn) {
5230
5878
  this.setState(fn(this.state));
5231
5879
  }
5232
- // ---------------------------------------------------------------- 语义事件
5233
5880
  /** 监听语义事件(底层逃生舱,见 events.ts)。返回取消函数。 */
5234
5881
  on(name, handler) {
5235
5882
  return this.emitter.on(name, handler);
@@ -5246,7 +5893,6 @@ var AgentSession = class _AgentSession {
5246
5893
  onCommand(action, handler) {
5247
5894
  return this.commands.onCommand(action, handler);
5248
5895
  }
5249
- // ---------------------------------------------------------------- 动作
5250
5896
  /**
5251
5897
  * 声明当前应用提供的页面上下文和业务 CLI。
5252
5898
  *
@@ -5257,10 +5903,78 @@ var AgentSession = class _AgentSession {
5257
5903
  this.getAppContext = options.getContext;
5258
5904
  return attachment;
5259
5905
  }
5906
+ /** Prepare a durable text input without sending any network request. */
5907
+ prepareSend(content, options = {}) {
5908
+ if (!content.trim()) throw new Error("\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A");
5909
+ if (Object.keys(options).some((key) => !["mode", "model", "thinkingOverride", "reasoningLevel", "contextAttachments"].includes(key))) {
5910
+ throw new Error("\u53EF\u9760\u6587\u672C\u53D1\u9001\u4E0D\u652F\u6301\u8FD9\u4E9B\u53C2\u6570\uFF0C\u8BF7\u4F7F\u7528 send()");
5911
+ }
5912
+ const snapshot2 = JSON.parse(JSON.stringify(options));
5913
+ if (snapshot2.contextAttachments) snapshot2.contextAttachments = snapshotContextAttachments(snapshot2.contextAttachments);
5914
+ return { sessionId: this.sessionId, requestId: createClientRequestId(), content, options: snapshot2 };
5915
+ }
5916
+ /** Durably enqueue an ordinary text input. Retries must reuse the saved request. */
5917
+ async sendPrepared(request) {
5918
+ if (request.sessionId !== this.sessionId) throw new Error("\u53D1\u9001\u8BF7\u6C42\u4E0D\u5C5E\u4E8E\u5F53\u524D\u4F1A\u8BDD");
5919
+ try {
5920
+ this.runtime.ensureConnected();
5921
+ const response = await this.runtime.emitWithAck("chat:append", {
5922
+ session_id: this.sessionId,
5923
+ client_request_id: request.requestId,
5924
+ message: request.content,
5925
+ prepared_options: {
5926
+ mode: request.options.mode,
5927
+ model: request.options.model,
5928
+ thinking_override: request.options.thinkingOverride,
5929
+ reasoning_level: request.options.reasoningLevel,
5930
+ ...request.options.contextAttachments ? { context_attachments: request.options.contextAttachments } : {}
5931
+ }
5932
+ }, QUEUE_ACK_TIMEOUT_MS);
5933
+ const result = parseQueueAck(response, this.sessionId);
5934
+ if (result.snapshot) this.update((state) => withQueue(state, result.snapshot));
5935
+ if (!result.ok) return {
5936
+ requestId: request.requestId,
5937
+ status: response?.status === "conflict" ? "rejected" : "unknown",
5938
+ message: result.message ?? void 0
5939
+ };
5940
+ const delivery = response?.delivery;
5941
+ if (!isRecord(delivery) || delivery.status !== "accepted") return { requestId: request.requestId, status: "unknown" };
5942
+ return {
5943
+ requestId: request.requestId,
5944
+ status: "accepted",
5945
+ itemId: typeof delivery.item_id === "string" ? delivery.item_id : void 0,
5946
+ queueStatus: typeof delivery.queue_status === "string" ? delivery.queue_status : void 0,
5947
+ executionId: typeof delivery.execution_id === "string" ? delivery.execution_id : null
5948
+ };
5949
+ } catch {
5950
+ return { requestId: request.requestId, status: "unknown" };
5951
+ }
5952
+ }
5953
+ /** Owner-authorized lookup; receipts remain until the session is deleted. */
5954
+ async getDelivery(requestId) {
5955
+ try {
5956
+ this.runtime.ensureConnected();
5957
+ const response = await this.runtime.emitWithAck("chat:queue:request", {
5958
+ session_id: this.sessionId,
5959
+ client_request_id: requestId
5960
+ }, QUEUE_ACK_TIMEOUT_MS);
5961
+ if (response?.status === "accepted") return {
5962
+ requestId,
5963
+ status: "accepted",
5964
+ itemId: typeof response.item_id === "string" ? response.item_id : void 0,
5965
+ queueStatus: typeof response.queue_status === "string" ? response.queue_status : void 0,
5966
+ executionId: typeof response.execution_id === "string" ? response.execution_id : null
5967
+ };
5968
+ if (response?.status === "not_found") return { requestId, status: "not_found" };
5969
+ return { requestId, status: "unknown", message: typeof response?.message === "string" ? response.message : void 0 };
5970
+ } catch {
5971
+ return { requestId, status: "unknown" };
5972
+ }
5973
+ }
5260
5974
  /** 发送一条消息。字符串或多模态内容均可。 */
5261
5975
  async send(content, options = {}) {
5262
5976
  this.runtime.ensureConnected();
5263
- if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.sendPending || this.manualCompactionPending) {
5977
+ if (this.state.activeCompaction || this.sendPending || this.manualCompactionRequest) {
5264
5978
  this.update(
5265
5979
  (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")
5266
5980
  );
@@ -5369,10 +6083,6 @@ var AgentSession = class _AgentSession {
5369
6083
  async refreshQueue() {
5370
6084
  return this.runQueueCommand("chat:queue:list", { session_id: this.sessionId });
5371
6085
  }
5372
- /**
5373
- * 发出一条队列命令并安装 ack 里的快照。
5374
- * 传输层失败(超时 / 断线)会 reject,由调用方决定是否重试。
5375
- */
5376
6086
  async requestQueueAck(event, payload) {
5377
6087
  this.runtime.ensureConnected();
5378
6088
  const response = await this.runtime.emitWithAck(event, payload, QUEUE_ACK_TIMEOUT_MS);
@@ -5381,7 +6091,6 @@ var AgentSession = class _AgentSession {
5381
6091
  if (snapshot2) this.update((state) => withQueue(state, snapshot2));
5382
6092
  return result;
5383
6093
  }
5384
- /** 队列命令的对外入口:传输失败只体现为失败结果,不向调用方抛异常。 */
5385
6094
  async runQueueCommand(event, payload) {
5386
6095
  try {
5387
6096
  return await this.requestQueueAck(event, payload);
@@ -5390,15 +6099,16 @@ var AgentSession = class _AgentSession {
5390
6099
  }
5391
6100
  }
5392
6101
  async sendUnlocked(content, options) {
6102
+ const contextAttachments = options.contextAttachments ? snapshotContextAttachments(options.contextAttachments) : void 0;
5393
6103
  await this.ensureIdleBeforeSend();
5394
- if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.manualCompactionPending) {
6104
+ if (this.state.activeCompaction || this.manualCompactionRequest) {
5395
6105
  this.update(
5396
6106
  (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")
5397
6107
  );
5398
6108
  return false;
5399
6109
  }
5400
6110
  let appContext;
5401
- if (this.getAppContext) {
6111
+ if (this.getAppContext && !this.state.isStreaming) {
5402
6112
  try {
5403
6113
  const rawContext = await this.getAppContext();
5404
6114
  if (!isRecord(rawContext)) {
@@ -5426,7 +6136,7 @@ var AgentSession = class _AgentSession {
5426
6136
  let optimisticAskAnswer;
5427
6137
  let previousAskAnswer;
5428
6138
  this.update((state) => {
5429
- const next = addUserMessage(state, content, clientRequestId);
6139
+ const next = addUserMessage(state, content, clientRequestId, options.topicAssetRefs, options.refs, contextAttachments);
5430
6140
  optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
5431
6141
  return next;
5432
6142
  });
@@ -5444,7 +6154,10 @@ var AgentSession = class _AgentSession {
5444
6154
  if (!options.replayDecision) {
5445
6155
  this.pendingReplayMessage = content;
5446
6156
  this.pendingReplayMode = options.mode;
5447
- this.pendingReplayKbIds = options.kbIds;
6157
+ this.pendingReplayRequestId = clientRequestId;
6158
+ this.pendingReplayAttachments = contextAttachments;
6159
+ this.pendingReplayRefs = options.refs;
6160
+ this.pendingReplayTopicAssetRefs = options.topicAssetRefs;
5448
6161
  }
5449
6162
  this.pendingChatRequestId = clientRequestId;
5450
6163
  const payload = {
@@ -5459,8 +6172,9 @@ var AgentSession = class _AgentSession {
5459
6172
  whatif: options.whatif,
5460
6173
  replay_decision: options.replayDecision,
5461
6174
  app_context: appContext,
5462
- kb_ids: options.kbIds,
5463
- // 运行中提交不必由客户端挑命令:服务端按会话运行状态决定直接执行还是入队。
6175
+ context_attachments: contextAttachments,
6176
+ refs: options.refs,
6177
+ topic_asset_refs: options.topicAssetRefs,
5464
6178
  queue_when_running: options.queueWhenRunning === true
5465
6179
  };
5466
6180
  try {
@@ -5502,6 +6216,14 @@ var AgentSession = class _AgentSession {
5502
6216
  typeof response?.code === "string" ? response.code : void 0
5503
6217
  );
5504
6218
  }
6219
+ if (optimisticTurnId) {
6220
+ this.update((state) => {
6221
+ const next = withTurns(state, state.turns.map(
6222
+ (turn) => turn.turn_id === optimisticTurnId ? { ...turn, status: "completed" } : turn
6223
+ ));
6224
+ return { ...next, askAnswers: state.askAnswers };
6225
+ });
6226
+ }
5505
6227
  return true;
5506
6228
  } catch (error) {
5507
6229
  const failure = describeSessionError(error, "\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
@@ -5536,14 +6258,16 @@ var AgentSession = class _AgentSession {
5536
6258
  }
5537
6259
  this.emitter.emit("error", { message, ...code ? { code } : {} });
5538
6260
  }
5539
- /** 撤回一次乐观提交:移除乐观消息、恢复提问作答、清掉回放草稿。 */
5540
6261
  discardOptimisticSend(optimisticTurnId, askUserAnswer, optimisticAskAnswer, previousAskAnswer) {
5541
6262
  if (optimisticTurnId && this.pendingChatRequestId && optimisticTurnId === `local-user-${this.pendingChatRequestId}`) {
5542
6263
  this.pendingChatRequestId = null;
5543
6264
  }
5544
6265
  this.pendingReplayMessage = null;
5545
6266
  this.pendingReplayMode = void 0;
5546
- this.pendingReplayKbIds = void 0;
6267
+ this.pendingReplayRequestId = void 0;
6268
+ this.pendingReplayAttachments = void 0;
6269
+ this.pendingReplayRefs = void 0;
6270
+ this.pendingReplayTopicAssetRefs = void 0;
5547
6271
  this.update((current) => {
5548
6272
  const filtered = optimisticTurnId ? current.turns.filter((turn) => turn.turn_id !== optimisticTurnId) : current.turns;
5549
6273
  let next = optimisticTurnId ? withTurns({ ...current, turns: filtered }, filtered) : current;
@@ -5565,22 +6289,62 @@ var AgentSession = class _AgentSession {
5565
6289
  }
5566
6290
  }
5567
6291
  /** 停止当前回复。 */
5568
- stop() {
5569
- return this.runtime.emitWithAck("chat:stop", { session_id: this.sessionId }, 35e3).then((response) => {
5570
- const result = response ?? {};
5571
- if (result.status !== "accepted") this.setManualCompactionPending(false);
5572
- return result;
5573
- });
6292
+ async stop() {
6293
+ const request = this.manualCompactionRequest;
6294
+ if (request) {
6295
+ const ack = await request.ack;
6296
+ if (ack.status !== "accepted") return ack;
6297
+ if (this.manualCompactionRequest !== request) return { status: "idle" };
6298
+ return await this.runtime.emitWithAck("chat:stop", {
6299
+ session_id: this.sessionId,
6300
+ chat_run_id: request.runId
6301
+ }, 35e3) ?? {};
6302
+ }
6303
+ const response = await this.runtime.emitWithAck("chat:stop", { session_id: this.sessionId }, 35e3) ?? {};
6304
+ const cancelled = response.cancelled_client_request_ids;
6305
+ if (response.status === "stopped" && Array.isArray(cancelled) && this.pendingChatRequestId !== null && cancelled.includes(this.pendingChatRequestId)) {
6306
+ this.pendingChatRequestId = null;
6307
+ this.update((state) => ({ ...withoutOptimisticUserTurns(state, cancelled), isStreaming: false }));
6308
+ this.applyDesiredStreamTokens();
6309
+ }
6310
+ return response;
5574
6311
  }
5575
6312
  /** 手动触发上下文压缩。 */
5576
6313
  compact() {
5577
- if (this.state.isStreaming || this.state.activeCompaction || this.manualCompactionPending) {
6314
+ if (this.state.isStreaming || this.state.activeCompaction || this.manualCompactionRequest) {
5578
6315
  return Promise.resolve({
5579
6316
  status: "error",
5580
6317
  message: "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002"
5581
6318
  });
5582
6319
  }
5583
- this.setManualCompactionPending(true);
6320
+ const request = {
6321
+ runId: null,
6322
+ finishedBeforeAck: /* @__PURE__ */ new Set(),
6323
+ ack: Promise.resolve().then(() => this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4)).then((response) => {
6324
+ const result = response ?? {};
6325
+ if (result.status !== "accepted") {
6326
+ if (this.manualCompactionRequest === request) this.setManualCompactionRequest(null);
6327
+ return result;
6328
+ }
6329
+ if (typeof result.run_id !== "string" || !result.run_id) {
6330
+ throw new AgentSessionError("\u6574\u7406\u8BF7\u6C42\u786E\u8BA4\u7F3A\u5C11 run_id\uFF0C\u65E0\u6CD5\u786E\u8BA4\u8FD0\u884C\u72B6\u6001");
6331
+ }
6332
+ request.runId = result.run_id;
6333
+ if (request.finishedBeforeAck.has(request.runId) && this.manualCompactionRequest === request) {
6334
+ this.setManualCompactionRequest(null);
6335
+ }
6336
+ request.finishedBeforeAck.clear();
6337
+ return result;
6338
+ }).catch((error) => {
6339
+ if (this.manualCompactionRequest === request) this.setManualCompactionRequest(null);
6340
+ throw error;
6341
+ }).finally(() => {
6342
+ if ((!this.manualCompactionRequest || this.manualCompactionRequest === request) && this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
6343
+ this.update((state) => ({ ...state, activeCompaction: null }));
6344
+ }
6345
+ })
6346
+ };
6347
+ this.setManualCompactionRequest(request);
5584
6348
  this.update((state) => ({
5585
6349
  ...state,
5586
6350
  activeCompaction: {
@@ -5590,18 +6354,7 @@ var AgentSession = class _AgentSession {
5590
6354
  trigger: "manual"
5591
6355
  }
5592
6356
  }));
5593
- return this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4).then((response) => {
5594
- const result = response ?? {};
5595
- if (result.status !== "accepted") this.setManualCompactionPending(false);
5596
- return result;
5597
- }).catch((error) => {
5598
- this.setManualCompactionPending(false);
5599
- throw error;
5600
- }).finally(() => {
5601
- if (this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
5602
- this.update((state) => ({ ...state, activeCompaction: null }));
5603
- }
5604
- });
6357
+ return request.ack;
5605
6358
  }
5606
6359
  /**
5607
6360
  * 把一段业务数据作为附件放进聊天输入框(比如地图上选中的点位)。
@@ -5654,22 +6407,15 @@ var AgentSession = class _AgentSession {
5654
6407
  get isDisposed() {
5655
6408
  return this.disposed;
5656
6409
  }
5657
- // ------------------------------------------------------- Hub 回调(内部)
5658
- /** @internal 连接状态变化(同 client 下所有会话共享一条连接)。 */
5659
6410
  _setConnection(connection) {
5660
6411
  if (this.state.connection === connection) return;
5661
6412
  this.update((s) => ({ ...s, connection }));
5662
6413
  }
5663
- /**
5664
- * @internal 服务端广播的权威队列快照。
5665
- * 结构不合法或 revision 不新的一律丢弃,绝不用旧快照覆盖新状态。
5666
- */
5667
6414
  _handleQueueUpdated(payload) {
5668
6415
  if (!isSessionQueueSnapshot(payload)) return;
5669
6416
  const snapshot2 = payload;
5670
6417
  this.update((state) => withQueue(state, snapshot2));
5671
6418
  }
5672
- /** @internal socket 重连后由 Hub 调用:重新加入房间并补数。 */
5673
6419
  _rejoinAfterReconnect() {
5674
6420
  this.invalidateOlderPaging();
5675
6421
  this.update((state) => ({ ...state, nextBefore: null, loadingOlder: false }));
@@ -5698,7 +6444,6 @@ var AgentSession = class _AgentSession {
5698
6444
  }
5699
6445
  });
5700
6446
  }
5701
- /** @internal 首次连接:加载历史 + 加入房间。 */
5702
6447
  _bootstrap() {
5703
6448
  const commandRecoveryGeneration = this.beginCommandRecovery();
5704
6449
  return this.enqueueSubscription(async () => {
@@ -5709,7 +6454,6 @@ var AgentSession = class _AgentSession {
5709
6454
  Promise.resolve().then(fetchDetail).catch(() => fetchDetail()).catch(() => null)
5710
6455
  ]);
5711
6456
  if (this.disposed) return;
5712
- if (detail) this.runtimeType = detail.runtime_type ?? null;
5713
6457
  this.subscriptionReplaySource = "bootstrap_replay";
5714
6458
  try {
5715
6459
  await this.ensureJoined();
@@ -5723,17 +6467,14 @@ var AgentSession = class _AgentSession {
5723
6467
  ]);
5724
6468
  });
5725
6469
  }
5726
- /** 订阅生命周期串行执行;一次失败只由自己的调用方观察,不截断后续重连。 */
5727
6470
  enqueueSubscription(operation) {
5728
6471
  const run = this.subscriptionQueue.then(operation);
5729
6472
  this.subscriptionQueue = run.catch(() => void 0);
5730
6473
  return run;
5731
6474
  }
5732
- /** 改回放是 owner 专属(服务端 PATCH 走 _get_owned_session)。 */
5733
6475
  get canControlReplay() {
5734
6476
  return this.state.viewerRole !== "viewer";
5735
6477
  }
5736
- /** 拉一次会话详情,填会话状态、回放状态、只读身份和运行时类型;失败不挡启动。 */
5737
6478
  async syncSessionFromDetail(options) {
5738
6479
  const statusRevisionAtRequest = this.statusRevision;
5739
6480
  try {
@@ -5742,18 +6483,14 @@ var AgentSession = class _AgentSession {
5742
6483
  } catch {
5743
6484
  }
5744
6485
  }
5745
- /** 安装一份会话详情;`statusRevisionAtRequest` 是发出请求时的状态修订号。 */
5746
6486
  applySessionDetail(info, statusRevisionAtRequest) {
5747
6487
  if (this.disposed) return;
5748
- this.runtimeType = info.runtime_type ?? null;
5749
6488
  const replay = toReplaySnapshot(info.replay_state);
5750
6489
  const viewerRole = info.viewer_role ?? null;
5751
6490
  this.update((s) => ({
5752
6491
  ...s,
5753
6492
  replay,
5754
6493
  viewerRole,
5755
- // 请求期间 status 变过(哪怕又变回原值)就说明实时事件比这份详情更新,不覆盖。
5756
- // child_pause 已让提问进入等待态,而稍旧的详情仍可能写着 running。
5757
6494
  status: this.statusRevision === statusRevisionAtRequest && !(s.status === "waiting_for_input" && info.status === "running" && s.turns.some((turn) => turn.tool_calls.some((call) => call.status === "awaiting_answer"))) ? info.status ?? s.status : s.status
5758
6495
  }));
5759
6496
  }
@@ -5767,7 +6504,6 @@ var AgentSession = class _AgentSession {
5767
6504
  if (!this.state.replay?.isReplay || !this.canControlReplay) return;
5768
6505
  await this.applyReplayControl({ status: "autonomous" });
5769
6506
  }
5770
- /** 把一次回放控制请求排进队列;前一个完成后才发出,响应直接安装。 */
5771
6507
  applyReplayControl(payload) {
5772
6508
  const run = this.replayQueue.then(async () => {
5773
6509
  const result = await this.runtime.updateReplay(this.sessionId, payload);
@@ -5776,12 +6512,10 @@ var AgentSession = class _AgentSession {
5776
6512
  this.replayQueue = run.catch(() => void 0);
5777
6513
  return run;
5778
6514
  }
5779
- /** @internal */
5780
6515
  _handleTurnStart(turn) {
5781
6516
  this.markRunningIfIdle();
5782
6517
  this.applyTurn(turn);
5783
6518
  }
5784
- /** @internal */
5785
6519
  _handleTurnPatch(patch) {
5786
6520
  this.markRunningIfIdle();
5787
6521
  if (patch.data.workspace_changed) {
@@ -5795,20 +6529,90 @@ var AgentSession = class _AgentSession {
5795
6529
  if (!current || patch.sequence >= current.sequence) {
5796
6530
  this.pendingTurnPatches.set(patch.turn_id, patch);
5797
6531
  }
5798
- this.schedulePatchFlush();
5799
- }
5800
- /** @internal */
5801
- _handleTurnEnd(turn) {
5802
- this.applyTurn(turn);
5803
- }
5804
- /** @internal 历史回放通道(event_format: raw)。 */
5805
- _handleTurnEvents(events, replayed = false) {
6532
+ this.schedulePatchFlush();
6533
+ }
6534
+ _handleTurnEnd(turn) {
6535
+ this.applyTurn(turn);
6536
+ }
6537
+ runEpochReload() {
6538
+ if (this.disposed || this.epochReloadRunning || this.epochReloadDone >= this.epochReloadRequested) return;
6539
+ const target = this.epochReloadRequested;
6540
+ this.epochReloadRunning = true;
6541
+ void this.reloadHistoryWindow().then((replaced) => {
6542
+ if (replaced) this.epochReloadDone = Math.max(this.epochReloadDone, target);
6543
+ }).catch(() => void 0).finally(() => {
6544
+ this.epochReloadRunning = false;
6545
+ if (this.epochReloadRequested > target) this.runEpochReload();
6546
+ });
6547
+ }
6548
+ _handleTurnEvents(incoming, replayed = false) {
6549
+ const delivery = this.logCursor.consume(incoming);
6550
+ let events = delivery.events;
6551
+ if (delivery.reload) {
6552
+ this.activeReplayPlaybackId = null;
6553
+ this.projectionBuilder.reset();
6554
+ this.pendingTurnPatches.clear();
6555
+ this.epochReloadRequested += 1;
6556
+ }
6557
+ this.runEpochReload();
6558
+ for (const event of events) {
6559
+ const playbackId = typeof event.payload.playback_id === "string" ? event.payload.playback_id : null;
6560
+ if (event.type === "chat:start") this.activeReplayPlaybackId = null;
6561
+ if (event.type === "replay:start" && playbackId) {
6562
+ this.activeReplayPlaybackId = playbackId;
6563
+ this._handleChatStart();
6564
+ } else if (event.type === "replay:state") {
6565
+ const status = event.payload.status;
6566
+ if (event.payload.reconciled === true && typeof status === "string") {
6567
+ if (status === "running") this._handleChatStart();
6568
+ else {
6569
+ this.activeReplayPlaybackId = null;
6570
+ this.finishStreaming(status);
6571
+ }
6572
+ }
6573
+ this._handleSessionUpdated(event.payload);
6574
+ } else if (event.type === "replay:end" && playbackId && (!this.activeReplayPlaybackId || this.activeReplayPlaybackId === playbackId)) {
6575
+ this.activeReplayPlaybackId = null;
6576
+ const status = event.payload.status === "interrupted" ? "interrupted" : event.payload.status === "failed" ? "failed" : "completed";
6577
+ this._handleChatEnd({ status });
6578
+ }
6579
+ if (event.type === "log:notification") {
6580
+ const notification = event.payload.event;
6581
+ if (isCompactionFinished(notification.type)) {
6582
+ this.handleManualCompactionFinished(notification);
6583
+ } else if (notification.type === "artifact:created" || notification.type === "artifact:updated") {
6584
+ this._handleArtifact(notification.payload, notification.type === "artifact:created" ? "created" : "updated", replayed);
6585
+ } else if (notification.type === "workspace:changed") {
6586
+ const change = notification.payload.workspace_change;
6587
+ const paths = Array.isArray(notification.payload.paths) ? notification.payload.paths.filter((path) => typeof path === "string") : [];
6588
+ this.emitter.emit("workspaceChanged", {
6589
+ ...paths.length ? { paths } : {},
6590
+ ...typeof change?.to === "string" && change.to ? { workspacePath: change.to } : {}
6591
+ });
6592
+ } else if (notification.type === "task:updated" && Array.isArray(notification.payload.tasks)) {
6593
+ this._handleTaskUpdated(notification.payload.tasks);
6594
+ } else if (notification.type === "bg:tasks_completed" && Array.isArray(notification.payload.tasks)) {
6595
+ for (const task of notification.payload.tasks) {
6596
+ const completed = completedBackgroundTask(task);
6597
+ if (completed) this._handleBackgroundTask(completed);
6598
+ }
6599
+ } else {
6600
+ this.applyWorkflowEvents([notification]);
6601
+ }
6602
+ }
6603
+ if (event.type === "turn:snapshot") {
6604
+ const turn = event.payload.turn;
6605
+ this.projectionBuilder.acceptDurableTurn(turn);
6606
+ this.pendingTurnPatches.delete(turn.turn_id);
6607
+ this.applyTurn(
6608
+ turn,
6609
+ replayed ? this.subscriptionReplaySource ?? "bootstrap_replay" : "live"
6610
+ );
6611
+ }
6612
+ }
6613
+ events = events.filter((event) => event.type !== "turn:snapshot" && event.type !== "log:notification" && !event.type.startsWith("replay:"));
5806
6614
  if (!events.length) return;
5807
- if (events.some(
5808
- (event) => event.type === "compaction:end" || event.type === "compaction:error" || event.type === "compaction:abort"
5809
- )) {
5810
- this.setManualCompactionPending(false);
5811
- }
6615
+ for (const event of events) this.handleManualCompactionFinished(event);
5812
6616
  this.dispatchRawCommands(events);
5813
6617
  const terminalChatEndStatus = extractTerminalChatEndStatus(events);
5814
6618
  if (!terminalChatEndStatus && hasChatRunEvent(events) && this.state.status !== "waiting_for_input") {
@@ -5820,7 +6624,6 @@ var AgentSession = class _AgentSession {
5820
6624
  const updates = this.projectionBuilder.processBatch(events);
5821
6625
  for (const update of updates) {
5822
6626
  if (update.kind === "upsert") {
5823
- if (this.isNativeBootstrapTurn(update.turn)) continue;
5824
6627
  this.applyTurn(
5825
6628
  update.turn,
5826
6629
  replayed ? this.subscriptionReplaySource ?? "bootstrap_replay" : "live"
@@ -5843,7 +6646,6 @@ var AgentSession = class _AgentSession {
5843
6646
  this.completeChat(terminalChatEndStatus ?? "completed");
5844
6647
  }
5845
6648
  }
5846
- /** @internal */
5847
6649
  _handleChatStart() {
5848
6650
  this.noteLiveStatus();
5849
6651
  this.update((s) => ({
@@ -5853,10 +6655,6 @@ var AgentSession = class _AgentSession {
5853
6655
  isStreaming: true
5854
6656
  }));
5855
6657
  }
5856
- /**
5857
- * @internal 返回 false 表示这是一条过期的排队回滚(被取消的是更早的请求,当前运行不受影响),
5858
- * Hub 据此不把它当成一次运行结束广播出去。
5859
- */
5860
6658
  _handleChatEnd(payload = {}) {
5861
6659
  const status = payload.status ?? "completed";
5862
6660
  if (payload.queued === true) {
@@ -5875,29 +6673,9 @@ var AgentSession = class _AgentSession {
5875
6673
  this.emitter.emit("chatEnd", { status });
5876
6674
  return true;
5877
6675
  }
5878
- if (this.runtimeType === "native") {
5879
- const updates = this.projectionBuilder.processBatch([
5880
- {
5881
- type: "chat:end",
5882
- payload: {
5883
- loop_name: "root",
5884
- status,
5885
- ...payload.pause_tool_data ? { pause_tool_data: payload.pause_tool_data } : {}
5886
- }
5887
- }
5888
- ]);
5889
- for (const update of updates) {
5890
- if (update.kind === "upsert") this.applyTurn(update.turn);
5891
- }
5892
- this.finishStreaming(status);
5893
- this.recentChatEndAt = Date.now();
5894
- this.emitter.emit("chatEnd", { status });
5895
- return true;
5896
- }
5897
6676
  this.completeChat(status);
5898
6677
  return true;
5899
6678
  }
5900
- /** @internal */
5901
6679
  _handleSystemError(message, code) {
5902
6680
  this.pendingChatRequestId = null;
5903
6681
  this.noteLiveStatus();
@@ -5927,15 +6705,12 @@ var AgentSession = class _AgentSession {
5927
6705
  ...code ? { code } : {}
5928
6706
  });
5929
6707
  }
5930
- /** @internal */
5931
6708
  _handleTaskUpdated(tasks) {
5932
6709
  this.emitter.emit("taskListUpdated", { tasks });
5933
6710
  }
5934
- /** @internal */
5935
6711
  _handleWorkspaceFilesChanged(filePath, paths) {
5936
6712
  this.emitter.emit("workspaceChanged", paths?.length ? { filePath, paths } : { filePath });
5937
6713
  }
5938
- /** @internal */
5939
6714
  _handleSessionUpdated(data) {
5940
6715
  if (data.status) {
5941
6716
  this.noteLiveStatus();
@@ -5951,11 +6726,9 @@ var AgentSession = class _AgentSession {
5951
6726
  intent: data.intent,
5952
6727
  status: data.status,
5953
6728
  model: data.model,
5954
- boundSkillId: data.bound_skill_id,
5955
6729
  replayState: data.replay_state
5956
6730
  });
5957
6731
  }
5958
- /** @internal */
5959
6732
  _handleRewind(checkpointId) {
5960
6733
  this.invalidateTimeline();
5961
6734
  this.invalidateOlderPaging();
@@ -5970,14 +6743,19 @@ var AgentSession = class _AgentSession {
5970
6743
  void this.replaceFromHistory();
5971
6744
  this.emitter.emit("rewind", { checkpointId });
5972
6745
  }
5973
- /** @internal */
5974
6746
  _handleReplayMismatch(data) {
5975
6747
  const message = this.pendingReplayMessage ?? data.actual_message ?? "";
5976
6748
  const mode = this.pendingReplayMode;
5977
- const kbIds = this.pendingReplayKbIds;
6749
+ const requestId = this.pendingReplayRequestId ?? createClientRequestId();
6750
+ const contextAttachments = this.pendingReplayAttachments;
6751
+ const refs = this.pendingReplayRefs;
6752
+ const topicAssetRefs = this.pendingReplayTopicAssetRefs;
5978
6753
  this.pendingReplayMessage = null;
5979
6754
  this.pendingReplayMode = void 0;
5980
- this.pendingReplayKbIds = void 0;
6755
+ this.pendingReplayRequestId = void 0;
6756
+ this.pendingReplayAttachments = void 0;
6757
+ this.pendingReplayRefs = void 0;
6758
+ this.pendingReplayTopicAssetRefs = void 0;
5981
6759
  const respond = (decision) => {
5982
6760
  void this.runtime.emitWithAck(
5983
6761
  "chat:send",
@@ -5986,7 +6764,10 @@ var AgentSession = class _AgentSession {
5986
6764
  message,
5987
6765
  mode,
5988
6766
  replay_decision: decision,
5989
- kb_ids: kbIds
6767
+ client_request_id: requestId,
6768
+ refs,
6769
+ topic_asset_refs: topicAssetRefs,
6770
+ context_attachments: contextAttachments
5990
6771
  },
5991
6772
  3e4
5992
6773
  ).then((response) => {
@@ -6015,7 +6796,6 @@ var AgentSession = class _AgentSession {
6015
6796
  respond("continue_replay");
6016
6797
  }
6017
6798
  }
6018
- /** @internal */
6019
6799
  _handleSystemNotification(loopId, notification) {
6020
6800
  const notificationType = typeof notification.notification_type === "string" ? notification.notification_type : "system_notification";
6021
6801
  const title = typeof notification.title === "string" ? notification.title : notificationType;
@@ -6042,15 +6822,24 @@ var AgentSession = class _AgentSession {
6042
6822
  }
6043
6823
  }
6044
6824
  }
6045
- /** @internal */
6046
6825
  _handleBackgroundTask(task) {
6047
6826
  this.emitter.emit("backgroundTask", task);
6048
6827
  }
6049
- /** @internal */
6050
- _handleArtifact(event) {
6051
- this.emitter.emit("artifact", event);
6828
+ _handleArtifact(data, kind, replayed = false) {
6829
+ const filePath = typeof data.file_path === "string" ? data.file_path : "";
6830
+ if (!filePath) return;
6831
+ this._handleWorkspaceFilesChanged(filePath);
6832
+ if (replayed) return;
6833
+ this.emitter.emit("artifact", {
6834
+ filePath,
6835
+ contentType: typeof data.content_type === "string" ? data.content_type : "",
6836
+ title: typeof data.title === "string" ? data.title : filePath,
6837
+ content: typeof data.content === "string" ? data.content : "",
6838
+ oldString: typeof data.old_string === "string" ? data.old_string : void 0,
6839
+ newString: typeof data.new_string === "string" ? data.new_string : void 0,
6840
+ kind
6841
+ });
6052
6842
  }
6053
- /** @internal 加入房间的应答里带 is_running,据此校准流式状态。 */
6054
6843
  _applyRunningState(isRunning, liveRevisionAtJoin = this.liveRevision) {
6055
6844
  const hasPendingQuestion = this.state.turns.some(
6056
6845
  (turn) => turn.tool_calls.some((call) => call.status === "awaiting_answer")
@@ -6063,7 +6852,6 @@ var AgentSession = class _AgentSession {
6063
6852
  void this.refreshAuthoritativeHistory({ liveRevisionAtStart: liveRevisionAtJoin });
6064
6853
  }
6065
6854
  }
6066
- // ---------------------------------------------------------------- 内部实现
6067
6855
  markRunningIfIdle() {
6068
6856
  if (!this.state.isStreaming) {
6069
6857
  this.noteLiveStatus();
@@ -6080,15 +6868,6 @@ var AgentSession = class _AgentSession {
6080
6868
  });
6081
6869
  this.applyDesiredStreamTokens();
6082
6870
  }
6083
- /**
6084
- * 原生智能体常在用户发言之前就宣告一个空的 assistant 回合(投影的起点),它的真实内容会随第一条
6085
- * 输出再发一次。消息列表在启动期间本来就显示一个「正在生成」占位,两者并存会出现两个占位,
6086
- * 所以只丢掉原生会话里这种空的流式回合。
6087
- */
6088
- isNativeBootstrapTurn(turn) {
6089
- return this.runtimeType === "native" && turn.role === "assistant" && turn.status === "streaming" && turn.blocks.length === 0 && turn.tool_calls.length === 0;
6090
- }
6091
- /** 实时 workflow:* 事件叠加到对应工具调用的时间线上(见 workflow.ts 的来源规则)。 */
6092
6871
  applyWorkflowEvents(events) {
6093
6872
  for (const event of events) {
6094
6873
  if (!isWorkflowEvent(event.type)) continue;
@@ -6103,10 +6882,6 @@ var AgentSession = class _AgentSession {
6103
6882
  this.update((state) => withWorkflowEvent(state, toolCallId, event.type, payload));
6104
6883
  }
6105
6884
  }
6106
- /**
6107
- * @internal 宿主改了 token 订阅偏好(精简模式)。空闲时立即重订阅;运行中等这一轮结束再换,
6108
- * 避免在输出中途丢掉 token 流。
6109
- */
6110
6885
  _setStreamTokens(enabled) {
6111
6886
  this.desiredStreamTokens = enabled;
6112
6887
  if (!this.state.isStreaming) this.applyDesiredStreamTokens();
@@ -6124,7 +6899,7 @@ var AgentSession = class _AgentSession {
6124
6899
  completeChat(status) {
6125
6900
  this.finishStreaming(status);
6126
6901
  this.recentChatEndAt = Date.now();
6127
- if (this.runtimeType !== "native") void this.refreshAuthoritativeHistory();
6902
+ void this.refreshAuthoritativeHistory();
6128
6903
  this.emitter.emit("chatEnd", { status });
6129
6904
  }
6130
6905
  markTurnFresh(turnId) {
@@ -6202,7 +6977,6 @@ var AgentSession = class _AgentSession {
6202
6977
  this.patchFlushCancel = null;
6203
6978
  }
6204
6979
  }
6205
- /** turn 数据落地后统一派生语义事件(指令 / 工具调用 / 消息 / 模式 / 预览 / OOM)。 */
6206
6980
  emitTurnDerivedEvents(previous, turn, source = "live") {
6207
6981
  this.emitter.emit("turnUpdated", { turn, source });
6208
6982
  const mode = extractModeFromBlocks(turn.blocks);
@@ -6250,6 +7024,7 @@ var AgentSession = class _AgentSession {
6250
7024
  display_name: toolCall.display_name,
6251
7025
  arguments: toolCall.arguments,
6252
7026
  result: toolCall.result ?? void 0,
7027
+ ...toolCall.evidence ? { evidence: toolCall.evidence } : {},
6253
7028
  status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending"
6254
7029
  };
6255
7030
  if (!this.seenToolCallIds.has(toolCall.id)) {
@@ -6314,20 +7089,11 @@ var AgentSession = class _AgentSession {
6314
7089
  this.update((s) => addErrorMessage(s, OOM_MESSAGE));
6315
7090
  this.emitter.emit("sandboxOom", { toolCallId: key });
6316
7091
  }
6317
- /**
6318
- * 权威刷新(运行结束 / 重连补数):**按当前窗口第一条发言取窗口**,不再另记"用户想看哪段"。
6319
- *
6320
- * 窗口本身就是"用户在看哪段":首屏就是最新一页,向上翻页 / 定位之后首条就是用户滚到的位置。
6321
- * 直接拿 `from_entry_id = 当前窗口第一条 turn_id` 去取,运行结束、重连补数、翻页之后的对账
6322
- * 是同一条规则,不需要意图、锚点、已落地锚点这些状态;窗口首条已经不在分支上(404 / 409)时
6323
- * 回落到最新一页。
6324
- */
6325
7092
  refreshAuthoritativeHistory(options = {}) {
6326
7093
  let entryGone = false;
6327
7094
  const locateTokenAtStart = this.locateToken;
6328
7095
  return this.replaceFromHistory({
6329
7096
  ...options,
6330
- // 锚点在**这次操作真正开始**时采样:排队期间用户可能刚定位过,那时窗口首条才是他要的。
6331
7097
  fromEntryId: () => {
6332
7098
  const anchor = this.state.turns[0]?.turn_id;
6333
7099
  return anchor?.startsWith("local-user-") ? void 0 : anchor;
@@ -6495,7 +7261,6 @@ var AgentSession = class _AgentSession {
6495
7261
  this.bufferedCommandDeliveries = null;
6496
7262
  }
6497
7263
  }
6498
- /** 首次订阅前的 REST 历史建立语义事件基线,随后同一工具的订阅回放不再投递。 */
6499
7264
  seedToolCallDeliveryBaseline(turns) {
6500
7265
  for (const turn of turns) {
6501
7266
  for (const toolCall of turn.tool_calls) {
@@ -6516,6 +7281,7 @@ var AgentSession = class _AgentSession {
6516
7281
  session_id: this.sessionId,
6517
7282
  event_format: "raw",
6518
7283
  resume: true,
7284
+ ...this.logCursor.subscription(),
6519
7285
  ...streamTokens === null ? {} : { internal_stream_tokens: streamTokens }
6520
7286
  },
6521
7287
  5e3
@@ -6549,23 +7315,10 @@ var AgentSession = class _AgentSession {
6549
7315
  };
6550
7316
 
6551
7317
  // src/session/hub.ts
6552
- var BACKGROUND_STATUSES = [
6553
- "starting",
6554
- "running",
6555
- "cancelling",
6556
- "succeeded",
6557
- "failed",
6558
- "cancelled",
6559
- "lost"
6560
- ];
6561
- function normalizeBackgroundStatus(value) {
6562
- return BACKGROUND_STATUSES.includes(value) ? value : "running";
6563
- }
6564
7318
  var SessionHub = class {
6565
7319
  client;
6566
7320
  sessions = /* @__PURE__ */ new Map();
6567
7321
  bootstrapPending = /* @__PURE__ */ new Map();
6568
- /** 每个会话当前有多少方持有(connect 一次 +1,dispose 一次 -1)。 */
6569
7322
  holdCounts = /* @__PURE__ */ new Map();
6570
7323
  connection = "disconnected";
6571
7324
  connectionListeners = /* @__PURE__ */ new Set();
@@ -6574,17 +7327,12 @@ var SessionHub = class {
6574
7327
  authBusyReconnect = new AuthBusyReconnect();
6575
7328
  hubEmitter = new Emitter();
6576
7329
  options = { restoreHistoricalCommands: true };
6577
- /** token 订阅偏好(第一方 Web 精简模式);null 表示不设置,订阅时不带字段。 */
6578
7330
  streamTokens = null;
6579
- /** 状态订阅(mode: "status")的持有计数。 */
6580
7331
  statusWatchCounts = /* @__PURE__ */ new Map();
6581
- /** 启动连接门控:扣住期间只登记「需要连接」,放开后连一次。 */
7332
+ statusLogCursors = /* @__PURE__ */ new Map();
7333
+ replayPlaybackIds = /* @__PURE__ */ new Map();
6582
7334
  connectionHeld = false;
6583
7335
  connectDeferred = false;
6584
- /**
6585
- * 宿主显式要过连接(ensureConnected / reconnect,例如只收产品事件的页面)。有会话、状态订阅或这个需求时,
6586
- * 鉴权忙碌的临时拒绝都要按服务端建议重试,否则连接会一直停在断开,直到别的动作碰巧重连。
6587
- */
6588
7336
  connectionDemanded = false;
6589
7337
  constructor(client) {
6590
7338
  this.client = client;
@@ -6597,10 +7345,6 @@ var SessionHub = class {
6597
7345
  on(name, handler) {
6598
7346
  return this.hubEmitter.on(name, handler);
6599
7347
  }
6600
- /**
6601
- * @internal 第一方 Web 的精简模式传输优化:订阅时是否要 token 级事件。
6602
- * 空闲会话立即重订阅,运行中的会话等这一轮结束。
6603
- */
6604
7348
  setStreamTokens(enabled) {
6605
7349
  if (this.streamTokens === enabled) return;
6606
7350
  this.streamTokens = enabled;
@@ -6629,6 +7373,7 @@ var SessionHub = class {
6629
7373
  return;
6630
7374
  }
6631
7375
  this.statusWatchCounts.delete(sessionId);
7376
+ this.statusLogCursors.delete(sessionId);
6632
7377
  if (!this.sessions.has(sessionId)) {
6633
7378
  ;
6634
7379
  this.client.socket().emit("session:unsubscribe", { session_id: sessionId });
@@ -6755,15 +7500,11 @@ var SessionHub = class {
6755
7500
  this.authBusyReconnect.cancel();
6756
7501
  this.bootstrapPending.clear();
6757
7502
  this.statusWatchCounts.clear();
7503
+ this.statusLogCursors.clear();
6758
7504
  for (const session of [...this.sessions.values()]) {
6759
7505
  this.forceRelease(session);
6760
7506
  }
6761
7507
  }
6762
- /**
6763
- * @internal 底层 socket 被换掉时由 BladeClient 调用(如 setBaseUrl)。
6764
- * 事件监听器绑在旧 socket 实例上,换新实例后必须重绑,否则所有
6765
- * turn:* / chat:* 事件都会静默丢弃。
6766
- */
6767
7508
  _handleSocketReplaced() {
6768
7509
  this.authBusyReconnect.cancel();
6769
7510
  this.bound = false;
@@ -6792,14 +7533,9 @@ var SessionHub = class {
6792
7533
  onDispose: (session) => this.release(session)
6793
7534
  };
6794
7535
  }
6795
- /** 记一份持有。 */
6796
7536
  hold(sessionId) {
6797
7537
  this.holdCounts.set(sessionId, (this.holdCounts.get(sessionId) ?? 0) + 1);
6798
7538
  }
6799
- /**
6800
- * 归还一份持有;还剩别的持有者时返回 false,让 AgentSession 保持可用。
6801
- * 归零才退订房间并从路由表移除。
6802
- */
6803
7539
  release(session) {
6804
7540
  const remaining = (this.holdCounts.get(session.sessionId) ?? 0) - 1;
6805
7541
  if (remaining > 0) {
@@ -6816,20 +7552,34 @@ var SessionHub = class {
6816
7552
  ;
6817
7553
  this.client.socket().emit("session:unsubscribe", { session_id: session.sessionId });
6818
7554
  if (this.statusWatchCounts.has(session.sessionId)) this.emitStatusSubscribe([session.sessionId]);
7555
+ else this.statusLogCursors.delete(session.sessionId);
7556
+ if (this.client.options.anonymousSessionId && !this.needsConnection()) this.disconnect();
6819
7557
  return true;
6820
7558
  }
7559
+ statusCursor(sessionId) {
7560
+ let cursor = this.statusLogCursors.get(sessionId);
7561
+ if (!cursor) {
7562
+ cursor = new LogCursor();
7563
+ this.statusLogCursors.set(sessionId, cursor);
7564
+ }
7565
+ return cursor;
7566
+ }
6821
7567
  emitStatusSubscribe(sessionIds) {
6822
7568
  if (sessionIds.length === 0) return;
6823
7569
  const socket = this.client.socket();
7570
+ const payload = (sessionId) => ({
7571
+ session_id: sessionId,
7572
+ mode: "status",
7573
+ ...this.statusCursor(sessionId).subscription()
7574
+ });
6824
7575
  if (sessionIds.length === 1) {
6825
- socket.emit("session:subscribe", { session_id: sessionIds[0], mode: "status" });
7576
+ socket.emit("session:subscribe", payload(sessionIds[0]));
6826
7577
  return;
6827
7578
  }
6828
7579
  socket.emit("session:subscribe_batch", {
6829
- sessions: sessionIds.map((sessionId) => ({ session_id: sessionId, mode: "status" }))
7580
+ sessions: sessionIds.map(payload)
6830
7581
  });
6831
7582
  }
6832
- /** 不管还有几方持有都立即释放(启动失败、切换身份)。 */
6833
7583
  forceRelease(session) {
6834
7584
  this.holdCounts.delete(session.sessionId);
6835
7585
  session.dispose();
@@ -6854,7 +7604,6 @@ var SessionHub = class {
6854
7604
  }
6855
7605
  }
6856
7606
  }
6857
- /** 事件带 session_id 时精确路由;缺省时按当前唯一活跃会话兜底。 */
6858
7607
  route(sessionId) {
6859
7608
  if (sessionId) {
6860
7609
  const session = this.sessions.get(sessionId);
@@ -6923,7 +7672,51 @@ var SessionHub = class {
6923
7672
  socket.on(
6924
7673
  "turn:events",
6925
7674
  (data, acknowledge) => {
7675
+ syncServerTime(this.client, data.server_time);
6926
7676
  acknowledgeTurnEvents(acknowledge);
7677
+ if (data.session_id && (this.statusWatchCounts.has(data.session_id) || this.sessions.has(data.session_id)) && data.events?.some((event) => event.type === "log:record")) {
7678
+ const delivery = this.statusCursor(data.session_id).consume(data.events ?? []);
7679
+ for (const event of delivery.events) {
7680
+ if (event.type === "chat:start") {
7681
+ this.replayPlaybackIds.delete(data.session_id);
7682
+ this.hubEmitter.emit("runStarted", { sessionId: data.session_id });
7683
+ } else if (event.type === "chat:end") {
7684
+ this.hubEmitter.emit("runEnded", {
7685
+ sessionId: data.session_id,
7686
+ status: typeof event.payload.status === "string" ? event.payload.status : void 0,
7687
+ queued: false,
7688
+ replayed: data.replayed === true,
7689
+ ...typeof event.payload.chat_run_id === "string" ? { chatRunId: event.payload.chat_run_id } : {}
7690
+ });
7691
+ }
7692
+ }
7693
+ }
7694
+ if (data.session_id) {
7695
+ for (const event of data.events ?? []) {
7696
+ if (event.type === "log:retract") this.replayPlaybackIds.delete(data.session_id);
7697
+ if (event.type === "replay:state") {
7698
+ this.hubEmitter.emit("sessionUpdated", { sessionId: data.session_id, data: event.payload });
7699
+ }
7700
+ const playbackId = event.payload.playback_id;
7701
+ if (typeof playbackId !== "string") continue;
7702
+ if (event.type === "replay:start") {
7703
+ if (this.replayPlaybackIds.get(data.session_id) !== playbackId) {
7704
+ this.replayPlaybackIds.set(data.session_id, playbackId);
7705
+ this.hubEmitter.emit("runStarted", { sessionId: data.session_id });
7706
+ }
7707
+ } else if (event.type === "replay:end") {
7708
+ const current = this.replayPlaybackIds.get(data.session_id);
7709
+ if (current && current !== playbackId) continue;
7710
+ this.replayPlaybackIds.delete(data.session_id);
7711
+ this.hubEmitter.emit("runEnded", {
7712
+ sessionId: data.session_id,
7713
+ status: typeof event.payload.status === "string" ? event.payload.status : "completed",
7714
+ queued: false,
7715
+ replayed: true
7716
+ });
7717
+ }
7718
+ }
7719
+ }
6927
7720
  for (const session of this.route(data.session_id)) {
6928
7721
  session._handleTurnEvents(data.events ?? [], data.replayed === true);
6929
7722
  }
@@ -7041,40 +7834,16 @@ var SessionHub = class {
7041
7834
  (data) => {
7042
7835
  for (const session of this.route(data.session_id)) {
7043
7836
  for (const task of data.tasks ?? []) {
7044
- const taskId = typeof task.id === "string" ? task.id : typeof task.task_id === "string" ? task.task_id : "";
7045
- if (!taskId) continue;
7046
- session._handleBackgroundTask({
7047
- id: taskId,
7048
- command: typeof task.command === "string" ? task.command : "",
7049
- description: typeof task.description === "string" ? task.description : void 0,
7050
- // Pass the server's status through unchanged. Collapsing it here
7051
- // made subscribers see a successful-looking "done" for tasks the
7052
- // REST API correctly reported as cancelled or lost.
7053
- status: normalizeBackgroundStatus(task.status),
7054
- failureReason: typeof task.failure_reason === "string" ? task.failure_reason : void 0,
7055
- exitCode: typeof task.exit_code === "number" || task.exit_code === null ? task.exit_code : void 0,
7056
- elapsedSeconds: typeof task.elapsed_seconds === "number" ? task.elapsed_seconds : 0,
7057
- output: Array.isArray(task.output_preview) && task.output_preview.length > 0 ? task.output_preview.join("\n") : void 0
7058
- });
7837
+ const completed = completedBackgroundTask(task);
7838
+ if (completed) session._handleBackgroundTask(completed);
7059
7839
  }
7060
7840
  }
7061
7841
  }
7062
7842
  );
7063
7843
  }
7064
7844
  routeArtifact(data, kind) {
7065
- const filePath = typeof data.file_path === "string" ? data.file_path : "";
7066
- if (!filePath) return;
7067
7845
  for (const session of this.route(data.session_id)) {
7068
- session._handleWorkspaceFilesChanged(filePath);
7069
- session._handleArtifact({
7070
- filePath,
7071
- contentType: typeof data.content_type === "string" ? data.content_type : "",
7072
- title: typeof data.title === "string" ? data.title : filePath,
7073
- content: typeof data.content === "string" ? data.content : "",
7074
- oldString: typeof data.old_string === "string" ? data.old_string : void 0,
7075
- newString: typeof data.new_string === "string" ? data.new_string : void 0,
7076
- kind
7077
- });
7846
+ session._handleArtifact(data, kind);
7078
7847
  }
7079
7848
  }
7080
7849
  };
@@ -7105,13 +7874,16 @@ function createSocket(options) {
7105
7874
 
7106
7875
  // src/blade-client.ts
7107
7876
  var REFRESH_PATH = "/api/auth/refresh";
7108
- var BladeClient = class {
7877
+ var BladeClient = class _BladeClient {
7878
+ anonymousClients = /* @__PURE__ */ new Map();
7879
+ toolProviders = /* @__PURE__ */ new Set();
7109
7880
  refreshPromise = null;
7881
+ cookieLogoutPending = 0;
7110
7882
  socketInstance = null;
7111
7883
  socketReplacementListeners = /* @__PURE__ */ new Set();
7112
- /** login() / setToken() 热挂载的令牌,优先级高于构造参数。 */
7113
- runtimeToken = null;
7884
+ runtimeToken = void 0;
7114
7885
  options;
7886
+ sandbox;
7115
7887
  auth;
7116
7888
  headless;
7117
7889
  computers;
@@ -7128,10 +7900,14 @@ var BladeClient = class {
7128
7900
  /** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
7129
7901
  hub;
7130
7902
  constructor(options) {
7903
+ 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)) {
7904
+ throw new Error("\u533F\u540D\u4F1A\u8BDD ID \u5FC5\u987B\u662F UUID v4");
7905
+ }
7131
7906
  this.options = {
7132
7907
  ...options,
7133
7908
  baseUrl: normalizeBaseUrl(options.baseUrl)
7134
7909
  };
7910
+ this.sandbox = new SandboxResource(this);
7135
7911
  this.auth = new AuthResource(this);
7136
7912
  this.headless = new HeadlessResource(this);
7137
7913
  this.computers = new ComputersResource(this);
@@ -7141,10 +7917,16 @@ var BladeClient = class {
7141
7917
  this.chatGroups = new ChatGroupsResource(this);
7142
7918
  this.siteVisits = new SiteVisitsResource(this);
7143
7919
  this.hub = new SessionHub(this);
7144
- if (this.options.token === void 0) {
7145
- this.runtimeToken = readStoredToken(this.options.baseUrl);
7920
+ if (this.options.token === void 0 && !this.options.anonymousSessionId) {
7921
+ this.runtimeToken = readStoredToken(this.options.baseUrl) ?? void 0;
7146
7922
  }
7147
7923
  }
7924
+ /** Backend application tools, callable over an outbound WebSocket. */
7925
+ connectToolProvider(options) {
7926
+ const provider = new ToolProvider(this, options, () => this.toolProviders.delete(provider));
7927
+ this.toolProviders.add(provider);
7928
+ return provider;
7929
+ }
7148
7930
  /**
7149
7931
  * 弹窗授权登录:用户在弹出的授权页点击"许可授权"后自动取得 PAT,
7150
7932
  * REST 与 Socket.IO 立即生效。只在浏览器可用。
@@ -7164,7 +7946,13 @@ var BladeClient = class {
7164
7946
  const previousToken = this.resolveToken();
7165
7947
  this.runtimeToken = token;
7166
7948
  const nextToken = this.resolveToken();
7949
+ if (token === null) for (const provider of this.toolProviders) provider.dispose();
7167
7950
  if (previousToken !== nextToken) {
7951
+ this.options.applicationTransport?.invalidate();
7952
+ for (const provider of this.toolProviders) {
7953
+ if (nextToken) provider.reconnect();
7954
+ else provider.dispose();
7955
+ }
7168
7956
  this.hub.disposeAll();
7169
7957
  }
7170
7958
  writeStoredToken(this.options.baseUrl, token, this.options.tokenStorage ?? "local");
@@ -7197,12 +7985,27 @@ var BladeClient = class {
7197
7985
  get token() {
7198
7986
  return this.resolveRestToken();
7199
7987
  }
7988
+ /** 获取只访问指定匿名会话的客户端;创建仍由已认证的原客户端完成。 */
7989
+ forAnonymousSession(sessionId) {
7990
+ let client = this.anonymousClients.get(sessionId);
7991
+ if (!client) {
7992
+ client = new _BladeClient({ baseUrl: this.options.baseUrl, anonymousSessionId: sessionId, fetchImpl: this.options.fetchImpl, commandTransport: this.options.commandTransport });
7993
+ this.anonymousClients.set(sessionId, client);
7994
+ }
7995
+ return client;
7996
+ }
7200
7997
  setBaseUrl(baseUrl) {
7201
7998
  const nextBaseUrl = normalizeBaseUrl(baseUrl);
7202
7999
  if (nextBaseUrl === this.options.baseUrl) {
7203
8000
  return;
7204
8001
  }
8002
+ for (const provider of this.toolProviders) provider.dispose();
7205
8003
  this.hub.disposeAll();
8004
+ for (const client of this.anonymousClients.values()) {
8005
+ client.hub.disposeAll();
8006
+ client.hub.disconnect();
8007
+ }
8008
+ this.anonymousClients.clear();
7206
8009
  this.options.baseUrl = nextBaseUrl;
7207
8010
  if (this.socketInstance) {
7208
8011
  this.socketInstance.removeAllListeners();
@@ -7227,7 +8030,7 @@ var BladeClient = class {
7227
8030
  }
7228
8031
  socket() {
7229
8032
  if (!this.socketInstance) {
7230
- this.socketInstance = createSocket({
8033
+ this.socketInstance = this.options.applicationTransport?.socket() ?? createSocket({
7231
8034
  baseUrl: this.options.baseUrl,
7232
8035
  token: () => this.resolveSocketToken()
7233
8036
  });
@@ -7242,7 +8045,10 @@ var BladeClient = class {
7242
8045
  else resolve(ack ?? {});
7243
8046
  });
7244
8047
  });
7245
- return this.options.commandTransport?.(event, payload, send) ?? send();
8048
+ const transport = this.options.commandTransport;
8049
+ if (!transport) return send();
8050
+ const handled = this.options.anonymousSessionId ? transport(event, payload, send, { anonymousSessionId: this.options.anonymousSessionId }) : transport(event, payload, send);
8051
+ return handled ?? send();
7246
8052
  }
7247
8053
  async json(method, path, body) {
7248
8054
  const response = await this.fetch(method, path, {
@@ -7300,24 +8106,31 @@ var BladeClient = class {
7300
8106
  }
7301
8107
  async fetch(method, path, init = {}, isRetry = false) {
7302
8108
  const url = this.buildUrl(path);
7303
- const response = await (this.options.fetchImpl ?? fetch)(url.toString(), {
7304
- method,
7305
- body: init.body,
7306
- credentials: init.credentials ?? "include",
7307
- headers: this.buildHeaders(url, init.headers),
7308
- signal: init.signal
7309
- });
7310
- if (response.status === 401 && !isRetry && this.shouldRefreshFor401(url)) {
7311
- const refreshed = await this.tryRefreshUnlessAborted(init.signal);
7312
- if (refreshed) {
7313
- return this.fetch(method, path, init, true);
8109
+ const isLogout = method === "POST" && url.pathname === this.buildUrl("/api/auth/logout").pathname && this.isSameBackendUrl(url);
8110
+ if (isLogout) this.cookieLogoutPending += 1;
8111
+ try {
8112
+ if (isLogout) await this.refreshPromise;
8113
+ const response = await (this.options.fetchImpl ?? fetch)(url.toString(), {
8114
+ method,
8115
+ body: init.body,
8116
+ credentials: init.credentials ?? "include",
8117
+ headers: this.buildHeaders(url, init.headers),
8118
+ signal: init.signal
8119
+ });
8120
+ if (response.status === 401 && !isRetry && this.shouldRefreshFor401(url)) {
8121
+ const refreshed = await this.tryRefreshUnlessAborted(init.signal);
8122
+ if (refreshed) {
8123
+ return this.fetch(method, path, init, true);
8124
+ }
7314
8125
  }
8126
+ if (init.expectOk !== false && !response.ok) {
8127
+ const detail = await extractErrorDetail(response);
8128
+ throw decorateAuthError(new BladeApiError(response, detail));
8129
+ }
8130
+ return response;
8131
+ } finally {
8132
+ if (isLogout) this.cookieLogoutPending -= 1;
7315
8133
  }
7316
- if (init.expectOk !== false && !response.ok) {
7317
- const detail = await extractErrorDetail(response);
7318
- throw decorateAuthError(new BladeApiError(response, detail));
7319
- }
7320
- return response;
7321
8134
  }
7322
8135
  buildAuthedUrl(path) {
7323
8136
  const url = buildBackendUrl(this.options.baseUrl, path);
@@ -7363,13 +8176,6 @@ var BladeClient = class {
7363
8176
  hasExplicitBearerToken() {
7364
8177
  return Boolean(this.resolveRestToken());
7365
8178
  }
7366
- /**
7367
- * 等一次令牌刷新,但调用方中止请求时不再等下去。
7368
- *
7369
- * 刷新本身是共享的(`refreshPromise`,别的请求也在等它),这里只是让这次已被取消的
7370
- * 请求尽早结束——否则它会一直挂在调用方(定位)的串行队列里,用户改选的新目标要等到
7371
- * 刷新结束才轮得到。
7372
- */
7373
8179
  async tryRefreshUnlessAborted(signal) {
7374
8180
  if (!signal) return this.tryRefresh();
7375
8181
  if (signal.aborted) throw new DOMException("The operation was aborted.", "AbortError");
@@ -7385,6 +8191,7 @@ var BladeClient = class {
7385
8191
  }
7386
8192
  }
7387
8193
  async tryRefresh() {
8194
+ if (this.options.anonymousSessionId || this.cookieLogoutPending > 0) return false;
7388
8195
  if (this.refreshPromise) {
7389
8196
  return this.refreshPromise;
7390
8197
  }
@@ -7395,7 +8202,6 @@ var BladeClient = class {
7395
8202
  {
7396
8203
  method: "POST",
7397
8204
  credentials: "include",
7398
- // Refresh remains cookie-only; do not attach the REST bearer.
7399
8205
  headers: { "X-Blade-Client": `${SDK_NAME}/${SDK_VERSION}` }
7400
8206
  }
7401
8207
  );
@@ -7419,14 +8225,16 @@ var BladeClient = class {
7419
8225
  return this.resolveRestToken();
7420
8226
  }
7421
8227
  resolveRestToken() {
8228
+ if (this.options.anonymousSessionId) return `ba-anonymous-${this.options.anonymousSessionId}`;
7422
8229
  return this.resolveToken();
7423
8230
  }
7424
8231
  resolveSocketToken() {
7425
- if (this.runtimeToken) return this.runtimeToken;
8232
+ if (this.options.anonymousSessionId) return `ba-anonymous-${this.options.anonymousSessionId}`;
8233
+ if (this.runtimeToken !== void 0) return this.runtimeToken;
7426
8234
  return resolveTokenOption(this.options.socketToken ?? this.options.token);
7427
8235
  }
7428
8236
  resolveToken() {
7429
- if (this.runtimeToken) return this.runtimeToken;
8237
+ if (this.runtimeToken !== void 0) return this.runtimeToken;
7430
8238
  return resolveTokenOption(this.options.token);
7431
8239
  }
7432
8240
  formDataWithUploadProgress(method, path, form, options, isRetry = false) {
@@ -7523,23 +8331,601 @@ function parseXhrHeaders(rawHeaders) {
7523
8331
  return headers;
7524
8332
  }
7525
8333
 
8334
+ // src/application-client.ts
8335
+ var Events = class {
8336
+ listeners = /* @__PURE__ */ new Map();
8337
+ on(name, listener) {
8338
+ let set = this.listeners.get(name);
8339
+ if (!set) {
8340
+ set = /* @__PURE__ */ new Set();
8341
+ this.listeners.set(name, set);
8342
+ }
8343
+ set.add(listener);
8344
+ return this;
8345
+ }
8346
+ off(name, listener) {
8347
+ if (listener) this.listeners.get(name)?.delete(listener);
8348
+ else this.listeners.delete(name);
8349
+ return this;
8350
+ }
8351
+ once(name, listener) {
8352
+ const wrapped = (...args) => {
8353
+ this.off(name, wrapped);
8354
+ listener(...args);
8355
+ };
8356
+ return this.on(name, wrapped);
8357
+ }
8358
+ fire(name, ...args) {
8359
+ for (const listener of [...this.listeners.get(name) ?? []]) {
8360
+ try {
8361
+ listener(...args);
8362
+ } catch (error) {
8363
+ console.warn(
8364
+ `[agent-client] "${name}" application handler failed:`,
8365
+ error
8366
+ );
8367
+ }
8368
+ }
8369
+ }
8370
+ removeAllListeners() {
8371
+ this.listeners.clear();
8372
+ return this;
8373
+ }
8374
+ };
8375
+ function encode(input) {
8376
+ const attachments = [];
8377
+ const walk = (value) => {
8378
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
8379
+ const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
8380
+ let text = "";
8381
+ for (let offset = 0; offset < bytes.length; offset += 8192)
8382
+ text += String.fromCharCode(...bytes.subarray(offset, offset + 8192));
8383
+ attachments.push(btoa(text));
8384
+ return ["bytes", attachments.length - 1];
8385
+ }
8386
+ if (Array.isArray(value)) return ["list", value.map(walk)];
8387
+ if (value && typeof value === "object")
8388
+ return [
8389
+ "dict",
8390
+ Object.entries(value).map(([key, item]) => [key, walk(item)])
8391
+ ];
8392
+ return ["value", value ?? null];
8393
+ };
8394
+ return { value: walk(input), attachments };
8395
+ }
8396
+ function decode(wire) {
8397
+ const walk = (node) => {
8398
+ if (!Array.isArray(node) || node.length !== 2)
8399
+ throw new Error("Invalid application value");
8400
+ if (node[0] === "value") return node[1];
8401
+ if (node[0] === "bytes")
8402
+ return Uint8Array.from(
8403
+ atob(wire.attachments[node[1]]),
8404
+ (c) => c.charCodeAt(0)
8405
+ );
8406
+ if (node[0] === "list") return node[1].map(walk);
8407
+ if (node[0] === "dict")
8408
+ return Object.fromEntries(
8409
+ node[1].map(([key, value2]) => [key, walk(value2)])
8410
+ );
8411
+ throw new Error("Invalid application value");
8412
+ };
8413
+ const value = walk(wire.value);
8414
+ if (!Array.isArray(value)) throw new Error("Invalid application event");
8415
+ return value;
8416
+ }
8417
+ var BladeApplicationClient = class {
8418
+ constructor(options) {
8419
+ this.options = options;
8420
+ this.channels = [
8421
+ new ApplicationChannel(this.options.baseUrl, this.applicationId),
8422
+ new ApplicationChannel(this.options.baseUrl, this.applicationId)
8423
+ ];
8424
+ }
8425
+ options;
8426
+ channels;
8427
+ clients = /* @__PURE__ */ new Map();
8428
+ applicationId = crypto.randomUUID();
8429
+ forUser(userId, options) {
8430
+ const saved = this.clients.get(userId);
8431
+ if (saved) return saved;
8432
+ let hash = 0;
8433
+ for (const character of userId)
8434
+ hash = hash * 31 + character.charCodeAt(0) | 0;
8435
+ const channel = this.channels[(hash >>> 0) % this.channels.length];
8436
+ const user = new ApplicationUser(channel, userId, () => client);
8437
+ const client = new BladeClient({
8438
+ ...this.options,
8439
+ ...options,
8440
+ tokenStorage: options.tokenStorage ?? "memory",
8441
+ applicationTransport: user
8442
+ });
8443
+ this.clients.set(userId, client);
8444
+ return client;
8445
+ }
8446
+ get connectionCount() {
8447
+ return this.channels.filter((channel) => channel.isConnected).length;
8448
+ }
8449
+ dispose() {
8450
+ for (const client of this.clients.values()) {
8451
+ client.hub.disposeAll();
8452
+ client.logoutToken();
8453
+ }
8454
+ for (const channel of this.channels) channel.dispose();
8455
+ this.clients.clear();
8456
+ }
8457
+ };
8458
+ var ApplicationChannel = class {
8459
+ constructor(baseUrl, applicationId) {
8460
+ this.baseUrl = baseUrl;
8461
+ this.applicationId = applicationId;
8462
+ }
8463
+ baseUrl;
8464
+ applicationId;
8465
+ socket;
8466
+ opening;
8467
+ timer;
8468
+ stopped = false;
8469
+ attempts = 0;
8470
+ controlCount = 0;
8471
+ controlWaiters = [];
8472
+ async controlSlot() {
8473
+ if (this.controlCount >= 16)
8474
+ await new Promise((resolve) => this.controlWaiters.push(resolve));
8475
+ else this.controlCount++;
8476
+ return () => {
8477
+ const next = this.controlWaiters.shift();
8478
+ if (next) next();
8479
+ else this.controlCount--;
8480
+ };
8481
+ }
8482
+ pending = /* @__PURE__ */ new Map();
8483
+ users = /* @__PURE__ */ new Map();
8484
+ get isConnected() {
8485
+ return this.socket?.readyState === WebSocket.OPEN;
8486
+ }
8487
+ ensure() {
8488
+ if (this.stopped)
8489
+ return Promise.reject(new Error("Application client disposed"));
8490
+ if (this.isConnected) return Promise.resolve();
8491
+ if (!this.opening) {
8492
+ const work = this.open();
8493
+ this.opening = work;
8494
+ void work.finally(() => {
8495
+ if (this.opening === work) this.opening = void 0;
8496
+ }).catch(() => {
8497
+ });
8498
+ }
8499
+ return this.opening;
8500
+ }
8501
+ async open() {
8502
+ clearTimeout(this.timer);
8503
+ this.timer = void 0;
8504
+ const candidates = [...this.users.values()].filter(
8505
+ (user2) => user2.demanded && user2.client().token
8506
+ );
8507
+ const user = candidates[this.attempts % candidates.length];
8508
+ let ticket;
8509
+ try {
8510
+ if (!user)
8511
+ throw new Error(
8512
+ "Application channel requires a demanded user credential"
8513
+ );
8514
+ ticket = await user.client().jsonFromInit("/api/app-channels", {
8515
+ method: "POST",
8516
+ body: JSON.stringify({ application_id: this.applicationId }),
8517
+ signal: AbortSignal.timeout(1e4)
8518
+ });
8519
+ } catch (error) {
8520
+ this.schedule();
8521
+ throw error;
8522
+ }
8523
+ if (this.stopped) throw new Error("Application client disposed");
8524
+ const endpoint = buildBackendUrl(this.baseUrl, "/api/app-channels/connect");
8525
+ endpoint.protocol = endpoint.protocol === "https:" ? "wss:" : "ws:";
8526
+ const socket = new WebSocket(endpoint, [
8527
+ "blade-application",
8528
+ `blade-channel.${ticket.token}`
8529
+ ]);
8530
+ this.socket = socket;
8531
+ await new Promise((resolve, reject) => {
8532
+ const timeout = setTimeout(() => {
8533
+ reject(new Error("Application connection timed out"));
8534
+ socket.close();
8535
+ }, 15e3);
8536
+ socket.addEventListener("open", () => {
8537
+ if (this.socket !== socket || this.stopped) return;
8538
+ clearTimeout(timeout);
8539
+ this.attempts = 0;
8540
+ resolve();
8541
+ for (const user2 of this.users.values()) user2.restore();
8542
+ });
8543
+ socket.addEventListener("message", (event) => {
8544
+ if (this.socket !== socket || typeof event.data !== "string") return;
8545
+ try {
8546
+ this.receive(JSON.parse(event.data));
8547
+ } catch {
8548
+ socket.close();
8549
+ }
8550
+ });
8551
+ socket.addEventListener("error", () => {
8552
+ clearTimeout(timeout);
8553
+ reject(new Error("Application connection failed"));
8554
+ });
8555
+ socket.addEventListener("close", () => {
8556
+ clearTimeout(timeout);
8557
+ reject(new Error("Application connection closed"));
8558
+ if (this.socket !== socket) return;
8559
+ this.socket = void 0;
8560
+ for (const request of this.pending.values()) {
8561
+ clearTimeout(request.timer);
8562
+ request.reject(new Error("Application connection closed"));
8563
+ }
8564
+ this.pending.clear();
8565
+ for (const user2 of this.users.values()) user2.lost();
8566
+ this.schedule();
8567
+ });
8568
+ });
8569
+ }
8570
+ schedule() {
8571
+ if (this.stopped || this.timer || ![...this.users.values()].some((user) => user.demanded))
8572
+ return;
8573
+ this.timer = setTimeout(
8574
+ () => {
8575
+ this.timer = void 0;
8576
+ void this.ensure().catch(() => this.schedule());
8577
+ },
8578
+ Math.min(1e4, 500 * 2 ** Math.min(this.attempts++, 5))
8579
+ );
8580
+ }
8581
+ receive(frame) {
8582
+ if (frame.id) {
8583
+ const request = this.pending.get(frame.id);
8584
+ if (!request || request.user !== frame.user) return;
8585
+ this.pending.delete(frame.id);
8586
+ clearTimeout(request.timer);
8587
+ if (frame.error)
8588
+ request.reject(
8589
+ Object.assign(new Error(frame.error.message), {
8590
+ code: frame.error.code,
8591
+ data: {
8592
+ code: frame.error.code,
8593
+ status: frame.error.code === "auth_busy" ? 429 : 401,
8594
+ retry_after: 1
8595
+ }
8596
+ })
8597
+ );
8598
+ else request.resolve(frame.result);
8599
+ return;
8600
+ }
8601
+ const user = frame.user && this.users.get(frame.user);
8602
+ if (user) user.receive(frame);
8603
+ }
8604
+ async request(user, method, params, timeoutMs = 3e4) {
8605
+ const release = method === "socket/event" ? () => {
8606
+ } : await this.controlSlot();
8607
+ try {
8608
+ return await new Promise((resolve, reject) => {
8609
+ const id = crypto.randomUUID();
8610
+ const timer = setTimeout(() => {
8611
+ this.pending.delete(id);
8612
+ reject(new Error("Application request timed out"));
8613
+ }, timeoutMs);
8614
+ this.pending.set(id, { user, resolve, reject, timer });
8615
+ try {
8616
+ this.send({ id, user, method, params });
8617
+ } catch (error) {
8618
+ clearTimeout(timer);
8619
+ this.pending.delete(id);
8620
+ reject(error);
8621
+ }
8622
+ });
8623
+ } finally {
8624
+ release();
8625
+ }
8626
+ }
8627
+ send(frame) {
8628
+ if (!this.isConnected || !this.socket)
8629
+ throw new Error("Application connection unavailable");
8630
+ this.socket.send(JSON.stringify(frame));
8631
+ }
8632
+ dispose() {
8633
+ this.stopped = true;
8634
+ clearTimeout(this.timer);
8635
+ this.timer = void 0;
8636
+ for (const user of this.users.values()) user.lost();
8637
+ this.socket?.close();
8638
+ for (const request of this.pending.values()) {
8639
+ clearTimeout(request.timer);
8640
+ request.reject(new Error("Application client disposed"));
8641
+ }
8642
+ this.pending.clear();
8643
+ this.users.clear();
8644
+ }
8645
+ };
8646
+ var ApplicationUser = class {
8647
+ constructor(channel, userId, client) {
8648
+ this.channel = channel;
8649
+ this.userId = userId;
8650
+ this.client = client;
8651
+ this.events = new ApplicationSocket(this);
8652
+ channel.users.set(this.key, this);
8653
+ }
8654
+ channel;
8655
+ userId;
8656
+ client;
8657
+ key = crypto.randomUUID();
8658
+ events;
8659
+ providers = /* @__PURE__ */ new Map();
8660
+ binding;
8661
+ bindingToken;
8662
+ get demanded() {
8663
+ return this.events.active || this.providers.size > 0;
8664
+ }
8665
+ async bind() {
8666
+ await this.channel.ensure();
8667
+ if (!this.demanded) throw new Error("Application user no longer needed");
8668
+ const token = this.client().token;
8669
+ if (!token) throw new Error("Application user requires a token");
8670
+ if (!this.binding || this.bindingToken !== token) {
8671
+ this.bindingToken = token;
8672
+ const work = this.channel.request(this.key, "user/bind", { user_id: this.userId, token }).then((result) => {
8673
+ if (result.user_id !== this.userId)
8674
+ throw new Error("Application user identity mismatch");
8675
+ if (!this.demanded) {
8676
+ this.binding = void 0;
8677
+ this.bindingToken = void 0;
8678
+ void this.channel.request(this.key, "user/unbind", {}).catch(() => {
8679
+ });
8680
+ throw new Error("Application user no longer needed");
8681
+ }
8682
+ });
8683
+ this.binding = work;
8684
+ void work.catch(() => {
8685
+ if (this.binding === work) this.binding = void 0;
8686
+ });
8687
+ }
8688
+ return this.binding;
8689
+ }
8690
+ socket() {
8691
+ return this.events;
8692
+ }
8693
+ provider() {
8694
+ const provider = new ApplicationProviderSocket(this);
8695
+ this.providers.set(provider.handle, provider);
8696
+ provider.open();
8697
+ return provider;
8698
+ }
8699
+ invalidate() {
8700
+ this.binding = void 0;
8701
+ this.bindingToken = void 0;
8702
+ if (!this.client().token && this.channel.isConnected)
8703
+ void this.channel.request(this.key, "user/unbind", {}).catch(() => {
8704
+ });
8705
+ }
8706
+ restore() {
8707
+ if (this.events.active) this.events.connect();
8708
+ }
8709
+ lost() {
8710
+ this.invalidate();
8711
+ this.events.lost();
8712
+ for (const provider of [...this.providers.values()]) provider.closeLocal();
8713
+ }
8714
+ receive(frame) {
8715
+ if (frame.method === "socket/closed") {
8716
+ this.events.lost();
8717
+ if (this.events.active) this.events.connect();
8718
+ return;
8719
+ }
8720
+ if (frame.method === "tool/closed") {
8721
+ this.providers.get(frame.handle ?? "")?.closeLocal();
8722
+ return;
8723
+ }
8724
+ if (frame.method === "tool/message") {
8725
+ this.providers.get(frame.handle ?? "")?.message(frame.message);
8726
+ return;
8727
+ }
8728
+ if (frame.event && frame.args) {
8729
+ const args = decode(frame.args);
8730
+ if (frame.ack_id)
8731
+ args.push(
8732
+ (...values) => this.channel.send({
8733
+ method: "event/ack",
8734
+ user: this.key,
8735
+ ack_id: frame.ack_id,
8736
+ params: encode(values)
8737
+ })
8738
+ );
8739
+ this.events.fire(frame.event, ...args);
8740
+ }
8741
+ }
8742
+ };
8743
+ var ApplicationSocket = class extends Events {
8744
+ constructor(user) {
8745
+ super();
8746
+ this.user = user;
8747
+ }
8748
+ user;
8749
+ connected = false;
8750
+ active = false;
8751
+ io = new Events();
8752
+ connecting;
8753
+ generation = 0;
8754
+ connect() {
8755
+ this.active = true;
8756
+ if (this.connected || this.connecting) return this;
8757
+ const generation = ++this.generation;
8758
+ const work = this.user.bind().then(() => {
8759
+ if (generation !== this.generation || !this.active) return;
8760
+ return this.user.channel.request(this.user.key, "socket/connect", {});
8761
+ }).then(() => {
8762
+ if (generation !== this.generation || !this.active) return;
8763
+ this.connected = true;
8764
+ this.fire("connect");
8765
+ }).catch((error) => {
8766
+ if (generation === this.generation && this.active) {
8767
+ if (error?.code === "auth_busy" || error?.code === "unauthorized")
8768
+ this.active = false;
8769
+ this.fire("connect_error", error);
8770
+ }
8771
+ }).finally(() => {
8772
+ if (this.connecting === work) this.connecting = void 0;
8773
+ });
8774
+ this.connecting = work;
8775
+ return this;
8776
+ }
8777
+ disconnect() {
8778
+ this.active = false;
8779
+ this.lost();
8780
+ if (this.user.channel.isConnected)
8781
+ void this.user.channel.request(this.user.key, "socket/disconnect", {}).catch(() => {
8782
+ });
8783
+ return this;
8784
+ }
8785
+ lost() {
8786
+ const wasConnected = this.connected;
8787
+ this.connected = false;
8788
+ this.connecting = void 0;
8789
+ this.generation++;
8790
+ if (wasConnected) this.fire("disconnect", "transport close");
8791
+ }
8792
+ emit(event, ...args) {
8793
+ this.send(event, args);
8794
+ return this;
8795
+ }
8796
+ timeout(timeoutMs) {
8797
+ return {
8798
+ emit: (event, ...args) => {
8799
+ this.send(event, args, timeoutMs);
8800
+ return this;
8801
+ }
8802
+ };
8803
+ }
8804
+ send(event, values, timeoutMs) {
8805
+ const args = [...values];
8806
+ const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
8807
+ const generation = this.generation;
8808
+ const deadline = Date.now() + (timeoutMs ?? 3e4);
8809
+ const work = (async () => {
8810
+ if (!this.active) throw new Error("Application user disconnected");
8811
+ if (!this.connected) await this.connecting;
8812
+ if (!this.connected || this.generation !== generation)
8813
+ throw new Error("Application user disconnected");
8814
+ const remaining = deadline - Date.now();
8815
+ if (remaining <= 0) throw new Error("Application request timed out");
8816
+ const params = { event, args: encode(args) };
8817
+ if (!callback) {
8818
+ this.user.channel.send({
8819
+ user: this.user.key,
8820
+ method: "socket/event",
8821
+ params
8822
+ });
8823
+ return;
8824
+ }
8825
+ return this.user.channel.request(
8826
+ this.user.key,
8827
+ "socket/event",
8828
+ params,
8829
+ remaining
8830
+ );
8831
+ })();
8832
+ void work.then(
8833
+ (result) => {
8834
+ if (!callback) return;
8835
+ const values2 = decode(result);
8836
+ if (timeoutMs !== void 0) callback?.(null, ...values2);
8837
+ else callback?.(...values2);
8838
+ },
8839
+ (error) => {
8840
+ if (timeoutMs !== void 0) callback?.(error);
8841
+ else if (callback) callback(error);
8842
+ }
8843
+ );
8844
+ }
8845
+ };
8846
+ var ApplicationProviderSocket = class extends Events {
8847
+ constructor(user) {
8848
+ super();
8849
+ this.user = user;
8850
+ }
8851
+ user;
8852
+ handle = crypto.randomUUID();
8853
+ readyState = WebSocket.CONNECTING;
8854
+ addEventListener(name, listener) {
8855
+ this.on(name, listener);
8856
+ }
8857
+ open() {
8858
+ void this.user.bind().then(
8859
+ () => {
8860
+ if (this.readyState !== WebSocket.CONNECTING) return;
8861
+ this.readyState = WebSocket.OPEN;
8862
+ this.fire("open", new Event("open"));
8863
+ },
8864
+ () => {
8865
+ if (this.readyState !== WebSocket.CLOSED) {
8866
+ this.fire("error", new Event("error"));
8867
+ this.closeLocal();
8868
+ }
8869
+ }
8870
+ );
8871
+ }
8872
+ send(text) {
8873
+ if (this.readyState !== WebSocket.OPEN)
8874
+ throw new Error("Application provider closed");
8875
+ const message = JSON.parse(text);
8876
+ if (message.method === "provider/register") {
8877
+ void this.user.channel.request(this.user.key, "tool/register", {
8878
+ ...message.params,
8879
+ handle: this.handle
8880
+ }).then(
8881
+ (result) => this.message({ jsonrpc: "2.0", id: message.id, result }),
8882
+ (error) => this.message({
8883
+ jsonrpc: "2.0",
8884
+ id: message.id,
8885
+ error: { code: -32001, message: error.message }
8886
+ })
8887
+ );
8888
+ } else
8889
+ this.user.channel.send({
8890
+ method: "tool/result",
8891
+ user: this.user.key,
8892
+ handle: this.handle,
8893
+ message
8894
+ });
8895
+ }
8896
+ message(message) {
8897
+ if (this.readyState === WebSocket.OPEN)
8898
+ this.fire(
8899
+ "message",
8900
+ new MessageEvent("message", { data: JSON.stringify(message) })
8901
+ );
8902
+ }
8903
+ close() {
8904
+ if (this.readyState === WebSocket.CLOSED) return;
8905
+ if (this.user.channel.isConnected)
8906
+ void this.user.channel.request(this.user.key, "tool/unregister", { handle: this.handle }).catch(() => {
8907
+ });
8908
+ this.closeLocal();
8909
+ }
8910
+ closeLocal() {
8911
+ if (this.readyState === WebSocket.CLOSED) return;
8912
+ this.readyState = WebSocket.CLOSED;
8913
+ this.user.providers.delete(this.handle);
8914
+ this.fire("close", new Event("close"));
8915
+ this.removeAllListeners();
8916
+ }
8917
+ };
8918
+
7526
8919
  export {
8920
+ SandboxResource,
8921
+ BladeApiError,
8922
+ ToolProvider,
7527
8923
  SDK_NAME,
7528
8924
  SDK_VERSION,
7529
- BladeApiError,
7530
8925
  isRecord,
7531
- computerState,
7532
- computerOS,
7533
- computerPlatformLabel,
7534
- computerDaemonVersion,
7535
- canToggleComputer,
7536
- sortComputers,
7537
- ComputersResource,
7538
- ModelsResource,
7539
- SessionStatus,
7540
- DEFAULT_REPLAY_SPEED,
7541
- SessionInfo,
7542
- SessionSetupError,
8926
+ normalizePluginRefs,
8927
+ normalizeMessageRefs,
8928
+ normalizeTopicAssetRefs,
7543
8929
  inferToolStatus,
7544
8930
  contextProjectionData,
7545
8931
  getContextDisplayState,
@@ -7557,9 +8943,22 @@ export {
7557
8943
  isAppDevToolUiCard,
7558
8944
  collectInlineToolUiCards,
7559
8945
  collectPreviewToolUiCards,
8946
+ computerState,
8947
+ computerOS,
8948
+ computerPlatformLabel,
8949
+ computerDaemonVersion,
8950
+ sortComputers,
8951
+ ComputersResource,
8952
+ ModelsResource,
8953
+ SessionStatus,
8954
+ DEFAULT_REPLAY_SPEED,
8955
+ SessionInfo,
8956
+ SessionSetupError,
7560
8957
  isEntryGoneError,
7561
8958
  PluginsResource,
7562
8959
  ChatGroupsResource,
8960
+ syncServerTime,
8961
+ getServerTime,
7563
8962
  authBusyRetryDelayMs,
7564
8963
  AuthBusyReconnect,
7565
8964
  prependOlder,
@@ -7604,6 +9003,7 @@ export {
7604
9003
  AgentSession,
7605
9004
  SessionHub,
7606
9005
  createSocket,
7607
- BladeClient
9006
+ BladeClient,
9007
+ BladeApplicationClient
7608
9008
  };
7609
- //# sourceMappingURL=chunk-6PM3EDZ7.js.map
9009
+ //# sourceMappingURL=chunk-FTGPJ7VW.js.map