@blade-hq/agent-react 2612.0.0-beta.76 → 2612.0.0-beta.78

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 (62) hide show
  1. package/README.md +15 -1
  2. package/dist/SessionPluginConfigForm-7LJ5SUNY.js +9 -0
  3. package/dist/SessionPluginConfigForm-7LJ5SUNY.js.map +1 -0
  4. package/dist/chat/LiteMessageList.d.ts +4 -0
  5. package/dist/chat/TurnNavRail.d.ts +1 -4
  6. package/dist/chat/index.d.ts +2 -1
  7. package/dist/chat/turn-locate-channel.d.ts +53 -0
  8. package/dist/{chunk-XP6T6SHZ.js → chunk-66JXSDLB.js} +1186 -1038
  9. package/dist/chunk-66JXSDLB.js.map +1 -0
  10. package/dist/{chunk-XJ6ZA6LN.js → chunk-C3LBNHVM.js} +12 -10
  11. package/dist/{chunk-XJ6ZA6LN.js.map → chunk-C3LBNHVM.js.map} +1 -1
  12. package/dist/chunk-F4MWGSKX.js +1308 -0
  13. package/dist/chunk-F4MWGSKX.js.map +1 -0
  14. package/dist/{chunk-7MCGQH34.js → chunk-FBGCDXRK.js} +116 -114
  15. package/dist/chunk-FBGCDXRK.js.map +1 -0
  16. package/dist/{chunk-HCW7SI5Q.js → chunk-FDB3J5QF.js} +8 -6
  17. package/dist/{chunk-HCW7SI5Q.js.map → chunk-FDB3J5QF.js.map} +1 -1
  18. package/dist/{chunk-J72ZLILV.js → chunk-FH5HA3GW.js} +554 -455
  19. package/dist/chunk-FH5HA3GW.js.map +1 -0
  20. package/dist/chunk-KHJEIHMJ.js +973 -0
  21. package/dist/chunk-KHJEIHMJ.js.map +1 -0
  22. package/dist/{chunk-QIR73CES.js → chunk-MWILWSPG.js} +7396 -5684
  23. package/dist/chunk-MWILWSPG.js.map +1 -0
  24. package/dist/{chunk-WRIMPZPN.js → chunk-NRYLAO3X.js} +102 -155
  25. package/dist/chunk-NRYLAO3X.js.map +1 -0
  26. package/dist/{chunk-2DPLLZOU.js → chunk-SK43YNRX.js} +151 -1442
  27. package/dist/chunk-SK43YNRX.js.map +1 -0
  28. package/dist/components/AccountConnectorRules.d.ts +7 -0
  29. package/dist/components/BrowserConnectionDialog.d.ts +3 -2
  30. package/dist/components/ConfigConnection.d.ts +16 -0
  31. package/dist/components/ConnectorDetail.d.ts +46 -0
  32. package/dist/components/ConnectorRulesPanel.d.ts +10 -0
  33. package/dist/components/ConnectorRuntimeDiagnostics.d.ts +2 -2
  34. package/dist/components/DeviceConnection.d.ts +3 -1
  35. package/dist/components/FeishuPermissionStatus.d.ts +3 -1
  36. package/dist/components/ManagedConnection.d.ts +5 -2
  37. package/dist/components/PackEventNotice.d.ts +3 -0
  38. package/dist/components/SessionPluginConfigForm.d.ts +2 -1
  39. package/dist/connectors.d.ts +4 -0
  40. package/dist/connectors.js +10 -2
  41. package/dist/embed/entry.d.ts +4 -0
  42. package/dist/hooks/use-turn-navigation.d.ts +19 -0
  43. package/dist/index.d.ts +4 -0
  44. package/dist/index.js +16 -7
  45. package/dist/internal.d.ts +1 -1
  46. package/dist/internal.js +18 -9
  47. package/dist/internal.js.map +1 -1
  48. package/dist/questions.js +8 -6
  49. package/dist/questions.js.map +1 -1
  50. package/dist/rich-input.js +32 -13
  51. package/dist/rich-input.js.map +1 -1
  52. package/dist/style.full.css +1 -1
  53. package/dist/tool-details.js +5 -4
  54. package/dist/tool-details.js.map +1 -1
  55. package/package.json +2 -2
  56. package/public-api.md +46 -2
  57. package/dist/chunk-2DPLLZOU.js.map +0 -1
  58. package/dist/chunk-7MCGQH34.js.map +0 -1
  59. package/dist/chunk-J72ZLILV.js.map +0 -1
  60. package/dist/chunk-QIR73CES.js.map +0 -1
  61. package/dist/chunk-WRIMPZPN.js.map +0 -1
  62. package/dist/chunk-XP6T6SHZ.js.map +0 -1
