@blade-hq/agent-client 2612.0.0-beta.4 → 2612.0.0-beta.40
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 +244 -28
- package/dist/application-client.d.ts +19 -0
- package/dist/blade-client.d.ts +20 -9
- package/dist/{chunk-4LJNZA3I.js → chunk-SNMDYU6W.js} +2456 -945
- package/dist/chunk-SNMDYU6W.js.map +1 -0
- package/dist/index.d.ts +16 -3
- package/dist/index.js +15 -1
- package/dist/internal.d.ts +13 -4
- package/dist/internal.js +21 -3
- 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/computers.d.ts +1 -6
- package/dist/resources/connectors.d.ts +89 -0
- package/dist/resources/headless.d.ts +5 -0
- package/dist/resources/models.d.ts +6 -1
- package/dist/resources/plugins.d.ts +2 -0
- package/dist/resources/sandbox.d.ts +19 -0
- package/dist/resources/sessions.d.ts +85 -15
- package/dist/schemas/context-attachments.d.ts +18 -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 -48
- 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 -98
- package/dist/session/background.d.ts +14 -0
- package/dist/session/definition.d.ts +3 -0
- package/dist/session/events.d.ts +0 -1
- package/dist/session/hub.d.ts +3 -24
- 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 -2
- 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 +52 -0
- package/dist/types/socket-events.d.ts +183 -8
- package/dist/version.d.ts +0 -1
- 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 +421 -38
- package/dist/chunk-4LJNZA3I.js.map +0 -1
|
@@ -1,6 +1,230 @@
|
|
|
1
|
+
// src/resources/sandbox.ts
|
|
2
|
+
var SandboxResource = class {
|
|
3
|
+
constructor(client) {
|
|
4
|
+
this.client = client;
|
|
5
|
+
}
|
|
6
|
+
client;
|
|
7
|
+
execute(input) {
|
|
8
|
+
return this.client.json("POST", "/api/sandbox/execute", input);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/rest.ts
|
|
13
|
+
var DOCS_URL = "https://blade-hq.github.io/public-skills/";
|
|
14
|
+
var BladeApiError = class extends Error {
|
|
15
|
+
response;
|
|
16
|
+
status;
|
|
17
|
+
statusText;
|
|
18
|
+
constructor(response, message) {
|
|
19
|
+
super(message ?? `\u8BF7\u6C42\u5931\u8D25\uFF08${response.status} ${response.statusText}\uFF09`);
|
|
20
|
+
this.name = "BladeApiError";
|
|
21
|
+
this.response = response;
|
|
22
|
+
this.status = response.status;
|
|
23
|
+
this.statusText = response.statusText;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
async function extractErrorDetail(response) {
|
|
27
|
+
try {
|
|
28
|
+
const cloned = response.clone();
|
|
29
|
+
const contentType = cloned.headers.get("content-type") ?? "";
|
|
30
|
+
if (contentType.includes("application/json")) {
|
|
31
|
+
const data = await cloned.json();
|
|
32
|
+
const detail = data?.detail;
|
|
33
|
+
if (typeof detail === "string" && detail.trim()) return detail;
|
|
34
|
+
if (Array.isArray(detail) && detail.length > 0) {
|
|
35
|
+
const first = detail[0];
|
|
36
|
+
if (first && typeof first.msg === "string") return first.msg;
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
const text = (await cloned.text()).trim();
|
|
40
|
+
if (text) return text;
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
function decorateAuthError(error) {
|
|
47
|
+
if (!(error instanceof BladeApiError)) return error;
|
|
48
|
+
if (error.status === 401) {
|
|
49
|
+
error.message = `${error.message}\uFF08\u672A\u767B\u5F55\u6216\u51ED\u8BC1\u5DF2\u5931\u6548\u3002\u6D4F\u89C8\u5668\u573A\u666F\u8C03\u7528 client.auth.login() \u5F39\u7A97\u767B\u5F55\uFF0C\u81EA\u52A8\u5316\u573A\u666F\u5728 new BladeClient({ token }) \u4F20\u5165 PAT\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
|
|
50
|
+
} else if (error.status === 403) {
|
|
51
|
+
error.message = `${error.message}\uFF08\u5F53\u524D\u8D26\u53F7\u65E0\u6743\u8BBF\u95EE\u8BE5\u8D44\u6E90\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
|
|
52
|
+
}
|
|
53
|
+
return error;
|
|
54
|
+
}
|
|
55
|
+
function buildBackendUrl(baseUrl, path) {
|
|
56
|
+
const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
57
|
+
return new URL(path.startsWith("/") ? path.slice(1) : path, base);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/tool-provider.ts
|
|
61
|
+
var ToolProvider = class {
|
|
62
|
+
constructor(client, options, onDispose) {
|
|
63
|
+
this.client = client;
|
|
64
|
+
this.options = options;
|
|
65
|
+
this.onDispose = onDispose;
|
|
66
|
+
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(options.providerId) || options.capability.length < 16 || options.capability.length > 4096) throw new Error("Invalid tool provider identity");
|
|
67
|
+
if (new Set(options.tools.map((t) => t.name)).size !== options.tools.length || options.tools.length > 256) throw new Error("Duplicate or excessive provider tools");
|
|
68
|
+
this.url = `blade-rpc://${options.providerId}`;
|
|
69
|
+
this.open();
|
|
70
|
+
}
|
|
71
|
+
client;
|
|
72
|
+
options;
|
|
73
|
+
onDispose;
|
|
74
|
+
url;
|
|
75
|
+
socket;
|
|
76
|
+
timer;
|
|
77
|
+
registrationTimer;
|
|
78
|
+
disposed = false;
|
|
79
|
+
registered = false;
|
|
80
|
+
attempts = 0;
|
|
81
|
+
calls = /* @__PURE__ */ new Map();
|
|
82
|
+
waiters = /* @__PURE__ */ new Set();
|
|
83
|
+
/** Resolves only after BA has authenticated and registered this connection. */
|
|
84
|
+
ready() {
|
|
85
|
+
if (this.disposed) return Promise.reject(new Error("Tool provider disposed"));
|
|
86
|
+
if (this.registered) return Promise.resolve();
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const timer = setTimeout(() => {
|
|
89
|
+
this.waiters.delete(waiter);
|
|
90
|
+
reject(new Error("Tool provider registration timed out"));
|
|
91
|
+
}, 25e3);
|
|
92
|
+
const waiter = { resolve: () => {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
resolve();
|
|
95
|
+
}, reject: (e) => {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
reject(e);
|
|
98
|
+
} };
|
|
99
|
+
this.waiters.add(waiter);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
reconnect() {
|
|
103
|
+
if (this.disposed) return;
|
|
104
|
+
clearTimeout(this.timer);
|
|
105
|
+
this.reset();
|
|
106
|
+
this.open();
|
|
107
|
+
}
|
|
108
|
+
reset() {
|
|
109
|
+
clearTimeout(this.registrationTimer);
|
|
110
|
+
this.registered = false;
|
|
111
|
+
const socket = this.socket;
|
|
112
|
+
this.socket = void 0;
|
|
113
|
+
socket?.close();
|
|
114
|
+
for (const controller of this.calls.values()) controller.abort();
|
|
115
|
+
this.calls.clear();
|
|
116
|
+
}
|
|
117
|
+
fail(error) {
|
|
118
|
+
for (const waiter of this.waiters) waiter.reject(error);
|
|
119
|
+
this.waiters.clear();
|
|
120
|
+
try {
|
|
121
|
+
this.options.onError?.(error);
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
open() {
|
|
126
|
+
if (this.disposed) return;
|
|
127
|
+
const endpoint = buildBackendUrl(this.client.options.baseUrl, "/api/app-tools/connect");
|
|
128
|
+
endpoint.protocol = endpoint.protocol === "https:" ? "wss:" : "ws:";
|
|
129
|
+
let socket;
|
|
130
|
+
try {
|
|
131
|
+
const token = this.client.token;
|
|
132
|
+
if (!token) throw new Error("Tool provider requires a token");
|
|
133
|
+
const encoded = btoa(Array.from(new TextEncoder().encode(token), (byte) => String.fromCharCode(byte)).join("")).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
134
|
+
socket = this.client.options.applicationTransport?.provider() ?? new WebSocket(endpoint, ["blade-rpc", `blade-bearer.${encoded}`]);
|
|
135
|
+
} catch {
|
|
136
|
+
this.fail(new Error("Tool provider WebSocket unavailable"));
|
|
137
|
+
this.schedule();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this.socket = socket;
|
|
141
|
+
this.registrationTimer = setTimeout(() => {
|
|
142
|
+
if (this.socket === socket) {
|
|
143
|
+
this.fail(new Error("Tool provider registration timed out"));
|
|
144
|
+
socket.close();
|
|
145
|
+
}
|
|
146
|
+
}, 15e3);
|
|
147
|
+
socket.addEventListener("open", () => {
|
|
148
|
+
if (this.socket !== socket) return;
|
|
149
|
+
socket.send(JSON.stringify({ jsonrpc: "2.0", id: "register", method: "provider/register", params: { providerId: this.options.providerId, capability: this.options.capability } }));
|
|
150
|
+
});
|
|
151
|
+
socket.addEventListener("message", (event) => {
|
|
152
|
+
if (this.socket !== socket || typeof event.data !== "string") return;
|
|
153
|
+
void this.receive(socket, event.data).catch(() => {
|
|
154
|
+
this.fail(new Error("Invalid application tool RPC message"));
|
|
155
|
+
socket.close();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
socket.addEventListener("error", () => {
|
|
159
|
+
if (this.socket === socket) this.fail(new Error("Application tool connection failed"));
|
|
160
|
+
});
|
|
161
|
+
socket.addEventListener("close", () => {
|
|
162
|
+
if (this.socket !== socket) return;
|
|
163
|
+
this.reset();
|
|
164
|
+
this.fail(new Error("Application tool connection closed; unfinished calls will not be replayed"));
|
|
165
|
+
this.schedule();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
schedule() {
|
|
169
|
+
if (this.disposed) return;
|
|
170
|
+
this.timer = setTimeout(() => this.open(), Math.min(1e4, 500 * 2 ** Math.min(this.attempts++, 5)));
|
|
171
|
+
}
|
|
172
|
+
async receive(socket, text) {
|
|
173
|
+
const message = JSON.parse(text);
|
|
174
|
+
if (message.jsonrpc !== "2.0") throw new Error("Invalid JSON-RPC envelope");
|
|
175
|
+
if (message.id === "register" && !message.method) {
|
|
176
|
+
clearTimeout(this.registrationTimer);
|
|
177
|
+
if (message.error || !message.result?.registered) {
|
|
178
|
+
this.fail(new Error(message.error?.message ?? "Tool provider registration rejected"));
|
|
179
|
+
socket.close();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
this.registered = true;
|
|
183
|
+
this.attempts = 0;
|
|
184
|
+
for (const waiter of this.waiters) waiter.resolve();
|
|
185
|
+
this.waiters.clear();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!this.registered) throw new Error("Provider not registered");
|
|
189
|
+
if (message.method === "notifications/cancelled") {
|
|
190
|
+
this.calls.get(String(message.params?.requestId))?.abort();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!message.id || typeof message.id !== "string" || this.calls.has(message.id)) throw new Error("Invalid RPC request ID");
|
|
194
|
+
const controller = new AbortController();
|
|
195
|
+
this.calls.set(message.id, controller);
|
|
196
|
+
let response = { jsonrpc: "2.0", id: message.id };
|
|
197
|
+
try {
|
|
198
|
+
if (message.method === "tools/list") {
|
|
199
|
+
response.result = { tools: this.options.tools.map(({ name, description, inputSchema, outputSchema }) => ({ name, description, inputSchema, ...outputSchema ? { outputSchema } : {} })) };
|
|
200
|
+
} else if (message.method === "tools/call") {
|
|
201
|
+
const params = message.params;
|
|
202
|
+
const tool = this.options.tools.find((t) => t.name === params?.name);
|
|
203
|
+
if (!tool || typeof params?.sessionId !== "string" || !params.sessionId || !params.arguments || typeof params.arguments !== "object" || Array.isArray(params.arguments)) throw new Error("Invalid or unauthorized tool call");
|
|
204
|
+
response.result = await tool.handler(params.arguments, { sessionId: params.sessionId, signal: controller.signal });
|
|
205
|
+
} else {
|
|
206
|
+
response.error = { code: -32601, message: "Method not found" };
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
response = { jsonrpc: "2.0", id: message.id, error: { code: -32e3, message: "Application tool execution failed" } };
|
|
210
|
+
} finally {
|
|
211
|
+
this.calls.delete(message.id);
|
|
212
|
+
}
|
|
213
|
+
if (this.socket === socket && socket.readyState === WebSocket.OPEN && !controller.signal.aborted) socket.send(JSON.stringify(response));
|
|
214
|
+
}
|
|
215
|
+
dispose() {
|
|
216
|
+
if (this.disposed) return;
|
|
217
|
+
this.disposed = true;
|
|
218
|
+
clearTimeout(this.timer);
|
|
219
|
+
this.reset();
|
|
220
|
+
this.fail(new Error("Tool provider disposed"));
|
|
221
|
+
this.onDispose();
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
1
225
|
// src/version.ts
|
|
2
226
|
var SDK_NAME = "agent-client";
|
|
3
|
-
var SDK_VERSION = true ? "2612.0.0-beta.
|
|
227
|
+
var SDK_VERSION = true ? "2612.0.0-beta.40" : "1.1.1";
|
|
4
228
|
|
|
5
229
|
// src/auth-login.ts
|
|
6
230
|
function isAuthCallbackMessage(value) {
|
|
@@ -128,54 +352,6 @@ async function loginWithPopup(client, options = {}) {
|
|
|
128
352
|
});
|
|
129
353
|
}
|
|
130
354
|
|
|
131
|
-
// src/rest.ts
|
|
132
|
-
var DOCS_URL = "https://blade-hq.github.io/public-skills/";
|
|
133
|
-
var BladeApiError = class extends Error {
|
|
134
|
-
response;
|
|
135
|
-
status;
|
|
136
|
-
statusText;
|
|
137
|
-
constructor(response, message) {
|
|
138
|
-
super(message ?? `\u8BF7\u6C42\u5931\u8D25\uFF08${response.status} ${response.statusText}\uFF09`);
|
|
139
|
-
this.name = "BladeApiError";
|
|
140
|
-
this.response = response;
|
|
141
|
-
this.status = response.status;
|
|
142
|
-
this.statusText = response.statusText;
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
async function extractErrorDetail(response) {
|
|
146
|
-
try {
|
|
147
|
-
const cloned = response.clone();
|
|
148
|
-
const contentType = cloned.headers.get("content-type") ?? "";
|
|
149
|
-
if (contentType.includes("application/json")) {
|
|
150
|
-
const data = await cloned.json();
|
|
151
|
-
const detail = data?.detail;
|
|
152
|
-
if (typeof detail === "string" && detail.trim()) return detail;
|
|
153
|
-
if (Array.isArray(detail) && detail.length > 0) {
|
|
154
|
-
const first = detail[0];
|
|
155
|
-
if (first && typeof first.msg === "string") return first.msg;
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
const text = (await cloned.text()).trim();
|
|
159
|
-
if (text) return text;
|
|
160
|
-
}
|
|
161
|
-
} catch {
|
|
162
|
-
}
|
|
163
|
-
return void 0;
|
|
164
|
-
}
|
|
165
|
-
function decorateAuthError(error) {
|
|
166
|
-
if (!(error instanceof BladeApiError)) return error;
|
|
167
|
-
if (error.status === 401) {
|
|
168
|
-
error.message = `${error.message}\uFF08\u672A\u767B\u5F55\u6216\u51ED\u8BC1\u5DF2\u5931\u6548\u3002\u6D4F\u89C8\u5668\u573A\u666F\u8C03\u7528 client.auth.login() \u5F39\u7A97\u767B\u5F55\uFF0C\u81EA\u52A8\u5316\u573A\u666F\u5728 new BladeClient({ token }) \u4F20\u5165 PAT\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
|
|
169
|
-
} else if (error.status === 403) {
|
|
170
|
-
error.message = `${error.message}\uFF08\u5F53\u524D\u8D26\u53F7\u65E0\u6743\u8BBF\u95EE\u8BE5\u8D44\u6E90\u3002\u63A5\u5165\u6307\u5357\uFF1A${DOCS_URL}\uFF09`;
|
|
171
|
-
}
|
|
172
|
-
return error;
|
|
173
|
-
}
|
|
174
|
-
function buildBackendUrl(baseUrl, path) {
|
|
175
|
-
const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
176
|
-
return new URL(path.startsWith("/") ? path.slice(1) : path, base);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
355
|
// src/resources/auth.ts
|
|
180
356
|
var AuthResource = class {
|
|
181
357
|
constructor(client) {
|
|
@@ -214,8 +390,8 @@ var AuthResource = class {
|
|
|
214
390
|
getProviders() {
|
|
215
391
|
return this.client.json("GET", "/api/auth/providers");
|
|
216
392
|
}
|
|
217
|
-
getMe() {
|
|
218
|
-
return this.client.
|
|
393
|
+
getMe(options = {}) {
|
|
394
|
+
return this.client.jsonFromInit("/api/auth/me", { signal: options.signal });
|
|
219
395
|
}
|
|
220
396
|
logout() {
|
|
221
397
|
return this.client.json("POST", "/api/auth/logout");
|
|
@@ -227,404 +403,168 @@ function isRecord(value) {
|
|
|
227
403
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
228
404
|
}
|
|
229
405
|
|
|
230
|
-
// src/
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
406
|
+
// src/session/log-cursor.ts
|
|
407
|
+
function positive(value) {
|
|
408
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
409
|
+
}
|
|
410
|
+
var LogCursor = class {
|
|
411
|
+
sequence = 0;
|
|
412
|
+
epoch = 0;
|
|
413
|
+
deltas = /* @__PURE__ */ new Map();
|
|
414
|
+
subscription() {
|
|
415
|
+
return this.sequence > 0 ? { after_log_seq: this.sequence } : {};
|
|
416
|
+
}
|
|
417
|
+
consume(events) {
|
|
418
|
+
const accepted = [];
|
|
419
|
+
let reload = false;
|
|
420
|
+
for (const event of events) {
|
|
421
|
+
if (event.type === "log:retract") {
|
|
422
|
+
this.deltas.clear();
|
|
423
|
+
reload = true;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (event.type === "log:record") {
|
|
427
|
+
if (!positive(event.log_seq) || event.log_seq <= this.sequence) continue;
|
|
428
|
+
const nested = event.payload.events;
|
|
429
|
+
if (!Array.isArray(nested)) continue;
|
|
430
|
+
if (positive(event.payload.epoch) && event.payload.epoch !== this.epoch) {
|
|
431
|
+
this.epoch = event.payload.epoch;
|
|
432
|
+
this.deltas.clear();
|
|
433
|
+
reload = true;
|
|
434
|
+
}
|
|
435
|
+
accepted.push(...nested);
|
|
436
|
+
if (["step_end", "run_end", "truncate"].includes(String(event.payload.kind))) {
|
|
437
|
+
for (const step of this.deltas.keys()) if (step <= event.log_seq) this.deltas.delete(step);
|
|
438
|
+
}
|
|
439
|
+
this.sequence = event.log_seq;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (event.type === "llm:stream:reset" && positive(event.step_log_seq)) {
|
|
443
|
+
this.deltas.delete(event.step_log_seq);
|
|
444
|
+
}
|
|
445
|
+
if (positive(event.step_log_seq) && positive(event.delta_seq)) {
|
|
446
|
+
if (event.delta_seq <= (this.deltas.get(event.step_log_seq) ?? 0)) continue;
|
|
447
|
+
this.deltas.set(event.step_log_seq, event.delta_seq);
|
|
448
|
+
}
|
|
449
|
+
if (event.type === "log:delta" && Array.isArray(event.payload.events)) {
|
|
450
|
+
accepted.push(...event.payload.events);
|
|
451
|
+
} else {
|
|
452
|
+
accepted.push(event);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return { events: accepted, reload };
|
|
236
456
|
}
|
|
237
|
-
detail;
|
|
238
457
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
458
|
+
|
|
459
|
+
// src/schemas/context-attachments.ts
|
|
460
|
+
function snapshotContextAttachments(value) {
|
|
461
|
+
const snapshot2 = JSON.parse(JSON.stringify(value));
|
|
462
|
+
if (!Array.isArray(snapshot2) || snapshot2.length > 20) throw new Error("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u63D0\u4F9B 20 \u4EFD\u8D44\u6599");
|
|
463
|
+
const ids = /* @__PURE__ */ new Set();
|
|
464
|
+
const validText = (text, limit) => typeof text === "string" && [...text].length > 0 && [...text].length <= limit;
|
|
465
|
+
for (const item of snapshot2) {
|
|
466
|
+
if (!item || !validText(item.id, 128) || !validText(item.label, 512) || !validText(item.content, 64e3) || ids.has(item.id)) throw new Error("\u9644\u52A0\u8D44\u6599\u683C\u5F0F\u9519\u8BEF\u6216 ID \u91CD\u590D");
|
|
467
|
+
ids.add(item.id);
|
|
468
|
+
if (item.update_format !== void 0 && item.update_format !== "snapshot" && item.update_format !== "diff") throw new Error("\u8D44\u6599\u66F4\u65B0\u683C\u5F0F\u65E0\u6548");
|
|
469
|
+
if (item.data != null && (typeof item.data !== "object" || Array.isArray(item.data))) throw new Error("\u8D44\u6599\u6570\u636E\u5FC5\u987B\u662F JSON \u5BF9\u8C61");
|
|
470
|
+
if (item.files != null && (!Array.isArray(item.files) || item.files.length > 20)) throw new Error("\u8D44\u6599\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF");
|
|
471
|
+
for (const file of item.files ?? []) {
|
|
472
|
+
if (!validText(file.name, 512)) throw new Error("\u8D44\u6599\u6587\u4EF6\u540D\u683C\u5F0F\u9519\u8BEF");
|
|
473
|
+
if (file.session_id == null && file.path == null) continue;
|
|
474
|
+
if (!validText(file.session_id, 128) || !validText(file.path, 2048) || /[\\:]/.test(file.path) || file.path.replace(/^\//, "").split("/").some((part) => !part || part === "." || part === "..")) throw new Error("\u8D44\u6599\u6587\u4EF6\u5FC5\u987B\u4F7F\u7528\u6709\u6548\u7684\u4F1A\u8BDD\u6587\u4EF6\u8DEF\u5F84");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (new TextEncoder().encode(JSON.stringify(snapshot2)).length > 256 * 1024) throw new Error("\u6D88\u606F\u9644\u52A0\u8D44\u6599\u4E0D\u80FD\u8D85\u8FC7 256 KiB");
|
|
478
|
+
return snapshot2;
|
|
479
|
+
}
|
|
480
|
+
function normalizeContextAttachments(value) {
|
|
481
|
+
return Array.isArray(value) ? value : [];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/schemas/plugin-refs.ts
|
|
485
|
+
function normalizePluginRefs(value) {
|
|
486
|
+
if (!Array.isArray(value)) return [];
|
|
487
|
+
const seen = /* @__PURE__ */ new Set();
|
|
488
|
+
return value.flatMap((item) => {
|
|
489
|
+
if (!item || typeof item !== "object" || item.provider !== "plugin" || ![item.plugin, item.server, item.id, item.label].every((part) => typeof part === "string" && part)) return [];
|
|
490
|
+
const key = JSON.stringify([item.plugin, item.server, item.id]);
|
|
491
|
+
if (seen.has(key)) return [];
|
|
492
|
+
seen.add(key);
|
|
493
|
+
return [{ provider: "plugin", plugin: item.plugin, server: item.server, id: item.id, label: item.label }];
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
function normalizeMessageRefs(value) {
|
|
497
|
+
if (!Array.isArray(value)) return [];
|
|
498
|
+
const seen = /* @__PURE__ */ new Set();
|
|
499
|
+
return value.flatMap((item) => {
|
|
500
|
+
if (!item || typeof item !== "object") return [];
|
|
501
|
+
const ref = item.provider === "skill" && typeof item.id === "string" && item.id && typeof item.label === "string" && item.label ? { provider: "skill", id: item.id, label: item.label } : normalizePluginRefs([item])[0];
|
|
502
|
+
if (!ref) return [];
|
|
503
|
+
const key = ref.provider === "skill" ? JSON.stringify([ref.provider, ref.id]) : JSON.stringify([ref.provider, ref.plugin, ref.server, ref.id]);
|
|
504
|
+
if (seen.has(key)) return [];
|
|
505
|
+
seen.add(key);
|
|
506
|
+
return [ref];
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/schemas/topic-assets.ts
|
|
511
|
+
function normalizeTopicAssetRefs(value) {
|
|
512
|
+
if (!Array.isArray(value)) return [];
|
|
513
|
+
const seen = /* @__PURE__ */ new Set();
|
|
514
|
+
return value.flatMap((item) => {
|
|
515
|
+
if (!item || typeof item !== "object" || typeof item.topic_id !== "string" || !item.topic_id || typeof item.path !== "string" || !item.path) return [];
|
|
516
|
+
const key = JSON.stringify([item.topic_id, item.path]);
|
|
517
|
+
if (seen.has(key)) return [];
|
|
518
|
+
seen.add(key);
|
|
519
|
+
return [{ topic_id: item.topic_id, path: item.path }];
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/shared/projection/helpers.ts
|
|
524
|
+
function upsertToolCall(state, toolCallId, toolName, displayName, args) {
|
|
525
|
+
if (!toolCallId) return;
|
|
526
|
+
let found = false;
|
|
527
|
+
for (let i = 0; i < state.toolCalls.length; i++) {
|
|
528
|
+
if (state.toolCalls[i].id !== toolCallId) continue;
|
|
529
|
+
const tc = state.toolCalls[i];
|
|
530
|
+
state.toolCalls[i] = {
|
|
531
|
+
...tc,
|
|
532
|
+
tool_name: toolName || tc.tool_name,
|
|
533
|
+
display_name: displayName || tc.display_name,
|
|
534
|
+
arguments: args || tc.arguments
|
|
535
|
+
};
|
|
536
|
+
found = true;
|
|
537
|
+
break;
|
|
243
538
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
539
|
+
if (!found) {
|
|
540
|
+
state.toolCalls.push({
|
|
541
|
+
id: toolCallId,
|
|
542
|
+
tool_name: toolName,
|
|
543
|
+
display_name: displayName,
|
|
544
|
+
arguments: args,
|
|
545
|
+
status: "pending"
|
|
249
546
|
});
|
|
250
|
-
return this.runTracked(session_id, prompt, options, false);
|
|
251
547
|
}
|
|
252
|
-
|
|
253
|
-
|
|
548
|
+
let blockFound = false;
|
|
549
|
+
for (let i = 0; i < state.blocks.length; i++) {
|
|
550
|
+
const b = state.blocks[i];
|
|
551
|
+
if (b.type !== "tool_use" || b.tool_call_id !== toolCallId) continue;
|
|
552
|
+
state.blocks[i] = {
|
|
553
|
+
...b,
|
|
554
|
+
content: args || b.content,
|
|
555
|
+
tool_name: toolName || b.tool_name,
|
|
556
|
+
display_name: displayName || b.display_name
|
|
557
|
+
};
|
|
558
|
+
blockFound = true;
|
|
559
|
+
break;
|
|
254
560
|
}
|
|
255
|
-
|
|
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
|
|
561
|
+
if (!blockFound) {
|
|
562
|
+
state.blocks.push({
|
|
563
|
+
type: "tool_use",
|
|
564
|
+
content: args,
|
|
565
|
+
tool_call_id: toolCallId,
|
|
566
|
+
tool_name: toolName || null,
|
|
567
|
+
display_name: displayName || null
|
|
628
568
|
});
|
|
629
569
|
}
|
|
630
570
|
}
|
|
@@ -941,14 +881,14 @@ function applyAskUserPauseToTurn(turn, pauseToolData, toolCallId, childLoopName,
|
|
|
941
881
|
}
|
|
942
882
|
function spacedJson(value) {
|
|
943
883
|
try {
|
|
944
|
-
const
|
|
945
|
-
if (Array.isArray(item)) return `[${item.map(
|
|
884
|
+
const encode2 = (item) => {
|
|
885
|
+
if (Array.isArray(item)) return `[${item.map(encode2).join(", ")}]`;
|
|
946
886
|
if (item && typeof item === "object") {
|
|
947
|
-
return `{${Object.entries(item).map(([key, part]) => `${JSON.stringify(key)}: ${
|
|
887
|
+
return `{${Object.entries(item).map(([key, part]) => `${JSON.stringify(key)}: ${encode2(part)}`).join(", ")}}`;
|
|
948
888
|
}
|
|
949
889
|
return JSON.stringify(item) ?? "null";
|
|
950
890
|
};
|
|
951
|
-
return
|
|
891
|
+
return encode2(JSON.parse(value));
|
|
952
892
|
} catch {
|
|
953
893
|
return value;
|
|
954
894
|
}
|
|
@@ -1115,6 +1055,10 @@ function normalizeChildPause(pausePayload) {
|
|
|
1115
1055
|
}
|
|
1116
1056
|
return { pauseToolData, childLoopName, childToolCallId, description };
|
|
1117
1057
|
}
|
|
1058
|
+
function attachToolEvidence(call, evidence) {
|
|
1059
|
+
if (!call) return;
|
|
1060
|
+
call.evidence = (call.status === "done" || call.status === "error") && Array.isArray(evidence) ? evidence : void 0;
|
|
1061
|
+
}
|
|
1118
1062
|
|
|
1119
1063
|
// src/schemas/message.ts
|
|
1120
1064
|
function inferToolStatus(resultStr) {
|
|
@@ -1173,8 +1117,8 @@ function snapshot(state, sequence, status) {
|
|
|
1173
1117
|
first_token_ms: state.firstTokenMs,
|
|
1174
1118
|
stream_duration_ms: state.streamDurationMs,
|
|
1175
1119
|
first_token_delivery_ms: state.firstTokenDeliveryMs,
|
|
1176
|
-
started_at: state.modelStartedAt
|
|
1177
|
-
//
|
|
1120
|
+
started_at: state.modelStartedAt,
|
|
1121
|
+
// 未上报时不伪造服务器起点;浏览器时钟不能与服务器时钟混用。
|
|
1178
1122
|
has_reported_timing: state.modelStartedAtReported,
|
|
1179
1123
|
context_window: state.contextWindow,
|
|
1180
1124
|
memory_refs: state.memoryRefs,
|
|
@@ -1316,7 +1260,7 @@ function acknowledgeTurnEvents(acknowledge) {
|
|
|
1316
1260
|
}
|
|
1317
1261
|
function hasChatRunEvent(events) {
|
|
1318
1262
|
return events.some(
|
|
1319
|
-
(event) => event.type !== "llm:first_token:delivered" && event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
|
|
1263
|
+
(event) => event.type !== "llm:first_token:delivered" && event.type !== "session:post_chat_followup" && event.type !== "loop:tool_bridge" && !event.type.startsWith("compaction:")
|
|
1320
1264
|
);
|
|
1321
1265
|
}
|
|
1322
1266
|
function acceptedPostChatFollowupCompletesLatestRun(events, updates) {
|
|
@@ -1400,10 +1344,8 @@ var ClientProjectionBuilder = class {
|
|
|
1400
1344
|
latestTurns = /* @__PURE__ */ new Map();
|
|
1401
1345
|
turnsById = /* @__PURE__ */ new Map();
|
|
1402
1346
|
loopDescriptions = /* @__PURE__ */ new Map();
|
|
1403
|
-
/** loop_name → 正在运行的子智能体起点,agent:end 结算并挂到父工具调用上。 */
|
|
1404
1347
|
activeSubagentRuns = /* @__PURE__ */ new Map();
|
|
1405
1348
|
pendingChildPauses = /* @__PURE__ */ new Map();
|
|
1406
|
-
/** 作答后子回路续跑,最终回复就是随后落盘的父 Agent 工具结果。 */
|
|
1407
1349
|
answeredChildParents = /* @__PURE__ */ new Map();
|
|
1408
1350
|
pendingMemoryRefs = /* @__PURE__ */ new Map();
|
|
1409
1351
|
activeCompactions = /* @__PURE__ */ new Map();
|
|
@@ -1556,6 +1498,19 @@ var ClientProjectionBuilder = class {
|
|
|
1556
1498
|
this.seq = 0;
|
|
1557
1499
|
this.syntheticCounter = 0;
|
|
1558
1500
|
}
|
|
1501
|
+
/** A durable snapshot supersedes only this turn's temporary overlay. */
|
|
1502
|
+
acceptDurableTurn(turn) {
|
|
1503
|
+
this.seedSequence(turn.sequence);
|
|
1504
|
+
if (!isConversationTurn(turn)) return;
|
|
1505
|
+
const active = this.activeTurns.get(turn.loop_id);
|
|
1506
|
+
if (active?.turnId === turn.turn_id) this.activeTurns.delete(turn.loop_id);
|
|
1507
|
+
this.turnsById.set(turn.turn_id, turn);
|
|
1508
|
+
const latest = this.latestTurns.get(turn.loop_id);
|
|
1509
|
+
if ((!active || active.turnId === turn.turn_id) && (!latest || latest.turn_id === turn.turn_id || turn.sequence >= latest.sequence)) {
|
|
1510
|
+
this.latestTurns.set(turn.loop_id, turn);
|
|
1511
|
+
this.lastTurnIds.set(turn.loop_id, turn.turn_id);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1559
1514
|
seedSequence(maxSeq) {
|
|
1560
1515
|
if (maxSeq > this.seq) {
|
|
1561
1516
|
this.seq = maxSeq;
|
|
@@ -1576,7 +1531,6 @@ var ClientProjectionBuilder = class {
|
|
|
1576
1531
|
this.latestTurns = latestTurns;
|
|
1577
1532
|
this.turnsById = turnsById;
|
|
1578
1533
|
}
|
|
1579
|
-
// --- Event handlers ---
|
|
1580
1534
|
onMemoryInject(loopId, payload) {
|
|
1581
1535
|
const refs = payload.memory_refs;
|
|
1582
1536
|
const turnId = String(payload.turn_id ?? "");
|
|
@@ -1802,6 +1756,7 @@ var ClientProjectionBuilder = class {
|
|
|
1802
1756
|
onUserMessage(_loopId, payload) {
|
|
1803
1757
|
const entryId = String(payload.entry_id ?? this.nextTurnId("user"));
|
|
1804
1758
|
const displayContent = payload.display_content ?? payload.content ?? "";
|
|
1759
|
+
const refs = normalizeMessageRefs(payload.refs);
|
|
1805
1760
|
const turn = {
|
|
1806
1761
|
id: entryId,
|
|
1807
1762
|
sequence: this.nextSeq(),
|
|
@@ -1811,6 +1766,9 @@ var ClientProjectionBuilder = class {
|
|
|
1811
1766
|
role: "user",
|
|
1812
1767
|
status: "completed",
|
|
1813
1768
|
blocks: [{ type: "text", content: displayContent }],
|
|
1769
|
+
...refs.length ? { refs } : {},
|
|
1770
|
+
context_attachments: normalizeContextAttachments(payload.context_attachments),
|
|
1771
|
+
topic_asset_refs: normalizeTopicAssetRefs(payload.topic_asset_refs),
|
|
1814
1772
|
tool_calls: [],
|
|
1815
1773
|
model: null,
|
|
1816
1774
|
usage: null,
|
|
@@ -1838,7 +1796,6 @@ var ClientProjectionBuilder = class {
|
|
|
1838
1796
|
this.latestTurns.set("root", turn);
|
|
1839
1797
|
return [{ kind: "upsert", turn }];
|
|
1840
1798
|
}
|
|
1841
|
-
// --- Delta / streaming events ---
|
|
1842
1799
|
onDelta(state, blockType, _toolCallId, content) {
|
|
1843
1800
|
if (!content) return null;
|
|
1844
1801
|
appendTextBlock(state, blockType, content);
|
|
@@ -1890,10 +1847,7 @@ var ClientProjectionBuilder = class {
|
|
|
1890
1847
|
if (!applyResponseBlockOrder(state, payload.response_blocks) && "content" in payload) {
|
|
1891
1848
|
replaceTextContent(state, completedResponseText(payload.content));
|
|
1892
1849
|
}
|
|
1893
|
-
|
|
1894
|
-
if (state.toolCalls.length === 0 || isNativeProviderResponse && state.toolCalls.every(
|
|
1895
|
-
(toolCall) => toolCall.status !== "pending" && toolCall.status !== "awaiting_answer"
|
|
1896
|
-
)) {
|
|
1850
|
+
if (state.toolCalls.length === 0) {
|
|
1897
1851
|
const childParent = this.answeredChildParents.get(loopId);
|
|
1898
1852
|
if (childParent) {
|
|
1899
1853
|
this.answeredChildParents.delete(loopId);
|
|
@@ -1910,6 +1864,7 @@ var ClientProjectionBuilder = class {
|
|
|
1910
1864
|
return this.syncPatch(state);
|
|
1911
1865
|
}
|
|
1912
1866
|
applyFirstTokenDelivery(state, payload) {
|
|
1867
|
+
this.applyStreamTiming(state, payload);
|
|
1913
1868
|
const firstTokenMs = payload.first_token_ms;
|
|
1914
1869
|
if (state.firstTokenMs === null && typeof firstTokenMs === "number" && firstTokenMs >= 0) {
|
|
1915
1870
|
state.firstTokenMs = Math.round(firstTokenMs);
|
|
@@ -1938,13 +1893,14 @@ var ClientProjectionBuilder = class {
|
|
|
1938
1893
|
const toolCallId = String(payload.tool_call_id ?? "");
|
|
1939
1894
|
const streamedResult = state.toolCalls.find((toolCall) => toolCall.id === toolCallId)?.result;
|
|
1940
1895
|
const content = payload.content_from_deltas === true ? streamedResult ?? "" : payload.content ?? "";
|
|
1941
|
-
const status = payload.is_error === true ? "error" : toolStatusFromResult(content);
|
|
1896
|
+
const status = payload.is_error === true ? "error" : payload._tool_pending === true ? "pending" : toolStatusFromResult(content);
|
|
1942
1897
|
const rawDuration = payload.duration_ms;
|
|
1943
1898
|
const durationMs = typeof rawDuration === "number" && rawDuration >= 0 ? Math.round(rawDuration) : null;
|
|
1944
1899
|
const ranInParallel = typeof payload.ran_in_parallel === "boolean" ? payload.ran_in_parallel : null;
|
|
1945
1900
|
const sl = sourceLoopFor(state.loopId, this.loopDescriptions);
|
|
1946
1901
|
const startedAt = typeof payload.started_at === "string" && payload.started_at ? payload.started_at : null;
|
|
1947
1902
|
applyToolResult(state, toolCallId, content, status, durationMs, ranInParallel, sl, startedAt);
|
|
1903
|
+
attachToolEvidence(state.toolCalls.find((call) => call.id === toolCallId), payload.evidence);
|
|
1948
1904
|
const pendingChild = [...this.pendingChildPauses.values()].find(
|
|
1949
1905
|
(pause) => pause.parentLoopName === loopId && pause.parentToolCallId === toolCallId
|
|
1950
1906
|
);
|
|
@@ -1958,8 +1914,7 @@ var ClientProjectionBuilder = class {
|
|
|
1958
1914
|
this.displayNameResolver
|
|
1959
1915
|
);
|
|
1960
1916
|
}
|
|
1961
|
-
|
|
1962
|
-
if (!isNativeProviderResult && this.shouldFinalizeAfterToolResults(state)) {
|
|
1917
|
+
if (this.shouldFinalizeAfterToolResults(state)) {
|
|
1963
1918
|
return this.finalize(loopId, "completed");
|
|
1964
1919
|
}
|
|
1965
1920
|
return this.syncPatch(state);
|
|
@@ -2218,7 +2173,6 @@ var ClientProjectionBuilder = class {
|
|
|
2218
2173
|
const turn = buildCompactionTurn(this.nextSeq(), compactionId, loopId, status, current);
|
|
2219
2174
|
return [{ kind: "upsert", turn }];
|
|
2220
2175
|
}
|
|
2221
|
-
// --- Internal helpers ---
|
|
2222
2176
|
syncPatch(state) {
|
|
2223
2177
|
const turn = snapshot(state, this.nextSeq(), "streaming");
|
|
2224
2178
|
this.latestTurns.set(state.loopId, turn);
|
|
@@ -2398,7 +2352,6 @@ function projectHistory(entries) {
|
|
|
2398
2352
|
const pendingChildPauses = /* @__PURE__ */ new Map();
|
|
2399
2353
|
const childPauseEntries = [];
|
|
2400
2354
|
const answeredToolCallIds = /* @__PURE__ */ new Set();
|
|
2401
|
-
const nativeToolTurns = /* @__PURE__ */ new Set();
|
|
2402
2355
|
const turnEntryAliases = /* @__PURE__ */ new Map();
|
|
2403
2356
|
let latestRootMessage = null;
|
|
2404
2357
|
let latestRootAssistant = null;
|
|
@@ -2464,19 +2417,18 @@ function projectHistory(entries) {
|
|
|
2464
2417
|
const owner = toolOwners.get(toolCallId);
|
|
2465
2418
|
const toolCall = owner?.tool_calls.find((item) => item.id === toolCallId);
|
|
2466
2419
|
if (owner && toolCall) {
|
|
2467
|
-
toolCall.status = message._error ? "error" : inferToolStatus(String(message.content ?? ""));
|
|
2420
|
+
toolCall.status = message._error ? "error" : message._tool_pending === true ? "pending" : inferToolStatus(String(message.content ?? ""));
|
|
2468
2421
|
toolCall.result = message.content ?? "";
|
|
2422
|
+
attachToolEvidence(toolCall, message._evidence);
|
|
2469
2423
|
const duration = numberOrNull(message._duration_ms);
|
|
2470
2424
|
if (duration !== null) toolCall.duration_ms = duration;
|
|
2471
2425
|
if (typeof message._started_at === "string") {
|
|
2472
2426
|
toolCall.started_at = message._started_at;
|
|
2473
2427
|
}
|
|
2474
2428
|
if (message._ran_in_parallel === true) toolCall.ran_in_parallel = true;
|
|
2475
|
-
owner.blocks.
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
tool_call_id: toolCallId
|
|
2479
|
-
});
|
|
2429
|
+
const existing = owner.blocks.find((block) => block.type === "tool_result" && block.tool_call_id === toolCallId);
|
|
2430
|
+
if (existing) existing.content = message.content ?? "";
|
|
2431
|
+
else owner.blocks.push({ type: "tool_result", content: message.content ?? "", tool_call_id: toolCallId });
|
|
2480
2432
|
}
|
|
2481
2433
|
continue;
|
|
2482
2434
|
}
|
|
@@ -2493,56 +2445,29 @@ function projectHistory(entries) {
|
|
|
2493
2445
|
loop_id: loopId,
|
|
2494
2446
|
kind: "message",
|
|
2495
2447
|
role,
|
|
2448
|
+
client_request_id: stringOrNull(entry.client_request_id),
|
|
2496
2449
|
status: message._error ? "failed" : message._interrupted ? "interrupted" : "completed",
|
|
2497
2450
|
blocks,
|
|
2498
2451
|
tool_calls: toolCalls,
|
|
2499
2452
|
model: stringOrNull(message.model),
|
|
2500
2453
|
usage: isRecord(message._usage) ? { ...message._usage } : null,
|
|
2501
|
-
// 模型请求区间来自持久化字段;旧会话缺失时为 null,不用落盘时刻冒充(#2581 A1)。
|
|
2502
2454
|
duration_ms: numberOrNull(message._duration_ms),
|
|
2503
2455
|
first_token_ms: numberOrNull(message._first_token_ms),
|
|
2504
2456
|
stream_duration_ms: numberOrNull(message._stream_duration_ms),
|
|
2505
2457
|
started_at: typeof message._started_at === "string" ? message._started_at : typeof entry.timestamp === "string" ? entry.timestamp : void 0,
|
|
2506
2458
|
has_reported_timing: typeof message._started_at === "string",
|
|
2507
|
-
// fork 子会话的父锚点:历史里落在消息级 _parent_fork_tool_call_id。丢掉它,
|
|
2508
|
-
// 刷新后工作流步骤 fork 就配不回 workflow:run 工具调用(普通 Agent fork 有
|
|
2509
|
-
// root Agent 工具调用兜底配对,工作流 fork 没有,子智能体卡会整体消失)。
|
|
2510
2459
|
parent_fork_tool_call_id: stringOrNull(message._parent_fork_tool_call_id)
|
|
2511
2460
|
};
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
if (role === "assistant" && previousTurn?.role === "assistant" && previousTurn.loop_id === loopId && nativeToolTurns.has(previousTurn)) {
|
|
2517
|
-
nativeToolTurns.delete(previousTurn);
|
|
2518
|
-
previousTurn.blocks.push(...turn.blocks);
|
|
2519
|
-
previousTurn.tool_calls.push(...turn.tool_calls);
|
|
2520
|
-
previousTurn.status = turn.status;
|
|
2521
|
-
previousTurn.model = turn.model ?? previousTurn.model;
|
|
2522
|
-
previousTurn.usage = turn.usage ?? previousTurn.usage;
|
|
2523
|
-
if (turn.duration_ms) previousTurn.duration_ms = turn.duration_ms;
|
|
2524
|
-
previousTurn.first_token_ms = turn.first_token_ms ?? previousTurn.first_token_ms;
|
|
2525
|
-
previousTurn.stream_duration_ms = turn.stream_duration_ms ?? previousTurn.stream_duration_ms;
|
|
2526
|
-
if (turn.has_reported_timing) {
|
|
2527
|
-
previousTurn.started_at = turn.started_at;
|
|
2528
|
-
previousTurn.has_reported_timing = true;
|
|
2529
|
-
} else if (!previousTurn.has_reported_timing) {
|
|
2530
|
-
previousTurn.started_at = previousTurn.started_at ?? turn.started_at;
|
|
2531
|
-
}
|
|
2532
|
-
previousTurn.id = turn.id;
|
|
2533
|
-
previousTurn.turn_id = turn.turn_id;
|
|
2534
|
-
if (isNativeToolEntry) nativeToolTurns.add(previousTurn);
|
|
2535
|
-
turnEntryAliases.set(id, previousTurn);
|
|
2536
|
-
if (loopId === "root") latestRootMessage = previousTurn;
|
|
2537
|
-
latestAssistantByLoop.set(loopId, previousTurn);
|
|
2538
|
-
continue;
|
|
2461
|
+
if (role === "user") {
|
|
2462
|
+
const refs = normalizeMessageRefs(message.refs);
|
|
2463
|
+
if (refs.length) turn.refs = refs;
|
|
2464
|
+
turn.context_attachments = normalizeContextAttachments(message.context_attachments);
|
|
2539
2465
|
}
|
|
2540
2466
|
if (loopId === "root" && (role === "user" || role === "assistant")) {
|
|
2541
2467
|
latestRootMessage = turn;
|
|
2542
2468
|
}
|
|
2543
2469
|
turns.push(turn);
|
|
2544
2470
|
turnEntryAliases.set(id, turn);
|
|
2545
|
-
if (isNativeToolEntry) nativeToolTurns.add(turn);
|
|
2546
2471
|
if (role === "assistant") latestAssistantByLoop.set(loopId, turn);
|
|
2547
2472
|
if (loopId === "root") {
|
|
2548
2473
|
if (role === "assistant") latestRootAssistant = turn;
|
|
@@ -2558,7 +2483,7 @@ function projectHistory(entries) {
|
|
|
2558
2483
|
if (owner && toolCallId) {
|
|
2559
2484
|
const rawContent = data.content;
|
|
2560
2485
|
const content = typeof rawContent === "string" ? rawContent : Array.isArray(rawContent) ? rawContent : String(rawContent ?? "");
|
|
2561
|
-
const status = data.is_error === true ? "error" : inferToolStatus(typeof content === "string" ? content : JSON.stringify(content));
|
|
2486
|
+
const status = data.is_error === true ? "error" : data._tool_pending === true ? "pending" : inferToolStatus(typeof content === "string" ? content : JSON.stringify(content));
|
|
2562
2487
|
setToolCallStatus(owner, toolCallId, status, content, null);
|
|
2563
2488
|
if (!owner.blocks.some((block) => block.type === "tool_result" && block.tool_call_id === toolCallId)) {
|
|
2564
2489
|
owner.blocks.push({ type: "tool_result", content, tool_call_id: toolCallId });
|
|
@@ -2566,6 +2491,33 @@ function projectHistory(entries) {
|
|
|
2566
2491
|
}
|
|
2567
2492
|
continue;
|
|
2568
2493
|
}
|
|
2494
|
+
if (kind === "workflow_step") {
|
|
2495
|
+
const data = entry.data ?? {};
|
|
2496
|
+
const toolCallId = String(data.tool_call_id ?? "");
|
|
2497
|
+
const owner = toolOwners.get(toolCallId) ?? findLatestAssistant(turns, loopId);
|
|
2498
|
+
if (owner && toolCallId) {
|
|
2499
|
+
const index = owner.tool_calls.findIndex((call) => call.id === toolCallId);
|
|
2500
|
+
if (index >= 0) {
|
|
2501
|
+
const call = owner.tool_calls[index];
|
|
2502
|
+
owner.tool_calls[index] = {
|
|
2503
|
+
...call,
|
|
2504
|
+
workflow_steps: [
|
|
2505
|
+
...call.workflow_steps ?? [],
|
|
2506
|
+
{
|
|
2507
|
+
step_id: String(data.step_id ?? ""),
|
|
2508
|
+
desc: String(data.desc ?? ""),
|
|
2509
|
+
step_kind: String(data.step_kind ?? "step"),
|
|
2510
|
+
status: String(data.status ?? "done"),
|
|
2511
|
+
output: String(data.output ?? ""),
|
|
2512
|
+
...typeof data.seq === "number" ? { seq: data.seq } : {},
|
|
2513
|
+
...typeof data.log_seq === "number" ? { log_seq: data.log_seq } : {}
|
|
2514
|
+
}
|
|
2515
|
+
]
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
continue;
|
|
2520
|
+
}
|
|
2569
2521
|
if (kind === "mode_change") {
|
|
2570
2522
|
turns.push(markerTurn(id, ++sequence, loopId, "mode_change", entry.data ?? {}));
|
|
2571
2523
|
} else if (kind === "workspace_change") {
|
|
@@ -2616,7 +2568,7 @@ function projectHistory(entries) {
|
|
|
2616
2568
|
let content = isRecord(entry.message) ? entry.message.content : void 0;
|
|
2617
2569
|
if (kind === "tool_result_archive") {
|
|
2618
2570
|
const archivedCount = Array.isArray(data.archived_files) ? data.archived_files.length : 0;
|
|
2619
|
-
content = `<compaction-summary>
|
|
2571
|
+
content = typeof data.summary === "string" && data.summary ? data.summary : `<compaction-summary>
|
|
2620
2572
|
\u5DF2\u5F52\u6863 ${archivedCount} \u4E2A\u5927\u578B\u5DE5\u5177\u7ED3\u679C\u3002
|
|
2621
2573
|
</compaction-summary>`;
|
|
2622
2574
|
}
|
|
@@ -2728,117 +2680,752 @@ function projectHistory(entries) {
|
|
|
2728
2680
|
pauseArguments.questions = resultArguments.questions;
|
|
2729
2681
|
}
|
|
2730
2682
|
}
|
|
2731
|
-
applyAskUserPauseToTurn(
|
|
2732
|
-
rootAssistant,
|
|
2683
|
+
applyAskUserPauseToTurn(
|
|
2684
|
+
rootAssistant,
|
|
2685
|
+
{
|
|
2686
|
+
name: toolCall.tool_name,
|
|
2687
|
+
tool_call_id: toolCall.id,
|
|
2688
|
+
arguments: pauseArguments
|
|
2689
|
+
},
|
|
2690
|
+
toolCall.id,
|
|
2691
|
+
"",
|
|
2692
|
+
"",
|
|
2693
|
+
(name) => name
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
return turns;
|
|
2698
|
+
}
|
|
2699
|
+
function parseToolArguments(value) {
|
|
2700
|
+
if (!value) return {};
|
|
2701
|
+
try {
|
|
2702
|
+
const parsed = JSON.parse(value);
|
|
2703
|
+
return isRecord(parsed) ? parsed : {};
|
|
2704
|
+
} catch {
|
|
2705
|
+
return {};
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
function buildToolCalls(value) {
|
|
2709
|
+
if (!Array.isArray(value)) return [];
|
|
2710
|
+
return value.filter(isRecord).map((raw) => {
|
|
2711
|
+
const fn = isRecord(raw.function) ? raw.function : {};
|
|
2712
|
+
const name = String(fn.name ?? "");
|
|
2713
|
+
return {
|
|
2714
|
+
id: String(raw.id ?? ""),
|
|
2715
|
+
tool_name: name,
|
|
2716
|
+
display_name: String(raw.display_name ?? name),
|
|
2717
|
+
arguments: typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments ?? {}),
|
|
2718
|
+
status: "running",
|
|
2719
|
+
result: null
|
|
2720
|
+
};
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
function buildBlocks(message, toolCalls) {
|
|
2724
|
+
const displayContent = message.role === "user" ? message._display_content : void 0;
|
|
2725
|
+
const stored = message._blocks;
|
|
2726
|
+
const blocks = [];
|
|
2727
|
+
const storedToolCallIds = /* @__PURE__ */ new Set();
|
|
2728
|
+
if (displayContent !== void 0) {
|
|
2729
|
+
blocks.push({ type: "text", content: displayContent });
|
|
2730
|
+
} else if (Array.isArray(stored)) {
|
|
2731
|
+
for (const raw of stored) {
|
|
2732
|
+
if (!isRecord(raw)) continue;
|
|
2733
|
+
const type2 = String(raw.type ?? "");
|
|
2734
|
+
if (type2 === "thinking") blocks.push({ type: type2, content: raw.thinking ?? raw.content ?? "" });
|
|
2735
|
+
if (type2 === "text") blocks.push({ type: type2, content: raw.text ?? raw.content ?? "" });
|
|
2736
|
+
if (type2 === "tool_call") {
|
|
2737
|
+
const toolCallId = String(raw.id ?? "");
|
|
2738
|
+
const toolCall = toolCalls.find((candidate) => candidate.id === toolCallId);
|
|
2739
|
+
if (!toolCall) continue;
|
|
2740
|
+
storedToolCallIds.add(toolCallId);
|
|
2741
|
+
blocks.push({
|
|
2742
|
+
type: "tool_use",
|
|
2743
|
+
content: toolCall.arguments,
|
|
2744
|
+
tool_call_id: toolCall.id,
|
|
2745
|
+
tool_name: toolCall.tool_name,
|
|
2746
|
+
display_name: toolCall.display_name
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
} else if (message.content !== void 0 && message.content !== "") {
|
|
2751
|
+
blocks.push({ type: "text", content: message.content });
|
|
2752
|
+
}
|
|
2753
|
+
for (const toolCall of toolCalls) {
|
|
2754
|
+
if (storedToolCallIds.has(toolCall.id)) continue;
|
|
2755
|
+
blocks.push({
|
|
2756
|
+
type: "tool_use",
|
|
2757
|
+
content: toolCall.arguments,
|
|
2758
|
+
tool_call_id: toolCall.id,
|
|
2759
|
+
tool_name: toolCall.tool_name,
|
|
2760
|
+
display_name: toolCall.display_name
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
return blocks;
|
|
2764
|
+
}
|
|
2765
|
+
function markerTurn(id, sequence, loopId, type2, content, toolCallId = null) {
|
|
2766
|
+
return {
|
|
2767
|
+
id,
|
|
2768
|
+
sequence,
|
|
2769
|
+
turn_id: id,
|
|
2770
|
+
loop_id: loopId,
|
|
2771
|
+
kind: type2,
|
|
2772
|
+
role: "system",
|
|
2773
|
+
status: "completed",
|
|
2774
|
+
blocks: [{ type: type2, content, tool_call_id: toolCallId }],
|
|
2775
|
+
tool_calls: [],
|
|
2776
|
+
model: null,
|
|
2777
|
+
usage: null,
|
|
2778
|
+
duration_ms: 0
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
function stringOrNull(value) {
|
|
2782
|
+
return typeof value === "string" ? value : null;
|
|
2783
|
+
}
|
|
2784
|
+
function numberOrNull(value) {
|
|
2785
|
+
return typeof value === "number" && value >= 0 ? value : null;
|
|
2786
|
+
}
|
|
2787
|
+
function findLatestAssistant(turns, loopId) {
|
|
2788
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
2789
|
+
const turn = turns[index];
|
|
2790
|
+
if (turn?.role === "assistant" && turn.loop_id === loopId) return turn;
|
|
2791
|
+
}
|
|
2792
|
+
return void 0;
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
// src/shared/projection/workflow.ts
|
|
2796
|
+
var STEP_KINDS = /* @__PURE__ */ new Set(["agent", "step", "parallel", "choice", "end"]);
|
|
2797
|
+
function workflowEntriesFromSteps(steps) {
|
|
2798
|
+
const items = [];
|
|
2799
|
+
const ordered = steps.every((step) => typeof step.seq === "number");
|
|
2800
|
+
steps.forEach((step, index) => {
|
|
2801
|
+
const base = ordered ? step.seq : index * 2;
|
|
2802
|
+
const stepKind = step.step_kind && STEP_KINDS.has(step.step_kind) ? step.step_kind : "step";
|
|
2803
|
+
items.push({
|
|
2804
|
+
key: base,
|
|
2805
|
+
entry: { kind: "step", stepId: step.step_id, desc: step.desc ?? "", stepKind, status: step.status }
|
|
2806
|
+
});
|
|
2807
|
+
const logKey = ordered && typeof step.log_seq === "number" ? step.log_seq : base + 0.5;
|
|
2808
|
+
const output = step.output ?? "";
|
|
2809
|
+
if (step.status === "done" && stepKind === "step") {
|
|
2810
|
+
const chars = Array.from(output);
|
|
2811
|
+
const shown = chars.length <= 300 ? output : `${chars.slice(0, 300).join("")}\u2026(\u5171${chars.length}\u5B57)`;
|
|
2812
|
+
items.push({ key: logKey, entry: { kind: "log", text: ` \u2514 \u8F93\u51FA: ${shown || "(\u7A7A)"}`, stepId: step.step_id } });
|
|
2813
|
+
}
|
|
2814
|
+
if (step.status === "error" && output) {
|
|
2815
|
+
items.push({ key: logKey, entry: { kind: "log", text: `\u5931\u8D25: ${output}`, stepId: step.step_id } });
|
|
2816
|
+
}
|
|
2817
|
+
});
|
|
2818
|
+
return items.sort((a, b) => a.key - b.key).map((item) => item.entry);
|
|
2819
|
+
}
|
|
2820
|
+
function workflowRunCancelled(steps) {
|
|
2821
|
+
return steps.some((step) => step.status === "cancelled") && !steps.some((step) => step.status === "error");
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
// src/resources/headless.ts
|
|
2825
|
+
var HeadlessError = class extends Error {
|
|
2826
|
+
constructor(message, detail) {
|
|
2827
|
+
super(message);
|
|
2828
|
+
this.detail = detail;
|
|
2829
|
+
this.name = "HeadlessError";
|
|
2830
|
+
}
|
|
2831
|
+
detail;
|
|
2832
|
+
};
|
|
2833
|
+
var SYSTEM_ERROR_CHAT_END_GRACE_MS = 1e3;
|
|
2834
|
+
var CLEANUP_ACK_TIMEOUT_MS = 35e3;
|
|
2835
|
+
var HeadlessResource = class {
|
|
2836
|
+
constructor(client) {
|
|
2837
|
+
this.client = client;
|
|
2838
|
+
}
|
|
2839
|
+
client;
|
|
2840
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
2841
|
+
async run(prompt, options = {}) {
|
|
2842
|
+
const { session_id } = await this.client.sessions.createSessionWithRequest({
|
|
2843
|
+
intent: prompt,
|
|
2844
|
+
model: options.model ?? null
|
|
2845
|
+
});
|
|
2846
|
+
return this.runTracked(session_id, prompt, options, false);
|
|
2847
|
+
}
|
|
2848
|
+
async runInSession(sessionId, prompt, options = {}) {
|
|
2849
|
+
return this.runTracked(sessionId, prompt, options, false);
|
|
2850
|
+
}
|
|
2851
|
+
async runWithSession(prompt, options = {}) {
|
|
2852
|
+
const { session_id } = await this.client.sessions.createSessionWithRequest({
|
|
2853
|
+
intent: prompt,
|
|
2854
|
+
model: options.model ?? null
|
|
2855
|
+
});
|
|
2856
|
+
return this.runTracked(session_id, prompt, options, true);
|
|
2857
|
+
}
|
|
2858
|
+
async runTracked(sessionId, prompt, options, returnEnvelope) {
|
|
2859
|
+
if ((options.refs?.length ?? 0) > 20) throw new HeadlessError("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u9009\u62E9 20 \u4E2A\u5F15\u7528");
|
|
2860
|
+
if ((options.topicAssetRefs?.length ?? 0) > 20) throw new HeadlessError("\u6BCF\u6761\u6D88\u606F\u6700\u591A\u5F15\u7528 20 \u4E2A\u4E3B\u9898\u8D44\u4EA7");
|
|
2861
|
+
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
2862
|
+
const session_id = sessionId;
|
|
2863
|
+
const socket = this.client.socket();
|
|
2864
|
+
await ensureSocketConnected(socket, timeoutMs, { session_id });
|
|
2865
|
+
const cursor = new LogCursor();
|
|
2866
|
+
const subscriptions = this.subscriptions.get(session_id) ?? /* @__PURE__ */ new Set();
|
|
2867
|
+
subscriptions.add(cursor);
|
|
2868
|
+
this.subscriptions.set(session_id, subscriptions);
|
|
2869
|
+
return new Promise((resolve, reject) => {
|
|
2870
|
+
let settled = false;
|
|
2871
|
+
let cleanupPromise;
|
|
2872
|
+
let stopAttempt;
|
|
2873
|
+
let handleLateAck;
|
|
2874
|
+
let acknowledgeSend = () => void 0;
|
|
2875
|
+
const sendAck = new Promise((resolveAck) => {
|
|
2876
|
+
acknowledgeSend = resolveAck;
|
|
2877
|
+
});
|
|
2878
|
+
let started = false;
|
|
2879
|
+
let chatRunId = null;
|
|
2880
|
+
let systemErrorTimeout = null;
|
|
2881
|
+
const beforeAck = /* @__PURE__ */ new Map();
|
|
2882
|
+
const timeout = setTimeout(() => {
|
|
2883
|
+
fail(new HeadlessError(`headless run timed out after ${timeoutMs}ms`, { session_id, chat_run_id: chatRunId }));
|
|
2884
|
+
}, timeoutMs);
|
|
2885
|
+
const stopOwnedRun = (runId, deadline) => {
|
|
2886
|
+
stopAttempt ??= (async () => {
|
|
2887
|
+
const stopped = await waitForCleanupAck(socket, deadline, (ack2) => {
|
|
2888
|
+
socket.emit("chat:stop", { session_id, chat_run_id: runId }, ack2);
|
|
2889
|
+
});
|
|
2890
|
+
if (stopped.status !== "received") return { ...stopped, phase: "stop_ack", chat_run_id: runId };
|
|
2891
|
+
const ack = stopped.ack;
|
|
2892
|
+
const confirmed = isRecord(ack) && ack.ok !== false && ack.chat_run_id === runId && typeof ack.status === "string" && ["stopping", "stopped", "idle", "done", "interrupted"].includes(ack.status);
|
|
2893
|
+
return {
|
|
2894
|
+
status: confirmed ? "confirmed" : "unconfirmed",
|
|
2895
|
+
phase: "stop_ack",
|
|
2896
|
+
chat_run_id: runId,
|
|
2897
|
+
...confirmed ? {} : { reason: "invalid_stop_ack" },
|
|
2898
|
+
ack
|
|
2899
|
+
};
|
|
2900
|
+
})().finally(releaseSubscription).catch((error) => ({
|
|
2901
|
+
status: "unconfirmed",
|
|
2902
|
+
phase: "stop_ack",
|
|
2903
|
+
chat_run_id: runId,
|
|
2904
|
+
reason: "cleanup_failed",
|
|
2905
|
+
error
|
|
2906
|
+
}));
|
|
2907
|
+
return stopAttempt;
|
|
2908
|
+
};
|
|
2909
|
+
const cancelRun = async (deadline) => {
|
|
2910
|
+
if (chatRunId === null) {
|
|
2911
|
+
const sent = await waitForCleanupAck(socket, deadline, (ack) => {
|
|
2912
|
+
void sendAck.then(ack);
|
|
2913
|
+
});
|
|
2914
|
+
if (sent.status !== "received") return { ...sent, phase: "send_ack", chat_run_id: null };
|
|
2915
|
+
const runId = acceptedRunId(sent.ack);
|
|
2916
|
+
if (runId === null) {
|
|
2917
|
+
return { status: "unconfirmed", phase: "send_ack", chat_run_id: null, reason: "no_accepted_run", ack: sent.ack };
|
|
2918
|
+
}
|
|
2919
|
+
chatRunId = runId;
|
|
2920
|
+
}
|
|
2921
|
+
const attempt = stopOwnedRun(chatRunId, deadline);
|
|
2922
|
+
const stopped = await waitForCleanupAck(socket, deadline, (ack) => {
|
|
2923
|
+
void attempt.then(ack);
|
|
2924
|
+
});
|
|
2925
|
+
return stopped.status === "received" ? stopped.ack : { ...stopped, phase: "stop_ack", chat_run_id: chatRunId };
|
|
2926
|
+
};
|
|
2927
|
+
const releaseSubscription = () => {
|
|
2928
|
+
const current = this.subscriptions.get(session_id);
|
|
2929
|
+
current?.delete(cursor);
|
|
2930
|
+
if (current?.size === 0) {
|
|
2931
|
+
this.subscriptions.delete(session_id);
|
|
2932
|
+
socket.emit("session:unsubscribe", { session_id });
|
|
2933
|
+
}
|
|
2934
|
+
};
|
|
2935
|
+
const reportDelivery = (error, delivery) => {
|
|
2936
|
+
let detailUpdated = true;
|
|
2937
|
+
try {
|
|
2938
|
+
if (error instanceof HeadlessError && isRecord(error.detail)) error.detail.stop_delivery = delivery;
|
|
2939
|
+
} catch {
|
|
2940
|
+
detailUpdated = false;
|
|
2941
|
+
}
|
|
2942
|
+
if (delivery.status !== "confirmed" || !detailUpdated) {
|
|
2943
|
+
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 });
|
|
2944
|
+
}
|
|
2945
|
+
};
|
|
2946
|
+
const cleanup = (error) => {
|
|
2947
|
+
cleanupPromise ??= (async () => {
|
|
2948
|
+
clearTimeout(timeout);
|
|
2949
|
+
if (systemErrorTimeout) clearTimeout(systemErrorTimeout);
|
|
2950
|
+
systemErrorTimeout = null;
|
|
2951
|
+
socket.off("chat:start", onChatStart);
|
|
2952
|
+
socket.off("chat:end", onChatEnd);
|
|
2953
|
+
socket.off("system:error", onSystemError);
|
|
2954
|
+
socket.off("turn:events", onTurnEvents);
|
|
2955
|
+
socket.off("connect", subscribe);
|
|
2956
|
+
beforeAck.clear();
|
|
2957
|
+
if (error !== void 0) {
|
|
2958
|
+
const deadline = Date.now() + CLEANUP_ACK_TIMEOUT_MS;
|
|
2959
|
+
handleLateAck = (ack) => {
|
|
2960
|
+
const runId = acceptedRunId(ack);
|
|
2961
|
+
if (runId === null || !socket.connected || stopAttempt) return;
|
|
2962
|
+
const late = Date.now() >= deadline;
|
|
2963
|
+
const current = this.subscriptions.get(session_id) ?? /* @__PURE__ */ new Set();
|
|
2964
|
+
current.add(cursor);
|
|
2965
|
+
this.subscriptions.set(session_id, current);
|
|
2966
|
+
void stopOwnedRun(runId, late ? Date.now() + CLEANUP_ACK_TIMEOUT_MS : deadline).then((delivery) => {
|
|
2967
|
+
if (late) reportDelivery(error, { ...delivery, late: true });
|
|
2968
|
+
}).catch((error2) => {
|
|
2969
|
+
console.warn("[agent-client] late headless stop cleanup failed", { session_id, chat_run_id: runId, error: error2 });
|
|
2970
|
+
});
|
|
2971
|
+
};
|
|
2972
|
+
reportDelivery(error, await cancelRun(deadline));
|
|
2973
|
+
}
|
|
2974
|
+
if (!stopAttempt) releaseSubscription();
|
|
2975
|
+
})();
|
|
2976
|
+
return cleanupPromise;
|
|
2977
|
+
};
|
|
2978
|
+
const finish = (value) => {
|
|
2979
|
+
if (settled) return;
|
|
2980
|
+
settled = true;
|
|
2981
|
+
void cleanup().then(() => resolve(returnEnvelope ? { session_id, chat_run_id: chatRunId, result: value } : value), reject);
|
|
2982
|
+
};
|
|
2983
|
+
const rejectRun = (error, sendStop = false) => {
|
|
2984
|
+
if (settled) return;
|
|
2985
|
+
settled = true;
|
|
2986
|
+
void cleanup(sendStop ? error : void 0).then(() => reject(error), () => reject(error));
|
|
2987
|
+
};
|
|
2988
|
+
const fail = (error) => rejectRun(error, true);
|
|
2989
|
+
const accept = (event) => {
|
|
2990
|
+
if (settled) return;
|
|
2991
|
+
const payload = event.payload;
|
|
2992
|
+
if (event.type === "chat:start") {
|
|
2993
|
+
started = true;
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
if (event.type === "system:error") {
|
|
2997
|
+
const error = new HeadlessError(String(payload.message || "headless run failed"), {
|
|
2998
|
+
session_id,
|
|
2999
|
+
chat_run_id: chatRunId,
|
|
3000
|
+
event: payload
|
|
3001
|
+
});
|
|
3002
|
+
if (!started) {
|
|
3003
|
+
rejectRun(error);
|
|
3004
|
+
} else {
|
|
3005
|
+
if (systemErrorTimeout) clearTimeout(systemErrorTimeout);
|
|
3006
|
+
systemErrorTimeout = setTimeout(() => fail(error), SYSTEM_ERROR_CHAT_END_GRACE_MS);
|
|
3007
|
+
}
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
3010
|
+
if (event.type !== "chat:end") return;
|
|
3011
|
+
if (payload.status !== "completed") {
|
|
3012
|
+
rejectRun(new HeadlessError(errorMessageFromResult(payload.result) ?? `headless run ${String(payload.status || "failed")}`, {
|
|
3013
|
+
session_id,
|
|
3014
|
+
chat_run_id: chatRunId,
|
|
3015
|
+
event: payload
|
|
3016
|
+
}));
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
try {
|
|
3020
|
+
finish(normalizeResult(payload.result, Boolean(options.schema)));
|
|
3021
|
+
} catch (error) {
|
|
3022
|
+
rejectRun(error);
|
|
3023
|
+
}
|
|
3024
|
+
};
|
|
3025
|
+
const observe = (type2, payload) => {
|
|
3026
|
+
if (settled || payload.session_id && payload.session_id !== session_id) return;
|
|
3027
|
+
const runId = payload.chat_run_id;
|
|
3028
|
+
const event = { type: type2, payload };
|
|
3029
|
+
if (typeof runId !== "string" || !runId) {
|
|
3030
|
+
if (!runId && chatRunId === null && type2 === "system:error" && payload.session_id === session_id && !beforeAck.has("")) {
|
|
3031
|
+
beforeAck.set("", [event]);
|
|
3032
|
+
}
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
if (chatRunId === null) {
|
|
3036
|
+
const pending = beforeAck.get(runId) ?? [];
|
|
3037
|
+
pending.push(event);
|
|
3038
|
+
beforeAck.set(runId, pending);
|
|
3039
|
+
} else if (runId === chatRunId) {
|
|
3040
|
+
accept(event);
|
|
3041
|
+
}
|
|
3042
|
+
};
|
|
3043
|
+
function onChatStart(payload) {
|
|
3044
|
+
observe("chat:start", payload);
|
|
3045
|
+
}
|
|
3046
|
+
function onChatEnd(payload) {
|
|
3047
|
+
observe("chat:end", payload);
|
|
3048
|
+
}
|
|
3049
|
+
function onSystemError(payload) {
|
|
3050
|
+
observe("system:error", payload);
|
|
3051
|
+
}
|
|
3052
|
+
function onTurnEvents(data, acknowledge) {
|
|
3053
|
+
acknowledgeTurnEvents(acknowledge);
|
|
3054
|
+
if (data.session_id !== session_id) return;
|
|
3055
|
+
for (const event of cursor.consume(data.events.map((event2) => ({
|
|
3056
|
+
...event2,
|
|
3057
|
+
log_seq: event2.log_seq ?? void 0,
|
|
3058
|
+
step_log_seq: event2.step_log_seq ?? void 0,
|
|
3059
|
+
delta_seq: event2.delta_seq ?? void 0
|
|
3060
|
+
}))).events) {
|
|
3061
|
+
if (["chat:start", "chat:end", "system:error"].includes(event.type)) observe(event.type, event.payload);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
function subscribe() {
|
|
3065
|
+
if (settled) return;
|
|
3066
|
+
const after = Math.min(...[...subscriptions].map((item) => item.subscription().after_log_seq ?? 0));
|
|
3067
|
+
socket.emit("session:subscribe", {
|
|
3068
|
+
session_id,
|
|
3069
|
+
event_format: "raw",
|
|
3070
|
+
...after > 0 ? { after_log_seq: after } : {}
|
|
3071
|
+
});
|
|
3072
|
+
}
|
|
3073
|
+
socket.on("chat:start", onChatStart);
|
|
3074
|
+
socket.on("chat:end", onChatEnd);
|
|
3075
|
+
socket.on("system:error", onSystemError);
|
|
3076
|
+
socket.on("turn:events", onTurnEvents);
|
|
3077
|
+
socket.on("connect", subscribe);
|
|
3078
|
+
subscribe();
|
|
3079
|
+
socket.emit(
|
|
3080
|
+
"chat:send",
|
|
2733
3081
|
{
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
3082
|
+
session_id,
|
|
3083
|
+
message: prompt,
|
|
3084
|
+
headless: true,
|
|
3085
|
+
output_schema: options.schema ?? null,
|
|
3086
|
+
model: options.model ?? null,
|
|
3087
|
+
refs: options.refs,
|
|
3088
|
+
topic_asset_refs: options.topicAssetRefs
|
|
2737
3089
|
},
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
3090
|
+
(response) => {
|
|
3091
|
+
acknowledgeSend(response);
|
|
3092
|
+
if (settled) {
|
|
3093
|
+
handleLateAck?.(response);
|
|
3094
|
+
return;
|
|
3095
|
+
}
|
|
3096
|
+
if (response?.status !== "accepted") {
|
|
3097
|
+
const validationError = beforeAck.get("")?.[0];
|
|
3098
|
+
if ((response == null || isRecord(response) && Object.keys(response).length === 0) && validationError) {
|
|
3099
|
+
accept(validationError);
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
3102
|
+
rejectRun(new HeadlessError(response?.message ?? "headless run was not accepted", response));
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
if (typeof response.chat_run_id !== "string" || !response.chat_run_id) {
|
|
3106
|
+
rejectRun(new HeadlessError("headless run acknowledgement has no run ID", response));
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
chatRunId = response.chat_run_id;
|
|
3110
|
+
const pending = beforeAck.get(chatRunId) ?? [];
|
|
3111
|
+
beforeAck.clear();
|
|
3112
|
+
for (const event of pending) accept(event);
|
|
3113
|
+
}
|
|
2742
3114
|
);
|
|
2743
|
-
}
|
|
3115
|
+
});
|
|
2744
3116
|
}
|
|
2745
|
-
|
|
3117
|
+
};
|
|
3118
|
+
function normalizeResult(result, hasSchema) {
|
|
3119
|
+
if (!isRecord(result)) {
|
|
3120
|
+
throw new HeadlessError("headless run finished without a result", result);
|
|
3121
|
+
}
|
|
3122
|
+
const errMsg = typeof result.err_msg === "string" ? result.err_msg : "";
|
|
3123
|
+
if (errMsg) {
|
|
3124
|
+
throw new HeadlessError(errMsg, result);
|
|
3125
|
+
}
|
|
3126
|
+
if (!hasSchema) {
|
|
3127
|
+
const text = result.result;
|
|
3128
|
+
return typeof text === "string" ? text : String(text ?? "");
|
|
3129
|
+
}
|
|
3130
|
+
const { err_msg: _errMsg, ...businessResult } = result;
|
|
3131
|
+
return businessResult;
|
|
2746
3132
|
}
|
|
2747
|
-
function
|
|
2748
|
-
if (
|
|
2749
|
-
|
|
2750
|
-
const parsed = JSON.parse(value);
|
|
2751
|
-
return isRecord(parsed) ? parsed : {};
|
|
2752
|
-
} catch {
|
|
2753
|
-
return {};
|
|
3133
|
+
function errorMessageFromResult(result) {
|
|
3134
|
+
if (isRecord(result) && typeof result.err_msg === "string" && result.err_msg) {
|
|
3135
|
+
return result.err_msg;
|
|
2754
3136
|
}
|
|
3137
|
+
return null;
|
|
2755
3138
|
}
|
|
2756
|
-
function
|
|
2757
|
-
if (
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
3139
|
+
function ensureSocketConnected(socket, timeoutMs, detail) {
|
|
3140
|
+
if (socket.connected) {
|
|
3141
|
+
return Promise.resolve();
|
|
3142
|
+
}
|
|
3143
|
+
return new Promise((resolve, reject) => {
|
|
3144
|
+
let settled = false;
|
|
3145
|
+
const timeout = setTimeout(() => {
|
|
3146
|
+
finish(() => reject(new HeadlessError(`socket connection timed out after ${timeoutMs}ms`, detail)));
|
|
3147
|
+
}, timeoutMs);
|
|
3148
|
+
const onConnect = () => finish(resolve);
|
|
3149
|
+
const onConnectError = (error) => {
|
|
3150
|
+
finish(() => reject(new HeadlessError("socket connection failed", { ...detail, event: error })));
|
|
3151
|
+
};
|
|
3152
|
+
const finish = (callback) => {
|
|
3153
|
+
if (settled) return;
|
|
3154
|
+
settled = true;
|
|
3155
|
+
clearTimeout(timeout);
|
|
3156
|
+
socket.off("connect", onConnect);
|
|
3157
|
+
socket.off("connect_error", onConnectError);
|
|
3158
|
+
callback();
|
|
2768
3159
|
};
|
|
3160
|
+
socket.on("connect", onConnect);
|
|
3161
|
+
socket.on("connect_error", onConnectError);
|
|
3162
|
+
socket.connect();
|
|
2769
3163
|
});
|
|
2770
3164
|
}
|
|
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
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
3165
|
+
function acceptedRunId(ack) {
|
|
3166
|
+
return isRecord(ack) && ack.status === "accepted" && typeof ack.chat_run_id === "string" && ack.chat_run_id ? ack.chat_run_id : null;
|
|
3167
|
+
}
|
|
3168
|
+
function waitForCleanupAck(socket, deadline, request) {
|
|
3169
|
+
if (!socket.connected) return Promise.resolve({ status: "unconfirmed", reason: "disconnected" });
|
|
3170
|
+
if (Date.now() >= deadline) return Promise.resolve({ status: "unconfirmed", reason: "ack_timeout" });
|
|
3171
|
+
return new Promise((resolve) => {
|
|
3172
|
+
const timer = setTimeout(() => finish({ status: "unconfirmed", reason: "ack_timeout" }), Math.max(0, deadline - Date.now()));
|
|
3173
|
+
let finished = false;
|
|
3174
|
+
const finish = (outcome) => {
|
|
3175
|
+
if (finished) return;
|
|
3176
|
+
finished = true;
|
|
3177
|
+
clearTimeout(timer);
|
|
3178
|
+
socket.off("disconnect", onDisconnect);
|
|
3179
|
+
resolve(outcome);
|
|
3180
|
+
};
|
|
3181
|
+
const onDisconnect = () => finish({ status: "unconfirmed", reason: "disconnected" });
|
|
3182
|
+
socket.on("disconnect", onDisconnect);
|
|
3183
|
+
try {
|
|
3184
|
+
request((ack) => finish({ status: "received", ack }));
|
|
3185
|
+
} catch (error) {
|
|
3186
|
+
finish({ status: "unconfirmed", reason: "emit_failed", error });
|
|
3187
|
+
}
|
|
3188
|
+
});
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
// src/resources/computers.ts
|
|
3192
|
+
function computerState(computer) {
|
|
3193
|
+
if (computer.enabled) return "enabled";
|
|
3194
|
+
return computer.online ? "idle" : "offline";
|
|
3195
|
+
}
|
|
3196
|
+
function computerOS(computer) {
|
|
3197
|
+
const os = (computer.os ?? "").toLowerCase();
|
|
3198
|
+
if (os.startsWith("darwin") || os.startsWith("mac")) return "macos";
|
|
3199
|
+
if (os.startsWith("windows")) return "windows";
|
|
3200
|
+
if (os.startsWith("linux")) return "linux";
|
|
3201
|
+
return "unknown";
|
|
3202
|
+
}
|
|
3203
|
+
function computerPlatformLabel(computer) {
|
|
3204
|
+
const os = (computer.os ?? "").trim();
|
|
3205
|
+
const arch = (computer.arch ?? "").trim();
|
|
3206
|
+
if (os && arch) return `${os}/${arch}`;
|
|
3207
|
+
return os || arch || "";
|
|
3208
|
+
}
|
|
3209
|
+
function computerDaemonVersion(computer) {
|
|
3210
|
+
return (computer.daemon_version ?? "").trim();
|
|
3211
|
+
}
|
|
3212
|
+
function sortComputers(computers) {
|
|
3213
|
+
return [...computers].sort((a, b) => {
|
|
3214
|
+
const byCreated = (a.created_at ?? "").localeCompare(b.created_at ?? "");
|
|
3215
|
+
if (byCreated !== 0) return byCreated;
|
|
3216
|
+
return a.label.localeCompare(b.label);
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
var ComputersResource = class {
|
|
3220
|
+
constructor(client) {
|
|
3221
|
+
this.client = client;
|
|
3222
|
+
}
|
|
3223
|
+
client;
|
|
3224
|
+
/**
|
|
3225
|
+
* 列出当前账号登记过的全部电脑及其在线状态。
|
|
3226
|
+
*
|
|
3227
|
+
* 首页还没有会话,也要能看到同一份清单——判定各写一份必然分叉,一边的兜底会把
|
|
3228
|
+
* 另一边的错误盖住(#1597)。所以账号事实只从这一个接口读。
|
|
3229
|
+
*/
|
|
3230
|
+
async listAccount() {
|
|
3231
|
+
const data = await this.client.json("GET", "/api/daemon/instances");
|
|
3232
|
+
return {
|
|
3233
|
+
computers: (data.instances ?? []).map((instance) => ({
|
|
3234
|
+
...instance,
|
|
3235
|
+
online: Boolean(instance.online)
|
|
3236
|
+
}))
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
/**
|
|
3240
|
+
* 列出当前用户的全部电脑,带上"在不在线"和"这次会话能不能用"。
|
|
3241
|
+
* 服务端只查库,电脑离线时同样返回,所以拿得到"它离线了"这个结论。
|
|
3242
|
+
*/
|
|
3243
|
+
list(sessionId) {
|
|
3244
|
+
return this.client.json(
|
|
3245
|
+
"GET",
|
|
3246
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/computers`
|
|
3247
|
+
);
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
3250
|
+
* 启动或停止一台电脑。只改这次会话的可用范围,不会碰远程机器,
|
|
3251
|
+
* 电脑离线时同样能登记,等它上线就直接可用。
|
|
3252
|
+
*/
|
|
3253
|
+
setEnabled(sessionId, computer, enabled) {
|
|
3254
|
+
return this.client.json(
|
|
3255
|
+
"PUT",
|
|
3256
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/computers/${encodeURIComponent(computer)}/enabled`,
|
|
3257
|
+
{ enabled }
|
|
3258
|
+
);
|
|
3259
|
+
}
|
|
3260
|
+
};
|
|
3261
|
+
|
|
3262
|
+
// src/resources/models.ts
|
|
3263
|
+
var ModelsResource = class {
|
|
3264
|
+
constructor(client) {
|
|
3265
|
+
this.client = client;
|
|
3266
|
+
}
|
|
3267
|
+
client;
|
|
3268
|
+
list(options = {}) {
|
|
3269
|
+
return this.client.jsonFromInit("/api/config/models", { signal: options.signal });
|
|
3270
|
+
}
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3273
|
+
// src/resources/connectors.ts
|
|
3274
|
+
function isLocalConnectorName(name) {
|
|
3275
|
+
return name.startsWith("connector.personal.") || name.startsWith("connector.builtin.");
|
|
3276
|
+
}
|
|
3277
|
+
var ConnectorsResource = class {
|
|
3278
|
+
constructor(client) {
|
|
3279
|
+
this.client = client;
|
|
3280
|
+
}
|
|
3281
|
+
client;
|
|
3282
|
+
create(draft) {
|
|
3283
|
+
return this.client.json("POST", "/api/connectors", draft);
|
|
3284
|
+
}
|
|
3285
|
+
update(name, draft) {
|
|
3286
|
+
return this.client.json("PATCH", `/api/connectors/${encodeURIComponent(name)}`, draft);
|
|
3287
|
+
}
|
|
3288
|
+
delete(name) {
|
|
3289
|
+
return this.client.json("DELETE", `/api/connectors/${encodeURIComponent(name)}`);
|
|
3290
|
+
}
|
|
3291
|
+
/** Create a generation conversation and send its visible Skill request.
|
|
3292
|
+
* Hosts use onCreated only to register caches and navigate before sending. */
|
|
3293
|
+
async startGeneration({ connector, onCreated, initiator = "sdk" } = {}) {
|
|
3294
|
+
const intent = connector ? "\u4FEE\u6539\u8FDE\u63A5\u5668" : "\u521B\u5EFA\u8FDE\u63A5\u5668";
|
|
3295
|
+
const request = connector ? "\u4FEE\u6539\u9009\u5B9A\u7684\u8FDE\u63A5\u5668" : "\u521B\u5EFA\u8FDE\u63A5\u5668";
|
|
3296
|
+
const { session_id } = await this.client.sessions.createSessionWithRequest({ intent, solution_id: "connector_generate", initiator });
|
|
3297
|
+
await onCreated?.({ session_id, intent });
|
|
3298
|
+
const session = await this.client.sessions.connect(session_id);
|
|
3299
|
+
try {
|
|
3300
|
+
const sent = await session.send(`\u8BF7\u4F7F\u7528\u8FDE\u63A5\u5668\u751F\u6210\u5668\uFF0C\u5E2E\u6211${request}\u3002\u5148\u786E\u8BA4\u6211\u5E0C\u671B\u8FDE\u63A5\u7684\u7CFB\u7EDF\u548C\u9700\u8981\u7684\u529F\u80FD\uFF0C\u518D\u6309\u6280\u80FD\u5F15\u5BFC\u6211\u5B8C\u6210\u3002`, {
|
|
3301
|
+
mode: "executing",
|
|
3302
|
+
refs: [{ provider: "skill", id: "generate-connector", label: "\u8FDE\u63A5\u5668\u751F\u6210\u5668" }],
|
|
3303
|
+
...connector ? { contextAttachments: [{
|
|
3304
|
+
id: "connector-generation-target",
|
|
3305
|
+
label: "\u9009\u5B9A\u7684\u8FDE\u63A5\u5668",
|
|
3306
|
+
content: JSON.stringify({ name: connector.name, display_name: connector.display_name }),
|
|
3307
|
+
update_format: "snapshot"
|
|
3308
|
+
}] } : {}
|
|
3309
|
+
});
|
|
3310
|
+
return { session_id, sent };
|
|
3311
|
+
} finally {
|
|
3312
|
+
session.dispose();
|
|
2797
3313
|
}
|
|
2798
|
-
} else if (message.content !== void 0 && message.content !== "") {
|
|
2799
|
-
blocks.push({ type: "text", content: message.content });
|
|
2800
3314
|
}
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
3315
|
+
list(init) {
|
|
3316
|
+
return this.client.jsonFromInit("/api/connectors", init);
|
|
3317
|
+
}
|
|
3318
|
+
listForSession(sessionId, init) {
|
|
3319
|
+
return this.client.jsonFromInit(`/api/connectors?session_id=${encodeURIComponent(sessionId)}`, init);
|
|
3320
|
+
}
|
|
3321
|
+
read(name, init) {
|
|
3322
|
+
return this.client.jsonFromInit(`/api/connectors/${encodeURIComponent(name)}/connection`, init);
|
|
3323
|
+
}
|
|
3324
|
+
connect(name, update, init) {
|
|
3325
|
+
return this.client.jsonFromInit(`/api/connectors/${encodeURIComponent(name)}/connection`, {
|
|
3326
|
+
method: "PUT",
|
|
3327
|
+
body: JSON.stringify(update),
|
|
3328
|
+
...init
|
|
2809
3329
|
});
|
|
2810
3330
|
}
|
|
2811
|
-
|
|
2812
|
-
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
}
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
const
|
|
2838
|
-
if (
|
|
3331
|
+
browserAuthorizationUrl(name) {
|
|
3332
|
+
return new URL(`connector-auth/${encodeURIComponent(name)}`, `${this.client.options.baseUrl.replace(/\/$/, "")}/`).toString();
|
|
3333
|
+
}
|
|
3334
|
+
/** One-use, one-minute identity handoff to this client's trusted Blade origin.
|
|
3335
|
+
* Tokens travel only through a source/origin-bound message, never a URL/storage. */
|
|
3336
|
+
bindBrowserAuthorizationFrame(frame, name) {
|
|
3337
|
+
const nonce = Array.from(crypto.getRandomValues(new Uint8Array(32)), (value) => value.toString(16).padStart(2, "0")).join("");
|
|
3338
|
+
const url = new URL(this.browserAuthorizationUrl(name), window.location.href);
|
|
3339
|
+
url.searchParams.set("embedded", "1");
|
|
3340
|
+
url.searchParams.set("handoff", nonce);
|
|
3341
|
+
url.searchParams.set("parent_origin", window.location.origin);
|
|
3342
|
+
const receive = (event) => {
|
|
3343
|
+
if (event.origin !== url.origin || event.source !== frame.contentWindow || event.data?.type !== "blade:connector-auth-ready" || event.data?.nonce !== nonce) return;
|
|
3344
|
+
cleanup();
|
|
3345
|
+
frame.contentWindow?.postMessage({ type: "blade:connector-auth-identity", nonce, token: this.client.token }, url.origin);
|
|
3346
|
+
};
|
|
3347
|
+
const timeout = window.setTimeout(() => cleanup(), 6e4);
|
|
3348
|
+
const cleanup = () => {
|
|
3349
|
+
window.clearTimeout(timeout);
|
|
3350
|
+
window.removeEventListener("message", receive);
|
|
3351
|
+
};
|
|
3352
|
+
window.addEventListener("message", receive);
|
|
3353
|
+
frame.src = url.toString();
|
|
3354
|
+
return cleanup;
|
|
3355
|
+
}
|
|
3356
|
+
async startBrowserAuthorization(name, sessionId) {
|
|
3357
|
+
const view = await this.read(name);
|
|
3358
|
+
if (view.authorization?.type !== "browser") throw new Error("\u8BE5\u8FDE\u63A5\u5668\u6CA1\u6709\u7F51\u9875\u767B\u5F55\u5165\u53E3");
|
|
3359
|
+
const id = sessionId ?? (await this.client.sessions.createSessionWithRequest({
|
|
3360
|
+
intent: `\u8FDE\u63A5 ${view.display_name || "\u4E1A\u52A1\u7CFB\u7EDF"}`,
|
|
3361
|
+
memory_enabled: false
|
|
3362
|
+
})).session_id;
|
|
3363
|
+
await this.client.json("POST", `/api/sessions/${encodeURIComponent(id)}/browser/wake`, {});
|
|
3364
|
+
const tabs = await this.client.json("GET", `/api/sessions/${encodeURIComponent(id)}/browser/tabs`);
|
|
3365
|
+
const hasBusinessTab = tabs.data?.some(({ url }) => url && url !== "about:blank" && !/^http:\/\/(?:127\.0\.0\.1|localhost):9223\/home(?:[/?#]|$)/.test(url));
|
|
3366
|
+
if (!sessionId || !hasBusinessTab) await this.client.json("POST", `/api/sessions/${encodeURIComponent(id)}/browser/tabs`, { url: view.authorization.url });
|
|
3367
|
+
return { session_id: id, view };
|
|
3368
|
+
}
|
|
3369
|
+
verify(name) {
|
|
3370
|
+
return this.client.json("POST", `/api/connectors/${encodeURIComponent(name)}/connection:verify`);
|
|
3371
|
+
}
|
|
3372
|
+
disconnect(name) {
|
|
3373
|
+
return this.client.json("DELETE", `/api/connectors/${encodeURIComponent(name)}/connection`);
|
|
2839
3374
|
}
|
|
2840
|
-
|
|
2841
|
-
|
|
3375
|
+
};
|
|
3376
|
+
|
|
3377
|
+
// src/schemas/session.ts
|
|
3378
|
+
import { type } from "arktype";
|
|
3379
|
+
var SessionStatus = type(
|
|
3380
|
+
"'created' | 'running' | 'completed' | 'failed' | 'interrupted' | 'waiting_for_input'"
|
|
3381
|
+
);
|
|
3382
|
+
var DEFAULT_REPLAY_SPEED = 5;
|
|
3383
|
+
var SessionInfo = type({
|
|
3384
|
+
id: "string",
|
|
3385
|
+
intent: "string",
|
|
3386
|
+
status: SessionStatus,
|
|
3387
|
+
created_at: "string",
|
|
3388
|
+
updated_at: "string",
|
|
3389
|
+
"shared?": "boolean",
|
|
3390
|
+
"anonymous?": "boolean",
|
|
3391
|
+
"memory_enabled?": "boolean",
|
|
3392
|
+
"is_persistent?": "boolean",
|
|
3393
|
+
"initiator?": "'ui' | 'sdk' | 'unknown' | null",
|
|
3394
|
+
"is_headless?": "boolean",
|
|
3395
|
+
"viewer_role?": "'owner' | 'viewer'",
|
|
3396
|
+
"model?": "string | null",
|
|
3397
|
+
"enable_thinking?": "boolean | null",
|
|
3398
|
+
"solution_id?": "string | null",
|
|
3399
|
+
"solution_ref?": "unknown",
|
|
3400
|
+
"biz_role_id?": "string | null",
|
|
3401
|
+
"solution?": "unknown",
|
|
3402
|
+
"plan_summary?": "string | null",
|
|
3403
|
+
"replay_state?": "unknown",
|
|
3404
|
+
"is_pinned?": "boolean",
|
|
3405
|
+
"pinned_at?": "string | null",
|
|
3406
|
+
"is_example?": "boolean",
|
|
3407
|
+
"ports?": "unknown",
|
|
3408
|
+
"disable_tools?": "string[]",
|
|
3409
|
+
"workspace_path?": "string | null",
|
|
3410
|
+
"ba_version?": "string | null",
|
|
3411
|
+
"sandbox_version?": "string | null",
|
|
3412
|
+
"chat_group_id?": "string | null",
|
|
3413
|
+
"match?": "unknown"
|
|
3414
|
+
});
|
|
3415
|
+
|
|
3416
|
+
// src/session/definition.ts
|
|
3417
|
+
var SessionSetupError = class extends Error {
|
|
3418
|
+
constructor(message, stage, sessionId, options) {
|
|
3419
|
+
super(message);
|
|
3420
|
+
this.stage = stage;
|
|
3421
|
+
this.sessionId = sessionId;
|
|
3422
|
+
this.cause = options?.cause;
|
|
3423
|
+
}
|
|
3424
|
+
stage;
|
|
3425
|
+
sessionId;
|
|
3426
|
+
name = "SessionSetupError";
|
|
3427
|
+
cause;
|
|
3428
|
+
};
|
|
2842
3429
|
|
|
2843
3430
|
// src/resources/sessions.ts
|
|
2844
3431
|
function isEntryGoneError(error) {
|
|
@@ -2849,14 +3436,14 @@ function toCreateSessionPayload(request) {
|
|
|
2849
3436
|
const solutionRef = request.solution_ref ?? externalSolutionRef(request.solution_id);
|
|
2850
3437
|
return {
|
|
2851
3438
|
intent: request.intent ?? "",
|
|
2852
|
-
|
|
3439
|
+
initiator: request.initiator ?? "sdk",
|
|
3440
|
+
anonymous: request.anonymous,
|
|
2853
3441
|
solution_id: solutionRef ? void 0 : request.solution_id,
|
|
2854
3442
|
solution_ref: solutionRef,
|
|
2855
3443
|
biz_role_id: request.biz_role_id ?? null,
|
|
2856
3444
|
model: request.model ?? null,
|
|
2857
3445
|
enable_thinking: request.enable_thinking ?? null,
|
|
2858
3446
|
memory_enabled: request.memory_enabled ?? null,
|
|
2859
|
-
primary_skill_id: request.primary_skill_id ?? null,
|
|
2860
3447
|
env: request.env,
|
|
2861
3448
|
disable_tools: request.disable_tools ?? null,
|
|
2862
3449
|
mcp_sources: request.mcp_sources,
|
|
@@ -2864,9 +3451,6 @@ function toCreateSessionPayload(request) {
|
|
|
2864
3451
|
session_solution_asset_id: request.session_solution_asset_id ?? null,
|
|
2865
3452
|
solution_snapshot_session_id: request.solution_snapshot_session_id ?? null,
|
|
2866
3453
|
compaction_ratio: request.compaction_ratio ?? null,
|
|
2867
|
-
...request.runtime_type ? { runtime_type: request.runtime_type } : {},
|
|
2868
|
-
...request.daemon_id ? { daemon_id: request.daemon_id } : {},
|
|
2869
|
-
...request.agent_runtime_id ? { agent_runtime_id: request.agent_runtime_id } : {},
|
|
2870
3454
|
...request.chat_group_id ? { chat_group_id: request.chat_group_id } : {}
|
|
2871
3455
|
};
|
|
2872
3456
|
}
|
|
@@ -2954,7 +3538,8 @@ var SessionsResource = class {
|
|
|
2954
3538
|
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins`, init);
|
|
2955
3539
|
}
|
|
2956
3540
|
setSessionPluginActivation(sessionId, name, active, init) {
|
|
2957
|
-
|
|
3541
|
+
const kind = isLocalConnectorName(name) ? "connectors" : "plugins";
|
|
3542
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/${kind}/${encodeURIComponent(name)}:activation`, { method: "POST", body: JSON.stringify({ active }), ...init });
|
|
2958
3543
|
}
|
|
2959
3544
|
/**
|
|
2960
3545
|
* Fetch a plugin's package-local icon as bytes.
|
|
@@ -2987,18 +3572,21 @@ var SessionsResource = class {
|
|
|
2987
3572
|
*
|
|
2988
3573
|
* Secrets are never returned: `write_only` lists the pointers that hold one.
|
|
2989
3574
|
*/
|
|
2990
|
-
getSessionPluginConfig(sessionId, name, init) {
|
|
2991
|
-
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config`, init);
|
|
3575
|
+
getSessionPluginConfig(sessionId, name, init, scope = "personal") {
|
|
3576
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config?scope=${scope}`, init);
|
|
2992
3577
|
}
|
|
2993
3578
|
/**
|
|
2994
|
-
*
|
|
3579
|
+
* Save explicit set/remove operations to encrypted server storage.
|
|
2995
3580
|
*
|
|
2996
3581
|
* Fields absent from both lists keep their stored value, so a form never has
|
|
2997
|
-
* to round-trip a secret. Rejections
|
|
2998
|
-
*
|
|
3582
|
+
* to round-trip a secret. Rejections preserve the previous values; the
|
|
3583
|
+
* shared settings restart only the affected hosted plugin.
|
|
2999
3584
|
*/
|
|
3000
|
-
updateSessionPluginConfig(sessionId, name, update, init) {
|
|
3001
|
-
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config`, { method: "PUT", body: JSON.stringify(update), ...init });
|
|
3585
|
+
updateSessionPluginConfig(sessionId, name, update, init, scope = "personal") {
|
|
3586
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/config?scope=${scope}`, { method: "PUT", body: JSON.stringify(update), ...init });
|
|
3587
|
+
}
|
|
3588
|
+
restartSessionPluginHost(sessionId, name) {
|
|
3589
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/plugins/${encodeURIComponent(name)}/host`, { method: "POST" });
|
|
3002
3590
|
}
|
|
3003
3591
|
/** 创建新会话并直接连接,返回可收发消息的 AgentSession。 */
|
|
3004
3592
|
async create(request = {}) {
|
|
@@ -3006,7 +3594,7 @@ var SessionsResource = class {
|
|
|
3006
3594
|
return this.createDeclared(request);
|
|
3007
3595
|
}
|
|
3008
3596
|
const { session_id } = await this.createSessionWithRequest(request);
|
|
3009
|
-
return this.connect(session_id);
|
|
3597
|
+
return request.anonymous ? this.client.forAnonymousSession(session_id).sessions.connect(session_id) : this.connect(session_id);
|
|
3010
3598
|
}
|
|
3011
3599
|
async createDeclared(definition) {
|
|
3012
3600
|
const { intent, solution, skills = [], config } = definition;
|
|
@@ -3027,7 +3615,8 @@ var SessionsResource = class {
|
|
|
3027
3615
|
memory_enabled: config?.memoryEnabled,
|
|
3028
3616
|
compaction_ratio: config?.compactionRatio,
|
|
3029
3617
|
env: config?.env,
|
|
3030
|
-
disable_tools: config?.disableTools
|
|
3618
|
+
disable_tools: config?.disableTools,
|
|
3619
|
+
mcp_sources: config?.mcpSources
|
|
3031
3620
|
});
|
|
3032
3621
|
session_id = created.session_id;
|
|
3033
3622
|
} catch (error) {
|
|
@@ -3093,7 +3682,6 @@ var SessionsResource = class {
|
|
|
3093
3682
|
fetchSessionsPage({
|
|
3094
3683
|
limit,
|
|
3095
3684
|
offset,
|
|
3096
|
-
template_id_prefix,
|
|
3097
3685
|
solution_id,
|
|
3098
3686
|
solution_registry_id,
|
|
3099
3687
|
q,
|
|
@@ -3102,7 +3690,6 @@ var SessionsResource = class {
|
|
|
3102
3690
|
const searchParams = new URLSearchParams();
|
|
3103
3691
|
searchParams.set("limit", String(limit));
|
|
3104
3692
|
searchParams.set("offset", String(offset));
|
|
3105
|
-
if (template_id_prefix) searchParams.set("template_id_prefix", template_id_prefix);
|
|
3106
3693
|
if (solution_registry_id) {
|
|
3107
3694
|
searchParams.set("solution_registry_id", solution_registry_id);
|
|
3108
3695
|
} else if (solution_id) {
|
|
@@ -3113,12 +3700,12 @@ var SessionsResource = class {
|
|
|
3113
3700
|
if (chat_group_id) searchParams.set("chat_group_id", chat_group_id);
|
|
3114
3701
|
return this.client.json("GET", `/api/sessions?${searchParams.toString()}`);
|
|
3115
3702
|
}
|
|
3116
|
-
async listSessions(
|
|
3703
|
+
async listSessions(solution_id) {
|
|
3117
3704
|
const pageSize = 100;
|
|
3118
3705
|
const sessions = [];
|
|
3119
3706
|
let offset = 0;
|
|
3120
3707
|
while (true) {
|
|
3121
|
-
const page = await this.fetchSessionsPage({ limit: pageSize, offset,
|
|
3708
|
+
const page = await this.fetchSessionsPage({ limit: pageSize, offset, solution_id });
|
|
3122
3709
|
sessions.push(...page.items);
|
|
3123
3710
|
if (sessions.length >= page.total || page.items.length === 0) return sessions;
|
|
3124
3711
|
offset += page.items.length;
|
|
@@ -3135,8 +3722,8 @@ var SessionsResource = class {
|
|
|
3135
3722
|
if (params.limit !== void 0) searchParams.set("limit", String(params.limit));
|
|
3136
3723
|
return this.client.json("GET", `/api/search?${searchParams.toString()}`);
|
|
3137
3724
|
}
|
|
3138
|
-
createSession(intent
|
|
3139
|
-
return this.createSessionWithRequest({ intent
|
|
3725
|
+
createSession(intent) {
|
|
3726
|
+
return this.createSessionWithRequest({ intent });
|
|
3140
3727
|
}
|
|
3141
3728
|
createSessionWithRequest(request) {
|
|
3142
3729
|
return this.client.json("POST", "/api/sessions", toCreateSessionPayload(request));
|
|
@@ -3154,6 +3741,13 @@ var SessionsResource = class {
|
|
|
3154
3741
|
updateSession(sessionId, payload) {
|
|
3155
3742
|
return this.client.json("PATCH", `/api/sessions/${sessionId}`, payload);
|
|
3156
3743
|
}
|
|
3744
|
+
getSessionConfiguration(sessionId, init) {
|
|
3745
|
+
return this.client.jsonFromInit(`/api/sessions/${encodeURIComponent(sessionId)}/configuration`, init);
|
|
3746
|
+
}
|
|
3747
|
+
/** Updates atomically between turns. A running/handoff session returns HTTP 409. */
|
|
3748
|
+
updateSessionConfiguration(sessionId, request) {
|
|
3749
|
+
return this.client.json("PATCH", `/api/sessions/${encodeURIComponent(sessionId)}/configuration`, request);
|
|
3750
|
+
}
|
|
3157
3751
|
setSessionEnv(sessionId, env) {
|
|
3158
3752
|
return this.client.json("PUT", `/api/sessions/${sessionId}/env`, { env });
|
|
3159
3753
|
}
|
|
@@ -3291,13 +3885,25 @@ var SessionsResource = class {
|
|
|
3291
3885
|
}
|
|
3292
3886
|
async rewindSession(sessionId, checkpointId) {
|
|
3293
3887
|
const result = await this.checkoutSession(sessionId, checkpointId, "before");
|
|
3294
|
-
return {
|
|
3888
|
+
return {
|
|
3889
|
+
id: result.id,
|
|
3890
|
+
content: result.content,
|
|
3891
|
+
...result.history_committed !== void 0 ? { history_committed: result.history_committed } : {},
|
|
3892
|
+
...result.file_checkpoint_cleanup_failed !== void 0 ? { file_checkpoint_cleanup_failed: result.file_checkpoint_cleanup_failed } : {},
|
|
3893
|
+
...result.warning !== void 0 ? { warning: result.warning } : {}
|
|
3894
|
+
};
|
|
3295
3895
|
}
|
|
3296
3896
|
switchBranch(sessionId, checkpointId) {
|
|
3297
3897
|
return this.client.json("POST", `/api/sessions/${sessionId}/switch-branch`, {
|
|
3298
3898
|
checkpoint_id: checkpointId
|
|
3299
3899
|
});
|
|
3300
3900
|
}
|
|
3901
|
+
listAnonymousSessions() {
|
|
3902
|
+
return this.client.json("GET", "/api/anonymous-sessions");
|
|
3903
|
+
}
|
|
3904
|
+
deleteAnonymousSession(sessionId) {
|
|
3905
|
+
return this.client.json("DELETE", `/api/anonymous-sessions/${encodeURIComponent(sessionId)}`);
|
|
3906
|
+
}
|
|
3301
3907
|
deleteSession(sessionId, options) {
|
|
3302
3908
|
const query = options?.deleteWorkspace ? "?delete_workspace=true" : "";
|
|
3303
3909
|
return this.client.json("DELETE", `/api/sessions/${sessionId}${query}`);
|
|
@@ -3545,6 +4151,9 @@ var ChatGroupsResource = class {
|
|
|
3545
4151
|
this.client = client;
|
|
3546
4152
|
}
|
|
3547
4153
|
client;
|
|
4154
|
+
path(groupId) {
|
|
4155
|
+
return `/api/chat-groups/${encodeURIComponent(groupId)}`;
|
|
4156
|
+
}
|
|
3548
4157
|
list() {
|
|
3549
4158
|
return this.client.json("GET", "/api/chat-groups");
|
|
3550
4159
|
}
|
|
@@ -3552,11 +4161,124 @@ var ChatGroupsResource = class {
|
|
|
3552
4161
|
return this.client.json("POST", "/api/chat-groups", payload);
|
|
3553
4162
|
}
|
|
3554
4163
|
update(groupId, payload) {
|
|
3555
|
-
return this.client.json("PATCH",
|
|
4164
|
+
return this.client.json("PATCH", this.path(groupId), payload);
|
|
3556
4165
|
}
|
|
3557
4166
|
delete(groupId, options) {
|
|
3558
4167
|
const query = options?.deleteWorkspace ? "?delete_workspace=true" : "";
|
|
3559
|
-
return this.client.json("DELETE",
|
|
4168
|
+
return this.client.json("DELETE", `${this.path(groupId)}${query}`);
|
|
4169
|
+
}
|
|
4170
|
+
listMembers(groupId) {
|
|
4171
|
+
return this.client.json("GET", `${this.path(groupId)}/members`);
|
|
4172
|
+
}
|
|
4173
|
+
addMembers(groupId, userIds, role) {
|
|
4174
|
+
return this.client.json("POST", `${this.path(groupId)}/members`, { user_ids: userIds, role });
|
|
4175
|
+
}
|
|
4176
|
+
updateMember(groupId, userId, role) {
|
|
4177
|
+
return this.client.json("PATCH", `${this.path(groupId)}/members/${encodeURIComponent(userId)}`, { role });
|
|
4178
|
+
}
|
|
4179
|
+
removeMember(groupId, userId) {
|
|
4180
|
+
return this.client.json("DELETE", `${this.path(groupId)}/members/${encodeURIComponent(userId)}`);
|
|
4181
|
+
}
|
|
4182
|
+
listTasks(groupId) {
|
|
4183
|
+
return this.client.json("GET", `${this.path(groupId)}/tasks`);
|
|
4184
|
+
}
|
|
4185
|
+
async createTask(groupId, payload) {
|
|
4186
|
+
try {
|
|
4187
|
+
return await this.client.json("POST", `${this.path(groupId)}/tasks`, payload);
|
|
4188
|
+
} catch (error) {
|
|
4189
|
+
if (error instanceof BladeApiError && error.status === 502) {
|
|
4190
|
+
const data = await error.response.clone().json().catch(() => null);
|
|
4191
|
+
if (typeof data?.detail?.message === "string") error.message = data.detail.message;
|
|
4192
|
+
}
|
|
4193
|
+
throw error;
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
updateTask(groupId, taskId, payload) {
|
|
4197
|
+
return this.client.json("PATCH", `${this.path(groupId)}/tasks/${encodeURIComponent(taskId)}`, payload);
|
|
4198
|
+
}
|
|
4199
|
+
deleteTask(groupId, taskId) {
|
|
4200
|
+
return this.client.json("DELETE", `${this.path(groupId)}/tasks/${encodeURIComponent(taskId)}`);
|
|
4201
|
+
}
|
|
4202
|
+
listAssets(groupId) {
|
|
4203
|
+
return this.client.json("GET", `${this.path(groupId)}/assets`);
|
|
4204
|
+
}
|
|
4205
|
+
async readAsset(groupId, path, signal) {
|
|
4206
|
+
const response = await this.client.fetch(
|
|
4207
|
+
"GET",
|
|
4208
|
+
`${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`,
|
|
4209
|
+
{ signal }
|
|
4210
|
+
);
|
|
4211
|
+
return response.blob();
|
|
4212
|
+
}
|
|
4213
|
+
/** Read at most the first 1 MiB. Full downloads continue to use readAsset. */
|
|
4214
|
+
async readAssetPreview(groupId, path, signal) {
|
|
4215
|
+
const limit = 1024 * 1024;
|
|
4216
|
+
let response;
|
|
4217
|
+
try {
|
|
4218
|
+
response = await this.client.fetch(
|
|
4219
|
+
"GET",
|
|
4220
|
+
`${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`,
|
|
4221
|
+
{ signal, headers: { Range: `bytes=0-${limit - 1}` } }
|
|
4222
|
+
);
|
|
4223
|
+
} catch (error) {
|
|
4224
|
+
if (error instanceof BladeApiError && error.status === 416 && error.response.headers.get("content-range") === "bytes */0") {
|
|
4225
|
+
await error.response.body?.cancel();
|
|
4226
|
+
return { blob: new Blob(), truncated: false };
|
|
4227
|
+
}
|
|
4228
|
+
throw error;
|
|
4229
|
+
}
|
|
4230
|
+
const reader = response.body?.getReader();
|
|
4231
|
+
if (!reader) return { blob: new Blob(), truncated: false };
|
|
4232
|
+
const chunks = [];
|
|
4233
|
+
let bytes = 0;
|
|
4234
|
+
let ended = false;
|
|
4235
|
+
let overflow = false;
|
|
4236
|
+
try {
|
|
4237
|
+
while (bytes < limit) {
|
|
4238
|
+
signal?.throwIfAborted();
|
|
4239
|
+
const { done, value } = await reader.read();
|
|
4240
|
+
if (done) {
|
|
4241
|
+
ended = true;
|
|
4242
|
+
break;
|
|
4243
|
+
}
|
|
4244
|
+
const remaining = limit - bytes;
|
|
4245
|
+
overflow = value.byteLength > remaining;
|
|
4246
|
+
const chunk = value.slice(0, remaining);
|
|
4247
|
+
chunks.push(chunk);
|
|
4248
|
+
bytes += chunk.byteLength;
|
|
4249
|
+
}
|
|
4250
|
+
signal?.throwIfAborted();
|
|
4251
|
+
} finally {
|
|
4252
|
+
if (!ended) await reader.cancel().catch(() => void 0);
|
|
4253
|
+
reader.releaseLock();
|
|
4254
|
+
}
|
|
4255
|
+
const range = response.headers.get("content-range")?.match(/^bytes 0-\d+\/(\d+)$/);
|
|
4256
|
+
const length = response.headers.get("content-length");
|
|
4257
|
+
const total = range ? Number(range[1]) : response.status === 200 && length && !response.headers.has("content-encoding") ? Number(length) : null;
|
|
4258
|
+
return {
|
|
4259
|
+
blob: new Blob(chunks, { type: response.headers.get("content-type") ?? "" }),
|
|
4260
|
+
truncated: overflow || (total !== null && Number.isFinite(total) ? total > bytes : !ended)
|
|
4261
|
+
};
|
|
4262
|
+
}
|
|
4263
|
+
async uploadAsset(groupId, file, path, message = "") {
|
|
4264
|
+
const form = new FormData();
|
|
4265
|
+
form.append("file", file);
|
|
4266
|
+
form.append("message", message);
|
|
4267
|
+
const response = await this.client.formData(
|
|
4268
|
+
"POST",
|
|
4269
|
+
`${this.path(groupId)}/assets/upload?${new URLSearchParams({ path })}`,
|
|
4270
|
+
form
|
|
4271
|
+
);
|
|
4272
|
+
return response.json();
|
|
4273
|
+
}
|
|
4274
|
+
createAssetFolder(groupId, path) {
|
|
4275
|
+
return this.client.json("POST", `${this.path(groupId)}/assets/folders`, { path });
|
|
4276
|
+
}
|
|
4277
|
+
deleteAsset(groupId, path) {
|
|
4278
|
+
return this.client.json("DELETE", `${this.path(groupId)}/assets/file?${new URLSearchParams({ path })}`);
|
|
4279
|
+
}
|
|
4280
|
+
submitSessionAsset(payload) {
|
|
4281
|
+
return this.client.json("POST", "/api/topic-agent/files/submit", payload);
|
|
3560
4282
|
}
|
|
3561
4283
|
};
|
|
3562
4284
|
|
|
@@ -3583,7 +4305,6 @@ var SiteVisitsResource = class {
|
|
|
3583
4305
|
try {
|
|
3584
4306
|
const response = await (this.client.options.fetchImpl ?? fetch)(url.toString(), {
|
|
3585
4307
|
method,
|
|
3586
|
-
// 访客 Cookie 是唯一的去重依据,必须随请求往返。
|
|
3587
4308
|
credentials: "include",
|
|
3588
4309
|
signal: controller.signal
|
|
3589
4310
|
});
|
|
@@ -3597,6 +4318,48 @@ var SiteVisitsResource = class {
|
|
|
3597
4318
|
}
|
|
3598
4319
|
};
|
|
3599
4320
|
|
|
4321
|
+
// src/server-clock.ts
|
|
4322
|
+
var clocks = /* @__PURE__ */ new WeakMap();
|
|
4323
|
+
function syncServerTime(client, timestamp, receivedAt = performance.now()) {
|
|
4324
|
+
if (typeof timestamp !== "string") return;
|
|
4325
|
+
const serverTime = Date.parse(timestamp);
|
|
4326
|
+
if (!Number.isFinite(serverTime)) return;
|
|
4327
|
+
const current = getServerTime(client, receivedAt);
|
|
4328
|
+
clocks.set(client, { serverTime: Math.max(serverTime, current ?? serverTime), receivedAt });
|
|
4329
|
+
}
|
|
4330
|
+
function getServerTime(client, now = performance.now()) {
|
|
4331
|
+
const clock = client ? clocks.get(client) : void 0;
|
|
4332
|
+
return clock ? clock.serverTime + Math.max(0, now - clock.receivedAt) : null;
|
|
4333
|
+
}
|
|
4334
|
+
|
|
4335
|
+
// src/session/background.ts
|
|
4336
|
+
var BACKGROUND_STATUSES = [
|
|
4337
|
+
"starting",
|
|
4338
|
+
"running",
|
|
4339
|
+
"cancelling",
|
|
4340
|
+
"succeeded",
|
|
4341
|
+
"failed",
|
|
4342
|
+
"cancelled",
|
|
4343
|
+
"lost"
|
|
4344
|
+
];
|
|
4345
|
+
function normalizeBackgroundStatus(value) {
|
|
4346
|
+
return BACKGROUND_STATUSES.includes(value) ? value : "running";
|
|
4347
|
+
}
|
|
4348
|
+
function completedBackgroundTask(task) {
|
|
4349
|
+
const taskId = typeof task.id === "string" ? task.id : typeof task.task_id === "string" ? task.task_id : "";
|
|
4350
|
+
if (!taskId) return null;
|
|
4351
|
+
return {
|
|
4352
|
+
id: taskId,
|
|
4353
|
+
command: typeof task.command === "string" ? task.command : "",
|
|
4354
|
+
description: typeof task.description === "string" ? task.description : void 0,
|
|
4355
|
+
status: normalizeBackgroundStatus(task.status),
|
|
4356
|
+
failureReason: typeof task.failure_reason === "string" ? task.failure_reason : void 0,
|
|
4357
|
+
exitCode: typeof task.exit_code === "number" || task.exit_code === null ? task.exit_code : void 0,
|
|
4358
|
+
elapsedSeconds: typeof task.elapsed_seconds === "number" ? task.elapsed_seconds : 0,
|
|
4359
|
+
output: Array.isArray(task.output_preview) && task.output_preview.length > 0 ? task.output_preview.join("\n") : void 0
|
|
4360
|
+
};
|
|
4361
|
+
}
|
|
4362
|
+
|
|
3600
4363
|
// src/shared/auth-busy.ts
|
|
3601
4364
|
var DEFAULT_RETRY_AFTER_SECONDS = 1;
|
|
3602
4365
|
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
@@ -4013,15 +4776,8 @@ function contentPreview(content, maxLen = 80) {
|
|
|
4013
4776
|
const text = getTextContent(content).trim();
|
|
4014
4777
|
return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
|
|
4015
4778
|
}
|
|
4016
|
-
function transformSlashCommand(skillName, rawInput,
|
|
4017
|
-
|
|
4018
|
-
if (local) {
|
|
4019
|
-
return prompt ? `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1
|
|
4020
|
-
${prompt}` : `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1`;
|
|
4021
|
-
}
|
|
4022
|
-
const steps = installed ? `\u8BF7\u5148\u8FD0\u884C blade hub skill use ${skillName} \u52A0\u8F7D\u6280\u80FD` : `\u8BE5\u6280\u80FD\u5C1A\u672A\u5B89\u88C5\u3002\u8BF7\u5148\u8FD0\u884C blade hub skill install ${skillName} \u5B89\u88C5\uFF0C\u518D\u8FD0\u884C blade hub skill use ${skillName} \u52A0\u8F7D`;
|
|
4023
|
-
return prompt ? `${steps}\uFF0C\u7136\u540E\u5B8C\u6210\u4EFB\u52A1\uFF1A
|
|
4024
|
-
${prompt}` : `${steps}\uFF0C\u7136\u540E\u4F7F\u7528\u5B83\u5B8C\u6210\u4EFB\u52A1`;
|
|
4779
|
+
function transformSlashCommand(skillName, rawInput, _availability = {}) {
|
|
4780
|
+
return rawInput.replace(`<skill>${skillName}</skill>`, `@${skillName}`).trim();
|
|
4025
4781
|
}
|
|
4026
4782
|
var ATTACHMENT_TAG_RE = /\[附件:\s*([^\]]+)\](?:[ \t]*已上传到工作区路径:[ \t]*([^\r\n]+))?/g;
|
|
4027
4783
|
var CONTEXT_PART_RE = /^\[上下文:\s*([^\]]+)\]\n([\s\S]*)$/;
|
|
@@ -4149,7 +4905,13 @@ function workflowEntriesFromOutput(output) {
|
|
|
4149
4905
|
}
|
|
4150
4906
|
function workflowNameFromArguments(argumentsJson) {
|
|
4151
4907
|
try {
|
|
4152
|
-
const
|
|
4908
|
+
const args = JSON.parse(argumentsJson);
|
|
4909
|
+
const skillId = typeof args?.skill_id === "string" ? args.skill_id.trim() : "";
|
|
4910
|
+
if (skillId) {
|
|
4911
|
+
const flow = typeof args?.flow === "string" ? args.flow.trim() : "";
|
|
4912
|
+
return `\u6280\u80FD\u300C${skillId}\u300D${flow && flow !== "main" ? ` \xB7 \u6D41\u7A0B ${flow}` : ""}`;
|
|
4913
|
+
}
|
|
4914
|
+
const name = args?.name;
|
|
4153
4915
|
return typeof name === "string" ? name : "";
|
|
4154
4916
|
} catch {
|
|
4155
4917
|
return "";
|
|
@@ -4173,6 +4935,15 @@ function applyWorkflowEvent(current, toolCallId, type2, payload) {
|
|
|
4173
4935
|
}
|
|
4174
4936
|
const base = current ?? { toolCallId, name: "", status: "running", entries: [] };
|
|
4175
4937
|
if (type2 === "workflow:step") {
|
|
4938
|
+
const settled = asText(payload.status);
|
|
4939
|
+
const stepId = asText(payload.step_id);
|
|
4940
|
+
if (stepId && isFinished(settled)) {
|
|
4941
|
+
const index = base.entries.findIndex((entry) => entry.kind === "step" && entry.stepId === stepId);
|
|
4942
|
+
if (index === -1) return null;
|
|
4943
|
+
const entries = [...base.entries];
|
|
4944
|
+
entries[index] = { ...entries[index], status: settled };
|
|
4945
|
+
return { ...base, entries };
|
|
4946
|
+
}
|
|
4176
4947
|
return {
|
|
4177
4948
|
...base,
|
|
4178
4949
|
entries: [
|
|
@@ -4187,7 +4958,9 @@ function applyWorkflowEvent(current, toolCallId, type2, payload) {
|
|
|
4187
4958
|
};
|
|
4188
4959
|
}
|
|
4189
4960
|
if (type2 === "workflow:log") {
|
|
4190
|
-
|
|
4961
|
+
const stepId = asText(payload.step_id);
|
|
4962
|
+
const entry = stepId ? { kind: "log", text: asText(payload.text), stepId } : { kind: "log", text: asText(payload.text) };
|
|
4963
|
+
return { ...base, entries: [...base.entries, entry] };
|
|
4191
4964
|
}
|
|
4192
4965
|
if (type2 === "workflow:run:end") {
|
|
4193
4966
|
const status = asText(payload.status);
|
|
@@ -4209,10 +4982,10 @@ function rebuildWorkflowRuns(turns, previous) {
|
|
|
4209
4982
|
const name = workflowNameFromArguments(toolCall.arguments);
|
|
4210
4983
|
const prior = previous[toolCall.id];
|
|
4211
4984
|
if (isFinished(toolCall.status)) {
|
|
4212
|
-
const
|
|
4213
|
-
|
|
4214
|
-
);
|
|
4215
|
-
runs[toolCall.id] = prior && prior.status ===
|
|
4985
|
+
const steps = toolCall.workflow_steps ?? [];
|
|
4986
|
+
const entries = steps.length > 0 ? workflowEntriesFromSteps(steps) : workflowEntriesFromOutput(typeof toolCall.result === "string" ? toolCall.result.trim() : "");
|
|
4987
|
+
const status = toolCall.status === "error" && workflowRunCancelled(steps) ? "cancelled" : toolCall.status;
|
|
4988
|
+
runs[toolCall.id] = prior && prior.status === status && prior.name === name && JSON.stringify(prior.entries) === JSON.stringify(entries) ? prior : { toolCallId: toolCall.id, name, status, entries };
|
|
4216
4989
|
continue;
|
|
4217
4990
|
}
|
|
4218
4991
|
runs[toolCall.id] = prior && prior.name === name ? prior : { toolCallId: toolCall.id, name, status: prior?.status ?? "running", entries: prior?.entries ?? [] };
|
|
@@ -4463,7 +5236,9 @@ function projectionToMessage(turn) {
|
|
|
4463
5236
|
display_name: toolCall.display_name,
|
|
4464
5237
|
arguments: toolCall.arguments,
|
|
4465
5238
|
result: toolCall.result ?? void 0,
|
|
5239
|
+
...toolCall.evidence ? { evidence: toolCall.evidence } : {},
|
|
4466
5240
|
pending_question_ref: toolCall.pending_question_ref ?? void 0,
|
|
5241
|
+
workflow_steps: toolCall.workflow_steps ?? void 0,
|
|
4467
5242
|
status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending",
|
|
4468
5243
|
...typeof toolCall.duration_ms === "number" ? { duration_ms: toolCall.duration_ms } : {}
|
|
4469
5244
|
})) : void 0;
|
|
@@ -4478,10 +5253,14 @@ function projectionToMessage(turn) {
|
|
|
4478
5253
|
...toolCalls ? { tool_calls: toolCalls } : {},
|
|
4479
5254
|
loop_name: turn.loop_id,
|
|
4480
5255
|
entry_id: turn.turn_id,
|
|
5256
|
+
...turn.client_request_id ? { client_request_id: turn.client_request_id } : {},
|
|
4481
5257
|
status: turn.status,
|
|
4482
5258
|
...typeof turn.duration_ms === "number" ? { duration_ms: turn.duration_ms } : {},
|
|
4483
5259
|
...turn.started_at ? { timestamp: turn.started_at } : {},
|
|
4484
|
-
...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {}
|
|
5260
|
+
...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {},
|
|
5261
|
+
...turn.refs?.length ? { refs: turn.refs } : {},
|
|
5262
|
+
...turn.context_attachments?.length ? { context_attachments: turn.context_attachments } : {},
|
|
5263
|
+
...turn.topic_asset_refs?.length ? { topic_asset_refs: turn.topic_asset_refs } : {}
|
|
4485
5264
|
};
|
|
4486
5265
|
}
|
|
4487
5266
|
var messageProjectionCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -4708,8 +5487,6 @@ function withTurns(state, turns) {
|
|
|
4708
5487
|
activeCompaction,
|
|
4709
5488
|
askAnswers,
|
|
4710
5489
|
mode: latestMode ?? state.mode,
|
|
4711
|
-
// 历史恢复时 chat:end(paused) 可能已不在 socket replay 缓存中;
|
|
4712
|
-
// awaiting_answer 是投影里的持久权威状态,必须据此恢复问答交互。
|
|
4713
5490
|
status: isWaitingForInput ? "waiting_for_input" : state.status
|
|
4714
5491
|
};
|
|
4715
5492
|
}
|
|
@@ -4718,17 +5495,17 @@ function withQueue(state, snapshot2) {
|
|
|
4718
5495
|
return { ...state, queue: snapshot2 };
|
|
4719
5496
|
}
|
|
4720
5497
|
var localTurnCounter = 0;
|
|
4721
|
-
function addUserMessage(state, content, clientRequestId) {
|
|
5498
|
+
function addUserMessage(state, content, clientRequestId, topicAssetRefs, refs, contextAttachments) {
|
|
4722
5499
|
localTurnCounter += 1;
|
|
4723
5500
|
const turnId = `local-user-${clientRequestId ?? `${Date.now()}-${localTurnCounter}`}`;
|
|
4724
5501
|
const optimisticTurn = {
|
|
4725
5502
|
id: turnId,
|
|
4726
5503
|
sequence: Math.max(0, ...state.turns.map((turn) => turn.sequence)) + 1,
|
|
4727
5504
|
turn_id: turnId,
|
|
4728
|
-
// 乐观态就自赋,跟服务端确认后 reconcileUserTurns 补上的
|
|
4729
|
-
// client_render_id(= 乐观 turn 自己的 turn_id)保持同一个值,
|
|
4730
|
-
// 消费方才能拿它当"确认前后不变"的稳定标识使用。
|
|
4731
5505
|
client_render_id: turnId,
|
|
5506
|
+
...refs ? { refs } : {},
|
|
5507
|
+
...contextAttachments?.length ? { context_attachments: contextAttachments } : {},
|
|
5508
|
+
...topicAssetRefs?.length ? { topic_asset_refs: topicAssetRefs } : {},
|
|
4732
5509
|
loop_id: "root",
|
|
4733
5510
|
role: "user",
|
|
4734
5511
|
status: "streaming",
|
|
@@ -4912,6 +5689,9 @@ function sessionStatusFromChatEnd(status) {
|
|
|
4912
5689
|
if (status === "failed") return "failed";
|
|
4913
5690
|
return "completed";
|
|
4914
5691
|
}
|
|
5692
|
+
function isCompactionFinished(type2) {
|
|
5693
|
+
return type2 === "compaction:end" || type2 === "compaction:error" || type2 === "compaction:abort";
|
|
5694
|
+
}
|
|
4915
5695
|
function extractTerminalChatEndStatus(events) {
|
|
4916
5696
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
4917
5697
|
const event = events[i];
|
|
@@ -4922,11 +5702,10 @@ function extractTerminalChatEndStatus(events) {
|
|
|
4922
5702
|
return null;
|
|
4923
5703
|
}
|
|
4924
5704
|
var AgentSession = class _AgentSession {
|
|
5705
|
+
logCursor = new LogCursor();
|
|
4925
5706
|
sessionId;
|
|
4926
5707
|
state;
|
|
4927
|
-
/** status 每变化一次加一(单调递增)。异步读取会话详情时据此判断请求期间状态是否变过,避免 running→终态→running 这类来回被误认为「没变」。 */
|
|
4928
5708
|
statusRevision = 0;
|
|
4929
|
-
/** 实时事件写入 status 时调用:值不变也要记一次,才能识别请求期间收到过更新的状态。 */
|
|
4930
5709
|
noteLiveStatus() {
|
|
4931
5710
|
this.statusRevision += 1;
|
|
4932
5711
|
}
|
|
@@ -4934,21 +5713,17 @@ var AgentSession = class _AgentSession {
|
|
|
4934
5713
|
emitter = new Emitter();
|
|
4935
5714
|
commands = new CommandRegistry();
|
|
4936
5715
|
runtime;
|
|
5716
|
+
activeReplayPlaybackId = null;
|
|
4937
5717
|
projectionBuilder = new ClientProjectionBuilder();
|
|
4938
5718
|
pendingTurnPatches = /* @__PURE__ */ new Map();
|
|
4939
5719
|
patchFlushHandle = null;
|
|
4940
5720
|
patchFlushCancel = null;
|
|
4941
5721
|
joined = false;
|
|
4942
5722
|
joinPending = null;
|
|
4943
|
-
// bootstrap 与每次 reconnect 共用一条订阅生命周期队列。它们都会重置 join
|
|
4944
|
-
// 状态并临时设置 replay source,禁止并发,否则较早操作的 finally 会清掉
|
|
4945
|
-
// 较晚操作仍在使用的来源上下文。
|
|
4946
5723
|
subscriptionQueue = Promise.resolve();
|
|
4947
5724
|
replaceQueue = Promise.resolve();
|
|
4948
5725
|
timelineEpoch = 0;
|
|
4949
|
-
/** 定位请求的流水号:被后一次选择取代的那次定位不再提交窗口(见 locateEntry)。 */
|
|
4950
5726
|
locateToken = 0;
|
|
4951
|
-
/** 在途那次定位的请求句柄。**只**服务定位:权威刷新不共用它(见 cancelPendingLocate)。 */
|
|
4952
5727
|
locateAbort = null;
|
|
4953
5728
|
pagingGeneration = 0;
|
|
4954
5729
|
liveRevision = 0;
|
|
@@ -4957,14 +5732,7 @@ var AgentSession = class _AgentSession {
|
|
|
4957
5732
|
pendingOptimisticTurnIds = /* @__PURE__ */ new Set();
|
|
4958
5733
|
olderPending = null;
|
|
4959
5734
|
replaceInFlight = 0;
|
|
4960
|
-
// 回放控制请求排成一条队列。连着点"5x"再点"退出回放"时,并发发出去的两个
|
|
4961
|
-
// 请求响应可能乱序回来,晚到的旧值会把已经退出的会话又显示成回放中;用
|
|
4962
|
-
// 序号丢弃过期响应又会在失败时留下中间态。串行执行就没有这些情况:每个
|
|
4963
|
-
// 响应都是当时最新的,失败也只影响自己。
|
|
4964
5735
|
replayQueue = Promise.resolve();
|
|
4965
|
-
// Historical snapshots and live commands share one delivery barrier. A
|
|
4966
|
-
// repair closes the barrier before joining, snapshots the raw command log
|
|
4967
|
-
// after the join, then releases buffered commands in socket arrival order.
|
|
4968
5736
|
commandRestoreQueue = Promise.resolve();
|
|
4969
5737
|
commandRecoveryGeneration = 0;
|
|
4970
5738
|
commandRestoreRetryAttempt = 0;
|
|
@@ -4972,42 +5740,35 @@ var AgentSession = class _AgentSession {
|
|
|
4972
5740
|
bufferedCommandDeliveries = null;
|
|
4973
5741
|
pendingReplayMessage = null;
|
|
4974
5742
|
pendingReplayMode;
|
|
4975
|
-
|
|
5743
|
+
pendingReplayRequestId;
|
|
5744
|
+
pendingReplayAttachments;
|
|
5745
|
+
pendingReplayRefs;
|
|
5746
|
+
pendingReplayTopicAssetRefs;
|
|
4976
5747
|
recentChatEndAt = 0;
|
|
4977
|
-
/**
|
|
4978
|
-
* 最近一次已经通过 `error` 事件投递过的服务端失败(`message` + `code`)。
|
|
4979
|
-
*
|
|
4980
|
-
* `chat:send` 被代理时服务端会先发 `system:error`、再用同一条失败回 ack,两条
|
|
4981
|
-
* 通道都会到达这里:前者走 `_handleSystemError`,后者走发送失败的回滚。不去重
|
|
4982
|
-
* 的话调用方会收到两次同一个 `error`(重复提示、重试逻辑跑两遍)。
|
|
4983
|
-
*
|
|
4984
|
-
* 带代际边界,只认「本次 `emitWithAck` 等待期间」投递的那一条:否则队列/停止
|
|
4985
|
-
* 失败留下的标记会一直挂着,之后一次无关的订阅失败只是文案码相同就被当成
|
|
4986
|
-
* 重复而静音。用单调递增的代际号而不是时间戳,同一毫秒内的两条也不会混淆。
|
|
4987
|
-
*/
|
|
4988
5748
|
lastDeliveredSystemError = null;
|
|
4989
|
-
/** 每次投递 `system:error` 递增,用于判定某条失败是否属于本次等待。 */
|
|
4990
5749
|
systemErrorGeneration = 0;
|
|
4991
5750
|
subscriptionReplaySource = null;
|
|
4992
5751
|
disposed = false;
|
|
4993
5752
|
getAppContext = null;
|
|
4994
5753
|
sendPending = false;
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
5754
|
+
manualCompactionRequest = null;
|
|
5755
|
+
epochReloadRequested = 0;
|
|
5756
|
+
epochReloadDone = 0;
|
|
5757
|
+
epochReloadRunning = false;
|
|
5758
|
+
setManualCompactionRequest(request) {
|
|
5759
|
+
this.manualCompactionRequest = request;
|
|
5760
|
+
const pending = request !== null;
|
|
4999
5761
|
if (this.state.compactionPending !== pending) this.update((s) => ({ ...s, compactionPending: pending }));
|
|
5000
5762
|
}
|
|
5001
|
-
|
|
5763
|
+
handleManualCompactionFinished(event) {
|
|
5764
|
+
const request = this.manualCompactionRequest;
|
|
5765
|
+
const runId = event.payload.run_id;
|
|
5766
|
+
if (!request || !isCompactionFinished(event.type) || typeof runId !== "string" || !runId) return;
|
|
5767
|
+
if (request.runId === null) request.finishedBeforeAck.add(runId);
|
|
5768
|
+
else if (request.runId === runId) this.setManualCompactionRequest(null);
|
|
5769
|
+
}
|
|
5002
5770
|
pendingChatRequestId = null;
|
|
5003
|
-
/** 会话详情给出的运行时类型;"native" 时启用原生智能体的投影规则(见 _handleTurnEvents / _handleChatEnd)。 */
|
|
5004
|
-
runtimeType = null;
|
|
5005
|
-
/** workflow_run_id → 触发它的 workflow:run 工具调用 id(只有 run:start 事件带工具调用 id)。 */
|
|
5006
5771
|
workflowRunToolCalls = /* @__PURE__ */ new Map();
|
|
5007
|
-
/**
|
|
5008
|
-
* 第一方 Web 的精简模式传输优化:订阅时是否要 token 级事件。null 表示宿主没设置,订阅时不带这个字段。
|
|
5009
|
-
* `desiredStreamTokens` 是宿主最新的选择;运行中不切换,等这一轮结束再重订阅。
|
|
5010
|
-
*/
|
|
5011
5772
|
desiredStreamTokens = null;
|
|
5012
5773
|
joinedStreamTokens = null;
|
|
5013
5774
|
deliveredCommandKeys = /* @__PURE__ */ new Set();
|
|
@@ -5045,11 +5806,8 @@ var AgentSession = class _AgentSession {
|
|
|
5045
5806
|
emitWithAck: async () => readonlyError(),
|
|
5046
5807
|
ensureConnected: readonlyError,
|
|
5047
5808
|
fetchTurnsPage: async () => ({ turns, nextBefore: null, currentMode: "executing" }),
|
|
5048
|
-
// 静态会话(分享页)没有会话目录接口,宿主用已加载的历史兜底条目。
|
|
5049
5809
|
fetchTurnIndex: async () => ({ sessionId, items: [] }),
|
|
5050
5810
|
fetchCommands: async () => [],
|
|
5051
|
-
// 静态历史没有回放身份可言,给个空详情即可,不要抛错——_bootstrap
|
|
5052
|
-
// 不会跑到这里,但保持只读语义一致。
|
|
5053
5811
|
fetchSessionInfo: async () => ({ replay_state: null }),
|
|
5054
5812
|
updateReplay: async () => readonlyError(),
|
|
5055
5813
|
listDir: async () => readonlyError(),
|
|
@@ -5068,7 +5826,6 @@ var AgentSession = class _AgentSession {
|
|
|
5068
5826
|
};
|
|
5069
5827
|
return session;
|
|
5070
5828
|
}
|
|
5071
|
-
// ---------------------------------------------------------------- 状态订阅
|
|
5072
5829
|
/** 返回当前会话快照。引用稳定:状态未变化时返回同一对象。 */
|
|
5073
5830
|
getState() {
|
|
5074
5831
|
return this.state;
|
|
@@ -5155,6 +5912,11 @@ var AgentSession = class _AgentSession {
|
|
|
5155
5912
|
* chat:end 会让界面停在「运行中」)。
|
|
5156
5913
|
*/
|
|
5157
5914
|
async reloadHistory() {
|
|
5915
|
+
const replaced = await this.reloadHistoryWindow();
|
|
5916
|
+
await this.refreshRunningState().catch(() => void 0);
|
|
5917
|
+
return replaced;
|
|
5918
|
+
}
|
|
5919
|
+
async reloadHistoryWindow() {
|
|
5158
5920
|
if (this.disposed) return false;
|
|
5159
5921
|
this.invalidateTimeline();
|
|
5160
5922
|
this.invalidateOlderPaging();
|
|
@@ -5164,9 +5926,7 @@ var AgentSession = class _AgentSession {
|
|
|
5164
5926
|
loadingOlder: false,
|
|
5165
5927
|
timelineRevision: state.timelineRevision + 1
|
|
5166
5928
|
}));
|
|
5167
|
-
|
|
5168
|
-
await this.refreshRunningState().catch(() => void 0);
|
|
5169
|
-
return replaced;
|
|
5929
|
+
return this.replaceFromHistory();
|
|
5170
5930
|
}
|
|
5171
5931
|
/**
|
|
5172
5932
|
* 向服务端确认会话此刻是否在运行,校准流式状态(残留的「运行中」会被清掉)。只在已加入房间时
|
|
@@ -5187,11 +5947,6 @@ var AgentSession = class _AgentSession {
|
|
|
5187
5947
|
if (this.disposed || !response || response.ok === false || typeof response.is_running !== "boolean") return;
|
|
5188
5948
|
this._applyRunningState(response.is_running, liveRevision);
|
|
5189
5949
|
}
|
|
5190
|
-
/**
|
|
5191
|
-
* timeline 被整体改写(rewind / checkout / 释放):推进纪元,并把在途定位请求一并中止
|
|
5192
|
-
* ——它已经属于旧 timeline,却还占着替换队列,会让改写后的权威窗口排在后面等它跑完。
|
|
5193
|
-
* 作废号仍然保留:中止只是让它更早结束。
|
|
5194
|
-
*/
|
|
5195
5950
|
invalidateTimeline() {
|
|
5196
5951
|
this.timelineEpoch += 1;
|
|
5197
5952
|
this.locateAbort?.abort();
|
|
@@ -5229,7 +5984,6 @@ var AgentSession = class _AgentSession {
|
|
|
5229
5984
|
update(fn) {
|
|
5230
5985
|
this.setState(fn(this.state));
|
|
5231
5986
|
}
|
|
5232
|
-
// ---------------------------------------------------------------- 语义事件
|
|
5233
5987
|
/** 监听语义事件(底层逃生舱,见 events.ts)。返回取消函数。 */
|
|
5234
5988
|
on(name, handler) {
|
|
5235
5989
|
return this.emitter.on(name, handler);
|
|
@@ -5246,7 +6000,6 @@ var AgentSession = class _AgentSession {
|
|
|
5246
6000
|
onCommand(action, handler) {
|
|
5247
6001
|
return this.commands.onCommand(action, handler);
|
|
5248
6002
|
}
|
|
5249
|
-
// ---------------------------------------------------------------- 动作
|
|
5250
6003
|
/**
|
|
5251
6004
|
* 声明当前应用提供的页面上下文和业务 CLI。
|
|
5252
6005
|
*
|
|
@@ -5257,10 +6010,78 @@ var AgentSession = class _AgentSession {
|
|
|
5257
6010
|
this.getAppContext = options.getContext;
|
|
5258
6011
|
return attachment;
|
|
5259
6012
|
}
|
|
6013
|
+
/** Prepare a durable text input without sending any network request. */
|
|
6014
|
+
prepareSend(content, options = {}) {
|
|
6015
|
+
if (!content.trim()) throw new Error("\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A");
|
|
6016
|
+
if (Object.keys(options).some((key) => !["mode", "model", "thinkingOverride", "reasoningLevel", "contextAttachments"].includes(key))) {
|
|
6017
|
+
throw new Error("\u53EF\u9760\u6587\u672C\u53D1\u9001\u4E0D\u652F\u6301\u8FD9\u4E9B\u53C2\u6570\uFF0C\u8BF7\u4F7F\u7528 send()");
|
|
6018
|
+
}
|
|
6019
|
+
const snapshot2 = JSON.parse(JSON.stringify(options));
|
|
6020
|
+
if (snapshot2.contextAttachments) snapshot2.contextAttachments = snapshotContextAttachments(snapshot2.contextAttachments);
|
|
6021
|
+
return { sessionId: this.sessionId, requestId: createClientRequestId(), content, options: snapshot2 };
|
|
6022
|
+
}
|
|
6023
|
+
/** Durably enqueue an ordinary text input. Retries must reuse the saved request. */
|
|
6024
|
+
async sendPrepared(request) {
|
|
6025
|
+
if (request.sessionId !== this.sessionId) throw new Error("\u53D1\u9001\u8BF7\u6C42\u4E0D\u5C5E\u4E8E\u5F53\u524D\u4F1A\u8BDD");
|
|
6026
|
+
try {
|
|
6027
|
+
this.runtime.ensureConnected();
|
|
6028
|
+
const response = await this.runtime.emitWithAck("chat:append", {
|
|
6029
|
+
session_id: this.sessionId,
|
|
6030
|
+
client_request_id: request.requestId,
|
|
6031
|
+
message: request.content,
|
|
6032
|
+
prepared_options: {
|
|
6033
|
+
mode: request.options.mode,
|
|
6034
|
+
model: request.options.model,
|
|
6035
|
+
thinking_override: request.options.thinkingOverride,
|
|
6036
|
+
reasoning_level: request.options.reasoningLevel,
|
|
6037
|
+
...request.options.contextAttachments ? { context_attachments: request.options.contextAttachments } : {}
|
|
6038
|
+
}
|
|
6039
|
+
}, QUEUE_ACK_TIMEOUT_MS);
|
|
6040
|
+
const result = parseQueueAck(response, this.sessionId);
|
|
6041
|
+
if (result.snapshot) this.update((state) => withQueue(state, result.snapshot));
|
|
6042
|
+
if (!result.ok) return {
|
|
6043
|
+
requestId: request.requestId,
|
|
6044
|
+
status: response?.status === "conflict" ? "rejected" : "unknown",
|
|
6045
|
+
message: result.message ?? void 0
|
|
6046
|
+
};
|
|
6047
|
+
const delivery = response?.delivery;
|
|
6048
|
+
if (!isRecord(delivery) || delivery.status !== "accepted") return { requestId: request.requestId, status: "unknown" };
|
|
6049
|
+
return {
|
|
6050
|
+
requestId: request.requestId,
|
|
6051
|
+
status: "accepted",
|
|
6052
|
+
itemId: typeof delivery.item_id === "string" ? delivery.item_id : void 0,
|
|
6053
|
+
queueStatus: typeof delivery.queue_status === "string" ? delivery.queue_status : void 0,
|
|
6054
|
+
executionId: typeof delivery.execution_id === "string" ? delivery.execution_id : null
|
|
6055
|
+
};
|
|
6056
|
+
} catch {
|
|
6057
|
+
return { requestId: request.requestId, status: "unknown" };
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6060
|
+
/** Owner-authorized lookup; receipts remain until the session is deleted. */
|
|
6061
|
+
async getDelivery(requestId) {
|
|
6062
|
+
try {
|
|
6063
|
+
this.runtime.ensureConnected();
|
|
6064
|
+
const response = await this.runtime.emitWithAck("chat:queue:request", {
|
|
6065
|
+
session_id: this.sessionId,
|
|
6066
|
+
client_request_id: requestId
|
|
6067
|
+
}, QUEUE_ACK_TIMEOUT_MS);
|
|
6068
|
+
if (response?.status === "accepted") return {
|
|
6069
|
+
requestId,
|
|
6070
|
+
status: "accepted",
|
|
6071
|
+
itemId: typeof response.item_id === "string" ? response.item_id : void 0,
|
|
6072
|
+
queueStatus: typeof response.queue_status === "string" ? response.queue_status : void 0,
|
|
6073
|
+
executionId: typeof response.execution_id === "string" ? response.execution_id : null
|
|
6074
|
+
};
|
|
6075
|
+
if (response?.status === "not_found") return { requestId, status: "not_found" };
|
|
6076
|
+
return { requestId, status: "unknown", message: typeof response?.message === "string" ? response.message : void 0 };
|
|
6077
|
+
} catch {
|
|
6078
|
+
return { requestId, status: "unknown" };
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
5260
6081
|
/** 发送一条消息。字符串或多模态内容均可。 */
|
|
5261
6082
|
async send(content, options = {}) {
|
|
5262
6083
|
this.runtime.ensureConnected();
|
|
5263
|
-
if (this.state.
|
|
6084
|
+
if (this.state.activeCompaction || this.sendPending || this.manualCompactionRequest) {
|
|
5264
6085
|
this.update(
|
|
5265
6086
|
(s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002")
|
|
5266
6087
|
);
|
|
@@ -5369,10 +6190,6 @@ var AgentSession = class _AgentSession {
|
|
|
5369
6190
|
async refreshQueue() {
|
|
5370
6191
|
return this.runQueueCommand("chat:queue:list", { session_id: this.sessionId });
|
|
5371
6192
|
}
|
|
5372
|
-
/**
|
|
5373
|
-
* 发出一条队列命令并安装 ack 里的快照。
|
|
5374
|
-
* 传输层失败(超时 / 断线)会 reject,由调用方决定是否重试。
|
|
5375
|
-
*/
|
|
5376
6193
|
async requestQueueAck(event, payload) {
|
|
5377
6194
|
this.runtime.ensureConnected();
|
|
5378
6195
|
const response = await this.runtime.emitWithAck(event, payload, QUEUE_ACK_TIMEOUT_MS);
|
|
@@ -5381,7 +6198,6 @@ var AgentSession = class _AgentSession {
|
|
|
5381
6198
|
if (snapshot2) this.update((state) => withQueue(state, snapshot2));
|
|
5382
6199
|
return result;
|
|
5383
6200
|
}
|
|
5384
|
-
/** 队列命令的对外入口:传输失败只体现为失败结果,不向调用方抛异常。 */
|
|
5385
6201
|
async runQueueCommand(event, payload) {
|
|
5386
6202
|
try {
|
|
5387
6203
|
return await this.requestQueueAck(event, payload);
|
|
@@ -5390,15 +6206,16 @@ var AgentSession = class _AgentSession {
|
|
|
5390
6206
|
}
|
|
5391
6207
|
}
|
|
5392
6208
|
async sendUnlocked(content, options) {
|
|
6209
|
+
const contextAttachments = options.contextAttachments ? snapshotContextAttachments(options.contextAttachments) : void 0;
|
|
5393
6210
|
await this.ensureIdleBeforeSend();
|
|
5394
|
-
if (this.state.
|
|
6211
|
+
if (this.state.activeCompaction || this.manualCompactionRequest) {
|
|
5395
6212
|
this.update(
|
|
5396
6213
|
(s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002")
|
|
5397
6214
|
);
|
|
5398
6215
|
return false;
|
|
5399
6216
|
}
|
|
5400
6217
|
let appContext;
|
|
5401
|
-
if (this.getAppContext) {
|
|
6218
|
+
if (this.getAppContext && !this.state.isStreaming) {
|
|
5402
6219
|
try {
|
|
5403
6220
|
const rawContext = await this.getAppContext();
|
|
5404
6221
|
if (!isRecord(rawContext)) {
|
|
@@ -5426,7 +6243,7 @@ var AgentSession = class _AgentSession {
|
|
|
5426
6243
|
let optimisticAskAnswer;
|
|
5427
6244
|
let previousAskAnswer;
|
|
5428
6245
|
this.update((state) => {
|
|
5429
|
-
const next = addUserMessage(state, content, clientRequestId);
|
|
6246
|
+
const next = addUserMessage(state, content, clientRequestId, options.topicAssetRefs, options.refs, contextAttachments);
|
|
5430
6247
|
optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
|
|
5431
6248
|
return next;
|
|
5432
6249
|
});
|
|
@@ -5444,7 +6261,10 @@ var AgentSession = class _AgentSession {
|
|
|
5444
6261
|
if (!options.replayDecision) {
|
|
5445
6262
|
this.pendingReplayMessage = content;
|
|
5446
6263
|
this.pendingReplayMode = options.mode;
|
|
5447
|
-
this.
|
|
6264
|
+
this.pendingReplayRequestId = clientRequestId;
|
|
6265
|
+
this.pendingReplayAttachments = contextAttachments;
|
|
6266
|
+
this.pendingReplayRefs = options.refs;
|
|
6267
|
+
this.pendingReplayTopicAssetRefs = options.topicAssetRefs;
|
|
5448
6268
|
}
|
|
5449
6269
|
this.pendingChatRequestId = clientRequestId;
|
|
5450
6270
|
const payload = {
|
|
@@ -5459,8 +6279,9 @@ var AgentSession = class _AgentSession {
|
|
|
5459
6279
|
whatif: options.whatif,
|
|
5460
6280
|
replay_decision: options.replayDecision,
|
|
5461
6281
|
app_context: appContext,
|
|
5462
|
-
|
|
5463
|
-
|
|
6282
|
+
context_attachments: contextAttachments,
|
|
6283
|
+
refs: options.refs,
|
|
6284
|
+
topic_asset_refs: options.topicAssetRefs,
|
|
5464
6285
|
queue_when_running: options.queueWhenRunning === true
|
|
5465
6286
|
};
|
|
5466
6287
|
try {
|
|
@@ -5502,6 +6323,14 @@ var AgentSession = class _AgentSession {
|
|
|
5502
6323
|
typeof response?.code === "string" ? response.code : void 0
|
|
5503
6324
|
);
|
|
5504
6325
|
}
|
|
6326
|
+
if (optimisticTurnId) {
|
|
6327
|
+
this.update((state) => {
|
|
6328
|
+
const next = withTurns(state, state.turns.map(
|
|
6329
|
+
(turn) => turn.turn_id === optimisticTurnId ? { ...turn, status: "completed" } : turn
|
|
6330
|
+
));
|
|
6331
|
+
return { ...next, askAnswers: state.askAnswers };
|
|
6332
|
+
});
|
|
6333
|
+
}
|
|
5505
6334
|
return true;
|
|
5506
6335
|
} catch (error) {
|
|
5507
6336
|
const failure = describeSessionError(error, "\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
|
|
@@ -5536,14 +6365,16 @@ var AgentSession = class _AgentSession {
|
|
|
5536
6365
|
}
|
|
5537
6366
|
this.emitter.emit("error", { message, ...code ? { code } : {} });
|
|
5538
6367
|
}
|
|
5539
|
-
/** 撤回一次乐观提交:移除乐观消息、恢复提问作答、清掉回放草稿。 */
|
|
5540
6368
|
discardOptimisticSend(optimisticTurnId, askUserAnswer, optimisticAskAnswer, previousAskAnswer) {
|
|
5541
6369
|
if (optimisticTurnId && this.pendingChatRequestId && optimisticTurnId === `local-user-${this.pendingChatRequestId}`) {
|
|
5542
6370
|
this.pendingChatRequestId = null;
|
|
5543
6371
|
}
|
|
5544
6372
|
this.pendingReplayMessage = null;
|
|
5545
6373
|
this.pendingReplayMode = void 0;
|
|
5546
|
-
this.
|
|
6374
|
+
this.pendingReplayRequestId = void 0;
|
|
6375
|
+
this.pendingReplayAttachments = void 0;
|
|
6376
|
+
this.pendingReplayRefs = void 0;
|
|
6377
|
+
this.pendingReplayTopicAssetRefs = void 0;
|
|
5547
6378
|
this.update((current) => {
|
|
5548
6379
|
const filtered = optimisticTurnId ? current.turns.filter((turn) => turn.turn_id !== optimisticTurnId) : current.turns;
|
|
5549
6380
|
let next = optimisticTurnId ? withTurns({ ...current, turns: filtered }, filtered) : current;
|
|
@@ -5565,22 +6396,62 @@ var AgentSession = class _AgentSession {
|
|
|
5565
6396
|
}
|
|
5566
6397
|
}
|
|
5567
6398
|
/** 停止当前回复。 */
|
|
5568
|
-
stop() {
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
return
|
|
5573
|
-
|
|
6399
|
+
async stop() {
|
|
6400
|
+
const request = this.manualCompactionRequest;
|
|
6401
|
+
if (request) {
|
|
6402
|
+
const ack = await request.ack;
|
|
6403
|
+
if (ack.status !== "accepted") return ack;
|
|
6404
|
+
if (this.manualCompactionRequest !== request) return { status: "idle" };
|
|
6405
|
+
return await this.runtime.emitWithAck("chat:stop", {
|
|
6406
|
+
session_id: this.sessionId,
|
|
6407
|
+
chat_run_id: request.runId
|
|
6408
|
+
}, 35e3) ?? {};
|
|
6409
|
+
}
|
|
6410
|
+
const response = await this.runtime.emitWithAck("chat:stop", { session_id: this.sessionId }, 35e3) ?? {};
|
|
6411
|
+
const cancelled = response.cancelled_client_request_ids;
|
|
6412
|
+
if (response.status === "stopped" && Array.isArray(cancelled) && this.pendingChatRequestId !== null && cancelled.includes(this.pendingChatRequestId)) {
|
|
6413
|
+
this.pendingChatRequestId = null;
|
|
6414
|
+
this.update((state) => ({ ...withoutOptimisticUserTurns(state, cancelled), isStreaming: false }));
|
|
6415
|
+
this.applyDesiredStreamTokens();
|
|
6416
|
+
}
|
|
6417
|
+
return response;
|
|
5574
6418
|
}
|
|
5575
6419
|
/** 手动触发上下文压缩。 */
|
|
5576
6420
|
compact() {
|
|
5577
|
-
if (this.state.isStreaming || this.state.activeCompaction || this.
|
|
6421
|
+
if (this.state.isStreaming || this.state.activeCompaction || this.manualCompactionRequest) {
|
|
5578
6422
|
return Promise.resolve({
|
|
5579
6423
|
status: "error",
|
|
5580
6424
|
message: "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002"
|
|
5581
6425
|
});
|
|
5582
6426
|
}
|
|
5583
|
-
|
|
6427
|
+
const request = {
|
|
6428
|
+
runId: null,
|
|
6429
|
+
finishedBeforeAck: /* @__PURE__ */ new Set(),
|
|
6430
|
+
ack: Promise.resolve().then(() => this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4)).then((response) => {
|
|
6431
|
+
const result = response ?? {};
|
|
6432
|
+
if (result.status !== "accepted") {
|
|
6433
|
+
if (this.manualCompactionRequest === request) this.setManualCompactionRequest(null);
|
|
6434
|
+
return result;
|
|
6435
|
+
}
|
|
6436
|
+
if (typeof result.run_id !== "string" || !result.run_id) {
|
|
6437
|
+
throw new AgentSessionError("\u6574\u7406\u8BF7\u6C42\u786E\u8BA4\u7F3A\u5C11 run_id\uFF0C\u65E0\u6CD5\u786E\u8BA4\u8FD0\u884C\u72B6\u6001");
|
|
6438
|
+
}
|
|
6439
|
+
request.runId = result.run_id;
|
|
6440
|
+
if (request.finishedBeforeAck.has(request.runId) && this.manualCompactionRequest === request) {
|
|
6441
|
+
this.setManualCompactionRequest(null);
|
|
6442
|
+
}
|
|
6443
|
+
request.finishedBeforeAck.clear();
|
|
6444
|
+
return result;
|
|
6445
|
+
}).catch((error) => {
|
|
6446
|
+
if (this.manualCompactionRequest === request) this.setManualCompactionRequest(null);
|
|
6447
|
+
throw error;
|
|
6448
|
+
}).finally(() => {
|
|
6449
|
+
if ((!this.manualCompactionRequest || this.manualCompactionRequest === request) && this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
|
|
6450
|
+
this.update((state) => ({ ...state, activeCompaction: null }));
|
|
6451
|
+
}
|
|
6452
|
+
})
|
|
6453
|
+
};
|
|
6454
|
+
this.setManualCompactionRequest(request);
|
|
5584
6455
|
this.update((state) => ({
|
|
5585
6456
|
...state,
|
|
5586
6457
|
activeCompaction: {
|
|
@@ -5590,18 +6461,7 @@ var AgentSession = class _AgentSession {
|
|
|
5590
6461
|
trigger: "manual"
|
|
5591
6462
|
}
|
|
5592
6463
|
}));
|
|
5593
|
-
return
|
|
5594
|
-
const result = response ?? {};
|
|
5595
|
-
if (result.status !== "accepted") this.setManualCompactionPending(false);
|
|
5596
|
-
return result;
|
|
5597
|
-
}).catch((error) => {
|
|
5598
|
-
this.setManualCompactionPending(false);
|
|
5599
|
-
throw error;
|
|
5600
|
-
}).finally(() => {
|
|
5601
|
-
if (this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
|
|
5602
|
-
this.update((state) => ({ ...state, activeCompaction: null }));
|
|
5603
|
-
}
|
|
5604
|
-
});
|
|
6464
|
+
return request.ack;
|
|
5605
6465
|
}
|
|
5606
6466
|
/**
|
|
5607
6467
|
* 把一段业务数据作为附件放进聊天输入框(比如地图上选中的点位)。
|
|
@@ -5654,22 +6514,15 @@ var AgentSession = class _AgentSession {
|
|
|
5654
6514
|
get isDisposed() {
|
|
5655
6515
|
return this.disposed;
|
|
5656
6516
|
}
|
|
5657
|
-
// ------------------------------------------------------- Hub 回调(内部)
|
|
5658
|
-
/** @internal 连接状态变化(同 client 下所有会话共享一条连接)。 */
|
|
5659
6517
|
_setConnection(connection) {
|
|
5660
6518
|
if (this.state.connection === connection) return;
|
|
5661
6519
|
this.update((s) => ({ ...s, connection }));
|
|
5662
6520
|
}
|
|
5663
|
-
/**
|
|
5664
|
-
* @internal 服务端广播的权威队列快照。
|
|
5665
|
-
* 结构不合法或 revision 不新的一律丢弃,绝不用旧快照覆盖新状态。
|
|
5666
|
-
*/
|
|
5667
6521
|
_handleQueueUpdated(payload) {
|
|
5668
6522
|
if (!isSessionQueueSnapshot(payload)) return;
|
|
5669
6523
|
const snapshot2 = payload;
|
|
5670
6524
|
this.update((state) => withQueue(state, snapshot2));
|
|
5671
6525
|
}
|
|
5672
|
-
/** @internal socket 重连后由 Hub 调用:重新加入房间并补数。 */
|
|
5673
6526
|
_rejoinAfterReconnect() {
|
|
5674
6527
|
this.invalidateOlderPaging();
|
|
5675
6528
|
this.update((state) => ({ ...state, nextBefore: null, loadingOlder: false }));
|
|
@@ -5698,7 +6551,6 @@ var AgentSession = class _AgentSession {
|
|
|
5698
6551
|
}
|
|
5699
6552
|
});
|
|
5700
6553
|
}
|
|
5701
|
-
/** @internal 首次连接:加载历史 + 加入房间。 */
|
|
5702
6554
|
_bootstrap() {
|
|
5703
6555
|
const commandRecoveryGeneration = this.beginCommandRecovery();
|
|
5704
6556
|
return this.enqueueSubscription(async () => {
|
|
@@ -5709,7 +6561,6 @@ var AgentSession = class _AgentSession {
|
|
|
5709
6561
|
Promise.resolve().then(fetchDetail).catch(() => fetchDetail()).catch(() => null)
|
|
5710
6562
|
]);
|
|
5711
6563
|
if (this.disposed) return;
|
|
5712
|
-
if (detail) this.runtimeType = detail.runtime_type ?? null;
|
|
5713
6564
|
this.subscriptionReplaySource = "bootstrap_replay";
|
|
5714
6565
|
try {
|
|
5715
6566
|
await this.ensureJoined();
|
|
@@ -5723,17 +6574,14 @@ var AgentSession = class _AgentSession {
|
|
|
5723
6574
|
]);
|
|
5724
6575
|
});
|
|
5725
6576
|
}
|
|
5726
|
-
/** 订阅生命周期串行执行;一次失败只由自己的调用方观察,不截断后续重连。 */
|
|
5727
6577
|
enqueueSubscription(operation) {
|
|
5728
6578
|
const run = this.subscriptionQueue.then(operation);
|
|
5729
6579
|
this.subscriptionQueue = run.catch(() => void 0);
|
|
5730
6580
|
return run;
|
|
5731
6581
|
}
|
|
5732
|
-
/** 改回放是 owner 专属(服务端 PATCH 走 _get_owned_session)。 */
|
|
5733
6582
|
get canControlReplay() {
|
|
5734
6583
|
return this.state.viewerRole !== "viewer";
|
|
5735
6584
|
}
|
|
5736
|
-
/** 拉一次会话详情,填会话状态、回放状态、只读身份和运行时类型;失败不挡启动。 */
|
|
5737
6585
|
async syncSessionFromDetail(options) {
|
|
5738
6586
|
const statusRevisionAtRequest = this.statusRevision;
|
|
5739
6587
|
try {
|
|
@@ -5742,18 +6590,14 @@ var AgentSession = class _AgentSession {
|
|
|
5742
6590
|
} catch {
|
|
5743
6591
|
}
|
|
5744
6592
|
}
|
|
5745
|
-
/** 安装一份会话详情;`statusRevisionAtRequest` 是发出请求时的状态修订号。 */
|
|
5746
6593
|
applySessionDetail(info, statusRevisionAtRequest) {
|
|
5747
6594
|
if (this.disposed) return;
|
|
5748
|
-
this.runtimeType = info.runtime_type ?? null;
|
|
5749
6595
|
const replay = toReplaySnapshot(info.replay_state);
|
|
5750
6596
|
const viewerRole = info.viewer_role ?? null;
|
|
5751
6597
|
this.update((s) => ({
|
|
5752
6598
|
...s,
|
|
5753
6599
|
replay,
|
|
5754
6600
|
viewerRole,
|
|
5755
|
-
// 请求期间 status 变过(哪怕又变回原值)就说明实时事件比这份详情更新,不覆盖。
|
|
5756
|
-
// child_pause 已让提问进入等待态,而稍旧的详情仍可能写着 running。
|
|
5757
6601
|
status: this.statusRevision === statusRevisionAtRequest && !(s.status === "waiting_for_input" && info.status === "running" && s.turns.some((turn) => turn.tool_calls.some((call) => call.status === "awaiting_answer"))) ? info.status ?? s.status : s.status
|
|
5758
6602
|
}));
|
|
5759
6603
|
}
|
|
@@ -5767,7 +6611,6 @@ var AgentSession = class _AgentSession {
|
|
|
5767
6611
|
if (!this.state.replay?.isReplay || !this.canControlReplay) return;
|
|
5768
6612
|
await this.applyReplayControl({ status: "autonomous" });
|
|
5769
6613
|
}
|
|
5770
|
-
/** 把一次回放控制请求排进队列;前一个完成后才发出,响应直接安装。 */
|
|
5771
6614
|
applyReplayControl(payload) {
|
|
5772
6615
|
const run = this.replayQueue.then(async () => {
|
|
5773
6616
|
const result = await this.runtime.updateReplay(this.sessionId, payload);
|
|
@@ -5776,12 +6619,10 @@ var AgentSession = class _AgentSession {
|
|
|
5776
6619
|
this.replayQueue = run.catch(() => void 0);
|
|
5777
6620
|
return run;
|
|
5778
6621
|
}
|
|
5779
|
-
/** @internal */
|
|
5780
6622
|
_handleTurnStart(turn) {
|
|
5781
6623
|
this.markRunningIfIdle();
|
|
5782
6624
|
this.applyTurn(turn);
|
|
5783
6625
|
}
|
|
5784
|
-
/** @internal */
|
|
5785
6626
|
_handleTurnPatch(patch) {
|
|
5786
6627
|
this.markRunningIfIdle();
|
|
5787
6628
|
if (patch.data.workspace_changed) {
|
|
@@ -5795,20 +6636,90 @@ var AgentSession = class _AgentSession {
|
|
|
5795
6636
|
if (!current || patch.sequence >= current.sequence) {
|
|
5796
6637
|
this.pendingTurnPatches.set(patch.turn_id, patch);
|
|
5797
6638
|
}
|
|
5798
|
-
this.schedulePatchFlush();
|
|
5799
|
-
}
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
6639
|
+
this.schedulePatchFlush();
|
|
6640
|
+
}
|
|
6641
|
+
_handleTurnEnd(turn) {
|
|
6642
|
+
this.applyTurn(turn);
|
|
6643
|
+
}
|
|
6644
|
+
runEpochReload() {
|
|
6645
|
+
if (this.disposed || this.epochReloadRunning || this.epochReloadDone >= this.epochReloadRequested) return;
|
|
6646
|
+
const target = this.epochReloadRequested;
|
|
6647
|
+
this.epochReloadRunning = true;
|
|
6648
|
+
void this.reloadHistoryWindow().then((replaced) => {
|
|
6649
|
+
if (replaced) this.epochReloadDone = Math.max(this.epochReloadDone, target);
|
|
6650
|
+
}).catch(() => void 0).finally(() => {
|
|
6651
|
+
this.epochReloadRunning = false;
|
|
6652
|
+
if (this.epochReloadRequested > target) this.runEpochReload();
|
|
6653
|
+
});
|
|
6654
|
+
}
|
|
6655
|
+
_handleTurnEvents(incoming, replayed = false) {
|
|
6656
|
+
const delivery = this.logCursor.consume(incoming);
|
|
6657
|
+
let events = delivery.events;
|
|
6658
|
+
if (delivery.reload) {
|
|
6659
|
+
this.activeReplayPlaybackId = null;
|
|
6660
|
+
this.projectionBuilder.reset();
|
|
6661
|
+
this.pendingTurnPatches.clear();
|
|
6662
|
+
this.epochReloadRequested += 1;
|
|
6663
|
+
}
|
|
6664
|
+
this.runEpochReload();
|
|
6665
|
+
for (const event of events) {
|
|
6666
|
+
const playbackId = typeof event.payload.playback_id === "string" ? event.payload.playback_id : null;
|
|
6667
|
+
if (event.type === "chat:start") this.activeReplayPlaybackId = null;
|
|
6668
|
+
if (event.type === "replay:start" && playbackId) {
|
|
6669
|
+
this.activeReplayPlaybackId = playbackId;
|
|
6670
|
+
this._handleChatStart();
|
|
6671
|
+
} else if (event.type === "replay:state") {
|
|
6672
|
+
const status = event.payload.status;
|
|
6673
|
+
if (event.payload.reconciled === true && typeof status === "string") {
|
|
6674
|
+
if (status === "running") this._handleChatStart();
|
|
6675
|
+
else {
|
|
6676
|
+
this.activeReplayPlaybackId = null;
|
|
6677
|
+
this.finishStreaming(status);
|
|
6678
|
+
}
|
|
6679
|
+
}
|
|
6680
|
+
this._handleSessionUpdated(event.payload);
|
|
6681
|
+
} else if (event.type === "replay:end" && playbackId && (!this.activeReplayPlaybackId || this.activeReplayPlaybackId === playbackId)) {
|
|
6682
|
+
this.activeReplayPlaybackId = null;
|
|
6683
|
+
const status = event.payload.status === "interrupted" ? "interrupted" : event.payload.status === "failed" ? "failed" : "completed";
|
|
6684
|
+
this._handleChatEnd({ status });
|
|
6685
|
+
}
|
|
6686
|
+
if (event.type === "log:notification") {
|
|
6687
|
+
const notification = event.payload.event;
|
|
6688
|
+
if (isCompactionFinished(notification.type)) {
|
|
6689
|
+
this.handleManualCompactionFinished(notification);
|
|
6690
|
+
} else if (notification.type === "artifact:created" || notification.type === "artifact:updated") {
|
|
6691
|
+
this._handleArtifact(notification.payload, notification.type === "artifact:created" ? "created" : "updated", replayed);
|
|
6692
|
+
} else if (notification.type === "workspace:changed") {
|
|
6693
|
+
const change = notification.payload.workspace_change;
|
|
6694
|
+
const paths = Array.isArray(notification.payload.paths) ? notification.payload.paths.filter((path) => typeof path === "string") : [];
|
|
6695
|
+
this.emitter.emit("workspaceChanged", {
|
|
6696
|
+
...paths.length ? { paths } : {},
|
|
6697
|
+
...typeof change?.to === "string" && change.to ? { workspacePath: change.to } : {}
|
|
6698
|
+
});
|
|
6699
|
+
} else if (notification.type === "task:updated" && Array.isArray(notification.payload.tasks)) {
|
|
6700
|
+
this._handleTaskUpdated(notification.payload.tasks);
|
|
6701
|
+
} else if (notification.type === "bg:tasks_completed" && Array.isArray(notification.payload.tasks)) {
|
|
6702
|
+
for (const task of notification.payload.tasks) {
|
|
6703
|
+
const completed = completedBackgroundTask(task);
|
|
6704
|
+
if (completed) this._handleBackgroundTask(completed);
|
|
6705
|
+
}
|
|
6706
|
+
} else {
|
|
6707
|
+
this.applyWorkflowEvents([notification]);
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
if (event.type === "turn:snapshot") {
|
|
6711
|
+
const turn = event.payload.turn;
|
|
6712
|
+
this.projectionBuilder.acceptDurableTurn(turn);
|
|
6713
|
+
this.pendingTurnPatches.delete(turn.turn_id);
|
|
6714
|
+
this.applyTurn(
|
|
6715
|
+
turn,
|
|
6716
|
+
replayed ? this.subscriptionReplaySource ?? "bootstrap_replay" : "live"
|
|
6717
|
+
);
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
events = events.filter((event) => event.type !== "turn:snapshot" && event.type !== "log:notification" && !event.type.startsWith("replay:"));
|
|
5806
6721
|
if (!events.length) return;
|
|
5807
|
-
|
|
5808
|
-
(event) => event.type === "compaction:end" || event.type === "compaction:error" || event.type === "compaction:abort"
|
|
5809
|
-
)) {
|
|
5810
|
-
this.setManualCompactionPending(false);
|
|
5811
|
-
}
|
|
6722
|
+
for (const event of events) this.handleManualCompactionFinished(event);
|
|
5812
6723
|
this.dispatchRawCommands(events);
|
|
5813
6724
|
const terminalChatEndStatus = extractTerminalChatEndStatus(events);
|
|
5814
6725
|
if (!terminalChatEndStatus && hasChatRunEvent(events) && this.state.status !== "waiting_for_input") {
|
|
@@ -5820,7 +6731,6 @@ var AgentSession = class _AgentSession {
|
|
|
5820
6731
|
const updates = this.projectionBuilder.processBatch(events);
|
|
5821
6732
|
for (const update of updates) {
|
|
5822
6733
|
if (update.kind === "upsert") {
|
|
5823
|
-
if (this.isNativeBootstrapTurn(update.turn)) continue;
|
|
5824
6734
|
this.applyTurn(
|
|
5825
6735
|
update.turn,
|
|
5826
6736
|
replayed ? this.subscriptionReplaySource ?? "bootstrap_replay" : "live"
|
|
@@ -5843,7 +6753,6 @@ var AgentSession = class _AgentSession {
|
|
|
5843
6753
|
this.completeChat(terminalChatEndStatus ?? "completed");
|
|
5844
6754
|
}
|
|
5845
6755
|
}
|
|
5846
|
-
/** @internal */
|
|
5847
6756
|
_handleChatStart() {
|
|
5848
6757
|
this.noteLiveStatus();
|
|
5849
6758
|
this.update((s) => ({
|
|
@@ -5853,10 +6762,6 @@ var AgentSession = class _AgentSession {
|
|
|
5853
6762
|
isStreaming: true
|
|
5854
6763
|
}));
|
|
5855
6764
|
}
|
|
5856
|
-
/**
|
|
5857
|
-
* @internal 返回 false 表示这是一条过期的排队回滚(被取消的是更早的请求,当前运行不受影响),
|
|
5858
|
-
* Hub 据此不把它当成一次运行结束广播出去。
|
|
5859
|
-
*/
|
|
5860
6765
|
_handleChatEnd(payload = {}) {
|
|
5861
6766
|
const status = payload.status ?? "completed";
|
|
5862
6767
|
if (payload.queued === true) {
|
|
@@ -5875,29 +6780,9 @@ var AgentSession = class _AgentSession {
|
|
|
5875
6780
|
this.emitter.emit("chatEnd", { status });
|
|
5876
6781
|
return true;
|
|
5877
6782
|
}
|
|
5878
|
-
if (this.runtimeType === "native") {
|
|
5879
|
-
const updates = this.projectionBuilder.processBatch([
|
|
5880
|
-
{
|
|
5881
|
-
type: "chat:end",
|
|
5882
|
-
payload: {
|
|
5883
|
-
loop_name: "root",
|
|
5884
|
-
status,
|
|
5885
|
-
...payload.pause_tool_data ? { pause_tool_data: payload.pause_tool_data } : {}
|
|
5886
|
-
}
|
|
5887
|
-
}
|
|
5888
|
-
]);
|
|
5889
|
-
for (const update of updates) {
|
|
5890
|
-
if (update.kind === "upsert") this.applyTurn(update.turn);
|
|
5891
|
-
}
|
|
5892
|
-
this.finishStreaming(status);
|
|
5893
|
-
this.recentChatEndAt = Date.now();
|
|
5894
|
-
this.emitter.emit("chatEnd", { status });
|
|
5895
|
-
return true;
|
|
5896
|
-
}
|
|
5897
6783
|
this.completeChat(status);
|
|
5898
6784
|
return true;
|
|
5899
6785
|
}
|
|
5900
|
-
/** @internal */
|
|
5901
6786
|
_handleSystemError(message, code) {
|
|
5902
6787
|
this.pendingChatRequestId = null;
|
|
5903
6788
|
this.noteLiveStatus();
|
|
@@ -5927,15 +6812,12 @@ var AgentSession = class _AgentSession {
|
|
|
5927
6812
|
...code ? { code } : {}
|
|
5928
6813
|
});
|
|
5929
6814
|
}
|
|
5930
|
-
/** @internal */
|
|
5931
6815
|
_handleTaskUpdated(tasks) {
|
|
5932
6816
|
this.emitter.emit("taskListUpdated", { tasks });
|
|
5933
6817
|
}
|
|
5934
|
-
/** @internal */
|
|
5935
6818
|
_handleWorkspaceFilesChanged(filePath, paths) {
|
|
5936
6819
|
this.emitter.emit("workspaceChanged", paths?.length ? { filePath, paths } : { filePath });
|
|
5937
6820
|
}
|
|
5938
|
-
/** @internal */
|
|
5939
6821
|
_handleSessionUpdated(data) {
|
|
5940
6822
|
if (data.status) {
|
|
5941
6823
|
this.noteLiveStatus();
|
|
@@ -5951,11 +6833,9 @@ var AgentSession = class _AgentSession {
|
|
|
5951
6833
|
intent: data.intent,
|
|
5952
6834
|
status: data.status,
|
|
5953
6835
|
model: data.model,
|
|
5954
|
-
boundSkillId: data.bound_skill_id,
|
|
5955
6836
|
replayState: data.replay_state
|
|
5956
6837
|
});
|
|
5957
6838
|
}
|
|
5958
|
-
/** @internal */
|
|
5959
6839
|
_handleRewind(checkpointId) {
|
|
5960
6840
|
this.invalidateTimeline();
|
|
5961
6841
|
this.invalidateOlderPaging();
|
|
@@ -5970,14 +6850,19 @@ var AgentSession = class _AgentSession {
|
|
|
5970
6850
|
void this.replaceFromHistory();
|
|
5971
6851
|
this.emitter.emit("rewind", { checkpointId });
|
|
5972
6852
|
}
|
|
5973
|
-
/** @internal */
|
|
5974
6853
|
_handleReplayMismatch(data) {
|
|
5975
6854
|
const message = this.pendingReplayMessage ?? data.actual_message ?? "";
|
|
5976
6855
|
const mode = this.pendingReplayMode;
|
|
5977
|
-
const
|
|
6856
|
+
const requestId = this.pendingReplayRequestId ?? createClientRequestId();
|
|
6857
|
+
const contextAttachments = this.pendingReplayAttachments;
|
|
6858
|
+
const refs = this.pendingReplayRefs;
|
|
6859
|
+
const topicAssetRefs = this.pendingReplayTopicAssetRefs;
|
|
5978
6860
|
this.pendingReplayMessage = null;
|
|
5979
6861
|
this.pendingReplayMode = void 0;
|
|
5980
|
-
this.
|
|
6862
|
+
this.pendingReplayRequestId = void 0;
|
|
6863
|
+
this.pendingReplayAttachments = void 0;
|
|
6864
|
+
this.pendingReplayRefs = void 0;
|
|
6865
|
+
this.pendingReplayTopicAssetRefs = void 0;
|
|
5981
6866
|
const respond = (decision) => {
|
|
5982
6867
|
void this.runtime.emitWithAck(
|
|
5983
6868
|
"chat:send",
|
|
@@ -5986,7 +6871,10 @@ var AgentSession = class _AgentSession {
|
|
|
5986
6871
|
message,
|
|
5987
6872
|
mode,
|
|
5988
6873
|
replay_decision: decision,
|
|
5989
|
-
|
|
6874
|
+
client_request_id: requestId,
|
|
6875
|
+
refs,
|
|
6876
|
+
topic_asset_refs: topicAssetRefs,
|
|
6877
|
+
context_attachments: contextAttachments
|
|
5990
6878
|
},
|
|
5991
6879
|
3e4
|
|
5992
6880
|
).then((response) => {
|
|
@@ -6015,7 +6903,6 @@ var AgentSession = class _AgentSession {
|
|
|
6015
6903
|
respond("continue_replay");
|
|
6016
6904
|
}
|
|
6017
6905
|
}
|
|
6018
|
-
/** @internal */
|
|
6019
6906
|
_handleSystemNotification(loopId, notification) {
|
|
6020
6907
|
const notificationType = typeof notification.notification_type === "string" ? notification.notification_type : "system_notification";
|
|
6021
6908
|
const title = typeof notification.title === "string" ? notification.title : notificationType;
|
|
@@ -6042,15 +6929,24 @@ var AgentSession = class _AgentSession {
|
|
|
6042
6929
|
}
|
|
6043
6930
|
}
|
|
6044
6931
|
}
|
|
6045
|
-
/** @internal */
|
|
6046
6932
|
_handleBackgroundTask(task) {
|
|
6047
6933
|
this.emitter.emit("backgroundTask", task);
|
|
6048
6934
|
}
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6935
|
+
_handleArtifact(data, kind, replayed = false) {
|
|
6936
|
+
const filePath = typeof data.file_path === "string" ? data.file_path : "";
|
|
6937
|
+
if (!filePath) return;
|
|
6938
|
+
this._handleWorkspaceFilesChanged(filePath);
|
|
6939
|
+
if (replayed) return;
|
|
6940
|
+
this.emitter.emit("artifact", {
|
|
6941
|
+
filePath,
|
|
6942
|
+
contentType: typeof data.content_type === "string" ? data.content_type : "",
|
|
6943
|
+
title: typeof data.title === "string" ? data.title : filePath,
|
|
6944
|
+
content: typeof data.content === "string" ? data.content : "",
|
|
6945
|
+
oldString: typeof data.old_string === "string" ? data.old_string : void 0,
|
|
6946
|
+
newString: typeof data.new_string === "string" ? data.new_string : void 0,
|
|
6947
|
+
kind
|
|
6948
|
+
});
|
|
6052
6949
|
}
|
|
6053
|
-
/** @internal 加入房间的应答里带 is_running,据此校准流式状态。 */
|
|
6054
6950
|
_applyRunningState(isRunning, liveRevisionAtJoin = this.liveRevision) {
|
|
6055
6951
|
const hasPendingQuestion = this.state.turns.some(
|
|
6056
6952
|
(turn) => turn.tool_calls.some((call) => call.status === "awaiting_answer")
|
|
@@ -6063,7 +6959,6 @@ var AgentSession = class _AgentSession {
|
|
|
6063
6959
|
void this.refreshAuthoritativeHistory({ liveRevisionAtStart: liveRevisionAtJoin });
|
|
6064
6960
|
}
|
|
6065
6961
|
}
|
|
6066
|
-
// ---------------------------------------------------------------- 内部实现
|
|
6067
6962
|
markRunningIfIdle() {
|
|
6068
6963
|
if (!this.state.isStreaming) {
|
|
6069
6964
|
this.noteLiveStatus();
|
|
@@ -6080,15 +6975,6 @@ var AgentSession = class _AgentSession {
|
|
|
6080
6975
|
});
|
|
6081
6976
|
this.applyDesiredStreamTokens();
|
|
6082
6977
|
}
|
|
6083
|
-
/**
|
|
6084
|
-
* 原生智能体常在用户发言之前就宣告一个空的 assistant 回合(投影的起点),它的真实内容会随第一条
|
|
6085
|
-
* 输出再发一次。消息列表在启动期间本来就显示一个「正在生成」占位,两者并存会出现两个占位,
|
|
6086
|
-
* 所以只丢掉原生会话里这种空的流式回合。
|
|
6087
|
-
*/
|
|
6088
|
-
isNativeBootstrapTurn(turn) {
|
|
6089
|
-
return this.runtimeType === "native" && turn.role === "assistant" && turn.status === "streaming" && turn.blocks.length === 0 && turn.tool_calls.length === 0;
|
|
6090
|
-
}
|
|
6091
|
-
/** 实时 workflow:* 事件叠加到对应工具调用的时间线上(见 workflow.ts 的来源规则)。 */
|
|
6092
6978
|
applyWorkflowEvents(events) {
|
|
6093
6979
|
for (const event of events) {
|
|
6094
6980
|
if (!isWorkflowEvent(event.type)) continue;
|
|
@@ -6103,10 +6989,6 @@ var AgentSession = class _AgentSession {
|
|
|
6103
6989
|
this.update((state) => withWorkflowEvent(state, toolCallId, event.type, payload));
|
|
6104
6990
|
}
|
|
6105
6991
|
}
|
|
6106
|
-
/**
|
|
6107
|
-
* @internal 宿主改了 token 订阅偏好(精简模式)。空闲时立即重订阅;运行中等这一轮结束再换,
|
|
6108
|
-
* 避免在输出中途丢掉 token 流。
|
|
6109
|
-
*/
|
|
6110
6992
|
_setStreamTokens(enabled) {
|
|
6111
6993
|
this.desiredStreamTokens = enabled;
|
|
6112
6994
|
if (!this.state.isStreaming) this.applyDesiredStreamTokens();
|
|
@@ -6124,7 +7006,7 @@ var AgentSession = class _AgentSession {
|
|
|
6124
7006
|
completeChat(status) {
|
|
6125
7007
|
this.finishStreaming(status);
|
|
6126
7008
|
this.recentChatEndAt = Date.now();
|
|
6127
|
-
|
|
7009
|
+
void this.refreshAuthoritativeHistory();
|
|
6128
7010
|
this.emitter.emit("chatEnd", { status });
|
|
6129
7011
|
}
|
|
6130
7012
|
markTurnFresh(turnId) {
|
|
@@ -6202,7 +7084,6 @@ var AgentSession = class _AgentSession {
|
|
|
6202
7084
|
this.patchFlushCancel = null;
|
|
6203
7085
|
}
|
|
6204
7086
|
}
|
|
6205
|
-
/** turn 数据落地后统一派生语义事件(指令 / 工具调用 / 消息 / 模式 / 预览 / OOM)。 */
|
|
6206
7087
|
emitTurnDerivedEvents(previous, turn, source = "live") {
|
|
6207
7088
|
this.emitter.emit("turnUpdated", { turn, source });
|
|
6208
7089
|
const mode = extractModeFromBlocks(turn.blocks);
|
|
@@ -6250,6 +7131,7 @@ var AgentSession = class _AgentSession {
|
|
|
6250
7131
|
display_name: toolCall.display_name,
|
|
6251
7132
|
arguments: toolCall.arguments,
|
|
6252
7133
|
result: toolCall.result ?? void 0,
|
|
7134
|
+
...toolCall.evidence ? { evidence: toolCall.evidence } : {},
|
|
6253
7135
|
status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending"
|
|
6254
7136
|
};
|
|
6255
7137
|
if (!this.seenToolCallIds.has(toolCall.id)) {
|
|
@@ -6314,20 +7196,11 @@ var AgentSession = class _AgentSession {
|
|
|
6314
7196
|
this.update((s) => addErrorMessage(s, OOM_MESSAGE));
|
|
6315
7197
|
this.emitter.emit("sandboxOom", { toolCallId: key });
|
|
6316
7198
|
}
|
|
6317
|
-
/**
|
|
6318
|
-
* 权威刷新(运行结束 / 重连补数):**按当前窗口第一条发言取窗口**,不再另记"用户想看哪段"。
|
|
6319
|
-
*
|
|
6320
|
-
* 窗口本身就是"用户在看哪段":首屏就是最新一页,向上翻页 / 定位之后首条就是用户滚到的位置。
|
|
6321
|
-
* 直接拿 `from_entry_id = 当前窗口第一条 turn_id` 去取,运行结束、重连补数、翻页之后的对账
|
|
6322
|
-
* 是同一条规则,不需要意图、锚点、已落地锚点这些状态;窗口首条已经不在分支上(404 / 409)时
|
|
6323
|
-
* 回落到最新一页。
|
|
6324
|
-
*/
|
|
6325
7199
|
refreshAuthoritativeHistory(options = {}) {
|
|
6326
7200
|
let entryGone = false;
|
|
6327
7201
|
const locateTokenAtStart = this.locateToken;
|
|
6328
7202
|
return this.replaceFromHistory({
|
|
6329
7203
|
...options,
|
|
6330
|
-
// 锚点在**这次操作真正开始**时采样:排队期间用户可能刚定位过,那时窗口首条才是他要的。
|
|
6331
7204
|
fromEntryId: () => {
|
|
6332
7205
|
const anchor = this.state.turns[0]?.turn_id;
|
|
6333
7206
|
return anchor?.startsWith("local-user-") ? void 0 : anchor;
|
|
@@ -6495,7 +7368,6 @@ var AgentSession = class _AgentSession {
|
|
|
6495
7368
|
this.bufferedCommandDeliveries = null;
|
|
6496
7369
|
}
|
|
6497
7370
|
}
|
|
6498
|
-
/** 首次订阅前的 REST 历史建立语义事件基线,随后同一工具的订阅回放不再投递。 */
|
|
6499
7371
|
seedToolCallDeliveryBaseline(turns) {
|
|
6500
7372
|
for (const turn of turns) {
|
|
6501
7373
|
for (const toolCall of turn.tool_calls) {
|
|
@@ -6516,6 +7388,7 @@ var AgentSession = class _AgentSession {
|
|
|
6516
7388
|
session_id: this.sessionId,
|
|
6517
7389
|
event_format: "raw",
|
|
6518
7390
|
resume: true,
|
|
7391
|
+
...this.logCursor.subscription(),
|
|
6519
7392
|
...streamTokens === null ? {} : { internal_stream_tokens: streamTokens }
|
|
6520
7393
|
},
|
|
6521
7394
|
5e3
|
|
@@ -6549,23 +7422,10 @@ var AgentSession = class _AgentSession {
|
|
|
6549
7422
|
};
|
|
6550
7423
|
|
|
6551
7424
|
// src/session/hub.ts
|
|
6552
|
-
var BACKGROUND_STATUSES = [
|
|
6553
|
-
"starting",
|
|
6554
|
-
"running",
|
|
6555
|
-
"cancelling",
|
|
6556
|
-
"succeeded",
|
|
6557
|
-
"failed",
|
|
6558
|
-
"cancelled",
|
|
6559
|
-
"lost"
|
|
6560
|
-
];
|
|
6561
|
-
function normalizeBackgroundStatus(value) {
|
|
6562
|
-
return BACKGROUND_STATUSES.includes(value) ? value : "running";
|
|
6563
|
-
}
|
|
6564
7425
|
var SessionHub = class {
|
|
6565
7426
|
client;
|
|
6566
7427
|
sessions = /* @__PURE__ */ new Map();
|
|
6567
7428
|
bootstrapPending = /* @__PURE__ */ new Map();
|
|
6568
|
-
/** 每个会话当前有多少方持有(connect 一次 +1,dispose 一次 -1)。 */
|
|
6569
7429
|
holdCounts = /* @__PURE__ */ new Map();
|
|
6570
7430
|
connection = "disconnected";
|
|
6571
7431
|
connectionListeners = /* @__PURE__ */ new Set();
|
|
@@ -6574,17 +7434,12 @@ var SessionHub = class {
|
|
|
6574
7434
|
authBusyReconnect = new AuthBusyReconnect();
|
|
6575
7435
|
hubEmitter = new Emitter();
|
|
6576
7436
|
options = { restoreHistoricalCommands: true };
|
|
6577
|
-
/** token 订阅偏好(第一方 Web 精简模式);null 表示不设置,订阅时不带字段。 */
|
|
6578
7437
|
streamTokens = null;
|
|
6579
|
-
/** 状态订阅(mode: "status")的持有计数。 */
|
|
6580
7438
|
statusWatchCounts = /* @__PURE__ */ new Map();
|
|
6581
|
-
|
|
7439
|
+
statusLogCursors = /* @__PURE__ */ new Map();
|
|
7440
|
+
replayPlaybackIds = /* @__PURE__ */ new Map();
|
|
6582
7441
|
connectionHeld = false;
|
|
6583
7442
|
connectDeferred = false;
|
|
6584
|
-
/**
|
|
6585
|
-
* 宿主显式要过连接(ensureConnected / reconnect,例如只收产品事件的页面)。有会话、状态订阅或这个需求时,
|
|
6586
|
-
* 鉴权忙碌的临时拒绝都要按服务端建议重试,否则连接会一直停在断开,直到别的动作碰巧重连。
|
|
6587
|
-
*/
|
|
6588
7443
|
connectionDemanded = false;
|
|
6589
7444
|
constructor(client) {
|
|
6590
7445
|
this.client = client;
|
|
@@ -6597,10 +7452,6 @@ var SessionHub = class {
|
|
|
6597
7452
|
on(name, handler) {
|
|
6598
7453
|
return this.hubEmitter.on(name, handler);
|
|
6599
7454
|
}
|
|
6600
|
-
/**
|
|
6601
|
-
* @internal 第一方 Web 的精简模式传输优化:订阅时是否要 token 级事件。
|
|
6602
|
-
* 空闲会话立即重订阅,运行中的会话等这一轮结束。
|
|
6603
|
-
*/
|
|
6604
7455
|
setStreamTokens(enabled) {
|
|
6605
7456
|
if (this.streamTokens === enabled) return;
|
|
6606
7457
|
this.streamTokens = enabled;
|
|
@@ -6629,6 +7480,7 @@ var SessionHub = class {
|
|
|
6629
7480
|
return;
|
|
6630
7481
|
}
|
|
6631
7482
|
this.statusWatchCounts.delete(sessionId);
|
|
7483
|
+
this.statusLogCursors.delete(sessionId);
|
|
6632
7484
|
if (!this.sessions.has(sessionId)) {
|
|
6633
7485
|
;
|
|
6634
7486
|
this.client.socket().emit("session:unsubscribe", { session_id: sessionId });
|
|
@@ -6755,15 +7607,11 @@ var SessionHub = class {
|
|
|
6755
7607
|
this.authBusyReconnect.cancel();
|
|
6756
7608
|
this.bootstrapPending.clear();
|
|
6757
7609
|
this.statusWatchCounts.clear();
|
|
7610
|
+
this.statusLogCursors.clear();
|
|
6758
7611
|
for (const session of [...this.sessions.values()]) {
|
|
6759
7612
|
this.forceRelease(session);
|
|
6760
7613
|
}
|
|
6761
7614
|
}
|
|
6762
|
-
/**
|
|
6763
|
-
* @internal 底层 socket 被换掉时由 BladeClient 调用(如 setBaseUrl)。
|
|
6764
|
-
* 事件监听器绑在旧 socket 实例上,换新实例后必须重绑,否则所有
|
|
6765
|
-
* turn:* / chat:* 事件都会静默丢弃。
|
|
6766
|
-
*/
|
|
6767
7615
|
_handleSocketReplaced() {
|
|
6768
7616
|
this.authBusyReconnect.cancel();
|
|
6769
7617
|
this.bound = false;
|
|
@@ -6792,14 +7640,9 @@ var SessionHub = class {
|
|
|
6792
7640
|
onDispose: (session) => this.release(session)
|
|
6793
7641
|
};
|
|
6794
7642
|
}
|
|
6795
|
-
/** 记一份持有。 */
|
|
6796
7643
|
hold(sessionId) {
|
|
6797
7644
|
this.holdCounts.set(sessionId, (this.holdCounts.get(sessionId) ?? 0) + 1);
|
|
6798
7645
|
}
|
|
6799
|
-
/**
|
|
6800
|
-
* 归还一份持有;还剩别的持有者时返回 false,让 AgentSession 保持可用。
|
|
6801
|
-
* 归零才退订房间并从路由表移除。
|
|
6802
|
-
*/
|
|
6803
7646
|
release(session) {
|
|
6804
7647
|
const remaining = (this.holdCounts.get(session.sessionId) ?? 0) - 1;
|
|
6805
7648
|
if (remaining > 0) {
|
|
@@ -6816,20 +7659,34 @@ var SessionHub = class {
|
|
|
6816
7659
|
;
|
|
6817
7660
|
this.client.socket().emit("session:unsubscribe", { session_id: session.sessionId });
|
|
6818
7661
|
if (this.statusWatchCounts.has(session.sessionId)) this.emitStatusSubscribe([session.sessionId]);
|
|
7662
|
+
else this.statusLogCursors.delete(session.sessionId);
|
|
7663
|
+
if (this.client.options.anonymousSessionId && !this.needsConnection()) this.disconnect();
|
|
6819
7664
|
return true;
|
|
6820
7665
|
}
|
|
7666
|
+
statusCursor(sessionId) {
|
|
7667
|
+
let cursor = this.statusLogCursors.get(sessionId);
|
|
7668
|
+
if (!cursor) {
|
|
7669
|
+
cursor = new LogCursor();
|
|
7670
|
+
this.statusLogCursors.set(sessionId, cursor);
|
|
7671
|
+
}
|
|
7672
|
+
return cursor;
|
|
7673
|
+
}
|
|
6821
7674
|
emitStatusSubscribe(sessionIds) {
|
|
6822
7675
|
if (sessionIds.length === 0) return;
|
|
6823
7676
|
const socket = this.client.socket();
|
|
7677
|
+
const payload = (sessionId) => ({
|
|
7678
|
+
session_id: sessionId,
|
|
7679
|
+
mode: "status",
|
|
7680
|
+
...this.statusCursor(sessionId).subscription()
|
|
7681
|
+
});
|
|
6824
7682
|
if (sessionIds.length === 1) {
|
|
6825
|
-
socket.emit("session:subscribe",
|
|
7683
|
+
socket.emit("session:subscribe", payload(sessionIds[0]));
|
|
6826
7684
|
return;
|
|
6827
7685
|
}
|
|
6828
7686
|
socket.emit("session:subscribe_batch", {
|
|
6829
|
-
sessions: sessionIds.map(
|
|
7687
|
+
sessions: sessionIds.map(payload)
|
|
6830
7688
|
});
|
|
6831
7689
|
}
|
|
6832
|
-
/** 不管还有几方持有都立即释放(启动失败、切换身份)。 */
|
|
6833
7690
|
forceRelease(session) {
|
|
6834
7691
|
this.holdCounts.delete(session.sessionId);
|
|
6835
7692
|
session.dispose();
|
|
@@ -6854,7 +7711,6 @@ var SessionHub = class {
|
|
|
6854
7711
|
}
|
|
6855
7712
|
}
|
|
6856
7713
|
}
|
|
6857
|
-
/** 事件带 session_id 时精确路由;缺省时按当前唯一活跃会话兜底。 */
|
|
6858
7714
|
route(sessionId) {
|
|
6859
7715
|
if (sessionId) {
|
|
6860
7716
|
const session = this.sessions.get(sessionId);
|
|
@@ -6923,7 +7779,51 @@ var SessionHub = class {
|
|
|
6923
7779
|
socket.on(
|
|
6924
7780
|
"turn:events",
|
|
6925
7781
|
(data, acknowledge) => {
|
|
7782
|
+
syncServerTime(this.client, data.server_time);
|
|
6926
7783
|
acknowledgeTurnEvents(acknowledge);
|
|
7784
|
+
if (data.session_id && (this.statusWatchCounts.has(data.session_id) || this.sessions.has(data.session_id)) && data.events?.some((event) => event.type === "log:record")) {
|
|
7785
|
+
const delivery = this.statusCursor(data.session_id).consume(data.events ?? []);
|
|
7786
|
+
for (const event of delivery.events) {
|
|
7787
|
+
if (event.type === "chat:start") {
|
|
7788
|
+
this.replayPlaybackIds.delete(data.session_id);
|
|
7789
|
+
this.hubEmitter.emit("runStarted", { sessionId: data.session_id });
|
|
7790
|
+
} else if (event.type === "chat:end") {
|
|
7791
|
+
this.hubEmitter.emit("runEnded", {
|
|
7792
|
+
sessionId: data.session_id,
|
|
7793
|
+
status: typeof event.payload.status === "string" ? event.payload.status : void 0,
|
|
7794
|
+
queued: false,
|
|
7795
|
+
replayed: data.replayed === true,
|
|
7796
|
+
...typeof event.payload.chat_run_id === "string" ? { chatRunId: event.payload.chat_run_id } : {}
|
|
7797
|
+
});
|
|
7798
|
+
}
|
|
7799
|
+
}
|
|
7800
|
+
}
|
|
7801
|
+
if (data.session_id) {
|
|
7802
|
+
for (const event of data.events ?? []) {
|
|
7803
|
+
if (event.type === "log:retract") this.replayPlaybackIds.delete(data.session_id);
|
|
7804
|
+
if (event.type === "replay:state") {
|
|
7805
|
+
this.hubEmitter.emit("sessionUpdated", { sessionId: data.session_id, data: event.payload });
|
|
7806
|
+
}
|
|
7807
|
+
const playbackId = event.payload.playback_id;
|
|
7808
|
+
if (typeof playbackId !== "string") continue;
|
|
7809
|
+
if (event.type === "replay:start") {
|
|
7810
|
+
if (this.replayPlaybackIds.get(data.session_id) !== playbackId) {
|
|
7811
|
+
this.replayPlaybackIds.set(data.session_id, playbackId);
|
|
7812
|
+
this.hubEmitter.emit("runStarted", { sessionId: data.session_id });
|
|
7813
|
+
}
|
|
7814
|
+
} else if (event.type === "replay:end") {
|
|
7815
|
+
const current = this.replayPlaybackIds.get(data.session_id);
|
|
7816
|
+
if (current && current !== playbackId) continue;
|
|
7817
|
+
this.replayPlaybackIds.delete(data.session_id);
|
|
7818
|
+
this.hubEmitter.emit("runEnded", {
|
|
7819
|
+
sessionId: data.session_id,
|
|
7820
|
+
status: typeof event.payload.status === "string" ? event.payload.status : "completed",
|
|
7821
|
+
queued: false,
|
|
7822
|
+
replayed: true
|
|
7823
|
+
});
|
|
7824
|
+
}
|
|
7825
|
+
}
|
|
7826
|
+
}
|
|
6927
7827
|
for (const session of this.route(data.session_id)) {
|
|
6928
7828
|
session._handleTurnEvents(data.events ?? [], data.replayed === true);
|
|
6929
7829
|
}
|
|
@@ -7041,40 +7941,16 @@ var SessionHub = class {
|
|
|
7041
7941
|
(data) => {
|
|
7042
7942
|
for (const session of this.route(data.session_id)) {
|
|
7043
7943
|
for (const task of data.tasks ?? []) {
|
|
7044
|
-
const
|
|
7045
|
-
if (
|
|
7046
|
-
session._handleBackgroundTask({
|
|
7047
|
-
id: taskId,
|
|
7048
|
-
command: typeof task.command === "string" ? task.command : "",
|
|
7049
|
-
description: typeof task.description === "string" ? task.description : void 0,
|
|
7050
|
-
// Pass the server's status through unchanged. Collapsing it here
|
|
7051
|
-
// made subscribers see a successful-looking "done" for tasks the
|
|
7052
|
-
// REST API correctly reported as cancelled or lost.
|
|
7053
|
-
status: normalizeBackgroundStatus(task.status),
|
|
7054
|
-
failureReason: typeof task.failure_reason === "string" ? task.failure_reason : void 0,
|
|
7055
|
-
exitCode: typeof task.exit_code === "number" || task.exit_code === null ? task.exit_code : void 0,
|
|
7056
|
-
elapsedSeconds: typeof task.elapsed_seconds === "number" ? task.elapsed_seconds : 0,
|
|
7057
|
-
output: Array.isArray(task.output_preview) && task.output_preview.length > 0 ? task.output_preview.join("\n") : void 0
|
|
7058
|
-
});
|
|
7944
|
+
const completed = completedBackgroundTask(task);
|
|
7945
|
+
if (completed) session._handleBackgroundTask(completed);
|
|
7059
7946
|
}
|
|
7060
7947
|
}
|
|
7061
7948
|
}
|
|
7062
7949
|
);
|
|
7063
7950
|
}
|
|
7064
7951
|
routeArtifact(data, kind) {
|
|
7065
|
-
const filePath = typeof data.file_path === "string" ? data.file_path : "";
|
|
7066
|
-
if (!filePath) return;
|
|
7067
7952
|
for (const session of this.route(data.session_id)) {
|
|
7068
|
-
session.
|
|
7069
|
-
session._handleArtifact({
|
|
7070
|
-
filePath,
|
|
7071
|
-
contentType: typeof data.content_type === "string" ? data.content_type : "",
|
|
7072
|
-
title: typeof data.title === "string" ? data.title : filePath,
|
|
7073
|
-
content: typeof data.content === "string" ? data.content : "",
|
|
7074
|
-
oldString: typeof data.old_string === "string" ? data.old_string : void 0,
|
|
7075
|
-
newString: typeof data.new_string === "string" ? data.new_string : void 0,
|
|
7076
|
-
kind
|
|
7077
|
-
});
|
|
7953
|
+
session._handleArtifact(data, kind);
|
|
7078
7954
|
}
|
|
7079
7955
|
}
|
|
7080
7956
|
};
|
|
@@ -7105,13 +7981,16 @@ function createSocket(options) {
|
|
|
7105
7981
|
|
|
7106
7982
|
// src/blade-client.ts
|
|
7107
7983
|
var REFRESH_PATH = "/api/auth/refresh";
|
|
7108
|
-
var BladeClient = class {
|
|
7984
|
+
var BladeClient = class _BladeClient {
|
|
7985
|
+
anonymousClients = /* @__PURE__ */ new Map();
|
|
7986
|
+
toolProviders = /* @__PURE__ */ new Set();
|
|
7109
7987
|
refreshPromise = null;
|
|
7988
|
+
cookieLogoutPending = 0;
|
|
7110
7989
|
socketInstance = null;
|
|
7111
7990
|
socketReplacementListeners = /* @__PURE__ */ new Set();
|
|
7112
|
-
|
|
7113
|
-
runtimeToken = null;
|
|
7991
|
+
runtimeToken = void 0;
|
|
7114
7992
|
options;
|
|
7993
|
+
sandbox;
|
|
7115
7994
|
auth;
|
|
7116
7995
|
headless;
|
|
7117
7996
|
computers;
|
|
@@ -7119,6 +7998,7 @@ var BladeClient = class {
|
|
|
7119
7998
|
sessions;
|
|
7120
7999
|
/** 按用户读取的插件目录与图标;没有会话时也能用。 */
|
|
7121
8000
|
plugins;
|
|
8001
|
+
connectors;
|
|
7122
8002
|
chatGroups;
|
|
7123
8003
|
/**
|
|
7124
8004
|
* 全站访问统计:匿名读写,不带 bearer 也不触发登录刷新。
|
|
@@ -7128,23 +8008,34 @@ var BladeClient = class {
|
|
|
7128
8008
|
/** 实时会话中枢:client.sessions.connect() 内部使用,一般不直接访问。 */
|
|
7129
8009
|
hub;
|
|
7130
8010
|
constructor(options) {
|
|
8011
|
+
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)) {
|
|
8012
|
+
throw new Error("\u533F\u540D\u4F1A\u8BDD ID \u5FC5\u987B\u662F UUID v4");
|
|
8013
|
+
}
|
|
7131
8014
|
this.options = {
|
|
7132
8015
|
...options,
|
|
7133
8016
|
baseUrl: normalizeBaseUrl(options.baseUrl)
|
|
7134
8017
|
};
|
|
8018
|
+
this.sandbox = new SandboxResource(this);
|
|
7135
8019
|
this.auth = new AuthResource(this);
|
|
7136
8020
|
this.headless = new HeadlessResource(this);
|
|
7137
8021
|
this.computers = new ComputersResource(this);
|
|
7138
8022
|
this.models = new ModelsResource(this);
|
|
7139
8023
|
this.sessions = new SessionsResource(this);
|
|
7140
8024
|
this.plugins = new PluginsResource(this);
|
|
8025
|
+
this.connectors = new ConnectorsResource(this);
|
|
7141
8026
|
this.chatGroups = new ChatGroupsResource(this);
|
|
7142
8027
|
this.siteVisits = new SiteVisitsResource(this);
|
|
7143
8028
|
this.hub = new SessionHub(this);
|
|
7144
|
-
if (this.options.token === void 0) {
|
|
7145
|
-
this.runtimeToken = readStoredToken(this.options.baseUrl);
|
|
8029
|
+
if (this.options.token === void 0 && !this.options.anonymousSessionId) {
|
|
8030
|
+
this.runtimeToken = readStoredToken(this.options.baseUrl) ?? void 0;
|
|
7146
8031
|
}
|
|
7147
8032
|
}
|
|
8033
|
+
/** Backend application tools, callable over an outbound WebSocket. */
|
|
8034
|
+
connectToolProvider(options) {
|
|
8035
|
+
const provider = new ToolProvider(this, options, () => this.toolProviders.delete(provider));
|
|
8036
|
+
this.toolProviders.add(provider);
|
|
8037
|
+
return provider;
|
|
8038
|
+
}
|
|
7148
8039
|
/**
|
|
7149
8040
|
* 弹窗授权登录:用户在弹出的授权页点击"许可授权"后自动取得 PAT,
|
|
7150
8041
|
* REST 与 Socket.IO 立即生效。只在浏览器可用。
|
|
@@ -7164,7 +8055,13 @@ var BladeClient = class {
|
|
|
7164
8055
|
const previousToken = this.resolveToken();
|
|
7165
8056
|
this.runtimeToken = token;
|
|
7166
8057
|
const nextToken = this.resolveToken();
|
|
8058
|
+
if (token === null) for (const provider of this.toolProviders) provider.dispose();
|
|
7167
8059
|
if (previousToken !== nextToken) {
|
|
8060
|
+
this.options.applicationTransport?.invalidate();
|
|
8061
|
+
for (const provider of this.toolProviders) {
|
|
8062
|
+
if (nextToken) provider.reconnect();
|
|
8063
|
+
else provider.dispose();
|
|
8064
|
+
}
|
|
7168
8065
|
this.hub.disposeAll();
|
|
7169
8066
|
}
|
|
7170
8067
|
writeStoredToken(this.options.baseUrl, token, this.options.tokenStorage ?? "local");
|
|
@@ -7197,12 +8094,27 @@ var BladeClient = class {
|
|
|
7197
8094
|
get token() {
|
|
7198
8095
|
return this.resolveRestToken();
|
|
7199
8096
|
}
|
|
8097
|
+
/** 获取只访问指定匿名会话的客户端;创建仍由已认证的原客户端完成。 */
|
|
8098
|
+
forAnonymousSession(sessionId) {
|
|
8099
|
+
let client = this.anonymousClients.get(sessionId);
|
|
8100
|
+
if (!client) {
|
|
8101
|
+
client = new _BladeClient({ baseUrl: this.options.baseUrl, anonymousSessionId: sessionId, fetchImpl: this.options.fetchImpl, commandTransport: this.options.commandTransport });
|
|
8102
|
+
this.anonymousClients.set(sessionId, client);
|
|
8103
|
+
}
|
|
8104
|
+
return client;
|
|
8105
|
+
}
|
|
7200
8106
|
setBaseUrl(baseUrl) {
|
|
7201
8107
|
const nextBaseUrl = normalizeBaseUrl(baseUrl);
|
|
7202
8108
|
if (nextBaseUrl === this.options.baseUrl) {
|
|
7203
8109
|
return;
|
|
7204
8110
|
}
|
|
8111
|
+
for (const provider of this.toolProviders) provider.dispose();
|
|
7205
8112
|
this.hub.disposeAll();
|
|
8113
|
+
for (const client of this.anonymousClients.values()) {
|
|
8114
|
+
client.hub.disposeAll();
|
|
8115
|
+
client.hub.disconnect();
|
|
8116
|
+
}
|
|
8117
|
+
this.anonymousClients.clear();
|
|
7206
8118
|
this.options.baseUrl = nextBaseUrl;
|
|
7207
8119
|
if (this.socketInstance) {
|
|
7208
8120
|
this.socketInstance.removeAllListeners();
|
|
@@ -7227,7 +8139,7 @@ var BladeClient = class {
|
|
|
7227
8139
|
}
|
|
7228
8140
|
socket() {
|
|
7229
8141
|
if (!this.socketInstance) {
|
|
7230
|
-
this.socketInstance = createSocket({
|
|
8142
|
+
this.socketInstance = this.options.applicationTransport?.socket() ?? createSocket({
|
|
7231
8143
|
baseUrl: this.options.baseUrl,
|
|
7232
8144
|
token: () => this.resolveSocketToken()
|
|
7233
8145
|
});
|
|
@@ -7242,7 +8154,10 @@ var BladeClient = class {
|
|
|
7242
8154
|
else resolve(ack ?? {});
|
|
7243
8155
|
});
|
|
7244
8156
|
});
|
|
7245
|
-
|
|
8157
|
+
const transport = this.options.commandTransport;
|
|
8158
|
+
if (!transport) return send();
|
|
8159
|
+
const handled = this.options.anonymousSessionId ? transport(event, payload, send, { anonymousSessionId: this.options.anonymousSessionId }) : transport(event, payload, send);
|
|
8160
|
+
return handled ?? send();
|
|
7246
8161
|
}
|
|
7247
8162
|
async json(method, path, body) {
|
|
7248
8163
|
const response = await this.fetch(method, path, {
|
|
@@ -7300,24 +8215,31 @@ var BladeClient = class {
|
|
|
7300
8215
|
}
|
|
7301
8216
|
async fetch(method, path, init = {}, isRetry = false) {
|
|
7302
8217
|
const url = this.buildUrl(path);
|
|
7303
|
-
const
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
8218
|
+
const isLogout = method === "POST" && url.pathname === this.buildUrl("/api/auth/logout").pathname && this.isSameBackendUrl(url);
|
|
8219
|
+
if (isLogout) this.cookieLogoutPending += 1;
|
|
8220
|
+
try {
|
|
8221
|
+
if (isLogout) await this.refreshPromise;
|
|
8222
|
+
const response = await (this.options.fetchImpl ?? fetch)(url.toString(), {
|
|
8223
|
+
method,
|
|
8224
|
+
body: init.body,
|
|
8225
|
+
credentials: init.credentials ?? "include",
|
|
8226
|
+
headers: this.buildHeaders(url, init.headers),
|
|
8227
|
+
signal: init.signal
|
|
8228
|
+
});
|
|
8229
|
+
if (response.status === 401 && !isRetry && this.shouldRefreshFor401(url)) {
|
|
8230
|
+
const refreshed = await this.tryRefreshUnlessAborted(init.signal);
|
|
8231
|
+
if (refreshed) {
|
|
8232
|
+
return this.fetch(method, path, init, true);
|
|
8233
|
+
}
|
|
7314
8234
|
}
|
|
8235
|
+
if (init.expectOk !== false && !response.ok) {
|
|
8236
|
+
const detail = await extractErrorDetail(response);
|
|
8237
|
+
throw decorateAuthError(new BladeApiError(response, detail));
|
|
8238
|
+
}
|
|
8239
|
+
return response;
|
|
8240
|
+
} finally {
|
|
8241
|
+
if (isLogout) this.cookieLogoutPending -= 1;
|
|
7315
8242
|
}
|
|
7316
|
-
if (init.expectOk !== false && !response.ok) {
|
|
7317
|
-
const detail = await extractErrorDetail(response);
|
|
7318
|
-
throw decorateAuthError(new BladeApiError(response, detail));
|
|
7319
|
-
}
|
|
7320
|
-
return response;
|
|
7321
8243
|
}
|
|
7322
8244
|
buildAuthedUrl(path) {
|
|
7323
8245
|
const url = buildBackendUrl(this.options.baseUrl, path);
|
|
@@ -7363,13 +8285,6 @@ var BladeClient = class {
|
|
|
7363
8285
|
hasExplicitBearerToken() {
|
|
7364
8286
|
return Boolean(this.resolveRestToken());
|
|
7365
8287
|
}
|
|
7366
|
-
/**
|
|
7367
|
-
* 等一次令牌刷新,但调用方中止请求时不再等下去。
|
|
7368
|
-
*
|
|
7369
|
-
* 刷新本身是共享的(`refreshPromise`,别的请求也在等它),这里只是让这次已被取消的
|
|
7370
|
-
* 请求尽早结束——否则它会一直挂在调用方(定位)的串行队列里,用户改选的新目标要等到
|
|
7371
|
-
* 刷新结束才轮得到。
|
|
7372
|
-
*/
|
|
7373
8288
|
async tryRefreshUnlessAborted(signal) {
|
|
7374
8289
|
if (!signal) return this.tryRefresh();
|
|
7375
8290
|
if (signal.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
@@ -7385,6 +8300,7 @@ var BladeClient = class {
|
|
|
7385
8300
|
}
|
|
7386
8301
|
}
|
|
7387
8302
|
async tryRefresh() {
|
|
8303
|
+
if (this.options.anonymousSessionId || this.cookieLogoutPending > 0) return false;
|
|
7388
8304
|
if (this.refreshPromise) {
|
|
7389
8305
|
return this.refreshPromise;
|
|
7390
8306
|
}
|
|
@@ -7395,7 +8311,6 @@ var BladeClient = class {
|
|
|
7395
8311
|
{
|
|
7396
8312
|
method: "POST",
|
|
7397
8313
|
credentials: "include",
|
|
7398
|
-
// Refresh remains cookie-only; do not attach the REST bearer.
|
|
7399
8314
|
headers: { "X-Blade-Client": `${SDK_NAME}/${SDK_VERSION}` }
|
|
7400
8315
|
}
|
|
7401
8316
|
);
|
|
@@ -7419,14 +8334,16 @@ var BladeClient = class {
|
|
|
7419
8334
|
return this.resolveRestToken();
|
|
7420
8335
|
}
|
|
7421
8336
|
resolveRestToken() {
|
|
8337
|
+
if (this.options.anonymousSessionId) return `ba-anonymous-${this.options.anonymousSessionId}`;
|
|
7422
8338
|
return this.resolveToken();
|
|
7423
8339
|
}
|
|
7424
8340
|
resolveSocketToken() {
|
|
7425
|
-
if (this.
|
|
8341
|
+
if (this.options.anonymousSessionId) return `ba-anonymous-${this.options.anonymousSessionId}`;
|
|
8342
|
+
if (this.runtimeToken !== void 0) return this.runtimeToken;
|
|
7426
8343
|
return resolveTokenOption(this.options.socketToken ?? this.options.token);
|
|
7427
8344
|
}
|
|
7428
8345
|
resolveToken() {
|
|
7429
|
-
if (this.runtimeToken) return this.runtimeToken;
|
|
8346
|
+
if (this.runtimeToken !== void 0) return this.runtimeToken;
|
|
7430
8347
|
return resolveTokenOption(this.options.token);
|
|
7431
8348
|
}
|
|
7432
8349
|
formDataWithUploadProgress(method, path, form, options, isRetry = false) {
|
|
@@ -7523,23 +8440,601 @@ function parseXhrHeaders(rawHeaders) {
|
|
|
7523
8440
|
return headers;
|
|
7524
8441
|
}
|
|
7525
8442
|
|
|
8443
|
+
// src/application-client.ts
|
|
8444
|
+
var Events = class {
|
|
8445
|
+
listeners = /* @__PURE__ */ new Map();
|
|
8446
|
+
on(name, listener) {
|
|
8447
|
+
let set = this.listeners.get(name);
|
|
8448
|
+
if (!set) {
|
|
8449
|
+
set = /* @__PURE__ */ new Set();
|
|
8450
|
+
this.listeners.set(name, set);
|
|
8451
|
+
}
|
|
8452
|
+
set.add(listener);
|
|
8453
|
+
return this;
|
|
8454
|
+
}
|
|
8455
|
+
off(name, listener) {
|
|
8456
|
+
if (listener) this.listeners.get(name)?.delete(listener);
|
|
8457
|
+
else this.listeners.delete(name);
|
|
8458
|
+
return this;
|
|
8459
|
+
}
|
|
8460
|
+
once(name, listener) {
|
|
8461
|
+
const wrapped = (...args) => {
|
|
8462
|
+
this.off(name, wrapped);
|
|
8463
|
+
listener(...args);
|
|
8464
|
+
};
|
|
8465
|
+
return this.on(name, wrapped);
|
|
8466
|
+
}
|
|
8467
|
+
fire(name, ...args) {
|
|
8468
|
+
for (const listener of [...this.listeners.get(name) ?? []]) {
|
|
8469
|
+
try {
|
|
8470
|
+
listener(...args);
|
|
8471
|
+
} catch (error) {
|
|
8472
|
+
console.warn(
|
|
8473
|
+
`[agent-client] "${name}" application handler failed:`,
|
|
8474
|
+
error
|
|
8475
|
+
);
|
|
8476
|
+
}
|
|
8477
|
+
}
|
|
8478
|
+
}
|
|
8479
|
+
removeAllListeners() {
|
|
8480
|
+
this.listeners.clear();
|
|
8481
|
+
return this;
|
|
8482
|
+
}
|
|
8483
|
+
};
|
|
8484
|
+
function encode(input) {
|
|
8485
|
+
const attachments = [];
|
|
8486
|
+
const walk = (value) => {
|
|
8487
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
|
8488
|
+
const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
8489
|
+
let text = "";
|
|
8490
|
+
for (let offset = 0; offset < bytes.length; offset += 8192)
|
|
8491
|
+
text += String.fromCharCode(...bytes.subarray(offset, offset + 8192));
|
|
8492
|
+
attachments.push(btoa(text));
|
|
8493
|
+
return ["bytes", attachments.length - 1];
|
|
8494
|
+
}
|
|
8495
|
+
if (Array.isArray(value)) return ["list", value.map(walk)];
|
|
8496
|
+
if (value && typeof value === "object")
|
|
8497
|
+
return [
|
|
8498
|
+
"dict",
|
|
8499
|
+
Object.entries(value).map(([key, item]) => [key, walk(item)])
|
|
8500
|
+
];
|
|
8501
|
+
return ["value", value ?? null];
|
|
8502
|
+
};
|
|
8503
|
+
return { value: walk(input), attachments };
|
|
8504
|
+
}
|
|
8505
|
+
function decode(wire) {
|
|
8506
|
+
const walk = (node) => {
|
|
8507
|
+
if (!Array.isArray(node) || node.length !== 2)
|
|
8508
|
+
throw new Error("Invalid application value");
|
|
8509
|
+
if (node[0] === "value") return node[1];
|
|
8510
|
+
if (node[0] === "bytes")
|
|
8511
|
+
return Uint8Array.from(
|
|
8512
|
+
atob(wire.attachments[node[1]]),
|
|
8513
|
+
(c) => c.charCodeAt(0)
|
|
8514
|
+
);
|
|
8515
|
+
if (node[0] === "list") return node[1].map(walk);
|
|
8516
|
+
if (node[0] === "dict")
|
|
8517
|
+
return Object.fromEntries(
|
|
8518
|
+
node[1].map(([key, value2]) => [key, walk(value2)])
|
|
8519
|
+
);
|
|
8520
|
+
throw new Error("Invalid application value");
|
|
8521
|
+
};
|
|
8522
|
+
const value = walk(wire.value);
|
|
8523
|
+
if (!Array.isArray(value)) throw new Error("Invalid application event");
|
|
8524
|
+
return value;
|
|
8525
|
+
}
|
|
8526
|
+
var BladeApplicationClient = class {
|
|
8527
|
+
constructor(options) {
|
|
8528
|
+
this.options = options;
|
|
8529
|
+
this.channels = [
|
|
8530
|
+
new ApplicationChannel(this.options.baseUrl, this.applicationId),
|
|
8531
|
+
new ApplicationChannel(this.options.baseUrl, this.applicationId)
|
|
8532
|
+
];
|
|
8533
|
+
}
|
|
8534
|
+
options;
|
|
8535
|
+
channels;
|
|
8536
|
+
clients = /* @__PURE__ */ new Map();
|
|
8537
|
+
applicationId = crypto.randomUUID();
|
|
8538
|
+
forUser(userId, options) {
|
|
8539
|
+
const saved = this.clients.get(userId);
|
|
8540
|
+
if (saved) return saved;
|
|
8541
|
+
let hash = 0;
|
|
8542
|
+
for (const character of userId)
|
|
8543
|
+
hash = hash * 31 + character.charCodeAt(0) | 0;
|
|
8544
|
+
const channel = this.channels[(hash >>> 0) % this.channels.length];
|
|
8545
|
+
const user = new ApplicationUser(channel, userId, () => client);
|
|
8546
|
+
const client = new BladeClient({
|
|
8547
|
+
...this.options,
|
|
8548
|
+
...options,
|
|
8549
|
+
tokenStorage: options.tokenStorage ?? "memory",
|
|
8550
|
+
applicationTransport: user
|
|
8551
|
+
});
|
|
8552
|
+
this.clients.set(userId, client);
|
|
8553
|
+
return client;
|
|
8554
|
+
}
|
|
8555
|
+
get connectionCount() {
|
|
8556
|
+
return this.channels.filter((channel) => channel.isConnected).length;
|
|
8557
|
+
}
|
|
8558
|
+
dispose() {
|
|
8559
|
+
for (const client of this.clients.values()) {
|
|
8560
|
+
client.hub.disposeAll();
|
|
8561
|
+
client.logoutToken();
|
|
8562
|
+
}
|
|
8563
|
+
for (const channel of this.channels) channel.dispose();
|
|
8564
|
+
this.clients.clear();
|
|
8565
|
+
}
|
|
8566
|
+
};
|
|
8567
|
+
var ApplicationChannel = class {
|
|
8568
|
+
constructor(baseUrl, applicationId) {
|
|
8569
|
+
this.baseUrl = baseUrl;
|
|
8570
|
+
this.applicationId = applicationId;
|
|
8571
|
+
}
|
|
8572
|
+
baseUrl;
|
|
8573
|
+
applicationId;
|
|
8574
|
+
socket;
|
|
8575
|
+
opening;
|
|
8576
|
+
timer;
|
|
8577
|
+
stopped = false;
|
|
8578
|
+
attempts = 0;
|
|
8579
|
+
controlCount = 0;
|
|
8580
|
+
controlWaiters = [];
|
|
8581
|
+
async controlSlot() {
|
|
8582
|
+
if (this.controlCount >= 16)
|
|
8583
|
+
await new Promise((resolve) => this.controlWaiters.push(resolve));
|
|
8584
|
+
else this.controlCount++;
|
|
8585
|
+
return () => {
|
|
8586
|
+
const next = this.controlWaiters.shift();
|
|
8587
|
+
if (next) next();
|
|
8588
|
+
else this.controlCount--;
|
|
8589
|
+
};
|
|
8590
|
+
}
|
|
8591
|
+
pending = /* @__PURE__ */ new Map();
|
|
8592
|
+
users = /* @__PURE__ */ new Map();
|
|
8593
|
+
get isConnected() {
|
|
8594
|
+
return this.socket?.readyState === WebSocket.OPEN;
|
|
8595
|
+
}
|
|
8596
|
+
ensure() {
|
|
8597
|
+
if (this.stopped)
|
|
8598
|
+
return Promise.reject(new Error("Application client disposed"));
|
|
8599
|
+
if (this.isConnected) return Promise.resolve();
|
|
8600
|
+
if (!this.opening) {
|
|
8601
|
+
const work = this.open();
|
|
8602
|
+
this.opening = work;
|
|
8603
|
+
void work.finally(() => {
|
|
8604
|
+
if (this.opening === work) this.opening = void 0;
|
|
8605
|
+
}).catch(() => {
|
|
8606
|
+
});
|
|
8607
|
+
}
|
|
8608
|
+
return this.opening;
|
|
8609
|
+
}
|
|
8610
|
+
async open() {
|
|
8611
|
+
clearTimeout(this.timer);
|
|
8612
|
+
this.timer = void 0;
|
|
8613
|
+
const candidates = [...this.users.values()].filter(
|
|
8614
|
+
(user2) => user2.demanded && user2.client().token
|
|
8615
|
+
);
|
|
8616
|
+
const user = candidates[this.attempts % candidates.length];
|
|
8617
|
+
let ticket;
|
|
8618
|
+
try {
|
|
8619
|
+
if (!user)
|
|
8620
|
+
throw new Error(
|
|
8621
|
+
"Application channel requires a demanded user credential"
|
|
8622
|
+
);
|
|
8623
|
+
ticket = await user.client().jsonFromInit("/api/app-channels", {
|
|
8624
|
+
method: "POST",
|
|
8625
|
+
body: JSON.stringify({ application_id: this.applicationId }),
|
|
8626
|
+
signal: AbortSignal.timeout(1e4)
|
|
8627
|
+
});
|
|
8628
|
+
} catch (error) {
|
|
8629
|
+
this.schedule();
|
|
8630
|
+
throw error;
|
|
8631
|
+
}
|
|
8632
|
+
if (this.stopped) throw new Error("Application client disposed");
|
|
8633
|
+
const endpoint = buildBackendUrl(this.baseUrl, "/api/app-channels/connect");
|
|
8634
|
+
endpoint.protocol = endpoint.protocol === "https:" ? "wss:" : "ws:";
|
|
8635
|
+
const socket = new WebSocket(endpoint, [
|
|
8636
|
+
"blade-application",
|
|
8637
|
+
`blade-channel.${ticket.token}`
|
|
8638
|
+
]);
|
|
8639
|
+
this.socket = socket;
|
|
8640
|
+
await new Promise((resolve, reject) => {
|
|
8641
|
+
const timeout = setTimeout(() => {
|
|
8642
|
+
reject(new Error("Application connection timed out"));
|
|
8643
|
+
socket.close();
|
|
8644
|
+
}, 15e3);
|
|
8645
|
+
socket.addEventListener("open", () => {
|
|
8646
|
+
if (this.socket !== socket || this.stopped) return;
|
|
8647
|
+
clearTimeout(timeout);
|
|
8648
|
+
this.attempts = 0;
|
|
8649
|
+
resolve();
|
|
8650
|
+
for (const user2 of this.users.values()) user2.restore();
|
|
8651
|
+
});
|
|
8652
|
+
socket.addEventListener("message", (event) => {
|
|
8653
|
+
if (this.socket !== socket || typeof event.data !== "string") return;
|
|
8654
|
+
try {
|
|
8655
|
+
this.receive(JSON.parse(event.data));
|
|
8656
|
+
} catch {
|
|
8657
|
+
socket.close();
|
|
8658
|
+
}
|
|
8659
|
+
});
|
|
8660
|
+
socket.addEventListener("error", () => {
|
|
8661
|
+
clearTimeout(timeout);
|
|
8662
|
+
reject(new Error("Application connection failed"));
|
|
8663
|
+
});
|
|
8664
|
+
socket.addEventListener("close", () => {
|
|
8665
|
+
clearTimeout(timeout);
|
|
8666
|
+
reject(new Error("Application connection closed"));
|
|
8667
|
+
if (this.socket !== socket) return;
|
|
8668
|
+
this.socket = void 0;
|
|
8669
|
+
for (const request of this.pending.values()) {
|
|
8670
|
+
clearTimeout(request.timer);
|
|
8671
|
+
request.reject(new Error("Application connection closed"));
|
|
8672
|
+
}
|
|
8673
|
+
this.pending.clear();
|
|
8674
|
+
for (const user2 of this.users.values()) user2.lost();
|
|
8675
|
+
this.schedule();
|
|
8676
|
+
});
|
|
8677
|
+
});
|
|
8678
|
+
}
|
|
8679
|
+
schedule() {
|
|
8680
|
+
if (this.stopped || this.timer || ![...this.users.values()].some((user) => user.demanded))
|
|
8681
|
+
return;
|
|
8682
|
+
this.timer = setTimeout(
|
|
8683
|
+
() => {
|
|
8684
|
+
this.timer = void 0;
|
|
8685
|
+
void this.ensure().catch(() => this.schedule());
|
|
8686
|
+
},
|
|
8687
|
+
Math.min(1e4, 500 * 2 ** Math.min(this.attempts++, 5))
|
|
8688
|
+
);
|
|
8689
|
+
}
|
|
8690
|
+
receive(frame) {
|
|
8691
|
+
if (frame.id) {
|
|
8692
|
+
const request = this.pending.get(frame.id);
|
|
8693
|
+
if (!request || request.user !== frame.user) return;
|
|
8694
|
+
this.pending.delete(frame.id);
|
|
8695
|
+
clearTimeout(request.timer);
|
|
8696
|
+
if (frame.error)
|
|
8697
|
+
request.reject(
|
|
8698
|
+
Object.assign(new Error(frame.error.message), {
|
|
8699
|
+
code: frame.error.code,
|
|
8700
|
+
data: {
|
|
8701
|
+
code: frame.error.code,
|
|
8702
|
+
status: frame.error.code === "auth_busy" ? 429 : 401,
|
|
8703
|
+
retry_after: 1
|
|
8704
|
+
}
|
|
8705
|
+
})
|
|
8706
|
+
);
|
|
8707
|
+
else request.resolve(frame.result);
|
|
8708
|
+
return;
|
|
8709
|
+
}
|
|
8710
|
+
const user = frame.user && this.users.get(frame.user);
|
|
8711
|
+
if (user) user.receive(frame);
|
|
8712
|
+
}
|
|
8713
|
+
async request(user, method, params, timeoutMs = 3e4) {
|
|
8714
|
+
const release = method === "socket/event" ? () => {
|
|
8715
|
+
} : await this.controlSlot();
|
|
8716
|
+
try {
|
|
8717
|
+
return await new Promise((resolve, reject) => {
|
|
8718
|
+
const id = crypto.randomUUID();
|
|
8719
|
+
const timer = setTimeout(() => {
|
|
8720
|
+
this.pending.delete(id);
|
|
8721
|
+
reject(new Error("Application request timed out"));
|
|
8722
|
+
}, timeoutMs);
|
|
8723
|
+
this.pending.set(id, { user, resolve, reject, timer });
|
|
8724
|
+
try {
|
|
8725
|
+
this.send({ id, user, method, params });
|
|
8726
|
+
} catch (error) {
|
|
8727
|
+
clearTimeout(timer);
|
|
8728
|
+
this.pending.delete(id);
|
|
8729
|
+
reject(error);
|
|
8730
|
+
}
|
|
8731
|
+
});
|
|
8732
|
+
} finally {
|
|
8733
|
+
release();
|
|
8734
|
+
}
|
|
8735
|
+
}
|
|
8736
|
+
send(frame) {
|
|
8737
|
+
if (!this.isConnected || !this.socket)
|
|
8738
|
+
throw new Error("Application connection unavailable");
|
|
8739
|
+
this.socket.send(JSON.stringify(frame));
|
|
8740
|
+
}
|
|
8741
|
+
dispose() {
|
|
8742
|
+
this.stopped = true;
|
|
8743
|
+
clearTimeout(this.timer);
|
|
8744
|
+
this.timer = void 0;
|
|
8745
|
+
for (const user of this.users.values()) user.lost();
|
|
8746
|
+
this.socket?.close();
|
|
8747
|
+
for (const request of this.pending.values()) {
|
|
8748
|
+
clearTimeout(request.timer);
|
|
8749
|
+
request.reject(new Error("Application client disposed"));
|
|
8750
|
+
}
|
|
8751
|
+
this.pending.clear();
|
|
8752
|
+
this.users.clear();
|
|
8753
|
+
}
|
|
8754
|
+
};
|
|
8755
|
+
var ApplicationUser = class {
|
|
8756
|
+
constructor(channel, userId, client) {
|
|
8757
|
+
this.channel = channel;
|
|
8758
|
+
this.userId = userId;
|
|
8759
|
+
this.client = client;
|
|
8760
|
+
this.events = new ApplicationSocket(this);
|
|
8761
|
+
channel.users.set(this.key, this);
|
|
8762
|
+
}
|
|
8763
|
+
channel;
|
|
8764
|
+
userId;
|
|
8765
|
+
client;
|
|
8766
|
+
key = crypto.randomUUID();
|
|
8767
|
+
events;
|
|
8768
|
+
providers = /* @__PURE__ */ new Map();
|
|
8769
|
+
binding;
|
|
8770
|
+
bindingToken;
|
|
8771
|
+
get demanded() {
|
|
8772
|
+
return this.events.active || this.providers.size > 0;
|
|
8773
|
+
}
|
|
8774
|
+
async bind() {
|
|
8775
|
+
await this.channel.ensure();
|
|
8776
|
+
if (!this.demanded) throw new Error("Application user no longer needed");
|
|
8777
|
+
const token = this.client().token;
|
|
8778
|
+
if (!token) throw new Error("Application user requires a token");
|
|
8779
|
+
if (!this.binding || this.bindingToken !== token) {
|
|
8780
|
+
this.bindingToken = token;
|
|
8781
|
+
const work = this.channel.request(this.key, "user/bind", { user_id: this.userId, token }).then((result) => {
|
|
8782
|
+
if (result.user_id !== this.userId)
|
|
8783
|
+
throw new Error("Application user identity mismatch");
|
|
8784
|
+
if (!this.demanded) {
|
|
8785
|
+
this.binding = void 0;
|
|
8786
|
+
this.bindingToken = void 0;
|
|
8787
|
+
void this.channel.request(this.key, "user/unbind", {}).catch(() => {
|
|
8788
|
+
});
|
|
8789
|
+
throw new Error("Application user no longer needed");
|
|
8790
|
+
}
|
|
8791
|
+
});
|
|
8792
|
+
this.binding = work;
|
|
8793
|
+
void work.catch(() => {
|
|
8794
|
+
if (this.binding === work) this.binding = void 0;
|
|
8795
|
+
});
|
|
8796
|
+
}
|
|
8797
|
+
return this.binding;
|
|
8798
|
+
}
|
|
8799
|
+
socket() {
|
|
8800
|
+
return this.events;
|
|
8801
|
+
}
|
|
8802
|
+
provider() {
|
|
8803
|
+
const provider = new ApplicationProviderSocket(this);
|
|
8804
|
+
this.providers.set(provider.handle, provider);
|
|
8805
|
+
provider.open();
|
|
8806
|
+
return provider;
|
|
8807
|
+
}
|
|
8808
|
+
invalidate() {
|
|
8809
|
+
this.binding = void 0;
|
|
8810
|
+
this.bindingToken = void 0;
|
|
8811
|
+
if (!this.client().token && this.channel.isConnected)
|
|
8812
|
+
void this.channel.request(this.key, "user/unbind", {}).catch(() => {
|
|
8813
|
+
});
|
|
8814
|
+
}
|
|
8815
|
+
restore() {
|
|
8816
|
+
if (this.events.active) this.events.connect();
|
|
8817
|
+
}
|
|
8818
|
+
lost() {
|
|
8819
|
+
this.invalidate();
|
|
8820
|
+
this.events.lost();
|
|
8821
|
+
for (const provider of [...this.providers.values()]) provider.closeLocal();
|
|
8822
|
+
}
|
|
8823
|
+
receive(frame) {
|
|
8824
|
+
if (frame.method === "socket/closed") {
|
|
8825
|
+
this.events.lost();
|
|
8826
|
+
if (this.events.active) this.events.connect();
|
|
8827
|
+
return;
|
|
8828
|
+
}
|
|
8829
|
+
if (frame.method === "tool/closed") {
|
|
8830
|
+
this.providers.get(frame.handle ?? "")?.closeLocal();
|
|
8831
|
+
return;
|
|
8832
|
+
}
|
|
8833
|
+
if (frame.method === "tool/message") {
|
|
8834
|
+
this.providers.get(frame.handle ?? "")?.message(frame.message);
|
|
8835
|
+
return;
|
|
8836
|
+
}
|
|
8837
|
+
if (frame.event && frame.args) {
|
|
8838
|
+
const args = decode(frame.args);
|
|
8839
|
+
if (frame.ack_id)
|
|
8840
|
+
args.push(
|
|
8841
|
+
(...values) => this.channel.send({
|
|
8842
|
+
method: "event/ack",
|
|
8843
|
+
user: this.key,
|
|
8844
|
+
ack_id: frame.ack_id,
|
|
8845
|
+
params: encode(values)
|
|
8846
|
+
})
|
|
8847
|
+
);
|
|
8848
|
+
this.events.fire(frame.event, ...args);
|
|
8849
|
+
}
|
|
8850
|
+
}
|
|
8851
|
+
};
|
|
8852
|
+
var ApplicationSocket = class extends Events {
|
|
8853
|
+
constructor(user) {
|
|
8854
|
+
super();
|
|
8855
|
+
this.user = user;
|
|
8856
|
+
}
|
|
8857
|
+
user;
|
|
8858
|
+
connected = false;
|
|
8859
|
+
active = false;
|
|
8860
|
+
io = new Events();
|
|
8861
|
+
connecting;
|
|
8862
|
+
generation = 0;
|
|
8863
|
+
connect() {
|
|
8864
|
+
this.active = true;
|
|
8865
|
+
if (this.connected || this.connecting) return this;
|
|
8866
|
+
const generation = ++this.generation;
|
|
8867
|
+
const work = this.user.bind().then(() => {
|
|
8868
|
+
if (generation !== this.generation || !this.active) return;
|
|
8869
|
+
return this.user.channel.request(this.user.key, "socket/connect", {});
|
|
8870
|
+
}).then(() => {
|
|
8871
|
+
if (generation !== this.generation || !this.active) return;
|
|
8872
|
+
this.connected = true;
|
|
8873
|
+
this.fire("connect");
|
|
8874
|
+
}).catch((error) => {
|
|
8875
|
+
if (generation === this.generation && this.active) {
|
|
8876
|
+
if (error?.code === "auth_busy" || error?.code === "unauthorized")
|
|
8877
|
+
this.active = false;
|
|
8878
|
+
this.fire("connect_error", error);
|
|
8879
|
+
}
|
|
8880
|
+
}).finally(() => {
|
|
8881
|
+
if (this.connecting === work) this.connecting = void 0;
|
|
8882
|
+
});
|
|
8883
|
+
this.connecting = work;
|
|
8884
|
+
return this;
|
|
8885
|
+
}
|
|
8886
|
+
disconnect() {
|
|
8887
|
+
this.active = false;
|
|
8888
|
+
this.lost();
|
|
8889
|
+
if (this.user.channel.isConnected)
|
|
8890
|
+
void this.user.channel.request(this.user.key, "socket/disconnect", {}).catch(() => {
|
|
8891
|
+
});
|
|
8892
|
+
return this;
|
|
8893
|
+
}
|
|
8894
|
+
lost() {
|
|
8895
|
+
const wasConnected = this.connected;
|
|
8896
|
+
this.connected = false;
|
|
8897
|
+
this.connecting = void 0;
|
|
8898
|
+
this.generation++;
|
|
8899
|
+
if (wasConnected) this.fire("disconnect", "transport close");
|
|
8900
|
+
}
|
|
8901
|
+
emit(event, ...args) {
|
|
8902
|
+
this.send(event, args);
|
|
8903
|
+
return this;
|
|
8904
|
+
}
|
|
8905
|
+
timeout(timeoutMs) {
|
|
8906
|
+
return {
|
|
8907
|
+
emit: (event, ...args) => {
|
|
8908
|
+
this.send(event, args, timeoutMs);
|
|
8909
|
+
return this;
|
|
8910
|
+
}
|
|
8911
|
+
};
|
|
8912
|
+
}
|
|
8913
|
+
send(event, values, timeoutMs) {
|
|
8914
|
+
const args = [...values];
|
|
8915
|
+
const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
|
|
8916
|
+
const generation = this.generation;
|
|
8917
|
+
const deadline = Date.now() + (timeoutMs ?? 3e4);
|
|
8918
|
+
const work = (async () => {
|
|
8919
|
+
if (!this.active) throw new Error("Application user disconnected");
|
|
8920
|
+
if (!this.connected) await this.connecting;
|
|
8921
|
+
if (!this.connected || this.generation !== generation)
|
|
8922
|
+
throw new Error("Application user disconnected");
|
|
8923
|
+
const remaining = deadline - Date.now();
|
|
8924
|
+
if (remaining <= 0) throw new Error("Application request timed out");
|
|
8925
|
+
const params = { event, args: encode(args) };
|
|
8926
|
+
if (!callback) {
|
|
8927
|
+
this.user.channel.send({
|
|
8928
|
+
user: this.user.key,
|
|
8929
|
+
method: "socket/event",
|
|
8930
|
+
params
|
|
8931
|
+
});
|
|
8932
|
+
return;
|
|
8933
|
+
}
|
|
8934
|
+
return this.user.channel.request(
|
|
8935
|
+
this.user.key,
|
|
8936
|
+
"socket/event",
|
|
8937
|
+
params,
|
|
8938
|
+
remaining
|
|
8939
|
+
);
|
|
8940
|
+
})();
|
|
8941
|
+
void work.then(
|
|
8942
|
+
(result) => {
|
|
8943
|
+
if (!callback) return;
|
|
8944
|
+
const values2 = decode(result);
|
|
8945
|
+
if (timeoutMs !== void 0) callback?.(null, ...values2);
|
|
8946
|
+
else callback?.(...values2);
|
|
8947
|
+
},
|
|
8948
|
+
(error) => {
|
|
8949
|
+
if (timeoutMs !== void 0) callback?.(error);
|
|
8950
|
+
else if (callback) callback(error);
|
|
8951
|
+
}
|
|
8952
|
+
);
|
|
8953
|
+
}
|
|
8954
|
+
};
|
|
8955
|
+
var ApplicationProviderSocket = class extends Events {
|
|
8956
|
+
constructor(user) {
|
|
8957
|
+
super();
|
|
8958
|
+
this.user = user;
|
|
8959
|
+
}
|
|
8960
|
+
user;
|
|
8961
|
+
handle = crypto.randomUUID();
|
|
8962
|
+
readyState = WebSocket.CONNECTING;
|
|
8963
|
+
addEventListener(name, listener) {
|
|
8964
|
+
this.on(name, listener);
|
|
8965
|
+
}
|
|
8966
|
+
open() {
|
|
8967
|
+
void this.user.bind().then(
|
|
8968
|
+
() => {
|
|
8969
|
+
if (this.readyState !== WebSocket.CONNECTING) return;
|
|
8970
|
+
this.readyState = WebSocket.OPEN;
|
|
8971
|
+
this.fire("open", new Event("open"));
|
|
8972
|
+
},
|
|
8973
|
+
() => {
|
|
8974
|
+
if (this.readyState !== WebSocket.CLOSED) {
|
|
8975
|
+
this.fire("error", new Event("error"));
|
|
8976
|
+
this.closeLocal();
|
|
8977
|
+
}
|
|
8978
|
+
}
|
|
8979
|
+
);
|
|
8980
|
+
}
|
|
8981
|
+
send(text) {
|
|
8982
|
+
if (this.readyState !== WebSocket.OPEN)
|
|
8983
|
+
throw new Error("Application provider closed");
|
|
8984
|
+
const message = JSON.parse(text);
|
|
8985
|
+
if (message.method === "provider/register") {
|
|
8986
|
+
void this.user.channel.request(this.user.key, "tool/register", {
|
|
8987
|
+
...message.params,
|
|
8988
|
+
handle: this.handle
|
|
8989
|
+
}).then(
|
|
8990
|
+
(result) => this.message({ jsonrpc: "2.0", id: message.id, result }),
|
|
8991
|
+
(error) => this.message({
|
|
8992
|
+
jsonrpc: "2.0",
|
|
8993
|
+
id: message.id,
|
|
8994
|
+
error: { code: -32001, message: error.message }
|
|
8995
|
+
})
|
|
8996
|
+
);
|
|
8997
|
+
} else
|
|
8998
|
+
this.user.channel.send({
|
|
8999
|
+
method: "tool/result",
|
|
9000
|
+
user: this.user.key,
|
|
9001
|
+
handle: this.handle,
|
|
9002
|
+
message
|
|
9003
|
+
});
|
|
9004
|
+
}
|
|
9005
|
+
message(message) {
|
|
9006
|
+
if (this.readyState === WebSocket.OPEN)
|
|
9007
|
+
this.fire(
|
|
9008
|
+
"message",
|
|
9009
|
+
new MessageEvent("message", { data: JSON.stringify(message) })
|
|
9010
|
+
);
|
|
9011
|
+
}
|
|
9012
|
+
close() {
|
|
9013
|
+
if (this.readyState === WebSocket.CLOSED) return;
|
|
9014
|
+
if (this.user.channel.isConnected)
|
|
9015
|
+
void this.user.channel.request(this.user.key, "tool/unregister", { handle: this.handle }).catch(() => {
|
|
9016
|
+
});
|
|
9017
|
+
this.closeLocal();
|
|
9018
|
+
}
|
|
9019
|
+
closeLocal() {
|
|
9020
|
+
if (this.readyState === WebSocket.CLOSED) return;
|
|
9021
|
+
this.readyState = WebSocket.CLOSED;
|
|
9022
|
+
this.user.providers.delete(this.handle);
|
|
9023
|
+
this.fire("close", new Event("close"));
|
|
9024
|
+
this.removeAllListeners();
|
|
9025
|
+
}
|
|
9026
|
+
};
|
|
9027
|
+
|
|
7526
9028
|
export {
|
|
9029
|
+
SandboxResource,
|
|
9030
|
+
BladeApiError,
|
|
9031
|
+
ToolProvider,
|
|
7527
9032
|
SDK_NAME,
|
|
7528
9033
|
SDK_VERSION,
|
|
7529
|
-
BladeApiError,
|
|
7530
9034
|
isRecord,
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
computerDaemonVersion,
|
|
7535
|
-
canToggleComputer,
|
|
7536
|
-
sortComputers,
|
|
7537
|
-
ComputersResource,
|
|
7538
|
-
ModelsResource,
|
|
7539
|
-
SessionStatus,
|
|
7540
|
-
DEFAULT_REPLAY_SPEED,
|
|
7541
|
-
SessionInfo,
|
|
7542
|
-
SessionSetupError,
|
|
9035
|
+
normalizePluginRefs,
|
|
9036
|
+
normalizeMessageRefs,
|
|
9037
|
+
normalizeTopicAssetRefs,
|
|
7543
9038
|
inferToolStatus,
|
|
7544
9039
|
contextProjectionData,
|
|
7545
9040
|
getContextDisplayState,
|
|
@@ -7557,9 +9052,24 @@ export {
|
|
|
7557
9052
|
isAppDevToolUiCard,
|
|
7558
9053
|
collectInlineToolUiCards,
|
|
7559
9054
|
collectPreviewToolUiCards,
|
|
9055
|
+
computerState,
|
|
9056
|
+
computerOS,
|
|
9057
|
+
computerPlatformLabel,
|
|
9058
|
+
computerDaemonVersion,
|
|
9059
|
+
sortComputers,
|
|
9060
|
+
ComputersResource,
|
|
9061
|
+
ModelsResource,
|
|
9062
|
+
isLocalConnectorName,
|
|
9063
|
+
ConnectorsResource,
|
|
9064
|
+
SessionStatus,
|
|
9065
|
+
DEFAULT_REPLAY_SPEED,
|
|
9066
|
+
SessionInfo,
|
|
9067
|
+
SessionSetupError,
|
|
7560
9068
|
isEntryGoneError,
|
|
7561
9069
|
PluginsResource,
|
|
7562
9070
|
ChatGroupsResource,
|
|
9071
|
+
syncServerTime,
|
|
9072
|
+
getServerTime,
|
|
7563
9073
|
authBusyRetryDelayMs,
|
|
7564
9074
|
AuthBusyReconnect,
|
|
7565
9075
|
prependOlder,
|
|
@@ -7604,6 +9114,7 @@ export {
|
|
|
7604
9114
|
AgentSession,
|
|
7605
9115
|
SessionHub,
|
|
7606
9116
|
createSocket,
|
|
7607
|
-
BladeClient
|
|
9117
|
+
BladeClient,
|
|
9118
|
+
BladeApplicationClient
|
|
7608
9119
|
};
|
|
7609
|
-
//# sourceMappingURL=chunk-
|
|
9120
|
+
//# sourceMappingURL=chunk-SNMDYU6W.js.map
|