@blade-hq/agent-kit 0.0.0-placeholder.0 → 0.4.4

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 (39) hide show
  1. package/README.md +66 -3
  2. package/dist/AskUserQuestionBlock-CjvG_pUY.d.ts +116 -0
  3. package/dist/SkillStatusBar-DItrW2vv.d.ts +203 -0
  4. package/dist/blade-client-nOsdVlb1.d.ts +1498 -0
  5. package/dist/client/index.d.ts +8036 -0
  6. package/dist/client/index.js +1057 -0
  7. package/dist/client/index.js.map +1 -0
  8. package/dist/licenses-Cxl1xGVy.d.ts +16 -0
  9. package/dist/projection-DIfyh6RK.d.ts +85 -0
  10. package/dist/react/api/licenses.d.ts +7 -0
  11. package/dist/react/api/licenses.js +1477 -0
  12. package/dist/react/api/licenses.js.map +1 -0
  13. package/dist/react/api/vibe-coding.d.ts +55 -0
  14. package/dist/react/api/vibe-coding.js +1503 -0
  15. package/dist/react/api/vibe-coding.js.map +1 -0
  16. package/dist/react/cards/register.d.ts +2 -0
  17. package/dist/react/cards/register.js +4367 -0
  18. package/dist/react/cards/register.js.map +1 -0
  19. package/dist/react/components/chat/index.d.ts +128 -0
  20. package/dist/react/components/chat/index.js +11389 -0
  21. package/dist/react/components/chat/index.js.map +1 -0
  22. package/dist/react/components/plan/index.d.ts +111 -0
  23. package/dist/react/components/plan/index.js +3490 -0
  24. package/dist/react/components/plan/index.js.map +1 -0
  25. package/dist/react/components/session/index.d.ts +53 -0
  26. package/dist/react/components/session/index.js +2175 -0
  27. package/dist/react/components/session/index.js.map +1 -0
  28. package/dist/react/components/workspace/index.d.ts +35 -0
  29. package/dist/react/components/workspace/index.js +2886 -0
  30. package/dist/react/components/workspace/index.js.map +1 -0
  31. package/dist/react/devtools/bridge-devtools/index.d.ts +36 -0
  32. package/dist/react/devtools/bridge-devtools/index.js +692 -0
  33. package/dist/react/devtools/bridge-devtools/index.js.map +1 -0
  34. package/dist/react/index.d.ts +1283 -0
  35. package/dist/react/index.js +14299 -0
  36. package/dist/react/index.js.map +1 -0
  37. package/dist/session-CDeiO81j.d.ts +128 -0
  38. package/package.json +73 -7
  39. package/index.js +0 -1
