@tangle-network/sandbox-ui 0.106.0 → 0.108.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat.d.ts +1 -287
- package/dist/chat.js +3 -14
- package/dist/{chunk-3GX5BSD4.js → chunk-72UF2H5H.js} +85 -3
- package/dist/{chunk-OI7HEWLZ.js → chunk-D5VXCWUG.js} +25 -10
- package/dist/{chunk-XIXYQNXD.js → chunk-EKYA6AFY.js} +120 -613
- package/dist/globals.css +0 -91
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -28
- package/dist/{sdk-hooks-Cjt_Wq7g.d.ts → sdk-hooks-Djkjq4cp.d.ts} +41 -1
- package/dist/sdk-hooks.d.ts +1 -1
- package/dist/sdk-hooks.js +1 -1
- package/dist/styles.css +0 -91
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.js +1 -1
- package/package.json +1 -3
- package/dist/chunk-WGOOQAW3.js +0 -12
- package/dist/mention-editor-Q3TKSZDL.js +0 -490
package/dist/chat.d.ts
CHANGED
|
@@ -57,292 +57,6 @@ declare const DEFAULT_REASONING_LEVEL_OPTIONS: ReadonlyArray<ReasoningLevelOptio
|
|
|
57
57
|
declare const HARNESS_REASONING_OPTIONS: Partial<Record<HarnessType, ReadonlyArray<ReasoningLevelOption>>>;
|
|
58
58
|
declare function ReasoningLevelPicker({ value, onChange, disabled, className, triggerClassName, options, available, side, avoidCollisions, }: ReasoningLevelPickerProps): React.JSX.Element;
|
|
59
59
|
|
|
60
|
-
/**
|
|
61
|
-
* Limits applied when staging files onto a composer.
|
|
62
|
-
*
|
|
63
|
-
* @deprecated Composer-only helper of the legacy AgentComposer. agent-app
|
|
64
|
-
* (≥0.46) splits this job: accept filtering lives in `filterAcceptedFiles`
|
|
65
|
-
* from `@tangle-network/agent-app/web-react`, and size/count limits belong to
|
|
66
|
-
* the staging queue (`useComposerAttachments`). AgentComposer is frozen and
|
|
67
|
-
* will be removed at sandbox-ui's next breaking release.
|
|
68
|
-
*/
|
|
69
|
-
interface ComposerFileValidationConfig {
|
|
70
|
-
/** Reject any single file larger than this. */
|
|
71
|
-
maxSizeBytes?: number;
|
|
72
|
-
/** Reject files once the staged count would exceed this. */
|
|
73
|
-
maxCount?: number;
|
|
74
|
-
/** Files already staged, counted against `maxCount` before this batch. */
|
|
75
|
-
currentCount?: number;
|
|
76
|
-
/**
|
|
77
|
-
* Comma-separated accept list, same grammar as the native `<input accept>`
|
|
78
|
-
* attribute: file extensions (`.png`), exact MIME types (`image/png`), and
|
|
79
|
-
* MIME wildcards (`image/*`).
|
|
80
|
-
*/
|
|
81
|
-
accept?: string;
|
|
82
|
-
}
|
|
83
|
-
/** A file that failed validation, paired with a human-readable reason.
|
|
84
|
-
*
|
|
85
|
-
* @deprecated Composer-only helper of the legacy AgentComposer. Import
|
|
86
|
-
* `ComposerFileRejection` from `@tangle-network/agent-app/web-react`
|
|
87
|
-
* (agent-app ≥0.46) — the same shape, reported by `ChatComposer`'s
|
|
88
|
-
* `onRejectFiles`. AgentComposer is frozen and will be removed at sandbox-ui's
|
|
89
|
-
* next breaking release. */
|
|
90
|
-
interface ComposerFileRejection {
|
|
91
|
-
file: File;
|
|
92
|
-
reason: string;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Checks a single file against a comma-separated `accept` list using the same
|
|
96
|
-
* grammar as the native `<input accept>` attribute. An undefined or empty
|
|
97
|
-
* `accept` accepts everything.
|
|
98
|
-
*
|
|
99
|
-
* @deprecated Composer-only helper of the legacy AgentComposer. Use
|
|
100
|
-
* `isAcceptedFileType` from `@tangle-network/agent-app/web-react`
|
|
101
|
-
* (agent-app ≥0.46) — the same `accept` grammar. AgentComposer is frozen and
|
|
102
|
-
* will be removed at sandbox-ui's next breaking release.
|
|
103
|
-
*/
|
|
104
|
-
declare function isAcceptedType(file: File, accept?: string): boolean;
|
|
105
|
-
/**
|
|
106
|
-
* Splits a batch of files into what's safe to stage and what's rejected,
|
|
107
|
-
* checking accept type, then size, then the running count against
|
|
108
|
-
* `currentCount` + `maxCount`. Each rejection carries a reason naming the
|
|
109
|
-
* file and the limit it broke. Pure data in, pure data out — no throwing,
|
|
110
|
-
* no console noise, so callers decide how to surface rejections.
|
|
111
|
-
*
|
|
112
|
-
* @deprecated Composer-only helper of the legacy AgentComposer. Use
|
|
113
|
-
* `filterAcceptedFiles` from `@tangle-network/agent-app/web-react`
|
|
114
|
-
* (agent-app ≥0.46) for accept filtering; size and count limits belong to the
|
|
115
|
-
* staging queue (`useComposerAttachments`). AgentComposer is frozen and will
|
|
116
|
-
* be removed at sandbox-ui's next breaking release.
|
|
117
|
-
*/
|
|
118
|
-
declare function validateComposerFiles(files: File[] | FileList, config: ComposerFileValidationConfig): {
|
|
119
|
-
accepted: File[];
|
|
120
|
-
rejected: ComposerFileRejection[];
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/** A selectable item in the `@`-mention popover. Generic — file semantics live
|
|
124
|
-
* in the consuming app, not here.
|
|
125
|
-
*
|
|
126
|
-
* @deprecated Part of the legacy AgentComposer API. Import `MentionItem` from
|
|
127
|
-
* `@tangle-network/agent-app/web-react` (agent-app ≥0.46) — the same contract,
|
|
128
|
-
* now owned by the package that defines it. AgentComposer is frozen and will
|
|
129
|
-
* be removed at sandbox-ui's next breaking release. */
|
|
130
|
-
interface MentionItem {
|
|
131
|
-
id: string;
|
|
132
|
-
label: string;
|
|
133
|
-
detail?: string;
|
|
134
|
-
kind?: string;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Opt-in `@`-mention support. Present ⇒ the composer swaps its textarea for a
|
|
138
|
-
* TipTap rich input that renders mentions as atomic pills; absent ⇒ exactly
|
|
139
|
-
* today's plain-textarea behavior. Data-agnostic: the app supplies items.
|
|
140
|
-
*
|
|
141
|
-
* @deprecated Part of the legacy AgentComposer API. `ComposerMentionProp` in
|
|
142
|
-
* `@tangle-network/agent-app/web-react` (agent-app ≥0.46) is the same contract
|
|
143
|
-
* on `ChatComposer`'s `mention` prop. AgentComposer is frozen and will be
|
|
144
|
-
* removed at sandbox-ui's next breaking release.
|
|
145
|
-
*/
|
|
146
|
-
interface AgentComposerMention {
|
|
147
|
-
/** The character that opens the popover. Default "@". */
|
|
148
|
-
trigger?: string;
|
|
149
|
-
/** Async provider called with the query typed after the trigger. */
|
|
150
|
-
fetchItems: (query: string) => Promise<MentionItem[]>;
|
|
151
|
-
/** Fired with the mentions currently in the document whenever they change. */
|
|
152
|
-
onMentionsChange?: (mentions: MentionItem[]) => void;
|
|
153
|
-
/** Custom row renderer for a popover item. */
|
|
154
|
-
renderItem?: (item: MentionItem) => React.ReactNode;
|
|
155
|
-
/** Shown when a fetch resolves to zero items. Default "No matches". */
|
|
156
|
-
emptyText?: string;
|
|
157
|
-
/**
|
|
158
|
-
* Extra classes merged onto the suggestion popover's root element
|
|
159
|
-
* (`role="listbox"`), applied last so they win over the component's own —
|
|
160
|
-
* the supported way to retheme the popover instead of targeting it by its
|
|
161
|
-
* ARIA attributes.
|
|
162
|
-
*/
|
|
163
|
-
popoverClassName?: string;
|
|
164
|
-
}
|
|
165
|
-
/** A staged attachment shown as a chip above the input.
|
|
166
|
-
*
|
|
167
|
-
* @deprecated Part of the legacy AgentComposer API. Import `ComposerFile` from
|
|
168
|
-
* `@tangle-network/agent-app/web-react` (agent-app ≥0.46) — the same shape,
|
|
169
|
-
* consumed by `ChatComposer`'s `pendingFiles`. AgentComposer is frozen and
|
|
170
|
-
* will be removed at sandbox-ui's next breaking release. */
|
|
171
|
-
interface ComposerFile {
|
|
172
|
-
id: string;
|
|
173
|
-
name: string;
|
|
174
|
-
size?: number;
|
|
175
|
-
kind: "file" | "folder";
|
|
176
|
-
/** File count for a folder chip. */
|
|
177
|
-
fileCount?: number;
|
|
178
|
-
status: "pending" | "uploading" | "ready" | "error";
|
|
179
|
-
/**
|
|
180
|
-
* Object URL for an image thumbnail. The app owns creation and revocation
|
|
181
|
-
* (e.g. `URL.createObjectURL` on stage, `URL.revokeObjectURL` on remove) —
|
|
182
|
-
* the composer only ever reads it.
|
|
183
|
-
*/
|
|
184
|
-
previewUrl?: string;
|
|
185
|
-
/** Shown on the chip when `status === "error"`. */
|
|
186
|
-
errorMessage?: string;
|
|
187
|
-
}
|
|
188
|
-
/** Context the agent will see alongside the next message.
|
|
189
|
-
*
|
|
190
|
-
* @deprecated Part of the legacy AgentComposer API. Import
|
|
191
|
-
* `ComposerContextItem` from `@tangle-network/agent-app/web-react`
|
|
192
|
-
* (agent-app ≥0.46) — the same shape, consumed by `ChatComposer`'s
|
|
193
|
-
* `contextItems`. AgentComposer is frozen and will be removed at sandbox-ui's
|
|
194
|
-
* next breaking release. */
|
|
195
|
-
interface ComposerContextItem {
|
|
196
|
-
id: string;
|
|
197
|
-
label: string;
|
|
198
|
-
icon?: React.ReactNode;
|
|
199
|
-
/** Omit to render a non-removable chip. */
|
|
200
|
-
onRemove?: () => void;
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Props of the legacy {@link AgentComposer}.
|
|
204
|
-
*
|
|
205
|
-
* @deprecated Part of the legacy AgentComposer API. `ChatComposerProps` in
|
|
206
|
-
* `@tangle-network/agent-app/web-react` (agent-app ≥0.46) covers every prop
|
|
207
|
-
* here — see {@link AgentComposer} for the four renames. AgentComposer is
|
|
208
|
-
* frozen and will be removed at sandbox-ui's next breaking release.
|
|
209
|
-
*/
|
|
210
|
-
interface AgentComposerProps {
|
|
211
|
-
/** Composer text (controlled). */
|
|
212
|
-
value: string;
|
|
213
|
-
onChange: (value: string) => void;
|
|
214
|
-
/** Fired on Enter (without Shift) or the send button. */
|
|
215
|
-
onSubmit: () => void;
|
|
216
|
-
placeholder?: string;
|
|
217
|
-
/** Disables typing and sending — e.g. while restoring. */
|
|
218
|
-
disabled?: boolean;
|
|
219
|
-
/**
|
|
220
|
-
* A turn is in flight. Spins the send button; paired with `onCancel` it
|
|
221
|
-
* becomes a Stop button instead, so the user can interrupt the stream.
|
|
222
|
-
*/
|
|
223
|
-
busy?: boolean;
|
|
224
|
-
/** Stop the in-flight turn. With `busy`, replaces send with a Stop button. */
|
|
225
|
-
onCancel?: () => void;
|
|
226
|
-
/**
|
|
227
|
-
* The composer's control strip (profile · harness · model · effort pickers,
|
|
228
|
-
* token meters, …), rendered in the bottom row. Required — the legacy
|
|
229
|
-
* built-in strip was removed with the legacy pickers: pass
|
|
230
|
-
* `AgentSessionControls` from `@tangle-network/agent-app/web-react` (or your
|
|
231
|
-
* own composition), or `null` for a bare composer.
|
|
232
|
-
*/
|
|
233
|
-
controls: React.ReactNode;
|
|
234
|
-
/** Extra content left of the send button (token meter, cost, status). */
|
|
235
|
-
trailing?: React.ReactNode;
|
|
236
|
-
/** Context the agent will see alongside the next message. */
|
|
237
|
-
contextItems?: ReadonlyArray<ComposerContextItem>;
|
|
238
|
-
/**
|
|
239
|
-
* Attachments are opt-in: pass `onAttach` to show the attach button, accept
|
|
240
|
-
* drag-and-drop onto the composer, and render `attachments` as chips.
|
|
241
|
-
*/
|
|
242
|
-
onAttach?: (files: FileList) => void;
|
|
243
|
-
/** Show a folder-attach button; falls back to `onAttach` if unset. */
|
|
244
|
-
onAttachFolder?: (files: FileList) => void;
|
|
245
|
-
attachments?: ReadonlyArray<ComposerFile>;
|
|
246
|
-
onRemoveFile?: (id: string) => void;
|
|
247
|
-
/** When set, chips with `status === "error"` render a retry button. */
|
|
248
|
-
onRetryFile?: (id: string) => void;
|
|
249
|
-
/**
|
|
250
|
-
* File types the composer takes, in the native `<input accept>` grammar.
|
|
251
|
-
* Enforced on every ingress path — the picker dialog, drag-and-drop, and
|
|
252
|
-
* clipboard paste — so a type the picker won't offer can't arrive by another
|
|
253
|
-
* route. Non-matching files go to `onRejectFiles` and never reach
|
|
254
|
-
* `onAttach`. Folder attach is exempt (directory selection has no native
|
|
255
|
-
* accept semantics); size/count limits are the app's job in `onAttach`.
|
|
256
|
-
*/
|
|
257
|
-
accept?: string;
|
|
258
|
-
/**
|
|
259
|
-
* Called with the files `accept` filtered out of a drop/paste/pick, each
|
|
260
|
-
* with a human-readable reason. Without it rejection is silent — the same
|
|
261
|
-
* feedback the native picker gives for a type it won't offer.
|
|
262
|
-
*/
|
|
263
|
-
onRejectFiles?: (rejections: ComposerFileRejection[]) => void;
|
|
264
|
-
dropTitle?: string;
|
|
265
|
-
dropDescription?: string;
|
|
266
|
-
/**
|
|
267
|
-
* `canSend` ignores `busy` so Enter/onSubmit keep firing while a turn is
|
|
268
|
-
* streaming. For composers that queue the next turn instead of blocking on
|
|
269
|
-
* the current one — the Stop-button rendering rule (`busy && onCancel`) is
|
|
270
|
-
* unaffected, so the button still flips to Stop while this is set. Default
|
|
271
|
-
* false.
|
|
272
|
-
*/
|
|
273
|
-
canSubmitWhileBusy?: boolean;
|
|
274
|
-
/**
|
|
275
|
-
* Allow submitting with empty text when at least one attachment is staged
|
|
276
|
-
* (an image-only message). Counts attachments of ANY status — a gate on
|
|
277
|
-
* `ready` would make Enter silently dead while an upload is in flight,
|
|
278
|
-
* with no way for the app to explain why. The app's onSubmit owns the send
|
|
279
|
-
* policy (block, queue, or toast on pending/errored files) and what the
|
|
280
|
-
* message body becomes. Default false.
|
|
281
|
-
*/
|
|
282
|
-
canSubmitAttachmentsOnly?: boolean;
|
|
283
|
-
/**
|
|
284
|
-
* Opt-in `@`-mentions. When set, the textarea is replaced by a rich input
|
|
285
|
-
* that renders mentions as atomic pills and serializes them to `@<id>` in
|
|
286
|
-
* `value`. Omit for exactly today's plain-textarea behavior.
|
|
287
|
-
*/
|
|
288
|
-
mention?: AgentComposerMention;
|
|
289
|
-
/** Cmd/Ctrl+L focuses the input and shows a hint. Default false. */
|
|
290
|
-
focusShortcut?: boolean;
|
|
291
|
-
/** Minimum textarea rows before it grows. Default 2. */
|
|
292
|
-
minRows?: number;
|
|
293
|
-
/** Max pixel height before the textarea scrolls. Default 200. */
|
|
294
|
-
maxHeight?: number;
|
|
295
|
-
className?: string;
|
|
296
|
-
sendLabel?: string;
|
|
297
|
-
autoFocus?: boolean;
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* @deprecated Use `ChatComposer` from `@tangle-network/agent-app/web-react` —
|
|
301
|
-
* since agent-app 0.46 it is a strict superset of this component. Everything
|
|
302
|
-
* that once lived only here now lives there: `mention` (the same
|
|
303
|
-
* `ComposerMentionProp` contract), `contextItems`, the full attachment
|
|
304
|
-
* lifecycle (`pendingFiles` with `previewUrl` thumbnails, `onRejectFiles`,
|
|
305
|
-
* `onRetryFile`, clipboard paste with `pasted-image-<n>.<ext>` naming),
|
|
306
|
-
* `autoFocus`, `canSubmitAttachmentsOnly`, `canSubmitWhileBusy`, `trailing`,
|
|
307
|
-
* `maxHeight`, and `minRows`. Migration is four prop renames — `onChange` →
|
|
308
|
-
* `onValueChange`, `onSubmit()` → `onSend(message)`, `busy` → `isStreaming`,
|
|
309
|
-
* `attachments` → `pendingFiles`; every other prop carries across unchanged.
|
|
310
|
-
* Add `sendVariant="icon"` and `focusShortcut={false}` where a surface wants
|
|
311
|
-
* this component's exact chrome. AgentComposer is frozen and will be removed
|
|
312
|
-
* at sandbox-ui's next breaking release.
|
|
313
|
-
*
|
|
314
|
-
* The input itself: one rounded surface holding an auto-growing
|
|
315
|
-
* textarea and a bottom row that runs left → right — attach buttons, the
|
|
316
|
-
* control strip (supplied via the required `controls` prop), trailing slot,
|
|
317
|
-
* and the send button on the far right. The strip is explicit: the canonical
|
|
318
|
-
* pickers are `ModelPicker` / `EffortPicker` / `AgentSessionControls` from
|
|
319
|
-
* `@tangle-network/agent-app/web-react` — this component only owns the slot.
|
|
320
|
-
*
|
|
321
|
-
* Opt-in extras cover what apps otherwise hand-roll:
|
|
322
|
-
* file/folder attachments with drag-and-drop + chips + clipboard paste
|
|
323
|
-
* (`onAttach` also gates pasting files onto the textarea, auto-naming
|
|
324
|
-
* generic clipboard image blobs `pasted-image-<n>.<ext>`), a streaming Stop
|
|
325
|
-
* button (`busy` + `onCancel`), and a Cmd/Ctrl+L focus shortcut.
|
|
326
|
-
*/
|
|
327
|
-
declare function AgentComposer({ value, onChange, onSubmit, placeholder, disabled, busy, onCancel, controls, trailing, contextItems, onAttach, onAttachFolder, attachments, onRemoveFile, onRetryFile, accept, onRejectFiles, dropTitle, dropDescription, canSubmitWhileBusy, canSubmitAttachmentsOnly, mention, focusShortcut, minRows, maxHeight, className, sendLabel, autoFocus, }: AgentComposerProps): React.JSX.Element;
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Visual contract for a rendered mention pill. The TipTap editor extension in
|
|
331
|
-
* `mention-editor.tsx` styles its inline atom node with this, and any other
|
|
332
|
-
* surface that renders a resolved mention (e.g. a sent-message transcript)
|
|
333
|
-
* should use the same constant so the two stay one visual contract instead of
|
|
334
|
-
* silently drifting apart. Kept in its own module (no TipTap import) so
|
|
335
|
-
* importing it never pulls the lazily-loaded editor chunk into a consumer's
|
|
336
|
-
* bundle.
|
|
337
|
-
*
|
|
338
|
-
* @deprecated Mention-pill contract of the legacy AgentComposer mention
|
|
339
|
-
* feature. Import `MENTION_PILL_CLASS` from
|
|
340
|
-
* `@tangle-network/agent-app/web-react` (agent-app ≥0.46) — `ChatComposer`
|
|
341
|
-
* owns the mention stack now. AgentComposer is frozen and will be removed at
|
|
342
|
-
* sandbox-ui's next breaking release.
|
|
343
|
-
*/
|
|
344
|
-
declare const MENTION_PILL_CLASS: string;
|
|
345
|
-
|
|
346
60
|
/**
|
|
347
61
|
* An agent profile: a named bundle of toolset + persona layered over the same
|
|
348
62
|
* model and session. `builtin` profiles are the product's shipped modes (e.g.
|
|
@@ -513,4 +227,4 @@ declare function createFetchTransport(opts: {
|
|
|
513
227
|
fetchImpl?: typeof fetch;
|
|
514
228
|
}): ArtifactAgentDockTransport;
|
|
515
229
|
|
|
516
|
-
export {
|
|
230
|
+
export { type AgentProfileCapability, type AgentProfileDraft, type AgentProfileOption, AgentProfilePicker, type AgentProfilePickerProps, ArtifactAgentDock, type ArtifactAgentDockProps, type ArtifactAgentDockTransport, type ArtifactDockMessage, type ArtifactDockStreamEvent, type ArtifactKind, type ArtifactScope, DEFAULT_REASONING_LEVEL_OPTIONS, HARNESS_REASONING_OPTIONS, type ReasoningLevel, type ReasoningLevelOption, ReasoningLevelPicker, type ReasoningLevelPickerProps, createFetchTransport };
|
package/dist/chat.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AgentComposer,
|
|
3
2
|
AgentProfilePicker,
|
|
4
3
|
AgentTimeline,
|
|
5
4
|
ArtifactAgentDock,
|
|
@@ -12,19 +11,12 @@ import {
|
|
|
12
11
|
ThinkingIndicator,
|
|
13
12
|
UserMessage,
|
|
14
13
|
createFetchTransport,
|
|
15
|
-
isAcceptedType,
|
|
16
14
|
modelProvider,
|
|
17
|
-
snapHarnessToModel
|
|
18
|
-
|
|
19
|
-
} from "./chunk-XIXYQNXD.js";
|
|
20
|
-
import "./chunk-TK46XFLM.js";
|
|
21
|
-
import {
|
|
22
|
-
MENTION_PILL_CLASS
|
|
23
|
-
} from "./chunk-WGOOQAW3.js";
|
|
15
|
+
snapHarnessToModel
|
|
16
|
+
} from "./chunk-EKYA6AFY.js";
|
|
24
17
|
import "./chunk-EI44GEQ5.js";
|
|
25
18
|
import "./chunk-CZ7CSFO4.js";
|
|
26
19
|
export {
|
|
27
|
-
AgentComposer,
|
|
28
20
|
AgentProfilePicker,
|
|
29
21
|
AgentTimeline,
|
|
30
22
|
ArtifactAgentDock,
|
|
@@ -32,14 +24,11 @@ export {
|
|
|
32
24
|
ChatMessage,
|
|
33
25
|
DEFAULT_REASONING_LEVEL_OPTIONS,
|
|
34
26
|
HARNESS_REASONING_OPTIONS,
|
|
35
|
-
MENTION_PILL_CLASS,
|
|
36
27
|
MessageList,
|
|
37
28
|
ReasoningLevelPicker,
|
|
38
29
|
ThinkingIndicator,
|
|
39
30
|
UserMessage,
|
|
40
31
|
createFetchTransport,
|
|
41
|
-
isAcceptedType,
|
|
42
32
|
modelProvider,
|
|
43
|
-
snapHarnessToModel
|
|
44
|
-
validateComposerFiles
|
|
33
|
+
snapHarnessToModel
|
|
45
34
|
};
|
|
@@ -104,6 +104,33 @@ async function fetchJson(url, token, init) {
|
|
|
104
104
|
if (!res.ok) throw await responseError(res, "Request failed");
|
|
105
105
|
return res.json();
|
|
106
106
|
}
|
|
107
|
+
function readDegradation(raw) {
|
|
108
|
+
if (!raw || typeof raw !== "object") return null;
|
|
109
|
+
const record = raw;
|
|
110
|
+
if (typeof record.message !== "string") return null;
|
|
111
|
+
return {
|
|
112
|
+
kind: "hub-connections",
|
|
113
|
+
reason: typeof record.reason === "string" ? record.reason : "unknown",
|
|
114
|
+
message: record.message,
|
|
115
|
+
requestShape: typeof record.requestShape === "string" ? record.requestShape : "unknown"
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function bumpHubFrameEpoch(epochs, sessionId) {
|
|
119
|
+
epochs.set(sessionId, (epochs.get(sessionId) ?? 0) + 1);
|
|
120
|
+
}
|
|
121
|
+
function withDegradation(prev, sessionId, value) {
|
|
122
|
+
const current = prev.get(sessionId) ?? null;
|
|
123
|
+
if (value === null) {
|
|
124
|
+
if (current === null) return prev;
|
|
125
|
+
const next = new Map(prev);
|
|
126
|
+
next.delete(sessionId);
|
|
127
|
+
return next;
|
|
128
|
+
}
|
|
129
|
+
if (current && current.reason === value.reason && current.message === value.message && current.requestShape === value.requestShape) {
|
|
130
|
+
return prev;
|
|
131
|
+
}
|
|
132
|
+
return new Map(prev).set(sessionId, value);
|
|
133
|
+
}
|
|
107
134
|
function useSessionStream({
|
|
108
135
|
apiUrl,
|
|
109
136
|
token,
|
|
@@ -119,11 +146,16 @@ function useSessionStream({
|
|
|
119
146
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
120
147
|
const [error, setError] = useState(null);
|
|
121
148
|
const [connected, setConnected] = useState(false);
|
|
149
|
+
const [degradationBySession, setDegradationBySession] = useState(() => /* @__PURE__ */ new Map());
|
|
150
|
+
const degradation = degradationBySession.get(sessionId) ?? null;
|
|
122
151
|
const abortRef = useRef(null);
|
|
123
152
|
const reconnectTimerRef = useRef(null);
|
|
124
153
|
const activeAssistantMessageIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
125
154
|
const completedExecutionIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
126
155
|
const lastEventIdRef = useRef("");
|
|
156
|
+
const hubFrameEpochRef = useRef(/* @__PURE__ */ new Map());
|
|
157
|
+
const seedGenerationRef = useRef(/* @__PURE__ */ new Map());
|
|
158
|
+
const executionScopedStreamRef = useRef(false);
|
|
127
159
|
const insertionCounterRef = useRef(0);
|
|
128
160
|
const echoCounterRef = useRef(0);
|
|
129
161
|
const pendingEchoesRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -169,6 +201,29 @@ function useSessionStream({
|
|
|
169
201
|
setError(msg);
|
|
170
202
|
}
|
|
171
203
|
}, [apiUrl, token, sessionId]);
|
|
204
|
+
const seedDegradation = useCallback(async () => {
|
|
205
|
+
if (!token || !sessionId || !apiUrl) return;
|
|
206
|
+
const epochAtRequest = hubFrameEpochRef.current.get(sessionId) ?? 0;
|
|
207
|
+
const generation = (seedGenerationRef.current.get(sessionId) ?? 0) + 1;
|
|
208
|
+
seedGenerationRef.current.set(sessionId, generation);
|
|
209
|
+
let seeded;
|
|
210
|
+
try {
|
|
211
|
+
const url = `${apiUrl}/session/sessions/${encodeURIComponent(sessionId)}`;
|
|
212
|
+
const status = await fetchJson(url, token);
|
|
213
|
+
const field = status.hubConnectionsDegraded;
|
|
214
|
+
if (field === void 0 || field === null) {
|
|
215
|
+
seeded = null;
|
|
216
|
+
} else {
|
|
217
|
+
seeded = readDegradation(field);
|
|
218
|
+
if (!seeded) return;
|
|
219
|
+
}
|
|
220
|
+
} catch {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if ((hubFrameEpochRef.current.get(sessionId) ?? 0) !== epochAtRequest) return;
|
|
224
|
+
if (seedGenerationRef.current.get(sessionId) !== generation) return;
|
|
225
|
+
setDegradationBySession((prev) => withDegradation(prev, sessionId, seeded));
|
|
226
|
+
}, [apiUrl, token, sessionId]);
|
|
172
227
|
const connectSSE = useCallback(async () => {
|
|
173
228
|
if (!token || !sessionId || !apiUrl || !enabled) return;
|
|
174
229
|
if (reconnectTimerRef.current) {
|
|
@@ -181,6 +236,7 @@ function useSessionStream({
|
|
|
181
236
|
try {
|
|
182
237
|
const eventQuery = new URLSearchParams({ sessionId });
|
|
183
238
|
const activeExecutionIds = [...activeAssistantMessageIdsRef.current];
|
|
239
|
+
executionScopedStreamRef.current = activeExecutionIds.length > 0;
|
|
184
240
|
if (activeExecutionIds.length === 1) {
|
|
185
241
|
eventQuery.set("executionId", activeExecutionIds[0]);
|
|
186
242
|
} else if (activeExecutionIds.length > 1) {
|
|
@@ -198,6 +254,7 @@ function useSessionStream({
|
|
|
198
254
|
if (!res.ok) throw await responseError(res, "Live response connection failed");
|
|
199
255
|
setConnected(true);
|
|
200
256
|
setError(null);
|
|
257
|
+
void seedDegradation();
|
|
201
258
|
const reader = res.body?.getReader();
|
|
202
259
|
if (!reader) throw new Error("No response body");
|
|
203
260
|
const decoder = new TextDecoder();
|
|
@@ -387,7 +444,22 @@ function useSessionStream({
|
|
|
387
444
|
const executionId = props.executionId ?? props.executionID ?? "";
|
|
388
445
|
const stillStreaming = retireExecution(executionId);
|
|
389
446
|
setIsStreaming(stillStreaming);
|
|
390
|
-
if (!stillStreaming)
|
|
447
|
+
if (!stillStreaming) {
|
|
448
|
+
refetch();
|
|
449
|
+
if (executionScopedStreamRef.current) void seedDegradation();
|
|
450
|
+
}
|
|
451
|
+
} else if (type === "hub.connections.degraded") {
|
|
452
|
+
const framedSessionId = typeof props.sessionId === "string" ? props.sessionId : null;
|
|
453
|
+
if (!framedSessionId) return;
|
|
454
|
+
const value = readDegradation(props);
|
|
455
|
+
if (!value) return;
|
|
456
|
+
bumpHubFrameEpoch(hubFrameEpochRef.current, framedSessionId);
|
|
457
|
+
setDegradationBySession((prev) => withDegradation(prev, framedSessionId, value));
|
|
458
|
+
} else if (type === "hub.connections.restored") {
|
|
459
|
+
const framedSessionId = typeof props.sessionId === "string" ? props.sessionId : null;
|
|
460
|
+
if (!framedSessionId) return;
|
|
461
|
+
bumpHubFrameEpoch(hubFrameEpochRef.current, framedSessionId);
|
|
462
|
+
setDegradationBySession((prev) => withDegradation(prev, framedSessionId, null));
|
|
391
463
|
} else if (type === "session.error") {
|
|
392
464
|
const executionId = props.executionId ?? props.executionID ?? "";
|
|
393
465
|
const stillStreaming = retireExecution(executionId);
|
|
@@ -395,7 +467,7 @@ function useSessionStream({
|
|
|
395
467
|
setError(formatError(readErrorDetails(props), "Agent response failed"));
|
|
396
468
|
if (!stillStreaming) refetch();
|
|
397
469
|
}
|
|
398
|
-
}, [refetch]);
|
|
470
|
+
}, [refetch, seedDegradation]);
|
|
399
471
|
handleSSEEventRef.current = handleSSEEvent;
|
|
400
472
|
const send = useCallback(async (text, options) => {
|
|
401
473
|
if (!token || !sessionId || !apiUrl || !enabled) {
|
|
@@ -505,7 +577,17 @@ function useSessionStream({
|
|
|
505
577
|
setConnected(false);
|
|
506
578
|
};
|
|
507
579
|
}, [enabled, token, sessionId, refetch, connectSSE]);
|
|
508
|
-
return {
|
|
580
|
+
return {
|
|
581
|
+
messages,
|
|
582
|
+
partMap,
|
|
583
|
+
isStreaming,
|
|
584
|
+
send,
|
|
585
|
+
abort,
|
|
586
|
+
refetch,
|
|
587
|
+
error,
|
|
588
|
+
connected,
|
|
589
|
+
degradation
|
|
590
|
+
};
|
|
509
591
|
}
|
|
510
592
|
|
|
511
593
|
// src/hooks/use-sidecar-auth.ts
|
|
@@ -619,12 +619,24 @@ function DirectoryPane({
|
|
|
619
619
|
import { Binary, ShieldCheck, TerminalSquare } from "lucide-react";
|
|
620
620
|
|
|
621
621
|
// src/workspace/status-banner.tsx
|
|
622
|
-
import { Loader2, AlertCircle, CheckCircle, Wifi } from "lucide-react";
|
|
622
|
+
import { Loader2, AlertCircle, AlertTriangle, CheckCircle, Wifi } from "lucide-react";
|
|
623
623
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
624
624
|
var BANNER_STYLES = {
|
|
625
625
|
provisioning: { bg: "bg-primary/5", border: "border-primary/20", text: "text-primary", icon: Loader2 },
|
|
626
626
|
connecting: { bg: "bg-[var(--code-number)]/5", border: "border-[var(--code-number)]/20", text: "text-[var(--code-number)]", icon: Wifi },
|
|
627
627
|
error: { bg: "bg-[var(--code-error)]/5", border: "border-[var(--code-error)]/20", text: "text-[var(--code-error)]", icon: AlertCircle },
|
|
628
|
+
// Degraded, not broken: the thing still works, with less than was asked for.
|
|
629
|
+
// `error` would overstate it and `info` would understate it, and a notice
|
|
630
|
+
// styled as neither gets read as neither. Uses the brand's theme-reactive
|
|
631
|
+
// `--surface-warning-*` triple rather than a `warning` color utility —
|
|
632
|
+
// `warning` is not registered in this package's `@theme`, so `bg-warning`
|
|
633
|
+
// and friends emit no rule at all.
|
|
634
|
+
warning: {
|
|
635
|
+
bg: "bg-[var(--surface-warning-bg)]",
|
|
636
|
+
border: "border-[var(--surface-warning-border)]",
|
|
637
|
+
text: "text-[var(--surface-warning-text)]",
|
|
638
|
+
icon: AlertTriangle
|
|
639
|
+
},
|
|
628
640
|
success: { bg: "bg-[var(--code-success)]/5", border: "border-[var(--code-success)]/20", text: "text-[var(--code-success)]", icon: CheckCircle },
|
|
629
641
|
info: { bg: "bg-surface-container-high", border: "border-[var(--md3-outline-variant)]", text: "text-muted-foreground", icon: AlertCircle }
|
|
630
642
|
};
|
|
@@ -632,6 +644,7 @@ function StatusBanner({ type, message, detail, onDismiss, className }) {
|
|
|
632
644
|
const style = BANNER_STYLES[type];
|
|
633
645
|
const Icon = style.icon;
|
|
634
646
|
const isAnimated = type === "provisioning" || type === "connecting";
|
|
647
|
+
const role = type === "error" ? "alert" : "status";
|
|
635
648
|
return /* @__PURE__ */ jsxs3(
|
|
636
649
|
"div",
|
|
637
650
|
{
|
|
@@ -642,15 +655,17 @@ function StatusBanner({ type, message, detail, onDismiss, className }) {
|
|
|
642
655
|
className
|
|
643
656
|
),
|
|
644
657
|
children: [
|
|
645
|
-
/* @__PURE__ */
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
658
|
+
/* @__PURE__ */ jsxs3("div", { role, className: "flex min-w-0 items-center gap-2.5", children: [
|
|
659
|
+
/* @__PURE__ */ jsx4(
|
|
660
|
+
Icon,
|
|
661
|
+
{
|
|
662
|
+
className: cn("h-4 w-4 shrink-0", style.text, isAnimated && "animate-spin"),
|
|
663
|
+
...isAnimated ? { "data-motion": "essential" } : {}
|
|
664
|
+
}
|
|
665
|
+
),
|
|
666
|
+
/* @__PURE__ */ jsx4("span", { className: "font-medium text-foreground", children: message }),
|
|
667
|
+
detail && /* @__PURE__ */ jsx4("span", { className: "font-mono text-xs text-muted-foreground tabular-nums", children: detail })
|
|
668
|
+
] }),
|
|
654
669
|
onDismiss && /* @__PURE__ */ jsx4(
|
|
655
670
|
"button",
|
|
656
671
|
{
|