@tangle-network/sandbox-ui 0.104.1 → 0.105.1

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 CHANGED
@@ -60,10 +60,9 @@ declare function ReasoningLevelPicker({ value, onChange, disabled, className, tr
60
60
  /**
61
61
  * Limits applied when staging files onto a composer.
62
62
  *
63
- * @deprecated Composer-only helper of the legacy AgentComposer. Use
64
- * ChatComposer from `@tangle-network/agent-app/web-react` — the canonical
65
- * composer. AgentComposer is frozen and will be removed at sandbox-ui's next
66
- * breaking release.
63
+ * Serves {@link AgentComposer}'s attachment lifecycle. `ChatComposer` in
64
+ * `@tangle-network/agent-app/web-react` stages files without a validation or
65
+ * retry step, so it has no equivalent.
67
66
  */
68
67
  interface ComposerFileValidationConfig {
69
68
  /** Reject any single file larger than this. */
@@ -81,10 +80,9 @@ interface ComposerFileValidationConfig {
81
80
  }
82
81
  /** A file that failed validation, paired with a human-readable reason.
83
82
  *
84
- * @deprecated Composer-only helper of the legacy AgentComposer. Use
85
- * ChatComposer from `@tangle-network/agent-app/web-react` — the canonical
86
- * composer. AgentComposer is frozen and will be removed at sandbox-ui's next
87
- * breaking release. */
83
+ * Serves {@link AgentComposer}'s attachment lifecycle. `ChatComposer` in
84
+ * `@tangle-network/agent-app/web-react` stages files without a validation or
85
+ * retry step, so it has no equivalent. */
88
86
  interface ComposerFileRejection {
89
87
  file: File;
90
88
  reason: string;
@@ -94,10 +92,9 @@ interface ComposerFileRejection {
94
92
  * grammar as the native `<input accept>` attribute. An undefined or empty
95
93
  * `accept` accepts everything.
96
94
  *
97
- * @deprecated Composer-only helper of the legacy AgentComposer. Use
98
- * ChatComposer from `@tangle-network/agent-app/web-react` — the canonical
99
- * composer. AgentComposer is frozen and will be removed at sandbox-ui's next
100
- * breaking release.
95
+ * Serves {@link AgentComposer}'s attachment lifecycle. `ChatComposer` in
96
+ * `@tangle-network/agent-app/web-react` stages files without a validation or
97
+ * retry step, so it has no equivalent.
101
98
  */
102
99
  declare function isAcceptedType(file: File, accept?: string): boolean;
103
100
  /**
@@ -107,10 +104,9 @@ declare function isAcceptedType(file: File, accept?: string): boolean;
107
104
  * file and the limit it broke. Pure data in, pure data out — no throwing,
108
105
  * no console noise, so callers decide how to surface rejections.
109
106
  *
110
- * @deprecated Composer-only helper of the legacy AgentComposer. Use
111
- * ChatComposer from `@tangle-network/agent-app/web-react` — the canonical
112
- * composer. AgentComposer is frozen and will be removed at sandbox-ui's next
113
- * breaking release.
107
+ * Serves {@link AgentComposer}'s attachment lifecycle. `ChatComposer` in
108
+ * `@tangle-network/agent-app/web-react` stages files without a validation or
109
+ * retry step, so it has no equivalent.
114
110
  */
115
111
  declare function validateComposerFiles(files: File[] | FileList, config: ComposerFileValidationConfig): {
116
112
  accepted: File[];
@@ -120,9 +116,9 @@ declare function validateComposerFiles(files: File[] | FileList, config: Compose
120
116
  /** A selectable item in the `@`-mention popover. Generic — file semantics live
121
117
  * in the consuming app, not here.
122
118
  *
123
- * @deprecated Part of the legacy AgentComposer API. Use ChatComposer from
124
- * `@tangle-network/agent-app/web-react` — the canonical composer. AgentComposer
125
- * is frozen and will be removed at sandbox-ui's next breaking release. */
119
+ * Part of the {@link AgentComposer} API. `ChatComposer` in
120
+ * `@tangle-network/agent-app/web-react` has no equivalent — see
121
+ * {@link AgentComposer} for which composer fits a surface. */
126
122
  interface MentionItem {
127
123
  id: string;
128
124
  label: string;
@@ -134,9 +130,9 @@ interface MentionItem {
134
130
  * TipTap rich input that renders mentions as atomic pills; absent ⇒ exactly
135
131
  * today's plain-textarea behavior. Data-agnostic: the app supplies items.
136
132
  *
137
- * @deprecated Part of the legacy AgentComposer API. Use ChatComposer from
138
- * `@tangle-network/agent-app/web-react` — the canonical composer. AgentComposer
139
- * is frozen and will be removed at sandbox-ui's next breaking release.
133
+ * Part of the {@link AgentComposer} API. `ChatComposer` in
134
+ * `@tangle-network/agent-app/web-react` has no equivalent — see
135
+ * {@link AgentComposer} for which composer fits a surface.
140
136
  */
141
137
  interface AgentComposerMention {
142
138
  /** The character that opens the popover. Default "@". */
@@ -159,9 +155,9 @@ interface AgentComposerMention {
159
155
  }
160
156
  /** A staged attachment shown as a chip above the input.
161
157
  *
162
- * @deprecated Part of the legacy AgentComposer API. Use ChatComposer from
163
- * `@tangle-network/agent-app/web-react` — the canonical composer. AgentComposer
164
- * is frozen and will be removed at sandbox-ui's next breaking release. */
158
+ * Part of the {@link AgentComposer} API. `ChatComposer` in
159
+ * `@tangle-network/agent-app/web-react` has no equivalent — see
160
+ * {@link AgentComposer} for which composer fits a surface. */
165
161
  interface ComposerFile {
166
162
  id: string;
167
163
  name: string;
@@ -181,9 +177,9 @@ interface ComposerFile {
181
177
  }
182
178
  /** Context the agent will see alongside the next message.
183
179
  *
184
- * @deprecated Part of the legacy AgentComposer API. Use ChatComposer from
185
- * `@tangle-network/agent-app/web-react` — the canonical composer. AgentComposer
186
- * is frozen and will be removed at sandbox-ui's next breaking release. */
180
+ * Part of the {@link AgentComposer} API. `ChatComposer` in
181
+ * `@tangle-network/agent-app/web-react` has no equivalent — see
182
+ * {@link AgentComposer} for which composer fits a surface. */
187
183
  interface ComposerContextItem {
188
184
  id: string;
189
185
  label: string;
@@ -192,11 +188,11 @@ interface ComposerContextItem {
192
188
  onRemove?: () => void;
193
189
  }
194
190
  /**
195
- * Props of the legacy {@link AgentComposer}.
191
+ * Props of {@link AgentComposer}.
196
192
  *
197
- * @deprecated Part of the legacy AgentComposer API. Use ChatComposer from
198
- * `@tangle-network/agent-app/web-react` — the canonical composer. AgentComposer
199
- * is frozen and will be removed at sandbox-ui's next breaking release.
193
+ * Part of the {@link AgentComposer} API. `ChatComposer` in
194
+ * `@tangle-network/agent-app/web-react` has no equivalent — see
195
+ * {@link AgentComposer} for which composer fits a surface.
200
196
  */
201
197
  interface AgentComposerProps {
202
198
  /** Composer text (controlled). */
@@ -288,25 +284,35 @@ interface AgentComposerProps {
288
284
  autoFocus?: boolean;
289
285
  }
290
286
  /**
291
- * @deprecated Use `ChatComposer` from `@tangle-network/agent-app/web-react` —
292
- * the canonical composer. It adds send-failure recovery, seeds, control
293
- * placement, floating elevation, and the circular icon send via
294
- * `sendVariant="icon"`. Plan a port, not a rename: 14 of the 28 props here
295
- * have no same-named counterpart there, among them `contextItems`, `mention`,
296
- * `trailing`, `maxHeight`, `minRows`, and `autoFocus`. The control model also
297
- * differs — this component requires `value` and `onChange` and calls
298
- * `onSubmit` with no arguments, while `ChatComposer` can own its draft and
299
- * gives the message to a refusable `onSend`. This slot-shell is frozen and
300
- * will be removed at sandbox-ui's next breaking release.
287
+ * Sibling of `ChatComposer` in `@tangle-network/agent-app/web-react`, not a
288
+ * predecessor of it. The two composers cover different jobs, and neither is a
289
+ * superset of the other, so pick by the features a surface needs.
301
290
  *
302
- * The canonical agent chat input: one rounded surface holding an auto-growing
291
+ * Reach for `AgentComposer` when the surface needs `@`-mentions (`mention`,
292
+ * backed by a lazily loaded rich-text input that renders mention pills),
293
+ * `contextItems`, or the full attachment lifecycle — `attachments` plus
294
+ * `onRejectFiles` and `onRetryFile`. It also owns `autoFocus`,
295
+ * `canSubmitAttachmentsOnly`, `trailing`, `maxHeight`, and `minRows`.
296
+ *
297
+ * Reach for `ChatComposer` when the surface is a plain send box. It owns
298
+ * send-failure recovery (`onSendFailed`, `sendFailureMessage`), one-shot
299
+ * prefill (`seed`), `controlsPlacement`, `floating`, `sendVariant`,
300
+ * parts-aware sends (`onSendParts`), and an uncontrolled mode
301
+ * (`initialValue`) — none of which exist here.
302
+ *
303
+ * Moving between them is a port, not a rename: the control models differ. This
304
+ * component requires `value` and `onChange` and calls `onSubmit` with no
305
+ * arguments, while `ChatComposer` can own its draft and hands the message to a
306
+ * refusable `onSend`.
307
+ *
308
+ * The input itself: one rounded surface holding an auto-growing
303
309
  * textarea and a bottom row that runs left → right — attach buttons, the
304
310
  * control strip (supplied via the required `controls` prop), trailing slot,
305
311
  * and the send button on the far right. The strip is explicit: the canonical
306
312
  * pickers are `ModelPicker` / `EffortPicker` / `AgentSessionControls` from
307
313
  * `@tangle-network/agent-app/web-react` — this component only owns the slot.
308
314
  *
309
- * Opt-in extras make it the superset of every app's hand-rolled composer:
315
+ * Opt-in extras cover what apps otherwise hand-roll:
310
316
  * file/folder attachments with drag-and-drop + chips + clipboard paste
311
317
  * (`onAttach` also gates pasting files onto the textarea, auto-naming
312
318
  * generic clipboard image blobs `pasted-image-<n>.<ext>`), a streaming Stop
@@ -323,10 +329,9 @@ declare function AgentComposer({ value, onChange, onSubmit, placeholder, disable
323
329
  * importing it never pulls the lazily-loaded editor chunk into a consumer's
324
330
  * bundle.
325
331
  *
326
- * @deprecated Mention-pill contract of the legacy AgentComposer mention
327
- * feature. Use ChatComposer from `@tangle-network/agent-app/web-react` — the
328
- * canonical composer. AgentComposer is frozen and will be removed at
329
- * sandbox-ui's next breaking release.
332
+ * Mention-pill contract of {@link AgentComposer}'s mention feature.
333
+ * `ChatComposer` in `@tangle-network/agent-app/web-react` has no mention
334
+ * support, so it has no equivalent.
330
335
  */
331
336
  declare const MENTION_PILL_CLASS: string;
332
337
 
@@ -3,7 +3,7 @@ import { useState, useEffect, useRef, useCallback } from "react";
3
3
  function createEmptyBatch() {
4
4
  return { data: "", waiters: [] };
5
5
  }
6
- var WS_OPEN_TIMEOUT_MS = 1e4;
6
+ var WS_READY_TIMEOUT_MS = 1e4;
7
7
  function toWsUrl(apiUrl, sessionId) {
8
8
  try {
9
9
  const base = typeof window !== "undefined" ? window.location.href : void 0;
@@ -64,6 +64,7 @@ function usePtySession({
64
64
  const reconnectRef = useRef(null);
65
65
  const shouldDeleteSessionRef = useRef(false);
66
66
  const transportReadyRef = useRef(false);
67
+ const protocolFailureRef = useRef(false);
67
68
  const colsRef = useRef(DEFAULT_TERMINAL_COLS);
68
69
  const rowsRef = useRef(DEFAULT_TERMINAL_ROWS);
69
70
  const wsRef = useRef(null);
@@ -169,13 +170,25 @@ function usePtySession({
169
170
  resolve(ok);
170
171
  };
171
172
  const handshakeTimer = setTimeout(() => {
172
- if (opened) return;
173
173
  try {
174
174
  ws.close();
175
175
  } catch {
176
176
  }
177
177
  settle(false);
178
- }, WS_OPEN_TIMEOUT_MS);
178
+ }, WS_READY_TIMEOUT_MS);
179
+ const failProtocol = (message) => {
180
+ protocolFailureRef.current = true;
181
+ transportReadyRef.current = false;
182
+ if (wsRef.current === ws) wsRef.current = null;
183
+ setIsConnected(false);
184
+ setError(message);
185
+ rejectPendingInput(message);
186
+ try {
187
+ ws.close();
188
+ } catch {
189
+ }
190
+ settle(false);
191
+ };
179
192
  ws.onopen = () => {
180
193
  opened = true;
181
194
  if (pendingWsRef.current !== ws || !mountedRef.current) {
@@ -212,12 +225,8 @@ function usePtySession({
212
225
  return;
213
226
  }
214
227
  }
228
+ if (protocolFailureRef.current) return;
215
229
  wsRef.current = ws;
216
- transportReadyRef.current = true;
217
- setIsConnected(true);
218
- setError(null);
219
- retryCountRef.current = 0;
220
- settle(true);
221
230
  };
222
231
  ws.onmessage = (ev) => {
223
232
  if (!mountedRef.current) return;
@@ -239,17 +248,38 @@ function usePtySession({
239
248
  if (typeof data === "string") {
240
249
  try {
241
250
  const event = JSON.parse(text);
242
- if (event?.type === "ready") {
243
- return;
244
- }
245
- if (event?.type === "error") {
246
- const message = typeof event.message === "string" ? event.message : "Terminal WebSocket error";
247
- setError(message);
248
- return;
249
- }
250
- if (event?.type === "exit") {
251
- setIsConnected(false);
252
- return;
251
+ if (event !== null && typeof event === "object" && !Array.isArray(event)) {
252
+ const controlFrame = event;
253
+ switch (controlFrame.type) {
254
+ case "ready":
255
+ transportReadyRef.current = true;
256
+ setIsConnected(true);
257
+ setError(null);
258
+ retryCountRef.current = 0;
259
+ settle(true);
260
+ return;
261
+ case "error": {
262
+ const message = typeof controlFrame.message === "string" ? controlFrame.message : "Terminal WebSocket error";
263
+ if (!transportReadyRef.current) {
264
+ failProtocol(message);
265
+ } else {
266
+ setError(message);
267
+ }
268
+ return;
269
+ }
270
+ case "exit":
271
+ transportReadyRef.current = false;
272
+ setIsConnected(false);
273
+ return;
274
+ case "rec":
275
+ case "rec.done":
276
+ return;
277
+ default: {
278
+ const frameType = typeof controlFrame.type === "string" ? controlFrame.type : "<missing type>";
279
+ failProtocol(`Unknown terminal control frame: ${frameType}`);
280
+ return;
281
+ }
282
+ }
253
283
  }
254
284
  } catch {
255
285
  }
@@ -263,6 +293,7 @@ function usePtySession({
263
293
  pendingWsRef.current = null;
264
294
  }
265
295
  const wasActive = wsRef.current === ws;
296
+ const wasReady = wasActive && transportReadyRef.current;
266
297
  if (wasActive) {
267
298
  wsRef.current = null;
268
299
  transportReadyRef.current = false;
@@ -271,7 +302,11 @@ function usePtySession({
271
302
  settle(false);
272
303
  return;
273
304
  }
274
- if (!wasActive || !mountedRef.current) return;
305
+ if (!wasReady) {
306
+ settle(false);
307
+ return;
308
+ }
309
+ if (protocolFailureRef.current || !wasActive || !mountedRef.current) return;
275
310
  setIsConnected(false);
276
311
  if (sessionIdRef.current) {
277
312
  retryTimerRef.current = setTimeout(() => {
@@ -286,7 +321,7 @@ function usePtySession({
286
321
  }
287
322
  };
288
323
  });
289
- }, [apiUrl]);
324
+ }, [apiUrl, rejectPendingInput]);
290
325
  const connectStream = useCallback(async (sessionId) => {
291
326
  if (exactAttachRequiredRef.current) {
292
327
  throw new Error(
@@ -387,6 +422,7 @@ function usePtySession({
387
422
  const connect = useCallback(async () => {
388
423
  cleanup();
389
424
  const myGen = ++connectGenRef.current;
425
+ protocolFailureRef.current = false;
390
426
  retryCountRef.current = 0;
391
427
  setError(null);
392
428
  if (exactAttachRequiredRef.current && (!incarnationIdRef.current || !controlRef.current)) {
@@ -402,6 +438,7 @@ function usePtySession({
402
438
  inputAbortRef.current = new AbortController();
403
439
  const directWsOk = await connectWs(connectionId, { initOnOpen: true });
404
440
  if (!mountedRef.current || connectGenRef.current !== myGen) return;
441
+ if (protocolFailureRef.current) return;
405
442
  ensureDrainRunningRef.current?.();
406
443
  if (directWsOk) {
407
444
  return;
@@ -442,6 +479,7 @@ function usePtySession({
442
479
  inputAbortRef.current = new AbortController();
443
480
  const wsOk = await connectWs(sessionId, { initOnOpen: true });
444
481
  if (!mountedRef.current || connectGenRef.current !== myGen) return;
482
+ if (protocolFailureRef.current) return;
445
483
  ensureDrainRunningRef.current?.();
446
484
  if (wsOk) {
447
485
  return;
@@ -564,6 +602,10 @@ function usePtySession({
564
602
  resolve();
565
603
  return;
566
604
  }
605
+ if (protocolFailureRef.current) {
606
+ reject(new Error("Terminal protocol failure"));
607
+ return;
608
+ }
567
609
  pendingBatchRef.current.data += command;
568
610
  pendingBatchRef.current.waiters.push({ resolve, reject });
569
611
  ensureDrainRunning();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  usePtySession
3
- } from "./chunk-7KB22BLB.js";
3
+ } from "./chunk-FW77EXKG.js";
4
4
 
5
5
  // src/terminal/terminal-view.tsx
6
6
  import "@xterm/xterm/css/xterm.css";
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-K4Q4E7D7.js";
7
7
  import {
8
8
  TerminalView
9
- } from "./chunk-4VLVVKQZ.js";
9
+ } from "./chunk-MXQT46AZ.js";
10
10
  import {
11
11
  cn
12
12
  } from "./chunk-EI44GEQ5.js";
package/dist/hooks.d.ts CHANGED
@@ -51,8 +51,9 @@ interface UsePtySessionReturn {
51
51
  * - Server → client: BINARY frames carrying PTY output; TEXT frames
52
52
  * carrying lifecycle/control messages.
53
53
  * - Client → server: TEXT frames carrying JSON input/resize messages.
54
- * 2. If direct WS does not reach OPEN within WS_OPEN_TIMEOUT_MS, or it
55
- * errors before opening, fall back to the older terminal contract:
54
+ * 2. If direct WS does not reach server `ready` within
55
+ * WS_READY_TIMEOUT_MS, or it errors before ready, fall back to the older
56
+ * terminal contract:
56
57
  * - POST /terminals creates the session.
57
58
  * - GET /terminals/:id/ws tries the older WS transport.
58
59
  * - GET /terminals/:id/stream (SSE for output)
package/dist/hooks.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  } from "./chunk-3GX5BSD4.js";
27
27
  import {
28
28
  usePtySession
29
- } from "./chunk-7KB22BLB.js";
29
+ } from "./chunk-FW77EXKG.js";
30
30
  import "./chunk-CZ7CSFO4.js";
31
31
  export {
32
32
  RealtimeSessionRegistry,
package/dist/index.js CHANGED
@@ -153,7 +153,7 @@ import {
153
153
  buildUnifiedPatch,
154
154
  computeDiffStats,
155
155
  resolveLanguage
156
- } from "./chunk-NAWEV5UJ.js";
156
+ } from "./chunk-NT6SCOY2.js";
157
157
  import {
158
158
  FileArtifactPane,
159
159
  FilePreview,
@@ -238,10 +238,10 @@ import {
238
238
  UsageChart,
239
239
  formatPrice
240
240
  } from "./chunk-OMUIPJQZ.js";
241
- import "./chunk-4VLVVKQZ.js";
241
+ import "./chunk-MXQT46AZ.js";
242
242
  import {
243
243
  usePtySession
244
- } from "./chunk-7KB22BLB.js";
244
+ } from "./chunk-FW77EXKG.js";
245
245
  import {
246
246
  Avatar,
247
247
  AvatarFallback,
package/dist/terminal.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  DEFAULT_TERMINAL_THEME,
3
3
  TerminalView
4
- } from "./chunk-4VLVVKQZ.js";
5
- import "./chunk-7KB22BLB.js";
4
+ } from "./chunk-MXQT46AZ.js";
5
+ import "./chunk-FW77EXKG.js";
6
6
  import "./chunk-CZ7CSFO4.js";
7
7
  export {
8
8
  DEFAULT_TERMINAL_THEME,
package/dist/workbench.js CHANGED
@@ -12,12 +12,12 @@ import {
12
12
  buildUnifiedPatch,
13
13
  computeDiffStats,
14
14
  resolveLanguage
15
- } from "./chunk-NAWEV5UJ.js";
15
+ } from "./chunk-NT6SCOY2.js";
16
16
  import "./chunk-PZVUIUUW.js";
17
17
  import "./chunk-K4Q4E7D7.js";
18
18
  import "./chunk-OMUIPJQZ.js";
19
- import "./chunk-4VLVVKQZ.js";
20
- import "./chunk-7KB22BLB.js";
19
+ import "./chunk-MXQT46AZ.js";
20
+ import "./chunk-FW77EXKG.js";
21
21
  import "./chunk-2XPJ2XF7.js";
22
22
  import "./chunk-RQZRXKMR.js";
23
23
  import "./chunk-BGVEHENC.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.104.1",
3
+ "version": "0.105.1",
4
4
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",