@blade-hq/agent-kit 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +5 -1
  2. package/dist/chunk-2UP7MG3J.js +66 -0
  3. package/dist/chunk-2UP7MG3J.js.map +1 -0
  4. package/dist/chunk-4VWLTG5L.js +2984 -0
  5. package/dist/chunk-4VWLTG5L.js.map +1 -0
  6. package/dist/chunk-7LEKQI47.js +32 -0
  7. package/dist/chunk-7LEKQI47.js.map +1 -0
  8. package/dist/chunk-DQCXSPHP.js +33 -0
  9. package/dist/chunk-DQCXSPHP.js.map +1 -0
  10. package/dist/chunk-I3FFV63W.js +30 -0
  11. package/dist/chunk-I3FFV63W.js.map +1 -0
  12. package/dist/chunk-J3XVFPOV.js +58 -0
  13. package/dist/chunk-J3XVFPOV.js.map +1 -0
  14. package/dist/chunk-JCJFFJ42.js +39 -0
  15. package/dist/chunk-JCJFFJ42.js.map +1 -0
  16. package/dist/chunk-LIL4FIZP.js +7992 -0
  17. package/dist/chunk-LIL4FIZP.js.map +1 -0
  18. package/dist/chunk-OKQWPNE3.js +1077 -0
  19. package/dist/chunk-OKQWPNE3.js.map +1 -0
  20. package/dist/chunk-PZ5AY32C.js +10 -0
  21. package/dist/chunk-PZ5AY32C.js.map +1 -0
  22. package/dist/chunk-TC5BBLWO.js +29 -0
  23. package/dist/chunk-TC5BBLWO.js.map +1 -0
  24. package/dist/chunk-VD4CKRMT.js +127 -0
  25. package/dist/chunk-VD4CKRMT.js.map +1 -0
  26. package/dist/chunk-X6MEYCU7.js +1401 -0
  27. package/dist/chunk-X6MEYCU7.js.map +1 -0
  28. package/dist/client/index.js +24 -1052
  29. package/dist/client/index.js.map +1 -1
  30. package/dist/react/api/licenses.js +11 -1470
  31. package/dist/react/api/licenses.js.map +1 -1
  32. package/dist/react/api/vibe-coding.js +25 -1481
  33. package/dist/react/api/vibe-coding.js.map +1 -1
  34. package/dist/react/cards/register.js +45 -138
  35. package/dist/react/cards/register.js.map +1 -1
  36. package/dist/react/components/chat/index.js +28 -11366
  37. package/dist/react/components/chat/index.js.map +1 -1
  38. package/dist/react/components/plan/index.js +135 -3054
  39. package/dist/react/components/plan/index.js.map +1 -1
  40. package/dist/react/components/session/index.js +21 -1499
  41. package/dist/react/components/session/index.js.map +1 -1
  42. package/dist/react/components/workspace/index.js +116 -1715
  43. package/dist/react/components/workspace/index.js.map +1 -1
  44. package/dist/react/devtools/bridge-devtools/index.js +8 -51
  45. package/dist/react/devtools/bridge-devtools/index.js.map +1 -1
  46. package/dist/react/index.js +625 -14035
  47. package/dist/react/index.js.map +1 -1
  48. package/package.json +1 -1
