@blade-hq/agent-client 2612.0.0-beta.2 → 2612.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +203 -19
- package/dist/blade-client.d.ts +13 -1
- package/dist/{chunk-PWJJJATA.js → chunk-J3XXTHHK.js} +1723 -701
- package/dist/chunk-J3XXTHHK.js.map +1 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +9 -1
- package/dist/internal.d.ts +9 -3
- package/dist/internal.js +13 -1
- package/dist/internal.js.map +1 -1
- package/dist/resources/auth.d.ts +3 -1
- package/dist/resources/chat-groups.d.ts +92 -0
- package/dist/resources/headless.d.ts +5 -0
- package/dist/resources/models.d.ts +6 -1
- package/dist/resources/plugins.d.ts +1 -0
- package/dist/resources/sessions.d.ts +96 -11
- package/dist/schemas/context-attachments.d.ts +16 -0
- package/dist/schemas/event.d.ts +2 -0
- package/dist/schemas/message-utils.d.ts +3 -9
- package/dist/schemas/message.d.ts +18 -1
- package/dist/schemas/plugin-refs.d.ts +15 -0
- package/dist/schemas/projection.d.ts +31 -0
- package/dist/schemas/session.d.ts +2 -42
- package/dist/schemas/topic-assets.d.ts +7 -0
- package/dist/server-clock.d.ts +2 -0
- package/dist/session/agent-session.d.ts +51 -7
- package/dist/session/background.d.ts +14 -0
- package/dist/session/events.d.ts +0 -1
- package/dist/session/hub.d.ts +3 -0
- package/dist/session/log-cursor.d.ts +14 -0
- package/dist/session/state.d.ts +2 -1
- package/dist/session/workflow.d.ts +10 -3
- package/dist/shared/projection/builder.d.ts +5 -0
- package/dist/shared/projection/helpers.d.ts +2 -0
- package/dist/shared/projection/index.d.ts +1 -0
- package/dist/shared/projection/workflow.d.ts +24 -0
- package/dist/tool-provider.d.ts +51 -0
- package/dist/types/socket-events.d.ts +179 -6
- package/node_modules/engine.io-client/build/cjs/globals.node.d.ts +2 -3
- package/node_modules/engine.io-client/build/cjs/index.d.ts +3 -3
- package/node_modules/engine.io-client/build/cjs/index.js +1 -4
- package/node_modules/engine.io-client/build/cjs/socket.d.ts +39 -82
- package/node_modules/engine.io-client/build/cjs/socket.js +151 -218
- package/node_modules/engine.io-client/build/cjs/transport.d.ts +1 -1
- package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.d.ts +4 -4
- package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.js +2 -2
- package/node_modules/engine.io-client/build/cjs/transports/polling-xhr.node.js +17 -7
- package/node_modules/engine.io-client/build/cjs/transports/websocket.node.js +17 -7
- package/node_modules/engine.io-client/build/esm/globals.node.d.ts +2 -3
- package/node_modules/engine.io-client/build/esm/index.d.ts +3 -3
- package/node_modules/engine.io-client/build/esm/index.js +1 -2
- package/node_modules/engine.io-client/build/esm/socket.d.ts +39 -82
- package/node_modules/engine.io-client/build/esm/socket.js +141 -206
- package/node_modules/engine.io-client/build/esm/transport.d.ts +1 -1
- package/node_modules/engine.io-client/build/esm/transports/polling-xhr.d.ts +4 -4
- package/node_modules/engine.io-client/build/esm/transports/polling-xhr.js +2 -2
- package/node_modules/engine.io-client/build/esm-debug/globals.node.d.ts +2 -3
- package/node_modules/engine.io-client/build/esm-debug/index.d.ts +3 -3
- package/node_modules/engine.io-client/build/esm-debug/index.js +1 -2
- package/node_modules/engine.io-client/build/esm-debug/socket.d.ts +39 -82
- package/node_modules/engine.io-client/build/esm-debug/socket.js +150 -215
- package/node_modules/engine.io-client/build/esm-debug/transport.d.ts +1 -1
- package/node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.d.ts +4 -4
- package/node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.js +2 -2
- package/node_modules/engine.io-client/dist/engine.io.esm.min.js +3 -3
- package/node_modules/engine.io-client/dist/engine.io.esm.min.js.map +1 -1
- package/node_modules/engine.io-client/dist/engine.io.js +164 -239
- package/node_modules/engine.io-client/dist/engine.io.js.map +1 -1
- package/node_modules/engine.io-client/dist/engine.io.min.js +3 -3
- package/node_modules/engine.io-client/dist/engine.io.min.js.map +1 -1
- package/node_modules/engine.io-client/package.json +2 -2
- package/node_modules/socket.io-parser/build/cjs/binary.js +4 -1
- package/node_modules/socket.io-parser/build/cjs/index.d.ts +14 -4
- package/node_modules/socket.io-parser/build/cjs/index.js +18 -11
- package/node_modules/socket.io-parser/build/esm/binary.js +4 -1
- package/node_modules/socket.io-parser/build/esm/index.d.ts +14 -4
- package/node_modules/socket.io-parser/build/esm/index.js +14 -10
- package/node_modules/socket.io-parser/build/esm-debug/binary.js +4 -1
- package/node_modules/socket.io-parser/build/esm-debug/index.d.ts +14 -4
- package/node_modules/socket.io-parser/build/esm-debug/index.js +14 -10
- package/node_modules/socket.io-parser/package.json +1 -1
- package/node_modules/ws/index.js +15 -6
- package/node_modules/ws/lib/constants.js +1 -0
- package/node_modules/ws/lib/permessage-deflate.js +9 -7
- package/node_modules/ws/lib/receiver.js +37 -0
- package/node_modules/ws/lib/sender.js +6 -1
- package/node_modules/ws/lib/websocket-server.js +18 -6
- package/node_modules/ws/lib/websocket.js +50 -17
- package/node_modules/ws/package.json +8 -3
- package/node_modules/ws/wrapper.mjs +14 -1
- package/package.json +4 -4
- package/public-api.md +335 -28
- package/dist/chunk-PWJJJATA.js.map +0 -1
|
@@ -1,6 +1,219 @@
|
|
|
1
|
+
// src/rest.ts
|
|
2
|
+
var DOCS_URL = "https://blade-hq.github.io/public-skills/";
|
|
3
|
+
var BladeApiError = class extends Error {
|
|
4
|
+
response;
|
|
5
|
+
status;
|
|
6
|
+
statusText;
|
|
7
|
+
constructor(response, message) {
|
|
8
|
+
super(message ?? `\u8BF7\u6C42\u5931\u8D25\uFF08${response.status} ${response.statusText}\uFF09`);
|
|
9
|
+
this.name = "BladeApiError";
|
|
10
|
+
this.response = response;
|
|
11
|
+
this.status = response.status;
|
|
12
|
+
this.statusText = response.statusText;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
async function extractErrorDetail(response) {
|
|
16
|
+
try {
|
|
17
|
+
const cloned = response.clone();
|
|
18
|
+
const contentType = cloned.headers.get("content-type") ?? "";
|
|
19
|
+
if (contentType.includes("application/json")) {
|
|
20
|
+
const data = await cloned.json();
|
|
21
|
+
const detail = data?.detail;
|
|
22
|
+
if (typeof detail === "string" && detail.trim()) return detail;
|
|
23
|
+
if (Array.isArray(detail) && detail.length > 0) {
|
|
24
|
+
const first = detail[0];
|
|
25
|
+
if (first && typeof first.msg === "string") return first.msg;
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
const text = (await cloned.text()).trim();
|
|
29
|
+
if (text) return text;
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
function decorateAuthError(error) {
|
|
36
|
+
if (!(error instanceof BladeApiError)) return error;
|
|
37
|
+
if (error.status === 401) {
|
|
38
|
+
error.message = `${error.message}\uFF08\u672A\u767B\u5F55\u6216\u51ED\u8BC1\u5DF2\u5931\u6548\u3002\u6D4F\u89C8\u5668\u573A\u666F\u8C03\u7528 client.auth.login() \u5F39\u7A97\u767B\u5F55\uFF0C\u81EA\u52A8\u5316\u573A\u666F\u5728 new BladeClient({ token }) \u4F20\u5165 PAT\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
|
|
39
|
+
} else if (error.status === 403) {
|
|
40
|
+
error.message = `${error.message}\uFF08\u5F53\u524D\u8D26\u53F7\u65E0\u6743\u8BBF\u95EE\u8BE5\u8D44\u6E90\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
|
|
41
|
+
}
|
|
42
|
+
return error;
|
|
43
|
+
}
|
|
44
|
+
function buildBackendUrl(baseUrl, path) {
|
|
45
|
+
const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
46
|
+
return new URL(path.startsWith("/") ? path.slice(1) : path, base);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/tool-provider.ts
|
|
50
|
+
var ToolProvider = class {
|
|
51
|
+
constructor(client, options, onDispose) {
|
|
52
|
+
this.client = client;
|
|
53
|
+
this.options = options;
|
|
54
|
+
this.onDispose = onDispose;
|
|
55
|
+
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(options.providerId) || options.capability.length < 16 || options.capability.length > 4096) throw new Error("Invalid tool provider identity");
|
|
56
|
+
if (new Set(options.tools.map((t) => t.name)).size !== options.tools.length || options.tools.length > 256) throw new Error("Duplicate or excessive provider tools");
|
|
57
|
+
this.url = `blade-rpc://${options.providerId}`;
|
|
58
|
+
this.open();
|
|
59
|
+
}
|
|
60
|
+
client;
|
|
61
|
+
options;
|
|
62
|
+
onDispose;
|
|
63
|
+
url;
|
|
64
|
+
socket;
|
|
65
|
+
timer;
|
|
66
|
+
registrationTimer;
|
|
67
|
+
disposed = false;
|
|
68
|
+
registered = false;
|
|
69
|
+
attempts = 0;
|
|
70
|
+
calls = /* @__PURE__ */ new Map();
|
|
71
|
+
waiters = /* @__PURE__ */ new Set();
|
|
72
|
+
/** Resolves only after BA has authenticated and registered this connection. */
|
|
73
|
+
ready() {
|
|
74
|
+
if (this.disposed) return Promise.reject(new Error("Tool provider disposed"));
|
|
75
|
+
if (this.registered) return Promise.resolve();
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const timer = setTimeout(() => {
|
|
78
|
+
this.waiters.delete(waiter);
|
|
79
|
+
reject(new Error("Tool provider registration timed out"));
|
|
80
|
+
}, 25e3);
|
|
81
|
+
const waiter = { resolve: () => {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
resolve();
|
|
84
|
+
}, reject: (e) => {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
reject(e);
|
|
87
|
+
} };
|
|
88
|
+
this.waiters.add(waiter);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
reconnect() {
|
|
92
|
+
if (this.disposed) return;
|
|
93
|
+
clearTimeout(this.timer);
|
|
94
|
+
this.reset();
|
|
95
|
+
this.open();
|
|
96
|
+
}
|
|
97
|
+
reset() {
|
|
98
|
+
clearTimeout(this.registrationTimer);
|
|
99
|
+
this.registered = false;
|
|
100
|
+
const socket = this.socket;
|
|
101
|
+
this.socket = void 0;
|
|
102
|
+
socket?.close();
|
|
103
|
+
for (const controller of this.calls.values()) controller.abort();
|
|
104
|
+
this.calls.clear();
|
|
105
|
+
}
|
|
106
|
+
fail(error) {
|
|
107
|
+
for (const waiter of this.waiters) waiter.reject(error);
|
|
108
|
+
this.waiters.clear();
|
|
109
|
+
try {
|
|
110
|
+
this.options.onError?.(error);
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
open() {
|
|
115
|
+
if (this.disposed) return;
|
|
116
|
+
const endpoint = buildBackendUrl(this.client.options.baseUrl, "/api/app-tools/connect");
|
|
117
|
+
endpoint.protocol = endpoint.protocol === "https:" ? "wss:" : "ws:";
|
|
118
|
+
let socket;
|
|
119
|
+
try {
|
|
120
|
+
const token = this.client.token;
|
|
121
|
+
if (!token) throw new Error("Tool provider requires a token");
|
|
122
|
+
const encoded = btoa(Array.from(new TextEncoder().encode(token), (byte) => String.fromCharCode(byte)).join("")).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
123
|
+
socket = new WebSocket(endpoint, ["blade-rpc", `blade-bearer.${encoded}`]);
|
|
124
|
+
} catch {
|
|
125
|
+
this.fail(new Error("Tool provider WebSocket unavailable"));
|
|
126
|
+
this.schedule();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.socket = socket;
|
|
130
|
+
this.registrationTimer = setTimeout(() => {
|
|
131
|
+
if (this.socket === socket) {
|
|
132
|
+
this.fail(new Error("Tool provider registration timed out"));
|
|
133
|
+
socket.close();
|
|
134
|
+
}
|
|
135
|
+
}, 15e3);
|
|
136
|
+
socket.addEventListener("open", () => {
|
|
137
|
+
if (this.socket !== socket) return;
|
|
138
|
+
socket.send(JSON.stringify({ jsonrpc: "2.0", id: "register", method: "provider/register", params: { providerId: this.options.providerId, capability: this.options.capability } }));
|
|
139
|
+
});
|
|
140
|
+
socket.addEventListener("message", (event) => {
|
|
141
|
+
if (this.socket !== socket || typeof event.data !== "string") return;
|
|
142
|
+
void this.receive(socket, event.data).catch(() => {
|
|
143
|
+
this.fail(new Error("Invalid application tool RPC message"));
|
|
144
|
+
socket.close();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
socket.addEventListener("error", () => {
|
|
148
|
+
if (this.socket === socket) this.fail(new Error("Application tool connection failed"));
|
|
149
|
+
});
|
|
150
|
+
socket.addEventListener("close", () => {
|
|
151
|
+
if (this.socket !== socket) return;
|
|
152
|
+
this.reset();
|
|
153
|
+
this.fail(new Error("Application tool connection closed; unfinished calls will not be replayed"));
|
|
154
|
+
this.schedule();
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
schedule() {
|
|
158
|
+
if (this.disposed) return;
|
|
159
|
+
this.timer = setTimeout(() => this.open(), Math.min(1e4, 500 * 2 ** Math.min(this.attempts++, 5)));
|
|
160
|
+
}
|
|
161
|
+
async receive(socket, text) {
|
|
162
|
+
const message = JSON.parse(text);
|
|
163
|
+
if (message.jsonrpc !== "2.0") throw new Error("Invalid JSON-RPC envelope");
|
|
164
|
+
if (message.id === "register" && !message.method) {
|
|
165
|
+
clearTimeout(this.registrationTimer);
|
|
166
|
+
if (message.error || !message.result?.registered) {
|
|
167
|
+
this.fail(new Error(message.error?.message ?? "Tool provider registration rejected"));
|
|
168
|
+
socket.close();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.registered = true;
|
|
172
|
+
this.attempts = 0;
|
|
173
|
+
for (const waiter of this.waiters) waiter.resolve();
|
|
174
|
+
this.waiters.clear();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (!this.registered) throw new Error("Provider not registered");
|
|
178
|
+
if (message.method === "notifications/cancelled") {
|
|
179
|
+
this.calls.get(String(message.params?.requestId))?.abort();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (!message.id || typeof message.id !== "string" || this.calls.has(message.id)) throw new Error("Invalid RPC request ID");
|
|
183
|
+
const controller = new AbortController();
|
|
184
|
+
this.calls.set(message.id, controller);
|
|
185
|
+
let response = { jsonrpc: "2.0", id: message.id };
|
|
186
|
+
try {
|
|
187
|
+
if (message.method === "tools/list") {
|
|
188
|
+
response.result = { tools: this.options.tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })) };
|
|
189
|
+
} else if (message.method === "tools/call") {
|
|
190
|
+
const params = message.params;
|
|
191
|
+
const tool = this.options.tools.find((t) => t.name === params?.name);
|
|
192
|
+
if (!tool || typeof params?.sessionId !== "string" || !params.sessionId || !params.arguments || typeof params.arguments !== "object" || Array.isArray(params.arguments)) throw new Error("Invalid or unauthorized tool call");
|
|
193
|
+
response.result = await tool.handler(params.arguments, { sessionId: params.sessionId, signal: controller.signal });
|
|
194
|
+
} else {
|
|
195
|
+
response.error = { code: -32601, message: "Method not found" };
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
response = { jsonrpc: "2.0", id: message.id, error: { code: -32e3, message: "Application tool execution failed" } };
|
|
199
|
+
} finally {
|
|
200
|
+
this.calls.delete(message.id);
|
|
201
|
+
}
|
|
202
|
+
if (this.socket === socket && socket.readyState === WebSocket.OPEN && !controller.signal.aborted) socket.send(JSON.stringify(response));
|
|
203
|
+
}
|
|
204
|
+
dispose() {
|
|
205
|
+
if (this.disposed) return;
|
|
206
|
+
this.disposed = true;
|
|
207
|
+
clearTimeout(this.timer);
|
|
208
|
+
this.reset();
|
|
209
|
+
this.fail(new Error("Tool provider disposed"));
|
|
210
|
+
this.onDispose();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
1
214
|
// src/version.ts
|
|
2
215
|
var SDK_NAME = "agent-client";
|
|
3
|
-
var SDK_VERSION = true ? "2612.0.0-beta.
|
|
216
|
+
var SDK_VERSION = true ? "2612.0.0-beta.21" : "1.1.1";
|
|
4
217
|
|
|
5
218
|
// src/auth-login.ts
|
|
6
219
|
function isAuthCallbackMessage(value) {
|
|
@@ -128,54 +341,6 @@ async function loginWithPopup(client, options = {}) {
|
|
|
128
341
|
});
|
|
129
342
|
}
|
|
130
343
|
|
|
131
|
-
// src/rest.ts
|
|
132
|
-
var DOCS_URL = "https://blade-hq.github.io/public-skills/";
|
|
133
|
-
var BladeApiError = class extends Error {
|
|
134
|
-
response;
|
|
135
|
-
status;
|
|
136
|
-
statusText;
|
|
137
|
-
constructor(response, message) {
|
|
138
|
-
super(message ?? `\u8BF7\u6C42\u5931\u8D25\uFF08${response.status} ${response.statusText}\uFF09`);
|
|
139
|
-
this.name = "BladeApiError";
|
|
140
|
-
this.response = response;
|
|
141
|
-
this.status = response.status;
|
|
142
|
-
this.statusText = response.statusText;
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
async function extractErrorDetail(response) {
|
|
146
|
-
try {
|
|
147
|
-
const cloned = response.clone();
|
|
148
|
-
const contentType = cloned.headers.get("content-type") ?? "";
|
|
149
|
-
if (contentType.includes("application/json")) {
|
|
150
|
-
const data = await cloned.json();
|
|
151
|
-
const detail = data?.detail;
|
|
152
|
-
if (typeof detail === "string" && detail.trim()) return detail;
|
|
153
|
-
if (Array.isArray(detail) && detail.length > 0) {
|
|
154
|
-
const first = detail[0];
|
|
155
|
-
if (first && typeof first.msg === "string") return first.msg;
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
const text = (await cloned.text()).trim();
|
|
159
|
-
if (text) return text;
|
|
160
|
-
}
|
|
161
|
-
} catch {
|
|
162
|
-
}
|
|
163
|
-
return void 0;
|
|
164
|
-
}
|
|
165
|
-
function decorateAuthError(error) {
|
|
166
|
-
if (!(error instanceof BladeApiError)) return error;
|
|
167
|
-
if (error.status === 401) {
|
|
168
|
-
error.message = `${error.message}\uFF08\u672A\u767B\u5F55\u6216\u51ED\u8BC1\u5DF2\u5931\u6548\u3002\u6D4F\u89C8\u5668\u573A\u666F\u8C03\u7528 client.auth.login() \u5F39\u7A97\u767B\u5F55\uFF0C\u81EA\u52A8\u5316\u573A\u666F\u5728 new BladeClient({ token }) \u4F20\u5165 PAT\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
|
|
169
|
-
} else if (error.status === 403) {
|
|
170
|
-
error.message = `${error.message}\uFF08\u5F53\u524D\u8D26\u53F7\u65E0\u6743\u8BBF\u95EE\u8BE5\u8D44\u6E90\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
|
|
171
|
-
}
|
|
172
|
-
return error;
|
|
173
|
-
}
|
|
174
|
-
function buildBackendUrl(baseUrl, path) {
|
|
175
|
-
const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
176
|
-
return new URL(path.startsWith("/") ? path.slice(1) : path, base);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
344
|
// src/resources/auth.ts
|
|
180
345
|
var AuthResource = class {
|
|
181
346
|
constructor(client) {
|
|
@@ -214,8 +379,8 @@ var AuthResource = class {
|
|
|
214
379
|
getProviders() {
|
|
215
380
|
return this.client.json("GET", "/api/auth/providers");
|
|
216
381
|
}
|
|
217
|
-
getMe() {
|
|
218
|
-
return this.client.
|
|
382
|
+
getMe(options = {}) {
|
|
383
|
+
return this.client.jsonFromInit("/api/auth/me", { signal: options.signal });
|
|
219
384
|
}
|
|
220
385
|
logout() {
|
|
221
386
|
return this.client.json("POST", "/api/auth/logout");
|
|
@@ -227,404 +392,167 @@ function isRecord(value) {
|
|
|
227
392
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
228
393
|
}
|
|
229
394
|
|
|
230
|
-
// src/
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
395
|
+
// src/session/log-cursor.ts
|
|
396
|
+
function positive(value) {
|
|
397
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
398
|
+
}
|
|
399
|
+
var LogCursor = class {
|
|
400
|
+
sequence = 0;
|
|
401
|
+
epoch = 0;
|
|
402
|
+
deltas = /* @__PURE__ */ new Map();
|
|
403
|
+
subscription() {
|
|
404
|
+
return this.sequence > 0 ? { after_log_seq: this.sequence } : {};
|
|
405
|
+
}
|
|
406
|
+
consume(events) {
|
|
407
|
+
const accepted = [];
|
|
408
|
+
let reload = false;
|
|
409
|
+
for (const event of events) {
|
|
410
|
+
if (event.type === "log:retract") {
|
|
411
|
+
this.deltas.clear();
|
|
412
|
+
reload = true;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (event.type === "log:record") {
|
|
416
|
+
if (!positive(event.log_seq) || event.log_seq <= this.sequence) continue;
|
|
417
|
+
const nested = event.payload.events;
|
|
418
|
+
if (!Array.isArray(nested)) continue;
|
|
419
|
+
if (positive(event.payload.epoch) && event.payload.epoch !== this.epoch) {
|
|
420
|
+
this.epoch = event.payload.epoch;
|
|
421
|
+
this.deltas.clear();
|
|
422
|
+
reload = true;
|
|
423
|
+
}
|
|
424
|
+
accepted.push(...nested);
|
|
425
|
+
if (["step_end", "run_end", "truncate"].includes(String(event.payload.kind))) {
|
|
426
|
+
for (const step of this.deltas.keys()) if (step <= event.log_seq) this.deltas.delete(step);
|
|
427
|
+
}
|
|
428
|
+
this.sequence = event.log_seq;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (event.type === "llm:stream:reset" && positive(event.step_log_seq)) {
|
|
432
|
+
this.deltas.delete(event.step_log_seq);
|
|
433
|
+
}
|
|
434
|
+
if (positive(event.step_log_seq) && positive(event.delta_seq)) {
|
|
435
|
+
if (event.delta_seq <= (this.deltas.get(event.step_log_seq) ?? 0)) continue;
|
|
436
|
+
this.deltas.set(event.step_log_seq, event.delta_seq);
|
|
437
|
+
}
|
|
438
|
+
if (event.type === "log:delta" && Array.isArray(event.payload.events)) {
|
|
439
|
+
accepted.push(...event.payload.events);
|
|
440
|
+
} else {
|
|
441
|
+
accepted.push(event);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return { events: accepted, reload };
|
|
236
445
|
}
|
|
237
|
-
detail;
|
|
238
446
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
447
|
+
|
|
448
|
+
// src/schemas/context-attachments.ts
|
|
449
|
+
function snapshotContextAttachments(value) {
|
|
450
|
+
const snapshot2 = JSON.parse(JSON.stringify(value));
|
|
451
|
+
if (!Array.isArray(snapshot2) || snapshot2.length > 20) throw new Error("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u63D0\u4F9B 20 \u4EFD\u8D44\u6599");
|
|
452
|
+
const ids = /* @__PURE__ */ new Set();
|
|
453
|
+
const validText = (text, limit) => typeof text === "string" && [...text].length > 0 && [...text].length <= limit;
|
|
454
|
+
for (const item of snapshot2) {
|
|
455
|
+
if (!item || !validText(item.id, 128) || !validText(item.label, 512) || !validText(item.content, 64e3) || ids.has(item.id)) throw new Error("\u9644\u52A0\u8D44\u6599\u683C\u5F0F\u9519\u8BEF\u6216 ID \u91CD\u590D");
|
|
456
|
+
ids.add(item.id);
|
|
457
|
+
if (item.data != null && (typeof item.data !== "object" || Array.isArray(item.data))) throw new Error("\u8D44\u6599\u6570\u636E\u5FC5\u987B\u662F JSON \u5BF9\u8C61");
|
|
458
|
+
if (item.files != null && (!Array.isArray(item.files) || item.files.length > 20)) throw new Error("\u8D44\u6599\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF");
|
|
459
|
+
for (const file of item.files ?? []) {
|
|
460
|
+
if (!validText(file.name, 512)) throw new Error("\u8D44\u6599\u6587\u4EF6\u540D\u683C\u5F0F\u9519\u8BEF");
|
|
461
|
+
if (file.session_id == null && file.path == null) continue;
|
|
462
|
+
if (!validText(file.session_id, 128) || !validText(file.path, 2048) || /[\\:]/.test(file.path) || file.path.replace(/^\//, "").split("/").some((part) => !part || part === "." || part === "..")) throw new Error("\u8D44\u6599\u6587\u4EF6\u5FC5\u987B\u4F7F\u7528\u6709\u6548\u7684\u4F1A\u8BDD\u6587\u4EF6\u8DEF\u5F84");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (new TextEncoder().encode(JSON.stringify(snapshot2)).length > 256 * 1024) throw new Error("\u6D88\u606F\u9644\u52A0\u8D44\u6599\u4E0D\u80FD\u8D85\u8FC7 256 KiB");
|
|
466
|
+
return snapshot2;
|
|
467
|
+
}
|
|
468
|
+
function normalizeContextAttachments(value) {
|
|
469
|
+
return Array.isArray(value) ? value : [];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/schemas/plugin-refs.ts
|
|
473
|
+
function normalizePluginRefs(value) {
|
|
474
|
+
if (!Array.isArray(value)) return [];
|
|
475
|
+
const seen = /* @__PURE__ */ new Set();
|
|
476
|
+
return value.flatMap((item) => {
|
|
477
|
+
if (!item || typeof item !== "object" || item.provider !== "plugin" || ![item.plugin, item.server, item.id, item.label].every((part) => typeof part === "string" && part)) return [];
|
|
478
|
+
const key = JSON.stringify([item.plugin, item.server, item.id]);
|
|
479
|
+
if (seen.has(key)) return [];
|
|
480
|
+
seen.add(key);
|
|
481
|
+
return [{ provider: "plugin", plugin: item.plugin, server: item.server, id: item.id, label: item.label }];
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
function normalizeMessageRefs(value) {
|
|
485
|
+
if (!Array.isArray(value)) return [];
|
|
486
|
+
const seen = /* @__PURE__ */ new Set();
|
|
487
|
+
return value.flatMap((item) => {
|
|
488
|
+
if (!item || typeof item !== "object") return [];
|
|
489
|
+
const ref = item.provider === "skill" && typeof item.id === "string" && item.id && typeof item.label === "string" && item.label ? { provider: "skill", id: item.id, label: item.label } : normalizePluginRefs([item])[0];
|
|
490
|
+
if (!ref) return [];
|
|
491
|
+
const key = ref.provider === "skill" ? JSON.stringify([ref.provider, ref.id]) : JSON.stringify([ref.provider, ref.plugin, ref.server, ref.id]);
|
|
492
|
+
if (seen.has(key)) return [];
|
|
493
|
+
seen.add(key);
|
|
494
|
+
return [ref];
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/schemas/topic-assets.ts
|
|
499
|
+
function normalizeTopicAssetRefs(value) {
|
|
500
|
+
if (!Array.isArray(value)) return [];
|
|
501
|
+
const seen = /* @__PURE__ */ new Set();
|
|
502
|
+
return value.flatMap((item) => {
|
|
503
|
+
if (!item || typeof item !== "object" || typeof item.topic_id !== "string" || !item.topic_id || typeof item.path !== "string" || !item.path) return [];
|
|
504
|
+
const key = JSON.stringify([item.topic_id, item.path]);
|
|
505
|
+
if (seen.has(key)) return [];
|
|
506
|
+
seen.add(key);
|
|
507
|
+
return [{ topic_id: item.topic_id, path: item.path }];
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/shared/projection/helpers.ts
|
|
512
|
+
function upsertToolCall(state, toolCallId, toolName, displayName, args) {
|
|
513
|
+
if (!toolCallId) return;
|
|
514
|
+
let found = false;
|
|
515
|
+
for (let i = 0; i < state.toolCalls.length; i++) {
|
|
516
|
+
if (state.toolCalls[i].id !== toolCallId) continue;
|
|
517
|
+
const tc = state.toolCalls[i];
|
|
518
|
+
state.toolCalls[i] = {
|
|
519
|
+
...tc,
|
|
520
|
+
tool_name: toolName || tc.tool_name,
|
|
521
|
+
display_name: displayName || tc.display_name,
|
|
522
|
+
arguments: args || tc.arguments
|
|
523
|
+
};
|
|
524
|
+
found = true;
|
|
525
|
+
break;
|
|
243
526
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
527
|
+
if (!found) {
|
|
528
|
+
state.toolCalls.push({
|
|
529
|
+
id: toolCallId,
|
|
530
|
+
tool_name: toolName,
|
|
531
|
+
display_name: displayName,
|
|
532
|
+
arguments: args,
|
|
533
|
+
status: "pending"
|
|
249
534
|
});
|
|
250
|
-
return this.runTracked(session_id, prompt, options, false);
|
|
251
535
|
}
|
|
252
|
-
|
|
253
|
-
|
|
536
|
+
let blockFound = false;
|
|
537
|
+
for (let i = 0; i < state.blocks.length; i++) {
|
|
538
|
+
const b = state.blocks[i];
|
|
539
|
+
if (b.type !== "tool_use" || b.tool_call_id !== toolCallId) continue;
|
|
540
|
+
state.blocks[i] = {
|
|
541
|
+
...b,
|
|
542
|
+
content: args || b.content,
|
|
543
|
+
tool_name: toolName || b.tool_name,
|
|
544
|
+
display_name: displayName || b.display_name
|
|
545
|
+
};
|
|
546
|
+
blockFound = true;
|
|
547
|
+
break;
|
|
254
548
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
async runTracked(sessionId, prompt, options, returnEnvelope) {
|
|
263
|
-
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
264
|
-
const session_id = sessionId;
|
|
265
|
-
const socket = this.client.socket();
|
|
266
|
-
await ensureSocketConnected(socket, timeoutMs, { session_id });
|
|
267
|
-
return new Promise((resolve, reject) => {
|
|
268
|
-
let settled = false;
|
|
269
|
-
let stopSent = false;
|
|
270
|
-
let started = false;
|
|
271
|
-
let chatRunId = null;
|
|
272
|
-
let systemErrorTimeout = null;
|
|
273
|
-
const timeout = setTimeout(() => {
|
|
274
|
-
fail(new HeadlessError(`headless run timed out after ${timeoutMs}ms`, { session_id, chat_run_id: chatRunId }));
|
|
275
|
-
}, timeoutMs);
|
|
276
|
-
const cleanup = (sendStop) => {
|
|
277
|
-
clearTimeout(timeout);
|
|
278
|
-
if (systemErrorTimeout) {
|
|
279
|
-
clearTimeout(systemErrorTimeout);
|
|
280
|
-
systemErrorTimeout = null;
|
|
281
|
-
}
|
|
282
|
-
socket.off("chat:start", onChatStart);
|
|
283
|
-
socket.off("chat:end", onChatEnd);
|
|
284
|
-
socket.off("system:error", onSystemError);
|
|
285
|
-
if (sendStop && !stopSent) {
|
|
286
|
-
stopSent = true;
|
|
287
|
-
socket.emit("chat:stop", { session_id }, () => void 0);
|
|
288
|
-
}
|
|
289
|
-
socket.emit("session:unsubscribe", { session_id });
|
|
290
|
-
};
|
|
291
|
-
const finish = (value) => {
|
|
292
|
-
if (settled) return;
|
|
293
|
-
settled = true;
|
|
294
|
-
cleanup(false);
|
|
295
|
-
if (returnEnvelope) {
|
|
296
|
-
resolve({ session_id, chat_run_id: chatRunId, result: value });
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
resolve(value);
|
|
300
|
-
};
|
|
301
|
-
const fail = (error) => {
|
|
302
|
-
if (settled) return;
|
|
303
|
-
settled = true;
|
|
304
|
-
cleanup(true);
|
|
305
|
-
reject(error);
|
|
306
|
-
};
|
|
307
|
-
const failWithoutStop = (error) => {
|
|
308
|
-
if (settled) return;
|
|
309
|
-
settled = true;
|
|
310
|
-
cleanup(false);
|
|
311
|
-
reject(error);
|
|
312
|
-
};
|
|
313
|
-
const rejectAfterChatEnd = (error) => {
|
|
314
|
-
if (settled) return;
|
|
315
|
-
settled = true;
|
|
316
|
-
cleanup(false);
|
|
317
|
-
reject(error);
|
|
318
|
-
};
|
|
319
|
-
function onChatStart(payload) {
|
|
320
|
-
if (payload.session_id && payload.session_id !== session_id) return;
|
|
321
|
-
started = true;
|
|
322
|
-
}
|
|
323
|
-
function onSystemError(payload) {
|
|
324
|
-
if (payload.session_id !== session_id) return;
|
|
325
|
-
if (!started) {
|
|
326
|
-
failWithoutStop(new HeadlessError(payload.message || "headless run failed", {
|
|
327
|
-
session_id,
|
|
328
|
-
chat_run_id: chatRunId,
|
|
329
|
-
event: payload
|
|
330
|
-
}));
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
if (systemErrorTimeout) {
|
|
334
|
-
clearTimeout(systemErrorTimeout);
|
|
335
|
-
}
|
|
336
|
-
systemErrorTimeout = setTimeout(() => {
|
|
337
|
-
systemErrorTimeout = null;
|
|
338
|
-
fail(new HeadlessError(payload.message || "headless run failed", {
|
|
339
|
-
session_id,
|
|
340
|
-
chat_run_id: chatRunId,
|
|
341
|
-
event: payload
|
|
342
|
-
}));
|
|
343
|
-
}, SYSTEM_ERROR_CHAT_END_GRACE_MS);
|
|
344
|
-
}
|
|
345
|
-
function onChatEnd(payload) {
|
|
346
|
-
if (payload.session_id && payload.session_id !== session_id) return;
|
|
347
|
-
chatRunId = typeof payload.chat_run_id === "string" ? payload.chat_run_id : chatRunId;
|
|
348
|
-
const result = payload.result;
|
|
349
|
-
if (payload.status === "failed") {
|
|
350
|
-
rejectAfterChatEnd(new HeadlessError(errorMessageFromResult(result) ?? "headless run failed", {
|
|
351
|
-
session_id,
|
|
352
|
-
chat_run_id: chatRunId,
|
|
353
|
-
event: payload
|
|
354
|
-
}));
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
try {
|
|
358
|
-
finish(normalizeResult(result, Boolean(options.schema)));
|
|
359
|
-
} catch (error) {
|
|
360
|
-
rejectAfterChatEnd(error);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
socket.on("chat:start", onChatStart);
|
|
364
|
-
socket.on("chat:end", onChatEnd);
|
|
365
|
-
socket.on("system:error", onSystemError);
|
|
366
|
-
socket.emit("session:subscribe", {
|
|
367
|
-
session_id,
|
|
368
|
-
event_format: "raw"
|
|
369
|
-
});
|
|
370
|
-
socket.emit(
|
|
371
|
-
"chat:send",
|
|
372
|
-
{
|
|
373
|
-
session_id,
|
|
374
|
-
message: prompt,
|
|
375
|
-
headless: true,
|
|
376
|
-
output_schema: options.schema ?? null,
|
|
377
|
-
model: options.model ?? null
|
|
378
|
-
},
|
|
379
|
-
(response) => {
|
|
380
|
-
if (response.status === "accepted") return;
|
|
381
|
-
rejectAfterChatEnd(
|
|
382
|
-
new HeadlessError(response.message ?? "headless run was not accepted", response)
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
);
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
function normalizeResult(result, hasSchema) {
|
|
390
|
-
if (!isRecord(result)) {
|
|
391
|
-
throw new HeadlessError("headless run finished without a result", result);
|
|
392
|
-
}
|
|
393
|
-
const errMsg = typeof result.err_msg === "string" ? result.err_msg : "";
|
|
394
|
-
if (errMsg) {
|
|
395
|
-
throw new HeadlessError(errMsg, result);
|
|
396
|
-
}
|
|
397
|
-
if (!hasSchema) {
|
|
398
|
-
const text = result.result;
|
|
399
|
-
return typeof text === "string" ? text : String(text ?? "");
|
|
400
|
-
}
|
|
401
|
-
const { err_msg: _errMsg, ...businessResult } = result;
|
|
402
|
-
return businessResult;
|
|
403
|
-
}
|
|
404
|
-
function errorMessageFromResult(result) {
|
|
405
|
-
if (isRecord(result) && typeof result.err_msg === "string" && result.err_msg) {
|
|
406
|
-
return result.err_msg;
|
|
407
|
-
}
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
function ensureSocketConnected(socket, timeoutMs, detail) {
|
|
411
|
-
if (socket.connected) {
|
|
412
|
-
return Promise.resolve();
|
|
413
|
-
}
|
|
414
|
-
return new Promise((resolve, reject) => {
|
|
415
|
-
let settled = false;
|
|
416
|
-
const timeout = setTimeout(() => {
|
|
417
|
-
finish(() => reject(new HeadlessError(`socket connection timed out after ${timeoutMs}ms`, detail)));
|
|
418
|
-
}, timeoutMs);
|
|
419
|
-
const onConnect = () => finish(resolve);
|
|
420
|
-
const onConnectError = (error) => {
|
|
421
|
-
finish(() => reject(new HeadlessError("socket connection failed", { ...detail, event: error })));
|
|
422
|
-
};
|
|
423
|
-
const finish = (callback) => {
|
|
424
|
-
if (settled) return;
|
|
425
|
-
settled = true;
|
|
426
|
-
clearTimeout(timeout);
|
|
427
|
-
socket.off("connect", onConnect);
|
|
428
|
-
socket.off("connect_error", onConnectError);
|
|
429
|
-
callback();
|
|
430
|
-
};
|
|
431
|
-
socket.on("connect", onConnect);
|
|
432
|
-
socket.on("connect_error", onConnectError);
|
|
433
|
-
socket.connect();
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// src/resources/computers.ts
|
|
438
|
-
function computerState(computer) {
|
|
439
|
-
if (computer.is_primary) return "primary";
|
|
440
|
-
if (computer.enabled) return "enabled";
|
|
441
|
-
return computer.online ? "idle" : "offline";
|
|
442
|
-
}
|
|
443
|
-
function computerOS(computer) {
|
|
444
|
-
const os = (computer.os ?? "").toLowerCase();
|
|
445
|
-
if (os.startsWith("darwin") || os.startsWith("mac")) return "macos";
|
|
446
|
-
if (os.startsWith("windows")) return "windows";
|
|
447
|
-
if (os.startsWith("linux")) return "linux";
|
|
448
|
-
return "unknown";
|
|
449
|
-
}
|
|
450
|
-
function computerPlatformLabel(computer) {
|
|
451
|
-
const os = (computer.os ?? "").trim();
|
|
452
|
-
const arch = (computer.arch ?? "").trim();
|
|
453
|
-
if (os && arch) return `${os}/${arch}`;
|
|
454
|
-
return os || arch || "";
|
|
455
|
-
}
|
|
456
|
-
function computerDaemonVersion(computer) {
|
|
457
|
-
return (computer.daemon_version ?? "").trim();
|
|
458
|
-
}
|
|
459
|
-
function canToggleComputer(computer) {
|
|
460
|
-
return !computer.is_primary;
|
|
461
|
-
}
|
|
462
|
-
function sortComputers(computers) {
|
|
463
|
-
return [...computers].sort((a, b) => {
|
|
464
|
-
const byCreated = (a.created_at ?? "").localeCompare(b.created_at ?? "");
|
|
465
|
-
if (byCreated !== 0) return byCreated;
|
|
466
|
-
return a.label.localeCompare(b.label);
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
var ComputersResource = class {
|
|
470
|
-
constructor(client) {
|
|
471
|
-
this.client = client;
|
|
472
|
-
}
|
|
473
|
-
client;
|
|
474
|
-
/**
|
|
475
|
-
* 列出当前账号登记过的全部电脑及其在线状态。
|
|
476
|
-
*
|
|
477
|
-
* 首页还没有会话,也要能看到同一份清单——判定各写一份必然分叉,一边的兜底会把
|
|
478
|
-
* 另一边的错误盖住(#1597)。所以账号事实只从这一个接口读。
|
|
479
|
-
*/
|
|
480
|
-
async listAccount() {
|
|
481
|
-
const data = await this.client.json("GET", "/api/daemon/instances");
|
|
482
|
-
return {
|
|
483
|
-
computers: (data.instances ?? []).map((instance) => ({
|
|
484
|
-
...instance,
|
|
485
|
-
online: Boolean(instance.online)
|
|
486
|
-
}))
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
|
|
491
|
-
* 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
|
|
492
|
-
*/
|
|
493
|
-
list(sessionId) {
|
|
494
|
-
return this.client.json(
|
|
495
|
-
"GET",
|
|
496
|
-
`/api/sessions/${encodeURIComponent(sessionId)}/computers`
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* 启动或停止一台电脑。只改这次会话的可用范围,不会碰远程机器,
|
|
501
|
-
* 电脑离线时同样能登记,等它上线就直接可用。
|
|
502
|
-
*/
|
|
503
|
-
setEnabled(sessionId, computer, enabled) {
|
|
504
|
-
return this.client.json(
|
|
505
|
-
"PUT",
|
|
506
|
-
`/api/sessions/${encodeURIComponent(sessionId)}/computers/${encodeURIComponent(computer)}/enabled`,
|
|
507
|
-
{ enabled }
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
// src/resources/models.ts
|
|
513
|
-
var ModelsResource = class {
|
|
514
|
-
constructor(client) {
|
|
515
|
-
this.client = client;
|
|
516
|
-
}
|
|
517
|
-
client;
|
|
518
|
-
list() {
|
|
519
|
-
return this.client.json("GET", "/api/config/models");
|
|
520
|
-
}
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
// src/schemas/session.ts
|
|
524
|
-
import { type } from "arktype";
|
|
525
|
-
var SessionStatus = type(
|
|
526
|
-
"'created' | 'running' | 'completed' | 'failed' | 'interrupted' | 'waiting_for_input'"
|
|
527
|
-
);
|
|
528
|
-
var DEFAULT_REPLAY_SPEED = 5;
|
|
529
|
-
var SessionInfo = type({
|
|
530
|
-
id: "string",
|
|
531
|
-
intent: "string",
|
|
532
|
-
status: SessionStatus,
|
|
533
|
-
created_at: "string",
|
|
534
|
-
updated_at: "string",
|
|
535
|
-
"shared?": "boolean",
|
|
536
|
-
"memory_enabled?": "boolean",
|
|
537
|
-
"is_persistent?": "boolean",
|
|
538
|
-
"initiator?": "'ui' | 'sdk' | 'unknown' | null",
|
|
539
|
-
"is_headless?": "boolean",
|
|
540
|
-
"viewer_role?": "'owner' | 'viewer'",
|
|
541
|
-
"template_id?": "string | null",
|
|
542
|
-
"model?": "string | null",
|
|
543
|
-
"enable_thinking?": "boolean | null",
|
|
544
|
-
"solution_id?": "string | null",
|
|
545
|
-
"solution_ref?": "unknown",
|
|
546
|
-
"biz_role_id?": "string | null",
|
|
547
|
-
"solution?": "unknown",
|
|
548
|
-
"plan_summary?": "string | null",
|
|
549
|
-
"primary_skill_id?": "string | null",
|
|
550
|
-
// ship-attack v2:后端 engine.set_bound_skill 写入的"绑定 skill id"。
|
|
551
|
-
// 未绑定 / 非 ship-attack session 为 null。
|
|
552
|
-
"bound_skill_id?": "string | null",
|
|
553
|
-
"replay_state?": "unknown",
|
|
554
|
-
"is_pinned?": "boolean",
|
|
555
|
-
"pinned_at?": "string | null",
|
|
556
|
-
"is_example?": "boolean",
|
|
557
|
-
"ports?": "unknown",
|
|
558
|
-
"disable_tools?": "string[]",
|
|
559
|
-
"runtime_type?": "string | null",
|
|
560
|
-
"daemon_id?": "string | null",
|
|
561
|
-
"agent_runtime_id?": "string | null",
|
|
562
|
-
"workspace_path?": "string | null",
|
|
563
|
-
"ba_version?": "string | null",
|
|
564
|
-
"sandbox_version?": "string | null",
|
|
565
|
-
"chat_group_id?": "string | null",
|
|
566
|
-
"match?": "unknown"
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
// src/session/definition.ts
|
|
570
|
-
var SessionSetupError = class extends Error {
|
|
571
|
-
constructor(message, stage, sessionId, options) {
|
|
572
|
-
super(message);
|
|
573
|
-
this.stage = stage;
|
|
574
|
-
this.sessionId = sessionId;
|
|
575
|
-
this.cause = options?.cause;
|
|
576
|
-
}
|
|
577
|
-
stage;
|
|
578
|
-
sessionId;
|
|
579
|
-
name = "SessionSetupError";
|
|
580
|
-
cause;
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
// src/shared/projection/helpers.ts
|
|
584
|
-
function upsertToolCall(state, toolCallId, toolName, displayName, args) {
|
|
585
|
-
if (!toolCallId) return;
|
|
586
|
-
let found = false;
|
|
587
|
-
for (let i = 0; i < state.toolCalls.length; i++) {
|
|
588
|
-
if (state.toolCalls[i].id !== toolCallId) continue;
|
|
589
|
-
const tc = state.toolCalls[i];
|
|
590
|
-
state.toolCalls[i] = {
|
|
591
|
-
...tc,
|
|
592
|
-
tool_name: toolName || tc.tool_name,
|
|
593
|
-
display_name: displayName || tc.display_name,
|
|
594
|
-
arguments: args || tc.arguments
|
|
595
|
-
};
|
|
596
|
-
found = true;
|
|
597
|
-
break;
|
|
598
|
-
}
|
|
599
|
-
if (!found) {
|
|
600
|
-
state.toolCalls.push({
|
|
601
|
-
id: toolCallId,
|
|
602
|
-
tool_name: toolName,
|
|
603
|
-
display_name: displayName,
|
|
604
|
-
arguments: args,
|
|
605
|
-
status: "pending"
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
let blockFound = false;
|
|
609
|
-
for (let i = 0; i < state.blocks.length; i++) {
|
|
610
|
-
const b = state.blocks[i];
|
|
611
|
-
if (b.type !== "tool_use" || b.tool_call_id !== toolCallId) continue;
|
|
612
|
-
state.blocks[i] = {
|
|
613
|
-
...b,
|
|
614
|
-
content: args || b.content,
|
|
615
|
-
tool_name: toolName || b.tool_name,
|
|
616
|
-
display_name: displayName || b.display_name
|
|
617
|
-
};
|
|
618
|
-
blockFound = true;
|
|
619
|
-
break;
|
|
620
|
-
}
|
|
621
|
-
if (!blockFound) {
|
|
622
|
-
state.blocks.push({
|
|
623
|
-
type: "tool_use",
|
|
624
|
-
content: args,
|
|
625
|
-
tool_call_id: toolCallId,
|
|
626
|
-
tool_name: toolName || null,
|
|
627
|
-
display_name: displayName || null
|
|
549
|
+
if (!blockFound) {
|
|
550
|
+
state.blocks.push({
|
|
551
|
+
type: "tool_use",
|
|
552
|
+
content: args,
|
|
553
|
+
tool_call_id: toolCallId,
|
|
554
|
+
tool_name: toolName || null,
|
|
555
|
+
display_name: displayName || null
|
|
628
556
|
});
|
|
629
557
|
}
|
|
630
558
|
}
|
|
@@ -1115,6 +1043,10 @@ function normalizeChildPause(pausePayload) {
|
|
|
1115
1043
|
}
|
|
1116
1044
|
return { pauseToolData, childLoopName, childToolCallId, description };
|
|
1117
1045
|
}
|
|
1046
|
+
function attachToolEvidence(call, evidence) {
|
|
1047
|
+
if (!call) return;
|
|
1048
|
+
call.evidence = call.status === "done" && Array.isArray(evidence) ? evidence : void 0;
|
|
1049
|
+
}
|
|
1118
1050
|
|
|
1119
1051
|
// src/schemas/message.ts
|
|
1120
1052
|
function inferToolStatus(resultStr) {
|
|
@@ -1173,8 +1105,8 @@ function snapshot(state, sequence, status) {
|
|
|
1173
1105
|
first_token_ms: state.firstTokenMs,
|
|
1174
1106
|
stream_duration_ms: state.streamDurationMs,
|
|
1175
1107
|
first_token_delivery_ms: state.firstTokenDeliveryMs,
|
|
1176
|
-
started_at: state.modelStartedAt
|
|
1177
|
-
//
|
|
1108
|
+
started_at: state.modelStartedAt,
|
|
1109
|
+
// 未上报时不伪造服务器起点;浏览器时钟不能与服务器时钟混用。
|
|
1178
1110
|
has_reported_timing: state.modelStartedAtReported,
|
|
1179
1111
|
context_window: state.contextWindow,
|
|
1180
1112
|
memory_refs: state.memoryRefs,
|
|
@@ -1316,7 +1248,7 @@ function acknowledgeTurnEvents(acknowledge) {
|
|
|
1316
1248
|
}
|
|
1317
1249
|
function hasChatRunEvent(events) {
|
|
1318
1250
|
return events.some(
|
|
1319
|
-
(event) => event.type !== "llm:first_token:delivered" && event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
|
|
1251
|
+
(event) => event.type !== "llm:first_token:delivered" && event.type !== "session:post_chat_followup" && event.type !== "loop:tool_bridge" && !event.type.startsWith("compaction:")
|
|
1320
1252
|
);
|
|
1321
1253
|
}
|
|
1322
1254
|
function acceptedPostChatFollowupCompletesLatestRun(events, updates) {
|
|
@@ -1556,6 +1488,19 @@ var ClientProjectionBuilder = class {
|
|
|
1556
1488
|
this.seq = 0;
|
|
1557
1489
|
this.syntheticCounter = 0;
|
|
1558
1490
|
}
|
|
1491
|
+
/** A durable snapshot supersedes only this turn's temporary overlay. */
|
|
1492
|
+
acceptDurableTurn(turn) {
|
|
1493
|
+
this.seedSequence(turn.sequence);
|
|
1494
|
+
if (!isConversationTurn(turn)) return;
|
|
1495
|
+
const active = this.activeTurns.get(turn.loop_id);
|
|
1496
|
+
if (active?.turnId === turn.turn_id) this.activeTurns.delete(turn.loop_id);
|
|
1497
|
+
this.turnsById.set(turn.turn_id, turn);
|
|
1498
|
+
const latest = this.latestTurns.get(turn.loop_id);
|
|
1499
|
+
if ((!active || active.turnId === turn.turn_id) && (!latest || latest.turn_id === turn.turn_id || turn.sequence >= latest.sequence)) {
|
|
1500
|
+
this.latestTurns.set(turn.loop_id, turn);
|
|
1501
|
+
this.lastTurnIds.set(turn.loop_id, turn.turn_id);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1559
1504
|
seedSequence(maxSeq) {
|
|
1560
1505
|
if (maxSeq > this.seq) {
|
|
1561
1506
|
this.seq = maxSeq;
|
|
@@ -1802,6 +1747,7 @@ var ClientProjectionBuilder = class {
|
|
|
1802
1747
|
onUserMessage(_loopId, payload) {
|
|
1803
1748
|
const entryId = String(payload.entry_id ?? this.nextTurnId("user"));
|
|
1804
1749
|
const displayContent = payload.display_content ?? payload.content ?? "";
|
|
1750
|
+
const refs = normalizeMessageRefs(payload.refs);
|
|
1805
1751
|
const turn = {
|
|
1806
1752
|
id: entryId,
|
|
1807
1753
|
sequence: this.nextSeq(),
|
|
@@ -1811,6 +1757,9 @@ var ClientProjectionBuilder = class {
|
|
|
1811
1757
|
role: "user",
|
|
1812
1758
|
status: "completed",
|
|
1813
1759
|
blocks: [{ type: "text", content: displayContent }],
|
|
1760
|
+
...refs.length ? { refs } : {},
|
|
1761
|
+
context_attachments: normalizeContextAttachments(payload.context_attachments),
|
|
1762
|
+
topic_asset_refs: normalizeTopicAssetRefs(payload.topic_asset_refs),
|
|
1814
1763
|
tool_calls: [],
|
|
1815
1764
|
model: null,
|
|
1816
1765
|
usage: null,
|
|
@@ -1910,6 +1859,7 @@ var ClientProjectionBuilder = class {
|
|
|
1910
1859
|
return this.syncPatch(state);
|
|
1911
1860
|
}
|
|
1912
1861
|
applyFirstTokenDelivery(state, payload) {
|
|
1862
|
+
this.applyStreamTiming(state, payload);
|
|
1913
1863
|
const firstTokenMs = payload.first_token_ms;
|
|
1914
1864
|
if (state.firstTokenMs === null && typeof firstTokenMs === "number" && firstTokenMs >= 0) {
|
|
1915
1865
|
state.firstTokenMs = Math.round(firstTokenMs);
|
|
@@ -1945,6 +1895,7 @@ var ClientProjectionBuilder = class {
|
|
|
1945
1895
|
const sl = sourceLoopFor(state.loopId, this.loopDescriptions);
|
|
1946
1896
|
const startedAt = typeof payload.started_at === "string" && payload.started_at ? payload.started_at : null;
|
|
1947
1897
|
applyToolResult(state, toolCallId, content, status, durationMs, ranInParallel, sl, startedAt);
|
|
1898
|
+
attachToolEvidence(state.toolCalls.find((call) => call.id === toolCallId), payload.evidence);
|
|
1948
1899
|
const pendingChild = [...this.pendingChildPauses.values()].find(
|
|
1949
1900
|
(pause) => pause.parentLoopName === loopId && pause.parentToolCallId === toolCallId
|
|
1950
1901
|
);
|
|
@@ -2466,6 +2417,7 @@ function projectHistory(entries) {
|
|
|
2466
2417
|
if (owner && toolCall) {
|
|
2467
2418
|
toolCall.status = message._error ? "error" : inferToolStatus(String(message.content ?? ""));
|
|
2468
2419
|
toolCall.result = message.content ?? "";
|
|
2420
|
+
attachToolEvidence(toolCall, message._evidence);
|
|
2469
2421
|
const duration = numberOrNull(message._duration_ms);
|
|
2470
2422
|
if (duration !== null) toolCall.duration_ms = duration;
|
|
2471
2423
|
if (typeof message._started_at === "string") {
|
|
@@ -2493,6 +2445,7 @@ 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,
|
|
@@ -2509,6 +2462,11 @@ function projectHistory(entries) {
|
|
|
2509
2462
|
// root Agent 工具调用兜底配对,工作流 fork 没有,子智能体卡会整体消失)。
|
|
2510
2463
|
parent_fork_tool_call_id: stringOrNull(message._parent_fork_tool_call_id)
|
|
2511
2464
|
};
|
|
2465
|
+
if (role === "user") {
|
|
2466
|
+
const refs = normalizeMessageRefs(message.refs);
|
|
2467
|
+
if (refs.length) turn.refs = refs;
|
|
2468
|
+
turn.context_attachments = normalizeContextAttachments(message.context_attachments);
|
|
2469
|
+
}
|
|
2512
2470
|
const isNativeToolEntry = role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.some(
|
|
2513
2471
|
(toolCall) => isRecord(toolCall) && typeof toolCall.provider === "string"
|
|
2514
2472
|
);
|
|
@@ -2566,6 +2524,33 @@ function projectHistory(entries) {
|
|
|
2566
2524
|
}
|
|
2567
2525
|
continue;
|
|
2568
2526
|
}
|
|
2527
|
+
if (kind === "workflow_step") {
|
|
2528
|
+
const data = entry.data ?? {};
|
|
2529
|
+
const toolCallId = String(data.tool_call_id ?? "");
|
|
2530
|
+
const owner = toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
|
|
2531
|
+
if (owner && toolCallId) {
|
|
2532
|
+
const index = owner.tool_calls.findIndex((call) => call.id === toolCallId);
|
|
2533
|
+
if (index >= 0) {
|
|
2534
|
+
const call = owner.tool_calls[index];
|
|
2535
|
+
owner.tool_calls[index] = {
|
|
2536
|
+
...call,
|
|
2537
|
+
workflow_steps: [
|
|
2538
|
+
...call.workflow_steps ?? [],
|
|
2539
|
+
{
|
|
2540
|
+
step_id: String(data.step_id ?? ""),
|
|
2541
|
+
desc: String(data.desc ?? ""),
|
|
2542
|
+
step_kind: String(data.step_kind ?? "step"),
|
|
2543
|
+
status: String(data.status ?? "done"),
|
|
2544
|
+
output: String(data.output ?? ""),
|
|
2545
|
+
...typeof data.seq === "number" ? { seq: data.seq } : {},
|
|
2546
|
+
...typeof data.log_seq === "number" ? { log_seq: data.log_seq } : {}
|
|
2547
|
+
}
|
|
2548
|
+
]
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
continue;
|
|
2553
|
+
}
|
|
2569
2554
|
if (kind === "mode_change") {
|
|
2570
2555
|
turns.push(markerTurn(id, ++sequence, loopId, "mode_change", entry.data ?? {}));
|
|
2571
2556
|
} else if (kind === "workspace_change") {
|
|
@@ -2616,7 +2601,7 @@ function projectHistory(entries) {
|
|
|
2616
2601
|
let content = isRecord(entry.message) ? entry.message.content : void 0;
|
|
2617
2602
|
if (kind === "tool_result_archive") {
|
|
2618
2603
|
const archivedCount = Array.isArray(data.archived_files) ? data.archived_files.length : 0;
|
|
2619
|
-
content = `<compaction-summary>
|
|
2604
|
+
content = typeof data.summary === "string" && data.summary ? data.summary : `<compaction-summary>
|
|
2620
2605
|
\u5DF2\u5F52\u6863 ${archivedCount} \u4E2A\u5927\u578B\u5DE5\u5177\u7ED3\u679C\u3002
|
|
2621
2606
|
</compaction-summary>`;
|
|
2622
2607
|
}
|
|
@@ -2728,117 +2713,654 @@ function projectHistory(entries) {
|
|
|
2728
2713
|
pauseArguments.questions = resultArguments.questions;
|
|
2729
2714
|
}
|
|
2730
2715
|
}
|
|
2731
|
-
applyAskUserPauseToTurn(
|
|
2732
|
-
rootAssistant,
|
|
2716
|
+
applyAskUserPauseToTurn(
|
|
2717
|
+
rootAssistant,
|
|
2718
|
+
{
|
|
2719
|
+
name: toolCall.tool_name,
|
|
2720
|
+
tool_call_id: toolCall.id,
|
|
2721
|
+
arguments: pauseArguments
|
|
2722
|
+
},
|
|
2723
|
+
toolCall.id,
|
|
2724
|
+
"",
|
|
2725
|
+
"",
|
|
2726
|
+
(name) => name
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
return turns;
|
|
2731
|
+
}
|
|
2732
|
+
function parseToolArguments(value) {
|
|
2733
|
+
if (!value) return {};
|
|
2734
|
+
try {
|
|
2735
|
+
const parsed = JSON.parse(value);
|
|
2736
|
+
return isRecord(parsed) ? parsed : {};
|
|
2737
|
+
} catch {
|
|
2738
|
+
return {};
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
function buildToolCalls(value) {
|
|
2742
|
+
if (!Array.isArray(value)) return [];
|
|
2743
|
+
return value.filter(isRecord).map((raw) => {
|
|
2744
|
+
const fn = isRecord(raw.function) ? raw.function : {};
|
|
2745
|
+
const name = String(fn.name ?? "");
|
|
2746
|
+
return {
|
|
2747
|
+
id: String(raw.id ?? ""),
|
|
2748
|
+
tool_name: name,
|
|
2749
|
+
display_name: String(raw.display_name ?? name),
|
|
2750
|
+
arguments: typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments ?? {}),
|
|
2751
|
+
status: "running",
|
|
2752
|
+
result: null
|
|
2753
|
+
};
|
|
2754
|
+
});
|
|
2755
|
+
}
|
|
2756
|
+
function buildBlocks(message, toolCalls) {
|
|
2757
|
+
const displayContent = message.role === "user" ? message._display_content : void 0;
|
|
2758
|
+
const stored = message._blocks;
|
|
2759
|
+
const blocks = [];
|
|
2760
|
+
const storedToolCallIds = /* @__PURE__ */ new Set();
|
|
2761
|
+
if (displayContent !== void 0) {
|
|
2762
|
+
blocks.push({ type: "text", content: displayContent });
|
|
2763
|
+
} else if (Array.isArray(stored)) {
|
|
2764
|
+
for (const raw of stored) {
|
|
2765
|
+
if (!isRecord(raw)) continue;
|
|
2766
|
+
const type2 = String(raw.type ?? "");
|
|
2767
|
+
if (type2 === "thinking") blocks.push({ type: type2, content: raw.thinking ?? raw.content ?? "" });
|
|
2768
|
+
if (type2 === "text") blocks.push({ type: type2, content: raw.text ?? raw.content ?? "" });
|
|
2769
|
+
if (type2 === "tool_call") {
|
|
2770
|
+
const toolCallId = String(raw.id ?? "");
|
|
2771
|
+
const toolCall = toolCalls.find((candidate) => candidate.id === toolCallId);
|
|
2772
|
+
if (!toolCall) continue;
|
|
2773
|
+
storedToolCallIds.add(toolCallId);
|
|
2774
|
+
blocks.push({
|
|
2775
|
+
type: "tool_use",
|
|
2776
|
+
content: toolCall.arguments,
|
|
2777
|
+
tool_call_id: toolCall.id,
|
|
2778
|
+
tool_name: toolCall.tool_name,
|
|
2779
|
+
display_name: toolCall.display_name
|
|
2780
|
+
});
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
} else if (message.content !== void 0 && message.content !== "") {
|
|
2784
|
+
blocks.push({ type: "text", content: message.content });
|
|
2785
|
+
}
|
|
2786
|
+
for (const toolCall of toolCalls) {
|
|
2787
|
+
if (storedToolCallIds.has(toolCall.id)) continue;
|
|
2788
|
+
blocks.push({
|
|
2789
|
+
type: "tool_use",
|
|
2790
|
+
content: toolCall.arguments,
|
|
2791
|
+
tool_call_id: toolCall.id,
|
|
2792
|
+
tool_name: toolCall.tool_name,
|
|
2793
|
+
display_name: toolCall.display_name
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
return blocks;
|
|
2797
|
+
}
|
|
2798
|
+
function markerTurn(id, sequence, loopId, type2, content, toolCallId = null) {
|
|
2799
|
+
return {
|
|
2800
|
+
id,
|
|
2801
|
+
sequence,
|
|
2802
|
+
turn_id: id,
|
|
2803
|
+
loop_id: loopId,
|
|
2804
|
+
kind: type2,
|
|
2805
|
+
role: "system",
|
|
2806
|
+
status: "completed",
|
|
2807
|
+
blocks: [{ type: type2, content, tool_call_id: toolCallId }],
|
|
2808
|
+
tool_calls: [],
|
|
2809
|
+
model: null,
|
|
2810
|
+
usage: null,
|
|
2811
|
+
duration_ms: 0
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
function stringOrNull(value) {
|
|
2815
|
+
return typeof value === "string" ? value : null;
|
|
2816
|
+
}
|
|
2817
|
+
function numberOrNull(value) {
|
|
2818
|
+
return typeof value === "number" && value >= 0 ? value : null;
|
|
2819
|
+
}
|
|
2820
|
+
function findLatestAssistant(turns, loopId) {
|
|
2821
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
2822
|
+
const turn = turns[index];
|
|
2823
|
+
if (turn?.role === "assistant" && turn.loop_id === loopId) return turn;
|
|
2824
|
+
}
|
|
2825
|
+
return void 0;
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// src/shared/projection/workflow.ts
|
|
2829
|
+
var STEP_KINDS = /* @__PURE__ */ new Set(["agent", "step", "parallel", "choice", "end"]);
|
|
2830
|
+
function workflowEntriesFromSteps(steps) {
|
|
2831
|
+
const items = [];
|
|
2832
|
+
const ordered = steps.every((step) => typeof step.seq === "number");
|
|
2833
|
+
steps.forEach((step, index) => {
|
|
2834
|
+
const base = ordered ? step.seq : index * 2;
|
|
2835
|
+
const stepKind = step.step_kind && STEP_KINDS.has(step.step_kind) ? step.step_kind : "step";
|
|
2836
|
+
items.push({
|
|
2837
|
+
key: base,
|
|
2838
|
+
entry: { kind: "step", stepId: step.step_id, desc: step.desc ?? "", stepKind, status: step.status }
|
|
2839
|
+
});
|
|
2840
|
+
const logKey = ordered && typeof step.log_seq === "number" ? step.log_seq : base + 0.5;
|
|
2841
|
+
const output = step.output ?? "";
|
|
2842
|
+
if (step.status === "done" && stepKind === "step") {
|
|
2843
|
+
const shown = output.length <= 300 ? output : `${output.slice(0, 300)}\u2026(\u5171${output.length}\u5B57)`;
|
|
2844
|
+
items.push({ key: logKey, entry: { kind: "log", text: ` \u2514 \u8F93\u51FA: ${shown || "(\u7A7A)"}`, stepId: step.step_id } });
|
|
2845
|
+
}
|
|
2846
|
+
if (step.status === "error" && output) {
|
|
2847
|
+
items.push({ key: logKey, entry: { kind: "log", text: `\u5931\u8D25: ${output}`, stepId: step.step_id } });
|
|
2848
|
+
}
|
|
2849
|
+
});
|
|
2850
|
+
return items.sort((a, b) => a.key - b.key).map((item) => item.entry);
|
|
2851
|
+
}
|
|
2852
|
+
function workflowRunCancelled(steps) {
|
|
2853
|
+
return steps.some((step) => step.status === "cancelled") && !steps.some((step) => step.status === "error");
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// src/resources/headless.ts
|
|
2857
|
+
var HeadlessError = class extends Error {
|
|
2858
|
+
constructor(message, detail) {
|
|
2859
|
+
super(message);
|
|
2860
|
+
this.detail = detail;
|
|
2861
|
+
this.name = "HeadlessError";
|
|
2862
|
+
}
|
|
2863
|
+
detail;
|
|
2864
|
+
};
|
|
2865
|
+
var SYSTEM_ERROR_CHAT_END_GRACE_MS = 1e3;
|
|
2866
|
+
var CLEANUP_ACK_TIMEOUT_MS = 35e3;
|
|
2867
|
+
var HeadlessResource = class {
|
|
2868
|
+
constructor(client) {
|
|
2869
|
+
this.client = client;
|
|
2870
|
+
}
|
|
2871
|
+
client;
|
|
2872
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
2873
|
+
async run(prompt, options = {}) {
|
|
2874
|
+
const { session_id } = await this.client.sessions.createSessionWithRequest({
|
|
2875
|
+
intent: prompt,
|
|
2876
|
+
model: options.model ?? null
|
|
2877
|
+
});
|
|
2878
|
+
return this.runTracked(session_id, prompt, options, false);
|
|
2879
|
+
}
|
|
2880
|
+
async runInSession(sessionId, prompt, options = {}) {
|
|
2881
|
+
return this.runTracked(sessionId, prompt, options, false);
|
|
2882
|
+
}
|
|
2883
|
+
async runWithSession(prompt, options = {}) {
|
|
2884
|
+
const { session_id } = await this.client.sessions.createSessionWithRequest({
|
|
2885
|
+
intent: prompt,
|
|
2886
|
+
model: options.model ?? null
|
|
2887
|
+
});
|
|
2888
|
+
return this.runTracked(session_id, prompt, options, true);
|
|
2889
|
+
}
|
|
2890
|
+
async runTracked(sessionId, prompt, options, returnEnvelope) {
|
|
2891
|
+
if ((options.refs?.length ?? 0) > 20) throw new HeadlessError("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u9009\u62E9 20 \u4E2A\u5F15\u7528");
|
|
2892
|
+
if ((options.topicAssetRefs?.length ?? 0) > 20) throw new HeadlessError("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u5F15\u7528 20 \u4E2A\u4E3B\u9898\u8D44\u4EA7");
|
|
2893
|
+
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
2894
|
+
const session_id = sessionId;
|
|
2895
|
+
const socket = this.client.socket();
|
|
2896
|
+
await ensureSocketConnected(socket, timeoutMs, { session_id });
|
|
2897
|
+
const cursor = new LogCursor();
|
|
2898
|
+
const subscriptions = this.subscriptions.get(session_id) ?? /* @__PURE__ */ new Set();
|
|
2899
|
+
subscriptions.add(cursor);
|
|
2900
|
+
this.subscriptions.set(session_id, subscriptions);
|
|
2901
|
+
return new Promise((resolve, reject) => {
|
|
2902
|
+
let settled = false;
|
|
2903
|
+
let cleanupPromise;
|
|
2904
|
+
let stopAttempt;
|
|
2905
|
+
let handleLateAck;
|
|
2906
|
+
let acknowledgeSend = () => void 0;
|
|
2907
|
+
const sendAck = new Promise((resolveAck) => {
|
|
2908
|
+
acknowledgeSend = resolveAck;
|
|
2909
|
+
});
|
|
2910
|
+
let started = false;
|
|
2911
|
+
let chatRunId = null;
|
|
2912
|
+
let systemErrorTimeout = null;
|
|
2913
|
+
const beforeAck = /* @__PURE__ */ new Map();
|
|
2914
|
+
const timeout = setTimeout(() => {
|
|
2915
|
+
fail(new HeadlessError(`headless run timed out after ${timeoutMs}ms`, { session_id, chat_run_id: chatRunId }));
|
|
2916
|
+
}, timeoutMs);
|
|
2917
|
+
const stopOwnedRun = (runId, deadline) => {
|
|
2918
|
+
stopAttempt ??= (async () => {
|
|
2919
|
+
const stopped = await waitForCleanupAck(socket, deadline, (ack2) => {
|
|
2920
|
+
socket.emit("chat:stop", { session_id, chat_run_id: runId }, ack2);
|
|
2921
|
+
});
|
|
2922
|
+
if (stopped.status !== "received") return { ...stopped, phase: "stop_ack", chat_run_id: runId };
|
|
2923
|
+
const ack = stopped.ack;
|
|
2924
|
+
const confirmed = isRecord(ack) && ack.ok !== false && (!Object.hasOwn(ack, "chat_run_id") || ack.chat_run_id === runId) && typeof ack.status === "string" && ["stopping", "stopped", "idle", "done", "interrupted"].includes(ack.status);
|
|
2925
|
+
return {
|
|
2926
|
+
status: confirmed ? "confirmed" : "unconfirmed",
|
|
2927
|
+
phase: "stop_ack",
|
|
2928
|
+
chat_run_id: runId,
|
|
2929
|
+
...confirmed ? {} : { reason: "invalid_stop_ack" },
|
|
2930
|
+
ack
|
|
2931
|
+
};
|
|
2932
|
+
})().finally(releaseSubscription).catch((error) => ({
|
|
2933
|
+
status: "unconfirmed",
|
|
2934
|
+
phase: "stop_ack",
|
|
2935
|
+
chat_run_id: runId,
|
|
2936
|
+
reason: "cleanup_failed",
|
|
2937
|
+
error
|
|
2938
|
+
}));
|
|
2939
|
+
return stopAttempt;
|
|
2940
|
+
};
|
|
2941
|
+
const cancelRun = async (deadline) => {
|
|
2942
|
+
if (chatRunId === null) {
|
|
2943
|
+
const sent = await waitForCleanupAck(socket, deadline, (ack) => {
|
|
2944
|
+
void sendAck.then(ack);
|
|
2945
|
+
});
|
|
2946
|
+
if (sent.status !== "received") return { ...sent, phase: "send_ack", chat_run_id: null };
|
|
2947
|
+
const runId = acceptedRunId(sent.ack);
|
|
2948
|
+
if (runId === null) {
|
|
2949
|
+
return { status: "unconfirmed", phase: "send_ack", chat_run_id: null, reason: "no_accepted_run", ack: sent.ack };
|
|
2950
|
+
}
|
|
2951
|
+
chatRunId = runId;
|
|
2952
|
+
}
|
|
2953
|
+
const attempt = stopOwnedRun(chatRunId, deadline);
|
|
2954
|
+
const stopped = await waitForCleanupAck(socket, deadline, (ack) => {
|
|
2955
|
+
void attempt.then(ack);
|
|
2956
|
+
});
|
|
2957
|
+
return stopped.status === "received" ? stopped.ack : { ...stopped, phase: "stop_ack", chat_run_id: chatRunId };
|
|
2958
|
+
};
|
|
2959
|
+
const releaseSubscription = () => {
|
|
2960
|
+
const current = this.subscriptions.get(session_id);
|
|
2961
|
+
current?.delete(cursor);
|
|
2962
|
+
if (current?.size === 0) {
|
|
2963
|
+
this.subscriptions.delete(session_id);
|
|
2964
|
+
socket.emit("session:unsubscribe", { session_id });
|
|
2965
|
+
}
|
|
2966
|
+
};
|
|
2967
|
+
const reportDelivery = (error, delivery) => {
|
|
2968
|
+
let detailUpdated = true;
|
|
2969
|
+
try {
|
|
2970
|
+
if (error instanceof HeadlessError && isRecord(error.detail)) error.detail.stop_delivery = delivery;
|
|
2971
|
+
} catch {
|
|
2972
|
+
detailUpdated = false;
|
|
2973
|
+
}
|
|
2974
|
+
if (delivery.status !== "confirmed" || !detailUpdated) {
|
|
2975
|
+
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 });
|
|
2976
|
+
}
|
|
2977
|
+
};
|
|
2978
|
+
const cleanup = (error) => {
|
|
2979
|
+
cleanupPromise ??= (async () => {
|
|
2980
|
+
clearTimeout(timeout);
|
|
2981
|
+
if (systemErrorTimeout) clearTimeout(systemErrorTimeout);
|
|
2982
|
+
systemErrorTimeout = null;
|
|
2983
|
+
socket.off("chat:start", onChatStart);
|
|
2984
|
+
socket.off("chat:end", onChatEnd);
|
|
2985
|
+
socket.off("system:error", onSystemError);
|
|
2986
|
+
socket.off("turn:events", onTurnEvents);
|
|
2987
|
+
socket.off("connect", subscribe);
|
|
2988
|
+
beforeAck.clear();
|
|
2989
|
+
if (error !== void 0) {
|
|
2990
|
+
const deadline = Date.now() + CLEANUP_ACK_TIMEOUT_MS;
|
|
2991
|
+
handleLateAck = (ack) => {
|
|
2992
|
+
const runId = acceptedRunId(ack);
|
|
2993
|
+
if (runId === null || !socket.connected || stopAttempt) return;
|
|
2994
|
+
const late = Date.now() >= deadline;
|
|
2995
|
+
const current = this.subscriptions.get(session_id) ?? /* @__PURE__ */ new Set();
|
|
2996
|
+
current.add(cursor);
|
|
2997
|
+
this.subscriptions.set(session_id, current);
|
|
2998
|
+
void stopOwnedRun(runId, late ? Date.now() + CLEANUP_ACK_TIMEOUT_MS : deadline).then((delivery) => {
|
|
2999
|
+
if (late) reportDelivery(error, { ...delivery, late: true });
|
|
3000
|
+
}).catch((error2) => {
|
|
3001
|
+
console.warn("[agent-client] late headless stop cleanup failed", { session_id, chat_run_id: runId, error: error2 });
|
|
3002
|
+
});
|
|
3003
|
+
};
|
|
3004
|
+
reportDelivery(error, await cancelRun(deadline));
|
|
3005
|
+
}
|
|
3006
|
+
if (!stopAttempt) releaseSubscription();
|
|
3007
|
+
})();
|
|
3008
|
+
return cleanupPromise;
|
|
3009
|
+
};
|
|
3010
|
+
const finish = (value) => {
|
|
3011
|
+
if (settled) return;
|
|
3012
|
+
settled = true;
|
|
3013
|
+
void cleanup().then(() => resolve(returnEnvelope ? { session_id, chat_run_id: chatRunId, result: value } : value), reject);
|
|
3014
|
+
};
|
|
3015
|
+
const rejectRun = (error, sendStop = false) => {
|
|
3016
|
+
if (settled) return;
|
|
3017
|
+
settled = true;
|
|
3018
|
+
void cleanup(sendStop ? error : void 0).then(() => reject(error), () => reject(error));
|
|
3019
|
+
};
|
|
3020
|
+
const fail = (error) => rejectRun(error, true);
|
|
3021
|
+
const accept = (event) => {
|
|
3022
|
+
if (settled) return;
|
|
3023
|
+
const payload = event.payload;
|
|
3024
|
+
if (event.type === "chat:start") {
|
|
3025
|
+
started = true;
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
if (event.type === "system:error") {
|
|
3029
|
+
const error = new HeadlessError(String(payload.message || "headless run failed"), {
|
|
3030
|
+
session_id,
|
|
3031
|
+
chat_run_id: chatRunId,
|
|
3032
|
+
event: payload
|
|
3033
|
+
});
|
|
3034
|
+
if (!started) {
|
|
3035
|
+
rejectRun(error);
|
|
3036
|
+
} else {
|
|
3037
|
+
if (systemErrorTimeout) clearTimeout(systemErrorTimeout);
|
|
3038
|
+
systemErrorTimeout = setTimeout(() => fail(error), SYSTEM_ERROR_CHAT_END_GRACE_MS);
|
|
3039
|
+
}
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
if (event.type !== "chat:end") return;
|
|
3043
|
+
if (payload.status !== "completed") {
|
|
3044
|
+
rejectRun(new HeadlessError(errorMessageFromResult(payload.result) ?? `headless run ${String(payload.status || "failed")}`, {
|
|
3045
|
+
session_id,
|
|
3046
|
+
chat_run_id: chatRunId,
|
|
3047
|
+
event: payload
|
|
3048
|
+
}));
|
|
3049
|
+
return;
|
|
3050
|
+
}
|
|
3051
|
+
try {
|
|
3052
|
+
finish(normalizeResult(payload.result, Boolean(options.schema)));
|
|
3053
|
+
} catch (error) {
|
|
3054
|
+
rejectRun(error);
|
|
3055
|
+
}
|
|
3056
|
+
};
|
|
3057
|
+
const observe = (type2, payload) => {
|
|
3058
|
+
if (settled || payload.session_id && payload.session_id !== session_id) return;
|
|
3059
|
+
const runId = payload.chat_run_id;
|
|
3060
|
+
const event = { type: type2, payload };
|
|
3061
|
+
if (typeof runId !== "string" || !runId) {
|
|
3062
|
+
if (!runId && chatRunId === null && type2 === "system:error" && payload.session_id === session_id && !beforeAck.has("")) {
|
|
3063
|
+
beforeAck.set("", [event]);
|
|
3064
|
+
}
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
if (chatRunId === null) {
|
|
3068
|
+
const pending = beforeAck.get(runId) ?? [];
|
|
3069
|
+
pending.push(event);
|
|
3070
|
+
beforeAck.set(runId, pending);
|
|
3071
|
+
} else if (runId === chatRunId) {
|
|
3072
|
+
accept(event);
|
|
3073
|
+
}
|
|
3074
|
+
};
|
|
3075
|
+
function onChatStart(payload) {
|
|
3076
|
+
observe("chat:start", payload);
|
|
3077
|
+
}
|
|
3078
|
+
function onChatEnd(payload) {
|
|
3079
|
+
observe("chat:end", payload);
|
|
3080
|
+
}
|
|
3081
|
+
function onSystemError(payload) {
|
|
3082
|
+
observe("system:error", payload);
|
|
3083
|
+
}
|
|
3084
|
+
function onTurnEvents(data, acknowledge) {
|
|
3085
|
+
acknowledgeTurnEvents(acknowledge);
|
|
3086
|
+
if (data.session_id !== session_id) return;
|
|
3087
|
+
for (const event of cursor.consume(data.events.map((event2) => ({
|
|
3088
|
+
...event2,
|
|
3089
|
+
log_seq: event2.log_seq ?? void 0,
|
|
3090
|
+
step_log_seq: event2.step_log_seq ?? void 0,
|
|
3091
|
+
delta_seq: event2.delta_seq ?? void 0
|
|
3092
|
+
}))).events) {
|
|
3093
|
+
if (["chat:start", "chat:end", "system:error"].includes(event.type)) observe(event.type, event.payload);
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
function subscribe() {
|
|
3097
|
+
if (settled) return;
|
|
3098
|
+
const after = Math.min(...[...subscriptions].map((item) => item.subscription().after_log_seq ?? 0));
|
|
3099
|
+
socket.emit("session:subscribe", {
|
|
3100
|
+
session_id,
|
|
3101
|
+
event_format: "raw",
|
|
3102
|
+
...after > 0 ? { after_log_seq: after } : {}
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
socket.on("chat:start", onChatStart);
|
|
3106
|
+
socket.on("chat:end", onChatEnd);
|
|
3107
|
+
socket.on("system:error", onSystemError);
|
|
3108
|
+
socket.on("turn:events", onTurnEvents);
|
|
3109
|
+
socket.on("connect", subscribe);
|
|
3110
|
+
subscribe();
|
|
3111
|
+
socket.emit(
|
|
3112
|
+
"chat:send",
|
|
2733
3113
|
{
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
3114
|
+
session_id,
|
|
3115
|
+
message: prompt,
|
|
3116
|
+
headless: true,
|
|
3117
|
+
output_schema: options.schema ?? null,
|
|
3118
|
+
model: options.model ?? null,
|
|
3119
|
+
refs: options.refs,
|
|
3120
|
+
topic_asset_refs: options.topicAssetRefs
|
|
2737
3121
|
},
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
3122
|
+
(response) => {
|
|
3123
|
+
acknowledgeSend(response);
|
|
3124
|
+
if (settled) {
|
|
3125
|
+
handleLateAck?.(response);
|
|
3126
|
+
return;
|
|
3127
|
+
}
|
|
3128
|
+
if (response?.status !== "accepted") {
|
|
3129
|
+
const validationError = beforeAck.get("")?.[0];
|
|
3130
|
+
if ((response == null || isRecord(response) && Object.keys(response).length === 0) && validationError) {
|
|
3131
|
+
accept(validationError);
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
rejectRun(new HeadlessError(response?.message ?? "headless run was not accepted", response));
|
|
3135
|
+
return;
|
|
3136
|
+
}
|
|
3137
|
+
if (typeof response.chat_run_id !== "string" || !response.chat_run_id) {
|
|
3138
|
+
rejectRun(new HeadlessError("headless run acknowledgement has no run ID", response));
|
|
3139
|
+
return;
|
|
3140
|
+
}
|
|
3141
|
+
chatRunId = response.chat_run_id;
|
|
3142
|
+
const pending = beforeAck.get(chatRunId) ?? [];
|
|
3143
|
+
beforeAck.clear();
|
|
3144
|
+
for (const event of pending) accept(event);
|
|
3145
|
+
}
|
|
2742
3146
|
);
|
|
2743
|
-
}
|
|
3147
|
+
});
|
|
2744
3148
|
}
|
|
2745
|
-
|
|
3149
|
+
};
|
|
3150
|
+
function normalizeResult(result, hasSchema) {
|
|
3151
|
+
if (!isRecord(result)) {
|
|
3152
|
+
throw new HeadlessError("headless run finished without a result", result);
|
|
3153
|
+
}
|
|
3154
|
+
const errMsg = typeof result.err_msg === "string" ? result.err_msg : "";
|
|
3155
|
+
if (errMsg) {
|
|
3156
|
+
throw new HeadlessError(errMsg, result);
|
|
3157
|
+
}
|
|
3158
|
+
if (!hasSchema) {
|
|
3159
|
+
const text = result.result;
|
|
3160
|
+
return typeof text === "string" ? text : String(text ?? "");
|
|
3161
|
+
}
|
|
3162
|
+
const { err_msg: _errMsg, ...businessResult } = result;
|
|
3163
|
+
return businessResult;
|
|
2746
3164
|
}
|
|
2747
|
-
function
|
|
2748
|
-
if (
|
|
2749
|
-
|
|
2750
|
-
const parsed = JSON.parse(value);
|
|
2751
|
-
return isRecord(parsed) ? parsed : {};
|
|
2752
|
-
} catch {
|
|
2753
|
-
return {};
|
|
3165
|
+
function errorMessageFromResult(result) {
|
|
3166
|
+
if (isRecord(result) && typeof result.err_msg === "string" && result.err_msg) {
|
|
3167
|
+
return result.err_msg;
|
|
2754
3168
|
}
|
|
3169
|
+
return null;
|
|
2755
3170
|
}
|
|
2756
|
-
function
|
|
2757
|
-
if (
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
3171
|
+
function ensureSocketConnected(socket, timeoutMs, detail) {
|
|
3172
|
+
if (socket.connected) {
|
|
3173
|
+
return Promise.resolve();
|
|
3174
|
+
}
|
|
3175
|
+
return new Promise((resolve, reject) => {
|
|
3176
|
+
let settled = false;
|
|
3177
|
+
const timeout = setTimeout(() => {
|
|
3178
|
+
finish(() => reject(new HeadlessError(`socket connection timed out after ${timeoutMs}ms`, detail)));
|
|
3179
|
+
}, timeoutMs);
|
|
3180
|
+
const onConnect = () => finish(resolve);
|
|
3181
|
+
const onConnectError = (error) => {
|
|
3182
|
+
finish(() => reject(new HeadlessError("socket connection failed", { ...detail, event: error })));
|
|
3183
|
+
};
|
|
3184
|
+
const finish = (callback) => {
|
|
3185
|
+
if (settled) return;
|
|
3186
|
+
settled = true;
|
|
3187
|
+
clearTimeout(timeout);
|
|
3188
|
+
socket.off("connect", onConnect);
|
|
3189
|
+
socket.off("connect_error", onConnectError);
|
|
3190
|
+
callback();
|
|
2768
3191
|
};
|
|
3192
|
+
socket.on("connect", onConnect);
|
|
3193
|
+
socket.on("connect_error", onConnectError);
|
|
3194
|
+
socket.connect();
|
|
2769
3195
|
});
|
|
2770
3196
|
}
|
|
2771
|
-
function
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
if (
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
tool_name: toolCall.tool_name,
|
|
2794
|
-
display_name: toolCall.display_name
|
|
2795
|
-
});
|
|
2796
|
-
}
|
|
3197
|
+
function acceptedRunId(ack) {
|
|
3198
|
+
return isRecord(ack) && ack.status === "accepted" && typeof ack.chat_run_id === "string" && ack.chat_run_id ? ack.chat_run_id : null;
|
|
3199
|
+
}
|
|
3200
|
+
function waitForCleanupAck(socket, deadline, request) {
|
|
3201
|
+
if (!socket.connected) return Promise.resolve({ status: "unconfirmed", reason: "disconnected" });
|
|
3202
|
+
if (Date.now() >= deadline) return Promise.resolve({ status: "unconfirmed", reason: "ack_timeout" });
|
|
3203
|
+
return new Promise((resolve) => {
|
|
3204
|
+
const timer = setTimeout(() => finish({ status: "unconfirmed", reason: "ack_timeout" }), Math.max(0, deadline - Date.now()));
|
|
3205
|
+
let finished = false;
|
|
3206
|
+
const finish = (outcome) => {
|
|
3207
|
+
if (finished) return;
|
|
3208
|
+
finished = true;
|
|
3209
|
+
clearTimeout(timer);
|
|
3210
|
+
socket.off("disconnect", onDisconnect);
|
|
3211
|
+
resolve(outcome);
|
|
3212
|
+
};
|
|
3213
|
+
const onDisconnect = () => finish({ status: "unconfirmed", reason: "disconnected" });
|
|
3214
|
+
socket.on("disconnect", onDisconnect);
|
|
3215
|
+
try {
|
|
3216
|
+
request((ack) => finish({ status: "received", ack }));
|
|
3217
|
+
} catch (error) {
|
|
3218
|
+
finish({ status: "unconfirmed", reason: "emit_failed", error });
|
|
2797
3219
|
}
|
|
2798
|
-
}
|
|
2799
|
-
blocks.push({ type: "text", content: message.content });
|
|
2800
|
-
}
|
|
2801
|
-
for (const toolCall of toolCalls) {
|
|
2802
|
-
if (storedToolCallIds.has(toolCall.id)) continue;
|
|
2803
|
-
blocks.push({
|
|
2804
|
-
type: "tool_use",
|
|
2805
|
-
content: toolCall.arguments,
|
|
2806
|
-
tool_call_id: toolCall.id,
|
|
2807
|
-
tool_name: toolCall.tool_name,
|
|
2808
|
-
display_name: toolCall.display_name
|
|
2809
|
-
});
|
|
2810
|
-
}
|
|
2811
|
-
return blocks;
|
|
3220
|
+
});
|
|
2812
3221
|
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
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
|
-
};
|
|
3222
|
+
|
|
3223
|
+
// src/resources/computers.ts
|
|
3224
|
+
function computerState(computer) {
|
|
3225
|
+
if (computer.is_primary) return "primary";
|
|
3226
|
+
if (computer.enabled) return "enabled";
|
|
3227
|
+
return computer.online ? "idle" : "offline";
|
|
2828
3228
|
}
|
|
2829
|
-
function
|
|
2830
|
-
|
|
3229
|
+
function computerOS(computer) {
|
|
3230
|
+
const os = (computer.os ?? "").toLowerCase();
|
|
3231
|
+
if (os.startsWith("darwin") || os.startsWith("mac")) return "macos";
|
|
3232
|
+
if (os.startsWith("windows")) return "windows";
|
|
3233
|
+
if (os.startsWith("linux")) return "linux";
|
|
3234
|
+
return "unknown";
|
|
2831
3235
|
}
|
|
2832
|
-
function
|
|
2833
|
-
|
|
3236
|
+
function computerPlatformLabel(computer) {
|
|
3237
|
+
const os = (computer.os ?? "").trim();
|
|
3238
|
+
const arch = (computer.arch ?? "").trim();
|
|
3239
|
+
if (os && arch) return `${os}/${arch}`;
|
|
3240
|
+
return os || arch || "";
|
|
2834
3241
|
}
|
|
2835
|
-
function
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
3242
|
+
function computerDaemonVersion(computer) {
|
|
3243
|
+
return (computer.daemon_version ?? "").trim();
|
|
3244
|
+
}
|
|
3245
|
+
function canToggleComputer(computer) {
|
|
3246
|
+
return !computer.is_primary;
|
|
3247
|
+
}
|
|
3248
|
+
function sortComputers(computers) {
|
|
3249
|
+
return [...computers].sort((a, b) => {
|
|
3250
|
+
const byCreated = (a.created_at ?? "").localeCompare(b.created_at ?? "");
|
|
3251
|
+
if (byCreated !== 0) return byCreated;
|
|
3252
|
+
return a.label.localeCompare(b.label);
|
|
3253
|
+
});
|
|
3254
|
+
}
|
|
3255
|
+
var ComputersResource = class {
|
|
3256
|
+
constructor(client) {
|
|
3257
|
+
this.client = client;
|
|
3258
|
+
}
|
|
3259
|
+
client;
|
|
3260
|
+
/**
|
|
3261
|
+
* 列出当前账号登记过的全部电脑及其在线状态。
|
|
3262
|
+
*
|
|
3263
|
+
* 首页还没有会话,也要能看到同一份清单——判定各写一份必然分叉,一边的兜底会把
|
|
3264
|
+
* 另一边的错误盖住(#1597)。所以账号事实只从这一个接口读。
|
|
3265
|
+
*/
|
|
3266
|
+
async listAccount() {
|
|
3267
|
+
const data = await this.client.json("GET", "/api/daemon/instances");
|
|
3268
|
+
return {
|
|
3269
|
+
computers: (data.instances ?? []).map((instance) => ({
|
|
3270
|
+
...instance,
|
|
3271
|
+
online: Boolean(instance.online)
|
|
3272
|
+
}))
|
|
3273
|
+
};
|
|
3274
|
+
}
|
|
3275
|
+
/**
|
|
3276
|
+
* 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
|
|
3277
|
+
* 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
|
|
3278
|
+
*/
|
|
3279
|
+
list(sessionId) {
|
|
3280
|
+
return this.client.json(
|
|
3281
|
+
"GET",
|
|
3282
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/computers`
|
|
3283
|
+
);
|
|
3284
|
+
}
|
|
3285
|
+
/**
|
|
3286
|
+
* 启动或停止一台电脑。只改这次会话的可用范围,不会碰远程机器,
|
|
3287
|
+
* 电脑离线时同样能登记,等它上线就直接可用。
|
|
3288
|
+
*/
|
|
3289
|
+
setEnabled(sessionId, computer, enabled) {
|
|
3290
|
+
return this.client.json(
|
|
3291
|
+
"PUT",
|
|
3292
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/computers/${encodeURIComponent(computer)}/enabled`,
|
|
3293
|
+
{ enabled }
|
|
3294
|
+
);
|
|
3295
|
+
}
|
|
3296
|
+
};
|
|
3297
|
+
|
|
3298
|
+
// src/resources/models.ts
|
|
3299
|
+
var ModelsResource = class {
|
|
3300
|
+
constructor(client) {
|
|
3301
|
+
this.client = client;
|
|
3302
|
+
}
|
|
3303
|
+
client;
|
|
3304
|
+
list(options = {}) {
|
|
3305
|
+
return this.client.jsonFromInit("/api/config/models", { signal: options.signal });
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
3308
|
+
|
|
3309
|
+
// src/schemas/session.ts
|
|
3310
|
+
import { type } from "arktype";
|
|
3311
|
+
var SessionStatus = type(
|
|
3312
|
+
"'created' | 'running' | 'completed' | 'failed' | 'interrupted' | 'waiting_for_input'"
|
|
3313
|
+
);
|
|
3314
|
+
var DEFAULT_REPLAY_SPEED = 5;
|
|
3315
|
+
var SessionInfo = type({
|
|
3316
|
+
id: "string",
|
|
3317
|
+
intent: "string",
|
|
3318
|
+
status: SessionStatus,
|
|
3319
|
+
created_at: "string",
|
|
3320
|
+
updated_at: "string",
|
|
3321
|
+
"shared?": "boolean",
|
|
3322
|
+
"anonymous?": "boolean",
|
|
3323
|
+
"memory_enabled?": "boolean",
|
|
3324
|
+
"is_persistent?": "boolean",
|
|
3325
|
+
"initiator?": "'ui' | 'sdk' | 'unknown' | null",
|
|
3326
|
+
"is_headless?": "boolean",
|
|
3327
|
+
"viewer_role?": "'owner' | 'viewer'",
|
|
3328
|
+
"model?": "string | null",
|
|
3329
|
+
"enable_thinking?": "boolean | null",
|
|
3330
|
+
"solution_id?": "string | null",
|
|
3331
|
+
"solution_ref?": "unknown",
|
|
3332
|
+
"biz_role_id?": "string | null",
|
|
3333
|
+
"solution?": "unknown",
|
|
3334
|
+
"plan_summary?": "string | null",
|
|
3335
|
+
"replay_state?": "unknown",
|
|
3336
|
+
"is_pinned?": "boolean",
|
|
3337
|
+
"pinned_at?": "string | null",
|
|
3338
|
+
"is_example?": "boolean",
|
|
3339
|
+
"ports?": "unknown",
|
|
3340
|
+
"disable_tools?": "string[]",
|
|
3341
|
+
"runtime_type?": "string | null",
|
|
3342
|
+
"daemon_id?": "string | null",
|
|
3343
|
+
"agent_runtime_id?": "string | null",
|
|
3344
|
+
"workspace_path?": "string | null",
|
|
3345
|
+
"ba_version?": "string | null",
|
|
3346
|
+
"sandbox_version?": "string | null",
|
|
3347
|
+
"chat_group_id?": "string | null",
|
|
3348
|
+
"match?": "unknown"
|
|
3349
|
+
});
|
|
3350
|
+
|
|
3351
|
+
// src/session/definition.ts
|
|
3352
|
+
var SessionSetupError = class extends Error {
|
|
3353
|
+
constructor(message, stage, sessionId, options) {
|
|
3354
|
+
super(message);
|
|
3355
|
+
this.stage = stage;
|
|
3356
|
+
this.sessionId = sessionId;
|
|
3357
|
+
this.cause = options?.cause;
|
|
2839
3358
|
}
|
|
2840
|
-
|
|
2841
|
-
|
|
3359
|
+
stage;
|
|
3360
|
+
sessionId;
|
|
3361
|
+
name = "SessionSetupError";
|
|
3362
|
+
cause;
|
|
3363
|
+
};
|
|
2842
3364
|
|
|
2843
3365
|
// src/resources/sessions.ts
|
|
2844
3366
|
function isEntryGoneError(error) {
|
|
@@ -2849,16 +3371,18 @@ function toCreateSessionPayload(request) {
|
|
|
2849
3371
|
const solutionRef = request.solution_ref ?? externalSolutionRef(request.solution_id);
|
|
2850
3372
|
return {
|
|
2851
3373
|
intent: request.intent ?? "",
|
|
2852
|
-
|
|
3374
|
+
initiator: request.initiator ?? "sdk",
|
|
3375
|
+
anonymous: request.anonymous,
|
|
2853
3376
|
solution_id: solutionRef ? void 0 : request.solution_id,
|
|
2854
3377
|
solution_ref: solutionRef,
|
|
2855
3378
|
biz_role_id: request.biz_role_id ?? null,
|
|
2856
3379
|
model: request.model ?? null,
|
|
2857
3380
|
enable_thinking: request.enable_thinking ?? null,
|
|
2858
3381
|
memory_enabled: request.memory_enabled ?? null,
|
|
2859
|
-
primary_skill_id: request.primary_skill_id ?? null,
|
|
2860
3382
|
env: request.env,
|
|
2861
3383
|
disable_tools: request.disable_tools ?? null,
|
|
3384
|
+
mcp_sources: request.mcp_sources,
|
|
3385
|
+
skip_confirmations: request.skip_confirmations,
|
|
2862
3386
|
session_solution_asset_id: request.session_solution_asset_id ?? null,
|
|
2863
3387
|
solution_snapshot_session_id: request.solution_snapshot_session_id ?? null,
|
|
2864
3388
|
compaction_ratio: request.compaction_ratio ?? null,
|
|
@@ -2985,18 +3509,21 @@ var SessionsResource = class {
|
|
|
2985
3509
|
*
|
|
2986
3510
|
* Secrets are never returned: `write_only` lists the pointers that hold one.
|
|
2987
3511
|
*/
|
|
2988
|
-
getSessionPluginConfig(sessionId, name, init) {
|
|
2989
|
-
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config`, init);
|
|
3512
|
+
getSessionPluginConfig(sessionId, name, init, scope = "personal") {
|
|
3513
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config?scope=${scope}`, init);
|
|
2990
3514
|
}
|
|
2991
3515
|
/**
|
|
2992
|
-
*
|
|
3516
|
+
* Save explicit set/remove operations to encrypted server storage.
|
|
2993
3517
|
*
|
|
2994
3518
|
* Fields absent from both lists keep their stored value, so a form never has
|
|
2995
|
-
* to round-trip a secret. Rejections
|
|
2996
|
-
*
|
|
3519
|
+
* to round-trip a secret. Rejections preserve the previous values; the
|
|
3520
|
+
* shared settings restart only the affected hosted plugin.
|
|
2997
3521
|
*/
|
|
2998
|
-
updateSessionPluginConfig(sessionId, name, update, init) {
|
|
2999
|
-
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config`, { method: "PUT", body: JSON.stringify(update), ...init });
|
|
3522
|
+
updateSessionPluginConfig(sessionId, name, update, init, scope = "personal") {
|
|
3523
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config?scope=${scope}`, { method: "PUT", body: JSON.stringify(update), ...init });
|
|
3524
|
+
}
|
|
3525
|
+
restartSessionPluginHost(sessionId, name) {
|
|
3526
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/host`, { method: "POST" });
|
|
3000
3527
|
}
|
|
3001
3528
|
/** 创建新会话并直接连接,返回可收发消息的 AgentSession。 */
|
|
3002
3529
|
async create(request = {}) {
|
|
@@ -3004,7 +3531,7 @@ var SessionsResource = class {
|
|
|
3004
3531
|
return this.createDeclared(request);
|
|
3005
3532
|
}
|
|
3006
3533
|
const { session_id } = await this.createSessionWithRequest(request);
|
|
3007
|
-
return this.connect(session_id);
|
|
3534
|
+
return request.anonymous ? this.client.forAnonymousSession(session_id).sessions.connect(session_id) : this.connect(session_id);
|
|
3008
3535
|
}
|
|
3009
3536
|
async createDeclared(definition) {
|
|
3010
3537
|
const { intent, solution, skills = [], config } = definition;
|
|
@@ -3091,7 +3618,6 @@ var SessionsResource = class {
|
|
|
3091
3618
|
fetchSessionsPage({
|
|
3092
3619
|
limit,
|
|
3093
3620
|
offset,
|
|
3094
|
-
template_id_prefix,
|
|
3095
3621
|
solution_id,
|
|
3096
3622
|
solution_registry_id,
|
|
3097
3623
|
q,
|
|
@@ -3100,7 +3626,6 @@ var SessionsResource = class {
|
|
|
3100
3626
|
const searchParams = new URLSearchParams();
|
|
3101
3627
|
searchParams.set("limit", String(limit));
|
|
3102
3628
|
searchParams.set("offset", String(offset));
|
|
3103
|
-
if (template_id_prefix) searchParams.set("template_id_prefix", template_id_prefix);
|
|
3104
3629
|
if (solution_registry_id) {
|
|
3105
3630
|
searchParams.set("solution_registry_id", solution_registry_id);
|
|
3106
3631
|
} else if (solution_id) {
|
|
@@ -3111,12 +3636,12 @@ var SessionsResource = class {
|
|
|
3111
3636
|
if (chat_group_id) searchParams.set("chat_group_id", chat_group_id);
|
|
3112
3637
|
return this.client.json("GET", `/api/sessions?${searchParams.toString()}`);
|
|
3113
3638
|
}
|
|
3114
|
-
async listSessions(
|
|
3639
|
+
async listSessions(solution_id) {
|
|
3115
3640
|
const pageSize = 100;
|
|
3116
3641
|
const sessions = [];
|
|
3117
3642
|
let offset = 0;
|
|
3118
3643
|
while (true) {
|
|
3119
|
-
const page = await this.fetchSessionsPage({ limit: pageSize, offset,
|
|
3644
|
+
const page = await this.fetchSessionsPage({ limit: pageSize, offset, solution_id });
|
|
3120
3645
|
sessions.push(...page.items);
|
|
3121
3646
|
if (sessions.length >= page.total || page.items.length === 0) return sessions;
|
|
3122
3647
|
offset += page.items.length;
|
|
@@ -3133,8 +3658,8 @@ var SessionsResource = class {
|
|
|
3133
3658
|
if (params.limit !== void 0) searchParams.set("limit", String(params.limit));
|
|
3134
3659
|
return this.client.json("GET", `/api/search?${searchParams.toString()}`);
|
|
3135
3660
|
}
|
|
3136
|
-
createSession(intent
|
|
3137
|
-
return this.createSessionWithRequest({ intent
|
|
3661
|
+
createSession(intent) {
|
|
3662
|
+
return this.createSessionWithRequest({ intent });
|
|
3138
3663
|
}
|
|
3139
3664
|
createSessionWithRequest(request) {
|
|
3140
3665
|
return this.client.json("POST", "/api/sessions", toCreateSessionPayload(request));
|
|
@@ -3152,6 +3677,13 @@ var SessionsResource = class {
|
|
|
3152
3677
|
updateSession(sessionId, payload) {
|
|
3153
3678
|
return this.client.json("PATCH", `/api/sessions/${sessionId}`, payload);
|
|
3154
3679
|
}
|
|
3680
|
+
getSessionConfiguration(sessionId, init) {
|
|
3681
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/configuration`, init);
|
|
3682
|
+
}
|
|
3683
|
+
/** Updates atomically between turns. A running/handoff session returns HTTP 409. */
|
|
3684
|
+
updateSessionConfiguration(sessionId, request) {
|
|
3685
|
+
return this.client.json("PATCH", `/api/sessions/${encodeURIComponent(sessionId)}/configuration`, request);
|
|
3686
|
+
}
|
|
3155
3687
|
setSessionEnv(sessionId, env) {
|
|
3156
3688
|
return this.client.json("PUT", `/api/sessions/${sessionId}/env`, { env });
|
|
3157
3689
|
}
|
|
@@ -3289,13 +3821,25 @@ var SessionsResource = class {
|
|
|
3289
3821
|
}
|
|
3290
3822
|
async rewindSession(sessionId, checkpointId) {
|
|
3291
3823
|
const result = await this.checkoutSession(sessionId, checkpointId, "before");
|
|
3292
|
-
return {
|
|
3824
|
+
return {
|
|
3825
|
+
id: result.id,
|
|
3826
|
+
content: result.content,
|
|
3827
|
+
...result.history_committed !== void 0 ? { history_committed: result.history_committed } : {},
|
|
3828
|
+
...result.file_checkpoint_cleanup_failed !== void 0 ? { file_checkpoint_cleanup_failed: result.file_checkpoint_cleanup_failed } : {},
|
|
3829
|
+
...result.warning !== void 0 ? { warning: result.warning } : {}
|
|
3830
|
+
};
|
|
3293
3831
|
}
|
|
3294
3832
|
switchBranch(sessionId, checkpointId) {
|
|
3295
3833
|
return this.client.json("POST", `/api/sessions/${sessionId}/switch-branch`, {
|
|
3296
3834
|
checkpoint_id: checkpointId
|
|
3297
3835
|
});
|
|
3298
3836
|
}
|
|
3837
|
+
listAnonymousSessions() {
|
|
3838
|
+
return this.client.json("GET", "/api/anonymous-sessions");
|
|
3839
|
+
}
|
|
3840
|
+
deleteAnonymousSession(sessionId) {
|
|
3841
|
+
return this.client.json("DELETE", `/api/anonymous-sessions/${encodeURIComponent(sessionId)}`);
|
|
3842
|
+
}
|
|
3299
3843
|
deleteSession(sessionId, options) {
|
|
3300
3844
|
const query = options?.deleteWorkspace ? "?delete_workspace=true" : "";
|
|
3301
3845
|
return this.client.json("DELETE", `/api/sessions/${sessionId}${query}`);
|
|
@@ -3543,6 +4087,9 @@ var ChatGroupsResource = class {
|
|
|
3543
4087
|
this.client = client;
|
|
3544
4088
|
}
|
|
3545
4089
|
client;
|
|
4090
|
+
path(groupId) {
|
|
4091
|
+
return `/api/chat-groups/${encodeURIComponent(groupId)}`;
|
|
4092
|
+
}
|
|
3546
4093
|
list() {
|
|
3547
4094
|
return this.client.json("GET", "/api/chat-groups");
|
|
3548
4095
|
}
|
|
@@ -3550,11 +4097,124 @@ var ChatGroupsResource = class {
|
|
|
3550
4097
|
return this.client.json("POST", "/api/chat-groups", payload);
|
|
3551
4098
|
}
|
|
3552
4099
|
update(groupId, payload) {
|
|
3553
|
-
return this.client.json("PATCH",
|
|
4100
|
+
return this.client.json("PATCH", this.path(groupId), payload);
|
|
3554
4101
|
}
|
|
3555
4102
|
delete(groupId, options) {
|
|
3556
4103
|
const query = options?.deleteWorkspace ? "?delete_workspace=true" : "";
|
|
3557
|
-
return this.client.json("DELETE",
|
|
4104
|
+
return this.client.json("DELETE", `${this.path(groupId)}${query}`);
|
|
4105
|
+
}
|
|
4106
|
+
listMembers(groupId) {
|
|
4107
|
+
return this.client.json("GET", `${this.path(groupId)}/members`);
|
|
4108
|
+
}
|
|
4109
|
+
addMembers(groupId, userIds, role) {
|
|
4110
|
+
return this.client.json("POST", `${this.path(groupId)}/members`, { user_ids: userIds, role });
|
|
4111
|
+
}
|
|
4112
|
+
updateMember(groupId, userId, role) {
|
|
4113
|
+
return this.client.json("PATCH", `${this.path(groupId)}/members/${encodeURIComponent(userId)}`, { role });
|
|
4114
|
+
}
|
|
4115
|
+
removeMember(groupId, userId) {
|
|
4116
|
+
return this.client.json("DELETE", `${this.path(groupId)}/members/${encodeURIComponent(userId)}`);
|
|
4117
|
+
}
|
|
4118
|
+
listTasks(groupId) {
|
|
4119
|
+
return this.client.json("GET", `${this.path(groupId)}/tasks`);
|
|
4120
|
+
}
|
|
4121
|
+
async createTask(groupId, payload) {
|
|
4122
|
+
try {
|
|
4123
|
+
return await this.client.json("POST", `${this.path(groupId)}/tasks`, payload);
|
|
4124
|
+
} catch (error) {
|
|
4125
|
+
if (error instanceof BladeApiError && error.status === 502) {
|
|
4126
|
+
const data = await error.response.clone().json().catch(() => null);
|
|
4127
|
+
if (typeof data?.detail?.message === "string") error.message = data.detail.message;
|
|
4128
|
+
}
|
|
4129
|
+
throw error;
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
updateTask(groupId, taskId, payload) {
|
|
4133
|
+
return this.client.json("PATCH", `${this.path(groupId)}/tasks/${encodeURIComponent(taskId)}`, payload);
|
|
4134
|
+
}
|
|
4135
|
+
deleteTask(groupId, taskId) {
|
|
4136
|
+
return this.client.json("DELETE", `${this.path(groupId)}/tasks/${encodeURIComponent(taskId)}`);
|
|
4137
|
+
}
|
|
4138
|
+
listAssets(groupId) {
|
|
4139
|
+
return this.client.json("GET", `${this.path(groupId)}/assets`);
|
|
4140
|
+
}
|
|
4141
|
+
async readAsset(groupId, path, signal) {
|
|
4142
|
+
const response = await this.client.fetch(
|
|
4143
|
+
"GET",
|
|
4144
|
+
`${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`,
|
|
4145
|
+
{ signal }
|
|
4146
|
+
);
|
|
4147
|
+
return response.blob();
|
|
4148
|
+
}
|
|
4149
|
+
/** Read at most the first 1 MiB. Full downloads continue to use readAsset. */
|
|
4150
|
+
async readAssetPreview(groupId, path, signal) {
|
|
4151
|
+
const limit = 1024 * 1024;
|
|
4152
|
+
let response;
|
|
4153
|
+
try {
|
|
4154
|
+
response = await this.client.fetch(
|
|
4155
|
+
"GET",
|
|
4156
|
+
`${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`,
|
|
4157
|
+
{ signal, headers: { Range: `bytes=0-${limit - 1}` } }
|
|
4158
|
+
);
|
|
4159
|
+
} catch (error) {
|
|
4160
|
+
if (error instanceof BladeApiError && error.status === 416 && error.response.headers.get("content-range") === "bytes */0") {
|
|
4161
|
+
await error.response.body?.cancel();
|
|
4162
|
+
return { blob: new Blob(), truncated: false };
|
|
4163
|
+
}
|
|
4164
|
+
throw error;
|
|
4165
|
+
}
|
|
4166
|
+
const reader = response.body?.getReader();
|
|
4167
|
+
if (!reader) return { blob: new Blob(), truncated: false };
|
|
4168
|
+
const chunks = [];
|
|
4169
|
+
let bytes = 0;
|
|
4170
|
+
let ended = false;
|
|
4171
|
+
let overflow = false;
|
|
4172
|
+
try {
|
|
4173
|
+
while (bytes < limit) {
|
|
4174
|
+
signal?.throwIfAborted();
|
|
4175
|
+
const { done, value } = await reader.read();
|
|
4176
|
+
if (done) {
|
|
4177
|
+
ended = true;
|
|
4178
|
+
break;
|
|
4179
|
+
}
|
|
4180
|
+
const remaining = limit - bytes;
|
|
4181
|
+
overflow = value.byteLength > remaining;
|
|
4182
|
+
const chunk = value.slice(0, remaining);
|
|
4183
|
+
chunks.push(chunk);
|
|
4184
|
+
bytes += chunk.byteLength;
|
|
4185
|
+
}
|
|
4186
|
+
signal?.throwIfAborted();
|
|
4187
|
+
} finally {
|
|
4188
|
+
if (!ended) await reader.cancel().catch(() => void 0);
|
|
4189
|
+
reader.releaseLock();
|
|
4190
|
+
}
|
|
4191
|
+
const range = response.headers.get("content-range")?.match(/^bytes 0-\d+\/(\d+)$/);
|
|
4192
|
+
const length = response.headers.get("content-length");
|
|
4193
|
+
const total = range ? Number(range[1]) : response.status === 200 && length && !response.headers.has("content-encoding") ? Number(length) : null;
|
|
4194
|
+
return {
|
|
4195
|
+
blob: new Blob(chunks, { type: response.headers.get("content-type") ?? "" }),
|
|
4196
|
+
truncated: overflow || (total !== null && Number.isFinite(total) ? total > bytes : !ended)
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
4199
|
+
async uploadAsset(groupId, file, path, message = "") {
|
|
4200
|
+
const form = new FormData();
|
|
4201
|
+
form.append("file", file);
|
|
4202
|
+
form.append("message", message);
|
|
4203
|
+
const response = await this.client.formData(
|
|
4204
|
+
"POST",
|
|
4205
|
+
`${this.path(groupId)}/assets/upload?${new URLSearchParams({ path })}`,
|
|
4206
|
+
form
|
|
4207
|
+
);
|
|
4208
|
+
return response.json();
|
|
4209
|
+
}
|
|
4210
|
+
createAssetFolder(groupId, path) {
|
|
4211
|
+
return this.client.json("POST", `${this.path(groupId)}/assets/folders`, { path });
|
|
4212
|
+
}
|
|
4213
|
+
deleteAsset(groupId, path) {
|
|
4214
|
+
return this.client.json("DELETE", `${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`);
|
|
4215
|
+
}
|
|
4216
|
+
submitSessionAsset(payload) {
|
|
4217
|
+
return this.client.json("POST", "/api/topic-agent/files/submit", payload);
|
|
3558
4218
|
}
|
|
3559
4219
|
};
|
|
3560
4220
|
|
|
@@ -3595,6 +4255,48 @@ var SiteVisitsResource = class {
|
|
|
3595
4255
|
}
|
|
3596
4256
|
};
|
|
3597
4257
|
|
|
4258
|
+
// src/server-clock.ts
|
|
4259
|
+
var clocks = /* @__PURE__ */ new WeakMap();
|
|
4260
|
+
function syncServerTime(client, timestamp, receivedAt = performance.now()) {
|
|
4261
|
+
if (typeof timestamp !== "string") return;
|
|
4262
|
+
const serverTime = Date.parse(timestamp);
|
|
4263
|
+
if (!Number.isFinite(serverTime)) return;
|
|
4264
|
+
const current = getServerTime(client, receivedAt);
|
|
4265
|
+
clocks.set(client, { serverTime: Math.max(serverTime, current ?? serverTime), receivedAt });
|
|
4266
|
+
}
|
|
4267
|
+
function getServerTime(client, now = performance.now()) {
|
|
4268
|
+
const clock = client ? clocks.get(client) : void 0;
|
|
4269
|
+
return clock ? clock.serverTime + Math.max(0, now - clock.receivedAt) : null;
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
// src/session/background.ts
|
|
4273
|
+
var BACKGROUND_STATUSES = [
|
|
4274
|
+
"starting",
|
|
4275
|
+
"running",
|
|
4276
|
+
"cancelling",
|
|
4277
|
+
"succeeded",
|
|
4278
|
+
"failed",
|
|
4279
|
+
"cancelled",
|
|
4280
|
+
"lost"
|
|
4281
|
+
];
|
|
4282
|
+
function normalizeBackgroundStatus(value) {
|
|
4283
|
+
return BACKGROUND_STATUSES.includes(value) ? value : "running";
|
|
4284
|
+
}
|
|
4285
|
+
function completedBackgroundTask(task) {
|
|
4286
|
+
const taskId = typeof task.id === "string" ? task.id : typeof task.task_id === "string" ? task.task_id : "";
|
|
4287
|
+
if (!taskId) return null;
|
|
4288
|
+
return {
|
|
4289
|
+
id: taskId,
|
|
4290
|
+
command: typeof task.command === "string" ? task.command : "",
|
|
4291
|
+
description: typeof task.description === "string" ? task.description : void 0,
|
|
4292
|
+
status: normalizeBackgroundStatus(task.status),
|
|
4293
|
+
failureReason: typeof task.failure_reason === "string" ? task.failure_reason : void 0,
|
|
4294
|
+
exitCode: typeof task.exit_code === "number" || task.exit_code === null ? task.exit_code : void 0,
|
|
4295
|
+
elapsedSeconds: typeof task.elapsed_seconds === "number" ? task.elapsed_seconds : 0,
|
|
4296
|
+
output: Array.isArray(task.output_preview) && task.output_preview.length > 0 ? task.output_preview.join("\n") : void 0
|
|
4297
|
+
};
|
|
4298
|
+
}
|
|
4299
|
+
|
|
3598
4300
|
// src/shared/auth-busy.ts
|
|
3599
4301
|
var DEFAULT_RETRY_AFTER_SECONDS = 1;
|
|
3600
4302
|
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
@@ -4011,15 +4713,8 @@ function contentPreview(content, maxLen = 80) {
|
|
|
4011
4713
|
const text = getTextContent(content).trim();
|
|
4012
4714
|
return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
|
|
4013
4715
|
}
|
|
4014
|
-
function transformSlashCommand(skillName, rawInput,
|
|
4015
|
-
|
|
4016
|
-
if (local) {
|
|
4017
|
-
return prompt ? `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1
|
|
4018
|
-
${prompt}` : `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1`;
|
|
4019
|
-
}
|
|
4020
|
-
const steps = installed ? `\u8BF7\u5148\u8FD0\u884C blade hub skill use ${skillName} \u52A0\u8F7D\u6280\u80FD` : `\u8BE5\u6280\u80FD\u5C1A\u672A\u5B89\u88C5\u3002\u8BF7\u5148\u8FD0\u884C blade hub skill install ${skillName} \u5B89\u88C5\uFF0C\u518D\u8FD0\u884C blade hub skill use ${skillName} \u52A0\u8F7D`;
|
|
4021
|
-
return prompt ? `${steps}\uFF0C\u7136\u540E\u5B8C\u6210\u4EFB\u52A1\uFF1A
|
|
4022
|
-
${prompt}` : `${steps}\uFF0C\u7136\u540E\u4F7F\u7528\u5B83\u5B8C\u6210\u4EFB\u52A1`;
|
|
4716
|
+
function transformSlashCommand(skillName, rawInput, _availability = {}) {
|
|
4717
|
+
return rawInput.replace(`<skill>${skillName}</skill>`, `@${skillName}`).trim();
|
|
4023
4718
|
}
|
|
4024
4719
|
var ATTACHMENT_TAG_RE = /\[附件:\s*([^\]]+)\](?:[ \t]*已上传到工作区路径:[ \t]*([^\r\n]+))?/g;
|
|
4025
4720
|
var CONTEXT_PART_RE = /^\[上下文:\s*([^\]]+)\]\n([\s\S]*)$/;
|
|
@@ -4147,7 +4842,13 @@ function workflowEntriesFromOutput(output) {
|
|
|
4147
4842
|
}
|
|
4148
4843
|
function workflowNameFromArguments(argumentsJson) {
|
|
4149
4844
|
try {
|
|
4150
|
-
const
|
|
4845
|
+
const args = JSON.parse(argumentsJson);
|
|
4846
|
+
const skillId = typeof args?.skill_id === "string" ? args.skill_id.trim() : "";
|
|
4847
|
+
if (skillId) {
|
|
4848
|
+
const flow = typeof args?.flow === "string" ? args.flow.trim() : "";
|
|
4849
|
+
return `\u6280\u80FD\u300C${skillId}\u300D${flow && flow !== "main" ? ` \xB7 \u6D41\u7A0B ${flow}` : ""}`;
|
|
4850
|
+
}
|
|
4851
|
+
const name = args?.name;
|
|
4151
4852
|
return typeof name === "string" ? name : "";
|
|
4152
4853
|
} catch {
|
|
4153
4854
|
return "";
|
|
@@ -4171,6 +4872,15 @@ function applyWorkflowEvent(current, toolCallId, type2, payload) {
|
|
|
4171
4872
|
}
|
|
4172
4873
|
const base = current ?? { toolCallId, name: "", status: "running", entries: [] };
|
|
4173
4874
|
if (type2 === "workflow:step") {
|
|
4875
|
+
const settled = asText(payload.status);
|
|
4876
|
+
const stepId = asText(payload.step_id);
|
|
4877
|
+
if (stepId && isFinished(settled)) {
|
|
4878
|
+
const index = base.entries.findIndex((entry) => entry.kind === "step" && entry.stepId === stepId);
|
|
4879
|
+
if (index === -1) return null;
|
|
4880
|
+
const entries = [...base.entries];
|
|
4881
|
+
entries[index] = { ...entries[index], status: settled };
|
|
4882
|
+
return { ...base, entries };
|
|
4883
|
+
}
|
|
4174
4884
|
return {
|
|
4175
4885
|
...base,
|
|
4176
4886
|
entries: [
|
|
@@ -4185,7 +4895,9 @@ function applyWorkflowEvent(current, toolCallId, type2, payload) {
|
|
|
4185
4895
|
};
|
|
4186
4896
|
}
|
|
4187
4897
|
if (type2 === "workflow:log") {
|
|
4188
|
-
|
|
4898
|
+
const stepId = asText(payload.step_id);
|
|
4899
|
+
const entry = stepId ? { kind: "log", text: asText(payload.text), stepId } : { kind: "log", text: asText(payload.text) };
|
|
4900
|
+
return { ...base, entries: [...base.entries, entry] };
|
|
4189
4901
|
}
|
|
4190
4902
|
if (type2 === "workflow:run:end") {
|
|
4191
4903
|
const status = asText(payload.status);
|
|
@@ -4207,10 +4919,10 @@ function rebuildWorkflowRuns(turns, previous) {
|
|
|
4207
4919
|
const name = workflowNameFromArguments(toolCall.arguments);
|
|
4208
4920
|
const prior = previous[toolCall.id];
|
|
4209
4921
|
if (isFinished(toolCall.status)) {
|
|
4210
|
-
const
|
|
4211
|
-
|
|
4212
|
-
);
|
|
4213
|
-
runs[toolCall.id] = prior && prior.status ===
|
|
4922
|
+
const steps = toolCall.workflow_steps ?? [];
|
|
4923
|
+
const entries = steps.length > 0 ? workflowEntriesFromSteps(steps) : workflowEntriesFromOutput(typeof toolCall.result === "string" ? toolCall.result.trim() : "");
|
|
4924
|
+
const status = toolCall.status === "error" && workflowRunCancelled(steps) ? "cancelled" : toolCall.status;
|
|
4925
|
+
runs[toolCall.id] = prior && prior.status === status && prior.name === name && JSON.stringify(prior.entries) === JSON.stringify(entries) ? prior : { toolCallId: toolCall.id, name, status, entries };
|
|
4214
4926
|
continue;
|
|
4215
4927
|
}
|
|
4216
4928
|
runs[toolCall.id] = prior && prior.name === name ? prior : { toolCallId: toolCall.id, name, status: prior?.status ?? "running", entries: prior?.entries ?? [] };
|
|
@@ -4461,7 +5173,9 @@ function projectionToMessage(turn) {
|
|
|
4461
5173
|
display_name: toolCall.display_name,
|
|
4462
5174
|
arguments: toolCall.arguments,
|
|
4463
5175
|
result: toolCall.result ?? void 0,
|
|
5176
|
+
...toolCall.evidence ? { evidence: toolCall.evidence } : {},
|
|
4464
5177
|
pending_question_ref: toolCall.pending_question_ref ?? void 0,
|
|
5178
|
+
workflow_steps: toolCall.workflow_steps ?? void 0,
|
|
4465
5179
|
status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending",
|
|
4466
5180
|
...typeof toolCall.duration_ms === "number" ? { duration_ms: toolCall.duration_ms } : {}
|
|
4467
5181
|
})) : void 0;
|
|
@@ -4476,10 +5190,14 @@ function projectionToMessage(turn) {
|
|
|
4476
5190
|
...toolCalls ? { tool_calls: toolCalls } : {},
|
|
4477
5191
|
loop_name: turn.loop_id,
|
|
4478
5192
|
entry_id: turn.turn_id,
|
|
5193
|
+
...turn.client_request_id ? { client_request_id: turn.client_request_id } : {},
|
|
4479
5194
|
status: turn.status,
|
|
4480
5195
|
...typeof turn.duration_ms === "number" ? { duration_ms: turn.duration_ms } : {},
|
|
4481
5196
|
...turn.started_at ? { timestamp: turn.started_at } : {},
|
|
4482
|
-
...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {}
|
|
5197
|
+
...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {},
|
|
5198
|
+
...turn.refs?.length ? { refs: turn.refs } : {},
|
|
5199
|
+
...turn.context_attachments?.length ? { context_attachments: turn.context_attachments } : {},
|
|
5200
|
+
...turn.topic_asset_refs?.length ? { topic_asset_refs: turn.topic_asset_refs } : {}
|
|
4483
5201
|
};
|
|
4484
5202
|
}
|
|
4485
5203
|
var messageProjectionCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -4716,7 +5434,7 @@ function withQueue(state, snapshot2) {
|
|
|
4716
5434
|
return { ...state, queue: snapshot2 };
|
|
4717
5435
|
}
|
|
4718
5436
|
var localTurnCounter = 0;
|
|
4719
|
-
function addUserMessage(state, content, clientRequestId) {
|
|
5437
|
+
function addUserMessage(state, content, clientRequestId, topicAssetRefs, refs, contextAttachments) {
|
|
4720
5438
|
localTurnCounter += 1;
|
|
4721
5439
|
const turnId = `local-user-${clientRequestId ?? `${Date.now()}-${localTurnCounter}`}`;
|
|
4722
5440
|
const optimisticTurn = {
|
|
@@ -4727,6 +5445,9 @@ function addUserMessage(state, content, clientRequestId) {
|
|
|
4727
5445
|
// client_render_id(= 乐观 turn 自己的 turn_id)保持同一个值,
|
|
4728
5446
|
// 消费方才能拿它当"确认前后不变"的稳定标识使用。
|
|
4729
5447
|
client_render_id: turnId,
|
|
5448
|
+
...refs ? { refs } : {},
|
|
5449
|
+
...contextAttachments?.length ? { context_attachments: contextAttachments } : {},
|
|
5450
|
+
...topicAssetRefs?.length ? { topic_asset_refs: topicAssetRefs } : {},
|
|
4730
5451
|
loop_id: "root",
|
|
4731
5452
|
role: "user",
|
|
4732
5453
|
status: "streaming",
|
|
@@ -4910,6 +5631,9 @@ function sessionStatusFromChatEnd(status) {
|
|
|
4910
5631
|
if (status === "failed") return "failed";
|
|
4911
5632
|
return "completed";
|
|
4912
5633
|
}
|
|
5634
|
+
function isCompactionFinished(type2) {
|
|
5635
|
+
return type2 === "compaction:end" || type2 === "compaction:error" || type2 === "compaction:abort";
|
|
5636
|
+
}
|
|
4913
5637
|
function extractTerminalChatEndStatus(events) {
|
|
4914
5638
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
4915
5639
|
const event = events[i];
|
|
@@ -4920,6 +5644,7 @@ function extractTerminalChatEndStatus(events) {
|
|
|
4920
5644
|
return null;
|
|
4921
5645
|
}
|
|
4922
5646
|
var AgentSession = class _AgentSession {
|
|
5647
|
+
logCursor = new LogCursor();
|
|
4923
5648
|
sessionId;
|
|
4924
5649
|
state;
|
|
4925
5650
|
/** status 每变化一次加一(单调递增)。异步读取会话详情时据此判断请求期间状态是否变过,避免 running→终态→running 这类来回被误认为「没变」。 */
|
|
@@ -4932,6 +5657,7 @@ var AgentSession = class _AgentSession {
|
|
|
4932
5657
|
emitter = new Emitter();
|
|
4933
5658
|
commands = new CommandRegistry();
|
|
4934
5659
|
runtime;
|
|
5660
|
+
activeReplayPlaybackId = null;
|
|
4935
5661
|
projectionBuilder = new ClientProjectionBuilder();
|
|
4936
5662
|
pendingTurnPatches = /* @__PURE__ */ new Map();
|
|
4937
5663
|
patchFlushHandle = null;
|
|
@@ -4970,7 +5696,10 @@ var AgentSession = class _AgentSession {
|
|
|
4970
5696
|
bufferedCommandDeliveries = null;
|
|
4971
5697
|
pendingReplayMessage = null;
|
|
4972
5698
|
pendingReplayMode;
|
|
4973
|
-
|
|
5699
|
+
pendingReplayRequestId;
|
|
5700
|
+
pendingReplayAttachments;
|
|
5701
|
+
pendingReplayRefs;
|
|
5702
|
+
pendingReplayTopicAssetRefs;
|
|
4974
5703
|
recentChatEndAt = 0;
|
|
4975
5704
|
/**
|
|
4976
5705
|
* 最近一次已经通过 `error` 事件投递过的服务端失败(`message` + `code`)。
|
|
@@ -4990,12 +5719,24 @@ var AgentSession = class _AgentSession {
|
|
|
4990
5719
|
disposed = false;
|
|
4991
5720
|
getAppContext = null;
|
|
4992
5721
|
sendPending = false;
|
|
4993
|
-
|
|
5722
|
+
manualCompactionRequest = null;
|
|
5723
|
+
/** Reload requests from retracts/epoch changes; done only when a reload started after the latest one succeeds. */
|
|
5724
|
+
epochReloadRequested = 0;
|
|
5725
|
+
epochReloadDone = 0;
|
|
5726
|
+
epochReloadRunning = false;
|
|
4994
5727
|
/** 手动整理的在途标记,同时写进状态(界面据此保持「正在整理」,见 SessionState.compactionPending)。 */
|
|
4995
|
-
|
|
4996
|
-
this.
|
|
5728
|
+
setManualCompactionRequest(request) {
|
|
5729
|
+
this.manualCompactionRequest = request;
|
|
5730
|
+
const pending = request !== null;
|
|
4997
5731
|
if (this.state.compactionPending !== pending) this.update((s) => ({ ...s, compactionPending: pending }));
|
|
4998
5732
|
}
|
|
5733
|
+
handleManualCompactionFinished(event) {
|
|
5734
|
+
const request = this.manualCompactionRequest;
|
|
5735
|
+
const runId = event.payload.run_id;
|
|
5736
|
+
if (!request || !isCompactionFinished(event.type) || typeof runId !== "string" || !runId) return;
|
|
5737
|
+
if (request.runId === null) request.finishedBeforeAck.add(runId);
|
|
5738
|
+
else if (request.runId === runId) this.setManualCompactionRequest(null);
|
|
5739
|
+
}
|
|
4999
5740
|
/** 最近一次还在等服务端结果的发送请求 id;排队中断的 chat:end 据此判断回滚是否过期。 */
|
|
5000
5741
|
pendingChatRequestId = null;
|
|
5001
5742
|
/** 会话详情给出的运行时类型;"native" 时启用原生智能体的投影规则(见 _handleTurnEvents / _handleChatEnd)。 */
|
|
@@ -5153,6 +5894,11 @@ var AgentSession = class _AgentSession {
|
|
|
5153
5894
|
* chat:end 会让界面停在「运行中」)。
|
|
5154
5895
|
*/
|
|
5155
5896
|
async reloadHistory() {
|
|
5897
|
+
const replaced = await this.reloadHistoryWindow();
|
|
5898
|
+
await this.refreshRunningState().catch(() => void 0);
|
|
5899
|
+
return replaced;
|
|
5900
|
+
}
|
|
5901
|
+
async reloadHistoryWindow() {
|
|
5156
5902
|
if (this.disposed) return false;
|
|
5157
5903
|
this.invalidateTimeline();
|
|
5158
5904
|
this.invalidateOlderPaging();
|
|
@@ -5162,9 +5908,7 @@ var AgentSession = class _AgentSession {
|
|
|
5162
5908
|
loadingOlder: false,
|
|
5163
5909
|
timelineRevision: state.timelineRevision + 1
|
|
5164
5910
|
}));
|
|
5165
|
-
|
|
5166
|
-
await this.refreshRunningState().catch(() => void 0);
|
|
5167
|
-
return replaced;
|
|
5911
|
+
return this.replaceFromHistory();
|
|
5168
5912
|
}
|
|
5169
5913
|
/**
|
|
5170
5914
|
* 向服务端确认会话此刻是否在运行,校准流式状态(残留的「运行中」会被清掉)。只在已加入房间时
|
|
@@ -5255,10 +5999,78 @@ var AgentSession = class _AgentSession {
|
|
|
5255
5999
|
this.getAppContext = options.getContext;
|
|
5256
6000
|
return attachment;
|
|
5257
6001
|
}
|
|
6002
|
+
/** Prepare a durable text input without sending any network request. */
|
|
6003
|
+
prepareSend(content, options = {}) {
|
|
6004
|
+
if (!content.trim()) throw new Error("\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A");
|
|
6005
|
+
if (Object.keys(options).some((key) => !["mode", "model", "thinkingOverride", "reasoningLevel", "contextAttachments"].includes(key))) {
|
|
6006
|
+
throw new Error("\u53EF\u9760\u6587\u672C\u53D1\u9001\u4E0D\u652F\u6301\u8FD9\u4E9B\u53C2\u6570\uFF0C\u8BF7\u4F7F\u7528 send()");
|
|
6007
|
+
}
|
|
6008
|
+
const snapshot2 = JSON.parse(JSON.stringify(options));
|
|
6009
|
+
if (snapshot2.contextAttachments) snapshot2.contextAttachments = snapshotContextAttachments(snapshot2.contextAttachments);
|
|
6010
|
+
return { sessionId: this.sessionId, requestId: createClientRequestId(), content, options: snapshot2 };
|
|
6011
|
+
}
|
|
6012
|
+
/** Durably enqueue an ordinary text input. Retries must reuse the saved request. */
|
|
6013
|
+
async sendPrepared(request) {
|
|
6014
|
+
if (request.sessionId !== this.sessionId) throw new Error("\u53D1\u9001\u8BF7\u6C42\u4E0D\u5C5E\u4E8E\u5F53\u524D\u4F1A\u8BDD");
|
|
6015
|
+
try {
|
|
6016
|
+
this.runtime.ensureConnected();
|
|
6017
|
+
const response = await this.runtime.emitWithAck("chat:append", {
|
|
6018
|
+
session_id: this.sessionId,
|
|
6019
|
+
client_request_id: request.requestId,
|
|
6020
|
+
message: request.content,
|
|
6021
|
+
prepared_options: {
|
|
6022
|
+
mode: request.options.mode,
|
|
6023
|
+
model: request.options.model,
|
|
6024
|
+
thinking_override: request.options.thinkingOverride,
|
|
6025
|
+
reasoning_level: request.options.reasoningLevel,
|
|
6026
|
+
...request.options.contextAttachments ? { context_attachments: request.options.contextAttachments } : {}
|
|
6027
|
+
}
|
|
6028
|
+
}, QUEUE_ACK_TIMEOUT_MS);
|
|
6029
|
+
const result = parseQueueAck(response, this.sessionId);
|
|
6030
|
+
if (result.snapshot) this.update((state) => withQueue(state, result.snapshot));
|
|
6031
|
+
if (!result.ok) return {
|
|
6032
|
+
requestId: request.requestId,
|
|
6033
|
+
status: response?.status === "conflict" ? "rejected" : "unknown",
|
|
6034
|
+
message: result.message ?? void 0
|
|
6035
|
+
};
|
|
6036
|
+
const delivery = response?.delivery;
|
|
6037
|
+
if (!isRecord(delivery) || delivery.status !== "accepted") return { requestId: request.requestId, status: "unknown" };
|
|
6038
|
+
return {
|
|
6039
|
+
requestId: request.requestId,
|
|
6040
|
+
status: "accepted",
|
|
6041
|
+
itemId: typeof delivery.item_id === "string" ? delivery.item_id : void 0,
|
|
6042
|
+
queueStatus: typeof delivery.queue_status === "string" ? delivery.queue_status : void 0,
|
|
6043
|
+
executionId: typeof delivery.execution_id === "string" ? delivery.execution_id : null
|
|
6044
|
+
};
|
|
6045
|
+
} catch {
|
|
6046
|
+
return { requestId: request.requestId, status: "unknown" };
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
/** Owner-authorized lookup; receipts remain until the session is deleted. */
|
|
6050
|
+
async getDelivery(requestId) {
|
|
6051
|
+
try {
|
|
6052
|
+
this.runtime.ensureConnected();
|
|
6053
|
+
const response = await this.runtime.emitWithAck("chat:queue:request", {
|
|
6054
|
+
session_id: this.sessionId,
|
|
6055
|
+
client_request_id: requestId
|
|
6056
|
+
}, QUEUE_ACK_TIMEOUT_MS);
|
|
6057
|
+
if (response?.status === "accepted") return {
|
|
6058
|
+
requestId,
|
|
6059
|
+
status: "accepted",
|
|
6060
|
+
itemId: typeof response.item_id === "string" ? response.item_id : void 0,
|
|
6061
|
+
queueStatus: typeof response.queue_status === "string" ? response.queue_status : void 0,
|
|
6062
|
+
executionId: typeof response.execution_id === "string" ? response.execution_id : null
|
|
6063
|
+
};
|
|
6064
|
+
if (response?.status === "not_found") return { requestId, status: "not_found" };
|
|
6065
|
+
return { requestId, status: "unknown", message: typeof response?.message === "string" ? response.message : void 0 };
|
|
6066
|
+
} catch {
|
|
6067
|
+
return { requestId, status: "unknown" };
|
|
6068
|
+
}
|
|
6069
|
+
}
|
|
5258
6070
|
/** 发送一条消息。字符串或多模态内容均可。 */
|
|
5259
6071
|
async send(content, options = {}) {
|
|
5260
6072
|
this.runtime.ensureConnected();
|
|
5261
|
-
if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.sendPending || this.
|
|
6073
|
+
if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.sendPending || this.manualCompactionRequest) {
|
|
5262
6074
|
this.update(
|
|
5263
6075
|
(s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002")
|
|
5264
6076
|
);
|
|
@@ -5388,8 +6200,9 @@ var AgentSession = class _AgentSession {
|
|
|
5388
6200
|
}
|
|
5389
6201
|
}
|
|
5390
6202
|
async sendUnlocked(content, options) {
|
|
6203
|
+
const contextAttachments = options.contextAttachments ? snapshotContextAttachments(options.contextAttachments) : void 0;
|
|
5391
6204
|
await this.ensureIdleBeforeSend();
|
|
5392
|
-
if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.
|
|
6205
|
+
if (this.state.isStreaming && options.queueWhenRunning !== true || this.state.activeCompaction || this.manualCompactionRequest) {
|
|
5393
6206
|
this.update(
|
|
5394
6207
|
(s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002")
|
|
5395
6208
|
);
|
|
@@ -5424,7 +6237,7 @@ var AgentSession = class _AgentSession {
|
|
|
5424
6237
|
let optimisticAskAnswer;
|
|
5425
6238
|
let previousAskAnswer;
|
|
5426
6239
|
this.update((state) => {
|
|
5427
|
-
const next = addUserMessage(state, content, clientRequestId);
|
|
6240
|
+
const next = addUserMessage(state, content, clientRequestId, options.topicAssetRefs, options.refs, contextAttachments);
|
|
5428
6241
|
optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
|
|
5429
6242
|
return next;
|
|
5430
6243
|
});
|
|
@@ -5442,7 +6255,10 @@ var AgentSession = class _AgentSession {
|
|
|
5442
6255
|
if (!options.replayDecision) {
|
|
5443
6256
|
this.pendingReplayMessage = content;
|
|
5444
6257
|
this.pendingReplayMode = options.mode;
|
|
5445
|
-
this.
|
|
6258
|
+
this.pendingReplayRequestId = clientRequestId;
|
|
6259
|
+
this.pendingReplayAttachments = contextAttachments;
|
|
6260
|
+
this.pendingReplayRefs = options.refs;
|
|
6261
|
+
this.pendingReplayTopicAssetRefs = options.topicAssetRefs;
|
|
5446
6262
|
}
|
|
5447
6263
|
this.pendingChatRequestId = clientRequestId;
|
|
5448
6264
|
const payload = {
|
|
@@ -5457,7 +6273,9 @@ var AgentSession = class _AgentSession {
|
|
|
5457
6273
|
whatif: options.whatif,
|
|
5458
6274
|
replay_decision: options.replayDecision,
|
|
5459
6275
|
app_context: appContext,
|
|
5460
|
-
|
|
6276
|
+
context_attachments: contextAttachments,
|
|
6277
|
+
refs: options.refs,
|
|
6278
|
+
topic_asset_refs: options.topicAssetRefs,
|
|
5461
6279
|
// 运行中提交不必由客户端挑命令:服务端按会话运行状态决定直接执行还是入队。
|
|
5462
6280
|
queue_when_running: options.queueWhenRunning === true
|
|
5463
6281
|
};
|
|
@@ -5541,7 +6359,10 @@ var AgentSession = class _AgentSession {
|
|
|
5541
6359
|
}
|
|
5542
6360
|
this.pendingReplayMessage = null;
|
|
5543
6361
|
this.pendingReplayMode = void 0;
|
|
5544
|
-
this.
|
|
6362
|
+
this.pendingReplayRequestId = void 0;
|
|
6363
|
+
this.pendingReplayAttachments = void 0;
|
|
6364
|
+
this.pendingReplayRefs = void 0;
|
|
6365
|
+
this.pendingReplayTopicAssetRefs = void 0;
|
|
5545
6366
|
this.update((current) => {
|
|
5546
6367
|
const filtered = optimisticTurnId ? current.turns.filter((turn) => turn.turn_id !== optimisticTurnId) : current.turns;
|
|
5547
6368
|
let next = optimisticTurnId ? withTurns({ ...current, turns: filtered }, filtered) : current;
|
|
@@ -5563,22 +6384,62 @@ var AgentSession = class _AgentSession {
|
|
|
5563
6384
|
}
|
|
5564
6385
|
}
|
|
5565
6386
|
/** 停止当前回复。 */
|
|
5566
|
-
stop() {
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
return
|
|
5571
|
-
|
|
6387
|
+
async stop() {
|
|
6388
|
+
const request = this.manualCompactionRequest;
|
|
6389
|
+
if (request) {
|
|
6390
|
+
const ack = await request.ack;
|
|
6391
|
+
if (ack.status !== "accepted") return ack;
|
|
6392
|
+
if (this.manualCompactionRequest !== request) return { status: "idle" };
|
|
6393
|
+
return await this.runtime.emitWithAck("chat:stop", {
|
|
6394
|
+
session_id: this.sessionId,
|
|
6395
|
+
chat_run_id: request.runId
|
|
6396
|
+
}, 35e3) ?? {};
|
|
6397
|
+
}
|
|
6398
|
+
const response = await this.runtime.emitWithAck("chat:stop", { session_id: this.sessionId }, 35e3) ?? {};
|
|
6399
|
+
const cancelled = response.cancelled_client_request_ids;
|
|
6400
|
+
if (response.status === "stopped" && Array.isArray(cancelled) && this.pendingChatRequestId !== null && cancelled.includes(this.pendingChatRequestId)) {
|
|
6401
|
+
this.pendingChatRequestId = null;
|
|
6402
|
+
this.update((state) => ({ ...withoutOptimisticUserTurns(state, cancelled), isStreaming: false }));
|
|
6403
|
+
this.applyDesiredStreamTokens();
|
|
6404
|
+
}
|
|
6405
|
+
return response;
|
|
5572
6406
|
}
|
|
5573
6407
|
/** 手动触发上下文压缩。 */
|
|
5574
6408
|
compact() {
|
|
5575
|
-
if (this.state.isStreaming || this.state.activeCompaction || this.
|
|
6409
|
+
if (this.state.isStreaming || this.state.activeCompaction || this.manualCompactionRequest) {
|
|
5576
6410
|
return Promise.resolve({
|
|
5577
6411
|
status: "error",
|
|
5578
6412
|
message: "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002"
|
|
5579
6413
|
});
|
|
5580
6414
|
}
|
|
5581
|
-
|
|
6415
|
+
const request = {
|
|
6416
|
+
runId: null,
|
|
6417
|
+
finishedBeforeAck: /* @__PURE__ */ new Set(),
|
|
6418
|
+
ack: Promise.resolve().then(() => this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4)).then((response) => {
|
|
6419
|
+
const result = response ?? {};
|
|
6420
|
+
if (result.status !== "accepted") {
|
|
6421
|
+
if (this.manualCompactionRequest === request) this.setManualCompactionRequest(null);
|
|
6422
|
+
return result;
|
|
6423
|
+
}
|
|
6424
|
+
if (typeof result.run_id !== "string" || !result.run_id) {
|
|
6425
|
+
throw new AgentSessionError("\u6574\u7406\u8BF7\u6C42\u786E\u8BA4\u7F3A\u5C11 run_id\uFF0C\u65E0\u6CD5\u786E\u8BA4\u8FD0\u884C\u72B6\u6001");
|
|
6426
|
+
}
|
|
6427
|
+
request.runId = result.run_id;
|
|
6428
|
+
if (request.finishedBeforeAck.has(request.runId) && this.manualCompactionRequest === request) {
|
|
6429
|
+
this.setManualCompactionRequest(null);
|
|
6430
|
+
}
|
|
6431
|
+
request.finishedBeforeAck.clear();
|
|
6432
|
+
return result;
|
|
6433
|
+
}).catch((error) => {
|
|
6434
|
+
if (this.manualCompactionRequest === request) this.setManualCompactionRequest(null);
|
|
6435
|
+
throw error;
|
|
6436
|
+
}).finally(() => {
|
|
6437
|
+
if ((!this.manualCompactionRequest || this.manualCompactionRequest === request) && this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
|
|
6438
|
+
this.update((state) => ({ ...state, activeCompaction: null }));
|
|
6439
|
+
}
|
|
6440
|
+
})
|
|
6441
|
+
};
|
|
6442
|
+
this.setManualCompactionRequest(request);
|
|
5582
6443
|
this.update((state) => ({
|
|
5583
6444
|
...state,
|
|
5584
6445
|
activeCompaction: {
|
|
@@ -5588,18 +6449,7 @@ var AgentSession = class _AgentSession {
|
|
|
5588
6449
|
trigger: "manual"
|
|
5589
6450
|
}
|
|
5590
6451
|
}));
|
|
5591
|
-
return
|
|
5592
|
-
const result = response ?? {};
|
|
5593
|
-
if (result.status !== "accepted") this.setManualCompactionPending(false);
|
|
5594
|
-
return result;
|
|
5595
|
-
}).catch((error) => {
|
|
5596
|
-
this.setManualCompactionPending(false);
|
|
5597
|
-
throw error;
|
|
5598
|
-
}).finally(() => {
|
|
5599
|
-
if (this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
|
|
5600
|
-
this.update((state) => ({ ...state, activeCompaction: null }));
|
|
5601
|
-
}
|
|
5602
|
-
});
|
|
6452
|
+
return request.ack;
|
|
5603
6453
|
}
|
|
5604
6454
|
/**
|
|
5605
6455
|
* 把一段业务数据作为附件放进聊天输入框(比如地图上选中的点位)。
|
|
@@ -5799,14 +6649,86 @@ var AgentSession = class _AgentSession {
|
|
|
5799
6649
|
_handleTurnEnd(turn) {
|
|
5800
6650
|
this.applyTurn(turn);
|
|
5801
6651
|
}
|
|
6652
|
+
runEpochReload() {
|
|
6653
|
+
if (this.disposed || this.epochReloadRunning || this.epochReloadDone >= this.epochReloadRequested) return;
|
|
6654
|
+
const target = this.epochReloadRequested;
|
|
6655
|
+
this.epochReloadRunning = true;
|
|
6656
|
+
void this.reloadHistoryWindow().then((replaced) => {
|
|
6657
|
+
if (replaced) this.epochReloadDone = Math.max(this.epochReloadDone, target);
|
|
6658
|
+
}).catch(() => void 0).finally(() => {
|
|
6659
|
+
this.epochReloadRunning = false;
|
|
6660
|
+
if (this.epochReloadRequested > target) this.runEpochReload();
|
|
6661
|
+
});
|
|
6662
|
+
}
|
|
5802
6663
|
/** @internal 历史回放通道(event_format: raw)。 */
|
|
5803
|
-
_handleTurnEvents(
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
this.
|
|
6664
|
+
_handleTurnEvents(incoming, replayed = false) {
|
|
6665
|
+
const delivery = this.logCursor.consume(incoming);
|
|
6666
|
+
let events = delivery.events;
|
|
6667
|
+
if (delivery.reload) {
|
|
6668
|
+
this.activeReplayPlaybackId = null;
|
|
6669
|
+
this.projectionBuilder.reset();
|
|
6670
|
+
this.pendingTurnPatches.clear();
|
|
6671
|
+
this.epochReloadRequested += 1;
|
|
6672
|
+
}
|
|
6673
|
+
this.runEpochReload();
|
|
6674
|
+
for (const event of events) {
|
|
6675
|
+
const playbackId = typeof event.payload.playback_id === "string" ? event.payload.playback_id : null;
|
|
6676
|
+
if (event.type === "chat:start") this.activeReplayPlaybackId = null;
|
|
6677
|
+
if (event.type === "replay:start" && playbackId) {
|
|
6678
|
+
this.activeReplayPlaybackId = playbackId;
|
|
6679
|
+
this._handleChatStart();
|
|
6680
|
+
} else if (event.type === "replay:state") {
|
|
6681
|
+
const status = event.payload.status;
|
|
6682
|
+
if (event.payload.reconciled === true && typeof status === "string") {
|
|
6683
|
+
if (status === "running") this._handleChatStart();
|
|
6684
|
+
else {
|
|
6685
|
+
this.activeReplayPlaybackId = null;
|
|
6686
|
+
this.finishStreaming(status);
|
|
6687
|
+
}
|
|
6688
|
+
}
|
|
6689
|
+
this._handleSessionUpdated(event.payload);
|
|
6690
|
+
} else if (event.type === "replay:end" && playbackId && (!this.activeReplayPlaybackId || this.activeReplayPlaybackId === playbackId)) {
|
|
6691
|
+
this.activeReplayPlaybackId = null;
|
|
6692
|
+
const status = event.payload.status === "interrupted" ? "interrupted" : event.payload.status === "failed" ? "failed" : "completed";
|
|
6693
|
+
this._handleChatEnd({ status });
|
|
6694
|
+
}
|
|
6695
|
+
if (event.type === "log:notification") {
|
|
6696
|
+
const notification = event.payload.event;
|
|
6697
|
+
if (isCompactionFinished(notification.type)) {
|
|
6698
|
+
this.handleManualCompactionFinished(notification);
|
|
6699
|
+
} else if (notification.type === "artifact:created" || notification.type === "artifact:updated") {
|
|
6700
|
+
this._handleArtifact(notification.payload, notification.type === "artifact:created" ? "created" : "updated", replayed);
|
|
6701
|
+
} else if (notification.type === "workspace:changed") {
|
|
6702
|
+
const change = notification.payload.workspace_change;
|
|
6703
|
+
const paths = Array.isArray(notification.payload.paths) ? notification.payload.paths.filter((path) => typeof path === "string") : [];
|
|
6704
|
+
this.emitter.emit("workspaceChanged", {
|
|
6705
|
+
...paths.length ? { paths } : {},
|
|
6706
|
+
...typeof change?.to === "string" && change.to ? { workspacePath: change.to } : {}
|
|
6707
|
+
});
|
|
6708
|
+
} else if (notification.type === "task:updated" && Array.isArray(notification.payload.tasks)) {
|
|
6709
|
+
this._handleTaskUpdated(notification.payload.tasks);
|
|
6710
|
+
} else if (notification.type === "bg:tasks_completed" && Array.isArray(notification.payload.tasks)) {
|
|
6711
|
+
for (const task of notification.payload.tasks) {
|
|
6712
|
+
const completed = completedBackgroundTask(task);
|
|
6713
|
+
if (completed) this._handleBackgroundTask(completed);
|
|
6714
|
+
}
|
|
6715
|
+
} else {
|
|
6716
|
+
this.applyWorkflowEvents([notification]);
|
|
6717
|
+
}
|
|
6718
|
+
}
|
|
6719
|
+
if (event.type === "turn:snapshot") {
|
|
6720
|
+
const turn = event.payload.turn;
|
|
6721
|
+
this.projectionBuilder.acceptDurableTurn(turn);
|
|
6722
|
+
this.pendingTurnPatches.delete(turn.turn_id);
|
|
6723
|
+
this.applyTurn(
|
|
6724
|
+
turn,
|
|
6725
|
+
replayed ? this.subscriptionReplaySource ?? "bootstrap_replay" : "live"
|
|
6726
|
+
);
|
|
6727
|
+
}
|
|
5809
6728
|
}
|
|
6729
|
+
events = events.filter((event) => event.type !== "turn:snapshot" && event.type !== "log:notification" && !event.type.startsWith("replay:"));
|
|
6730
|
+
if (!events.length) return;
|
|
6731
|
+
for (const event of events) this.handleManualCompactionFinished(event);
|
|
5810
6732
|
this.dispatchRawCommands(events);
|
|
5811
6733
|
const terminalChatEndStatus = extractTerminalChatEndStatus(events);
|
|
5812
6734
|
if (!terminalChatEndStatus && hasChatRunEvent(events) && this.state.status !== "waiting_for_input") {
|
|
@@ -5949,7 +6871,6 @@ var AgentSession = class _AgentSession {
|
|
|
5949
6871
|
intent: data.intent,
|
|
5950
6872
|
status: data.status,
|
|
5951
6873
|
model: data.model,
|
|
5952
|
-
boundSkillId: data.bound_skill_id,
|
|
5953
6874
|
replayState: data.replay_state
|
|
5954
6875
|
});
|
|
5955
6876
|
}
|
|
@@ -5972,10 +6893,16 @@ var AgentSession = class _AgentSession {
|
|
|
5972
6893
|
_handleReplayMismatch(data) {
|
|
5973
6894
|
const message = this.pendingReplayMessage ?? data.actual_message ?? "";
|
|
5974
6895
|
const mode = this.pendingReplayMode;
|
|
5975
|
-
const
|
|
6896
|
+
const requestId = this.pendingReplayRequestId ?? createClientRequestId();
|
|
6897
|
+
const contextAttachments = this.pendingReplayAttachments;
|
|
6898
|
+
const refs = this.pendingReplayRefs;
|
|
6899
|
+
const topicAssetRefs = this.pendingReplayTopicAssetRefs;
|
|
5976
6900
|
this.pendingReplayMessage = null;
|
|
5977
6901
|
this.pendingReplayMode = void 0;
|
|
5978
|
-
this.
|
|
6902
|
+
this.pendingReplayRequestId = void 0;
|
|
6903
|
+
this.pendingReplayAttachments = void 0;
|
|
6904
|
+
this.pendingReplayRefs = void 0;
|
|
6905
|
+
this.pendingReplayTopicAssetRefs = void 0;
|
|
5979
6906
|
const respond = (decision) => {
|
|
5980
6907
|
void this.runtime.emitWithAck(
|
|
5981
6908
|
"chat:send",
|
|
@@ -5984,7 +6911,10 @@ var AgentSession = class _AgentSession {
|
|
|
5984
6911
|
message,
|
|
5985
6912
|
mode,
|
|
5986
6913
|
replay_decision: decision,
|
|
5987
|
-
|
|
6914
|
+
client_request_id: requestId,
|
|
6915
|
+
refs,
|
|
6916
|
+
topic_asset_refs: topicAssetRefs,
|
|
6917
|
+
context_attachments: contextAttachments
|
|
5988
6918
|
},
|
|
5989
6919
|
3e4
|
|
5990
6920
|
).then((response) => {
|
|
@@ -6045,8 +6975,20 @@ var AgentSession = class _AgentSession {
|
|
|
6045
6975
|
this.emitter.emit("backgroundTask", task);
|
|
6046
6976
|
}
|
|
6047
6977
|
/** @internal */
|
|
6048
|
-
_handleArtifact(
|
|
6049
|
-
|
|
6978
|
+
_handleArtifact(data, kind, replayed = false) {
|
|
6979
|
+
const filePath = typeof data.file_path === "string" ? data.file_path : "";
|
|
6980
|
+
if (!filePath) return;
|
|
6981
|
+
this._handleWorkspaceFilesChanged(filePath);
|
|
6982
|
+
if (replayed) return;
|
|
6983
|
+
this.emitter.emit("artifact", {
|
|
6984
|
+
filePath,
|
|
6985
|
+
contentType: typeof data.content_type === "string" ? data.content_type : "",
|
|
6986
|
+
title: typeof data.title === "string" ? data.title : filePath,
|
|
6987
|
+
content: typeof data.content === "string" ? data.content : "",
|
|
6988
|
+
oldString: typeof data.old_string === "string" ? data.old_string : void 0,
|
|
6989
|
+
newString: typeof data.new_string === "string" ? data.new_string : void 0,
|
|
6990
|
+
kind
|
|
6991
|
+
});
|
|
6050
6992
|
}
|
|
6051
6993
|
/** @internal 加入房间的应答里带 is_running,据此校准流式状态。 */
|
|
6052
6994
|
_applyRunningState(isRunning, liveRevisionAtJoin = this.liveRevision) {
|
|
@@ -6248,6 +7190,7 @@ var AgentSession = class _AgentSession {
|
|
|
6248
7190
|
display_name: toolCall.display_name,
|
|
6249
7191
|
arguments: toolCall.arguments,
|
|
6250
7192
|
result: toolCall.result ?? void 0,
|
|
7193
|
+
...toolCall.evidence ? { evidence: toolCall.evidence } : {},
|
|
6251
7194
|
status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending"
|
|
6252
7195
|
};
|
|
6253
7196
|
if (!this.seenToolCallIds.has(toolCall.id)) {
|
|
@@ -6514,6 +7457,7 @@ var AgentSession = class _AgentSession {
|
|
|
6514
7457
|
session_id: this.sessionId,
|
|
6515
7458
|
event_format: "raw",
|
|
6516
7459
|
resume: true,
|
|
7460
|
+
...this.logCursor.subscription(),
|
|
6517
7461
|
...streamTokens === null ? {} : { internal_stream_tokens: streamTokens }
|
|
6518
7462
|
},
|
|
6519
7463
|
5e3
|
|
@@ -6547,18 +7491,6 @@ var AgentSession = class _AgentSession {
|
|
|
6547
7491
|
};
|
|
6548
7492
|
|
|
6549
7493
|
// src/session/hub.ts
|
|
6550
|
-
var BACKGROUND_STATUSES = [
|
|
6551
|
-
"starting",
|
|
6552
|
-
"running",
|
|
6553
|
-
"cancelling",
|
|
6554
|
-
"succeeded",
|
|
6555
|
-
"failed",
|
|
6556
|
-
"cancelled",
|
|
6557
|
-
"lost"
|
|
6558
|
-
];
|
|
6559
|
-
function normalizeBackgroundStatus(value) {
|
|
6560
|
-
return BACKGROUND_STATUSES.includes(value) ? value : "running";
|
|
6561
|
-
}
|
|
6562
7494
|
var SessionHub = class {
|
|
6563
7495
|
client;
|
|
6564
7496
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -6576,6 +7508,8 @@ var SessionHub = class {
|
|
|
6576
7508
|
streamTokens = null;
|
|
6577
7509
|
/** 状态订阅(mode: "status")的持有计数。 */
|
|
6578
7510
|
statusWatchCounts = /* @__PURE__ */ new Map();
|
|
7511
|
+
statusLogCursors = /* @__PURE__ */ new Map();
|
|
7512
|
+
replayPlaybackIds = /* @__PURE__ */ new Map();
|
|
6579
7513
|
/** 启动连接门控:扣住期间只登记「需要连接」,放开后连一次。 */
|
|
6580
7514
|
connectionHeld = false;
|
|
6581
7515
|
connectDeferred = false;
|
|
@@ -6627,6 +7561,7 @@ var SessionHub = class {
|
|
|
6627
7561
|
return;
|
|
6628
7562
|
}
|
|
6629
7563
|
this.statusWatchCounts.delete(sessionId);
|
|
7564
|
+
this.statusLogCursors.delete(sessionId);
|
|
6630
7565
|
if (!this.sessions.has(sessionId)) {
|
|
6631
7566
|
;
|
|
6632
7567
|
this.client.socket().emit("session:unsubscribe", { session_id: sessionId });
|
|
@@ -6753,6 +7688,7 @@ var SessionHub = class {
|
|
|
6753
7688
|
this.authBusyReconnect.cancel();
|
|
6754
7689
|
this.bootstrapPending.clear();
|
|
6755
7690
|
this.statusWatchCounts.clear();
|
|
7691
|
+
this.statusLogCursors.clear();
|
|
6756
7692
|
for (const session of [...this.sessions.values()]) {
|
|
6757
7693
|
this.forceRelease(session);
|
|
6758
7694
|
}
|
|
@@ -6814,17 +7750,32 @@ var SessionHub = class {
|
|
|
6814
7750
|
;
|
|
6815
7751
|
this.client.socket().emit("session:unsubscribe", { session_id: session.sessionId });
|
|
6816
7752
|
if (this.statusWatchCounts.has(session.sessionId)) this.emitStatusSubscribe([session.sessionId]);
|
|
7753
|
+
else this.statusLogCursors.delete(session.sessionId);
|
|
7754
|
+
if (this.client.options.anonymousSessionId && !this.needsConnection()) this.disconnect();
|
|
6817
7755
|
return true;
|
|
6818
7756
|
}
|
|
7757
|
+
statusCursor(sessionId) {
|
|
7758
|
+
let cursor = this.statusLogCursors.get(sessionId);
|
|
7759
|
+
if (!cursor) {
|
|
7760
|
+
cursor = new LogCursor();
|
|
7761
|
+
this.statusLogCursors.set(sessionId, cursor);
|
|
7762
|
+
}
|
|
7763
|
+
return cursor;
|
|
7764
|
+
}
|
|
6819
7765
|
emitStatusSubscribe(sessionIds) {
|
|
6820
7766
|
if (sessionIds.length === 0) return;
|
|
6821
7767
|
const socket = this.client.socket();
|
|
7768
|
+
const payload = (sessionId) => ({
|
|
7769
|
+
session_id: sessionId,
|
|
7770
|
+
mode: "status",
|
|
7771
|
+
...this.statusCursor(sessionId).subscription()
|
|
7772
|
+
});
|
|
6822
7773
|
if (sessionIds.length === 1) {
|
|
6823
|
-
socket.emit("session:subscribe",
|
|
7774
|
+
socket.emit("session:subscribe", payload(sessionIds[0]));
|
|
6824
7775
|
return;
|
|
6825
7776
|
}
|
|
6826
7777
|
socket.emit("session:subscribe_batch", {
|
|
6827
|
-
sessions: sessionIds.map(
|
|
7778
|
+
sessions: sessionIds.map(payload)
|
|
6828
7779
|
});
|
|
6829
7780
|
}
|
|
6830
7781
|
/** 不管还有几方持有都立即释放(启动失败、切换身份)。 */
|
|
@@ -6921,7 +7872,51 @@ var SessionHub = class {
|
|
|
6921
7872
|
socket.on(
|
|
6922
7873
|
"turn:events",
|
|
6923
7874
|
(data, acknowledge) => {
|
|
7875
|
+
syncServerTime(this.client, data.server_time);
|
|
6924
7876
|
acknowledgeTurnEvents(acknowledge);
|
|
7877
|
+
if (data.session_id && (this.statusWatchCounts.has(data.session_id) || this.sessions.has(data.session_id)) && data.events?.some((event) => event.type === "log:record")) {
|
|
7878
|
+
const delivery = this.statusCursor(data.session_id).consume(data.events ?? []);
|
|
7879
|
+
for (const event of delivery.events) {
|
|
7880
|
+
if (event.type === "chat:start") {
|
|
7881
|
+
this.replayPlaybackIds.delete(data.session_id);
|
|
7882
|
+
this.hubEmitter.emit("runStarted", { sessionId: data.session_id });
|
|
7883
|
+
} else if (event.type === "chat:end") {
|
|
7884
|
+
this.hubEmitter.emit("runEnded", {
|
|
7885
|
+
sessionId: data.session_id,
|
|
7886
|
+
status: typeof event.payload.status === "string" ? event.payload.status : void 0,
|
|
7887
|
+
queued: false,
|
|
7888
|
+
replayed: data.replayed === true,
|
|
7889
|
+
...typeof event.payload.chat_run_id === "string" ? { chatRunId: event.payload.chat_run_id } : {}
|
|
7890
|
+
});
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7893
|
+
}
|
|
7894
|
+
if (data.session_id) {
|
|
7895
|
+
for (const event of data.events ?? []) {
|
|
7896
|
+
if (event.type === "log:retract") this.replayPlaybackIds.delete(data.session_id);
|
|
7897
|
+
if (event.type === "replay:state") {
|
|
7898
|
+
this.hubEmitter.emit("sessionUpdated", { sessionId: data.session_id, data: event.payload });
|
|
7899
|
+
}
|
|
7900
|
+
const playbackId = event.payload.playback_id;
|
|
7901
|
+
if (typeof playbackId !== "string") continue;
|
|
7902
|
+
if (event.type === "replay:start") {
|
|
7903
|
+
if (this.replayPlaybackIds.get(data.session_id) !== playbackId) {
|
|
7904
|
+
this.replayPlaybackIds.set(data.session_id, playbackId);
|
|
7905
|
+
this.hubEmitter.emit("runStarted", { sessionId: data.session_id });
|
|
7906
|
+
}
|
|
7907
|
+
} else if (event.type === "replay:end") {
|
|
7908
|
+
const current = this.replayPlaybackIds.get(data.session_id);
|
|
7909
|
+
if (current && current !== playbackId) continue;
|
|
7910
|
+
this.replayPlaybackIds.delete(data.session_id);
|
|
7911
|
+
this.hubEmitter.emit("runEnded", {
|
|
7912
|
+
sessionId: data.session_id,
|
|
7913
|
+
status: typeof event.payload.status === "string" ? event.payload.status : "completed",
|
|
7914
|
+
queued: false,
|
|
7915
|
+
replayed: true
|
|
7916
|
+
});
|
|
7917
|
+
}
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
6925
7920
|
for (const session of this.route(data.session_id)) {
|
|
6926
7921
|
session._handleTurnEvents(data.events ?? [], data.replayed === true);
|
|
6927
7922
|
}
|
|
@@ -7039,40 +8034,16 @@ var SessionHub = class {
|
|
|
7039
8034
|
(data) => {
|
|
7040
8035
|
for (const session of this.route(data.session_id)) {
|
|
7041
8036
|
for (const task of data.tasks ?? []) {
|
|
7042
|
-
const
|
|
7043
|
-
if (
|
|
7044
|
-
session._handleBackgroundTask({
|
|
7045
|
-
id: taskId,
|
|
7046
|
-
command: typeof task.command === "string" ? task.command : "",
|
|
7047
|
-
description: typeof task.description === "string" ? task.description : void 0,
|
|
7048
|
-
// Pass the server's status through unchanged. Collapsing it here
|
|
7049
|
-
// made subscribers see a successful-looking "done" for tasks the
|
|
7050
|
-
// REST API correctly reported as cancelled or lost.
|
|
7051
|
-
status: normalizeBackgroundStatus(task.status),
|
|
7052
|
-
failureReason: typeof task.failure_reason === "string" ? task.failure_reason : void 0,
|
|
7053
|
-
exitCode: typeof task.exit_code === "number" || task.exit_code === null ? task.exit_code : void 0,
|
|
7054
|
-
elapsedSeconds: typeof task.elapsed_seconds === "number" ? task.elapsed_seconds : 0,
|
|
7055
|
-
output: Array.isArray(task.output_preview) && task.output_preview.length > 0 ? task.output_preview.join("\n") : void 0
|
|
7056
|
-
});
|
|
8037
|
+
const completed = completedBackgroundTask(task);
|
|
8038
|
+
if (completed) session._handleBackgroundTask(completed);
|
|
7057
8039
|
}
|
|
7058
8040
|
}
|
|
7059
8041
|
}
|
|
7060
8042
|
);
|
|
7061
8043
|
}
|
|
7062
8044
|
routeArtifact(data, kind) {
|
|
7063
|
-
const filePath = typeof data.file_path === "string" ? data.file_path : "";
|
|
7064
|
-
if (!filePath) return;
|
|
7065
8045
|
for (const session of this.route(data.session_id)) {
|
|
7066
|
-
session.
|
|
7067
|
-
session._handleArtifact({
|
|
7068
|
-
filePath,
|
|
7069
|
-
contentType: typeof data.content_type === "string" ? data.content_type : "",
|
|
7070
|
-
title: typeof data.title === "string" ? data.title : filePath,
|
|
7071
|
-
content: typeof data.content === "string" ? data.content : "",
|
|
7072
|
-
oldString: typeof data.old_string === "string" ? data.old_string : void 0,
|
|
7073
|
-
newString: typeof data.new_string === "string" ? data.new_string : void 0,
|
|
7074
|
-
kind
|
|
7075
|
-
});
|
|
8046
|
+
session._handleArtifact(data, kind);
|
|
7076
8047
|
}
|
|
7077
8048
|
}
|
|
7078
8049
|
};
|
|
@@ -7103,12 +8074,15 @@ function createSocket(options) {
|
|
|
7103
8074
|
|
|
7104
8075
|
// src/blade-client.ts
|
|
7105
8076
|
var REFRESH_PATH = "/api/auth/refresh";
|
|
7106
|
-
var BladeClient = class {
|
|
8077
|
+
var BladeClient = class _BladeClient {
|
|
8078
|
+
anonymousClients = /* @__PURE__ */ new Map();
|
|
8079
|
+
toolProviders = /* @__PURE__ */ new Set();
|
|
7107
8080
|
refreshPromise = null;
|
|
8081
|
+
cookieLogoutPending = 0;
|
|
7108
8082
|
socketInstance = null;
|
|
7109
8083
|
socketReplacementListeners = /* @__PURE__ */ new Set();
|
|
7110
8084
|
/** login() / setToken() 热挂载的令牌,优先级高于构造参数。 */
|
|
7111
|
-
runtimeToken =
|
|
8085
|
+
runtimeToken = void 0;
|
|
7112
8086
|
options;
|
|
7113
8087
|
auth;
|
|
7114
8088
|
headless;
|
|
@@ -7126,6 +8100,9 @@ var BladeClient = class {
|
|
|
7126
8100
|
/** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
|
|
7127
8101
|
hub;
|
|
7128
8102
|
constructor(options) {
|
|
8103
|
+
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)) {
|
|
8104
|
+
throw new Error("\u533F\u540D\u4F1A\u8BDD ID \u5FC5\u987B\u662F UUID v4");
|
|
8105
|
+
}
|
|
7129
8106
|
this.options = {
|
|
7130
8107
|
...options,
|
|
7131
8108
|
baseUrl: normalizeBaseUrl(options.baseUrl)
|
|
@@ -7139,10 +8116,16 @@ var BladeClient = class {
|
|
|
7139
8116
|
this.chatGroups = new ChatGroupsResource(this);
|
|
7140
8117
|
this.siteVisits = new SiteVisitsResource(this);
|
|
7141
8118
|
this.hub = new SessionHub(this);
|
|
7142
|
-
if (this.options.token === void 0) {
|
|
7143
|
-
this.runtimeToken = readStoredToken(this.options.baseUrl);
|
|
8119
|
+
if (this.options.token === void 0 && !this.options.anonymousSessionId) {
|
|
8120
|
+
this.runtimeToken = readStoredToken(this.options.baseUrl) ?? void 0;
|
|
7144
8121
|
}
|
|
7145
8122
|
}
|
|
8123
|
+
/** Backend application tools, callable over an outbound WebSocket. */
|
|
8124
|
+
connectToolProvider(options) {
|
|
8125
|
+
const provider = new ToolProvider(this, options, () => this.toolProviders.delete(provider));
|
|
8126
|
+
this.toolProviders.add(provider);
|
|
8127
|
+
return provider;
|
|
8128
|
+
}
|
|
7146
8129
|
/**
|
|
7147
8130
|
* 弹窗授权登录:用户在弹出的授权页点击"许可授权"后自动取得 PAT,
|
|
7148
8131
|
* REST 与 Socket.IO 立即生效。只在浏览器可用。
|
|
@@ -7162,7 +8145,12 @@ var BladeClient = class {
|
|
|
7162
8145
|
const previousToken = this.resolveToken();
|
|
7163
8146
|
this.runtimeToken = token;
|
|
7164
8147
|
const nextToken = this.resolveToken();
|
|
8148
|
+
if (token === null) for (const provider of this.toolProviders) provider.dispose();
|
|
7165
8149
|
if (previousToken !== nextToken) {
|
|
8150
|
+
for (const provider of this.toolProviders) {
|
|
8151
|
+
if (nextToken) provider.reconnect();
|
|
8152
|
+
else provider.dispose();
|
|
8153
|
+
}
|
|
7166
8154
|
this.hub.disposeAll();
|
|
7167
8155
|
}
|
|
7168
8156
|
writeStoredToken(this.options.baseUrl, token, this.options.tokenStorage ?? "local");
|
|
@@ -7195,12 +8183,27 @@ var BladeClient = class {
|
|
|
7195
8183
|
get token() {
|
|
7196
8184
|
return this.resolveRestToken();
|
|
7197
8185
|
}
|
|
8186
|
+
/** 获取只访问指定匿名会话的客户端;创建仍由已认证的原客户端完成。 */
|
|
8187
|
+
forAnonymousSession(sessionId) {
|
|
8188
|
+
let client = this.anonymousClients.get(sessionId);
|
|
8189
|
+
if (!client) {
|
|
8190
|
+
client = new _BladeClient({ baseUrl: this.options.baseUrl, anonymousSessionId: sessionId, fetchImpl: this.options.fetchImpl, commandTransport: this.options.commandTransport });
|
|
8191
|
+
this.anonymousClients.set(sessionId, client);
|
|
8192
|
+
}
|
|
8193
|
+
return client;
|
|
8194
|
+
}
|
|
7198
8195
|
setBaseUrl(baseUrl) {
|
|
7199
8196
|
const nextBaseUrl = normalizeBaseUrl(baseUrl);
|
|
7200
8197
|
if (nextBaseUrl === this.options.baseUrl) {
|
|
7201
8198
|
return;
|
|
7202
8199
|
}
|
|
8200
|
+
for (const provider of this.toolProviders) provider.dispose();
|
|
7203
8201
|
this.hub.disposeAll();
|
|
8202
|
+
for (const client of this.anonymousClients.values()) {
|
|
8203
|
+
client.hub.disposeAll();
|
|
8204
|
+
client.hub.disconnect();
|
|
8205
|
+
}
|
|
8206
|
+
this.anonymousClients.clear();
|
|
7204
8207
|
this.options.baseUrl = nextBaseUrl;
|
|
7205
8208
|
if (this.socketInstance) {
|
|
7206
8209
|
this.socketInstance.removeAllListeners();
|
|
@@ -7240,7 +8243,10 @@ var BladeClient = class {
|
|
|
7240
8243
|
else resolve(ack ?? {});
|
|
7241
8244
|
});
|
|
7242
8245
|
});
|
|
7243
|
-
|
|
8246
|
+
const transport = this.options.commandTransport;
|
|
8247
|
+
if (!transport) return send();
|
|
8248
|
+
const handled = this.options.anonymousSessionId ? transport(event, payload, send, { anonymousSessionId: this.options.anonymousSessionId }) : transport(event, payload, send);
|
|
8249
|
+
return handled ?? send();
|
|
7244
8250
|
}
|
|
7245
8251
|
async json(method, path, body) {
|
|
7246
8252
|
const response = await this.fetch(method, path, {
|
|
@@ -7298,24 +8304,31 @@ var BladeClient = class {
|
|
|
7298
8304
|
}
|
|
7299
8305
|
async fetch(method, path, init = {}, isRetry = false) {
|
|
7300
8306
|
const url = this.buildUrl(path);
|
|
7301
|
-
const
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
8307
|
+
const isLogout = method === "POST" && url.pathname === this.buildUrl("/api/auth/logout").pathname && this.isSameBackendUrl(url);
|
|
8308
|
+
if (isLogout) this.cookieLogoutPending += 1;
|
|
8309
|
+
try {
|
|
8310
|
+
if (isLogout) await this.refreshPromise;
|
|
8311
|
+
const response = await (this.options.fetchImpl ?? fetch)(url.toString(), {
|
|
8312
|
+
method,
|
|
8313
|
+
body: init.body,
|
|
8314
|
+
credentials: init.credentials ?? "include",
|
|
8315
|
+
headers: this.buildHeaders(url, init.headers),
|
|
8316
|
+
signal: init.signal
|
|
8317
|
+
});
|
|
8318
|
+
if (response.status === 401 && !isRetry && this.shouldRefreshFor401(url)) {
|
|
8319
|
+
const refreshed = await this.tryRefreshUnlessAborted(init.signal);
|
|
8320
|
+
if (refreshed) {
|
|
8321
|
+
return this.fetch(method, path, init, true);
|
|
8322
|
+
}
|
|
7312
8323
|
}
|
|
8324
|
+
if (init.expectOk !== false && !response.ok) {
|
|
8325
|
+
const detail = await extractErrorDetail(response);
|
|
8326
|
+
throw decorateAuthError(new BladeApiError(response, detail));
|
|
8327
|
+
}
|
|
8328
|
+
return response;
|
|
8329
|
+
} finally {
|
|
8330
|
+
if (isLogout) this.cookieLogoutPending -= 1;
|
|
7313
8331
|
}
|
|
7314
|
-
if (init.expectOk !== false && !response.ok) {
|
|
7315
|
-
const detail = await extractErrorDetail(response);
|
|
7316
|
-
throw decorateAuthError(new BladeApiError(response, detail));
|
|
7317
|
-
}
|
|
7318
|
-
return response;
|
|
7319
8332
|
}
|
|
7320
8333
|
buildAuthedUrl(path) {
|
|
7321
8334
|
const url = buildBackendUrl(this.options.baseUrl, path);
|
|
@@ -7383,6 +8396,7 @@ var BladeClient = class {
|
|
|
7383
8396
|
}
|
|
7384
8397
|
}
|
|
7385
8398
|
async tryRefresh() {
|
|
8399
|
+
if (this.options.anonymousSessionId || this.cookieLogoutPending > 0) return false;
|
|
7386
8400
|
if (this.refreshPromise) {
|
|
7387
8401
|
return this.refreshPromise;
|
|
7388
8402
|
}
|
|
@@ -7417,14 +8431,16 @@ var BladeClient = class {
|
|
|
7417
8431
|
return this.resolveRestToken();
|
|
7418
8432
|
}
|
|
7419
8433
|
resolveRestToken() {
|
|
8434
|
+
if (this.options.anonymousSessionId) return `ba-anonymous-${this.options.anonymousSessionId}`;
|
|
7420
8435
|
return this.resolveToken();
|
|
7421
8436
|
}
|
|
7422
8437
|
resolveSocketToken() {
|
|
7423
|
-
if (this.
|
|
8438
|
+
if (this.options.anonymousSessionId) return `ba-anonymous-${this.options.anonymousSessionId}`;
|
|
8439
|
+
if (this.runtimeToken !== void 0) return this.runtimeToken;
|
|
7424
8440
|
return resolveTokenOption(this.options.socketToken ?? this.options.token);
|
|
7425
8441
|
}
|
|
7426
8442
|
resolveToken() {
|
|
7427
|
-
if (this.runtimeToken) return this.runtimeToken;
|
|
8443
|
+
if (this.runtimeToken !== void 0) return this.runtimeToken;
|
|
7428
8444
|
return resolveTokenOption(this.options.token);
|
|
7429
8445
|
}
|
|
7430
8446
|
formDataWithUploadProgress(method, path, form, options, isRetry = false) {
|
|
@@ -7522,22 +8538,14 @@ function parseXhrHeaders(rawHeaders) {
|
|
|
7522
8538
|
}
|
|
7523
8539
|
|
|
7524
8540
|
export {
|
|
8541
|
+
BladeApiError,
|
|
8542
|
+
ToolProvider,
|
|
7525
8543
|
SDK_NAME,
|
|
7526
8544
|
SDK_VERSION,
|
|
7527
|
-
BladeApiError,
|
|
7528
8545
|
isRecord,
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
computerDaemonVersion,
|
|
7533
|
-
canToggleComputer,
|
|
7534
|
-
sortComputers,
|
|
7535
|
-
ComputersResource,
|
|
7536
|
-
ModelsResource,
|
|
7537
|
-
SessionStatus,
|
|
7538
|
-
DEFAULT_REPLAY_SPEED,
|
|
7539
|
-
SessionInfo,
|
|
7540
|
-
SessionSetupError,
|
|
8546
|
+
normalizePluginRefs,
|
|
8547
|
+
normalizeMessageRefs,
|
|
8548
|
+
normalizeTopicAssetRefs,
|
|
7541
8549
|
inferToolStatus,
|
|
7542
8550
|
contextProjectionData,
|
|
7543
8551
|
getContextDisplayState,
|
|
@@ -7555,9 +8563,23 @@ export {
|
|
|
7555
8563
|
isAppDevToolUiCard,
|
|
7556
8564
|
collectInlineToolUiCards,
|
|
7557
8565
|
collectPreviewToolUiCards,
|
|
8566
|
+
computerState,
|
|
8567
|
+
computerOS,
|
|
8568
|
+
computerPlatformLabel,
|
|
8569
|
+
computerDaemonVersion,
|
|
8570
|
+
canToggleComputer,
|
|
8571
|
+
sortComputers,
|
|
8572
|
+
ComputersResource,
|
|
8573
|
+
ModelsResource,
|
|
8574
|
+
SessionStatus,
|
|
8575
|
+
DEFAULT_REPLAY_SPEED,
|
|
8576
|
+
SessionInfo,
|
|
8577
|
+
SessionSetupError,
|
|
7558
8578
|
isEntryGoneError,
|
|
7559
8579
|
PluginsResource,
|
|
7560
8580
|
ChatGroupsResource,
|
|
8581
|
+
syncServerTime,
|
|
8582
|
+
getServerTime,
|
|
7561
8583
|
authBusyRetryDelayMs,
|
|
7562
8584
|
AuthBusyReconnect,
|
|
7563
8585
|
prependOlder,
|
|
@@ -7604,4 +8626,4 @@ export {
|
|
|
7604
8626
|
createSocket,
|
|
7605
8627
|
BladeClient
|
|
7606
8628
|
};
|
|
7607
|
-
//# sourceMappingURL=chunk-
|
|
8629
|
+
//# sourceMappingURL=chunk-J3XXTHHK.js.map
|