@@ -0,0 +1,2886 @@
1
+ // src/react/components/workspace/WorkspaceFilesPanel.tsx
2
+ import { useQueryClient as useQueryClient2 } from "@tanstack/react-query";
3
+ import { FileText, FolderTree, FolderUp, Loader2 as Loader22, PanelRightOpen, RefreshCw, Upload } from "lucide-react";
4
+ import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
5
+
6
+ // src/client/resources/models.ts
7
+ import { type } from "arktype";
8
+ var ModelOption = type({
9
+ id: "string",
10
+ label: "string"
11
+ });
12
+ var ModelsConfig = type({
13
+ default: "string",
14
+ models: ModelOption.array()
15
+ });
16
+
17
+ // src/client/socket.ts
18
+ import { io } from "socket.io-client";
19
+
20
+ // src/react/schemas/partner-skill.ts
21
+ import { type as type2 } from "arktype";
22
+ var PartnerSkillName = type2("/^[a-z0-9-]+\\/[a-z0-9-]+$/");
23
+ var PartnerSkillFile = type2({
24
+ path: "string > 0",
25
+ content: "string"
26
+ });
27
+ var PartnerSkillInstallPayload = type2({
28
+ name: PartnerSkillName,
29
+ files: PartnerSkillFile.array().atLeastLength(1)
30
+ });
31
+ var PartnerSkillInstallResult = type2({
32
+ name: PartnerSkillName,
33
+ skill_dir: "string",
34
+ file_count: "number.integer >= 0",
35
+ overwritten: "boolean"
36
+ });
37
+
38
+ // src/react/stores/ui-bridge-store.ts
39
+ import { create } from "zustand";
40
+
41
+ // src/react/stores/client-aware.ts
42
+ function createClientActions(set) {
43
+ return {
44
+ _client: null,
45
+ setClient: (client) => set({ _client: client })
46
+ };
47
+ }
48
+
49
+ // src/react/stores/ui-bridge-store.ts
50
+ function buildSignalId() {
51
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
52
+ return crypto.randomUUID();
53
+ }
54
+ return `bridge-${Date.now()}-${Math.random().toString(36).slice(2)}`;
55
+ }
56
+ function clearSignalRecord(record, sessionId) {
57
+ if (!(sessionId in record)) {
58
+ return record;
59
+ }
60
+ const next = { ...record };
61
+ delete next[sessionId];
62
+ return next;
63
+ }
64
+ var useUiBridgeStore = create()((set, get) => ({
65
+ ...createClientActions(set),
66
+ pendingContexts: {},
67
+ draftAppends: {},
68
+ sendRequests: {},
69
+ addPendingContext: (sessionId, context) => set((state) => ({
70
+ pendingContexts: {
71
+ ...state.pendingContexts,
72
+ [sessionId]: [
73
+ ...state.pendingContexts[sessionId] ?? [],
74
+ {
75
+ id: buildSignalId(),
76
+ label: context.label,
77
+ content: context.content
78
+ }
79
+ ]
80
+ }
81
+ })),
82
+ removePendingContext: (sessionId, contextId) => set((state) => {
83
+ const contexts = state.pendingContexts[sessionId];
84
+ if (!contexts?.length) {
85
+ return state;
86
+ }
87
+ const nextContexts = contexts.filter((context) => context.id !== contextId);
88
+ if (nextContexts.length === contexts.length) {
89
+ return state;
90
+ }
91
+ return {
92
+ pendingContexts: nextContexts.length > 0 ? {
93
+ ...state.pendingContexts,
94
+ [sessionId]: nextContexts
95
+ } : clearSignalRecord(state.pendingContexts, sessionId)
96
+ };
97
+ }),
98
+ consumePendingContexts: (sessionId) => {
99
+ const signals = get().pendingContexts[sessionId] ?? [];
100
+ if (signals.length === 0) return [];
101
+ set((state) => ({
102
+ pendingContexts: clearSignalRecord(state.pendingContexts, sessionId)
103
+ }));
104
+ return signals;
105
+ },
106
+ clearPendingContexts: (sessionId) => set((state) => ({
107
+ pendingContexts: clearSignalRecord(state.pendingContexts, sessionId)
108
+ })),
109
+ addDraftAppend: (sessionId, text) => set((state) => ({
110
+ draftAppends: {
111
+ ...state.draftAppends,
112
+ [sessionId]: [
113
+ ...state.draftAppends[sessionId] ?? [],
114
+ {
115
+ id: buildSignalId(),
116
+ text
117
+ }
118
+ ]
119
+ }
120
+ })),
121
+ consumeDraftAppends: (sessionId) => {
122
+ const signals = get().draftAppends[sessionId] ?? [];
123
+ if (signals.length === 0) return [];
124
+ set((state) => ({
125
+ draftAppends: clearSignalRecord(state.draftAppends, sessionId)
126
+ }));
127
+ return signals;
128
+ },
129
+ clearDraftAppends: (sessionId) => set((state) => ({
130
+ draftAppends: clearSignalRecord(state.draftAppends, sessionId)
131
+ })),
132
+ addSendRequest: (sessionId) => set((state) => ({
133
+ sendRequests: {
134
+ ...state.sendRequests,
135
+ [sessionId]: [
136
+ ...state.sendRequests[sessionId] ?? [],
137
+ {
138
+ id: buildSignalId()
139
+ }
140
+ ]
141
+ }
142
+ })),
143
+ consumeSendRequests: (sessionId) => {
144
+ const signals = get().sendRequests[sessionId] ?? [];
145
+ if (signals.length === 0) return [];
146
+ set((state) => ({
147
+ sendRequests: clearSignalRecord(state.sendRequests, sessionId)
148
+ }));
149
+ return signals;
150
+ },
151
+ clearSendRequests: (sessionId) => set((state) => ({
152
+ sendRequests: clearSignalRecord(state.sendRequests, sessionId)
153
+ })),
154
+ clearSession: (sessionId) => set((state) => ({
155
+ pendingContexts: clearSignalRecord(state.pendingContexts, sessionId),
156
+ draftAppends: clearSignalRecord(state.draftAppends, sessionId),
157
+ sendRequests: clearSignalRecord(state.sendRequests, sessionId)
158
+ }))
159
+ }));
160
+
161
+ // src/react/lib/chat.ts
162
+ function normalizeMessageContent(content) {
163
+ if (typeof content === "string") return content;
164
+ if (Array.isArray(content)) return content;
165
+ return String(content ?? "");
166
+ }
167
+
168
+ // src/react/lib/ui-meta.ts
169
+ function isNonEmptyString(value) {
170
+ return typeof value === "string" && value.trim().length > 0;
171
+ }
172
+ function isPositiveNumber(value) {
173
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
174
+ }
175
+ function isUiMeta(value) {
176
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
177
+ return false;
178
+ }
179
+ const raw = value;
180
+ if (raw.target !== "inline" && raw.target !== "preview") {
181
+ return false;
182
+ }
183
+ if (!isPositiveNumber(raw.height)) {
184
+ return false;
185
+ }
186
+ if (!isNonEmptyString(raw.resourceHTML) && !isNonEmptyString(raw.resourceUri) && !isNonEmptyString(raw.resourceURI)) {
187
+ return false;
188
+ }
189
+ if (raw.title != null && !isNonEmptyString(raw.title)) {
190
+ return false;
191
+ }
192
+ return true;
193
+ }
194
+
195
+ // src/react/stores/auth-store.ts
196
+ import { create as create6 } from "zustand";
197
+ import { createJSONStorage, persist } from "zustand/middleware";
198
+
199
+ // src/react/api/auth.ts
200
+ var r = () => getClient().auth;
201
+ var getMe = (...args) => r().getMe(...args);
202
+ var logout = (...args) => r().logout(...args);
203
+
204
+ // src/react/sockets/socket-state.ts
205
+ var agentSocket = null;
206
+
207
+ // src/react/stores/session-store.ts
208
+ import { create as create5 } from "zustand";
209
+
210
+ // src/react/stores/chat-store.ts
211
+ import { create as create3 } from "zustand";
212
+
213
+ // src/react/components/chat/display-utils.ts
214
+ var TOOL_NAME_ALIASES = {
215
+ agent: "Agent",
216
+ ask_user_question: "AskUserQuestion",
217
+ bash: "Bash",
218
+ bg_bash: "BgBash",
219
+ edit: "Edit",
220
+ exit_plan_mode: "ExitPlanMode",
221
+ file_edit: "Edit",
222
+ file_read: "Read",
223
+ file_write: "Write",
224
+ finish_task: "FinishTask",
225
+ get_skill_content: "GetSkillContent",
226
+ glob: "Glob",
227
+ grep: "Grep",
228
+ list_skill_tools: "ListSkillTools",
229
+ load_skill_tools: "LoadSkillTools",
230
+ ls: "Ls",
231
+ read: "Read",
232
+ run_skill_tool: "RunSkillTool",
233
+ search_skills: "SearchSkills",
234
+ web_fetch: "WebFetch",
235
+ web_search: "WebSearch",
236
+ write: "Write"
237
+ };
238
+ var TOOL_DISPLAY_LABELS = {
239
+ Bash: "\u6267\u884C\u547D\u4EE4",
240
+ BgBash: "\u540E\u53F0\u6267\u884C\u547D\u4EE4",
241
+ Read: "\u8BFB\u53D6\u6587\u4EF6",
242
+ Write: "\u5199\u5165\u6587\u4EF6",
243
+ Edit: "\u7F16\u8F91\u6587\u4EF6",
244
+ Ls: "\u5217\u51FA\u76EE\u5F55",
245
+ Glob: "\u5339\u914D\u6587\u4EF6",
246
+ Grep: "\u641C\u7D22\u6587\u672C",
247
+ WebSearch: "\u641C\u7D22\u7F51\u9875",
248
+ WebFetch: "\u6574\u7406\u7F51\u9875\u5185\u5BB9",
249
+ Agent: "\u6D3E\u751F\u5B50\u667A\u80FD\u4F53",
250
+ AskUserQuestion: "\u5411\u7528\u6237\u63D0\u95EE",
251
+ SearchSkills: "\u641C\u7D22\u6280\u80FD",
252
+ GetSkillContent: "\u8BFB\u53D6\u6280\u80FD",
253
+ ListSkillTools: "\u67E5\u770B\u5DE5\u5177\u5217\u8868",
254
+ LoadSkillTools: "\u52A0\u8F7D\u6280\u80FD",
255
+ RunSkillTool: "\u6267\u884C\u6280\u80FD\u5DE5\u5177",
256
+ FinishTask: "\u4EFB\u52A1\u5B8C\u6210",
257
+ ExitPlanMode: "\u63D0\u4EA4\u8BA1\u5212",
258
+ ListSessions: "\u5217\u51FA\u5386\u53F2\u4F1A\u8BDD",
259
+ GetSessionHistory: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2"
260
+ };
261
+ var GENERIC_DISPLAY_NAMES = new Set(Object.values(TOOL_DISPLAY_LABELS));
262
+ function formatToolName(name) {
263
+ const trimmed = name.trim();
264
+ if (!trimmed) return name;
265
+ const stripped = trimmed.split(":").pop()?.split("/").pop()?.split(".").pop()?.trim() || trimmed;
266
+ const normalized = stripped.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
267
+ return TOOL_NAME_ALIASES[normalized] ?? stripped;
268
+ }
269
+
270
+ // src/react/stores/ui-store.ts
271
+ import { create as create2 } from "zustand";
272
+ var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
273
+ function resolveEffectiveTheme(theme) {
274
+ if (theme !== "system") return theme;
275
+ return isBrowser && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
276
+ }
277
+ function applyTheme(theme) {
278
+ if (!isBrowser) return;
279
+ const effective = resolveEffectiveTheme(theme);
280
+ document.documentElement.setAttribute("data-theme", effective);
281
+ }
282
+ var storedTheme = isBrowser ? localStorage.getItem("blade-theme") ?? "light" : "light";
283
+ applyTheme(storedTheme);
284
+ if (isBrowser) {
285
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
286
+ const current = useUiStore?.getState?.()?.theme;
287
+ if (current === "system") applyTheme("system");
288
+ });
289
+ }
290
+ function removeArtifactAtIndex(state, index) {
291
+ const next = state.artifacts.filter((_, i) => i !== index);
292
+ let nextActive = state.activeArtifactIndex;
293
+ if (next.length === 0) {
294
+ return { artifacts: [], activeArtifactIndex: -1, rightPanelCollapsed: true };
295
+ }
296
+ if (index < nextActive) {
297
+ nextActive -= 1;
298
+ } else if (index === nextActive) {
299
+ nextActive = Math.min(index, next.length - 1);
300
+ }
301
+ return { artifacts: next, activeArtifactIndex: nextActive };
302
+ }
303
+ function upsertArtifactState(state, target, options) {
304
+ const reveal = options?.reveal ?? true;
305
+ const activate = options?.activate ?? true;
306
+ const targetKey = target.key ?? target.title;
307
+ const applyUiState = (partial) => ({
308
+ ...partial,
309
+ ...reveal ? { rightPanelCollapsed: false, activeRightTab: "preview" } : {}
310
+ });
311
+ const existing = state.artifacts.findIndex((artifact) => targetKey && (artifact.key ?? artifact.title) === targetKey);
312
+ if (existing >= 0) {
313
+ const updated = [...state.artifacts];
314
+ updated[existing] = target;
315
+ return applyUiState({
316
+ artifacts: updated,
317
+ activeArtifactIndex: activate ? existing : state.activeArtifactIndex
318
+ });
319
+ }
320
+ const next = [...state.artifacts, target];
321
+ return applyUiState({
322
+ artifacts: next,
323
+ activeArtifactIndex: activate ? next.length - 1 : state.activeArtifactIndex >= 0 ? state.activeArtifactIndex : 0
324
+ });
325
+ }
326
+ var useUiStore = create2()((set) => ({
327
+ ...createClientActions(set),
328
+ leftPanelSize: 20,
329
+ rightPanelSize: 25,
330
+ leftPanelCollapsed: false,
331
+ rightPanelCollapsed: false,
332
+ activeRightTab: "situation",
333
+ artifacts: [],
334
+ activeArtifactIndex: -1,
335
+ theme: storedTheme,
336
+ setLeftPanelSize: (size) => set({ leftPanelSize: size }),
337
+ setRightPanelSize: (size) => set({ rightPanelSize: size }),
338
+ setLeftPanelCollapsed: (collapsed) => set({ leftPanelCollapsed: collapsed }),
339
+ setRightPanelCollapsed: (collapsed) => set({ rightPanelCollapsed: collapsed }),
340
+ toggleLeftPanel: () => set((s) => ({ leftPanelCollapsed: !s.leftPanelCollapsed })),
341
+ toggleRightPanel: () => set((s) => ({ rightPanelCollapsed: !s.rightPanelCollapsed })),
342
+ setActiveRightTab: (tab) => set({ activeRightTab: tab }),
343
+ pushArtifact: (target) => set((state) => upsertArtifactState(state, target)),
344
+ upsertArtifact: (target, options) => set((state) => upsertArtifactState(state, target, options)),
345
+ setActiveArtifact: (index) => set({ activeArtifactIndex: index }),
346
+ closeArtifact: (index) => set((state) => removeArtifactAtIndex(state, index)),
347
+ removeArtifactByKey: (key) => set((state) => {
348
+ const index = state.artifacts.findIndex((artifact) => (artifact.key ?? artifact.title) === key);
349
+ if (index < 0) return state;
350
+ return removeArtifactAtIndex(state, index);
351
+ }),
352
+ clearArtifacts: () => set({ artifacts: [], activeArtifactIndex: -1 }),
353
+ setPreviewTarget: (target) => set(() => {
354
+ if (target === null) {
355
+ return { artifacts: [], activeArtifactIndex: -1 };
356
+ }
357
+ return {
358
+ artifacts: [target],
359
+ activeArtifactIndex: 0,
360
+ rightPanelCollapsed: false,
361
+ activeRightTab: "preview"
362
+ };
363
+ }),
364
+ setTheme: (theme) => {
365
+ localStorage.setItem("blade-theme", theme);
366
+ applyTheme(theme);
367
+ set({ theme });
368
+ }
369
+ }));
370
+
371
+ // src/react/stores/chat-store.ts
372
+ var _getActiveSessionId = null;
373
+ function setChatStoreSessionAccessor(fn) {
374
+ _getActiveSessionId = fn;
375
+ }
376
+ function parseAgentDescription(argumentsJson) {
377
+ try {
378
+ return JSON.parse(argumentsJson)?.description ?? "\u5B50\u667A\u80FD\u4F53";
379
+ } catch {
380
+ return "\u5B50\u667A\u80FD\u4F53";
381
+ }
382
+ }
383
+ function inferLoopStatusFromMessages(messages) {
384
+ const toolCalls = messages.flatMap((message) => message.tool_calls ?? []);
385
+ if (messages.some((message) => message.status === "streaming")) return "running";
386
+ if (toolCalls.some((toolCall) => toolCall.status === "awaiting_answer")) return "awaiting_answer";
387
+ if (toolCalls.some((toolCall) => toolCall.status === "error")) return "error";
388
+ if (toolCalls.some((toolCall) => toolCall.status === "cancelled")) return "cancelled";
389
+ if (toolCalls.some((toolCall) => toolCall.status === "pending")) return "running";
390
+ return "done";
391
+ }
392
+ function inferLoopStatusFromTurns(turns, messages) {
393
+ const latestAgentNotification = [...turns].reverse().flatMap((turn) => turn.blocks).find((block) => {
394
+ if (block.type !== "system_notification" || !isRecord(block.content)) return false;
395
+ return block.content.notification_type === "agent:start" || block.content.notification_type === "agent:end";
396
+ });
397
+ if (latestAgentNotification?.type === "system_notification" && isRecord(latestAgentNotification.content)) {
398
+ const notificationType = latestAgentNotification.content.notification_type;
399
+ const status = latestAgentNotification.content.status;
400
+ if (notificationType === "agent:start" || status === "running") return "running";
401
+ if (status === "error") return "error";
402
+ if (status === "cancelled") return "cancelled";
403
+ }
404
+ return inferLoopStatusFromMessages(messages);
405
+ }
406
+ function isRecord(value) {
407
+ return typeof value === "object" && value !== null && !Array.isArray(value);
408
+ }
409
+ function parentForkToolCallIdFromTurn(turn) {
410
+ if (typeof turn.parent_fork_tool_call_id === "string" && turn.parent_fork_tool_call_id.length > 0) {
411
+ return turn.parent_fork_tool_call_id;
412
+ }
413
+ for (const block of turn.blocks) {
414
+ if (block.type !== "system_notification" || !isRecord(block.content)) continue;
415
+ const metadata = block.content.metadata;
416
+ if (!isRecord(metadata)) continue;
417
+ const parentId = metadata.parent_fork_tool_call_id;
418
+ if (typeof parentId === "string" && parentId.length > 0) return parentId;
419
+ }
420
+ return null;
421
+ }
422
+ function buildMessageContent(turn) {
423
+ const textBlocks = turn.blocks.filter((block) => block.type === "text");
424
+ if (textBlocks.length === 0) return "";
425
+ if (textBlocks.length === 1) return normalizeMessageContent(textBlocks[0].content);
426
+ return textBlocks.map((block) => {
427
+ if (typeof block.content === "string") return block.content;
428
+ return JSON.stringify(block.content);
429
+ }).join("");
430
+ }
431
+ function buildReasoning(turn) {
432
+ const thinking = turn.blocks.filter((block) => block.type === "thinking").map((block) => typeof block.content === "string" ? block.content : JSON.stringify(block.content)).filter(Boolean);
433
+ if (thinking.length === 0) return void 0;
434
+ return thinking.join("\n\n");
435
+ }
436
+ function isModeChangeContent(value) {
437
+ return typeof value === "object" && value !== null && typeof value.from === "string" && typeof value.to === "string";
438
+ }
439
+ function extractModeFromBlocks(blocks) {
440
+ const modeBlock = blocks.find((block) => block.type === "mode_change");
441
+ if (modeBlock && isModeChangeContent(modeBlock.content)) {
442
+ return modeBlock.content.to === "planning" || modeBlock.content.to === "executing" ? modeBlock.content.to : null;
443
+ }
444
+ if (blocks.some((block) => block.type === "planning_enter")) return "planning";
445
+ if (blocks.some((block) => block.type === "planning_exit")) return "executing";
446
+ return null;
447
+ }
448
+ function projectionToMessage(turn) {
449
+ if (turn.kind === "compaction" && turn.compaction_id) {
450
+ return {
451
+ role: "assistant",
452
+ content: turn.summary_preview ?? "",
453
+ kind: "compaction",
454
+ loop_name: turn.loop_id,
455
+ entry_id: turn.turn_id,
456
+ status: turn.status,
457
+ compaction: {
458
+ compaction_id: turn.compaction_id,
459
+ summary_preview: turn.summary_preview,
460
+ summary_full: turn.summary_full,
461
+ archived_count: turn.archived_count,
462
+ archived_files: turn.archived_files,
463
+ archived_tool_calls: turn.archived_tool_calls,
464
+ tokens_before: turn.tokens_before,
465
+ tokens_after: turn.tokens_after,
466
+ saved_ratio: turn.saved_ratio,
467
+ trigger: turn.trigger,
468
+ failure_reason: turn.failure_reason,
469
+ fallback_applied: turn.fallback_applied
470
+ }
471
+ };
472
+ }
473
+ const planningBlock = turn.blocks.find(
474
+ (block) => block.type === "mode_change" || block.type === "planning_enter" || block.type === "planning_exit" || block.type === "plan_status"
475
+ );
476
+ if (planningBlock) {
477
+ if (planningBlock.type === "plan_status") {
478
+ return {
479
+ role: "tool",
480
+ content: typeof planningBlock.content === "string" ? planningBlock.content : JSON.stringify(planningBlock.content ?? {}, null, 2),
481
+ kind: "plan_status",
482
+ loop_name: turn.loop_id,
483
+ entry_id: turn.turn_id,
484
+ status: turn.status
485
+ };
486
+ }
487
+ return {
488
+ role: "assistant",
489
+ content: planningBlock.type === "mode_change" ? typeof planningBlock.content === "string" ? planningBlock.content : JSON.stringify(planningBlock.content ?? {}) : "",
490
+ kind: planningBlock.type,
491
+ loop_name: turn.loop_id,
492
+ entry_id: turn.turn_id,
493
+ status: turn.status
494
+ };
495
+ }
496
+ if (turn.blocks.some((block) => block.type === "ask_user_answer")) {
497
+ return null;
498
+ }
499
+ const content = buildMessageContent(turn);
500
+ const reasoning = buildReasoning(turn);
501
+ const toolCalls = turn.tool_calls.length > 0 ? turn.tool_calls.map((toolCall) => ({
502
+ id: toolCall.id,
503
+ name: toolCall.tool_name,
504
+ display_name: toolCall.display_name,
505
+ arguments: toolCall.arguments,
506
+ result: toolCall.result ?? void 0,
507
+ pending_question_ref: toolCall.pending_question_ref ?? void 0,
508
+ status: toolCall.status === "pending" || toolCall.status === "awaiting_answer" || toolCall.status === "done" || toolCall.status === "error" || toolCall.status === "cancelled" ? toolCall.status : "pending",
509
+ ...typeof toolCall.duration_ms === "number" ? { duration_ms: toolCall.duration_ms } : {}
510
+ })) : void 0;
511
+ if (turn.role === "system" && !content && !toolCalls?.length) {
512
+ return null;
513
+ }
514
+ return {
515
+ role: turn.role === "system" ? "assistant" : turn.role,
516
+ content,
517
+ blocks: turn.blocks,
518
+ ...reasoning ? { reasoning } : {},
519
+ ...toolCalls ? { tool_calls: toolCalls } : {},
520
+ loop_name: turn.loop_id,
521
+ entry_id: turn.turn_id,
522
+ status: turn.status,
523
+ ...typeof turn.duration_ms === "number" ? { duration_ms: turn.duration_ms } : {},
524
+ ...turn.started_at ? { timestamp: turn.started_at } : {},
525
+ ...turn.memory_refs?.length ? { memory_refs: turn.memory_refs } : {}
526
+ };
527
+ }
528
+ function rebuildAgentLoops(turns) {
529
+ const messages = turns.map(projectionToMessage).filter(Boolean);
530
+ const childLoopNames = [...new Set(turns.map((turn) => turn.loop_id).filter((name) => name !== "root"))];
531
+ if (childLoopNames.length === 0) return {};
532
+ const agentToolCalls = messages.filter((message) => message.role === "assistant" && (message.loop_name ?? "root") === "root").flatMap((message) => message.tool_calls ?? []).filter((toolCall) => formatToolName(toolCall.name) === "Agent");
533
+ const loops = {};
534
+ const agentToolCallsById = new Map(agentToolCalls.map((toolCall) => [toolCall.id, toolCall]));
535
+ const explicitParentToolCallIds = new Set(
536
+ turns.map(parentForkToolCallIdFromTurn).filter((id) => id !== null)
537
+ );
538
+ const usedToolCallIds = /* @__PURE__ */ new Set();
539
+ for (const loopName of childLoopNames) {
540
+ const loopMessages = messages.filter((message) => (message.loop_name ?? "root") === loopName);
541
+ const loopTurns = turns.filter((turn) => turn.loop_id === loopName);
542
+ const parentToolCallId = loopTurns.map(parentForkToolCallIdFromTurn).find(Boolean);
543
+ const explicitToolCall = parentToolCallId && !usedToolCallIds.has(parentToolCallId) ? agentToolCallsById.get(parentToolCallId) ?? null : null;
544
+ const fallbackToolCall = explicitToolCall === null ? agentToolCalls.find(
545
+ (toolCall2) => !usedToolCallIds.has(toolCall2.id) && !explicitParentToolCallIds.has(toolCall2.id)
546
+ ) : null;
547
+ const toolCall = explicitToolCall ?? fallbackToolCall;
548
+ if (!toolCall) continue;
549
+ usedToolCallIds.add(toolCall.id);
550
+ loops[loopName] = {
551
+ toolCallId: toolCall.id,
552
+ description: parseAgentDescription(toolCall.arguments),
553
+ status: inferLoopStatusFromTurns(loopTurns, loopMessages)
554
+ };
555
+ }
556
+ return loops;
557
+ }
558
+ function applyPlanningSideEffects(sessionId, turns) {
559
+ const latestMode = [...turns].reverse().map((turn) => extractModeFromBlocks(turn.blocks)).find((mode) => mode !== null);
560
+ if (latestMode !== "planning") return;
561
+ if (_getActiveSessionId?.() !== sessionId) return;
562
+ const ui = useUiStore.getState();
563
+ ui.setActiveRightTab("situation");
564
+ if (ui.rightPanelCollapsed) {
565
+ ui.toggleRightPanel();
566
+ }
567
+ }
568
+ function materialize(turns) {
569
+ const messages = turns.map(projectionToMessage).filter((message) => message !== null);
570
+ const activeCompaction = [...turns].reverse().find(
571
+ (turn) => turn.kind === "compaction" && turn.status === "streaming" && typeof turn.compaction_id === "string"
572
+ );
573
+ return {
574
+ messages,
575
+ agentLoops: rebuildAgentLoops(turns),
576
+ activeCompaction: activeCompaction ? {
577
+ turn_id: activeCompaction.turn_id,
578
+ status: activeCompaction.status,
579
+ compaction_id: activeCompaction.compaction_id,
580
+ summary_preview: activeCompaction.summary_preview,
581
+ summary_full: activeCompaction.summary_full,
582
+ archived_count: activeCompaction.archived_count,
583
+ archived_files: activeCompaction.archived_files,
584
+ archived_tool_calls: activeCompaction.archived_tool_calls,
585
+ tokens_before: activeCompaction.tokens_before,
586
+ tokens_after: activeCompaction.tokens_after,
587
+ saved_ratio: activeCompaction.saved_ratio,
588
+ trigger: activeCompaction.trigger,
589
+ failure_reason: activeCompaction.failure_reason,
590
+ fallback_applied: activeCompaction.fallback_applied
591
+ } : null
592
+ };
593
+ }
594
+ var ERROR_ANCHOR_PREFIX = "error-anchor:";
595
+ function updateSessionState(state, sessionId, turns) {
596
+ const orderedTurns = [...turns].sort((left, right) => left.sequence - right.sequence);
597
+ const { messages, agentLoops, activeCompaction } = materialize(orderedTurns);
598
+ applyPlanningSideEffects(sessionId, orderedTurns);
599
+ const lastTurnId = orderedTurns[orderedTurns.length - 1]?.turn_id ?? null;
600
+ const preservedErrors = lastTurnId ? (state.messages[sessionId] ?? []).filter(
601
+ (m) => m.role === "error" && typeof m.entry_id === "string" && m.entry_id.startsWith(`${ERROR_ANCHOR_PREFIX}${lastTurnId}:`)
602
+ ) : [];
603
+ const mergedMessages = preservedErrors.length > 0 ? [...messages, ...preservedErrors] : messages;
604
+ return {
605
+ turns: { ...state.turns, [sessionId]: orderedTurns },
606
+ messages: { ...state.messages, [sessionId]: mergedMessages },
607
+ agentLoops: { ...state.agentLoops, [sessionId]: agentLoops },
608
+ activeCompactions: { ...state.activeCompactions, [sessionId]: activeCompaction }
609
+ };
610
+ }
611
+ var useChatStore = create3()((set) => ({
612
+ ...createClientActions(set),
613
+ turns: {},
614
+ messages: {},
615
+ askAnswers: {},
616
+ isStreaming: {},
617
+ agentLoops: {},
618
+ activeCompactions: {},
619
+ addUserMessage: (sessionId, content) => {
620
+ set((state) => {
621
+ const existing = state.turns[sessionId] ?? [];
622
+ const turnId = `local-user-${Date.now()}`;
623
+ const optimisticTurn = {
624
+ id: turnId,
625
+ sequence: Math.max(0, ...existing.map((turn) => turn.sequence)) + 1,
626
+ turn_id: turnId,
627
+ loop_id: "root",
628
+ role: "user",
629
+ status: "completed",
630
+ blocks: [{ type: "text", content }],
631
+ tool_calls: [],
632
+ model: null,
633
+ usage: null,
634
+ duration_ms: 0
635
+ };
636
+ return updateSessionState(state, sessionId, [...existing, optimisticTurn]);
637
+ });
638
+ },
639
+ setTurns: (sessionId, turns) => {
640
+ set((state) => updateSessionState(state, sessionId, [...turns]));
641
+ },
642
+ upsertTurn: (sessionId, turn) => {
643
+ set((state) => {
644
+ const existing = [...state.turns[sessionId] ?? []];
645
+ const index = existing.findIndex((item) => item.turn_id === turn.turn_id);
646
+ if (index >= 0) {
647
+ existing[index] = turn;
648
+ } else {
649
+ existing.push(turn);
650
+ }
651
+ return {
652
+ ...updateSessionState(state, sessionId, existing)
653
+ };
654
+ });
655
+ },
656
+ applyTurnPatch: (sessionId, patch) => {
657
+ set((state) => {
658
+ const turn = patch.data.turn;
659
+ if (!turn) return state;
660
+ const existing = [...state.turns[sessionId] ?? []];
661
+ const index = existing.findIndex((item) => item.turn_id === turn.turn_id);
662
+ const lastSequence = index >= 0 ? existing[index].sequence : null;
663
+ if (lastSequence !== null && patch.sequence <= lastSequence) {
664
+ return state;
665
+ }
666
+ const nextTurn = {
667
+ ...turn,
668
+ sequence: patch.sequence
669
+ };
670
+ if (index >= 0) {
671
+ existing[index] = nextTurn;
672
+ } else {
673
+ existing.push(nextTurn);
674
+ }
675
+ return {
676
+ ...updateSessionState(state, sessionId, existing)
677
+ };
678
+ });
679
+ },
680
+ addErrorMessage: (sessionId, content) => {
681
+ set((state) => {
682
+ const turns = state.turns[sessionId] ?? [];
683
+ const anchorTurnId = turns[turns.length - 1]?.turn_id ?? null;
684
+ const entry_id = anchorTurnId ? `${ERROR_ANCHOR_PREFIX}${anchorTurnId}:${Date.now()}` : void 0;
685
+ return {
686
+ messages: {
687
+ ...state.messages,
688
+ [sessionId]: [
689
+ ...state.messages[sessionId] ?? [],
690
+ { role: "error", content, loop_name: "root", entry_id }
691
+ ]
692
+ }
693
+ };
694
+ });
695
+ },
696
+ markInterrupted: (sessionId) => {
697
+ set((state) => {
698
+ const turns = (state.turns[sessionId] ?? []).map((turn) => {
699
+ if (turn.status !== "streaming") return turn;
700
+ return {
701
+ ...turn,
702
+ status: "interrupted",
703
+ tool_calls: turn.tool_calls.map(
704
+ (toolCall) => toolCall.status === "pending" || toolCall.status === "awaiting_answer" ? { ...toolCall, status: "cancelled" } : toolCall
705
+ )
706
+ };
707
+ });
708
+ return {
709
+ ...updateSessionState(state, sessionId, turns)
710
+ };
711
+ });
712
+ },
713
+ markFailed: (sessionId) => {
714
+ set((state) => {
715
+ const turns = (state.turns[sessionId] ?? []).map((turn) => {
716
+ if (turn.status !== "streaming") return turn;
717
+ return {
718
+ ...turn,
719
+ status: "failed",
720
+ tool_calls: turn.tool_calls.map(
721
+ (toolCall) => toolCall.status === "pending" || toolCall.status === "awaiting_answer" ? { ...toolCall, status: "error" } : toolCall
722
+ )
723
+ };
724
+ });
725
+ return {
726
+ ...updateSessionState(state, sessionId, turns)
727
+ };
728
+ });
729
+ },
730
+ setStreaming: (sessionId, streaming) => {
731
+ set((state) => ({
732
+ isStreaming: { ...state.isStreaming, [sessionId]: streaming }
733
+ }));
734
+ },
735
+ setAskAnswers: (sessionId, answers) => {
736
+ set((state) => ({
737
+ askAnswers: {
738
+ ...state.askAnswers,
739
+ [sessionId]: answers
740
+ }
741
+ }));
742
+ },
743
+ clearMessages: (sessionId) => {
744
+ set((state) => {
745
+ const { [sessionId]: _turns, ...restTurns } = state.turns;
746
+ const { [sessionId]: _messages, ...restMessages } = state.messages;
747
+ const { [sessionId]: _answers, ...restAnswers } = state.askAnswers;
748
+ const { [sessionId]: _loops, ...restLoops } = state.agentLoops;
749
+ const { [sessionId]: _compaction, ...restCompactions } = state.activeCompactions;
750
+ return {
751
+ turns: restTurns,
752
+ messages: restMessages,
753
+ askAnswers: restAnswers,
754
+ agentLoops: restLoops,
755
+ activeCompactions: restCompactions
756
+ };
757
+ });
758
+ }
759
+ }));
760
+
761
+ // src/react/stores/task-store.ts
762
+ import { create as create4 } from "zustand";
763
+ var EMPTY_TASKS = [];
764
+ var useTaskStore = create4()((set, get) => ({
765
+ ...createClientActions(set),
766
+ tasks: {},
767
+ setTasks: (sessionId, tasks) => {
768
+ set((state) => ({
769
+ tasks: { ...state.tasks, [sessionId]: tasks }
770
+ }));
771
+ },
772
+ getTasks: (sessionId) => {
773
+ return get().tasks[sessionId] ?? EMPTY_TASKS;
774
+ }
775
+ }));
776
+
777
+ // src/react/stores/session-store.ts
778
+ var DEFAULT_SESSION_MODE = "executing";
779
+ var onSessionChange = null;
780
+ function invalidateHomeSidebarSessions() {
781
+ const queryClient = globalThis.__agentQueryClient;
782
+ if (!queryClient) return;
783
+ void queryClient.invalidateQueries({ queryKey: ["sessions", "home-sidebar"] });
784
+ }
785
+ function isSessionAccessRevoked(error) {
786
+ if (error instanceof Error) {
787
+ const msg = error.message;
788
+ return msg.includes("API 403") || msg.includes("API 404");
789
+ }
790
+ return false;
791
+ }
792
+ function removeSessionArtifacts(sessionId) {
793
+ useChatStore.getState().clearMessages(sessionId);
794
+ useTaskStore.getState().setTasks(sessionId, []);
795
+ }
796
+ function pruneSessionState(state, sessionId) {
797
+ const nextFresh = new Set(state._freshSessions);
798
+ nextFresh.delete(sessionId);
799
+ const { [sessionId]: _mode, ...modes } = state.modes;
800
+ const { [sessionId]: _rewindDraft, ...rewindDrafts } = state.rewindDrafts;
801
+ return {
802
+ sessions: state.sessions.filter((session) => session.id !== sessionId),
803
+ activeSessionId: state.activeSessionId === sessionId ? null : state.activeSessionId,
804
+ modes,
805
+ rewindDrafts,
806
+ _freshSessions: nextFresh
807
+ };
808
+ }
809
+ function navigateAwayFromSession(sessionId) {
810
+ if (typeof window === "undefined") return;
811
+ if (window.location.pathname !== `/chat/${sessionId}`) return;
812
+ window.history.replaceState(window.history.state, "", "/chat");
813
+ window.dispatchEvent(new PopStateEvent("popstate"));
814
+ }
815
+ function handleUnreadableSession(set, get, sessionId) {
816
+ const wasActive = get().activeSessionId === sessionId;
817
+ removeSessionArtifacts(sessionId);
818
+ set((state) => pruneSessionState(state, sessionId));
819
+ if (wasActive) {
820
+ onSessionChange?.(null);
821
+ navigateAwayFromSession(sessionId);
822
+ }
823
+ invalidateHomeSidebarSessions();
824
+ }
825
+ function isPlainRecord(value) {
826
+ return typeof value === "object" && value !== null && !Array.isArray(value);
827
+ }
828
+ function extractModeFromBlocks2(blocks) {
829
+ const modeBlock = blocks.find((block) => block.type === "mode_change");
830
+ if (modeBlock && isPlainRecord(modeBlock.content) && (modeBlock.content.to === "planning" || modeBlock.content.to === "executing")) {
831
+ return modeBlock.content.to;
832
+ }
833
+ if (blocks.some((block) => block.type === "planning_enter")) return "planning";
834
+ if (blocks.some((block) => block.type === "planning_exit")) return "executing";
835
+ return null;
836
+ }
837
+ function toSelectionMap(value) {
838
+ if (!isPlainRecord(value)) return {};
839
+ const entries = Object.entries(value).map(([questionKey, optionIndexes]) => {
840
+ if (!Array.isArray(optionIndexes)) return null;
841
+ const parsedIndexes = optionIndexes.map((item) => typeof item === "number" ? item : Number(item)).filter((item) => Number.isInteger(item));
842
+ return [Number(questionKey), parsedIndexes];
843
+ }).filter((entry) => entry !== null);
844
+ return Object.fromEntries(entries);
845
+ }
846
+ function toCustomMap(value) {
847
+ if (!isPlainRecord(value)) return {};
848
+ const entries = Object.entries(value).filter(([, text]) => typeof text === "string").map(([questionKey, text]) => [Number(questionKey), text]);
849
+ return Object.fromEntries(entries);
850
+ }
851
+ function extractAskAnswers(turns) {
852
+ const answers = {};
853
+ for (const turn of turns) {
854
+ for (const block of turn.blocks) {
855
+ if (block.type !== "ask_user_answer" || typeof block.tool_call_id !== "string") continue;
856
+ answers[block.tool_call_id] = {
857
+ selections: toSelectionMap(isPlainRecord(block.content) ? block.content.selections : void 0),
858
+ custom: toCustomMap(isPlainRecord(block.content) ? block.content.custom : void 0)
859
+ };
860
+ }
861
+ }
862
+ return answers;
863
+ }
864
+ function registerCreatedSessionState(set, session, mode = DEFAULT_SESSION_MODE) {
865
+ useChatStore.getState().setTurns(session.id, []);
866
+ useTaskStore.getState().setTasks(session.id, []);
867
+ set((state) => ({
868
+ sessions: [session, ...state.sessions.filter((item) => item.id !== session.id)],
869
+ modes: { ...state.modes, [session.id]: state.modes[session.id] ?? mode },
870
+ _freshSessions: new Set(state._freshSessions).add(session.id)
871
+ }));
872
+ }
873
+ async function revalidateViewerSessions(existingSessions) {
874
+ const viewerSessions = existingSessions.filter((session) => session.viewer_role === "viewer");
875
+ if (viewerSessions.length === 0) {
876
+ return [];
877
+ }
878
+ const refreshed = await Promise.all(
879
+ viewerSessions.map(async (session) => {
880
+ try {
881
+ return await getSession(session.id);
882
+ } catch (error) {
883
+ if (isSessionAccessRevoked(error)) {
884
+ return null;
885
+ }
886
+ return session;
887
+ }
888
+ })
889
+ );
890
+ return refreshed.filter((session) => session !== null);
891
+ }
892
+ var useSessionStore = create5()((set, get) => ({
893
+ ...createClientActions(set),
894
+ sessions: [],
895
+ activeSessionId: null,
896
+ loading: false,
897
+ modes: {},
898
+ rewindDrafts: {},
899
+ _freshSessions: /* @__PURE__ */ new Set(),
900
+ fetchSessions: async () => {
901
+ set({ loading: true });
902
+ try {
903
+ const currentState = get();
904
+ const [sessions, viewerSessions] = await Promise.all([
905
+ listSessions(),
906
+ revalidateViewerSessions(currentState.sessions)
907
+ ]);
908
+ const knownSessionIds = new Set(sessions.map((session) => session.id));
909
+ const mergedSessions = [
910
+ ...sessions,
911
+ ...viewerSessions.filter((session) => !knownSessionIds.has(session.id))
912
+ ];
913
+ const removedSessionIds = currentState.sessions.filter(
914
+ (session) => session.viewer_role === "viewer" && !mergedSessions.some((candidate) => candidate.id === session.id)
915
+ ).map((session) => session.id);
916
+ const activeSessionId = currentState.activeSessionId;
917
+ for (const sessionId of removedSessionIds) {
918
+ removeSessionArtifacts(sessionId);
919
+ }
920
+ let nextState = {
921
+ sessions: mergedSessions,
922
+ activeSessionId: currentState.activeSessionId,
923
+ loading: false,
924
+ modes: currentState.modes,
925
+ rewindDrafts: currentState.rewindDrafts,
926
+ _freshSessions: currentState._freshSessions
927
+ };
928
+ for (const sessionId of removedSessionIds) {
929
+ nextState = {
930
+ ...nextState,
931
+ ...pruneSessionState(
932
+ {
933
+ ...currentState,
934
+ sessions: nextState.sessions ?? currentState.sessions,
935
+ activeSessionId: nextState.activeSessionId ?? currentState.activeSessionId,
936
+ loading: nextState.loading ?? currentState.loading,
937
+ modes: nextState.modes ?? currentState.modes,
938
+ rewindDrafts: nextState.rewindDrafts ?? currentState.rewindDrafts,
939
+ _freshSessions: nextState._freshSessions ?? currentState._freshSessions
940
+ },
941
+ sessionId
942
+ )
943
+ };
944
+ }
945
+ set(nextState);
946
+ if (activeSessionId && removedSessionIds.includes(activeSessionId)) {
947
+ onSessionChange?.(null);
948
+ navigateAwayFromSession(activeSessionId);
949
+ }
950
+ invalidateHomeSidebarSessions();
951
+ } catch (error) {
952
+ set({ loading: false });
953
+ throw error;
954
+ }
955
+ },
956
+ createSession: async (intent) => {
957
+ const { session_id } = await createSession(intent);
958
+ const now = (/* @__PURE__ */ new Date()).toISOString();
959
+ get().registerCreatedSession({
960
+ id: session_id,
961
+ intent: intent ?? "",
962
+ status: "created",
963
+ created_at: now,
964
+ updated_at: now
965
+ });
966
+ get().setActiveSession(session_id);
967
+ invalidateHomeSidebarSessions();
968
+ get().fetchSessions().catch(() => {
969
+ });
970
+ return session_id;
971
+ },
972
+ registerCreatedSession: (session, mode = DEFAULT_SESSION_MODE) => {
973
+ registerCreatedSessionState(set, session, mode);
974
+ },
975
+ upsertSession: (session) => {
976
+ set((state) => ({
977
+ sessions: state.sessions.some((item) => item.id === session.id) ? state.sessions.map((item) => item.id === session.id ? { ...item, ...session } : item) : [session, ...state.sessions]
978
+ }));
979
+ },
980
+ isFreshSession: (sessionId) => get()._freshSessions.has(sessionId),
981
+ setActiveSession: (id) => {
982
+ set({ activeSessionId: id });
983
+ onSessionChange?.(id);
984
+ getSession(id).then((detail) => {
985
+ set((state) => ({
986
+ sessions: state.sessions.some((s) => s.id === id) ? state.sessions.map(
987
+ (s) => s.id === id ? { ...s, status: detail.status, updated_at: detail.updated_at } : s
988
+ ) : [detail, ...state.sessions]
989
+ }));
990
+ }).catch(() => {
991
+ });
992
+ const tasksPromise = getSessionTasks(id).catch(() => null);
993
+ Promise.all([getSessionTurns(id), tasksPromise]).then(([turns, tasks]) => {
994
+ if (tasks) useTaskStore.getState().setTasks(id, tasks);
995
+ useChatStore.getState().setAskAnswers(id, extractAskAnswers(turns));
996
+ let inferredMode = DEFAULT_SESSION_MODE;
997
+ for (const turn of turns) {
998
+ const nextMode = extractModeFromBlocks2(turn.blocks);
999
+ if (nextMode) {
1000
+ inferredMode = nextMode;
1001
+ }
1002
+ }
1003
+ set((state) => {
1004
+ if (state.modes[id] == null) {
1005
+ return { modes: { ...state.modes, [id]: inferredMode } };
1006
+ }
1007
+ return state;
1008
+ });
1009
+ const isFresh = get()._freshSessions.has(id);
1010
+ if (isFresh) {
1011
+ set((state) => {
1012
+ const next = new Set(state._freshSessions);
1013
+ next.delete(id);
1014
+ return { _freshSessions: next };
1015
+ });
1016
+ if (turns.length > 0) {
1017
+ useChatStore.getState().setTurns(id, turns);
1018
+ }
1019
+ } else {
1020
+ useChatStore.getState().setTurns(id, turns);
1021
+ }
1022
+ }).catch((error) => {
1023
+ if (isSessionAccessRevoked(error)) {
1024
+ handleUnreadableSession(set, get, id);
1025
+ return;
1026
+ }
1027
+ console.error("Failed to load session data", error);
1028
+ });
1029
+ },
1030
+ clearActiveSession: () => {
1031
+ set({ activeSessionId: null });
1032
+ onSessionChange?.(null);
1033
+ },
1034
+ deleteSession: async (id) => {
1035
+ await deleteSession(id);
1036
+ invalidateHomeSidebarSessions();
1037
+ const wasActive = get().activeSessionId === id;
1038
+ await get().fetchSessions();
1039
+ if (!wasActive) {
1040
+ return;
1041
+ }
1042
+ const nextId = get().sessions[0]?.id ?? null;
1043
+ if (nextId) {
1044
+ get().setActiveSession(nextId);
1045
+ return;
1046
+ }
1047
+ get().clearActiveSession();
1048
+ },
1049
+ updateSessionStatus: (sessionId, status) => {
1050
+ set((state) => ({
1051
+ sessions: state.sessions.map((s) => s.id === sessionId ? { ...s, status } : s)
1052
+ }));
1053
+ if (status === "failed" || status === "interrupted") {
1054
+ useChatStore.getState().setStreaming(sessionId, false);
1055
+ if (status === "interrupted") {
1056
+ useChatStore.getState().markInterrupted(sessionId);
1057
+ } else {
1058
+ useChatStore.getState().markFailed(sessionId);
1059
+ }
1060
+ }
1061
+ },
1062
+ updateSessionIntent: (sessionId, intent) => {
1063
+ set((state) => ({
1064
+ sessions: state.sessions.map((s) => s.id === sessionId ? { ...s, intent } : s)
1065
+ }));
1066
+ },
1067
+ patchSession: (sessionId, patch) => {
1068
+ set((state) => ({
1069
+ sessions: state.sessions.map(
1070
+ (s) => s.id === sessionId ? { ...s, ...patch } : s
1071
+ )
1072
+ }));
1073
+ },
1074
+ pinSession: async (sessionId, pinned) => {
1075
+ const updated = await pinSession(sessionId, pinned);
1076
+ set((state) => ({
1077
+ sessions: state.sessions.map(
1078
+ (s) => s.id === sessionId ? { ...s, is_pinned: updated.is_pinned, pinned_at: updated.pinned_at } : s
1079
+ )
1080
+ }));
1081
+ invalidateHomeSidebarSessions();
1082
+ },
1083
+ setRewindDraft: (sessionId, text) => {
1084
+ set((state) => {
1085
+ if (text == null) {
1086
+ const { [sessionId]: _, ...rest } = state.rewindDrafts;
1087
+ return { rewindDrafts: rest };
1088
+ }
1089
+ return {
1090
+ rewindDrafts: {
1091
+ ...state.rewindDrafts,
1092
+ [sessionId]: text
1093
+ }
1094
+ };
1095
+ });
1096
+ },
1097
+ setMode: (sessionId, mode) => {
1098
+ set((state) => ({ modes: { ...state.modes, [sessionId]: mode } }));
1099
+ },
1100
+ togglePlanningMode: (sessionId) => {
1101
+ const current = get().modes[sessionId] ?? DEFAULT_SESSION_MODE;
1102
+ const next = current === "executing" ? "planning" : "executing";
1103
+ set((state) => ({ modes: { ...state.modes, [sessionId]: next } }));
1104
+ },
1105
+ toggleSharing: async (sessionId) => {
1106
+ const session = get().sessions.find((s) => s.id === sessionId);
1107
+ if (!session) return;
1108
+ const newShared = !session.shared;
1109
+ const result = await updateSharing(sessionId, newShared);
1110
+ set((state) => ({
1111
+ sessions: state.sessions.map(
1112
+ (s) => s.id === sessionId ? { ...s, shared: result.shared } : s
1113
+ )
1114
+ }));
1115
+ },
1116
+ reset: () => {
1117
+ set({
1118
+ sessions: [],
1119
+ activeSessionId: null,
1120
+ loading: false,
1121
+ modes: {},
1122
+ rewindDrafts: {},
1123
+ _freshSessions: /* @__PURE__ */ new Set()
1124
+ });
1125
+ invalidateHomeSidebarSessions();
1126
+ }
1127
+ }));
1128
+ setChatStoreSessionAccessor(() => useSessionStore.getState().activeSessionId);
1129
+
1130
+ // src/react/stores/auth-store.ts
1131
+ var noopStorage = {
1132
+ getItem: () => null,
1133
+ setItem: () => {
1134
+ },
1135
+ removeItem: () => {
1136
+ }
1137
+ };
1138
+ function shouldClearPersistedAuthState(value) {
1139
+ try {
1140
+ const parsed = JSON.parse(value);
1141
+ return parsed.state?.token == null && parsed.state?.user == null;
1142
+ } catch {
1143
+ return false;
1144
+ }
1145
+ }
1146
+ var authStorage = createJSONStorage(() => {
1147
+ if (typeof localStorage === "undefined") {
1148
+ return noopStorage;
1149
+ }
1150
+ return {
1151
+ getItem: (name) => localStorage.getItem(name),
1152
+ setItem: (name, value) => {
1153
+ if (shouldClearPersistedAuthState(value)) {
1154
+ localStorage.removeItem(name);
1155
+ return;
1156
+ }
1157
+ localStorage.setItem(name, value);
1158
+ },
1159
+ removeItem: (name) => localStorage.removeItem(name)
1160
+ };
1161
+ });
1162
+ function finishAuth(set, payload) {
1163
+ set({
1164
+ token: payload.token,
1165
+ socketAuthToken: null,
1166
+ user: payload.user,
1167
+ loading: false,
1168
+ error: null
1169
+ });
1170
+ agentSocket?.reconnect();
1171
+ useSessionStore.getState().fetchSessions().catch(() => {
1172
+ });
1173
+ }
1174
+ function finishCookieHydration(set, payload) {
1175
+ set({
1176
+ token: null,
1177
+ socketAuthToken: payload.token,
1178
+ user: payload.user,
1179
+ loading: false,
1180
+ error: null
1181
+ });
1182
+ agentSocket?.reconnect();
1183
+ useSessionStore.getState().fetchSessions().catch(() => {
1184
+ });
1185
+ }
1186
+ function toUser(info) {
1187
+ return {
1188
+ id: info.id,
1189
+ username: info.username,
1190
+ display_name: info.display_name,
1191
+ avatar_url: info.avatar_url,
1192
+ is_admin: info.is_admin
1193
+ };
1194
+ }
1195
+ var useAuthStore = create6()(
1196
+ persist(
1197
+ (set, get) => ({
1198
+ ...createClientActions(set),
1199
+ token: null,
1200
+ socketAuthToken: null,
1201
+ user: null,
1202
+ loading: false,
1203
+ error: null,
1204
+ logout: () => {
1205
+ void logout().catch(() => {
1206
+ });
1207
+ set({ token: null, socketAuthToken: null, user: null, error: null });
1208
+ agentSocket?.disconnect();
1209
+ useSessionStore.getState().reset();
1210
+ },
1211
+ checkAuth: async () => {
1212
+ const token = get().token;
1213
+ if (!token) return;
1214
+ try {
1215
+ const auth = await getMe();
1216
+ finishAuth(set, { token: auth.token, user: toUser(auth) });
1217
+ } catch {
1218
+ set({ token: null, socketAuthToken: null, user: null, error: null });
1219
+ useSessionStore.getState().reset();
1220
+ }
1221
+ },
1222
+ hydrateFromCookie: async () => {
1223
+ set({ loading: true, error: null });
1224
+ const previousToken = get().token;
1225
+ if (previousToken) {
1226
+ try {
1227
+ const auth = await getMe();
1228
+ finishAuth(set, { token: auth.token, user: toUser(auth) });
1229
+ return;
1230
+ } catch {
1231
+ set({ token: null, socketAuthToken: null, user: null, error: null });
1232
+ useSessionStore.getState().reset();
1233
+ }
1234
+ }
1235
+ try {
1236
+ const auth = await getMe();
1237
+ finishCookieHydration(set, { token: auth.token, user: toUser(auth) });
1238
+ } catch {
1239
+ set({
1240
+ token: null,
1241
+ socketAuthToken: null,
1242
+ user: null,
1243
+ loading: false,
1244
+ error: null
1245
+ });
1246
+ useSessionStore.getState().reset();
1247
+ }
1248
+ }
1249
+ }),
1250
+ {
1251
+ name: "agent-auth",
1252
+ storage: authStorage,
1253
+ partialize: (state) => {
1254
+ if (state.socketAuthToken) {
1255
+ return { token: null, user: null };
1256
+ }
1257
+ return { token: state.token, user: state.user };
1258
+ }
1259
+ }
1260
+ )
1261
+ );
1262
+
1263
+ // src/react/stores/background-store.ts
1264
+ import { create as create7 } from "zustand";
1265
+ var useBackgroundStore = create7()((set) => ({
1266
+ ...createClientActions(set),
1267
+ tasks: {},
1268
+ selectedTaskId: {},
1269
+ setTasks: (sessionId, tasks) => set((state) => ({
1270
+ tasks: { ...state.tasks, [sessionId]: tasks },
1271
+ selectedTaskId: {
1272
+ ...state.selectedTaskId,
1273
+ [sessionId]: state.selectedTaskId[sessionId] ?? tasks[0]?.id ?? null
1274
+ }
1275
+ })),
1276
+ upsertTask: (sessionId, task) => set((state) => {
1277
+ const existing = state.tasks[sessionId] ?? [];
1278
+ const index = existing.findIndex((item) => item.id === task.id);
1279
+ const next = [...existing];
1280
+ if (index >= 0) {
1281
+ next[index] = { ...next[index], ...task };
1282
+ } else {
1283
+ next.unshift(task);
1284
+ }
1285
+ return {
1286
+ tasks: { ...state.tasks, [sessionId]: next },
1287
+ selectedTaskId: {
1288
+ ...state.selectedTaskId,
1289
+ [sessionId]: state.selectedTaskId[sessionId] ?? task.id
1290
+ }
1291
+ };
1292
+ }),
1293
+ selectTask: (sessionId, taskId) => set((state) => ({
1294
+ selectedTaskId: { ...state.selectedTaskId, [sessionId]: taskId }
1295
+ }))
1296
+ }));
1297
+
1298
+ // src/react/stores/card-state-store.ts
1299
+ import { create as create8 } from "zustand";
1300
+ var useCardStateStore = create8((set, get) => ({
1301
+ ...createClientActions(set),
1302
+ states: {},
1303
+ getCardState: (cardId) => {
1304
+ return get().states[cardId];
1305
+ },
1306
+ setCardState: (cardId, state) => {
1307
+ set((prev) => ({
1308
+ states: { ...prev.states, [cardId]: state }
1309
+ }));
1310
+ },
1311
+ removeCardState: (cardId) => {
1312
+ set((prev) => {
1313
+ const { [cardId]: _, ...rest } = prev.states;
1314
+ return { states: rest };
1315
+ });
1316
+ },
1317
+ clearAllStates: () => {
1318
+ set({ states: {} });
1319
+ }
1320
+ }));
1321
+
1322
+ // src/react/stores/connection-store.ts
1323
+ import { create as create9 } from "zustand";
1324
+ var initialConnectionState = {
1325
+ status: "disconnected",
1326
+ reconnectAttempt: 0,
1327
+ lastConnectedAt: null,
1328
+ lastDisconnectedAt: null,
1329
+ hasEverConnected: false
1330
+ };
1331
+ var useConnectionStore = create9()((set) => ({
1332
+ ...createClientActions(set),
1333
+ ...initialConnectionState,
1334
+ markConnecting: (attempt = 0) => set({
1335
+ status: "connecting",
1336
+ reconnectAttempt: attempt
1337
+ }),
1338
+ markConnected: () => set({
1339
+ status: "connected",
1340
+ reconnectAttempt: 0,
1341
+ lastConnectedAt: Date.now(),
1342
+ hasEverConnected: true
1343
+ }),
1344
+ markDisconnected: () => set({
1345
+ status: "disconnected",
1346
+ reconnectAttempt: 0,
1347
+ lastDisconnectedAt: Date.now()
1348
+ }),
1349
+ reset: () => set(initialConnectionState)
1350
+ }));
1351
+
1352
+ // src/react/stores/runtime-store.ts
1353
+ import { create as create10 } from "zustand";
1354
+ var useRuntimeStore = create10()((set) => ({
1355
+ ...createClientActions(set),
1356
+ events: {},
1357
+ addEvent: (sessionId, event) => set((state) => {
1358
+ const current = state.events[sessionId] ?? [];
1359
+ const nextEvent = {
1360
+ ...event,
1361
+ id: `${Date.now()}-${current.length}`,
1362
+ sessionId,
1363
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1364
+ };
1365
+ return {
1366
+ events: {
1367
+ ...state.events,
1368
+ [sessionId]: [...current.slice(-59), nextEvent]
1369
+ }
1370
+ };
1371
+ }),
1372
+ clearSession: (sessionId) => set((state) => ({
1373
+ events: { ...state.events, [sessionId]: [] }
1374
+ }))
1375
+ }));
1376
+
1377
+ // src/react/stores/gis-store.ts
1378
+ import { create as create11 } from "zustand";
1379
+ var EMPTY_GOALS = [];
1380
+ var EMPTY_RESOURCES = [];
1381
+ var EMPTY_TARGETS = [];
1382
+ var EMPTY_COMMANDS = [];
1383
+ function newCommandId() {
1384
+ if (typeof globalThis !== "undefined" && "crypto" in globalThis) {
1385
+ return globalThis.crypto?.randomUUID?.() ?? `gis-map-${Date.now()}-${Math.random()}`;
1386
+ }
1387
+ return `gis-map-${Date.now()}-${Math.random()}`;
1388
+ }
1389
+ var useGisStore = create11()((set, get) => ({
1390
+ ...createClientActions(set),
1391
+ goalsBySession: {},
1392
+ resourcesBySession: {},
1393
+ targetsBySession: {},
1394
+ pendingMapCommandsBySession: {},
1395
+ setGoals: (sessionId, goals) => {
1396
+ set((state) => ({
1397
+ goalsBySession: { ...state.goalsBySession, [sessionId]: goals }
1398
+ }));
1399
+ },
1400
+ setResources: (sessionId, resources) => {
1401
+ set((state) => ({
1402
+ resourcesBySession: { ...state.resourcesBySession, [sessionId]: resources }
1403
+ }));
1404
+ },
1405
+ setTargets: (sessionId, targets) => {
1406
+ set((state) => ({
1407
+ targetsBySession: { ...state.targetsBySession, [sessionId]: targets }
1408
+ }));
1409
+ },
1410
+ pushMapCommand: (sessionId, command) => {
1411
+ set((state) => ({
1412
+ pendingMapCommandsBySession: {
1413
+ ...state.pendingMapCommandsBySession,
1414
+ [sessionId]: [
1415
+ ...state.pendingMapCommandsBySession[sessionId] ?? EMPTY_COMMANDS,
1416
+ {
1417
+ ...command,
1418
+ id: newCommandId(),
1419
+ createdAt: Date.now()
1420
+ }
1421
+ ]
1422
+ }
1423
+ }));
1424
+ },
1425
+ consumeMapCommand: (sessionId, commandId) => {
1426
+ set((state) => ({
1427
+ pendingMapCommandsBySession: {
1428
+ ...state.pendingMapCommandsBySession,
1429
+ [sessionId]: (state.pendingMapCommandsBySession[sessionId] ?? EMPTY_COMMANDS).filter(
1430
+ (command) => command.id !== commandId
1431
+ )
1432
+ }
1433
+ }));
1434
+ },
1435
+ getGoals: (sessionId) => get().goalsBySession[sessionId] ?? EMPTY_GOALS,
1436
+ getResources: (sessionId) => get().resourcesBySession[sessionId] ?? EMPTY_RESOURCES,
1437
+ getTargets: (sessionId) => get().targetsBySession[sessionId] ?? EMPTY_TARGETS,
1438
+ getPendingMapCommands: (sessionId) => get().pendingMapCommandsBySession[sessionId] ?? EMPTY_COMMANDS
1439
+ }));
1440
+
1441
+ // src/react/stores/answer-callback-store.ts
1442
+ import { create as create12 } from "zustand";
1443
+ var useAnswerCallbackStore = create12()((set) => ({
1444
+ ...createClientActions(set),
1445
+ callbacks: {},
1446
+ setAnswerCallback: (sessionId, callback) => {
1447
+ set((state) => ({
1448
+ callbacks: {
1449
+ ...state.callbacks,
1450
+ [sessionId]: callback
1451
+ }
1452
+ }));
1453
+ }
1454
+ }));
1455
+
1456
+ // src/react/stores/runtime-features-store.ts
1457
+ import { create as create13 } from "zustand";
1458
+ var useRuntimeFeaturesStore = create13((set) => ({
1459
+ ...createClientActions(set),
1460
+ asrEnabled: false,
1461
+ asrProvider: "volcengine",
1462
+ publicSharingEnabled: false,
1463
+ memoryEnabled: false,
1464
+ setFeatures: (features) => set((prev) => ({
1465
+ asrEnabled: features.asrEnabled ?? prev.asrEnabled,
1466
+ asrProvider: features.asrProvider ?? prev.asrProvider,
1467
+ publicSharingEnabled: features.publicSharingEnabled ?? prev.publicSharingEnabled,
1468
+ memoryEnabled: features.memoryEnabled ?? prev.memoryEnabled
1469
+ }))
1470
+ }));
1471
+
1472
+ // src/react/bootstrap.ts
1473
+ var bootstrappedClient = null;
1474
+ function getBootstrappedClient() {
1475
+ if (!bootstrappedClient) {
1476
+ throw new Error("bootstrapBladeClient() must be called before any SDK usage");
1477
+ }
1478
+ return bootstrappedClient;
1479
+ }
1480
+
1481
+ // src/react/api/client.ts
1482
+ function getAuthedUrl(path) {
1483
+ return getClient().buildAuthedUrl(path);
1484
+ }
1485
+ async function apiFetchText(path, init) {
1486
+ return getClient().textFromInit(path, init);
1487
+ }
1488
+ async function apiFetchResponse(path, init) {
1489
+ return getClient().responseFromInit(path, init);
1490
+ }
1491
+ function getClient() {
1492
+ return getBootstrappedClient();
1493
+ }
1494
+
1495
+ // src/react/api/sessions.ts
1496
+ var r2 = () => getClient().sessions;
1497
+ var listSessions = (...args) => r2().listSessions(...args);
1498
+ var createSession = (...args) => r2().createSession(...args);
1499
+ var getSession = (...args) => r2().getSession(...args);
1500
+ var pinSession = (...args) => r2().pinSession(...args);
1501
+ var updateSharing = (...args) => r2().updateSharing(...args);
1502
+ var getSessionTasks = (...args) => r2().getSessionTasks(...args);
1503
+ var getSessionTurns = (...args) => r2().getSessionTurns(...args);
1504
+ var deleteSession = (...args) => r2().deleteSession(...args);
1505
+ var listDir = (...args) => r2().listDir(...args);
1506
+ var uploadFiles = (...args) => r2().uploadFiles(...args);
1507
+ var deleteFile = (...args) => r2().deleteFile(...args);
1508
+ var renameFile = (...args) => r2().renameFile(...args);
1509
+ var copyFile = (...args) => r2().copyFile(...args);
1510
+ var getDownloadDirUrl = (...args) => r2().getDownloadDirUrl(...args);
1511
+
1512
+ // src/react/lib/utils.ts
1513
+ import { clsx } from "clsx";
1514
+ import { twMerge } from "tailwind-merge";
1515
+ function cn(...inputs) {
1516
+ return twMerge(clsx(inputs));
1517
+ }
1518
+
1519
+ // src/react/components/workspace/FileTree.tsx
1520
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
1521
+ import {
1522
+ ChevronRight,
1523
+ Copy,
1524
+ Download,
1525
+ Link2,
1526
+ Loader2,
1527
+ Pencil,
1528
+ Trash2
1529
+ } from "lucide-react";
1530
+ import { useEffect, useEffectEvent, useRef, useState as useState2 } from "react";
1531
+
1532
+ // src/react/lib/session-file-preview.ts
1533
+ var IMAGE_EXTS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "svg", "webp", "ico", "bmp"]);
1534
+ var DOCX_EXTS = /* @__PURE__ */ new Set(["docx", "doc"]);
1535
+ var EXCEL_EXTS = /* @__PURE__ */ new Set(["xlsx", "xls", "xlsm", "xlsb"]);
1536
+ var PPT_EXTS = /* @__PURE__ */ new Set(["pptx", "ppt"]);
1537
+ var CSV_EXTS = /* @__PURE__ */ new Set(["csv"]);
1538
+ var MARKDOWN_EXTS = /* @__PURE__ */ new Set(["md", "markdown"]);
1539
+ var HTML_EXTS = /* @__PURE__ */ new Set(["html", "htm"]);
1540
+ function getExt(fileName) {
1541
+ return fileName.split(".").pop()?.toLowerCase() ?? "";
1542
+ }
1543
+ function getDisplayFileName(filePath, fileName) {
1544
+ return fileName?.trim() || filePath.split("/").pop() || filePath;
1545
+ }
1546
+ function getSessionFilePath(sessionId, filePath) {
1547
+ return `/api/sessions/${sessionId}/files/${encodeURIComponent(filePath)}`;
1548
+ }
1549
+ function getSessionFileDownloadUrl(sessionId, filePath) {
1550
+ return getAuthedUrl(getSessionFilePath(sessionId, filePath));
1551
+ }
1552
+ function isTextualContentType(contentType) {
1553
+ return contentType.startsWith("text/") || contentType.includes("json") || contentType.includes("xml") || contentType.includes("yaml") || contentType.includes("javascript");
1554
+ }
1555
+ function buildSessionBinaryPreviewTarget(sessionId, filePath, fileName) {
1556
+ const resolvedFileName = getDisplayFileName(filePath, fileName);
1557
+ const ext = getExt(resolvedFileName);
1558
+ const filePathUrl = getSessionFilePath(sessionId, filePath);
1559
+ const downloadUrl = getSessionFileDownloadUrl(sessionId, filePath);
1560
+ if (IMAGE_EXTS.has(ext)) {
1561
+ return { type: "image", content: downloadUrl, title: resolvedFileName, key: filePath };
1562
+ }
1563
+ if (ext === "pdf") {
1564
+ return { type: "pdf", content: downloadUrl, title: resolvedFileName, key: filePath };
1565
+ }
1566
+ if (DOCX_EXTS.has(ext)) {
1567
+ return { type: "docx", content: downloadUrl, title: resolvedFileName, key: filePath };
1568
+ }
1569
+ if (EXCEL_EXTS.has(ext)) {
1570
+ return { type: "excel", content: downloadUrl, title: resolvedFileName, key: filePath };
1571
+ }
1572
+ if (PPT_EXTS.has(ext)) {
1573
+ return {
1574
+ type: "ppt",
1575
+ content: filePathUrl,
1576
+ sourceUrl: downloadUrl,
1577
+ title: resolvedFileName,
1578
+ key: filePath
1579
+ };
1580
+ }
1581
+ return null;
1582
+ }
1583
+ async function resolveSessionFilePreviewTarget(sessionId, filePath, fileName) {
1584
+ const resolvedFileName = getDisplayFileName(filePath, fileName);
1585
+ const binaryTarget = buildSessionBinaryPreviewTarget(sessionId, filePath, resolvedFileName);
1586
+ if (binaryTarget) {
1587
+ return binaryTarget;
1588
+ }
1589
+ const downloadUrl = getSessionFileDownloadUrl(sessionId, filePath);
1590
+ const res = await apiFetchResponse(getSessionFilePath(sessionId, filePath));
1591
+ const contentType = (res.headers.get("content-type") ?? "").toLowerCase();
1592
+ if (contentType.startsWith("video/")) {
1593
+ void res.body?.cancel();
1594
+ return { type: "video", content: downloadUrl, title: resolvedFileName, key: filePath };
1595
+ }
1596
+ if (contentType.startsWith("audio/")) {
1597
+ void res.body?.cancel();
1598
+ return { type: "audio", content: downloadUrl, title: resolvedFileName, key: filePath };
1599
+ }
1600
+ if (!isTextualContentType(contentType)) {
1601
+ void res.body?.cancel();
1602
+ return { type: "download", content: downloadUrl, title: resolvedFileName, key: filePath };
1603
+ }
1604
+ const content = await res.text();
1605
+ const ext = getExt(resolvedFileName);
1606
+ const type3 = MARKDOWN_EXTS.has(ext) ? "markdown" : CSV_EXTS.has(ext) ? "csv" : HTML_EXTS.has(ext) ? "html" : "file";
1607
+ return {
1608
+ type: type3,
1609
+ content,
1610
+ title: resolvedFileName,
1611
+ key: filePath
1612
+ };
1613
+ }
1614
+
1615
+ // src/react/lib/open-session-file.ts
1616
+ async function openSessionFileInPreview(sessionId, filePath, fileName, pushArtifact, shouldOpen = () => true) {
1617
+ try {
1618
+ const target = await resolveSessionFilePreviewTarget(sessionId, filePath, fileName);
1619
+ if (!shouldOpen()) {
1620
+ return;
1621
+ }
1622
+ pushArtifact(target);
1623
+ } catch {
1624
+ }
1625
+ }
1626
+
1627
+ // src/react/components/ui/collapsible.tsx
1628
+ import { Collapsible as CollapsiblePrimitive } from "radix-ui";
1629
+ import { jsx } from "react/jsx-runtime";
1630
+ function Collapsible({
1631
+ ...props
1632
+ }) {
1633
+ return /* @__PURE__ */ jsx(CollapsiblePrimitive.Root, { "data-slot": "collapsible", ...props });
1634
+ }
1635
+ function CollapsibleTrigger({
1636
+ ...props
1637
+ }) {
1638
+ return /* @__PURE__ */ jsx(
1639
+ CollapsiblePrimitive.CollapsibleTrigger,
1640
+ {
1641
+ "data-slot": "collapsible-trigger",
1642
+ ...props
1643
+ }
1644
+ );
1645
+ }
1646
+ function CollapsibleContent({
1647
+ ...props
1648
+ }) {
1649
+ return /* @__PURE__ */ jsx(
1650
+ CollapsiblePrimitive.CollapsibleContent,
1651
+ {
1652
+ "data-slot": "collapsible-content",
1653
+ ...props
1654
+ }
1655
+ );
1656
+ }
1657
+
1658
+ // src/react/components/ai-elements/file-tree.tsx
1659
+ import {
1660
+ ChevronRightIcon,
1661
+ FileIcon,
1662
+ FolderIcon,
1663
+ FolderOpenIcon
1664
+ } from "lucide-react";
1665
+ import {
1666
+ createContext,
1667
+ useCallback,
1668
+ useContext,
1669
+ useMemo,
1670
+ useState
1671
+ } from "react";
1672
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
1673
+ var noop = () => {
1674
+ };
1675
+ var FileTreeContext = createContext({
1676
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-new-builtin)
1677
+ expandedPaths: /* @__PURE__ */ new Set(),
1678
+ togglePath: noop
1679
+ });
1680
+ var FileTree = ({
1681
+ expanded: controlledExpanded,
1682
+ defaultExpanded = /* @__PURE__ */ new Set(),
1683
+ selectedPath,
1684
+ onSelect,
1685
+ onExpandedChange,
1686
+ className,
1687
+ children,
1688
+ ...props
1689
+ }) => {
1690
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
1691
+ const expandedPaths = controlledExpanded ?? internalExpanded;
1692
+ const togglePath = useCallback(
1693
+ (path) => {
1694
+ const newExpanded = new Set(expandedPaths);
1695
+ if (newExpanded.has(path)) {
1696
+ newExpanded.delete(path);
1697
+ } else {
1698
+ newExpanded.add(path);
1699
+ }
1700
+ setInternalExpanded(newExpanded);
1701
+ onExpandedChange?.(newExpanded);
1702
+ },
1703
+ [expandedPaths, onExpandedChange]
1704
+ );
1705
+ const contextValue = useMemo(
1706
+ () => ({ expandedPaths, onSelect, selectedPath, togglePath }),
1707
+ [expandedPaths, onSelect, selectedPath, togglePath]
1708
+ );
1709
+ return /* @__PURE__ */ jsx2(FileTreeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2(
1710
+ "div",
1711
+ {
1712
+ className: cn(
1713
+ "rounded-lg border bg-background font-mono text-sm",
1714
+ className
1715
+ ),
1716
+ role: "tree",
1717
+ ...props,
1718
+ children: /* @__PURE__ */ jsx2("div", { className: "p-2", children })
1719
+ }
1720
+ ) });
1721
+ };
1722
+ var FileTreeIcon = ({
1723
+ className,
1724
+ children,
1725
+ ...props
1726
+ }) => /* @__PURE__ */ jsx2("span", { className: cn("shrink-0", className), ...props, children });
1727
+ var FileTreeName = ({
1728
+ className,
1729
+ children,
1730
+ ...props
1731
+ }) => /* @__PURE__ */ jsx2("span", { className: cn("truncate", className), ...props, children });
1732
+ var FileTreeFolderContext = createContext({
1733
+ isExpanded: false,
1734
+ name: "",
1735
+ path: ""
1736
+ });
1737
+ var FileTreeFileContext = createContext({
1738
+ name: "",
1739
+ path: ""
1740
+ });
1741
+ var stopPropagation = (e) => e.stopPropagation();
1742
+ var FileTreeActions = ({
1743
+ className,
1744
+ children,
1745
+ ...props
1746
+ }) => /* @__PURE__ */ jsx2(
1747
+ "div",
1748
+ {
1749
+ className: cn("ml-auto flex items-center gap-1", className),
1750
+ onClick: stopPropagation,
1751
+ onKeyDown: stopPropagation,
1752
+ ...props,
1753
+ children
1754
+ }
1755
+ );
1756
+
1757
+ // src/react/components/workspace/FileTree.tsx
1758
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1759
+ var ROOT_PATH = ".";
1760
+ var TREE_ROW_CLASS = "group flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50";
1761
+ var FILE_ICON_GROUPS = {
1762
+ csv: "office",
1763
+ doc: "office",
1764
+ docx: "office",
1765
+ ppt: "office",
1766
+ pptx: "office",
1767
+ rtf: "office",
1768
+ tsv: "office",
1769
+ xls: "office",
1770
+ xlsx: "office",
1771
+ odg: "open-document",
1772
+ odp: "open-document",
1773
+ ods: "open-document",
1774
+ odt: "open-document",
1775
+ epub: "document",
1776
+ key: "document",
1777
+ log: "document",
1778
+ markdown: "document",
1779
+ md: "document",
1780
+ numbers: "document",
1781
+ pages: "document",
1782
+ pdf: "document",
1783
+ tex: "document",
1784
+ txt: "document",
1785
+ ai: "image",
1786
+ avif: "image",
1787
+ bmp: "image",
1788
+ gif: "image",
1789
+ heic: "image",
1790
+ heif: "image",
1791
+ ico: "image",
1792
+ jpeg: "image",
1793
+ jpg: "image",
1794
+ png: "image",
1795
+ psd: "image",
1796
+ svg: "image",
1797
+ tif: "image",
1798
+ tiff: "image",
1799
+ webp: "image",
1800
+ avi: "video",
1801
+ flv: "video",
1802
+ m4v: "video",
1803
+ mkv: "video",
1804
+ mov: "video",
1805
+ mp4: "video",
1806
+ webm: "video",
1807
+ wmv: "video",
1808
+ aac: "audio",
1809
+ aiff: "audio",
1810
+ flac: "audio",
1811
+ m4a: "audio",
1812
+ mid: "audio",
1813
+ mp3: "audio",
1814
+ ogg: "audio",
1815
+ wav: "audio",
1816
+ "7z": "archive",
1817
+ bz2: "archive",
1818
+ dmg: "archive",
1819
+ gz: "archive",
1820
+ iso: "archive",
1821
+ pkg: "archive",
1822
+ rar: "archive",
1823
+ tar: "archive",
1824
+ tgz: "archive",
1825
+ xz: "archive",
1826
+ zip: "archive",
1827
+ c: "code",
1828
+ cpp: "code",
1829
+ cs: "code",
1830
+ css: "code",
1831
+ go: "code",
1832
+ h: "code",
1833
+ hpp: "code",
1834
+ html: "code",
1835
+ java: "code",
1836
+ js: "code",
1837
+ json: "code",
1838
+ jsx: "code",
1839
+ kt: "code",
1840
+ lock: "code",
1841
+ php: "code",
1842
+ py: "code",
1843
+ rb: "code",
1844
+ rs: "code",
1845
+ scss: "code",
1846
+ sh: "code",
1847
+ sql: "code",
1848
+ svelte: "code",
1849
+ swift: "code",
1850
+ toml: "code",
1851
+ ts: "code",
1852
+ tsx: "code",
1853
+ vue: "code",
1854
+ xml: "code",
1855
+ yaml: "code",
1856
+ yml: "code",
1857
+ zsh: "code",
1858
+ db: "data",
1859
+ jsonl: "data",
1860
+ ndjson: "data",
1861
+ parquet: "data",
1862
+ plist: "data",
1863
+ sqlite: "data",
1864
+ otf: "font",
1865
+ ttf: "font",
1866
+ woff: "font",
1867
+ woff2: "font",
1868
+ blend: "design",
1869
+ fig: "design",
1870
+ sketch: "design",
1871
+ xd: "design",
1872
+ conf: "config",
1873
+ env: "config",
1874
+ ini: "config"
1875
+ };
1876
+ function FileTree2({
1877
+ sessionId,
1878
+ checkActiveSession = true,
1879
+ enableEditor = false,
1880
+ onExpandedDirsChange,
1881
+ rootPath = ROOT_PATH,
1882
+ readOnly = false,
1883
+ onShareFile,
1884
+ allowDelete = false,
1885
+ onOpenFile
1886
+ }) {
1887
+ const pushArtifact = useUiStore((state) => state.pushArtifact);
1888
+ const [expandedDirs, setExpandedDirs] = useState2(/* @__PURE__ */ new Set([rootPath]));
1889
+ const [selectedPath, setSelectedPath] = useState2();
1890
+ const [openingFilePath, setOpeningFilePath] = useState2(null);
1891
+ const isStreaming = useChatStore((s) => sessionId ? s.isStreaming[sessionId] ?? false : false);
1892
+ const queryClient = useQueryClient();
1893
+ const prevStreamingRef = useRef(isStreaming);
1894
+ const applyExpandedDirs = useEffectEvent((dirs) => {
1895
+ setExpandedDirs(dirs);
1896
+ onExpandedDirsChange?.(dirs);
1897
+ });
1898
+ useEffect(() => {
1899
+ if (prevStreamingRef.current && !isStreaming && sessionId) {
1900
+ queryClient.invalidateQueries({ queryKey: ["file-tree", sessionId] });
1901
+ }
1902
+ prevStreamingRef.current = isStreaming;
1903
+ }, [isStreaming, sessionId, queryClient]);
1904
+ const onExpandedDirsChangeStable = useEffectEvent((dirs) => {
1905
+ onExpandedDirsChange?.(dirs);
1906
+ });
1907
+ useEffect(() => {
1908
+ onExpandedDirsChangeStable(expandedDirs);
1909
+ }, []);
1910
+ const handleExpandedChange = (dirs) => {
1911
+ applyExpandedDirs(dirs);
1912
+ };
1913
+ const toggleExpandedDir = (path) => {
1914
+ const next = new Set(expandedDirs);
1915
+ if (next.has(path)) {
1916
+ next.delete(path);
1917
+ } else {
1918
+ next.add(path);
1919
+ }
1920
+ handleExpandedChange(next);
1921
+ };
1922
+ const handleTreePathChange = useEffectEvent((previousPath, nextPath) => {
1923
+ setSelectedPath((current) => {
1924
+ if (!current) {
1925
+ return current;
1926
+ }
1927
+ if (current === previousPath || current.startsWith(`${previousPath}/`)) {
1928
+ if (!nextPath) {
1929
+ return void 0;
1930
+ }
1931
+ return current === previousPath ? nextPath : `${nextPath}${current.slice(previousPath.length)}`;
1932
+ }
1933
+ return current;
1934
+ });
1935
+ const nextExpanded = /* @__PURE__ */ new Set();
1936
+ for (const path of expandedDirs) {
1937
+ if (path === previousPath || path.startsWith(`${previousPath}/`)) {
1938
+ if (nextPath) {
1939
+ nextExpanded.add(
1940
+ path === previousPath ? nextPath : `${nextPath}${path.slice(previousPath.length)}`
1941
+ );
1942
+ }
1943
+ continue;
1944
+ }
1945
+ nextExpanded.add(path);
1946
+ }
1947
+ handleExpandedChange(nextExpanded);
1948
+ });
1949
+ const {
1950
+ data: rootEntries,
1951
+ isLoading: rootLoading,
1952
+ error: rootError
1953
+ } = useQuery({
1954
+ queryKey: ["file-tree", sessionId, rootPath],
1955
+ queryFn: () => listDir(sessionId, rootPath),
1956
+ enabled: !!sessionId,
1957
+ refetchInterval: isStreaming ? 6e4 : false
1958
+ });
1959
+ const handleOpenFile = useEffectEvent(async (path, name) => {
1960
+ if (!sessionId) return;
1961
+ const currentSessionId = sessionId;
1962
+ setOpeningFilePath(path);
1963
+ setSelectedPath(path);
1964
+ try {
1965
+ if (onOpenFile) {
1966
+ await onOpenFile(path, name);
1967
+ } else if (enableEditor) {
1968
+ const content = await apiFetchText(
1969
+ `/api/sessions/${encodeURIComponent(sessionId)}/files/${encodeURIComponent(path)}`
1970
+ );
1971
+ void content;
1972
+ alert("enableEditor \u9700\u5728\u5BBF\u4E3B\u5E94\u7528\u4E2D\u63D0\u4F9B onOpenFile");
1973
+ } else {
1974
+ await openSessionFileInPreview(sessionId, path, name, pushArtifact, () => {
1975
+ return checkActiveSession ? useSessionStore.getState().activeSessionId === currentSessionId : true;
1976
+ });
1977
+ }
1978
+ } finally {
1979
+ setOpeningFilePath((current) => current === path ? null : current);
1980
+ }
1981
+ });
1982
+ const handleSelect = (path) => {
1983
+ setSelectedPath(path);
1984
+ };
1985
+ if (!sessionId) {
1986
+ return /* @__PURE__ */ jsx3(EmptyState, { title: "\u6682\u65E0\u6D3B\u8DC3\u4F1A\u8BDD", description: "\u9009\u62E9\u6216\u521B\u5EFA\u4F1A\u8BDD\u540E\u67E5\u770B\u5DE5\u4F5C\u533A\u6587\u4EF6" });
1987
+ }
1988
+ if (rootLoading) {
1989
+ return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 px-3 py-4 text-sm text-[hsl(var(--muted-foreground))]", children: [
1990
+ /* @__PURE__ */ jsx3(Loader2, { size: 14, className: "animate-spin" }),
1991
+ /* @__PURE__ */ jsx3("span", { children: "\u52A0\u8F7D\u4E2D..." })
1992
+ ] });
1993
+ }
1994
+ if (rootError) {
1995
+ return /* @__PURE__ */ jsx3(
1996
+ EmptyState,
1997
+ {
1998
+ title: "\u6587\u4EF6\u6811\u52A0\u8F7D\u5931\u8D25",
1999
+ description: rootError instanceof Error ? rootError.message : "\u52A0\u8F7D\u5931\u8D25"
2000
+ }
2001
+ );
2002
+ }
2003
+ if (!rootEntries || rootEntries.length === 0) {
2004
+ return /* @__PURE__ */ jsx3(EmptyState, { title: "\u5F53\u524D\u5DE5\u4F5C\u533A\u4E3A\u7A7A", description: "\u4F1A\u8BDD\u4EA7\u751F\u6587\u4EF6\u540E\u4F1A\u663E\u793A\u5728\u8FD9\u91CC" });
2005
+ }
2006
+ return /* @__PURE__ */ jsx3(
2007
+ FileTree,
2008
+ {
2009
+ expanded: expandedDirs,
2010
+ onExpandedChange: handleExpandedChange,
2011
+ selectedPath,
2012
+ onSelect: handleSelect,
2013
+ className: "border-none bg-transparent",
2014
+ children: rootEntries.map(
2015
+ (entry) => entry.is_dir ? /* @__PURE__ */ jsx3(
2016
+ FolderNode,
2017
+ {
2018
+ entry,
2019
+ expandedDirs,
2020
+ isStreaming,
2021
+ onOpenFile: handleOpenFile,
2022
+ onSelectPath: handleSelect,
2023
+ onToggleDir: toggleExpandedDir,
2024
+ onTreePathChange: handleTreePathChange,
2025
+ openingFilePath,
2026
+ selectedPath,
2027
+ sessionId,
2028
+ readOnly,
2029
+ onShareFile,
2030
+ allowDelete
2031
+ },
2032
+ entry.path
2033
+ ) : /* @__PURE__ */ jsx3(
2034
+ FileNode,
2035
+ {
2036
+ entry,
2037
+ onOpenFile: handleOpenFile,
2038
+ onSelectPath: handleSelect,
2039
+ onTreePathChange: handleTreePathChange,
2040
+ openingFilePath,
2041
+ selectedPath,
2042
+ sessionId,
2043
+ readOnly,
2044
+ onShareFile,
2045
+ allowDelete
2046
+ },
2047
+ entry.path
2048
+ )
2049
+ )
2050
+ }
2051
+ );
2052
+ }
2053
+ function FolderNode({
2054
+ entry,
2055
+ expandedDirs,
2056
+ isStreaming,
2057
+ openingFilePath,
2058
+ onOpenFile,
2059
+ onSelectPath,
2060
+ onToggleDir,
2061
+ onTreePathChange,
2062
+ selectedPath,
2063
+ sessionId,
2064
+ readOnly,
2065
+ onShareFile,
2066
+ allowDelete
2067
+ }) {
2068
+ const queryClient = useQueryClient();
2069
+ const isExpanded = expandedDirs.has(entry.path);
2070
+ const isSelected = selectedPath === entry.path;
2071
+ const renameInputRef = useRef(null);
2072
+ const [isRenaming, setIsRenaming] = useState2(false);
2073
+ const [renamingValue, setRenamingValue] = useState2(entry.name);
2074
+ const [activeAction, setActiveAction] = useState2(null);
2075
+ const downloadUrl = getDownloadDirUrl(sessionId, entry.path);
2076
+ useEffect(() => {
2077
+ if (!isRenaming) setRenamingValue(entry.name);
2078
+ }, [entry.name, isRenaming]);
2079
+ useEffect(() => {
2080
+ if (!isRenaming) return;
2081
+ renameInputRef.current?.focus();
2082
+ renameInputRef.current?.select();
2083
+ }, [isRenaming]);
2084
+ const finishRename = useEffectEvent(async () => {
2085
+ const nextName = renamingValue.trim();
2086
+ if (!nextName || nextName === entry.name) {
2087
+ setIsRenaming(false);
2088
+ setRenamingValue(entry.name);
2089
+ return;
2090
+ }
2091
+ setActiveAction("rename");
2092
+ try {
2093
+ const result = await renameFile(sessionId, entry.path, nextName);
2094
+ onTreePathChange(entry.path, result.path);
2095
+ setIsRenaming(false);
2096
+ await queryClient.invalidateQueries({ queryKey: ["file-tree", sessionId] });
2097
+ } catch (error) {
2098
+ alert(getErrorMessage(error, "\u91CD\u547D\u540D\u5931\u8D25"));
2099
+ renameInputRef.current?.focus();
2100
+ renameInputRef.current?.select();
2101
+ } finally {
2102
+ setActiveAction(null);
2103
+ }
2104
+ });
2105
+ const cancelRename = () => {
2106
+ setIsRenaming(false);
2107
+ setRenamingValue(entry.name);
2108
+ };
2109
+ const handleCopy = async () => {
2110
+ setActiveAction("copy");
2111
+ try {
2112
+ await copyFile(sessionId, entry.path);
2113
+ await queryClient.invalidateQueries({ queryKey: ["file-tree", sessionId] });
2114
+ } catch (error) {
2115
+ alert(getErrorMessage(error, "\u590D\u5236\u76EE\u5F55\u5931\u8D25"));
2116
+ } finally {
2117
+ setActiveAction(null);
2118
+ }
2119
+ };
2120
+ const handleDelete = async () => {
2121
+ if (!window.confirm(`\u786E\u8BA4\u5220\u9664\u76EE\u5F55"${entry.name}"\u5417\uFF1F`)) {
2122
+ return;
2123
+ }
2124
+ setActiveAction("delete");
2125
+ try {
2126
+ await deleteFile(sessionId, entry.path);
2127
+ onTreePathChange(entry.path);
2128
+ await queryClient.invalidateQueries({ queryKey: ["file-tree", sessionId] });
2129
+ } catch (error) {
2130
+ alert(getErrorMessage(error, "\u5220\u9664\u76EE\u5F55\u5931\u8D25"));
2131
+ } finally {
2132
+ setActiveAction(null);
2133
+ }
2134
+ };
2135
+ return /* @__PURE__ */ jsx3(Collapsible, { open: isExpanded, onOpenChange: () => onToggleDir(entry.path), children: /* @__PURE__ */ jsxs2("div", { children: [
2136
+ /* @__PURE__ */ jsxs2("div", { className: cn(TREE_ROW_CLASS, isSelected && "bg-muted"), children: [
2137
+ /* @__PURE__ */ jsx3(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsx3(
2138
+ "button",
2139
+ {
2140
+ type: "button",
2141
+ className: "flex shrink-0 cursor-pointer items-center rounded border-none bg-transparent p-0",
2142
+ title: isExpanded ? "\u6536\u8D77\u76EE\u5F55" : "\u5C55\u5F00\u76EE\u5F55",
2143
+ children: /* @__PURE__ */ jsx3(
2144
+ ChevronRight,
2145
+ {
2146
+ className: cn(
2147
+ "size-4 shrink-0 text-[hsl(var(--muted-foreground))] transition-transform",
2148
+ isExpanded && "rotate-90"
2149
+ )
2150
+ }
2151
+ )
2152
+ }
2153
+ ) }),
2154
+ isRenaming ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
2155
+ /* @__PURE__ */ jsx3(FileTreeIcon, { children: getFolderIcon() }),
2156
+ /* @__PURE__ */ jsx3(
2157
+ "input",
2158
+ {
2159
+ ref: renameInputRef,
2160
+ value: renamingValue,
2161
+ className: "h-6 min-w-0 flex-1 rounded border border-[hsl(var(--border))] bg-background px-2 text-xs outline-none ring-offset-background focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))]",
2162
+ disabled: activeAction === "rename",
2163
+ onBlur: () => void finishRename(),
2164
+ onChange: (event) => setRenamingValue(event.target.value),
2165
+ onClick: (event) => event.stopPropagation(),
2166
+ onKeyDown: (event) => {
2167
+ event.stopPropagation();
2168
+ if (event.key === "Enter") {
2169
+ event.preventDefault();
2170
+ void finishRename();
2171
+ } else if (event.key === "Escape") {
2172
+ event.preventDefault();
2173
+ cancelRename();
2174
+ }
2175
+ }
2176
+ }
2177
+ )
2178
+ ] }) : /* @__PURE__ */ jsxs2(
2179
+ "button",
2180
+ {
2181
+ type: "button",
2182
+ className: "flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left",
2183
+ onClick: () => {
2184
+ onToggleDir(entry.path);
2185
+ onSelectPath(entry.path);
2186
+ },
2187
+ children: [
2188
+ /* @__PURE__ */ jsx3(FileTreeIcon, { children: getFolderIcon() }),
2189
+ /* @__PURE__ */ jsx3(FileTreeName, { className: "min-w-0 flex-1", children: entry.name })
2190
+ ]
2191
+ }
2192
+ ),
2193
+ !readOnly ? /* @__PURE__ */ jsxs2(FileTreeActions, { className: "ml-auto hidden shrink-0 group-hover:flex", children: [
2194
+ /* @__PURE__ */ jsx3(
2195
+ TreeActionButton,
2196
+ {
2197
+ onClick: () => {
2198
+ const anchor = document.createElement("a");
2199
+ anchor.href = downloadUrl;
2200
+ anchor.download = `${entry.name}.zip`;
2201
+ anchor.click();
2202
+ },
2203
+ title: "\u4E0B\u8F7D\u76EE\u5F55",
2204
+ children: /* @__PURE__ */ jsx3(Download, { size: 12 })
2205
+ }
2206
+ ),
2207
+ /* @__PURE__ */ jsx3(
2208
+ TreeActionButton,
2209
+ {
2210
+ disabled: activeAction !== null,
2211
+ onClick: () => {
2212
+ onSelectPath(entry.path);
2213
+ setRenamingValue(entry.name);
2214
+ setIsRenaming(true);
2215
+ },
2216
+ title: "\u91CD\u547D\u540D",
2217
+ children: activeAction === "rename" ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx3(Pencil, { size: 12 })
2218
+ }
2219
+ ),
2220
+ /* @__PURE__ */ jsx3(
2221
+ TreeActionButton,
2222
+ {
2223
+ disabled: activeAction !== null,
2224
+ onClick: () => void handleCopy(),
2225
+ title: "\u590D\u5236\u76EE\u5F55",
2226
+ children: activeAction === "copy" ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx3(Copy, { size: 12 })
2227
+ }
2228
+ ),
2229
+ /* @__PURE__ */ jsx3(
2230
+ TreeActionButton,
2231
+ {
2232
+ disabled: activeAction !== null || !onShareFile,
2233
+ onClick: async () => {
2234
+ if (!onShareFile) return;
2235
+ setActiveAction("share");
2236
+ try {
2237
+ await onShareFile(entry.path, entry.name);
2238
+ await queryClient.invalidateQueries({ queryKey: ["file-tree", sessionId] });
2239
+ } catch (error) {
2240
+ alert(getErrorMessage(error, "\u5171\u4EAB\u5931\u8D25"));
2241
+ } finally {
2242
+ setActiveAction(null);
2243
+ }
2244
+ },
2245
+ title: "\u5171\u4EAB\u5230 .share",
2246
+ children: activeAction === "share" ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx3(Link2, { size: 12 })
2247
+ }
2248
+ ),
2249
+ /* @__PURE__ */ jsx3(
2250
+ TreeActionButton,
2251
+ {
2252
+ disabled: activeAction !== null,
2253
+ onClick: () => void handleDelete(),
2254
+ title: "\u5220\u9664\u76EE\u5F55",
2255
+ children: activeAction === "delete" ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx3(Trash2, { size: 12 })
2256
+ }
2257
+ )
2258
+ ] }) : allowDelete ? /* @__PURE__ */ jsx3(FileTreeActions, { className: "ml-auto hidden shrink-0 group-hover:flex", children: /* @__PURE__ */ jsx3(
2259
+ TreeActionButton,
2260
+ {
2261
+ disabled: activeAction !== null,
2262
+ onClick: () => void handleDelete(),
2263
+ title: "\u5220\u9664\u76EE\u5F55",
2264
+ children: activeAction === "delete" ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx3(Trash2, { size: 12 })
2265
+ }
2266
+ ) }) : null
2267
+ ] }),
2268
+ /* @__PURE__ */ jsx3(CollapsibleContent, { children: /* @__PURE__ */ jsx3("div", { className: "ml-4 border-l pl-2", children: /* @__PURE__ */ jsx3(
2269
+ LazyDirChildren,
2270
+ {
2271
+ dirPath: entry.path,
2272
+ expandedDirs,
2273
+ isStreaming,
2274
+ onOpenFile,
2275
+ onSelectPath,
2276
+ onToggleDir,
2277
+ onTreePathChange,
2278
+ openingFilePath,
2279
+ selectedPath,
2280
+ sessionId,
2281
+ readOnly,
2282
+ onShareFile,
2283
+ allowDelete
2284
+ }
2285
+ ) }) })
2286
+ ] }) });
2287
+ }
2288
+ function FileNode({
2289
+ entry,
2290
+ onOpenFile,
2291
+ onSelectPath,
2292
+ onTreePathChange,
2293
+ openingFilePath,
2294
+ selectedPath,
2295
+ sessionId,
2296
+ readOnly,
2297
+ onShareFile,
2298
+ allowDelete
2299
+ }) {
2300
+ const downloadUrl = getAuthedUrl(`/api/sessions/${sessionId}/files/${encodeURIComponent(entry.path)}`);
2301
+ const isOpening = openingFilePath === entry.path;
2302
+ const isSelected = selectedPath === entry.path;
2303
+ const queryClient = useQueryClient();
2304
+ const renameInputRef = useRef(null);
2305
+ const [isRenaming, setIsRenaming] = useState2(false);
2306
+ const [renamingValue, setRenamingValue] = useState2(entry.name);
2307
+ const [activeAction, setActiveAction] = useState2(null);
2308
+ useEffect(() => {
2309
+ if (!isRenaming) {
2310
+ setRenamingValue(entry.name);
2311
+ }
2312
+ }, [entry.name, isRenaming]);
2313
+ useEffect(() => {
2314
+ if (!isRenaming) {
2315
+ return;
2316
+ }
2317
+ renameInputRef.current?.focus();
2318
+ renameInputRef.current?.select();
2319
+ }, [isRenaming]);
2320
+ const finishRename = useEffectEvent(async () => {
2321
+ const nextName = renamingValue.trim();
2322
+ if (!nextName || nextName === entry.name) {
2323
+ setIsRenaming(false);
2324
+ setRenamingValue(entry.name);
2325
+ return;
2326
+ }
2327
+ setActiveAction("rename");
2328
+ try {
2329
+ const result = await renameFile(sessionId, entry.path, nextName);
2330
+ onTreePathChange(entry.path, result.path);
2331
+ setIsRenaming(false);
2332
+ await queryClient.invalidateQueries({ queryKey: ["file-tree", sessionId] });
2333
+ } catch (error) {
2334
+ alert(getErrorMessage(error, "\u91CD\u547D\u540D\u5931\u8D25"));
2335
+ renameInputRef.current?.focus();
2336
+ renameInputRef.current?.select();
2337
+ } finally {
2338
+ setActiveAction(null);
2339
+ }
2340
+ });
2341
+ const cancelRename = () => {
2342
+ setIsRenaming(false);
2343
+ setRenamingValue(entry.name);
2344
+ };
2345
+ const handleCopy = async () => {
2346
+ setActiveAction("copy");
2347
+ try {
2348
+ await copyFile(sessionId, entry.path);
2349
+ await queryClient.invalidateQueries({ queryKey: ["file-tree", sessionId] });
2350
+ } catch (error) {
2351
+ alert(getErrorMessage(error, "\u590D\u5236\u6587\u4EF6\u5931\u8D25"));
2352
+ } finally {
2353
+ setActiveAction(null);
2354
+ }
2355
+ };
2356
+ const handleDelete = async () => {
2357
+ if (!window.confirm(`\u786E\u8BA4\u5220\u9664"${entry.name}"\u5417\uFF1F`)) {
2358
+ return;
2359
+ }
2360
+ setActiveAction("delete");
2361
+ try {
2362
+ await deleteFile(sessionId, entry.path);
2363
+ onTreePathChange(entry.path);
2364
+ await queryClient.invalidateQueries({ queryKey: ["file-tree", sessionId] });
2365
+ } catch (error) {
2366
+ alert(getErrorMessage(error, "\u5220\u9664\u6587\u4EF6\u5931\u8D25"));
2367
+ } finally {
2368
+ setActiveAction(null);
2369
+ }
2370
+ };
2371
+ return /* @__PURE__ */ jsxs2("div", { className: cn(TREE_ROW_CLASS, isSelected && "bg-muted"), children: [
2372
+ /* @__PURE__ */ jsx3("span", { className: "size-4 shrink-0" }),
2373
+ /* @__PURE__ */ jsx3(FileTreeIcon, { children: getFileIcon(entry.name) }),
2374
+ isRenaming ? /* @__PURE__ */ jsx3(
2375
+ "input",
2376
+ {
2377
+ ref: renameInputRef,
2378
+ value: renamingValue,
2379
+ className: "h-6 min-w-0 flex-1 rounded border border-[hsl(var(--border))] bg-background px-2 text-xs outline-none ring-offset-background focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))]",
2380
+ disabled: activeAction === "rename",
2381
+ onBlur: () => void finishRename(),
2382
+ onChange: (event) => setRenamingValue(event.target.value),
2383
+ onClick: (event) => event.stopPropagation(),
2384
+ onKeyDown: (event) => {
2385
+ event.stopPropagation();
2386
+ if (event.key === "Enter") {
2387
+ event.preventDefault();
2388
+ void finishRename();
2389
+ } else if (event.key === "Escape") {
2390
+ event.preventDefault();
2391
+ cancelRename();
2392
+ }
2393
+ }
2394
+ }
2395
+ ) : /* @__PURE__ */ jsxs2(
2396
+ "button",
2397
+ {
2398
+ type: "button",
2399
+ className: "flex min-w-0 flex-1 cursor-pointer items-center border-none bg-transparent p-0 text-left",
2400
+ onClick: () => {
2401
+ onSelectPath(entry.path);
2402
+ void onOpenFile(entry.path, entry.name);
2403
+ },
2404
+ children: [
2405
+ /* @__PURE__ */ jsx3(FileTreeName, { className: "min-w-0 flex-1", children: entry.name }),
2406
+ /* @__PURE__ */ jsx3(FileTagList, { tags: entry.tags })
2407
+ ]
2408
+ }
2409
+ ),
2410
+ isOpening ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "ml-auto shrink-0 animate-spin text-[hsl(var(--muted-foreground))]" }) : !readOnly ? /* @__PURE__ */ jsxs2(FileTreeActions, { className: "ml-auto hidden shrink-0 group-hover:flex", children: [
2411
+ /* @__PURE__ */ jsx3(
2412
+ TreeActionButton,
2413
+ {
2414
+ onClick: () => {
2415
+ const anchor = document.createElement("a");
2416
+ anchor.href = downloadUrl;
2417
+ anchor.download = entry.name;
2418
+ anchor.click();
2419
+ },
2420
+ title: "\u4E0B\u8F7D\u6587\u4EF6",
2421
+ children: /* @__PURE__ */ jsx3(Download, { size: 12 })
2422
+ }
2423
+ ),
2424
+ /* @__PURE__ */ jsx3(
2425
+ TreeActionButton,
2426
+ {
2427
+ disabled: activeAction !== null,
2428
+ onClick: () => {
2429
+ onSelectPath(entry.path);
2430
+ setRenamingValue(entry.name);
2431
+ setIsRenaming(true);
2432
+ },
2433
+ title: "\u91CD\u547D\u540D",
2434
+ children: activeAction === "rename" ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx3(Pencil, { size: 12 })
2435
+ }
2436
+ ),
2437
+ /* @__PURE__ */ jsx3(
2438
+ TreeActionButton,
2439
+ {
2440
+ disabled: activeAction !== null,
2441
+ onClick: () => void handleCopy(),
2442
+ title: "\u590D\u5236\u6587\u4EF6",
2443
+ children: activeAction === "copy" ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx3(Copy, { size: 12 })
2444
+ }
2445
+ ),
2446
+ /* @__PURE__ */ jsx3(
2447
+ TreeActionButton,
2448
+ {
2449
+ disabled: activeAction !== null || !onShareFile,
2450
+ onClick: async () => {
2451
+ if (!onShareFile) return;
2452
+ setActiveAction("share");
2453
+ try {
2454
+ await onShareFile(entry.path, entry.name);
2455
+ await queryClient.invalidateQueries({ queryKey: ["file-tree", sessionId] });
2456
+ } catch (error) {
2457
+ alert(getErrorMessage(error, "\u5171\u4EAB\u5931\u8D25"));
2458
+ } finally {
2459
+ setActiveAction(null);
2460
+ }
2461
+ },
2462
+ title: "\u5171\u4EAB\u5230 .share",
2463
+ children: activeAction === "share" ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx3(Link2, { size: 12 })
2464
+ }
2465
+ ),
2466
+ /* @__PURE__ */ jsx3(
2467
+ TreeActionButton,
2468
+ {
2469
+ disabled: activeAction !== null,
2470
+ onClick: () => void handleDelete(),
2471
+ title: "\u5220\u9664\u6587\u4EF6",
2472
+ children: activeAction === "delete" ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx3(Trash2, { size: 12 })
2473
+ }
2474
+ )
2475
+ ] }) : allowDelete ? /* @__PURE__ */ jsx3(FileTreeActions, { className: "ml-auto hidden shrink-0 group-hover:flex", children: /* @__PURE__ */ jsx3(
2476
+ TreeActionButton,
2477
+ {
2478
+ disabled: activeAction !== null,
2479
+ onClick: () => void handleDelete(),
2480
+ title: "\u5220\u9664\u6587\u4EF6",
2481
+ children: activeAction === "delete" ? /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx3(Trash2, { size: 12 })
2482
+ }
2483
+ ) }) : null
2484
+ ] });
2485
+ }
2486
+ function LazyDirChildren({
2487
+ dirPath,
2488
+ expandedDirs,
2489
+ isStreaming,
2490
+ onOpenFile,
2491
+ onSelectPath,
2492
+ onToggleDir,
2493
+ onTreePathChange,
2494
+ openingFilePath,
2495
+ selectedPath,
2496
+ sessionId,
2497
+ readOnly,
2498
+ onShareFile,
2499
+ allowDelete
2500
+ }) {
2501
+ const {
2502
+ data: entries,
2503
+ isLoading,
2504
+ error
2505
+ } = useQuery({
2506
+ queryKey: ["file-tree", sessionId, dirPath],
2507
+ queryFn: () => listDir(sessionId, dirPath),
2508
+ refetchInterval: isStreaming ? 6e4 : false
2509
+ });
2510
+ if (isLoading) {
2511
+ return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 px-2 py-1 text-xs text-[hsl(var(--muted-foreground))]", children: [
2512
+ /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "animate-spin" }),
2513
+ /* @__PURE__ */ jsx3("span", { children: "\u52A0\u8F7D\u4E2D..." })
2514
+ ] });
2515
+ }
2516
+ if (error) {
2517
+ return /* @__PURE__ */ jsx3("div", { className: "px-2 py-1 text-xs text-[hsl(var(--muted-foreground))]", children: error instanceof Error ? error.message : "\u52A0\u8F7D\u5931\u8D25" });
2518
+ }
2519
+ if (!entries || entries.length === 0) {
2520
+ return /* @__PURE__ */ jsx3("div", { className: "px-2 py-1 text-xs text-[hsl(var(--muted-foreground))]", children: "\u7A7A\u76EE\u5F55" });
2521
+ }
2522
+ return /* @__PURE__ */ jsx3(Fragment2, { children: entries.map(
2523
+ (child) => child.is_dir ? /* @__PURE__ */ jsx3(
2524
+ FolderNode,
2525
+ {
2526
+ entry: child,
2527
+ expandedDirs,
2528
+ isStreaming,
2529
+ onOpenFile,
2530
+ onSelectPath,
2531
+ onToggleDir,
2532
+ onTreePathChange,
2533
+ openingFilePath,
2534
+ selectedPath,
2535
+ sessionId,
2536
+ readOnly,
2537
+ onShareFile,
2538
+ allowDelete
2539
+ },
2540
+ child.path
2541
+ ) : /* @__PURE__ */ jsx3(
2542
+ FileNode,
2543
+ {
2544
+ entry: child,
2545
+ onOpenFile,
2546
+ onSelectPath,
2547
+ onTreePathChange,
2548
+ openingFilePath,
2549
+ selectedPath,
2550
+ sessionId,
2551
+ readOnly,
2552
+ onShareFile,
2553
+ allowDelete
2554
+ },
2555
+ child.path
2556
+ )
2557
+ ) });
2558
+ }
2559
+ function FileTagList({ tags }) {
2560
+ if (!tags || tags.length === 0) return null;
2561
+ return /* @__PURE__ */ jsx3("span", { className: "ml-2 flex min-w-0 shrink-0 items-center gap-1", children: tags.map((tag) => /* @__PURE__ */ jsx3(
2562
+ "span",
2563
+ {
2564
+ className: "max-w-20 truncate rounded border border-[hsl(var(--border))] bg-[hsl(var(--muted))] px-1.5 py-0.5 text-[10px] leading-none text-[hsl(var(--muted-foreground))]",
2565
+ title: tag,
2566
+ children: tag
2567
+ },
2568
+ tag
2569
+ )) });
2570
+ }
2571
+ function TreeActionButton({
2572
+ children,
2573
+ disabled = false,
2574
+ onClick,
2575
+ title
2576
+ }) {
2577
+ return /* @__PURE__ */ jsx3(
2578
+ "button",
2579
+ {
2580
+ type: "button",
2581
+ disabled,
2582
+ onClick,
2583
+ title,
2584
+ className: "flex items-center rounded p-1 text-[hsl(var(--muted-foreground))] transition-colors hover:bg-[hsl(var(--muted-foreground))/10] hover:text-[hsl(var(--foreground))] disabled:cursor-not-allowed disabled:opacity-50",
2585
+ children
2586
+ }
2587
+ );
2588
+ }
2589
+ function EmptyState({ title, description }) {
2590
+ return /* @__PURE__ */ jsxs2("div", { className: "flex h-full min-h-[12rem] flex-col items-center justify-center gap-3 px-6 text-center", children: [
2591
+ /* @__PURE__ */ jsx3("div", { className: "flex h-10 w-10 items-center justify-center text-[hsl(var(--muted-foreground))]", children: getFolderIcon() }),
2592
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
2593
+ /* @__PURE__ */ jsx3("div", { className: "text-sm font-medium text-[hsl(var(--foreground))]", children: title }),
2594
+ /* @__PURE__ */ jsx3("div", { className: "text-sm text-[hsl(var(--muted-foreground))]", children: description })
2595
+ ] })
2596
+ ] });
2597
+ }
2598
+ function getFileIcon(fileName) {
2599
+ const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
2600
+ const group = FILE_ICON_GROUPS[ext];
2601
+ const src = group ? `/file-icons/${group}/${ext}.png` : "/file-icons/generic-file.png";
2602
+ return /* @__PURE__ */ jsx3(FileTypeIcon, { src, alt: `${ext || "file"} \u6587\u4EF6` });
2603
+ }
2604
+ function getFolderIcon() {
2605
+ return /* @__PURE__ */ jsx3(FileTypeIcon, { src: "/file-icons/folder.png", alt: "\u6587\u4EF6\u5939" });
2606
+ }
2607
+ function FileTypeIcon({ src, alt }) {
2608
+ return /* @__PURE__ */ jsx3(
2609
+ "img",
2610
+ {
2611
+ src,
2612
+ alt,
2613
+ className: "size-4 shrink-0 object-contain",
2614
+ draggable: false,
2615
+ loading: "lazy"
2616
+ }
2617
+ );
2618
+ }
2619
+ function getErrorMessage(error, fallback) {
2620
+ if (error instanceof Error && error.message) {
2621
+ return error.message;
2622
+ }
2623
+ return fallback;
2624
+ }
2625
+
2626
+ // src/react/components/workspace/WorkspaceFilesPanel.tsx
2627
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2628
+ var EMPTY_MESSAGES = [];
2629
+ function WorkspaceFilesPanel({
2630
+ sessionId,
2631
+ enableEditor = false,
2632
+ className,
2633
+ treeContainerClassName,
2634
+ title = "\u5DE5\u4F5C\u533A",
2635
+ rootPath = ".",
2636
+ readOnly = false,
2637
+ onShareFile,
2638
+ onSync,
2639
+ allowDelete = false,
2640
+ topSlot,
2641
+ onOpenFile
2642
+ }) {
2643
+ const [expandedDirs, setExpandedDirs] = useState3(/* @__PURE__ */ new Set());
2644
+ const [isUploading, setIsUploading] = useState3(false);
2645
+ const [activeTab, setActiveTab] = useState3("files");
2646
+ const [isSyncing, setIsSyncing] = useState3(false);
2647
+ const queryClient = useQueryClient2();
2648
+ const fileInputRef = useRef2(null);
2649
+ const folderInputRef = useRef2(null);
2650
+ const messages = useChatStore(
2651
+ (state) => sessionId ? state.messages[sessionId] ?? EMPTY_MESSAGES : EMPTY_MESSAGES
2652
+ );
2653
+ const pushArtifact = useUiStore((state) => state.pushArtifact);
2654
+ const inlineResources = useMemo2(() => {
2655
+ if (!sessionId) return [];
2656
+ return messages.flatMap(
2657
+ (message, messageIndex) => (message.blocks ?? []).flatMap((block, blockIndex) => {
2658
+ if (block.type !== "tool_ui" || !isUiMeta(block.content) || block.content.target !== "inline") {
2659
+ return [];
2660
+ }
2661
+ const ui = block.content;
2662
+ const type3 = ui.resourceHTML ? "resource-html" : "resource-uri";
2663
+ const content = ui.resourceHTML ?? ui.resourceUri ?? ui.resourceURI ?? "";
2664
+ if (!content) return [];
2665
+ const title2 = ui.title ?? (typeof block.display_name === "string" && block.display_name.trim().length > 0 ? block.display_name : "\u53EF\u89C6\u5316\u4EA7\u7269");
2666
+ const key = `${message.entry_id ?? message.timestamp ?? messageIndex}-${block.tool_call_id ?? "ui"}-${blockIndex}`;
2667
+ return [
2668
+ {
2669
+ key,
2670
+ title: title2,
2671
+ target: {
2672
+ type: type3,
2673
+ content,
2674
+ title: title2,
2675
+ key,
2676
+ bridgeSessionId: sessionId
2677
+ }
2678
+ }
2679
+ ];
2680
+ })
2681
+ );
2682
+ }, [messages, sessionId]);
2683
+ useEffect2(() => {
2684
+ if (activeTab === "resources" && inlineResources.length === 0) {
2685
+ setActiveTab("files");
2686
+ }
2687
+ }, [activeTab, inlineResources.length]);
2688
+ const uploadTargetDir = () => {
2689
+ const candidates = Array.from(expandedDirs).filter((path) => path !== ".").filter((path) => {
2690
+ const parts = path.split("/").filter(Boolean);
2691
+ for (let index = 1; index < parts.length; index += 1) {
2692
+ const ancestor = parts.slice(0, index).join("/");
2693
+ if (!expandedDirs.has(ancestor)) return false;
2694
+ }
2695
+ return true;
2696
+ });
2697
+ if (candidates.length === 0) {
2698
+ return ".";
2699
+ }
2700
+ return candidates.reduce((deepestPath, currentPath) => {
2701
+ const deepestDepth = deepestPath.split("/").filter(Boolean).length;
2702
+ const currentDepth = currentPath.split("/").filter(Boolean).length;
2703
+ return currentDepth > deepestDepth ? currentPath : deepestPath;
2704
+ }, candidates[0] ?? ".");
2705
+ };
2706
+ const handleUploadChange = async (event) => {
2707
+ const files = event.target.files;
2708
+ if (!sessionId || !files || files.length === 0) {
2709
+ event.target.value = "";
2710
+ return;
2711
+ }
2712
+ setIsUploading(true);
2713
+ try {
2714
+ const targetDir = uploadTargetDir();
2715
+ const result = await uploadFiles(sessionId, targetDir, files);
2716
+ await queryClient.invalidateQueries({
2717
+ queryKey: ["file-tree", sessionId],
2718
+ refetchType: "all"
2719
+ });
2720
+ await queryClient.refetchQueries({
2721
+ queryKey: ["file-tree", sessionId],
2722
+ type: "active"
2723
+ });
2724
+ if (targetDir !== ".") {
2725
+ setExpandedDirs((prev) => {
2726
+ const next = new Set(prev);
2727
+ next.add(targetDir);
2728
+ return next;
2729
+ });
2730
+ }
2731
+ if (result.failed.length > 0) {
2732
+ alert(`\u90E8\u5206\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25: ${result.failed.join(", ")}`);
2733
+ }
2734
+ } catch (error) {
2735
+ console.error("Upload files failed", error);
2736
+ alert("\u4E0A\u4F20\u6587\u4EF6\u5931\u8D25");
2737
+ } finally {
2738
+ setIsUploading(false);
2739
+ event.target.value = "";
2740
+ }
2741
+ };
2742
+ const handleUploadClick = () => {
2743
+ fileInputRef.current?.click();
2744
+ };
2745
+ const handleFolderUploadClick = () => {
2746
+ folderInputRef.current?.click();
2747
+ };
2748
+ return /* @__PURE__ */ jsxs3("div", { className: cn("flex flex-col", className), children: [
2749
+ topSlot ? /* @__PURE__ */ jsx4("div", { className: "mb-3", children: topSlot }) : null,
2750
+ /* @__PURE__ */ jsxs3("div", { className: "mb-2 flex shrink-0 items-center justify-between gap-2", children: [
2751
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-1 rounded-lg bg-[hsl(var(--muted))]/60 p-0.5 text-[11px] font-medium", children: [
2752
+ /* @__PURE__ */ jsxs3(
2753
+ "button",
2754
+ {
2755
+ type: "button",
2756
+ onClick: () => setActiveTab("files"),
2757
+ className: cn(
2758
+ "inline-flex items-center gap-1.5 rounded-md px-2 py-1 transition-colors",
2759
+ activeTab === "files" ? "bg-[hsl(var(--background))] text-[hsl(var(--foreground))] shadow-sm" : "text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
2760
+ ),
2761
+ children: [
2762
+ /* @__PURE__ */ jsx4(FolderTree, { size: 12 }),
2763
+ title
2764
+ ]
2765
+ }
2766
+ ),
2767
+ inlineResources.length > 0 ? /* @__PURE__ */ jsxs3(
2768
+ "button",
2769
+ {
2770
+ type: "button",
2771
+ onClick: () => setActiveTab("resources"),
2772
+ className: cn(
2773
+ "inline-flex items-center gap-1.5 rounded-md px-2 py-1 transition-colors",
2774
+ activeTab === "resources" ? "bg-[hsl(var(--background))] text-[hsl(var(--foreground))] shadow-sm" : "text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
2775
+ ),
2776
+ children: [
2777
+ /* @__PURE__ */ jsx4(PanelRightOpen, { size: 12 }),
2778
+ "\u53EF\u89C6\u5316\u4EA7\u7269",
2779
+ /* @__PURE__ */ jsx4("span", { className: "font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: inlineResources.length })
2780
+ ]
2781
+ }
2782
+ ) : null
2783
+ ] }),
2784
+ onSync ? /* @__PURE__ */ jsx4("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx4(
2785
+ "button",
2786
+ {
2787
+ type: "button",
2788
+ disabled: !sessionId || isSyncing,
2789
+ onClick: async () => {
2790
+ setIsSyncing(true);
2791
+ try {
2792
+ await onSync();
2793
+ await queryClient.invalidateQueries({
2794
+ queryKey: ["file-tree", sessionId],
2795
+ refetchType: "all"
2796
+ });
2797
+ } finally {
2798
+ setIsSyncing(false);
2799
+ }
2800
+ },
2801
+ className: "rounded p-1 text-[hsl(var(--muted-foreground))] transition-colors hover:bg-[hsl(var(--muted-foreground))/10] hover:text-[hsl(var(--foreground))] disabled:cursor-not-allowed disabled:opacity-50",
2802
+ title: "\u540C\u6B65\u5171\u4EAB\u533A",
2803
+ children: isSyncing ? /* @__PURE__ */ jsx4(Loader22, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx4(RefreshCw, { size: 12 })
2804
+ }
2805
+ ) }) : null,
2806
+ !readOnly && activeTab === "files" ? /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", children: [
2807
+ /* @__PURE__ */ jsx4(
2808
+ "button",
2809
+ {
2810
+ type: "button",
2811
+ disabled: !sessionId || isUploading,
2812
+ onClick: handleUploadClick,
2813
+ className: "rounded p-1 text-[hsl(var(--muted-foreground))] transition-colors hover:bg-[hsl(var(--muted-foreground))/10] hover:text-[hsl(var(--foreground))] disabled:cursor-not-allowed disabled:opacity-50",
2814
+ title: "\u4E0A\u4F20\u6587\u4EF6",
2815
+ children: isUploading ? /* @__PURE__ */ jsx4(Loader22, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx4(Upload, { size: 12 })
2816
+ }
2817
+ ),
2818
+ /* @__PURE__ */ jsx4(
2819
+ "button",
2820
+ {
2821
+ type: "button",
2822
+ disabled: !sessionId || isUploading,
2823
+ onClick: handleFolderUploadClick,
2824
+ className: "rounded p-1 text-[hsl(var(--muted-foreground))] transition-colors hover:bg-[hsl(var(--muted-foreground))/10] hover:text-[hsl(var(--foreground))] disabled:cursor-not-allowed disabled:opacity-50",
2825
+ title: "\u4E0A\u4F20\u6587\u4EF6\u5939",
2826
+ children: isUploading ? /* @__PURE__ */ jsx4(Loader22, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx4(FolderUp, { size: 12 })
2827
+ }
2828
+ )
2829
+ ] }) : null
2830
+ ] }),
2831
+ activeTab === "resources" && inlineResources.length > 0 ? /* @__PURE__ */ jsx4("div", { className: cn("min-h-0 overflow-auto", treeContainerClassName), children: /* @__PURE__ */ jsx4("div", { className: "flex flex-col gap-1.5", children: inlineResources.map((item, index) => /* @__PURE__ */ jsxs3(
2832
+ "button",
2833
+ {
2834
+ type: "button",
2835
+ onClick: () => pushArtifact(item.target),
2836
+ className: "group flex min-w-0 items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-[hsl(var(--muted))]",
2837
+ title: item.title,
2838
+ children: [
2839
+ /* @__PURE__ */ jsx4(FileText, { size: 14, className: "shrink-0 text-[hsl(var(--primary))]" }),
2840
+ /* @__PURE__ */ jsx4("span", { className: "min-w-0 flex-1 truncate text-[hsl(var(--foreground))]", children: item.title }),
2841
+ /* @__PURE__ */ jsx4("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: index + 1 })
2842
+ ]
2843
+ },
2844
+ item.key
2845
+ )) }) }) : /* @__PURE__ */ jsx4("div", { className: treeContainerClassName, children: /* @__PURE__ */ jsx4(
2846
+ FileTree2,
2847
+ {
2848
+ sessionId,
2849
+ enableEditor,
2850
+ onExpandedDirsChange: setExpandedDirs,
2851
+ rootPath,
2852
+ readOnly,
2853
+ onShareFile,
2854
+ allowDelete,
2855
+ onOpenFile
2856
+ },
2857
+ sessionId ?? "workspace-empty"
2858
+ ) }),
2859
+ /* @__PURE__ */ jsx4(
2860
+ "input",
2861
+ {
2862
+ ref: fileInputRef,
2863
+ type: "file",
2864
+ multiple: true,
2865
+ className: "hidden",
2866
+ onChange: handleUploadChange
2867
+ }
2868
+ ),
2869
+ /* @__PURE__ */ jsx4(
2870
+ "input",
2871
+ {
2872
+ ref: folderInputRef,
2873
+ type: "file",
2874
+ multiple: true,
2875
+ className: "hidden",
2876
+ onChange: handleUploadChange,
2877
+ ...{ webkitdirectory: "" }
2878
+ }
2879
+ )
2880
+ ] });
2881
+ }
2882
+ export {
2883
+ FileTree2 as FileTree,
2884
+ WorkspaceFilesPanel
2885
+ };
2886
+ //# sourceMappingURL=index.js.map