@@ -0,0 +1,2984 @@
1
+ import {
2
+ tapBridgeEvent
3
+ } from "./chunk-J3XVFPOV.js";
4
+ import {
5
+ BladeClient
6
+ } from "./chunk-OKQWPNE3.js";
7
+ import {
8
+ createClientActions,
9
+ useCardStateStore
10
+ } from "./chunk-JCJFFJ42.js";
11
+ import {
12
+ __export
13
+ } from "./chunk-PZ5AY32C.js";
14
+
15
+ // src/react/api/partner-skill.ts
16
+ var partner_skill_exports = {};
17
+ __export(partner_skill_exports, {
18
+ installPartnerSkill: () => installPartnerSkill
19
+ });
20
+
21
+ // src/react/api/skills.ts
22
+ var skills_exports = {};
23
+ __export(skills_exports, {
24
+ getGlobalSkillStats: () => getGlobalSkillStats,
25
+ getSkill: () => getSkill,
26
+ getSkillStats: () => getSkillStats,
27
+ installPartnerSkill: () => installPartnerSkill,
28
+ listSessionSkills: () => listSessionSkills,
29
+ listSkills: () => listSkills,
30
+ resyncSkills: () => resyncSkills,
31
+ searchSkills: () => searchSkills
32
+ });
33
+ var r = () => getClient().skills;
34
+ var listSkills = (...args) => r().listSkills(...args);
35
+ var listSessionSkills = (...args) => r().listSessionSkills(...args);
36
+ var resyncSkills = (...args) => r().resyncSkills(...args);
37
+ var searchSkills = (...args) => r().searchSkills(...args);
38
+ var getSkillStats = (...args) => r().getSkillStats(...args);
39
+ var getGlobalSkillStats = (...args) => r().getGlobalSkillStats(...args);
40
+ var getSkill = (...args) => r().getSkill(...args);
41
+ var installPartnerSkill = (...args) => r().installPartnerSkill(...args);
42
+
43
+ // src/react/schemas/partner-skill.ts
44
+ import { type } from "arktype";
45
+ var PartnerSkillName = type("/^[a-z0-9-]+\\/[a-z0-9-]+$/");
46
+ var PartnerSkillFile = type({
47
+ path: "string > 0",
48
+ content: "string"
49
+ });
50
+ var PartnerSkillInstallPayload = type({
51
+ name: PartnerSkillName,
52
+ files: PartnerSkillFile.array().atLeastLength(1)
53
+ });
54
+ var PartnerSkillInstallResult = type({
55
+ name: PartnerSkillName,
56
+ skill_dir: "string",
57
+ file_count: "number.integer >= 0",
58
+ overwritten: "boolean"
59
+ });
60
+ function parsePartnerSkillInstallPayload(payload) {
61
+ const parsed = PartnerSkillInstallPayload.assert(payload);
62
+ if (!parsed.files.some((file) => file.path === "SKILL.md")) {
63
+ throw new Error("installSkill payload must include SKILL.md");
64
+ }
65
+ return parsed;
66
+ }
67
+
68
+ // src/react/stores/ui-bridge-store.ts
69
+ import { create } from "zustand";
70
+ function buildSignalId() {
71
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
72
+ return crypto.randomUUID();
73
+ }
74
+ return `bridge-${Date.now()}-${Math.random().toString(36).slice(2)}`;
75
+ }
76
+ function clearSignalRecord(record, sessionId) {
77
+ if (!(sessionId in record)) {
78
+ return record;
79
+ }
80
+ const next = { ...record };
81
+ delete next[sessionId];
82
+ return next;
83
+ }
84
+ var useUiBridgeStore = create()((set, get) => ({
85
+ ...createClientActions(set),
86
+ pendingContexts: {},
87
+ draftAppends: {},
88
+ sendRequests: {},
89
+ addPendingContext: (sessionId, context) => set((state) => ({
90
+ pendingContexts: {
91
+ ...state.pendingContexts,
92
+ [sessionId]: [
93
+ ...state.pendingContexts[sessionId] ?? [],
94
+ {
95
+ id: buildSignalId(),
96
+ label: context.label,
97
+ content: context.content
98
+ }
99
+ ]
100
+ }
101
+ })),
102
+ removePendingContext: (sessionId, contextId) => set((state) => {
103
+ const contexts = state.pendingContexts[sessionId];
104
+ if (!contexts?.length) {
105
+ return state;
106
+ }
107
+ const nextContexts = contexts.filter((context) => context.id !== contextId);
108
+ if (nextContexts.length === contexts.length) {
109
+ return state;
110
+ }
111
+ return {
112
+ pendingContexts: nextContexts.length > 0 ? {
113
+ ...state.pendingContexts,
114
+ [sessionId]: nextContexts
115
+ } : clearSignalRecord(state.pendingContexts, sessionId)
116
+ };
117
+ }),
118
+ consumePendingContexts: (sessionId) => {
119
+ const signals = get().pendingContexts[sessionId] ?? [];
120
+ if (signals.length === 0) return [];
121
+ set((state) => ({
122
+ pendingContexts: clearSignalRecord(state.pendingContexts, sessionId)
123
+ }));
124
+ return signals;
125
+ },
126
+ clearPendingContexts: (sessionId) => set((state) => ({
127
+ pendingContexts: clearSignalRecord(state.pendingContexts, sessionId)
128
+ })),
129
+ addDraftAppend: (sessionId, text) => set((state) => ({
130
+ draftAppends: {
131
+ ...state.draftAppends,
132
+ [sessionId]: [
133
+ ...state.draftAppends[sessionId] ?? [],
134
+ {
135
+ id: buildSignalId(),
136
+ text
137
+ }
138
+ ]
139
+ }
140
+ })),
141
+ consumeDraftAppends: (sessionId) => {
142
+ const signals = get().draftAppends[sessionId] ?? [];
143
+ if (signals.length === 0) return [];
144
+ set((state) => ({
145
+ draftAppends: clearSignalRecord(state.draftAppends, sessionId)
146
+ }));
147
+ return signals;
148
+ },
149
+ clearDraftAppends: (sessionId) => set((state) => ({
150
+ draftAppends: clearSignalRecord(state.draftAppends, sessionId)
151
+ })),
152
+ addSendRequest: (sessionId) => set((state) => ({
153
+ sendRequests: {
154
+ ...state.sendRequests,
155
+ [sessionId]: [
156
+ ...state.sendRequests[sessionId] ?? [],
157
+ {
158
+ id: buildSignalId()
159
+ }
160
+ ]
161
+ }
162
+ })),
163
+ consumeSendRequests: (sessionId) => {
164
+ const signals = get().sendRequests[sessionId] ?? [];
165
+ if (signals.length === 0) return [];
166
+ set((state) => ({
167
+ sendRequests: clearSignalRecord(state.sendRequests, sessionId)
168
+ }));
169
+ return signals;
170
+ },
171
+ clearSendRequests: (sessionId) => set((state) => ({
172
+ sendRequests: clearSignalRecord(state.sendRequests, sessionId)
173
+ })),
174
+ clearSession: (sessionId) => set((state) => ({
175
+ pendingContexts: clearSignalRecord(state.pendingContexts, sessionId),
176
+ draftAppends: clearSignalRecord(state.draftAppends, sessionId),
177
+ sendRequests: clearSignalRecord(state.sendRequests, sessionId)
178
+ }))
179
+ }));
180
+
181
+ // src/react/lib/resource-bridge.ts
182
+ function isRecord(value) {
183
+ return typeof value === "object" && value !== null && !Array.isArray(value);
184
+ }
185
+ function getNonEmptyString(value) {
186
+ return typeof value === "string" && value.trim() ? value.trim() : null;
187
+ }
188
+ function hasUserActivation() {
189
+ if (typeof navigator === "undefined") {
190
+ return false;
191
+ }
192
+ return navigator.userActivation?.isActive === true;
193
+ }
194
+ var BRIDGE_ACTIONS = ["addContext", "appendInput", "sendMessage"];
195
+ function isBridgeAction(action) {
196
+ return BRIDGE_ACTIONS.includes(action);
197
+ }
198
+ function resourceBridgeDispatch({
199
+ sessionId,
200
+ activeSessionId,
201
+ isStreaming,
202
+ action,
203
+ payload
204
+ }) {
205
+ if (!sessionId || !activeSessionId || sessionId !== activeSessionId) {
206
+ return false;
207
+ }
208
+ if (!isBridgeAction(action)) {
209
+ return false;
210
+ }
211
+ const bridgeStore = useUiBridgeStore.getState();
212
+ if (action === "addContext") {
213
+ if (!isRecord(payload)) return false;
214
+ const content = getNonEmptyString(payload.content);
215
+ const label = getNonEmptyString(payload.label);
216
+ if (!content || !label) return false;
217
+ bridgeStore.addPendingContext(sessionId, { content, label });
218
+ return true;
219
+ }
220
+ if (action === "appendInput") {
221
+ if (!isRecord(payload)) return false;
222
+ const text = getNonEmptyString(payload.text);
223
+ if (!text) return false;
224
+ bridgeStore.addDraftAppend(sessionId, text);
225
+ return true;
226
+ }
227
+ if (isStreaming || !hasUserActivation()) {
228
+ return false;
229
+ }
230
+ bridgeStore.addSendRequest(sessionId);
231
+ return true;
232
+ }
233
+
234
+ // src/react/lib/parent-bridge.ts
235
+ function invalidateSkillStats(sessionId) {
236
+ const queryClient = globalThis.__agentQueryClient;
237
+ if (!queryClient) return;
238
+ void queryClient.invalidateQueries({ queryKey: ["skill-stats", sessionId] });
239
+ }
240
+ var HOST_BRIDGE_ACTIONS = ["addContext", "appendInput", "sendMessage"];
241
+ function isRecord2(value) {
242
+ return typeof value === "object" && value !== null && !Array.isArray(value);
243
+ }
244
+ function isAllowedBridgeAction(action) {
245
+ return HOST_BRIDGE_ACTIONS.includes(action);
246
+ }
247
+ function isBladeBridgeEnvelope(value) {
248
+ return isRecord2(value) && value.__bladeBridge === true && typeof value.action === "string" && (value.direction === "agent-to-host" || value.direction === "host-to-agent");
249
+ }
250
+ function isLegacyResourceBridgeEnvelope(value) {
251
+ return isRecord2(value) && value.__resourceBridge === true && typeof value.action === "string";
252
+ }
253
+ function toBridgeActionEnvelope(value) {
254
+ if (isBladeBridgeEnvelope(value)) {
255
+ return {
256
+ action: value.action,
257
+ payload: value.payload,
258
+ direction: value.direction,
259
+ canonical: true
260
+ };
261
+ }
262
+ if (isLegacyResourceBridgeEnvelope(value)) {
263
+ return { action: value.action, payload: value.payload, canonical: false };
264
+ }
265
+ return null;
266
+ }
267
+ function isInsideIframe() {
268
+ return typeof window !== "undefined" && window.parent !== window;
269
+ }
270
+ function postToParent(envelope) {
271
+ if (!isInsideIframe()) {
272
+ return;
273
+ }
274
+ tapBridgeEvent({
275
+ side: "agent",
276
+ direction: "agent-to-host",
277
+ action: envelope.action,
278
+ payload: envelope.payload,
279
+ meta: envelope.meta
280
+ });
281
+ try {
282
+ window.parent.postMessage(envelope, "*");
283
+ } catch (error) {
284
+ console.debug("[parent-bridge] failed to post message to parent", error);
285
+ }
286
+ }
287
+ function attachHostBridgeListener(getState) {
288
+ if (typeof window === "undefined") {
289
+ return () => {
290
+ };
291
+ }
292
+ const listener = (event) => {
293
+ const envelope = toBridgeActionEnvelope(event.data);
294
+ if (!envelope) {
295
+ return;
296
+ }
297
+ const { sessionId, activeSessionId, isStreaming } = getState();
298
+ const tap = (note, rejected) => {
299
+ tapBridgeEvent({
300
+ side: "agent",
301
+ direction: envelope.direction ?? "host-to-agent",
302
+ action: envelope.action,
303
+ payload: envelope.payload,
304
+ meta: { canonical: envelope.canonical, sessionId: sessionId ?? null },
305
+ note,
306
+ rejected
307
+ });
308
+ };
309
+ if (!sessionId) {
310
+ tap("ignored: no active session", true);
311
+ return;
312
+ }
313
+ switch (envelope.action) {
314
+ case "installSkill":
315
+ if (!envelope.canonical || envelope.direction !== "host-to-agent") {
316
+ tap(
317
+ "installSkill rejected: requires __bladeBridge envelope with direction=host-to-agent",
318
+ true
319
+ );
320
+ console.warn(
321
+ "[parent-bridge] installSkill rejected: requires __bladeBridge envelope with direction=host-to-agent"
322
+ );
323
+ return;
324
+ }
325
+ tap(void 0, false);
326
+ try {
327
+ const payload = parsePartnerSkillInstallPayload(envelope.payload);
328
+ void installPartnerSkill(sessionId, payload).then(() => {
329
+ invalidateSkillStats(sessionId);
330
+ }).catch((error) => {
331
+ console.warn("[parent-bridge] installSkill request failed", error);
332
+ });
333
+ } catch (error) {
334
+ console.warn("[parent-bridge] invalid installSkill payload", error);
335
+ }
336
+ return;
337
+ default:
338
+ if (!isAllowedBridgeAction(envelope.action)) {
339
+ tap(`rejected: action not in allowlist [${HOST_BRIDGE_ACTIONS.join(", ")}]`, true);
340
+ return;
341
+ }
342
+ tap(void 0, false);
343
+ }
344
+ resourceBridgeDispatch({
345
+ sessionId,
346
+ activeSessionId,
347
+ isStreaming,
348
+ action: envelope.action,
349
+ payload: envelope.payload
350
+ });
351
+ };
352
+ window.addEventListener("message", listener);
353
+ return () => {
354
+ window.removeEventListener("message", listener);
355
+ };
356
+ }
357
+
358
+ // src/react/lib/chat.ts
359
+ var SYSTEM_REMINDER_PATTERN = /^<system-reminder>\s*[\s\S]*?\s*<\/system-reminder>$/i;
360
+ var LEGACY_FORK_CONTEXT_MARKERS = [
361
+ "\u7236\u667A\u80FD\u4F53\u5728\u6D3E\u751F\u4F60\u4E4B\u524D\u5DF2\u7ECF\u5B8C\u6210\u4E86\u4E00\u4E9B\u64CD\u4F5C\u3002",
362
+ "\u53EF\u901A\u8FC7 `read_file` \u8BFB\u53D6\u3002",
363
+ "# \u4F60\u7684\u4EFB\u52A1"
364
+ ];
365
+ function normalizeMessageContent(content) {
366
+ if (typeof content === "string") return content;
367
+ if (Array.isArray(content)) return content;
368
+ return String(content ?? "");
369
+ }
370
+ function getTextContent(content) {
371
+ if (typeof content === "string") return content;
372
+ return content.filter((part) => part.type === "text").map((part) => part.text).join("");
373
+ }
374
+ function isSystemReminderText(text) {
375
+ return SYSTEM_REMINDER_PATTERN.test(text.trim());
376
+ }
377
+ function isLegacyForkContextText(text) {
378
+ const normalized = text.trim();
379
+ return LEGACY_FORK_CONTEXT_MARKERS.every((marker) => normalized.includes(marker));
380
+ }
381
+ function isHiddenInternalMessage(message) {
382
+ if (message.role !== "user") return false;
383
+ const text = getTextContent(normalizeMessageContent(message.content));
384
+ return isSystemReminderText(text) || isLegacyForkContextText(text);
385
+ }
386
+ function buildMessageContent(text, attachments) {
387
+ if (attachments.length === 0) return text;
388
+ const textSegments = [];
389
+ if (text.trim()) {
390
+ textSegments.push(text);
391
+ }
392
+ for (const attachment of attachments) {
393
+ let fileText;
394
+ if (attachment.uploadedPath) {
395
+ const displayName = attachment.uploadedPath.split("/").pop() || attachment.name;
396
+ fileText = `[\u9644\u4EF6: ${displayName}] \u5DF2\u4E0A\u4F20\u5230\u5DE5\u4F5C\u533A\u8DEF\u5F84: ${attachment.uploadedPath}`;
397
+ } else if (attachment.textContent) {
398
+ const MAX_TEXT_CHARS = 1e5;
399
+ const content = attachment.textContent.length > MAX_TEXT_CHARS ? `${attachment.textContent.slice(0, MAX_TEXT_CHARS)}
400
+
401
+ [\u5185\u5BB9\u5DF2\u622A\u65AD\uFF0C\u539F\u59CB\u5171 ${attachment.textContent.length} \u5B57\u7B26]` : attachment.textContent;
402
+ fileText = `[\u9644\u4EF6: ${attachment.name}]
403
+ ${content}`;
404
+ } else {
405
+ fileText = `[\u9644\u4EF6: ${attachment.name}]`;
406
+ }
407
+ textSegments.push(fileText);
408
+ }
409
+ return textSegments.join("\n\n");
410
+ }
411
+ function contentPreview(content, maxLen = 80) {
412
+ const text = getTextContent(content).trim();
413
+ return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
414
+ }
415
+ function transformSlashCommand(skillName, rawInput) {
416
+ const prompt = rawInput.replace(`<skill>${skillName}</skill>`, "").trim();
417
+ return prompt ? `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1
418
+ ${prompt}` : `\u8BF7\u4F7F\u7528 ${skillName} skill \u5B8C\u6210\u4EFB\u52A1`;
419
+ }
420
+ var ATTACHMENT_TAG_RE = /\[附件:\s*([^\]]+)\](?:[ \t]*已上传到工作区路径:[ \t]*([^\r\n]+))?/g;
421
+ var CONTEXT_PART_RE = /^\[上下文:\s*([^\]]+)\]\n([\s\S]*)$/;
422
+ function extractTextAttachments(content) {
423
+ if (typeof content === "string") {
424
+ const attachments2 = [];
425
+ const contexts2 = [];
426
+ const ctxMatch = CONTEXT_PART_RE.exec(content);
427
+ let working = content;
428
+ if (ctxMatch) {
429
+ contexts2.push({ label: ctxMatch[1].trim(), content: ctxMatch[2].trim() });
430
+ working = "";
431
+ }
432
+ for (const match of working.matchAll(ATTACHMENT_TAG_RE)) {
433
+ attachments2.push({
434
+ name: match[1].trim(),
435
+ uploadedPath: match[2]?.trim() || void 0
436
+ });
437
+ }
438
+ return {
439
+ cleanText: working.replace(ATTACHMENT_TAG_RE, "").trim(),
440
+ attachments: attachments2,
441
+ contexts: contexts2
442
+ };
443
+ }
444
+ const attachments = [];
445
+ const contexts = [];
446
+ const cleanParts = [];
447
+ for (const part of content) {
448
+ if (part.type !== "text") continue;
449
+ const ctxMatch = CONTEXT_PART_RE.exec(part.text);
450
+ if (ctxMatch) {
451
+ contexts.push({ label: ctxMatch[1].trim(), content: ctxMatch[2].trim() });
452
+ continue;
453
+ }
454
+ let hasAttachment = false;
455
+ for (const match of part.text.matchAll(ATTACHMENT_TAG_RE)) {
456
+ attachments.push({
457
+ name: match[1].trim(),
458
+ uploadedPath: match[2]?.trim() || void 0
459
+ });
460
+ hasAttachment = true;
461
+ }
462
+ if (hasAttachment) {
463
+ const remaining = part.text.replace(ATTACHMENT_TAG_RE, "").trim();
464
+ if (remaining) cleanParts.push(remaining);
465
+ } else {
466
+ cleanParts.push(part.text);
467
+ }
468
+ }
469
+ return { cleanText: cleanParts.join("\n").trim(), attachments, contexts };
470
+ }
471
+ function getImageParts(content) {
472
+ if (typeof content === "string") return [];
473
+ return content.filter((part) => part.type === "image_url");
474
+ }
475
+ function getFileParts(content) {
476
+ if (typeof content === "string") return [];
477
+ return content.filter((part) => part.type === "file");
478
+ }
479
+ function groupMessagesByLoop(messages) {
480
+ const groups = [];
481
+ let currentGroup = null;
482
+ for (const msg of messages) {
483
+ const loop = msg.loop_name ?? "root";
484
+ if (loop === "root") {
485
+ currentGroup = { root: msg, childLoops: [] };
486
+ groups.push(currentGroup);
487
+ } else {
488
+ if (!currentGroup) {
489
+ currentGroup = { root: null, childLoops: [] };
490
+ groups.push(currentGroup);
491
+ }
492
+ const existingLoop = currentGroup.childLoops.find((l) => l.loopName === loop);
493
+ if (existingLoop) {
494
+ existingLoop.messages.push(msg);
495
+ } else {
496
+ currentGroup.childLoops.push({ loopName: loop, messages: [msg] });
497
+ }
498
+ }
499
+ }
500
+ return groups;
501
+ }
502
+
503
+ // src/react/lib/analytics.ts
504
+ var analyticsClient = null;
505
+ function setAnalyticsClient(client) {
506
+ analyticsClient = client;
507
+ }
508
+ function trackEvent(name, props = {}) {
509
+ analyticsClient?.capture(name, props);
510
+ }
511
+
512
+ // src/react/lib/tool-preview.ts
513
+ function buildToolPreviewKey(content, type2, toolCallId) {
514
+ if (type2 === "resource-uri") {
515
+ return `tool-preview-uri:${content}`;
516
+ }
517
+ return `tool-preview:${toolCallId}`;
518
+ }
519
+
520
+ // src/react/lib/ui-meta.ts
521
+ function isNonEmptyString(value) {
522
+ return typeof value === "string" && value.trim().length > 0;
523
+ }
524
+ function isPositiveNumber(value) {
525
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
526
+ }
527
+ function isUiMeta(value) {
528
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
529
+ return false;
530
+ }
531
+ const raw = value;
532
+ if (raw.target !== "inline" && raw.target !== "preview") {
533
+ return false;
534
+ }
535
+ if (!isPositiveNumber(raw.height)) {
536
+ return false;
537
+ }
538
+ if (!isNonEmptyString(raw.resourceHTML) && !isNonEmptyString(raw.resourceUri) && !isNonEmptyString(raw.resourceURI)) {
539
+ return false;
540
+ }
541
+ if (raw.title != null && !isNonEmptyString(raw.title)) {
542
+ return false;
543
+ }
544
+ return true;
545
+ }
546
+
547
+ // src/react/stores/auth-store.ts
548
+ import { create as create6 } from "zustand";
549
+ import { createJSONStorage, persist } from "zustand/middleware";
550
+
551
+ // src/react/api/auth.ts
552
+ var auth_exports = {};
553
+ __export(auth_exports, {
554
+ getMe: () => getMe,
555
+ getProviders: () => getProviders,
556
+ logout: () => logout
557
+ });
558
+ var r2 = () => getClient().auth;
559
+ var getProviders = (...args) => r2().getProviders(...args);
560
+ var getMe = (...args) => r2().getMe(...args);
561
+ var logout = (...args) => r2().logout(...args);
562
+
563
+ // src/react/sockets/socket-state.ts
564
+ var agentSocket = null;
565
+ function setAgentSocket(nextSocket) {
566
+ agentSocket = nextSocket;
567
+ }
568
+ function getSocket() {
569
+ if (!agentSocket) throw new Error("Socket not initialized. Call bootstrapBladeClient() first.");
570
+ return agentSocket;
571
+ }
572
+
573
+ // src/react/stores/session-store.ts
574
+ import { create as create5 } from "zustand";
575
+
576
+ // src/react/api/sessions.ts
577
+ var sessions_exports = {};
578
+ __export(sessions_exports, {
579
+ checkoutSession: () => checkoutSession,
580
+ copyFile: () => copyFile,
581
+ createSession: () => createSession,
582
+ createSessionWithRequest: () => createSessionWithRequest,
583
+ createShare: () => createShare,
584
+ deleteFile: () => deleteFile,
585
+ deleteSession: () => deleteSession,
586
+ exportSession: () => exportSession,
587
+ getBackgroundTask: () => getBackgroundTask,
588
+ getDownloadDirUrl: () => getDownloadDirUrl,
589
+ getSession: () => getSession,
590
+ getSessionCheckpoints: () => getSessionCheckpoints,
591
+ getSessionContextStats: () => getSessionContextStats,
592
+ getSessionHistory: () => getSessionHistory,
593
+ getSessionTasks: () => getSessionTasks,
594
+ getSessionTurns: () => getSessionTurns,
595
+ getSharedSession: () => getSharedSession,
596
+ importSession: () => importSession,
597
+ listBackgroundTasks: () => listBackgroundTasks,
598
+ listDir: () => listDir,
599
+ listSessions: () => listSessions,
600
+ listSessionsPaginated: () => listSessionsPaginated,
601
+ listSessionsWithSkillData: () => listSessionsWithSkillData,
602
+ pinSession: () => pinSession,
603
+ previewImport: () => previewImport,
604
+ renameFile: () => renameFile,
605
+ revokeShare: () => revokeShare,
606
+ rewindSession: () => rewindSession,
607
+ shareFile: () => shareFile,
608
+ startReplaySession: () => startReplaySession,
609
+ stopBackgroundTask: () => stopBackgroundTask,
610
+ switchBranch: () => switchBranch,
611
+ tokenizeMessages: () => tokenizeMessages,
612
+ tokenizePrompt: () => tokenizePrompt,
613
+ updateReplaySession: () => updateReplaySession,
614
+ updateSession: () => updateSession,
615
+ updateSessionMemory: () => updateSessionMemory,
616
+ updateSharing: () => updateSharing,
617
+ uploadFiles: () => uploadFiles,
618
+ writeFile: () => writeFile
619
+ });
620
+ var r3 = () => getClient().sessions;
621
+ var listSessions = (...args) => r3().listSessions(...args);
622
+ var listSessionsPaginated = (...args) => r3().listSessionsPaginated(...args);
623
+ var listSessionsWithSkillData = (...args) => r3().listSessionsWithSkillData(...args);
624
+ var createSession = (...args) => r3().createSession(...args);
625
+ var createSessionWithRequest = (...args) => r3().createSessionWithRequest(...args);
626
+ var getSession = (...args) => r3().getSession(...args);
627
+ var updateSession = (...args) => r3().updateSession(...args);
628
+ var pinSession = (...args) => r3().pinSession(...args);
629
+ var startReplaySession = (...args) => r3().startReplaySession(...args);
630
+ var updateReplaySession = (...args) => r3().updateReplaySession(...args);
631
+ var updateSharing = (...args) => r3().updateSharing(...args);
632
+ var updateSessionMemory = (...args) => r3().updateSessionMemory(...args);
633
+ var createShare = (...args) => r3().createShare(...args);
634
+ var revokeShare = (...args) => r3().revokeShare(...args);
635
+ var getSharedSession = (...args) => r3().getSharedSession(...args);
636
+ var getSessionTasks = (...args) => r3().getSessionTasks(...args);
637
+ var getSessionTurns = (...args) => r3().getSessionTurns(...args);
638
+ var getSessionContextStats = (...args) => r3().getSessionContextStats(...args);
639
+ var getSessionHistory = (...args) => r3().getSessionHistory(...args);
640
+ var tokenizePrompt = (...args) => r3().tokenizePrompt(...args);
641
+ var tokenizeMessages = (...args) => r3().tokenizeMessages(...args);
642
+ var getSessionCheckpoints = (...args) => r3().getSessionCheckpoints(...args);
643
+ var checkoutSession = (...args) => r3().checkoutSession(...args);
644
+ var rewindSession = (...args) => r3().rewindSession(...args);
645
+ var switchBranch = (...args) => r3().switchBranch(...args);
646
+ var deleteSession = (...args) => r3().deleteSession(...args);
647
+ var listBackgroundTasks = (...args) => r3().listBackgroundTasks(...args);
648
+ var getBackgroundTask = (...args) => r3().getBackgroundTask(...args);
649
+ var stopBackgroundTask = (...args) => r3().stopBackgroundTask(...args);
650
+ var listDir = (...args) => r3().listDir(...args);
651
+ var uploadFiles = (...args) => r3().uploadFiles(...args);
652
+ var deleteFile = (...args) => r3().deleteFile(...args);
653
+ var writeFile = (...args) => r3().writeFile(...args);
654
+ var renameFile = (...args) => r3().renameFile(...args);
655
+ var copyFile = (...args) => r3().copyFile(...args);
656
+ var shareFile = (...args) => r3().shareFile(...args);
657
+ var getDownloadDirUrl = (...args) => r3().getDownloadDirUrl(...args);
658
+ var exportSession = (...args) => r3().exportSession(...args);
659
+ var previewImport = (...args) => r3().previewImport(...args);
660
+ var importSession = (...args) => r3().importSession(...args);
661
+
662
+ // src/react/stores/chat-store.ts
663
+ import { create as create3 } from "zustand";
664
+
665
+ // src/react/components/chat/display-utils.ts
666
+ var TOOL_NAME_ALIASES = {
667
+ agent: "Agent",
668
+ ask_user_question: "AskUserQuestion",
669
+ bash: "Bash",
670
+ bg_bash: "BgBash",
671
+ edit: "Edit",
672
+ exit_plan_mode: "ExitPlanMode",
673
+ file_edit: "Edit",
674
+ file_read: "Read",
675
+ file_write: "Write",
676
+ finish_task: "FinishTask",
677
+ get_skill_content: "GetSkillContent",
678
+ glob: "Glob",
679
+ grep: "Grep",
680
+ list_skill_tools: "ListSkillTools",
681
+ load_skill_tools: "LoadSkillTools",
682
+ ls: "Ls",
683
+ read: "Read",
684
+ run_skill_tool: "RunSkillTool",
685
+ search_skills: "SearchSkills",
686
+ web_fetch: "WebFetch",
687
+ web_search: "WebSearch",
688
+ write: "Write"
689
+ };
690
+ var TOOL_DISPLAY_LABELS = {
691
+ Bash: "\u6267\u884C\u547D\u4EE4",
692
+ BgBash: "\u540E\u53F0\u6267\u884C\u547D\u4EE4",
693
+ Read: "\u8BFB\u53D6\u6587\u4EF6",
694
+ Write: "\u5199\u5165\u6587\u4EF6",
695
+ Edit: "\u7F16\u8F91\u6587\u4EF6",
696
+ Ls: "\u5217\u51FA\u76EE\u5F55",
697
+ Glob: "\u5339\u914D\u6587\u4EF6",
698
+ Grep: "\u641C\u7D22\u6587\u672C",
699
+ WebSearch: "\u641C\u7D22\u7F51\u9875",
700
+ WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
701
+ Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
702
+ AskUserQuestion: "\u5411\u7528\u6237\u63D0\u95EE",
703
+ SearchSkills: "\u641C\u7D22\u6280\u80FD",
704
+ GetSkillContent: "\u8BFB\u53D6\u6280\u80FD",
705
+ ListSkillTools: "\u67E5\u770B\u5DE5\u5177\u5217\u8868",
706
+ LoadSkillTools: "\u52A0\u8F7D\u6280\u80FD",
707
+ RunSkillTool: "\u6267\u884C\u6280\u80FD\u5DE5\u5177",
708
+ FinishTask: "\u4EFB\u52A1\u5B8C\u6210",
709
+ ExitPlanMode: "\u63D0\u4EA4\u8BA1\u5212",
710
+ ListSessions: "\u5217\u51FA\u5386\u53F2\u4F1A\u8BDD",
711
+ GetSessionHistory: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2"
712
+ };
713
+ var OUTPUT_TOOL_ICONS = {
714
+ Write: "\u{1F4C4}",
715
+ Edit: "\u{1F4DD}"
716
+ };
717
+ var PRIORITY_FILE_KEYS = [
718
+ "file_path",
719
+ "output_file",
720
+ "output_path",
721
+ "path",
722
+ "filepath",
723
+ "target_file",
724
+ "destination",
725
+ "filename"
726
+ ];
727
+ var FILE_PATH_PATTERN = /(?:^|[\s"'`::])([^\s"'`,;:)\]},。;、]+?\.[a-z0-9]{1,8})(?=$|[\s"'`,.;:)\]},。;、])/i;
728
+ function safeParseJson(value) {
729
+ if (!value) return null;
730
+ try {
731
+ return JSON.parse(value);
732
+ } catch {
733
+ return null;
734
+ }
735
+ }
736
+ function isPlainObject(value) {
737
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
738
+ }
739
+ function getStringArgValue(args, key) {
740
+ const value = args?.[key];
741
+ return typeof value === "string" ? value.trim() : "";
742
+ }
743
+ var GENERIC_DISPLAY_NAMES = new Set(Object.values(TOOL_DISPLAY_LABELS));
744
+ function getRunSkillDisplayName(toolCall, args) {
745
+ const argDisplayName = getStringArgValue(args, "display_name");
746
+ if (argDisplayName) return argDisplayName;
747
+ const displayName = toolCall.display_name?.trim() ?? "";
748
+ if (displayName && (displayName === "\u5199\u5165\u6587\u4EF6" || !GENERIC_DISPLAY_NAMES.has(displayName))) {
749
+ return displayName;
750
+ }
751
+ const argToolName = getStringArgValue(args, "tool_name");
752
+ if (argToolName) return argToolName;
753
+ return "";
754
+ }
755
+ function getCommandDescription(args) {
756
+ return getStringArgValue(args, "description");
757
+ }
758
+ function findFileLikeValue(value, depth = 0, allowPlainString = false) {
759
+ if (depth > 3) return null;
760
+ if (typeof value === "string") {
761
+ const trimmed = value.trim();
762
+ if (!trimmed) return null;
763
+ if (allowPlainString || trimmed.includes("/") || /\.[a-z0-9]{1,8}$/i.test(trimmed)) {
764
+ return trimmed;
765
+ }
766
+ return null;
767
+ }
768
+ if (Array.isArray(value)) {
769
+ for (const item of value) {
770
+ const found = findFileLikeValue(item, depth + 1, allowPlainString);
771
+ if (found) return found;
772
+ }
773
+ return null;
774
+ }
775
+ if (isPlainObject(value)) {
776
+ for (const key of PRIORITY_FILE_KEYS) {
777
+ const direct = findFileLikeValue(value[key], depth + 1, true);
778
+ if (direct) return direct;
779
+ }
780
+ for (const nested of Object.values(value)) {
781
+ const found = findFileLikeValue(nested, depth + 1, allowPlainString);
782
+ if (found) return found;
783
+ }
784
+ }
785
+ return null;
786
+ }
787
+ function extractFilePathFromText(text) {
788
+ return text.match(FILE_PATH_PATTERN)?.[1] ?? null;
789
+ }
790
+ function extractFilePathFromResult(result) {
791
+ if (typeof result === "string") {
792
+ const parsed = safeParseJson(result);
793
+ if (parsed != null && parsed !== result) {
794
+ const found = extractFilePathFromResult(parsed);
795
+ if (found) return found;
796
+ }
797
+ return extractFilePathFromText(result);
798
+ }
799
+ if (Array.isArray(result)) {
800
+ for (const item of result) {
801
+ const found = extractFilePathFromResult(item);
802
+ if (found) return found;
803
+ }
804
+ return null;
805
+ }
806
+ if (isPlainObject(result)) {
807
+ for (const key of PRIORITY_FILE_KEYS) {
808
+ const direct = findFileLikeValue(result[key], 0, true);
809
+ if (direct) return direct;
810
+ }
811
+ for (const nested of Object.values(result)) {
812
+ const found = extractFilePathFromResult(nested);
813
+ if (found) return found;
814
+ }
815
+ }
816
+ return null;
817
+ }
818
+ function isWriteLikeRunSkillTool(label, args) {
819
+ const normalizedLabel = label.trim();
820
+ if (normalizedLabel === "\u5199\u5165\u6587\u4EF6" || /write/i.test(normalizedLabel)) return true;
821
+ const toolName = getStringArgValue(args, "tool_name");
822
+ if (/write/i.test(toolName)) return true;
823
+ return false;
824
+ }
825
+ function formatFileName(filePath) {
826
+ return filePath.split("/").pop() ?? filePath;
827
+ }
828
+ function isInternalStatusFile(filePath) {
829
+ const fileName = formatFileName(filePath).toLowerCase();
830
+ return fileName === "phase.json";
831
+ }
832
+ function formatToolName(name) {
833
+ const trimmed = name.trim();
834
+ if (!trimmed) return name;
835
+ const stripped = trimmed.split(":").pop()?.split("/").pop()?.split(".").pop()?.trim() || trimmed;
836
+ const normalized = stripped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
837
+ return TOOL_NAME_ALIASES[normalized] ?? stripped;
838
+ }
839
+ function getToolDisplayLabel(toolCall) {
840
+ const normalized = formatToolName(toolCall.name);
841
+ const args = safeParseJson(toolCall.arguments);
842
+ const displayName = toolCall.display_name?.trim() ?? "";
843
+ const baseLabel = displayName || TOOL_DISPLAY_LABELS[normalized] || normalized;
844
+ const metaDisplayName = getStringArgValue(args, "_meta_display_name");
845
+ if (metaDisplayName) {
846
+ return metaDisplayName;
847
+ }
848
+ switch (normalized) {
849
+ case "RunSkillTool": {
850
+ const runSkillDisplayName = getRunSkillDisplayName(toolCall, args);
851
+ if (runSkillDisplayName) {
852
+ const filePath = extractToolFilePath(toolCall);
853
+ if (filePath && isWriteLikeRunSkillTool(runSkillDisplayName, args)) {
854
+ return `${runSkillDisplayName}\u300C${formatFileName(filePath)}\u300D`;
855
+ }
856
+ return runSkillDisplayName;
857
+ }
858
+ return "\u6267\u884C\u6280\u80FD\u5DE5\u5177";
859
+ }
860
+ case "Bash": {
861
+ const description = getCommandDescription(args);
862
+ return description ? `\u6267\u884C\u547D\u4EE4\u884C\u64CD\u4F5C\uFF1A${description}` : "\u6267\u884C\u547D\u4EE4\u884C\u64CD\u4F5C";
863
+ }
864
+ case "BgBash": {
865
+ const description = getCommandDescription(args);
866
+ return description ? `\u540E\u53F0\u6267\u884C\uFF1A${description}` : "\u540E\u53F0\u6267\u884C\u547D\u4EE4";
867
+ }
868
+ case "Ls": {
869
+ const path = args && typeof args.path === "string" ? args.path : "";
870
+ return path ? `${baseLabel}\u300C${path}\u300D` : baseLabel;
871
+ }
872
+ case "Glob": {
873
+ const pattern = args && typeof args.pattern === "string" ? args.pattern : "";
874
+ return pattern ? `${baseLabel}\u300C${pattern}\u300D` : baseLabel;
875
+ }
876
+ case "Grep": {
877
+ const pattern = args && typeof args.pattern === "string" ? args.pattern : "";
878
+ return pattern ? `${baseLabel}\u300C${pattern}\u300D` : baseLabel;
879
+ }
880
+ case "WebSearch":
881
+ case "WebFetch": {
882
+ const query = args && typeof args.query === "string" ? args.query : "";
883
+ return query ? `${baseLabel}\u300C${query}\u300D` : baseLabel;
884
+ }
885
+ case "GetSkillContent": {
886
+ const skillName = args && typeof args.skill_id === "string" ? args.skill_id : args && typeof args.skill_name === "string" ? args.skill_name : "";
887
+ return skillName ? `${baseLabel}\u300C${skillName}\u300D` : baseLabel;
888
+ }
889
+ case "SearchSkills": {
890
+ const query = args && typeof args.query === "string" ? args.query : "";
891
+ return query ? `${baseLabel}\u300C${query}\u300D` : baseLabel;
892
+ }
893
+ case "ListSkillTools": {
894
+ const skillId = args && typeof args.skill_id === "string" ? args.skill_id : "";
895
+ return skillId ? `${baseLabel}\u300C${skillId}\u300D` : baseLabel;
896
+ }
897
+ case "LoadSkillTools":
898
+ return "\u52A0\u8F7D\u6280\u80FD";
899
+ case "FinishTask": {
900
+ const title = args && typeof args.title === "string" ? args.title : "";
901
+ return title ? `${baseLabel}\uFF1A${title}` : baseLabel;
902
+ }
903
+ default:
904
+ return baseLabel;
905
+ }
906
+ }
907
+ function getToolTone(status) {
908
+ if (status === "error" || status === "cancelled") return "red";
909
+ if (status === "awaiting_answer") return "amber";
910
+ if (status === "pending") return "blue";
911
+ return "emerald";
912
+ }
913
+ function getToolStatusLabel(status) {
914
+ if (status === "pending") return "\u8FD0\u884C\u4E2D";
915
+ if (status === "awaiting_answer") return "\u7B49\u5F85\u56DE\u7B54";
916
+ if (status === "error") return "\u9519\u8BEF";
917
+ if (status === "cancelled") return "\u5DF2\u53D6\u6D88";
918
+ return "\u5B8C\u6210";
919
+ }
920
+ function extractToolFilePath(toolCall) {
921
+ const formattedName = formatToolName(toolCall.name);
922
+ const argsValue = safeParseJson(toolCall.arguments);
923
+ let filePath = null;
924
+ if (formattedName === "Read" || formattedName === "Write" || formattedName === "Edit") {
925
+ filePath = findFileLikeValue(argsValue);
926
+ } else if (formattedName === "RunSkillTool") {
927
+ filePath = findFileLikeValue(argsValue) ?? extractFilePathFromResult(toolCall.result);
928
+ }
929
+ if (!filePath || isInternalStatusFile(filePath)) return null;
930
+ return filePath;
931
+ }
932
+ function getToolOutputTag(toolCall) {
933
+ const toolName = formatToolName(toolCall.name);
934
+ const filePath = extractToolFilePath(toolCall);
935
+ const icon = OUTPUT_TOOL_ICONS[toolName];
936
+ if (!icon || !filePath) return null;
937
+ return {
938
+ key: `${toolCall.id}-${filePath}`,
939
+ label: `${icon} ${filePath.split("/").pop() ?? filePath}`,
940
+ tone: "blue"
941
+ };
942
+ }
943
+
944
+ // src/react/stores/ui-store.ts
945
+ import { create as create2 } from "zustand";
946
+ var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
947
+ function resolveEffectiveTheme(theme) {
948
+ if (theme !== "system") return theme;
949
+ return isBrowser && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
950
+ }
951
+ function applyTheme(theme) {
952
+ if (!isBrowser) return;
953
+ const effective = resolveEffectiveTheme(theme);
954
+ document.documentElement.setAttribute("data-theme", effective);
955
+ }
956
+ var storedTheme = isBrowser ? localStorage.getItem("blade-theme") ?? "light" : "light";
957
+ applyTheme(storedTheme);
958
+ if (isBrowser) {
959
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
960
+ const current = useUiStore?.getState?.()?.theme;
961
+ if (current === "system") applyTheme("system");
962
+ });
963
+ }
964
+ function removeArtifactAtIndex(state, index) {
965
+ const next = state.artifacts.filter((_, i) => i !== index);
966
+ let nextActive = state.activeArtifactIndex;
967
+ if (next.length === 0) {
968
+ return { artifacts: [], activeArtifactIndex: -1, rightPanelCollapsed: true };
969
+ }
970
+ if (index < nextActive) {
971
+ nextActive -= 1;
972
+ } else if (index === nextActive) {
973
+ nextActive = Math.min(index, next.length - 1);
974
+ }
975
+ return { artifacts: next, activeArtifactIndex: nextActive };
976
+ }
977
+ function upsertArtifactState(state, target, options) {
978
+ const reveal = options?.reveal ?? true;
979
+ const activate = options?.activate ?? true;
980
+ const targetKey = target.key ?? target.title;
981
+ const applyUiState = (partial) => ({
982
+ ...partial,
983
+ ...reveal ? { rightPanelCollapsed: false, activeRightTab: "preview" } : {}
984
+ });
985
+ const existing = state.artifacts.findIndex((artifact) => targetKey && (artifact.key ?? artifact.title) === targetKey);
986
+ if (existing >= 0) {
987
+ const updated = [...state.artifacts];
988
+ updated[existing] = target;
989
+ return applyUiState({
990
+ artifacts: updated,
991
+ activeArtifactIndex: activate ? existing : state.activeArtifactIndex
992
+ });
993
+ }
994
+ const next = [...state.artifacts, target];
995
+ return applyUiState({
996
+ artifacts: next,
997
+ activeArtifactIndex: activate ? next.length - 1 : state.activeArtifactIndex >= 0 ? state.activeArtifactIndex : 0
998
+ });
999
+ }
1000
+ var useUiStore = create2()((set) => ({
1001
+ ...createClientActions(set),
1002
+ leftPanelSize: 20,
1003
+ rightPanelSize: 25,
1004
+ leftPanelCollapsed: false,
1005
+ rightPanelCollapsed: false,
1006
+ activeRightTab: "situation",
1007
+ artifacts: [],
1008
+ activeArtifactIndex: -1,
1009
+ theme: storedTheme,
1010
+ setLeftPanelSize: (size) => set({ leftPanelSize: size }),
1011
+ setRightPanelSize: (size) => set({ rightPanelSize: size }),
1012
+ setLeftPanelCollapsed: (collapsed) => set({ leftPanelCollapsed: collapsed }),
1013
+ setRightPanelCollapsed: (collapsed) => set({ rightPanelCollapsed: collapsed }),
1014
+ toggleLeftPanel: () => set((s) => ({ leftPanelCollapsed: !s.leftPanelCollapsed })),
1015
+ toggleRightPanel: () => set((s) => ({ rightPanelCollapsed: !s.rightPanelCollapsed })),
1016
+ setActiveRightTab: (tab) => set({ activeRightTab: tab }),
1017
+ pushArtifact: (target) => set((state) => upsertArtifactState(state, target)),
1018
+ upsertArtifact: (target, options) => set((state) => upsertArtifactState(state, target, options)),
1019
+ setActiveArtifact: (index) => set({ activeArtifactIndex: index }),
1020
+ closeArtifact: (index) => set((state) => removeArtifactAtIndex(state, index)),
1021
+ removeArtifactByKey: (key) => set((state) => {
1022
+ const index = state.artifacts.findIndex((artifact) => (artifact.key ?? artifact.title) === key);
1023
+ if (index < 0) return state;
1024
+ return removeArtifactAtIndex(state, index);
1025
+ }),
1026
+ clearArtifacts: () => set({ artifacts: [], activeArtifactIndex: -1 }),
1027
+ setPreviewTarget: (target) => set(() => {
1028
+ if (target === null) {
1029
+ return { artifacts: [], activeArtifactIndex: -1 };
1030
+ }
1031
+ return {
1032
+ artifacts: [target],
1033
+ activeArtifactIndex: 0,
1034
+ rightPanelCollapsed: false,
1035
+ activeRightTab: "preview"
1036
+ };
1037
+ }),
1038
+ setTheme: (theme) => {
1039
+ localStorage.setItem("blade-theme", theme);
1040
+ applyTheme(theme);
1041
+ set({ theme });
1042
+ }
1043
+ }));
1044
+
1045
+ // src/react/stores/chat-store.ts
1046
+ var _getActiveSessionId = null;
1047
+ function setChatStoreSessionAccessor(fn) {
1048
+ _getActiveSessionId = fn;
1049
+ }
1050
+ function parseAgentDescription(argumentsJson) {
1051
+ try {
1052
+ return JSON.parse(argumentsJson)?.description ?? "\u5B50\u667A\u80FD\u4F53";
1053
+ } catch {
1054
+ return "\u5B50\u667A\u80FD\u4F53";
1055
+ }
1056
+ }
1057
+ function inferLoopStatusFromMessages(messages) {
1058
+ const toolCalls = messages.flatMap((message) => message.tool_calls ?? []);
1059
+ if (messages.some((message) => message.status === "streaming")) return "running";
1060
+ if (toolCalls.some((toolCall) => toolCall.status === "awaiting_answer")) return "awaiting_answer";
1061
+ if (toolCalls.some((toolCall) => toolCall.status === "error")) return "error";
1062
+ if (toolCalls.some((toolCall) => toolCall.status === "cancelled")) return "cancelled";
1063
+ if (toolCalls.some((toolCall) => toolCall.status === "pending")) return "running";
1064
+ return "done";
1065
+ }
1066
+ function inferLoopStatusFromTurns(turns, messages) {
1067
+ const latestAgentNotification = [...turns].reverse().flatMap((turn) => turn.blocks).find((block) => {
1068
+ if (block.type !== "system_notification" || !isRecord3(block.content)) return false;
1069
+ return block.content.notification_type === "agent:start" || block.content.notification_type === "agent:end";
1070
+ });
1071
+ if (latestAgentNotification?.type === "system_notification" && isRecord3(latestAgentNotification.content)) {
1072
+ const notificationType = latestAgentNotification.content.notification_type;
1073
+ const status = latestAgentNotification.content.status;
1074
+ if (notificationType === "agent:start" || status === "running") return "running";
1075
+ if (status === "error") return "error";
1076
+ if (status === "cancelled") return "cancelled";
1077
+ }
1078
+ return inferLoopStatusFromMessages(messages);
1079
+ }
1080
+ function isRecord3(value) {
1081
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1082
+ }
1083
+ function parentForkToolCallIdFromTurn(turn) {
1084
+ if (typeof turn.parent_fork_tool_call_id === "string" && turn.parent_fork_tool_call_id.length > 0) {
1085
+ return turn.parent_fork_tool_call_id;
1086
+ }
1087
+ for (const block of turn.blocks) {
1088
+ if (block.type !== "system_notification" || !isRecord3(block.content)) continue;
1089
+ const metadata = block.content.metadata;
1090
+ if (!isRecord3(metadata)) continue;
1091
+ const parentId = metadata.parent_fork_tool_call_id;
1092
+ if (typeof parentId === "string" && parentId.length > 0) return parentId;
1093
+ }
1094
+ return null;
1095
+ }
1096
+ function buildMessageContent2(turn) {
1097
+ const textBlocks = turn.blocks.filter((block) => block.type === "text");
1098
+ if (textBlocks.length === 0) return "";
1099
+ if (textBlocks.length === 1) return normalizeMessageContent(textBlocks[0].content);
1100
+ return textBlocks.map((block) => {
1101
+ if (typeof block.content === "string") return block.content;
1102
+ return JSON.stringify(block.content);
1103
+ }).join("");
1104
+ }
1105
+ function buildReasoning(turn) {
1106
+ const thinking = turn.blocks.filter((block) => block.type === "thinking").map((block) => typeof block.content === "string" ? block.content : JSON.stringify(block.content)).filter(Boolean);
1107
+ if (thinking.length === 0) return void 0;
1108
+ return thinking.join("\n\n");
1109
+ }
1110
+ function isModeChangeContent(value) {
1111
+ return typeof value === "object" && value !== null && typeof value.from === "string" && typeof value.to === "string";
1112
+ }
1113
+ function extractModeFromBlocks(blocks) {
1114
+ const modeBlock = blocks.find((block) => block.type === "mode_change");
1115
+ if (modeBlock && isModeChangeContent(modeBlock.content)) {
1116
+ return modeBlock.content.to === "planning" || modeBlock.content.to === "executing" ? modeBlock.content.to : null;
1117
+ }
1118
+ if (blocks.some((block) => block.type === "planning_enter")) return "planning";
1119
+ if (blocks.some((block) => block.type === "planning_exit")) return "executing";
1120
+ return null;
1121
+ }
1122
+ function projectionToMessage(turn) {
1123
+ if (turn.kind === "compaction" && turn.compaction_id) {
1124
+ return {
1125
+ role: "assistant",
1126
+ content: turn.summary_preview ?? "",
1127
+ kind: "compaction",
1128
+ loop_name: turn.loop_id,
1129
+ entry_id: turn.turn_id,
1130
+ status: turn.status,
1131
+ compaction: {
1132
+ compaction_id: turn.compaction_id,
1133
+ summary_preview: turn.summary_preview,
1134
+ summary_full: turn.summary_full,
1135
+ archived_count: turn.archived_count,
1136
+ archived_files: turn.archived_files,
1137
+ archived_tool_calls: turn.archived_tool_calls,
1138
+ tokens_before: turn.tokens_before,
1139
+ tokens_after: turn.tokens_after,
1140
+ saved_ratio: turn.saved_ratio,
1141
+ trigger: turn.trigger,
1142
+ failure_reason: turn.failure_reason,
1143
+ fallback_applied: turn.fallback_applied
1144
+ }
1145
+ };
1146
+ }
1147
+ const planningBlock = turn.blocks.find(
1148
+ (block) => block.type === "mode_change" || block.type === "planning_enter" || block.type === "planning_exit" || block.type === "plan_status"
1149
+ );
1150
+ if (planningBlock) {
1151
+ if (planningBlock.type === "plan_status") {
1152
+ return {
1153
+ role: "tool",
1154
+ content: typeof planningBlock.content === "string" ? planningBlock.content : JSON.stringify(planningBlock.content ?? {}, null, 2),
1155
+ kind: "plan_status",
1156
+ loop_name: turn.loop_id,
1157
+ entry_id: turn.turn_id,
1158
+ status: turn.status
1159
+ };
1160
+ }
1161
+ return {
1162
+ role: "assistant",
1163
+ content: planningBlock.type === "mode_change" ? typeof planningBlock.content === "string" ? planningBlock.content : JSON.stringify(planningBlock.content ?? {}) : "",
1164
+ kind: planningBlock.type,
1165
+ loop_name: turn.loop_id,
1166
+ entry_id: turn.turn_id,
1167
+ status: turn.status
1168
+ };
1169
+ }
1170
+ if (turn.blocks.some((block) => block.type === "ask_user_answer")) {
1171
+ return null;
1172
+ }
1173
+ const content = buildMessageContent2(turn);
1174
+ const reasoning = buildReasoning(turn);
1175
+ const toolCalls = turn.tool_calls.length > 0 ? turn.tool_calls.map((toolCall) => ({
1176
+ id: toolCall.id,
1177
+ name: toolCall.tool_name,
1178
+ display_name: toolCall.display_name,
1179
+ arguments: toolCall.arguments,
1180
+ result: toolCall.result ?? void 0,
1181
+ pending_question_ref: toolCall.pending_question_ref ?? void 0,
1182
+ status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending",
1183
+ ...typeof toolCall.duration_ms === "number" ? { duration_ms: toolCall.duration_ms } : {}
1184
+ })) : void 0;
1185
+ if (turn.role === "system" && !content && !toolCalls?.length) {
1186
+ return null;
1187
+ }
1188
+ return {
1189
+ role: turn.role === "system" ? "assistant" : turn.role,
1190
+ content,
1191
+ blocks: turn.blocks,
1192
+ ...reasoning ? { reasoning } : {},
1193
+ ...toolCalls ? { tool_calls: toolCalls } : {},
1194
+ loop_name: turn.loop_id,
1195
+ entry_id: turn.turn_id,
1196
+ status: turn.status,
1197
+ ...typeof turn.duration_ms === "number" ? { duration_ms: turn.duration_ms } : {},
1198
+ ...turn.started_at ? { timestamp: turn.started_at } : {},
1199
+ ...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {}
1200
+ };
1201
+ }
1202
+ function rebuildAgentLoops(turns) {
1203
+ const messages = turns.map(projectionToMessage).filter(Boolean);
1204
+ const childLoopNames = [...new Set(turns.map((turn) => turn.loop_id).filter((name) => name !== "root"))];
1205
+ if (childLoopNames.length === 0) return {};
1206
+ const agentToolCalls = messages.filter((message) => message.role === "assistant" && (message.loop_name ?? "root") === "root").flatMap((message) => message.tool_calls ?? []).filter((toolCall) => formatToolName(toolCall.name) === "Agent");
1207
+ const loops = {};
1208
+ const agentToolCallsById = new Map(agentToolCalls.map((toolCall) => [toolCall.id, toolCall]));
1209
+ const explicitParentToolCallIds = new Set(
1210
+ turns.map(parentForkToolCallIdFromTurn).filter((id) => id !== null)
1211
+ );
1212
+ const usedToolCallIds = /* @__PURE__ */ new Set();
1213
+ for (const loopName of childLoopNames) {
1214
+ const loopMessages = messages.filter((message) => (message.loop_name ?? "root") === loopName);
1215
+ const loopTurns = turns.filter((turn) => turn.loop_id === loopName);
1216
+ const parentToolCallId = loopTurns.map(parentForkToolCallIdFromTurn).find(Boolean);
1217
+ const explicitToolCall = parentToolCallId && !usedToolCallIds.has(parentToolCallId) ? agentToolCallsById.get(parentToolCallId) ?? null : null;
1218
+ const fallbackToolCall = explicitToolCall === null ? agentToolCalls.find(
1219
+ (toolCall2) => !usedToolCallIds.has(toolCall2.id) && !explicitParentToolCallIds.has(toolCall2.id)
1220
+ ) : null;
1221
+ const toolCall = explicitToolCall ?? fallbackToolCall;
1222
+ if (!toolCall) continue;
1223
+ usedToolCallIds.add(toolCall.id);
1224
+ loops[loopName] = {
1225
+ toolCallId: toolCall.id,
1226
+ description: parseAgentDescription(toolCall.arguments),
1227
+ status: inferLoopStatusFromTurns(loopTurns, loopMessages)
1228
+ };
1229
+ }
1230
+ return loops;
1231
+ }
1232
+ function applyPlanningSideEffects(sessionId, turns) {
1233
+ const latestMode = [...turns].reverse().map((turn) => extractModeFromBlocks(turn.blocks)).find((mode) => mode !== null);
1234
+ if (latestMode !== "planning") return;
1235
+ if (_getActiveSessionId?.() !== sessionId) return;
1236
+ const ui = useUiStore.getState();
1237
+ ui.setActiveRightTab("situation");
1238
+ if (ui.rightPanelCollapsed) {
1239
+ ui.toggleRightPanel();
1240
+ }
1241
+ }
1242
+ function materialize(turns) {
1243
+ const messages = turns.map(projectionToMessage).filter((message) => message !== null);
1244
+ const activeCompaction = [...turns].reverse().find(
1245
+ (turn) => turn.kind === "compaction" && turn.status === "streaming" && typeof turn.compaction_id === "string"
1246
+ );
1247
+ return {
1248
+ messages,
1249
+ agentLoops: rebuildAgentLoops(turns),
1250
+ activeCompaction: activeCompaction ? {
1251
+ turn_id: activeCompaction.turn_id,
1252
+ status: activeCompaction.status,
1253
+ compaction_id: activeCompaction.compaction_id,
1254
+ summary_preview: activeCompaction.summary_preview,
1255
+ summary_full: activeCompaction.summary_full,
1256
+ archived_count: activeCompaction.archived_count,
1257
+ archived_files: activeCompaction.archived_files,
1258
+ archived_tool_calls: activeCompaction.archived_tool_calls,
1259
+ tokens_before: activeCompaction.tokens_before,
1260
+ tokens_after: activeCompaction.tokens_after,
1261
+ saved_ratio: activeCompaction.saved_ratio,
1262
+ trigger: activeCompaction.trigger,
1263
+ failure_reason: activeCompaction.failure_reason,
1264
+ fallback_applied: activeCompaction.fallback_applied
1265
+ } : null
1266
+ };
1267
+ }
1268
+ var ERROR_ANCHOR_PREFIX = "error-anchor:";
1269
+ function updateSessionState(state, sessionId, turns) {
1270
+ const orderedTurns = [...turns].sort((left, right) => left.sequence - right.sequence);
1271
+ const { messages, agentLoops, activeCompaction } = materialize(orderedTurns);
1272
+ applyPlanningSideEffects(sessionId, orderedTurns);
1273
+ const lastTurnId = orderedTurns[orderedTurns.length - 1]?.turn_id ?? null;
1274
+ const preservedErrors = lastTurnId ? (state.messages[sessionId] ?? []).filter(
1275
+ (m) => m.role === "error" && typeof m.entry_id === "string" && m.entry_id.startsWith(`${ERROR_ANCHOR_PREFIX}${lastTurnId}:`)
1276
+ ) : [];
1277
+ const mergedMessages = preservedErrors.length > 0 ? [...messages, ...preservedErrors] : messages;
1278
+ return {
1279
+ turns: { ...state.turns, [sessionId]: orderedTurns },
1280
+ messages: { ...state.messages, [sessionId]: mergedMessages },
1281
+ agentLoops: { ...state.agentLoops, [sessionId]: agentLoops },
1282
+ activeCompactions: { ...state.activeCompactions, [sessionId]: activeCompaction }
1283
+ };
1284
+ }
1285
+ var useChatStore = create3()((set) => ({
1286
+ ...createClientActions(set),
1287
+ turns: {},
1288
+ messages: {},
1289
+ askAnswers: {},
1290
+ isStreaming: {},
1291
+ agentLoops: {},
1292
+ activeCompactions: {},
1293
+ addUserMessage: (sessionId, content) => {
1294
+ set((state) => {
1295
+ const existing = state.turns[sessionId] ?? [];
1296
+ const turnId = `local-user-${Date.now()}`;
1297
+ const optimisticTurn = {
1298
+ id: turnId,
1299
+ sequence: Math.max(0, ...existing.map((turn) => turn.sequence)) + 1,
1300
+ turn_id: turnId,
1301
+ loop_id: "root",
1302
+ role: "user",
1303
+ status: "completed",
1304
+ blocks: [{ type: "text", content }],
1305
+ tool_calls: [],
1306
+ model: null,
1307
+ usage: null,
1308
+ duration_ms: 0
1309
+ };
1310
+ return updateSessionState(state, sessionId, [...existing, optimisticTurn]);
1311
+ });
1312
+ },
1313
+ setTurns: (sessionId, turns) => {
1314
+ set((state) => updateSessionState(state, sessionId, [...turns]));
1315
+ },
1316
+ upsertTurn: (sessionId, turn) => {
1317
+ set((state) => {
1318
+ const existing = [...state.turns[sessionId] ?? []];
1319
+ const index = existing.findIndex((item) => item.turn_id === turn.turn_id);
1320
+ if (index >= 0) {
1321
+ existing[index] = turn;
1322
+ } else {
1323
+ existing.push(turn);
1324
+ }
1325
+ return {
1326
+ ...updateSessionState(state, sessionId, existing)
1327
+ };
1328
+ });
1329
+ },
1330
+ applyTurnPatch: (sessionId, patch) => {
1331
+ set((state) => {
1332
+ const turn = patch.data.turn;
1333
+ if (!turn) return state;
1334
+ const existing = [...state.turns[sessionId] ?? []];
1335
+ const index = existing.findIndex((item) => item.turn_id === turn.turn_id);
1336
+ const lastSequence = index >= 0 ? existing[index].sequence : null;
1337
+ if (lastSequence !== null && patch.sequence <= lastSequence) {
1338
+ return state;
1339
+ }
1340
+ const nextTurn = {
1341
+ ...turn,
1342
+ sequence: patch.sequence
1343
+ };
1344
+ if (index >= 0) {
1345
+ existing[index] = nextTurn;
1346
+ } else {
1347
+ existing.push(nextTurn);
1348
+ }
1349
+ return {
1350
+ ...updateSessionState(state, sessionId, existing)
1351
+ };
1352
+ });
1353
+ },
1354
+ addErrorMessage: (sessionId, content) => {
1355
+ set((state) => {
1356
+ const turns = state.turns[sessionId] ?? [];
1357
+ const anchorTurnId = turns[turns.length - 1]?.turn_id ?? null;
1358
+ const entry_id = anchorTurnId ? `${ERROR_ANCHOR_PREFIX}${anchorTurnId}:${Date.now()}` : void 0;
1359
+ return {
1360
+ messages: {
1361
+ ...state.messages,
1362
+ [sessionId]: [
1363
+ ...state.messages[sessionId] ?? [],
1364
+ { role: "error", content, loop_name: "root", entry_id }
1365
+ ]
1366
+ }
1367
+ };
1368
+ });
1369
+ },
1370
+ markInterrupted: (sessionId) => {
1371
+ set((state) => {
1372
+ const turns = (state.turns[sessionId] ?? []).map((turn) => {
1373
+ if (turn.status !== "streaming") return turn;
1374
+ return {
1375
+ ...turn,
1376
+ status: "interrupted",
1377
+ tool_calls: turn.tool_calls.map(
1378
+ (toolCall) => toolCall.status === "pending" || toolCall.status === "awaiting_answer" ? { ...toolCall, status: "cancelled" } : toolCall
1379
+ )
1380
+ };
1381
+ });
1382
+ return {
1383
+ ...updateSessionState(state, sessionId, turns)
1384
+ };
1385
+ });
1386
+ },
1387
+ markFailed: (sessionId) => {
1388
+ set((state) => {
1389
+ const turns = (state.turns[sessionId] ?? []).map((turn) => {
1390
+ if (turn.status !== "streaming") return turn;
1391
+ return {
1392
+ ...turn,
1393
+ status: "failed",
1394
+ tool_calls: turn.tool_calls.map(
1395
+ (toolCall) => toolCall.status === "pending" || toolCall.status === "awaiting_answer" ? { ...toolCall, status: "error" } : toolCall
1396
+ )
1397
+ };
1398
+ });
1399
+ return {
1400
+ ...updateSessionState(state, sessionId, turns)
1401
+ };
1402
+ });
1403
+ },
1404
+ setStreaming: (sessionId, streaming) => {
1405
+ set((state) => ({
1406
+ isStreaming: { ...state.isStreaming, [sessionId]: streaming }
1407
+ }));
1408
+ },
1409
+ setAskAnswers: (sessionId, answers) => {
1410
+ set((state) => ({
1411
+ askAnswers: {
1412
+ ...state.askAnswers,
1413
+ [sessionId]: answers
1414
+ }
1415
+ }));
1416
+ },
1417
+ clearMessages: (sessionId) => {
1418
+ set((state) => {
1419
+ const { [sessionId]: _turns, ...restTurns } = state.turns;
1420
+ const { [sessionId]: _messages, ...restMessages } = state.messages;
1421
+ const { [sessionId]: _answers, ...restAnswers } = state.askAnswers;
1422
+ const { [sessionId]: _loops, ...restLoops } = state.agentLoops;
1423
+ const { [sessionId]: _compaction, ...restCompactions } = state.activeCompactions;
1424
+ return {
1425
+ turns: restTurns,
1426
+ messages: restMessages,
1427
+ askAnswers: restAnswers,
1428
+ agentLoops: restLoops,
1429
+ activeCompactions: restCompactions
1430
+ };
1431
+ });
1432
+ }
1433
+ }));
1434
+
1435
+ // src/react/stores/task-store.ts
1436
+ import { create as create4 } from "zustand";
1437
+ var EMPTY_TASKS = [];
1438
+ var useTaskStore = create4()((set, get) => ({
1439
+ ...createClientActions(set),
1440
+ tasks: {},
1441
+ setTasks: (sessionId, tasks) => {
1442
+ set((state) => ({
1443
+ tasks: { ...state.tasks, [sessionId]: tasks }
1444
+ }));
1445
+ },
1446
+ getTasks: (sessionId) => {
1447
+ return get().tasks[sessionId] ?? EMPTY_TASKS;
1448
+ }
1449
+ }));
1450
+
1451
+ // src/react/stores/session-store.ts
1452
+ var DEFAULT_SESSION_MODE = "executing";
1453
+ var onSessionChange = null;
1454
+ function setOnSessionChange(fn) {
1455
+ onSessionChange = fn;
1456
+ }
1457
+ function invalidateHomeSidebarSessions() {
1458
+ const queryClient = globalThis.__agentQueryClient;
1459
+ if (!queryClient) return;
1460
+ void queryClient.invalidateQueries({ queryKey: ["sessions", "home-sidebar"] });
1461
+ }
1462
+ function isSessionAccessRevoked(error) {
1463
+ if (error instanceof Error) {
1464
+ const msg = error.message;
1465
+ return msg.includes("API 403") || msg.includes("API 404");
1466
+ }
1467
+ return false;
1468
+ }
1469
+ function removeSessionArtifacts(sessionId) {
1470
+ useChatStore.getState().clearMessages(sessionId);
1471
+ useTaskStore.getState().setTasks(sessionId, []);
1472
+ }
1473
+ function pruneSessionState(state, sessionId) {
1474
+ const nextFresh = new Set(state._freshSessions);
1475
+ nextFresh.delete(sessionId);
1476
+ const { [sessionId]: _mode, ...modes } = state.modes;
1477
+ const { [sessionId]: _rewindDraft, ...rewindDrafts } = state.rewindDrafts;
1478
+ return {
1479
+ sessions: state.sessions.filter((session) => session.id !== sessionId),
1480
+ activeSessionId: state.activeSessionId === sessionId ? null : state.activeSessionId,
1481
+ modes,
1482
+ rewindDrafts,
1483
+ _freshSessions: nextFresh
1484
+ };
1485
+ }
1486
+ function navigateAwayFromSession(sessionId) {
1487
+ if (typeof window === "undefined") return;
1488
+ if (window.location.pathname !== `/chat/${sessionId}`) return;
1489
+ window.history.replaceState(window.history.state, "", "/chat");
1490
+ window.dispatchEvent(new PopStateEvent("popstate"));
1491
+ }
1492
+ function handleUnreadableSession(set, get, sessionId) {
1493
+ const wasActive = get().activeSessionId === sessionId;
1494
+ removeSessionArtifacts(sessionId);
1495
+ set((state) => pruneSessionState(state, sessionId));
1496
+ if (wasActive) {
1497
+ onSessionChange?.(null);
1498
+ navigateAwayFromSession(sessionId);
1499
+ }
1500
+ invalidateHomeSidebarSessions();
1501
+ }
1502
+ function isPlainRecord(value) {
1503
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1504
+ }
1505
+ function extractModeFromBlocks2(blocks) {
1506
+ const modeBlock = blocks.find((block) => block.type === "mode_change");
1507
+ if (modeBlock && isPlainRecord(modeBlock.content) && (modeBlock.content.to === "planning" || modeBlock.content.to === "executing")) {
1508
+ return modeBlock.content.to;
1509
+ }
1510
+ if (blocks.some((block) => block.type === "planning_enter")) return "planning";
1511
+ if (blocks.some((block) => block.type === "planning_exit")) return "executing";
1512
+ return null;
1513
+ }
1514
+ function toSelectionMap(value) {
1515
+ if (!isPlainRecord(value)) return {};
1516
+ const entries = Object.entries(value).map(([questionKey, optionIndexes]) => {
1517
+ if (!Array.isArray(optionIndexes)) return null;
1518
+ const parsedIndexes = optionIndexes.map((item) => typeof item === "number" ? item : Number(item)).filter((item) => Number.isInteger(item));
1519
+ return [Number(questionKey), parsedIndexes];
1520
+ }).filter((entry) => entry !== null);
1521
+ return Object.fromEntries(entries);
1522
+ }
1523
+ function toCustomMap(value) {
1524
+ if (!isPlainRecord(value)) return {};
1525
+ const entries = Object.entries(value).filter(([, text]) => typeof text === "string").map(([questionKey, text]) => [Number(questionKey), text]);
1526
+ return Object.fromEntries(entries);
1527
+ }
1528
+ function extractAskAnswers(turns) {
1529
+ const answers = {};
1530
+ for (const turn of turns) {
1531
+ for (const block of turn.blocks) {
1532
+ if (block.type !== "ask_user_answer" || typeof block.tool_call_id !== "string") continue;
1533
+ answers[block.tool_call_id] = {
1534
+ selections: toSelectionMap(isPlainRecord(block.content) ? block.content.selections : void 0),
1535
+ custom: toCustomMap(isPlainRecord(block.content) ? block.content.custom : void 0)
1536
+ };
1537
+ }
1538
+ }
1539
+ return answers;
1540
+ }
1541
+ function registerCreatedSessionState(set, session, mode = DEFAULT_SESSION_MODE) {
1542
+ useChatStore.getState().setTurns(session.id, []);
1543
+ useTaskStore.getState().setTasks(session.id, []);
1544
+ set((state) => ({
1545
+ sessions: [session, ...state.sessions.filter((item) => item.id !== session.id)],
1546
+ modes: { ...state.modes, [session.id]: state.modes[session.id] ?? mode },
1547
+ _freshSessions: new Set(state._freshSessions).add(session.id)
1548
+ }));
1549
+ }
1550
+ async function revalidateViewerSessions(existingSessions) {
1551
+ const viewerSessions = existingSessions.filter((session) => session.viewer_role === "viewer");
1552
+ if (viewerSessions.length === 0) {
1553
+ return [];
1554
+ }
1555
+ const refreshed = await Promise.all(
1556
+ viewerSessions.map(async (session) => {
1557
+ try {
1558
+ return await getSession(session.id);
1559
+ } catch (error) {
1560
+ if (isSessionAccessRevoked(error)) {
1561
+ return null;
1562
+ }
1563
+ return session;
1564
+ }
1565
+ })
1566
+ );
1567
+ return refreshed.filter((session) => session !== null);
1568
+ }
1569
+ var useSessionStore = create5()((set, get) => ({
1570
+ ...createClientActions(set),
1571
+ sessions: [],
1572
+ activeSessionId: null,
1573
+ loading: false,
1574
+ modes: {},
1575
+ rewindDrafts: {},
1576
+ _freshSessions: /* @__PURE__ */ new Set(),
1577
+ fetchSessions: async () => {
1578
+ set({ loading: true });
1579
+ try {
1580
+ const currentState = get();
1581
+ const [sessions, viewerSessions] = await Promise.all([
1582
+ listSessions(),
1583
+ revalidateViewerSessions(currentState.sessions)
1584
+ ]);
1585
+ const knownSessionIds = new Set(sessions.map((session) => session.id));
1586
+ const mergedSessions = [
1587
+ ...sessions,
1588
+ ...viewerSessions.filter((session) => !knownSessionIds.has(session.id))
1589
+ ];
1590
+ const removedSessionIds = currentState.sessions.filter(
1591
+ (session) => session.viewer_role === "viewer" && !mergedSessions.some((candidate) => candidate.id === session.id)
1592
+ ).map((session) => session.id);
1593
+ const activeSessionId = currentState.activeSessionId;
1594
+ for (const sessionId of removedSessionIds) {
1595
+ removeSessionArtifacts(sessionId);
1596
+ }
1597
+ let nextState = {
1598
+ sessions: mergedSessions,
1599
+ activeSessionId: currentState.activeSessionId,
1600
+ loading: false,
1601
+ modes: currentState.modes,
1602
+ rewindDrafts: currentState.rewindDrafts,
1603
+ _freshSessions: currentState._freshSessions
1604
+ };
1605
+ for (const sessionId of removedSessionIds) {
1606
+ nextState = {
1607
+ ...nextState,
1608
+ ...pruneSessionState(
1609
+ {
1610
+ ...currentState,
1611
+ sessions: nextState.sessions ?? currentState.sessions,
1612
+ activeSessionId: nextState.activeSessionId ?? currentState.activeSessionId,
1613
+ loading: nextState.loading ?? currentState.loading,
1614
+ modes: nextState.modes ?? currentState.modes,
1615
+ rewindDrafts: nextState.rewindDrafts ?? currentState.rewindDrafts,
1616
+ _freshSessions: nextState._freshSessions ?? currentState._freshSessions
1617
+ },
1618
+ sessionId
1619
+ )
1620
+ };
1621
+ }
1622
+ set(nextState);
1623
+ if (activeSessionId && removedSessionIds.includes(activeSessionId)) {
1624
+ onSessionChange?.(null);
1625
+ navigateAwayFromSession(activeSessionId);
1626
+ }
1627
+ invalidateHomeSidebarSessions();
1628
+ } catch (error) {
1629
+ set({ loading: false });
1630
+ throw error;
1631
+ }
1632
+ },
1633
+ createSession: async (intent) => {
1634
+ const { session_id } = await createSession(intent);
1635
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1636
+ get().registerCreatedSession({
1637
+ id: session_id,
1638
+ intent: intent ?? "",
1639
+ status: "created",
1640
+ created_at: now,
1641
+ updated_at: now
1642
+ });
1643
+ get().setActiveSession(session_id);
1644
+ invalidateHomeSidebarSessions();
1645
+ get().fetchSessions().catch(() => {
1646
+ });
1647
+ return session_id;
1648
+ },
1649
+ registerCreatedSession: (session, mode = DEFAULT_SESSION_MODE) => {
1650
+ registerCreatedSessionState(set, session, mode);
1651
+ },
1652
+ upsertSession: (session) => {
1653
+ set((state) => ({
1654
+ sessions: state.sessions.some((item) => item.id === session.id) ? state.sessions.map((item) => item.id === session.id ? { ...item, ...session } : item) : [session, ...state.sessions]
1655
+ }));
1656
+ },
1657
+ isFreshSession: (sessionId) => get()._freshSessions.has(sessionId),
1658
+ setActiveSession: (id) => {
1659
+ set({ activeSessionId: id });
1660
+ onSessionChange?.(id);
1661
+ getSession(id).then((detail) => {
1662
+ set((state) => ({
1663
+ sessions: state.sessions.some((s) => s.id === id) ? state.sessions.map(
1664
+ (s) => s.id === id ? { ...s, status: detail.status, updated_at: detail.updated_at } : s
1665
+ ) : [detail, ...state.sessions]
1666
+ }));
1667
+ }).catch(() => {
1668
+ });
1669
+ const tasksPromise = getSessionTasks(id).catch(() => null);
1670
+ Promise.all([getSessionTurns(id), tasksPromise]).then(([turns, tasks]) => {
1671
+ if (tasks) useTaskStore.getState().setTasks(id, tasks);
1672
+ useChatStore.getState().setAskAnswers(id, extractAskAnswers(turns));
1673
+ let inferredMode = DEFAULT_SESSION_MODE;
1674
+ for (const turn of turns) {
1675
+ const nextMode = extractModeFromBlocks2(turn.blocks);
1676
+ if (nextMode) {
1677
+ inferredMode = nextMode;
1678
+ }
1679
+ }
1680
+ set((state) => {
1681
+ if (state.modes[id] == null) {
1682
+ return { modes: { ...state.modes, [id]: inferredMode } };
1683
+ }
1684
+ return state;
1685
+ });
1686
+ const isFresh = get()._freshSessions.has(id);
1687
+ if (isFresh) {
1688
+ set((state) => {
1689
+ const next = new Set(state._freshSessions);
1690
+ next.delete(id);
1691
+ return { _freshSessions: next };
1692
+ });
1693
+ if (turns.length > 0) {
1694
+ useChatStore.getState().setTurns(id, turns);
1695
+ }
1696
+ } else {
1697
+ useChatStore.getState().setTurns(id, turns);
1698
+ }
1699
+ }).catch((error) => {
1700
+ if (isSessionAccessRevoked(error)) {
1701
+ handleUnreadableSession(set, get, id);
1702
+ return;
1703
+ }
1704
+ console.error("Failed to load session data", error);
1705
+ });
1706
+ },
1707
+ clearActiveSession: () => {
1708
+ set({ activeSessionId: null });
1709
+ onSessionChange?.(null);
1710
+ },
1711
+ deleteSession: async (id) => {
1712
+ await deleteSession(id);
1713
+ invalidateHomeSidebarSessions();
1714
+ const wasActive = get().activeSessionId === id;
1715
+ await get().fetchSessions();
1716
+ if (!wasActive) {
1717
+ return;
1718
+ }
1719
+ const nextId = get().sessions[0]?.id ?? null;
1720
+ if (nextId) {
1721
+ get().setActiveSession(nextId);
1722
+ return;
1723
+ }
1724
+ get().clearActiveSession();
1725
+ },
1726
+ updateSessionStatus: (sessionId, status) => {
1727
+ set((state) => ({
1728
+ sessions: state.sessions.map((s) => s.id === sessionId ? { ...s, status } : s)
1729
+ }));
1730
+ if (status === "failed" || status === "interrupted") {
1731
+ useChatStore.getState().setStreaming(sessionId, false);
1732
+ if (status === "interrupted") {
1733
+ useChatStore.getState().markInterrupted(sessionId);
1734
+ } else {
1735
+ useChatStore.getState().markFailed(sessionId);
1736
+ }
1737
+ }
1738
+ },
1739
+ updateSessionIntent: (sessionId, intent) => {
1740
+ set((state) => ({
1741
+ sessions: state.sessions.map((s) => s.id === sessionId ? { ...s, intent } : s)
1742
+ }));
1743
+ },
1744
+ patchSession: (sessionId, patch) => {
1745
+ set((state) => ({
1746
+ sessions: state.sessions.map(
1747
+ (s) => s.id === sessionId ? { ...s, ...patch } : s
1748
+ )
1749
+ }));
1750
+ },
1751
+ pinSession: async (sessionId, pinned) => {
1752
+ const updated = await pinSession(sessionId, pinned);
1753
+ set((state) => ({
1754
+ sessions: state.sessions.map(
1755
+ (s) => s.id === sessionId ? { ...s, is_pinned: updated.is_pinned, pinned_at: updated.pinned_at } : s
1756
+ )
1757
+ }));
1758
+ invalidateHomeSidebarSessions();
1759
+ },
1760
+ setRewindDraft: (sessionId, text) => {
1761
+ set((state) => {
1762
+ if (text == null) {
1763
+ const { [sessionId]: _, ...rest } = state.rewindDrafts;
1764
+ return { rewindDrafts: rest };
1765
+ }
1766
+ return {
1767
+ rewindDrafts: {
1768
+ ...state.rewindDrafts,
1769
+ [sessionId]: text
1770
+ }
1771
+ };
1772
+ });
1773
+ },
1774
+ setMode: (sessionId, mode) => {
1775
+ set((state) => ({ modes: { ...state.modes, [sessionId]: mode } }));
1776
+ },
1777
+ togglePlanningMode: (sessionId) => {
1778
+ const current = get().modes[sessionId] ?? DEFAULT_SESSION_MODE;
1779
+ const next = current === "executing" ? "planning" : "executing";
1780
+ set((state) => ({ modes: { ...state.modes, [sessionId]: next } }));
1781
+ },
1782
+ toggleSharing: async (sessionId) => {
1783
+ const session = get().sessions.find((s) => s.id === sessionId);
1784
+ if (!session) return;
1785
+ const newShared = !session.shared;
1786
+ const result = await updateSharing(sessionId, newShared);
1787
+ set((state) => ({
1788
+ sessions: state.sessions.map(
1789
+ (s) => s.id === sessionId ? { ...s, shared: result.shared } : s
1790
+ )
1791
+ }));
1792
+ },
1793
+ reset: () => {
1794
+ set({
1795
+ sessions: [],
1796
+ activeSessionId: null,
1797
+ loading: false,
1798
+ modes: {},
1799
+ rewindDrafts: {},
1800
+ _freshSessions: /* @__PURE__ */ new Set()
1801
+ });
1802
+ invalidateHomeSidebarSessions();
1803
+ }
1804
+ }));
1805
+ setChatStoreSessionAccessor(() => useSessionStore.getState().activeSessionId);
1806
+
1807
+ // src/react/stores/auth-store.ts
1808
+ var noopStorage = {
1809
+ getItem: () => null,
1810
+ setItem: () => {
1811
+ },
1812
+ removeItem: () => {
1813
+ }
1814
+ };
1815
+ function shouldClearPersistedAuthState(value) {
1816
+ try {
1817
+ const parsed = JSON.parse(value);
1818
+ return parsed.state?.token == null && parsed.state?.user == null;
1819
+ } catch {
1820
+ return false;
1821
+ }
1822
+ }
1823
+ var authStorage = createJSONStorage(() => {
1824
+ if (typeof localStorage === "undefined") {
1825
+ return noopStorage;
1826
+ }
1827
+ return {
1828
+ getItem: (name) => localStorage.getItem(name),
1829
+ setItem: (name, value) => {
1830
+ if (shouldClearPersistedAuthState(value)) {
1831
+ localStorage.removeItem(name);
1832
+ return;
1833
+ }
1834
+ localStorage.setItem(name, value);
1835
+ },
1836
+ removeItem: (name) => localStorage.removeItem(name)
1837
+ };
1838
+ });
1839
+ function finishAuth(set, payload) {
1840
+ set({
1841
+ token: payload.token,
1842
+ socketAuthToken: null,
1843
+ user: payload.user,
1844
+ loading: false,
1845
+ error: null
1846
+ });
1847
+ agentSocket?.reconnect();
1848
+ useSessionStore.getState().fetchSessions().catch(() => {
1849
+ });
1850
+ }
1851
+ function finishCookieHydration(set, payload) {
1852
+ set({
1853
+ token: null,
1854
+ socketAuthToken: payload.token,
1855
+ user: payload.user,
1856
+ loading: false,
1857
+ error: null
1858
+ });
1859
+ agentSocket?.reconnect();
1860
+ useSessionStore.getState().fetchSessions().catch(() => {
1861
+ });
1862
+ }
1863
+ function toUser(info) {
1864
+ return {
1865
+ id: info.id,
1866
+ username: info.username,
1867
+ display_name: info.display_name,
1868
+ avatar_url: info.avatar_url,
1869
+ is_admin: info.is_admin
1870
+ };
1871
+ }
1872
+ var useAuthStore = create6()(
1873
+ persist(
1874
+ (set, get) => ({
1875
+ ...createClientActions(set),
1876
+ token: null,
1877
+ socketAuthToken: null,
1878
+ user: null,
1879
+ loading: false,
1880
+ error: null,
1881
+ logout: () => {
1882
+ void logout().catch(() => {
1883
+ });
1884
+ set({ token: null, socketAuthToken: null, user: null, error: null });
1885
+ agentSocket?.disconnect();
1886
+ useSessionStore.getState().reset();
1887
+ },
1888
+ checkAuth: async () => {
1889
+ const token = get().token;
1890
+ if (!token) return;
1891
+ try {
1892
+ const auth = await getMe();
1893
+ finishAuth(set, { token: auth.token, user: toUser(auth) });
1894
+ } catch {
1895
+ set({ token: null, socketAuthToken: null, user: null, error: null });
1896
+ useSessionStore.getState().reset();
1897
+ }
1898
+ },
1899
+ hydrateFromCookie: async () => {
1900
+ set({ loading: true, error: null });
1901
+ const previousToken = get().token;
1902
+ if (previousToken) {
1903
+ try {
1904
+ const auth = await getMe();
1905
+ finishAuth(set, { token: auth.token, user: toUser(auth) });
1906
+ return;
1907
+ } catch {
1908
+ set({ token: null, socketAuthToken: null, user: null, error: null });
1909
+ useSessionStore.getState().reset();
1910
+ }
1911
+ }
1912
+ try {
1913
+ const auth = await getMe();
1914
+ finishCookieHydration(set, { token: auth.token, user: toUser(auth) });
1915
+ } catch {
1916
+ set({
1917
+ token: null,
1918
+ socketAuthToken: null,
1919
+ user: null,
1920
+ loading: false,
1921
+ error: null
1922
+ });
1923
+ useSessionStore.getState().reset();
1924
+ }
1925
+ }
1926
+ }),
1927
+ {
1928
+ name: "agent-auth",
1929
+ storage: authStorage,
1930
+ partialize: (state) => {
1931
+ if (state.socketAuthToken) {
1932
+ return { token: null, user: null };
1933
+ }
1934
+ return { token: state.token, user: state.user };
1935
+ }
1936
+ }
1937
+ )
1938
+ );
1939
+
1940
+ // src/react/stores/background-store.ts
1941
+ import { create as create7 } from "zustand";
1942
+ var useBackgroundStore = create7()((set) => ({
1943
+ ...createClientActions(set),
1944
+ tasks: {},
1945
+ selectedTaskId: {},
1946
+ setTasks: (sessionId, tasks) => set((state) => ({
1947
+ tasks: { ...state.tasks, [sessionId]: tasks },
1948
+ selectedTaskId: {
1949
+ ...state.selectedTaskId,
1950
+ [sessionId]: state.selectedTaskId[sessionId] ?? tasks[0]?.id ?? null
1951
+ }
1952
+ })),
1953
+ upsertTask: (sessionId, task) => set((state) => {
1954
+ const existing = state.tasks[sessionId] ?? [];
1955
+ const index = existing.findIndex((item) => item.id === task.id);
1956
+ const next = [...existing];
1957
+ if (index >= 0) {
1958
+ next[index] = { ...next[index], ...task };
1959
+ } else {
1960
+ next.unshift(task);
1961
+ }
1962
+ return {
1963
+ tasks: { ...state.tasks, [sessionId]: next },
1964
+ selectedTaskId: {
1965
+ ...state.selectedTaskId,
1966
+ [sessionId]: state.selectedTaskId[sessionId] ?? task.id
1967
+ }
1968
+ };
1969
+ }),
1970
+ selectTask: (sessionId, taskId) => set((state) => ({
1971
+ selectedTaskId: { ...state.selectedTaskId, [sessionId]: taskId }
1972
+ }))
1973
+ }));
1974
+
1975
+ // src/react/stores/connection-store.ts
1976
+ import { create as create8 } from "zustand";
1977
+ var initialConnectionState = {
1978
+ status: "disconnected",
1979
+ reconnectAttempt: 0,
1980
+ lastConnectedAt: null,
1981
+ lastDisconnectedAt: null,
1982
+ hasEverConnected: false
1983
+ };
1984
+ var useConnectionStore = create8()((set) => ({
1985
+ ...createClientActions(set),
1986
+ ...initialConnectionState,
1987
+ markConnecting: (attempt = 0) => set({
1988
+ status: "connecting",
1989
+ reconnectAttempt: attempt
1990
+ }),
1991
+ markConnected: () => set({
1992
+ status: "connected",
1993
+ reconnectAttempt: 0,
1994
+ lastConnectedAt: Date.now(),
1995
+ hasEverConnected: true
1996
+ }),
1997
+ markDisconnected: () => set({
1998
+ status: "disconnected",
1999
+ reconnectAttempt: 0,
2000
+ lastDisconnectedAt: Date.now()
2001
+ }),
2002
+ reset: () => set(initialConnectionState)
2003
+ }));
2004
+
2005
+ // src/react/stores/runtime-store.ts
2006
+ import { create as create9 } from "zustand";
2007
+ var useRuntimeStore = create9()((set) => ({
2008
+ ...createClientActions(set),
2009
+ events: {},
2010
+ addEvent: (sessionId, event) => set((state) => {
2011
+ const current = state.events[sessionId] ?? [];
2012
+ const nextEvent = {
2013
+ ...event,
2014
+ id: `${Date.now()}-${current.length}`,
2015
+ sessionId,
2016
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2017
+ };
2018
+ return {
2019
+ events: {
2020
+ ...state.events,
2021
+ [sessionId]: [...current.slice(-59), nextEvent]
2022
+ }
2023
+ };
2024
+ }),
2025
+ clearSession: (sessionId) => set((state) => ({
2026
+ events: { ...state.events, [sessionId]: [] }
2027
+ }))
2028
+ }));
2029
+
2030
+ // src/react/stores/gis-store.ts
2031
+ import { create as create10 } from "zustand";
2032
+ var EMPTY_GOALS = [];
2033
+ var EMPTY_RESOURCES = [];
2034
+ var EMPTY_TARGETS = [];
2035
+ var EMPTY_COMMANDS = [];
2036
+ function newCommandId() {
2037
+ if (typeof globalThis !== "undefined" && "crypto" in globalThis) {
2038
+ return globalThis.crypto?.randomUUID?.() ?? `gis-map-${Date.now()}-${Math.random()}`;
2039
+ }
2040
+ return `gis-map-${Date.now()}-${Math.random()}`;
2041
+ }
2042
+ var useGisStore = create10()((set, get) => ({
2043
+ ...createClientActions(set),
2044
+ goalsBySession: {},
2045
+ resourcesBySession: {},
2046
+ targetsBySession: {},
2047
+ pendingMapCommandsBySession: {},
2048
+ setGoals: (sessionId, goals) => {
2049
+ set((state) => ({
2050
+ goalsBySession: { ...state.goalsBySession, [sessionId]: goals }
2051
+ }));
2052
+ },
2053
+ setResources: (sessionId, resources) => {
2054
+ set((state) => ({
2055
+ resourcesBySession: { ...state.resourcesBySession, [sessionId]: resources }
2056
+ }));
2057
+ },
2058
+ setTargets: (sessionId, targets) => {
2059
+ set((state) => ({
2060
+ targetsBySession: { ...state.targetsBySession, [sessionId]: targets }
2061
+ }));
2062
+ },
2063
+ pushMapCommand: (sessionId, command) => {
2064
+ set((state) => ({
2065
+ pendingMapCommandsBySession: {
2066
+ ...state.pendingMapCommandsBySession,
2067
+ [sessionId]: [
2068
+ ...state.pendingMapCommandsBySession[sessionId] ?? EMPTY_COMMANDS,
2069
+ {
2070
+ ...command,
2071
+ id: newCommandId(),
2072
+ createdAt: Date.now()
2073
+ }
2074
+ ]
2075
+ }
2076
+ }));
2077
+ },
2078
+ consumeMapCommand: (sessionId, commandId) => {
2079
+ set((state) => ({
2080
+ pendingMapCommandsBySession: {
2081
+ ...state.pendingMapCommandsBySession,
2082
+ [sessionId]: (state.pendingMapCommandsBySession[sessionId] ?? EMPTY_COMMANDS).filter(
2083
+ (command) => command.id !== commandId
2084
+ )
2085
+ }
2086
+ }));
2087
+ },
2088
+ getGoals: (sessionId) => get().goalsBySession[sessionId] ?? EMPTY_GOALS,
2089
+ getResources: (sessionId) => get().resourcesBySession[sessionId] ?? EMPTY_RESOURCES,
2090
+ getTargets: (sessionId) => get().targetsBySession[sessionId] ?? EMPTY_TARGETS,
2091
+ getPendingMapCommands: (sessionId) => get().pendingMapCommandsBySession[sessionId] ?? EMPTY_COMMANDS
2092
+ }));
2093
+
2094
+ // src/react/sockets/event-bridge.ts
2095
+ function extractSkillIdForAnalytics(message) {
2096
+ const text = getTextContent(message);
2097
+ const tagMatch = text.match(/<skill>([^<]+)<\/skill>/);
2098
+ if (tagMatch?.[1]?.trim()) {
2099
+ return tagMatch[1].trim();
2100
+ }
2101
+ const slashCommandMatch = text.match(/^请使用\s+(.+?)\s+skill\s+完成任务(?:\n|$)/);
2102
+ if (slashCommandMatch?.[1]?.trim()) {
2103
+ return slashCommandMatch[1].trim();
2104
+ }
2105
+ return void 0;
2106
+ }
2107
+ function invalidateSessionCheckpoints(sessionId) {
2108
+ const queryClient = globalThis.__agentQueryClient;
2109
+ if (!queryClient) return;
2110
+ void queryClient.invalidateQueries({ queryKey: ["checkpoints", sessionId] });
2111
+ }
2112
+ function invalidateFileTree(sessionId) {
2113
+ const queryClient = globalThis.__agentQueryClient;
2114
+ if (!queryClient) return;
2115
+ void queryClient.invalidateQueries({ queryKey: ["file-tree", sessionId] });
2116
+ }
2117
+ function shouldSuppressOptimisticUserMessage(sessionId, askuserAnswer) {
2118
+ if (askuserAnswer && typeof askuserAnswer.tool_call_id === "string") {
2119
+ return true;
2120
+ }
2121
+ const session = useSessionStore.getState().sessions.find((item) => item.id === sessionId);
2122
+ if (session?.status !== "waiting_for_input") {
2123
+ return false;
2124
+ }
2125
+ const messages = useChatStore.getState().messages[sessionId] ?? [];
2126
+ return messages.some(
2127
+ (message) => (message.tool_calls ?? []).some((toolCall) => toolCall.status === "awaiting_answer")
2128
+ );
2129
+ }
2130
+ function invalidateSkillStats2(sessionId) {
2131
+ const queryClient = globalThis.__agentQueryClient;
2132
+ if (!queryClient) return;
2133
+ void queryClient.invalidateQueries({ queryKey: ["skill-stats", sessionId] });
2134
+ }
2135
+ function invalidateSessionSkills(sessionId) {
2136
+ const queryClient = globalThis.__agentQueryClient;
2137
+ if (!queryClient) return;
2138
+ void queryClient.invalidateQueries({ queryKey: ["session-skills", sessionId] });
2139
+ }
2140
+ function invalidateSessionDetail(sessionId) {
2141
+ const queryClient = globalThis.__agentQueryClient;
2142
+ if (!queryClient) return;
2143
+ void queryClient.invalidateQueries({ queryKey: ["session-detail", sessionId] });
2144
+ }
2145
+ function invalidateContextStats(sessionId) {
2146
+ const queryClient = globalThis.__agentQueryClient;
2147
+ if (!queryClient) return;
2148
+ void queryClient.invalidateQueries({ queryKey: ["context-stats", sessionId] });
2149
+ }
2150
+ var onWorkspaceFilesChanged = null;
2151
+ function setOnWorkspaceFilesChanged(fn) {
2152
+ onWorkspaceFilesChanged = fn;
2153
+ }
2154
+ function invalidateSessionFiles(sessionId, filePath) {
2155
+ const queryClient = globalThis.__agentQueryClient;
2156
+ if (!queryClient) return;
2157
+ if (filePath) {
2158
+ void queryClient.invalidateQueries({ queryKey: ["session-file", sessionId, filePath] });
2159
+ void queryClient.invalidateQueries({ queryKey: ["session-image", sessionId, filePath] });
2160
+ return;
2161
+ }
2162
+ void queryClient.invalidateQueries({
2163
+ predicate: (query) => {
2164
+ const [root, sid] = query.queryKey;
2165
+ return (root === "session-file" || root === "session-image") && sid === sessionId;
2166
+ }
2167
+ });
2168
+ }
2169
+ function notifyWorkspaceFilesChanged(sessionId, filePath) {
2170
+ invalidateFileTree(sessionId);
2171
+ invalidateSessionFiles(sessionId, filePath);
2172
+ if (typeof window !== "undefined") {
2173
+ window.dispatchEvent(
2174
+ new CustomEvent("blade:workspace-files-changed", {
2175
+ detail: { sessionId, filePath }
2176
+ })
2177
+ );
2178
+ }
2179
+ onWorkspaceFilesChanged?.({ sessionId, filePath });
2180
+ }
2181
+ function isRecord4(value) {
2182
+ return typeof value === "object" && value !== null;
2183
+ }
2184
+ function extractModeFromBlocks3(blocks) {
2185
+ const modeBlock = blocks.find((block) => block.type === "mode_change");
2186
+ if (modeBlock && isRecord4(modeBlock.content) && typeof modeBlock.content.to === "string") {
2187
+ return modeBlock.content.to;
2188
+ }
2189
+ if (blocks.some((block) => block.type === "planning_enter")) return "planning";
2190
+ if (blocks.some((block) => block.type === "planning_exit")) return "executing";
2191
+ return null;
2192
+ }
2193
+ function hasAuthenticatedSession(client) {
2194
+ if (hasTokenValue(resolveClientToken(client))) return true;
2195
+ const { token, socketAuthToken, user } = useAuthStore.getState();
2196
+ return Boolean(hasTokenValue(token) || hasTokenValue(socketAuthToken) || user);
2197
+ }
2198
+ function resolveClientToken(client) {
2199
+ const token = client.options.token;
2200
+ return typeof token === "function" ? token() : token;
2201
+ }
2202
+ function hasTokenValue(token) {
2203
+ return typeof token === "string" && token.trim() !== "";
2204
+ }
2205
+ function resolveArtifactType(contentType) {
2206
+ const normalized = contentType.trim().toLowerCase();
2207
+ if (normalized === "markdown" || normalized === "md" || normalized === "text/markdown" || normalized === "text/x-markdown") {
2208
+ return "markdown";
2209
+ }
2210
+ if (normalized === "html" || normalized === "text/html") {
2211
+ return "html";
2212
+ }
2213
+ if (normalized === "url" || normalized === "text/uri-list") {
2214
+ return "url";
2215
+ }
2216
+ if (normalized === "pdf" || normalized === "application/pdf") {
2217
+ return "pdf";
2218
+ }
2219
+ if (normalized === "docx" || normalized === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
2220
+ return "docx";
2221
+ }
2222
+ if (normalized === "excel" || normalized === "xlsx" || normalized === "xls" || normalized === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || normalized === "application/vnd.ms-excel") {
2223
+ return "excel";
2224
+ }
2225
+ if (normalized === "pptx" || normalized === "ppt" || normalized === "application/vnd.openxmlformats-officedocument.presentationml.presentation" || normalized === "application/vnd.ms-powerpoint" || normalized === "application/vnd.ms-powerpoint.presentation.macroenabled.12") {
2226
+ return "ppt";
2227
+ }
2228
+ if (normalized === "csv" || normalized === "text/csv") {
2229
+ return "csv";
2230
+ }
2231
+ if (normalized === "image" || normalized.startsWith("image/")) {
2232
+ return "image";
2233
+ }
2234
+ return "file";
2235
+ }
2236
+ function buildToolPreviewSignature(content, type2) {
2237
+ return `${type2}:${content}`;
2238
+ }
2239
+ function isToolBridgeContent(value) {
2240
+ return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.action === "string";
2241
+ }
2242
+ function isGisGoalListPayload(value) {
2243
+ return isRecord4(value) && Array.isArray(value.goals);
2244
+ }
2245
+ function isGisResourceListPayload(value) {
2246
+ return isRecord4(value) && Array.isArray(value.resources);
2247
+ }
2248
+ function isGisTargetListPayload(value) {
2249
+ return isRecord4(value) && Array.isArray(value.targets);
2250
+ }
2251
+ var AgentSocket = class {
2252
+ constructor(client) {
2253
+ this.client = client;
2254
+ this.socket = client.socket();
2255
+ this._bindStoreHandlers();
2256
+ this._bindConnectionHandlers();
2257
+ setOnSessionChange((sessionId) => {
2258
+ if (sessionId) {
2259
+ this.subscribe(sessionId);
2260
+ return;
2261
+ }
2262
+ this.unsubscribe();
2263
+ });
2264
+ if (hasAuthenticatedSession(this.client)) {
2265
+ useConnectionStore.getState().markConnecting();
2266
+ this.socket.connect();
2267
+ } else {
2268
+ useConnectionStore.getState().markDisconnected();
2269
+ }
2270
+ }
2271
+ client;
2272
+ // The protocol socket is strongly typed in client/socket.ts; this bridge accepts
2273
+ // legacy event payloads while stores finish moving to generated payload shapes.
2274
+ // biome-ignore lint/suspicious/noExplicitAny: event bridge normalizes mixed legacy payloads.
2275
+ socket;
2276
+ subscribedSession = null;
2277
+ previewArtifactsByToolCall = /* @__PURE__ */ new Map();
2278
+ pendingReplayMessages = /* @__PURE__ */ new Map();
2279
+ pendingReplayModes = /* @__PURE__ */ new Map();
2280
+ _ensureConnected() {
2281
+ if (this.socket.connected || this.socket.active) {
2282
+ return;
2283
+ }
2284
+ if (!hasAuthenticatedSession(this.client)) {
2285
+ return;
2286
+ }
2287
+ useConnectionStore.getState().markConnecting();
2288
+ this.socket.connect();
2289
+ }
2290
+ _resolveSessionId(data) {
2291
+ return data?.session_id ?? this.subscribedSession;
2292
+ }
2293
+ _bindStoreHandlers() {
2294
+ const s = this.socket;
2295
+ const applyTurn = (sessionId, turn) => {
2296
+ useChatStore.getState().upsertTurn(sessionId, turn);
2297
+ invalidateContextStats(sessionId);
2298
+ const mode = extractModeFromBlocks3(turn.blocks);
2299
+ if (mode === "planning" || mode === "executing") {
2300
+ useSessionStore.getState().setMode(sessionId, mode);
2301
+ }
2302
+ this._applyProjectionSideEffects(sessionId, turn);
2303
+ };
2304
+ s.on("turn:start", (data) => {
2305
+ const sessionId = this._resolveSessionId(data);
2306
+ if (!sessionId) return;
2307
+ applyTurn(sessionId, data);
2308
+ });
2309
+ s.on("turn:patch", (data) => {
2310
+ const sessionId = this._resolveSessionId(data);
2311
+ if (!sessionId) return;
2312
+ useChatStore.getState().applyTurnPatch(sessionId, data);
2313
+ if (data.data.workspace_changed) {
2314
+ notifyWorkspaceFilesChanged(sessionId);
2315
+ }
2316
+ const turn = data.data.turn;
2317
+ if (!turn) return;
2318
+ const mode = extractModeFromBlocks3(turn.blocks);
2319
+ if (mode === "planning" || mode === "executing") {
2320
+ useSessionStore.getState().setMode(sessionId, mode);
2321
+ }
2322
+ this._applyProjectionSideEffects(sessionId, turn);
2323
+ });
2324
+ s.on("turn:end", (data) => {
2325
+ const sessionId = this._resolveSessionId(data);
2326
+ if (!sessionId) return;
2327
+ applyTurn(sessionId, data);
2328
+ });
2329
+ s.on("chat:start", (data) => {
2330
+ const sessionId = this._resolveSessionId(data);
2331
+ if (!sessionId) return;
2332
+ useSessionStore.getState().updateSessionStatus(sessionId, "running");
2333
+ useChatStore.getState().setStreaming(sessionId, true);
2334
+ });
2335
+ s.on("chat:end", (data) => {
2336
+ const sessionId = this._resolveSessionId(data);
2337
+ if (sessionId) {
2338
+ const status = data?.status ?? "completed";
2339
+ useSessionStore.getState().updateSessionStatus(
2340
+ sessionId,
2341
+ status === "paused" ? "waiting_for_input" : status === "interrupted" ? "interrupted" : status === "failed" ? "failed" : "completed"
2342
+ );
2343
+ if (status === "interrupted") {
2344
+ useChatStore.getState().markInterrupted(sessionId);
2345
+ } else if (status === "failed") {
2346
+ useChatStore.getState().markFailed(sessionId);
2347
+ }
2348
+ useChatStore.getState().setStreaming(sessionId, false);
2349
+ invalidateContextStats(sessionId);
2350
+ useRuntimeStore.getState().addEvent(sessionId, {
2351
+ type: "chat:end",
2352
+ title: status === "paused" ? "Waiting for input" : status === "interrupted" ? "\u8FD0\u884C\u5DF2\u4E2D\u65AD" : status === "failed" ? "Run failed" : "Run completed",
2353
+ status: status === "paused" ? "info" : status === "interrupted" || status === "failed" ? "error" : "done",
2354
+ detail: status === "paused" ? "Paused for user input" : status === "interrupted" ? "\u7528\u6237\u5DF2\u4E2D\u65AD" : status === "failed" ? "Streaming failed" : "Streaming finished"
2355
+ });
2356
+ }
2357
+ });
2358
+ s.on("task:updated", (data) => {
2359
+ const sessionId = this._resolveSessionId(data);
2360
+ if (sessionId) {
2361
+ useTaskStore.getState().setTasks(sessionId, data.tasks);
2362
+ }
2363
+ });
2364
+ s.on("system:error", (data) => {
2365
+ const sessionId = this._resolveSessionId(data);
2366
+ if (sessionId) {
2367
+ useSessionStore.getState().updateSessionStatus(sessionId, "failed");
2368
+ useChatStore.getState().markFailed(sessionId);
2369
+ useChatStore.getState().addErrorMessage(sessionId, data.message);
2370
+ useChatStore.getState().setStreaming(sessionId, false);
2371
+ useRuntimeStore.getState().addEvent(sessionId, {
2372
+ type: "system:error",
2373
+ title: "Runtime error",
2374
+ status: "error",
2375
+ detail: data.message
2376
+ });
2377
+ }
2378
+ });
2379
+ s.on(
2380
+ "replay:input_mismatch",
2381
+ (data) => {
2382
+ const sessionId = this._resolveSessionId(data);
2383
+ if (!sessionId) return;
2384
+ const keepReplay = !window.confirm("\u5373\u5C06\u8F6C\u5165\u81EA\u4E3B\u6A21\u5F0F\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F");
2385
+ const message = this.pendingReplayMessages.get(sessionId) ?? data.actual_message ?? "";
2386
+ const mode = this.pendingReplayModes.get(sessionId);
2387
+ this.pendingReplayMessages.delete(sessionId);
2388
+ this.pendingReplayModes.delete(sessionId);
2389
+ this.socket.emit("chat:send", {
2390
+ session_id: sessionId,
2391
+ message,
2392
+ mode,
2393
+ replay_decision: keepReplay ? "keep_replay" : "continue_replay"
2394
+ });
2395
+ }
2396
+ );
2397
+ s.on(
2398
+ "system:notification",
2399
+ (data) => {
2400
+ const sessionId = this._resolveSessionId(data);
2401
+ if (!sessionId) return;
2402
+ this._applySystemNotification(sessionId, "root", data);
2403
+ }
2404
+ );
2405
+ s.on("task:synced", (data) => {
2406
+ const sessionId = this._resolveSessionId(data);
2407
+ if (sessionId) {
2408
+ useTaskStore.getState().setTasks(sessionId, data.tasks);
2409
+ }
2410
+ });
2411
+ s.on("workspace:files_changed", (data) => {
2412
+ const sessionId = this._resolveSessionId(data);
2413
+ if (sessionId) {
2414
+ notifyWorkspaceFilesChanged(sessionId, data.file_path);
2415
+ }
2416
+ });
2417
+ s.on(
2418
+ "session:updated",
2419
+ (data) => {
2420
+ const sessionId = this._resolveSessionId(data);
2421
+ if (!sessionId) return;
2422
+ const patch = {};
2423
+ if (typeof data.intent === "string") patch.intent = data.intent;
2424
+ if ("model" in data) patch.model = data.model;
2425
+ if ("bound_skill_id" in data) patch.bound_skill_id = data.bound_skill_id;
2426
+ if ("replay_state" in data) patch.replay_state = data.replay_state;
2427
+ if (Object.keys(patch).length > 0) {
2428
+ useSessionStore.getState().patchSession(sessionId, patch);
2429
+ }
2430
+ if (typeof data.intent === "string") {
2431
+ invalidateHomeSidebarSessions();
2432
+ }
2433
+ invalidateSessionDetail(sessionId);
2434
+ }
2435
+ );
2436
+ s.on("session:rewind", (data) => {
2437
+ if (!this.subscribedSession) return;
2438
+ const sessionId = this.subscribedSession;
2439
+ const { modes, ...rest } = useSessionStore.getState();
2440
+ const { [sessionId]: _, ...restModes } = modes;
2441
+ useSessionStore.setState({ ...rest, modes: restModes });
2442
+ useSessionStore.getState().setActiveSession(sessionId);
2443
+ useRuntimeStore.getState().addEvent(sessionId, {
2444
+ type: "session:rewind",
2445
+ title: "Conversation rewound",
2446
+ status: "info",
2447
+ detail: data.checkpoint_id
2448
+ });
2449
+ invalidateSessionCheckpoints(sessionId);
2450
+ });
2451
+ const handleArtifact = (sessionId, data) => {
2452
+ const type2 = resolveArtifactType(data.content_type);
2453
+ const filePath = `/api/sessions/${sessionId}/files/${encodeURIComponent(data.file_path)}`;
2454
+ const fileUrl = this.client.buildAuthedUrl(filePath);
2455
+ const content = type2 === "ppt" ? filePath : type2 === "image" || type2 === "pdf" || type2 === "docx" || type2 === "excel" ? fileUrl : data.content;
2456
+ if (data.session_id != null && data.session_id === this.subscribedSession) {
2457
+ useUiStore.getState().setActiveRightTab("preview");
2458
+ }
2459
+ useUiStore.getState().pushArtifact({
2460
+ type: type2,
2461
+ content,
2462
+ sourceUrl: type2 === "ppt" ? fileUrl : void 0,
2463
+ title: data.title,
2464
+ key: data.file_path,
2465
+ revision: Date.now()
2466
+ });
2467
+ };
2468
+ s.on("artifact:created", (data) => {
2469
+ const sessionId = this._resolveSessionId(data);
2470
+ if (!sessionId) return;
2471
+ notifyWorkspaceFilesChanged(sessionId, data.file_path);
2472
+ handleArtifact(sessionId, data);
2473
+ });
2474
+ s.on("bg:started", (data) => {
2475
+ const sessionId = this._resolveSessionId(data);
2476
+ if (!sessionId) return;
2477
+ const taskId = typeof data.task_id === "string" ? data.task_id : typeof data.id === "string" ? data.id : "";
2478
+ if (!taskId) return;
2479
+ useBackgroundStore.getState().upsertTask(sessionId, {
2480
+ id: taskId,
2481
+ command: typeof data.command === "string" ? data.command : "",
2482
+ description: typeof data.description === "string" ? data.description : void 0,
2483
+ status: "running",
2484
+ elapsed_seconds: typeof data.elapsed_seconds === "number" ? data.elapsed_seconds : 0
2485
+ });
2486
+ });
2487
+ s.on("bg:tasks_completed", (data) => {
2488
+ const sessionId = this._resolveSessionId(data);
2489
+ if (!sessionId) return;
2490
+ for (const task of data.tasks ?? []) {
2491
+ const taskId = typeof task.id === "string" ? task.id : typeof task.task_id === "string" ? task.task_id : "";
2492
+ if (!taskId) continue;
2493
+ useBackgroundStore.getState().upsertTask(sessionId, {
2494
+ id: taskId,
2495
+ command: typeof task.command === "string" ? task.command : "",
2496
+ description: typeof task.description === "string" ? task.description : void 0,
2497
+ status: task.status === "running" || task.status === "failed" ? task.status : "done",
2498
+ exit_code: typeof task.exit_code === "number" || task.exit_code === null ? task.exit_code : void 0,
2499
+ elapsed_seconds: typeof task.elapsed_seconds === "number" ? task.elapsed_seconds : 0,
2500
+ output_lines_count: typeof task.output_lines_count === "number" ? task.output_lines_count : void 0,
2501
+ output: Array.isArray(task.output_preview) && task.output_preview.length > 0 ? task.output_preview.join("\n") : void 0
2502
+ });
2503
+ }
2504
+ });
2505
+ s.on("skills:changed", (data) => {
2506
+ const sessionId = this._resolveSessionId(data);
2507
+ if (!sessionId) return;
2508
+ invalidateSkillStats2(sessionId);
2509
+ invalidateSessionSkills(sessionId);
2510
+ });
2511
+ s.on("artifact:updated", (data) => {
2512
+ const sessionId = this._resolveSessionId(data);
2513
+ if (!sessionId) return;
2514
+ notifyWorkspaceFilesChanged(sessionId, data.file_path);
2515
+ if (data.old_string != null && data.new_string != null) {
2516
+ useUiStore.getState().pushArtifact({
2517
+ type: "diff",
2518
+ content: data.new_string,
2519
+ oldContent: data.old_string,
2520
+ title: data.title,
2521
+ key: data.file_path
2522
+ });
2523
+ return;
2524
+ }
2525
+ handleArtifact(sessionId, data);
2526
+ });
2527
+ }
2528
+ _bindConnectionHandlers() {
2529
+ this.socket.on("connect", () => {
2530
+ useConnectionStore.getState().markConnected();
2531
+ console.log("[agent-socket] connected, id:", this.socket.id);
2532
+ const sessionId = this.subscribedSession;
2533
+ if (sessionId) {
2534
+ useSessionStore.getState().setActiveSession(sessionId);
2535
+ this.socket.emit("session:subscribe", { session_id: sessionId });
2536
+ }
2537
+ });
2538
+ this.socket.on("connect_error", (err) => {
2539
+ useConnectionStore.getState().markDisconnected();
2540
+ console.error("[agent-socket] connect_error:", err.message);
2541
+ });
2542
+ this.socket.on("disconnect", (reason) => {
2543
+ useConnectionStore.getState().markDisconnected();
2544
+ console.log("[agent-socket] disconnected:", reason);
2545
+ });
2546
+ this.socket.io.on("reconnect_attempt", (attempt) => {
2547
+ useConnectionStore.getState().markConnecting(typeof attempt === "number" ? attempt : 0);
2548
+ });
2549
+ }
2550
+ _applyProjectionSideEffects(sessionId, turn) {
2551
+ for (const block of turn.blocks) {
2552
+ if (block.type === "tool_bridge" && isToolBridgeContent(block.content)) {
2553
+ if (block.content.action === "gis.goals.updated" && isGisGoalListPayload(block.content.payload)) {
2554
+ useGisStore.getState().setGoals(sessionId, block.content.payload.goals);
2555
+ continue;
2556
+ }
2557
+ if (block.content.action === "gis.resources.updated" && isGisResourceListPayload(block.content.payload)) {
2558
+ useGisStore.getState().setResources(sessionId, block.content.payload.resources);
2559
+ continue;
2560
+ }
2561
+ if (block.content.action === "gis.targets.updated" && isGisTargetListPayload(block.content.payload)) {
2562
+ useGisStore.getState().setTargets(sessionId, block.content.payload.targets);
2563
+ continue;
2564
+ }
2565
+ if (block.content.action.startsWith("map.")) {
2566
+ useGisStore.getState().pushMapCommand(sessionId, {
2567
+ action: block.content.action,
2568
+ payload: block.content.payload,
2569
+ toolCallId: block.tool_call_id
2570
+ });
2571
+ continue;
2572
+ }
2573
+ postToParent({
2574
+ __bladeBridge: true,
2575
+ direction: "agent-to-host",
2576
+ action: block.content.action,
2577
+ payload: block.content.payload,
2578
+ meta: {
2579
+ sessionId,
2580
+ ...block.tool_call_id ? { toolCallId: block.tool_call_id } : {},
2581
+ timestamp: Date.now()
2582
+ }
2583
+ });
2584
+ continue;
2585
+ }
2586
+ if (block.type !== "tool_ui" || !block.tool_call_id || !isUiMeta(block.content)) {
2587
+ continue;
2588
+ }
2589
+ const ui = block.content;
2590
+ if (ui.target !== "preview") {
2591
+ continue;
2592
+ }
2593
+ const previewType = ui.resourceHTML ? "resource-html" : "resource-uri";
2594
+ const previewContent = ui.resourceHTML ?? ui.resourceUri ?? ui.resourceURI;
2595
+ if (!previewContent) {
2596
+ continue;
2597
+ }
2598
+ const previewKey = buildToolPreviewKey(previewContent, previewType, block.tool_call_id);
2599
+ const signature = buildToolPreviewSignature(previewContent, previewType);
2600
+ if (this.previewArtifactsByToolCall.get(previewKey) === signature) {
2601
+ continue;
2602
+ }
2603
+ this.previewArtifactsByToolCall.set(previewKey, signature);
2604
+ useUiStore.getState().pushArtifact({
2605
+ type: previewType,
2606
+ content: previewContent,
2607
+ title: ui.title ?? "\u5DE5\u5177\u9884\u89C8",
2608
+ key: previewKey,
2609
+ bridgeSessionId: sessionId
2610
+ });
2611
+ }
2612
+ for (const block of turn.blocks) {
2613
+ if (block.type !== "system_notification" || !isRecord4(block.content)) {
2614
+ continue;
2615
+ }
2616
+ this._applySystemNotification(sessionId, turn.loop_id, block.content);
2617
+ }
2618
+ }
2619
+ _applySystemNotification(sessionId, loopId, notification) {
2620
+ const notificationType = notification.notification_type ?? "system_notification";
2621
+ const title = notification.title ?? notificationType;
2622
+ const detail = typeof notification.detail === "string" ? notification.detail : notification.detail == null ? void 0 : JSON.stringify(notification.detail);
2623
+ const status = notification.status === "running" || notification.status === "done" || notification.status === "error" ? notification.status : "info";
2624
+ useRuntimeStore.getState().addEvent(sessionId, {
2625
+ type: notificationType,
2626
+ title,
2627
+ status,
2628
+ detail,
2629
+ loopName: loopId
2630
+ });
2631
+ if (notificationType !== "bg:started" || !isRecord4(notification.metadata)) {
2632
+ return;
2633
+ }
2634
+ const taskId = typeof notification.metadata.task_id === "string" ? notification.metadata.task_id : "";
2635
+ const command = typeof notification.metadata.command === "string" ? notification.metadata.command : "";
2636
+ const description = typeof notification.metadata.description === "string" ? notification.metadata.description : void 0;
2637
+ if (!taskId) return;
2638
+ useBackgroundStore.getState().upsertTask(sessionId, {
2639
+ id: taskId,
2640
+ command,
2641
+ description,
2642
+ status: "running",
2643
+ elapsed_seconds: 0
2644
+ });
2645
+ }
2646
+ subscribe(sessionId) {
2647
+ const previousSessionId = this.subscribedSession;
2648
+ const isSameSession = previousSessionId === sessionId;
2649
+ if (previousSessionId && !isSameSession) {
2650
+ this.socket.emit("session:unsubscribe", { session_id: previousSessionId });
2651
+ useUiBridgeStore.getState().clearSession(previousSessionId);
2652
+ }
2653
+ this.subscribedSession = sessionId;
2654
+ if (!isSameSession) {
2655
+ this.previewArtifactsByToolCall.clear();
2656
+ useUiStore.getState().clearArtifacts();
2657
+ useCardStateStore.getState().clearAllStates();
2658
+ }
2659
+ this.socket.emit("session:subscribe", { session_id: sessionId });
2660
+ }
2661
+ unsubscribe() {
2662
+ const previousSessionId = this.subscribedSession;
2663
+ if (previousSessionId) {
2664
+ this.socket.emit("session:unsubscribe", { session_id: previousSessionId });
2665
+ useUiBridgeStore.getState().clearSession(previousSessionId);
2666
+ this.subscribedSession = null;
2667
+ }
2668
+ this.previewArtifactsByToolCall.clear();
2669
+ }
2670
+ /**
2671
+ * 附加订阅:仅把当前 sid 加进 sessionId 的 room,不切 subscribedSession,
2672
+ * 也不清空当前会话的 artifact/card state。用于"并行拟制"这类场景 ——
2673
+ * 创建 N 个后台 session 后需要收到它们的 chat:end / task:updated 才能让
2674
+ * 成果表里那几条卡片状态流转到"已完成";缺失订阅时广播事件永远到不了
2675
+ * 前端,卡片就会停留在乐观写入的"运行中"态。
2676
+ *
2677
+ * 幂等:后端 session:subscribe handler 只做 enter_room,重复 emit 无副作用。
2678
+ */
2679
+ attachSession(sessionId) {
2680
+ this.socket.emit("session:subscribe", { session_id: sessionId });
2681
+ }
2682
+ send(sessionId, message, mode, askuserAnswer, extras) {
2683
+ this._ensureConnected();
2684
+ const suppressOptimisticUserMessage = shouldSuppressOptimisticUserMessage(
2685
+ sessionId,
2686
+ askuserAnswer
2687
+ );
2688
+ if (!suppressOptimisticUserMessage) {
2689
+ useChatStore.getState().addUserMessage(sessionId, message);
2690
+ }
2691
+ useChatStore.getState().setStreaming(sessionId, true);
2692
+ if (askuserAnswer && typeof askuserAnswer.tool_call_id === "string") {
2693
+ const { tool_call_id, ...rest } = askuserAnswer;
2694
+ const current = useChatStore.getState().askAnswers[sessionId] ?? {};
2695
+ useChatStore.getState().setAskAnswers(sessionId, {
2696
+ ...current,
2697
+ [tool_call_id]: rest
2698
+ });
2699
+ }
2700
+ if (!suppressOptimisticUserMessage) {
2701
+ useRuntimeStore.getState().addEvent(sessionId, {
2702
+ type: "chat:start",
2703
+ title: "User prompt sent",
2704
+ status: "running",
2705
+ detail: contentPreview(message)
2706
+ });
2707
+ }
2708
+ if (!extras?.replay_decision) {
2709
+ this.pendingReplayMessages.set(sessionId, message);
2710
+ this.pendingReplayModes.set(sessionId, mode);
2711
+ }
2712
+ this.socket.emit("chat:send", {
2713
+ session_id: sessionId,
2714
+ message,
2715
+ mode,
2716
+ askuser_answer: askuserAnswer,
2717
+ model: extras?.model,
2718
+ whatif: extras?.whatif,
2719
+ replay_decision: extras?.replay_decision
2720
+ });
2721
+ const skillId = extractSkillIdForAnalytics(message);
2722
+ trackEvent("message_sent", {
2723
+ session_id: sessionId,
2724
+ ...skillId ? { skill_id: skillId } : {}
2725
+ });
2726
+ if (skillId) {
2727
+ trackEvent("skill_invoked", {
2728
+ session_id: sessionId,
2729
+ skill_id: skillId
2730
+ });
2731
+ }
2732
+ }
2733
+ stop(sessionId) {
2734
+ this.socket.emit("chat:stop", { session_id: sessionId });
2735
+ }
2736
+ compact(sessionId) {
2737
+ return new Promise((resolve, reject) => {
2738
+ this.socket.timeout(3e4).emit(
2739
+ "chat:compact",
2740
+ { session_id: sessionId },
2741
+ (err, response) => {
2742
+ if (err) {
2743
+ reject(err);
2744
+ return;
2745
+ }
2746
+ resolve(response ?? {});
2747
+ }
2748
+ );
2749
+ });
2750
+ }
2751
+ reconnect() {
2752
+ this.socket.disconnect();
2753
+ useConnectionStore.getState().markConnecting();
2754
+ this.socket.connect();
2755
+ }
2756
+ disconnect() {
2757
+ this.socket.disconnect();
2758
+ useConnectionStore.getState().markDisconnected();
2759
+ }
2760
+ destroy() {
2761
+ this.socket.removeAllListeners();
2762
+ this.socket.disconnect();
2763
+ useConnectionStore.getState().markDisconnected();
2764
+ }
2765
+ // biome-ignore lint/suspicious/noExplicitAny: socket.io listeners are event-specific and forwarded transparently.
2766
+ on(event, listener) {
2767
+ this.socket.on(event, listener);
2768
+ return this;
2769
+ }
2770
+ // biome-ignore lint/suspicious/noExplicitAny: socket.io listeners are event-specific and forwarded transparently.
2771
+ off(event, listener) {
2772
+ this.socket.off(event, listener);
2773
+ return this;
2774
+ }
2775
+ // biome-ignore lint/suspicious/noExplicitAny: generic emit for custom events.
2776
+ emit(event, data) {
2777
+ this.socket.emit(event, data);
2778
+ return this;
2779
+ }
2780
+ getSubscribedSession() {
2781
+ return this.subscribedSession;
2782
+ }
2783
+ };
2784
+ function bridgeSocketEvents(client, _stores) {
2785
+ agentSocket?.destroy();
2786
+ const nextSocket = new AgentSocket(client);
2787
+ setAgentSocket(nextSocket);
2788
+ return nextSocket;
2789
+ }
2790
+
2791
+ // src/react/stores/answer-callback-store.ts
2792
+ import { create as create11 } from "zustand";
2793
+ var useAnswerCallbackStore = create11()((set) => ({
2794
+ ...createClientActions(set),
2795
+ callbacks: {},
2796
+ setAnswerCallback: (sessionId, callback) => {
2797
+ set((state) => ({
2798
+ callbacks: {
2799
+ ...state.callbacks,
2800
+ [sessionId]: callback
2801
+ }
2802
+ }));
2803
+ }
2804
+ }));
2805
+
2806
+ // src/react/stores/runtime-features-store.ts
2807
+ import { create as create12 } from "zustand";
2808
+ var useRuntimeFeaturesStore = create12((set) => ({
2809
+ ...createClientActions(set),
2810
+ asrEnabled: false,
2811
+ asrProvider: "volcengine",
2812
+ publicSharingEnabled: false,
2813
+ memoryEnabled: false,
2814
+ setFeatures: (features) => set((prev) => ({
2815
+ asrEnabled: features.asrEnabled ?? prev.asrEnabled,
2816
+ asrProvider: features.asrProvider ?? prev.asrProvider,
2817
+ publicSharingEnabled: features.publicSharingEnabled ?? prev.publicSharingEnabled,
2818
+ memoryEnabled: features.memoryEnabled ?? prev.memoryEnabled
2819
+ }))
2820
+ }));
2821
+
2822
+ // src/react/bootstrap.ts
2823
+ var bootstrappedClient = null;
2824
+ var detachHostBridgeListener = null;
2825
+ var stores = {
2826
+ answerCallback: useAnswerCallbackStore,
2827
+ auth: useAuthStore,
2828
+ background: useBackgroundStore,
2829
+ cardState: useCardStateStore,
2830
+ chat: useChatStore,
2831
+ connection: useConnectionStore,
2832
+ gis: useGisStore,
2833
+ runtime: useRuntimeStore,
2834
+ runtimeFeatures: useRuntimeFeaturesStore,
2835
+ session: useSessionStore,
2836
+ task: useTaskStore,
2837
+ ui: useUiStore,
2838
+ uiBridge: useUiBridgeStore
2839
+ };
2840
+ function attachClientToStores(client) {
2841
+ for (const store of Object.values(stores)) {
2842
+ const state = store.getState();
2843
+ state.setClient(client);
2844
+ }
2845
+ bridgeSocketEvents(client, stores);
2846
+ detachHostBridgeListener?.();
2847
+ detachHostBridgeListener = attachHostBridgeListener(() => {
2848
+ const activeSessionId = useSessionStore.getState().activeSessionId;
2849
+ return {
2850
+ sessionId: activeSessionId,
2851
+ activeSessionId,
2852
+ isStreaming: activeSessionId ? useChatStore.getState().isStreaming[activeSessionId] ?? false : false
2853
+ };
2854
+ });
2855
+ }
2856
+ function bootstrapBladeClient(options) {
2857
+ const client = new BladeClient(withStoreAuth(options));
2858
+ if (options.token === void 0) {
2859
+ client._attachStoreRestTokenResolver(() => useAuthStore.getState().token);
2860
+ client._attachStoreSocketTokenResolver(() => {
2861
+ const auth = useAuthStore.getState();
2862
+ return auth.token ?? auth.socketAuthToken;
2863
+ });
2864
+ }
2865
+ attachClientToStores(client);
2866
+ bootstrappedClient = client;
2867
+ return client;
2868
+ }
2869
+ function getBootstrappedClient() {
2870
+ if (!bootstrappedClient) {
2871
+ throw new Error("bootstrapBladeClient() must be called before any SDK usage");
2872
+ }
2873
+ return bootstrappedClient;
2874
+ }
2875
+ function withStoreAuth(options) {
2876
+ if (options.token !== void 0) {
2877
+ return options;
2878
+ }
2879
+ return {
2880
+ ...options,
2881
+ onRefreshSuccess: async () => {
2882
+ await options.onRefreshSuccess?.();
2883
+ useAuthStore.setState({ token: null });
2884
+ }
2885
+ };
2886
+ }
2887
+
2888
+ // src/react/api/client.ts
2889
+ function getBaseUrl() {
2890
+ return getClient().options.baseUrl;
2891
+ }
2892
+ function getAuthedUrl(path) {
2893
+ return getClient().buildAuthedUrl(path);
2894
+ }
2895
+ async function apiFetch(path, init) {
2896
+ return getClient().jsonFromInit(path, init);
2897
+ }
2898
+ async function apiFetchText(path, init) {
2899
+ return getClient().textFromInit(path, init);
2900
+ }
2901
+ async function apiFetchResponse(path, init) {
2902
+ return getClient().responseFromInit(path, init);
2903
+ }
2904
+ function getClient() {
2905
+ return getBootstrappedClient();
2906
+ }
2907
+
2908
+ export {
2909
+ getBaseUrl,
2910
+ getAuthedUrl,
2911
+ apiFetch,
2912
+ apiFetchText,
2913
+ apiFetchResponse,
2914
+ getClient,
2915
+ listSkills,
2916
+ listSessionSkills,
2917
+ searchSkills,
2918
+ getSkillStats,
2919
+ getGlobalSkillStats,
2920
+ skills_exports,
2921
+ partner_skill_exports,
2922
+ PartnerSkillName,
2923
+ PartnerSkillFile,
2924
+ PartnerSkillInstallPayload,
2925
+ PartnerSkillInstallResult,
2926
+ useUiBridgeStore,
2927
+ resourceBridgeDispatch,
2928
+ isInsideIframe,
2929
+ attachHostBridgeListener,
2930
+ getTextContent,
2931
+ isHiddenInternalMessage,
2932
+ buildMessageContent,
2933
+ contentPreview,
2934
+ transformSlashCommand,
2935
+ extractTextAttachments,
2936
+ getImageParts,
2937
+ getFileParts,
2938
+ groupMessagesByLoop,
2939
+ setAnalyticsClient,
2940
+ trackEvent,
2941
+ buildToolPreviewKey,
2942
+ isUiMeta,
2943
+ auth_exports,
2944
+ agentSocket,
2945
+ getSocket,
2946
+ listSessions,
2947
+ getSessionTurns,
2948
+ getSessionContextStats,
2949
+ getSessionCheckpoints,
2950
+ checkoutSession,
2951
+ listBackgroundTasks,
2952
+ listDir,
2953
+ uploadFiles,
2954
+ deleteFile,
2955
+ writeFile,
2956
+ renameFile,
2957
+ copyFile,
2958
+ getDownloadDirUrl,
2959
+ sessions_exports,
2960
+ formatToolName,
2961
+ getToolDisplayLabel,
2962
+ getToolTone,
2963
+ getToolStatusLabel,
2964
+ extractToolFilePath,
2965
+ getToolOutputTag,
2966
+ resolveEffectiveTheme,
2967
+ useUiStore,
2968
+ useChatStore,
2969
+ useTaskStore,
2970
+ invalidateHomeSidebarSessions,
2971
+ useSessionStore,
2972
+ useAuthStore,
2973
+ useBackgroundStore,
2974
+ useConnectionStore,
2975
+ useRuntimeStore,
2976
+ useGisStore,
2977
+ setOnWorkspaceFilesChanged,
2978
+ useAnswerCallbackStore,
2979
+ useRuntimeFeaturesStore,
2980
+ attachClientToStores,
2981
+ bootstrapBladeClient,
2982
+ getBootstrappedClient
2983
+ };
2984
+ //# sourceMappingURL=chunk-4VWLTG5L.js.map