@@ -0,0 +1,1308 @@
1
+ import {
2
+ useBladeClient,
3
+ useSessionHandle,
4
+ useSessionState
5
+ } from "./chunk-NRYLAO3X.js";
6
+ import {
7
+ SessionPluginConfigForm
8
+ } from "./chunk-SK43YNRX.js";
9
+ import {
10
+ LoaderCircle,
11
+ Puzzle
12
+ } from "./chunk-KHJEIHMJ.js";
13
+
14
+ // src/chat/runtime/input-history-storage.ts
15
+ var generation = 0;
16
+ function getInputHistoryGeneration() {
17
+ return generation;
18
+ }
19
+
20
+ // src/chat/runtime/input-cache-reset.ts
21
+ var resetters = /* @__PURE__ */ new WeakMap();
22
+ function onInputCachesReset(client, reset) {
23
+ let set = resetters.get(client);
24
+ if (!set) {
25
+ set = /* @__PURE__ */ new Set();
26
+ resetters.set(client, set);
27
+ }
28
+ set.add(reset);
29
+ return () => {
30
+ set.delete(reset);
31
+ };
32
+ }
33
+
34
+ // src/rich-input/lib/plugin-suggestions.ts
35
+ var accountPreparation = /* @__PURE__ */ new WeakMap();
36
+ var registeredClients = /* @__PURE__ */ new WeakSet();
37
+ function pluginRefsForSend(refs, turns = []) {
38
+ const latest = [...turns].reverse().find((turn) => turn.role === "user");
39
+ return refs.length || latest?.refs?.length ? refs : void 0;
40
+ }
41
+ function prepareAccount(client) {
42
+ if (!registeredClients.has(client)) {
43
+ registeredClients.add(client);
44
+ onInputCachesReset(client, () => accountPreparation.delete(client));
45
+ }
46
+ const current = accountPreparation.get(client);
47
+ if (current && current.token === client.token) return current.ready;
48
+ const ready = client.plugins.catalog().catch((error) => {
49
+ accountPreparation.delete(client);
50
+ throw error;
51
+ });
52
+ accountPreparation.set(client, { token: client.token, ready });
53
+ return ready;
54
+ }
55
+ async function getPluginSuggestionItems(client, session, query, pendingPlugins = []) {
56
+ if (!session && pendingPlugins.length === 0) return [];
57
+ const path = session ? `/api/sessions/${encodeURIComponent(session)}/plugin-tools/mentions` : "/api/plugin-mentions/mentions";
58
+ try {
59
+ if (!session) await prepareAccount(client);
60
+ const data = await client.json("POST", path, { arguments: { query, cursor: "" }, ...!session ? { plugin_selection: pendingPlugins } : {} });
61
+ return [...data.items.map((item) => ({ ...item, kind: "plugin", name: item.label })), ...data.errors.map((message) => ({ kind: "pluginError", message }))];
62
+ } catch {
63
+ return [{ kind: "pluginError", message: "\u63D2\u4EF6\u6761\u76EE\u6682\u65F6\u65E0\u6CD5\u52A0\u8F7D\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165 @ \u91CD\u8BD5" }];
64
+ }
65
+ }
66
+
67
+ // src/chat/runtime/input-services.tsx
68
+ import { createContext, useContext } from "react";
69
+ import { jsx } from "react/jsx-runtime";
70
+ function parseModelReasoningLevels(value) {
71
+ if (!value) return {};
72
+ try {
73
+ const parsed = JSON.parse(value);
74
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
75
+ return Object.fromEntries(
76
+ Object.entries(parsed).filter(
77
+ (entry) => Boolean(entry[0]) && typeof entry[1] === "string" && Boolean(entry[1])
78
+ )
79
+ );
80
+ } catch {
81
+ return {};
82
+ }
83
+ }
84
+ var RichInputServicesContext = createContext({});
85
+ function RichInputServicesProvider({ value, children }) {
86
+ return /* @__PURE__ */ jsx(RichInputServicesContext.Provider, { value, children });
87
+ }
88
+ function useRichInputServices() {
89
+ return useContext(RichInputServicesContext);
90
+ }
91
+
92
+ // src/rich-input/lib/client-query.ts
93
+ import { useCallback, useEffect, useSyncExternalStore } from "react";
94
+ var EMPTY = { data: void 0, error: null, isLoading: false, isFetching: false };
95
+ var caches = /* @__PURE__ */ new WeakMap();
96
+ function entryFor(client, key) {
97
+ let cache = caches.get(client);
98
+ if (!cache) {
99
+ const created = /* @__PURE__ */ new Map();
100
+ cache = created;
101
+ caches.set(client, created);
102
+ onInputCachesReset(client, () => {
103
+ for (const [key2, entry2] of created) {
104
+ entry2.request = null;
105
+ entry2.fetchedAt = 0;
106
+ update(entry2, EMPTY);
107
+ const load = loadersFor(client).get(key2);
108
+ if (load && entry2.listeners.size > 0) void fetchClientQuery(client, key2, load, { force: true }).catch(() => {
109
+ });
110
+ }
111
+ });
112
+ }
113
+ let entry = cache.get(key);
114
+ if (!entry) {
115
+ entry = { snapshot: EMPTY, fetchedAt: 0, request: null, listeners: /* @__PURE__ */ new Set() };
116
+ cache.set(key, entry);
117
+ }
118
+ return entry;
119
+ }
120
+ function update(entry, patch) {
121
+ entry.snapshot = { ...entry.snapshot, ...patch };
122
+ for (const listener of [...entry.listeners]) listener();
123
+ }
124
+ function fetchClientQuery(client, key, load, { staleMs = 3e4, force = false } = {}) {
125
+ const entry = entryFor(client, key);
126
+ if (entry.request) return entry.request;
127
+ if (!force && entry.snapshot.data !== void 0 && Date.now() - entry.fetchedAt < staleMs) {
128
+ return Promise.resolve(entry.snapshot.data);
129
+ }
130
+ const request = Promise.resolve().then(() => load(client));
131
+ entry.request = request;
132
+ update(entry, { isFetching: true, isLoading: entry.snapshot.data === void 0 });
133
+ request.then(
134
+ (data) => {
135
+ if (entry.request !== request) return;
136
+ entry.request = null;
137
+ entry.fetchedAt = Date.now();
138
+ update(entry, { data, error: null, isFetching: false, isLoading: false });
139
+ },
140
+ (error) => {
141
+ if (entry.request !== request) return;
142
+ entry.request = null;
143
+ update(entry, { error, isFetching: false, isLoading: false });
144
+ }
145
+ );
146
+ return request;
147
+ }
148
+ function setClientQueryData(client, key, updater) {
149
+ const entry = entryFor(client, key);
150
+ if (entry.snapshot.data === void 0) return;
151
+ update(entry, { data: updater(entry.snapshot.data) });
152
+ }
153
+ function useClientQuery(key, load, { staleMs = 3e4 } = {}) {
154
+ const client = useBladeClient();
155
+ const subscribe = useCallback(
156
+ (listener) => {
157
+ if (!key) return () => {
158
+ };
159
+ const entry = entryFor(client, key);
160
+ entry.listeners.add(listener);
161
+ return () => {
162
+ entry.listeners.delete(listener);
163
+ };
164
+ },
165
+ [client, key]
166
+ );
167
+ const snapshot = useSyncExternalStore(
168
+ subscribe,
169
+ () => key ? entryFor(client, key).snapshot : EMPTY,
170
+ () => key ? entryFor(client, key).snapshot : EMPTY
171
+ );
172
+ if (key) loadersFor(client).set(key, load);
173
+ useEffect(() => {
174
+ if (!key) return;
175
+ const registered = loadersFor(client).get(key);
176
+ if (registered) void fetchClientQuery(client, key, registered, { staleMs }).catch(() => {
177
+ });
178
+ }, [client, key, staleMs]);
179
+ const refetch = useCallback(async () => {
180
+ if (!key) return void 0;
181
+ const registered = loadersFor(client).get(key);
182
+ if (!registered) return void 0;
183
+ return fetchClientQuery(client, key, registered, { force: true }).catch(() => void 0);
184
+ }, [client, key]);
185
+ return { ...snapshot, refetch };
186
+ }
187
+ var loaderRegistry = /* @__PURE__ */ new WeakMap();
188
+ function loadersFor(client) {
189
+ let loaders = loaderRegistry.get(client);
190
+ if (!loaders) {
191
+ loaders = /* @__PURE__ */ new Map();
192
+ loaderRegistry.set(client, loaders);
193
+ }
194
+ return loaders;
195
+ }
196
+ function refreshClientQuery(client, key) {
197
+ const entry = caches.get(client)?.get(key);
198
+ if (!entry) return;
199
+ entry.fetchedAt = 0;
200
+ const load = loadersFor(client).get(key);
201
+ if (load && entry.listeners.size > 0) void fetchClientQuery(client, key, load, { force: true }).catch(() => {
202
+ });
203
+ }
204
+
205
+ // src/rich-input/lib/session-data.ts
206
+ import { useEffect as useEffect2, useRef } from "react";
207
+ var sessionPath = (sessionId, rest) => `/api/sessions/${encodeURIComponent(sessionId)}/${rest}`;
208
+ function useRefreshWhenRunEnds(sessionId, keys) {
209
+ const client = useBladeClient();
210
+ const streaming = useSessionState(sessionId, (state) => state.isStreaming);
211
+ const wasStreaming = useRef(streaming);
212
+ useEffect2(() => {
213
+ if (wasStreaming.current && !streaming) {
214
+ for (const key of keys) refreshClientQuery(client, key);
215
+ }
216
+ wasStreaming.current = streaming;
217
+ });
218
+ }
219
+ function useClientSessionSkills(sessionId) {
220
+ const key = sessionId ? `session-skills:${sessionId}` : null;
221
+ const query = useClientQuery(
222
+ key,
223
+ (client) => client.json("GET", sessionPath(sessionId ?? "", "skills"))
224
+ );
225
+ useRefreshWhenRunEnds(sessionId, key ? [key] : []);
226
+ return { data: query.data, loading: query.isLoading, error: query.error };
227
+ }
228
+ function useClientSkillStats(sessionId) {
229
+ const key = sessionId ? `skill-stats:${sessionId}` : null;
230
+ const query = useClientQuery(key, (client) => client.json("GET", sessionPath(sessionId ?? "", "skill-stats")), {
231
+ staleMs: 3e5
232
+ });
233
+ useRefreshWhenRunEnds(sessionId, key ? [key] : []);
234
+ return { data: query.data, loading: query.isLoading, error: query.error };
235
+ }
236
+ function useClientContextStats(sessionId) {
237
+ const key = sessionId ? `context-stats:${sessionId}` : null;
238
+ const query = useClientQuery(
239
+ key,
240
+ (client) => client.json("GET", sessionPath(sessionId ?? "", "context-stats"))
241
+ );
242
+ useRefreshWhenRunEnds(sessionId, key ? [key] : []);
243
+ return { data: query.data, loading: query.isLoading, error: query.error };
244
+ }
245
+ function useClientBackgroundTasks(sessionId) {
246
+ const client = useBladeClient();
247
+ const key = sessionId ? `background-tasks:${sessionId}` : null;
248
+ const query = useClientQuery(key, (c) => c.sessions.listBackgroundTasks(sessionId ?? ""));
249
+ const session = useSessionHandle(sessionId);
250
+ useEffect2(() => {
251
+ if (!session || !key) return;
252
+ return session.on("backgroundTask", () => refreshClientQuery(client, key));
253
+ }, [client, key, session]);
254
+ return { data: query.data, loading: query.isLoading, error: query.error };
255
+ }
256
+ function refreshClientSessionSkills(client, sessionId) {
257
+ refreshClientQuery(client, `session-skills:${sessionId}`);
258
+ refreshClientQuery(client, `skill-stats:${sessionId}`);
259
+ }
260
+ function useClientSessionModel(sessionId) {
261
+ const key = sessionId ? `session-detail:${sessionId}` : null;
262
+ const query = useClientQuery(key, (client) => client.sessions.getSession(sessionId ?? ""));
263
+ useRefreshWhenRunEnds(sessionId, key ? [key] : []);
264
+ return query.data?.model ?? null;
265
+ }
266
+
267
+ // src/rich-input/model/model-data.ts
268
+ var EMPTY_MODELS = [];
269
+ var EMPTY_IDS = [];
270
+ var CATALOG_KEY = "model-catalog";
271
+ function useClientModelCatalog() {
272
+ const { data, isLoading, isFetching, refetch } = useClientQuery(
273
+ CATALOG_KEY,
274
+ (client) => client.json("GET", "/api/config/models"),
275
+ { staleMs: 0 }
276
+ );
277
+ return {
278
+ models: data?.models ?? EMPTY_MODELS,
279
+ defaultModel: data?.default ?? "",
280
+ thinkingAvailable: data?.thinkingAvailable ?? false,
281
+ unavailableProviderIds: data?.unavailableProviderIds ?? EMPTY_IDS,
282
+ unavailableModelIds: data?.unavailableModelIds ?? EMPTY_IDS,
283
+ isLoading,
284
+ isFetching,
285
+ refresh: async () => {
286
+ await refetch();
287
+ }
288
+ };
289
+ }
290
+ var PREFERRED_MODEL_KEY = "preferred_model";
291
+ var MODEL_REASONING_LEVELS_KEY = "llm.model_thinking_preferences";
292
+ var PREFERENCES_KEY = "model-preferences";
293
+ function loadPreferences(client) {
294
+ const query = new URLSearchParams();
295
+ query.append("key", PREFERRED_MODEL_KEY);
296
+ query.append("key", MODEL_REASONING_LEVELS_KEY);
297
+ return client.json("GET", `/api/user/preferences?${query.toString()}`).then((response) => response.values ?? {});
298
+ }
299
+ async function writePreference(client, key, value) {
300
+ const previous = await fetchClientQuery(client, PREFERENCES_KEY, loadPreferences, { staleMs: 6e4 }).catch(
301
+ () => ({})
302
+ );
303
+ setClientQueryData(client, PREFERENCES_KEY, (current) => ({ ...current, [key]: value }));
304
+ try {
305
+ await client.json("PUT", `/api/user/preferences/${encodeURIComponent(key)}`, { value });
306
+ } catch {
307
+ setClientQueryData(client, PREFERENCES_KEY, (current) => ({ ...current, [key]: previous[key] ?? null }));
308
+ }
309
+ }
310
+ var EMPTY_LEVELS = {};
311
+ function useClientModelPreferences() {
312
+ const client = useBladeClient();
313
+ const { data } = useClientQuery(PREFERENCES_KEY, loadPreferences, { staleMs: 6e4 });
314
+ const rawLevels = data?.[MODEL_REASONING_LEVELS_KEY] ?? null;
315
+ const reasoningLevels = rawLevels ? parseModelReasoningLevels(rawLevels) : EMPTY_LEVELS;
316
+ return {
317
+ preferredModel: data?.[PREFERRED_MODEL_KEY] ?? null,
318
+ setPreferredModel: (modelId) => void writePreference(client, PREFERRED_MODEL_KEY, modelId),
319
+ reasoningLevels,
320
+ setReasoningLevel: (modelId, level) => {
321
+ if (!modelId) return;
322
+ void writePreference(
323
+ client,
324
+ MODEL_REASONING_LEVELS_KEY,
325
+ JSON.stringify({ ...reasoningLevels, [modelId]: level })
326
+ );
327
+ }
328
+ };
329
+ }
330
+
331
+ // src/rich-input/services.tsx
332
+ import { useMemo, useRef as useRef2 } from "react";
333
+ function useModelCatalog() {
334
+ const use = useRichInputServices().useModelCatalog ?? useClientModelCatalog;
335
+ return use();
336
+ }
337
+ function useModelPreferences() {
338
+ const use = useRichInputServices().useModelPreferences ?? useClientModelPreferences;
339
+ return use();
340
+ }
341
+ function useSessionConnection(sessionId) {
342
+ const connection = useSessionState(sessionId, (state) => state.connection);
343
+ const status = sessionId ? connection : "connected";
344
+ const everConnected = useRef2(false);
345
+ if (status === "connected") everConnected.current = true;
346
+ return { status, reconnectAttempt: 0, hasEverConnected: everConnected.current };
347
+ }
348
+ function useComposerConnectionInfo(sessionId) {
349
+ const use = useRichInputServices().useConnection ?? useSessionConnection;
350
+ return use(sessionId);
351
+ }
352
+ function useComposerConnection(sessionId) {
353
+ return useComposerConnectionInfo(sessionId).status;
354
+ }
355
+ function useSessionSkills(sessionId) {
356
+ const use = useRichInputServices().useSessionSkills ?? useClientSessionSkills;
357
+ return use(sessionId);
358
+ }
359
+ function useSkillStats(sessionId) {
360
+ const use = useRichInputServices().useSkillStats ?? useClientSkillStats;
361
+ return use(sessionId);
362
+ }
363
+ function useContextStats(sessionId) {
364
+ const use = useRichInputServices().useContextStats ?? useClientContextStats;
365
+ return use(sessionId);
366
+ }
367
+ function useBackgroundTaskList(sessionId) {
368
+ const use = useRichInputServices().useBackgroundTasks ?? useClientBackgroundTasks;
369
+ return use(sessionId);
370
+ }
371
+ function useClientSessionDataInvalidation() {
372
+ const client = useBladeClient();
373
+ return useMemo(
374
+ () => ({
375
+ filesUploaded: (_sessionId) => {
376
+ },
377
+ pluginsChanged: (sessionId) => refreshClientSessionSkills(client, sessionId)
378
+ }),
379
+ [client]
380
+ );
381
+ }
382
+ function useSessionDataInvalidation() {
383
+ const use = useRichInputServices().useSessionDataInvalidation ?? useClientSessionDataInvalidation;
384
+ return use();
385
+ }
386
+
387
+ // src/components/SessionPluginIcon.tsx
388
+ import { useEffect as useEffect3, useState } from "react";
389
+ import { jsx as jsx2 } from "react/jsx-runtime";
390
+ function sessionPluginLabel(plugin) {
391
+ const display = typeof plugin.display_name === "string" ? plugin.display_name.trim() : "";
392
+ return display || plugin.name;
393
+ }
394
+ var clientCaches = /* @__PURE__ */ new WeakMap();
395
+ var BLOB_CACHE_LIMIT = 64;
396
+ function iconBlob(client, sessionId, name, token) {
397
+ let blobCache = clientCaches.get(client);
398
+ if (!blobCache) {
399
+ blobCache = /* @__PURE__ */ new Map();
400
+ clientCaches.set(client, blobCache);
401
+ }
402
+ const key = sessionId ? `${sessionId}:${name}:${token}` : `account:${name}:${token}`;
403
+ const cached = blobCache.get(key);
404
+ if (cached) return cached;
405
+ const pending = (sessionId ? client.sessions.fetchSessionPluginIcon(sessionId, name) : client.plugins.fetchIcon(name)).catch(() => {
406
+ blobCache?.delete(key);
407
+ return null;
408
+ });
409
+ if (blobCache.size >= BLOB_CACHE_LIMIT) blobCache.clear();
410
+ blobCache.set(key, pending);
411
+ return pending;
412
+ }
413
+ function SessionPluginIcon({ client, sessionId, plugin, size = 20, shape = "rounded", className }) {
414
+ const [url, setUrl] = useState(null);
415
+ const token = plugin.icon;
416
+ const inlineIcon = token && /^data:image\/(?:png|jpeg|gif|webp);base64,/.test(token) ? token : null;
417
+ useEffect3(() => {
418
+ setUrl(null);
419
+ if (!token || inlineIcon) return;
420
+ let alive = true;
421
+ let objectUrl = null;
422
+ void iconBlob(client, sessionId, plugin.name, token).then((blob) => {
423
+ if (!alive || !blob) return;
424
+ objectUrl = URL.createObjectURL(blob);
425
+ setUrl(objectUrl);
426
+ });
427
+ return () => {
428
+ alive = false;
429
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
430
+ };
431
+ }, [client, sessionId, plugin.name, token, inlineIcon]);
432
+ const frame = `flex shrink-0 items-center justify-center overflow-hidden ${shape === "circle" ? "rounded-full" : "rounded"} ${className ?? ""}`;
433
+ const frameStyle = { width: size, height: size };
434
+ const source = url === "" ? null : inlineIcon ?? url;
435
+ if (!source) {
436
+ return /* @__PURE__ */ jsx2("span", { className: `${frame} bg-[hsl(var(--muted))] text-[hsl(var(--muted-foreground))]`, style: frameStyle, "data-plugin-icon": "placeholder", children: /* @__PURE__ */ jsx2(Puzzle, { "aria-hidden": true, size: Math.round(size * 0.7) }) });
437
+ }
438
+ return /* @__PURE__ */ jsx2("span", { className: frame, style: frameStyle, "data-plugin-icon": "image", children: /* @__PURE__ */ jsx2("img", { src: source, alt: "", "aria-hidden": true, className: "h-full w-full object-cover", onError: () => setUrl("") }) });
439
+ }
440
+
441
+ // src/components/SessionPluginConfigDialog.tsx
442
+ import { useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
443
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
444
+ async function readFailure(error) {
445
+ const response = error?.response;
446
+ if (!response || typeof response.json !== "function") {
447
+ return { message: "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" };
448
+ }
449
+ try {
450
+ const body = await response.json();
451
+ const detail = body?.detail;
452
+ if (typeof detail === "string") return { message: detail };
453
+ if (detail && typeof detail === "object") {
454
+ return {
455
+ message: detail.message ?? "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u68C0\u67E5\u586B\u5199\u5185\u5BB9",
456
+ errors: Array.isArray(detail.errors) ? detail.errors : void 0,
457
+ code: detail.code
458
+ };
459
+ }
460
+ } catch {
461
+ }
462
+ return { message: "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" };
463
+ }
464
+ function SessionPluginConfigDialog({
465
+ client,
466
+ sessionId,
467
+ plugin,
468
+ ...props
469
+ }) {
470
+ return /* @__PURE__ */ jsx3(
471
+ ConfigDialogState,
472
+ {
473
+ client,
474
+ sessionId,
475
+ plugin,
476
+ ...props
477
+ },
478
+ `${sessionId}:${plugin.name}`
479
+ );
480
+ }
481
+ function ConfigDialogState({
482
+ client,
483
+ sessionId,
484
+ plugin,
485
+ onSaved,
486
+ onClose
487
+ }) {
488
+ const [host, setHost] = useState2(null);
489
+ const [restarting, setRestarting] = useState2(false);
490
+ const [scope, setScope] = useState2("personal");
491
+ const [config, setConfig] = useState2(null);
492
+ const pendingRevision = scope === "global" && host?.state === "ready" && config?.revision != null && host.revision !== config.revision;
493
+ const waitingForHost = host?.state === "starting" || host?.state === "updating" || pendingRevision;
494
+ useEffect4(() => {
495
+ if (!waitingForHost) return;
496
+ const abort = new AbortController();
497
+ let timer;
498
+ const refresh = async () => {
499
+ try {
500
+ const next = await client.sessions.getSessionPluginConfig(sessionId, plugin.name, { signal: abort.signal }, scope);
501
+ if (!abort.signal.aborted) setHost(next.host ?? null);
502
+ } catch {
503
+ }
504
+ if (!abort.signal.aborted) timer = setTimeout(refresh, 2e3);
505
+ };
506
+ timer = setTimeout(refresh, 2e3);
507
+ return () => {
508
+ abort.abort();
509
+ clearTimeout(timer);
510
+ };
511
+ }, [client, sessionId, plugin.name, scope, waitingForHost]);
512
+ const [canConfigureGlobal, setCanConfigureGlobal] = useState2(false);
513
+ const [loading, setLoading] = useState2(true);
514
+ const [loadFailure, setLoadFailure] = useState2(null);
515
+ const [saving, setSaving] = useState2(false);
516
+ const [failure, setFailure] = useState2(null);
517
+ const [fieldErrors, setFieldErrors] = useState2([]);
518
+ const [formVersion, setFormVersion] = useState2(0);
519
+ const alive = useRef3(true);
520
+ const generation2 = useRef3(0);
521
+ const dialog = useRef3(null);
522
+ useEffect4(() => {
523
+ const element = dialog.current;
524
+ if (!element || element.open) return;
525
+ if (typeof element.showModal === "function") element.showModal();
526
+ else element.setAttribute("open", "");
527
+ }, []);
528
+ const load = async () => {
529
+ const current = ++generation2.current;
530
+ setLoading(true);
531
+ setLoadFailure(null);
532
+ try {
533
+ const { config: state, can_configure_global, host: hostState } = await client.sessions.getSessionPluginConfig(sessionId, plugin.name, void 0, scope);
534
+ if (!alive.current || generation2.current !== current) return;
535
+ setCanConfigureGlobal(can_configure_global ?? false);
536
+ setHost(hostState ?? null);
537
+ setConfig(state);
538
+ setFormVersion((value) => value + 1);
539
+ } catch (error) {
540
+ if (!alive.current || generation2.current !== current) return;
541
+ const detail = await readFailure(error);
542
+ if (!alive.current || generation2.current !== current) return;
543
+ setLoadFailure(detail.message ?? "\u914D\u7F6E\u6682\u4E0D\u53EF\u7528");
544
+ } finally {
545
+ if (alive.current && generation2.current === current) setLoading(false);
546
+ }
547
+ };
548
+ useEffect4(() => {
549
+ alive.current = true;
550
+ setConfig(null);
551
+ setFailure(null);
552
+ setFieldErrors([]);
553
+ void load();
554
+ return () => {
555
+ alive.current = false;
556
+ generation2.current++;
557
+ };
558
+ }, [client, sessionId, plugin.name, scope]);
559
+ const save = async (draft) => {
560
+ if (!config) return;
561
+ const current = generation2.current;
562
+ setSaving(true);
563
+ setFailure(null);
564
+ setFieldErrors([]);
565
+ try {
566
+ const { config: state } = await client.sessions.updateSessionPluginConfig(
567
+ sessionId,
568
+ plugin.name,
569
+ {
570
+ expected_revision: config.revision,
571
+ set: draft.set,
572
+ remove: draft.remove
573
+ },
574
+ void 0,
575
+ scope
576
+ );
577
+ if (!alive.current || generation2.current !== current) return;
578
+ setConfig(state);
579
+ setFormVersion((value) => value + 1);
580
+ if (scope === "personal") onSaved?.(state);
581
+ } catch (error) {
582
+ if (!alive.current || generation2.current !== current) return;
583
+ const detail = await readFailure(error);
584
+ if (!alive.current || generation2.current !== current) return;
585
+ setFailure(detail.message ?? "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
586
+ setFieldErrors(detail.errors ?? []);
587
+ if (detail.code === "plugin_config_conflict") await load();
588
+ } finally {
589
+ if (alive.current) setSaving(false);
590
+ }
591
+ };
592
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
593
+ /* @__PURE__ */ jsx3("style", { children: ".blade-plugin-dialog::backdrop { background: rgb(0 0 0 / 40%); }" }),
594
+ /* @__PURE__ */ jsxs(
595
+ "dialog",
596
+ {
597
+ ref: dialog,
598
+ "aria-modal": "true",
599
+ "aria-label": `${sessionPluginLabel(plugin)} \u914D\u7F6E`,
600
+ onCancel: (event) => {
601
+ event.preventDefault();
602
+ onClose();
603
+ },
604
+ onKeyDown: (event) => {
605
+ if (event.key !== "Escape") return;
606
+ event.preventDefault();
607
+ event.stopPropagation();
608
+ onClose();
609
+ },
610
+ style: {
611
+ position: "fixed",
612
+ inset: 0,
613
+ margin: "auto",
614
+ boxSizing: "border-box",
615
+ width: "min(448px, calc(100% - 32px))"
616
+ },
617
+ className: "blade-plugin-dialog max-h-[80vh] overflow-y-auto rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--popover))] p-4 text-[hsl(var(--popover-foreground))] shadow-lg",
618
+ children: [
619
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center gap-2", children: [
620
+ /* @__PURE__ */ jsx3(SessionPluginIcon, { client, sessionId, plugin }),
621
+ /* @__PURE__ */ jsx3(
622
+ "span",
623
+ {
624
+ className: "min-w-0 flex-1 truncate text-sm font-medium",
625
+ title: sessionPluginLabel(plugin),
626
+ children: sessionPluginLabel(plugin)
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsx3(
630
+ "button",
631
+ {
632
+ type: "button",
633
+ "aria-label": "\u5173\u95ED",
634
+ className: "shrink-0 text-xs underline",
635
+ onClick: onClose,
636
+ children: "\u5173\u95ED"
637
+ }
638
+ )
639
+ ] }),
640
+ canConfigureGlobal ? /* @__PURE__ */ jsxs("div", { className: "mb-3 flex gap-2 text-xs", children: [
641
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: saving, "aria-pressed": scope === "personal", onClick: () => setScope("personal"), children: "\u6211\u7684\u767B\u5F55" }),
642
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: saving, "aria-pressed": scope === "global", onClick: () => setScope("global"), children: "\u7EC4\u7EC7\u5171\u4EAB\u914D\u7F6E" })
643
+ ] }) : null,
644
+ host ? /* @__PURE__ */ jsxs("div", { className: "mb-3 space-y-1 text-xs", children: [
645
+ /* @__PURE__ */ jsxs("p", { children: [
646
+ "\u670D\u52A1\uFF1A",
647
+ pendingRevision ? "\u6B63\u5728\u66F4\u65B0" : host.reason,
648
+ " \xB7 \u5DF2\u6062\u590D ",
649
+ host.restarts,
650
+ " \u6B21"
651
+ ] }),
652
+ canConfigureGlobal ? /* @__PURE__ */ jsxs(Fragment, { children: [
653
+ /* @__PURE__ */ jsxs("p", { className: "break-all text-[hsl(var(--muted-foreground))]", children: [
654
+ "\u5305\u6458\u8981\uFF1A",
655
+ host.digest,
656
+ " \xB7 \u914D\u7F6E\uFF1A",
657
+ host.revision
658
+ ] }),
659
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: restarting || saving, onClick: () => {
660
+ setRestarting(true);
661
+ void client.sessions.restartSessionPluginHost(sessionId, plugin.name).then(() => load()).catch(() => setFailure("\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5")).finally(() => {
662
+ if (alive.current) setRestarting(false);
663
+ });
664
+ }, children: restarting ? "\u6B63\u5728\u91CD\u542F\u2026" : "\u91CD\u542F\u670D\u52A1" }),
665
+ host.recent?.length ? /* @__PURE__ */ jsxs("details", { children: [
666
+ /* @__PURE__ */ jsx3("summary", { children: "\u6700\u8FD1\u6D3B\u52A8" }),
667
+ host.recent.map((line, index) => /* @__PURE__ */ jsx3("p", { children: line }, `${index}-${line}`))
668
+ ] }) : null
669
+ ] }) : null
670
+ ] }) : null,
671
+ scope === "personal" && config?.auth_status === "expired" ? /* @__PURE__ */ jsx3("p", { className: "mb-2 text-xs", children: "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u586B\u5199" }) : null,
672
+ scope === "personal" && config?.global_ready === false ? /* @__PURE__ */ jsx3("p", { className: "mb-2 text-xs", children: "\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u586B\u5199\u5171\u4EAB\u914D\u7F6E" }) : null,
673
+ scope === "global" ? /* @__PURE__ */ jsx3("p", { className: "mb-2 text-xs", children: "\u5171\u4EAB\u5BC6\u94A5\u7528\u4E8E\u63D2\u4EF6\u670D\u52A1\u3002\u547D\u4EE4\u7C7B\u63D2\u4EF6\u7684\u6C99\u76D2\u4EE3\u7801\u53EF\u4EE5\u8BFB\u53D6\u5B83\uFF1B\u9700\u4FDD\u5BC6\u65F6\u8BF7\u4F7F\u7528\u6258\u7BA1\u670D\u52A1\u3002" }) : null,
674
+ loading ? /* @__PURE__ */ jsx3("p", { className: "text-xs text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E\u2026" }) : null,
675
+ loadFailure ? /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-xs", children: [
676
+ /* @__PURE__ */ jsx3("p", { children: loadFailure }),
677
+ /* @__PURE__ */ jsx3(
678
+ "button",
679
+ {
680
+ type: "button",
681
+ className: "rounded-full border border-[hsl(var(--border))] px-2.5 py-1",
682
+ onClick: () => void load(),
683
+ children: "\u91CD\u8BD5"
684
+ }
685
+ )
686
+ ] }) : null,
687
+ config && !loading ? /* @__PURE__ */ jsx3(
688
+ SessionPluginConfigForm,
689
+ {
690
+ config,
691
+ submitting: saving,
692
+ errors: fieldErrors,
693
+ failure,
694
+ onSubmit: (draft) => void save(draft),
695
+ onCancel: onClose
696
+ },
697
+ formVersion
698
+ ) : null
699
+ ]
700
+ }
701
+ )
702
+ ] });
703
+ }
704
+
705
+ // src/components/PluginConnectorList.tsx
706
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
707
+ function PluginSwitch({
708
+ label,
709
+ checked,
710
+ pending,
711
+ disabled,
712
+ draft = false,
713
+ onToggle
714
+ }) {
715
+ return /* @__PURE__ */ jsx4(
716
+ "button",
717
+ {
718
+ type: "button",
719
+ role: "switch",
720
+ "aria-checked": checked,
721
+ "aria-label": draft ? `${label} \u5F85\u9009` : `${label} \u542F\u7528`,
722
+ disabled,
723
+ onClick: () => onToggle(!checked),
724
+ className: `relative inline-flex h-[18px] w-8 shrink-0 cursor-pointer items-center rounded-full border transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${checked ? "border-transparent bg-[hsl(213_94%_55%)]" : "border-[hsl(var(--border))] bg-[hsl(var(--muted))]"}`,
725
+ children: /* @__PURE__ */ jsx4(
726
+ "span",
727
+ {
728
+ "aria-hidden": "true",
729
+ className: `pointer-events-none flex h-3.5 w-3.5 items-center justify-center rounded-full bg-white shadow transition-transform ${checked ? "translate-x-[16px]" : "translate-x-[2px]"}`,
730
+ children: pending ? /* @__PURE__ */ jsx4(LoaderCircle, { size: 9, className: "animate-spin text-[hsl(var(--muted-foreground))]" }) : null
731
+ }
732
+ )
733
+ }
734
+ );
735
+ }
736
+ function PluginConnectorList({
737
+ client,
738
+ sessionId,
739
+ items,
740
+ inFlight,
741
+ busy,
742
+ mutationError,
743
+ onToggle,
744
+ onRetry,
745
+ onEditConfig,
746
+ configuring,
747
+ onConfigSaved,
748
+ onConfigClose,
749
+ disabledReason,
750
+ unavailableText,
751
+ emptyText,
752
+ draft = false
753
+ }) {
754
+ const configuringItem = configuring ? items.find((item) => item.name === configuring.name) ?? null : null;
755
+ return /* @__PURE__ */ jsxs2("div", { className: "py-0.5", children: [
756
+ disabledReason ? /* @__PURE__ */ jsx4("div", { className: "px-3 py-1.5 text-[11px] text-[hsl(var(--muted-foreground))]", children: disabledReason }) : null,
757
+ items.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "px-3 py-1.5 text-[11px] text-[hsl(var(--muted-foreground))]", children: unavailableText ?? emptyText }) : null,
758
+ items.map((item) => {
759
+ const label = sessionPluginLabel(item);
760
+ const actionable = !disabledReason && item.installed !== false || item.active;
761
+ const pending = Boolean(inFlight[item.name]);
762
+ const checked = item.active;
763
+ return /* @__PURE__ */ jsxs2("div", { className: "group px-3 py-1", children: [
764
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
765
+ /* @__PURE__ */ jsx4(
766
+ SessionPluginIcon,
767
+ {
768
+ client,
769
+ sessionId: item.iconSource === "account" ? null : sessionId,
770
+ plugin: item
771
+ }
772
+ ),
773
+ /* @__PURE__ */ jsx4("span", { className: "min-w-0 flex-1 truncate text-[11px]", title: label, children: label }),
774
+ item.auth_status === "expired" || item.auth_status === "login_required" || item.auth_status === "unavailable" ? /* @__PURE__ */ jsx4("button", { type: "button", disabled: !sessionId || busy, onClick: () => onEditConfig(item), className: "shrink-0 text-[10px] underline", children: item.auth_status === "expired" ? "\u767B\u5F55\u5DF2\u8FC7\u671F" : item.auth_status === "unavailable" ? "\u6682\u65F6\u4E0D\u53EF\u7528" : "\u672A\u767B\u5F55" }) : null,
775
+ !draft && item.config === "configured" ? /* @__PURE__ */ jsx4(
776
+ "button",
777
+ {
778
+ type: "button",
779
+ title: "\u66F4\u65B0\u8BE5\u63D2\u4EF6\u7684\u914D\u7F6E",
780
+ disabled: busy || !sessionId,
781
+ onClick: () => onEditConfig(item),
782
+ className: "shrink-0 text-[10px] text-[hsl(var(--muted-foreground))] underline opacity-0 [@media(hover:none)]:opacity-100 transition-opacity focus-visible:opacity-100 group-hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-50",
783
+ children: "\u66F4\u65B0\u914D\u7F6E"
784
+ }
785
+ ) : null,
786
+ !draft && (item.config === "required" || item.config === "error") ? /* @__PURE__ */ jsx4(
787
+ "button",
788
+ {
789
+ type: "button",
790
+ title: "\u8BE5\u63D2\u4EF6\u9700\u8981\u586B\u5199\u914D\u7F6E",
791
+ disabled: busy || !sessionId,
792
+ onClick: () => onEditConfig(item),
793
+ className: "shrink-0 text-[10px] text-[hsl(var(--muted-foreground))] underline disabled:cursor-not-allowed disabled:opacity-50",
794
+ children: "\u586B\u5199\u914D\u7F6E"
795
+ }
796
+ ) : null,
797
+ /* @__PURE__ */ jsx4(
798
+ PluginSwitch,
799
+ {
800
+ label,
801
+ checked,
802
+ pending,
803
+ draft,
804
+ disabled: !actionable,
805
+ onToggle: (next) => onToggle(item, next)
806
+ }
807
+ )
808
+ ] }),
809
+ !draft && item.active && item.status !== "valid" && item.status !== "required" ? /* @__PURE__ */ jsxs2("div", { className: "mt-0.5 flex items-center gap-2 pl-7 text-[10px] text-[hsl(var(--muted-foreground))]", children: [
810
+ /* @__PURE__ */ jsx4("span", { children: item.reason || "\u63D2\u4EF6\u6682\u4E0D\u53EF\u7528" }),
811
+ /* @__PURE__ */ jsx4(
812
+ "button",
813
+ {
814
+ type: "button",
815
+ disabled: busy,
816
+ onClick: () => onRetry(item),
817
+ className: "underline disabled:cursor-not-allowed disabled:opacity-50",
818
+ children: "\u91CD\u8BD5"
819
+ }
820
+ )
821
+ ] }) : null
822
+ ] }, item.name);
823
+ }),
824
+ mutationError ? /* @__PURE__ */ jsx4("div", { className: "px-3 pt-1 text-[10px] text-[hsl(var(--destructive))]", children: "\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5" }) : null,
825
+ configuring && configuringItem && sessionId ? /* @__PURE__ */ jsx4(
826
+ SessionPluginConfigDialog,
827
+ {
828
+ client,
829
+ sessionId,
830
+ plugin: configuringItem,
831
+ onSaved: onConfigSaved,
832
+ onClose: onConfigClose
833
+ }
834
+ ) : null
835
+ ] });
836
+ }
837
+
838
+ // src/components/plugin-connector.ts
839
+ function catalogToConnectorItems(catalog) {
840
+ return catalog.map((entry) => ({
841
+ name: entry.name,
842
+ display_name: entry.display_name,
843
+ icon: entry.icon,
844
+ iconSource: "account",
845
+ installed: entry.installed,
846
+ connector: entry.connector,
847
+ auth_status: entry.auth_status,
848
+ active: false,
849
+ status: "not_prepared",
850
+ projected: false
851
+ }));
852
+ }
853
+ function mergeConnectorItems(sessionPlugins, catalog) {
854
+ const byName = new Map((catalog ?? []).map((entry) => [entry.name, entry]));
855
+ return sessionPlugins.map((plugin) => {
856
+ const entry = byName.get(plugin.name);
857
+ if (!entry) {
858
+ return {
859
+ name: plugin.name,
860
+ display_name: plugin.display_name ?? null,
861
+ icon: plugin.icon ?? null,
862
+ iconSource: "session",
863
+ installed: plugin.installed,
864
+ active: plugin.active,
865
+ status: plugin.status,
866
+ reason: plugin.reason,
867
+ config: plugin.config,
868
+ projected: plugin.projected
869
+ };
870
+ }
871
+ return {
872
+ name: plugin.name,
873
+ display_name: entry.display_name ?? plugin.display_name ?? null,
874
+ icon: entry.icon,
875
+ iconSource: "account",
876
+ installed: entry.installed,
877
+ connector: entry.connector,
878
+ auth_status: entry.auth_status,
879
+ active: plugin.active,
880
+ status: plugin.status,
881
+ reason: plugin.reason,
882
+ config: plugin.config,
883
+ projected: plugin.projected
884
+ };
885
+ });
886
+ }
887
+ function filterConnectorItems(items, query) {
888
+ const needle = query.trim().toLowerCase();
889
+ if (!needle) return items;
890
+ return items.filter(
891
+ (item) => `${item.display_name ?? ""} ${item.name}`.toLowerCase().includes(needle)
892
+ );
893
+ }
894
+
895
+ // src/components/use-session-plugin-activation.ts
896
+ import { isLocalConnectorName } from "@blade-hq/agent-client/internal";
897
+ import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
898
+ function useSessionPluginActivation({
899
+ client,
900
+ sessionId,
901
+ onChange
902
+ }) {
903
+ const [plugins, setPlugins] = useState3([]);
904
+ const [catalog, setCatalog] = useState3(null);
905
+ const [loading, setLoading] = useState3(false);
906
+ const [listError, setListError] = useState3(false);
907
+ const [mutationError, setMutationError] = useState3(false);
908
+ const [inFlight, setInFlight] = useState3({});
909
+ const [configuring, setConfiguring] = useState3(null);
910
+ const alive = useRef4(true);
911
+ const generation2 = useRef4(0);
912
+ const mutationGenerations = useRef4(/* @__PURE__ */ new Map());
913
+ const desiredActivations = useRef4(/* @__PURE__ */ new Map());
914
+ const pendingMutations = useRef4(/* @__PURE__ */ new Map());
915
+ const submittedActivations = useRef4(/* @__PURE__ */ new Set());
916
+ const serverActive = useRef4(/* @__PURE__ */ new Set());
917
+ const lifetime = useRef4(0);
918
+ const request = useRef4(null);
919
+ const [desiredVersion, setDesiredVersion] = useState3(0);
920
+ const setDesired = useCallback2((name, value) => {
921
+ if (value === null) {
922
+ if (!desiredActivations.current.delete(name)) return;
923
+ } else {
924
+ if (desiredActivations.current.get(name) === value) return;
925
+ desiredActivations.current.set(name, value);
926
+ }
927
+ setDesiredVersion((version) => version + 1);
928
+ }, []);
929
+ const items = useMemo2(() => {
930
+ void desiredVersion;
931
+ const merged = mergeConnectorItems(plugins, catalog);
932
+ return merged.map((item) => {
933
+ if (configuring?.name === item.name && configuring.pendingActivation) {
934
+ return { ...item, active: false };
935
+ }
936
+ const desired = desiredActivations.current.get(item.name);
937
+ return desired === void 0 ? item : { ...item, active: desired };
938
+ });
939
+ }, [catalog, configuring, desiredVersion, plugins]);
940
+ const markInFlight = useCallback2((name, on) => {
941
+ setInFlight((current) => {
942
+ if (on) return current[name] ? current : { ...current, [name]: true };
943
+ if (!current[name]) return current;
944
+ const { [name]: removed, ...rest } = current;
945
+ void removed;
946
+ return rest;
947
+ });
948
+ }, []);
949
+ useEffect5(() => {
950
+ alive.current = true;
951
+ setPlugins([]);
952
+ setCatalog(null);
953
+ setLoading(false);
954
+ setListError(false);
955
+ setMutationError(false);
956
+ setInFlight({});
957
+ setConfiguring(null);
958
+ desiredActivations.current.clear();
959
+ mutationGenerations.current.clear();
960
+ pendingMutations.current.clear();
961
+ submittedActivations.current.clear();
962
+ return () => {
963
+ alive.current = false;
964
+ lifetime.current++;
965
+ generation2.current++;
966
+ request.current?.abort();
967
+ };
968
+ }, [client]);
969
+ const load = useCallback2(async () => {
970
+ if (!alive.current) return;
971
+ request.current?.abort();
972
+ const controller = new AbortController();
973
+ request.current = controller;
974
+ const current = ++generation2.current;
975
+ setLoading(true);
976
+ setListError(false);
977
+ try {
978
+ const result = await client.sessions.listSessionPlugins(sessionId, { signal: controller.signal });
979
+ const withConfig = await Promise.all(
980
+ result.plugins.map(async (item) => {
981
+ if (isLocalConnectorName(item.name) || item.config !== void 0 && item.config !== "unchecked") return item;
982
+ try {
983
+ const { config } = await client.sessions.getSessionPluginConfig(sessionId, item.name, {
984
+ signal: controller.signal
985
+ });
986
+ return { ...item, config: config.state };
987
+ } catch {
988
+ return item;
989
+ }
990
+ })
991
+ );
992
+ if (alive.current && current === generation2.current) {
993
+ serverActive.current = new Set(withConfig.filter((item) => item.active).map((item) => item.name));
994
+ setPlugins(withConfig.map((item) => {
995
+ const desired = desiredActivations.current.get(item.name);
996
+ return desired === void 0 ? item : { ...item, active: desired };
997
+ }));
998
+ }
999
+ } catch {
1000
+ if (alive.current && current === generation2.current && !controller.signal.aborted) setListError(true);
1001
+ } finally {
1002
+ if (alive.current && current === generation2.current) setLoading(false);
1003
+ }
1004
+ }, [client, sessionId]);
1005
+ const loadCatalog = useCallback2(async () => {
1006
+ try {
1007
+ const { plugins: entries } = await client.plugins.catalog();
1008
+ if (alive.current) setCatalog(entries);
1009
+ } catch {
1010
+ }
1011
+ }, [client]);
1012
+ const reload = useCallback2(() => {
1013
+ void load();
1014
+ void loadCatalog();
1015
+ }, [load, loadCatalog]);
1016
+ const abandon = useCallback2((name) => {
1017
+ setDesired(name, null);
1018
+ pendingMutations.current.delete(name);
1019
+ mutationGenerations.current.set(name, (mutationGenerations.current.get(name) ?? 0) + 1);
1020
+ markInFlight(name, false);
1021
+ void load();
1022
+ }, [load, markInFlight, setDesired]);
1023
+ const finish = useCallback2((name, mutation) => {
1024
+ pendingMutations.current.delete(name);
1025
+ setDesired(name, null);
1026
+ if (mutation === mutationGenerations.current.get(name)) markInFlight(name, false);
1027
+ }, [markInFlight, setDesired]);
1028
+ const commitActivation = useCallback2(
1029
+ async (name, active, mutation, startedAt) => {
1030
+ submittedActivations.current.add(name);
1031
+ try {
1032
+ const { plugin } = await client.sessions.setSessionPluginActivation(sessionId, name, active);
1033
+ if (!alive.current || startedAt !== lifetime.current) return;
1034
+ if (mutation !== mutationGenerations.current.get(name)) {
1035
+ const desired = desiredActivations.current.get(name);
1036
+ if (desired !== void 0 && desired !== plugin.active) {
1037
+ const next = (mutationGenerations.current.get(name) ?? 0) + 1;
1038
+ mutationGenerations.current.set(name, next);
1039
+ pendingMutations.current.set(name, next);
1040
+ markInFlight(name, true);
1041
+ await commitActivation(name, desired, next, startedAt);
1042
+ }
1043
+ return;
1044
+ }
1045
+ if (active) serverActive.current.add(name);
1046
+ else serverActive.current.delete(name);
1047
+ finish(name, mutation);
1048
+ setPlugins((current) => current.map((item) => item.name === plugin.name ? { ...item, ...plugin } : item));
1049
+ onChange?.(plugin);
1050
+ if (plugin.active && plugin.status === "required") {
1051
+ setConfiguring({ name: plugin.name, pendingActivation: true });
1052
+ }
1053
+ await load();
1054
+ } catch {
1055
+ if (alive.current && startedAt === lifetime.current && mutation === mutationGenerations.current.get(name)) {
1056
+ pendingMutations.current.delete(name);
1057
+ submittedActivations.current.delete(name);
1058
+ setDesired(name, null);
1059
+ markInFlight(name, false);
1060
+ setMutationError(true);
1061
+ void load();
1062
+ }
1063
+ }
1064
+ },
1065
+ [client, finish, load, markInFlight, onChange, sessionId, setDesired]
1066
+ );
1067
+ const enable = useCallback2(async (name) => {
1068
+ if (!alive.current) return;
1069
+ const startedAt = lifetime.current;
1070
+ const mutation = (mutationGenerations.current.get(name) ?? 0) + 1;
1071
+ mutationGenerations.current.set(name, mutation);
1072
+ pendingMutations.current.set(name, mutation);
1073
+ setDesired(name, true);
1074
+ setMutationError(false);
1075
+ markInFlight(name, true);
1076
+ request.current?.abort();
1077
+ generation2.current++;
1078
+ setLoading(false);
1079
+ let state;
1080
+ try {
1081
+ const result = await client.sessions.prepareSessionPluginConfig(sessionId, name);
1082
+ if (!alive.current || startedAt !== lifetime.current || mutation !== mutationGenerations.current.get(name)) return;
1083
+ state = result.config;
1084
+ } catch {
1085
+ if (!alive.current || startedAt !== lifetime.current || mutation !== mutationGenerations.current.get(name)) return;
1086
+ abandon(name);
1087
+ setMutationError(true);
1088
+ return;
1089
+ }
1090
+ if (state.state === "required") {
1091
+ pendingMutations.current.delete(name);
1092
+ markInFlight(name, false);
1093
+ setConfiguring({ name, pendingActivation: true });
1094
+ return;
1095
+ }
1096
+ if (state.state !== "configured" && state.state !== "not_required") {
1097
+ abandon(name);
1098
+ setMutationError(true);
1099
+ return;
1100
+ }
1101
+ setPlugins((current) => current.map((item) => item.name === name ? { ...item, config: state.state } : item));
1102
+ await commitActivation(name, true, mutation, startedAt);
1103
+ }, [abandon, client, commitActivation, markInFlight, sessionId, setDesired]);
1104
+ const disable = useCallback2((name) => {
1105
+ const startedAt = lifetime.current;
1106
+ request.current?.abort();
1107
+ generation2.current++;
1108
+ setLoading(false);
1109
+ if (!submittedActivations.current.has(name) && !serverActive.current.has(name)) {
1110
+ abandon(name);
1111
+ return;
1112
+ }
1113
+ const mutation = (mutationGenerations.current.get(name) ?? 0) + 1;
1114
+ mutationGenerations.current.set(name, mutation);
1115
+ pendingMutations.current.set(name, mutation);
1116
+ setDesired(name, false);
1117
+ setMutationError(false);
1118
+ markInFlight(name, true);
1119
+ void commitActivation(name, false, mutation, startedAt);
1120
+ }, [abandon, commitActivation, markInFlight, setDesired]);
1121
+ const toggle = useCallback2((item, next) => {
1122
+ if (next) void enable(item.name);
1123
+ else disable(item.name);
1124
+ }, [disable, enable]);
1125
+ const retry = useCallback2((item) => {
1126
+ void enable(item.name);
1127
+ }, [enable]);
1128
+ const editConfig = useCallback2((item) => {
1129
+ setConfiguring({ name: item.name, pendingActivation: false });
1130
+ }, []);
1131
+ const onConfigSaved = useCallback2((state) => {
1132
+ const target = configuring;
1133
+ if (!target) return;
1134
+ setConfiguring(null);
1135
+ void loadCatalog();
1136
+ setPlugins((current) => current.map((item) => item.name === target.name ? { ...item, config: state.state } : item));
1137
+ if (!target.pendingActivation || state.state !== "configured") {
1138
+ void load();
1139
+ return;
1140
+ }
1141
+ const startedAt = lifetime.current;
1142
+ const mutation = (mutationGenerations.current.get(target.name) ?? 0) + 1;
1143
+ mutationGenerations.current.set(target.name, mutation);
1144
+ pendingMutations.current.set(target.name, mutation);
1145
+ setDesired(target.name, true);
1146
+ markInFlight(target.name, true);
1147
+ void commitActivation(target.name, true, mutation, startedAt);
1148
+ }, [commitActivation, configuring, load, loadCatalog, markInFlight, setDesired]);
1149
+ const onConfigClose = useCallback2(() => {
1150
+ const target = configuring;
1151
+ if (!target) return;
1152
+ setConfiguring(null);
1153
+ if (target.pendingActivation) abandon(target.name);
1154
+ else void load();
1155
+ }, [abandon, configuring, load]);
1156
+ return {
1157
+ items,
1158
+ loading,
1159
+ listError,
1160
+ mutationError,
1161
+ inFlight,
1162
+ busy: Object.keys(inFlight).length > 0,
1163
+ configuring,
1164
+ reload,
1165
+ toggle,
1166
+ retry,
1167
+ editConfig,
1168
+ onConfigSaved,
1169
+ onConfigClose
1170
+ };
1171
+ }
1172
+
1173
+ // src/lib/message-shortcuts.ts
1174
+ function usesCommandKey() {
1175
+ if (typeof navigator === "undefined") return false;
1176
+ return /Mac|iPhone|iPad|iPod/i.test(navigator.platform || navigator.userAgent);
1177
+ }
1178
+ function queueShortcutLabel() {
1179
+ return usesCommandKey() ? "\u2318 Enter" : "Ctrl Enter";
1180
+ }
1181
+ function isQueueShortcut(event) {
1182
+ return usesCommandKey() ? event.metaKey : event.ctrlKey;
1183
+ }
1184
+
1185
+ // src/chat/runtime/actions.ts
1186
+ function buildPendingAskUserAnswer(state, message) {
1187
+ if (state.status !== "waiting_for_input") return void 0;
1188
+ const text = extractMessageText(message);
1189
+ if (!text) return void 0;
1190
+ for (let turnIndex = state.turns.length - 1; turnIndex >= 0; turnIndex -= 1) {
1191
+ const turn = state.turns[turnIndex];
1192
+ if (turn.loop_id === "root" || turn.role !== "assistant") continue;
1193
+ for (let toolIndex = turn.tool_calls.length - 1; toolIndex >= 0; toolIndex -= 1) {
1194
+ const toolCall = turn.tool_calls[toolIndex];
1195
+ if (toolCall.status !== "awaiting_answer") continue;
1196
+ if (!toolCall.tool_name.includes("AskUserQuestion")) continue;
1197
+ return { tool_call_id: toolCall.id, selections: {}, custom: { "0": text } };
1198
+ }
1199
+ }
1200
+ return void 0;
1201
+ }
1202
+ function extractMessageText(message) {
1203
+ if (typeof message === "string") return message.trim();
1204
+ return message.filter((part) => part.type === "text").map((part) => part.text.trim()).filter(Boolean).join("\n").trim();
1205
+ }
1206
+ function sendToSession(session, message, mode, askUserAnswer, extras = {}) {
1207
+ return session.send(message, {
1208
+ mode,
1209
+ askUserAnswer: askUserAnswer ?? buildPendingAskUserAnswer(session.getState(), message),
1210
+ model: extras.model,
1211
+ thinkingOverride: extras.thinkingOverride,
1212
+ reasoningLevel: extras.reasoningLevel,
1213
+ refs: extras.refs,
1214
+ contextAttachments: extras.contextAttachments,
1215
+ topicAssetRefs: extras.topicAssetRefs,
1216
+ whatif: extras.whatif,
1217
+ replayDecision: extras.replayDecision,
1218
+ queueWhenRunning: extras.queueWhenRunning
1219
+ });
1220
+ }
1221
+
1222
+ // src/chat/chat-theme.tsx
1223
+ var NEUTRAL = {
1224
+ text: "text-[hsl(var(--muted-foreground))]",
1225
+ border: "border-l-[hsl(var(--swf-border-strong))]",
1226
+ surface: "bg-[hsl(var(--muted))]"
1227
+ };
1228
+ var TONE_CLASSES = {
1229
+ running: NEUTRAL,
1230
+ reasoning: NEUTRAL,
1231
+ success: {
1232
+ text: "text-[hsl(var(--swf-success))]",
1233
+ border: "border-l-[hsl(var(--swf-success))]",
1234
+ surface: "bg-[hsl(var(--swf-success-soft))]"
1235
+ },
1236
+ warning: {
1237
+ text: "text-[hsl(var(--swf-warning))]",
1238
+ border: "border-l-[hsl(var(--swf-warning))]",
1239
+ surface: "bg-[hsl(var(--swf-warning-soft))]"
1240
+ },
1241
+ danger: {
1242
+ text: "text-[hsl(var(--swf-danger))]",
1243
+ border: "border-l-[hsl(var(--swf-danger))]",
1244
+ surface: "bg-[hsl(var(--swf-danger-soft))]"
1245
+ }
1246
+ };
1247
+ function chatTone(tone) {
1248
+ return TONE_CLASSES[tone];
1249
+ }
1250
+
1251
+ // src/lib/use-effect-event.ts
1252
+ import * as React from "react";
1253
+ function useEffectEventShim(callback) {
1254
+ const ref = React.useRef(callback);
1255
+ React.useLayoutEffect(() => {
1256
+ ref.current = callback;
1257
+ });
1258
+ return React.useCallback(((...args) => ref.current(...args)), []);
1259
+ }
1260
+ var builtin = React.useEffectEvent;
1261
+ var useEffectEvent2 = builtin ?? useEffectEventShim;
1262
+
1263
+ export {
1264
+ getInputHistoryGeneration,
1265
+ onInputCachesReset,
1266
+ pluginRefsForSend,
1267
+ getPluginSuggestionItems,
1268
+ queueShortcutLabel,
1269
+ isQueueShortcut,
1270
+ sendToSession,
1271
+ chatTone,
1272
+ RichInputServicesProvider,
1273
+ useRichInputServices,
1274
+ fetchClientQuery,
1275
+ setClientQueryData,
1276
+ useClientQuery,
1277
+ refreshClientQuery,
1278
+ useRefreshWhenRunEnds,
1279
+ useClientSessionSkills,
1280
+ useClientSkillStats,
1281
+ useClientContextStats,
1282
+ useClientBackgroundTasks,
1283
+ refreshClientSessionSkills,
1284
+ useClientSessionModel,
1285
+ useClientModelCatalog,
1286
+ PREFERRED_MODEL_KEY,
1287
+ MODEL_REASONING_LEVELS_KEY,
1288
+ useClientModelPreferences,
1289
+ useModelCatalog,
1290
+ useModelPreferences,
1291
+ useComposerConnectionInfo,
1292
+ useComposerConnection,
1293
+ useSessionSkills,
1294
+ useSkillStats,
1295
+ useContextStats,
1296
+ useBackgroundTaskList,
1297
+ useSessionDataInvalidation,
1298
+ useEffectEvent2 as useEffectEvent,
1299
+ sessionPluginLabel,
1300
+ SessionPluginIcon,
1301
+ SessionPluginConfigDialog,
1302
+ PluginConnectorList,
1303
+ catalogToConnectorItems,
1304
+ mergeConnectorItems,
1305
+ filterConnectorItems,
1306
+ useSessionPluginActivation
1307
+ };
1308
+ //# sourceMappingURL=chunk-F4MWGSKX.js.map