@tangle-network/sandbox-ui 0.18.0 → 0.20.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
@@ -57,6 +57,13 @@ interface ArtifactAgentDockTransport {
57
57
  content: string;
58
58
  signal: AbortSignal;
59
59
  }): AsyncIterable<ArtifactDockStreamEvent>;
60
+ /** Stop the server-side run for a thread. Optional — when present, the dock
61
+ * calls it on cancel/scope-switch BEFORE aborting the local fetch, so the
62
+ * agent run is actually stopped (not orphaned, still billing) rather than
63
+ * the client merely closing its reader. */
64
+ cancel?(args: {
65
+ threadId: string;
66
+ }): Promise<void> | void;
60
67
  }
61
68
  interface ArtifactAgentDockProps {
62
69
  scope: ArtifactScope;
@@ -95,7 +102,7 @@ declare function ArtifactAgentDock({ scope, open, onOpenChange, transport, defau
95
102
  * their own transport object.
96
103
  *
97
104
  * The expected NDJSON event shape:
98
- * { type: "message.part.delta", data: { text: string } } // assistant chunk
105
+ * { type: "message.part.updated", data: { delta: string, part?: {type} } } // assistant chunk
99
106
  * { type: "error", data: { message: string, ... } } // surface error
100
107
  *
101
108
  * Other event types are ignored — consumers that need tool-call rendering
package/dist/chat.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  ThinkingIndicator,
11
11
  UserMessage,
12
12
  createFetchTransport
13
- } from "./chunk-CNVE6KOM.js";
13
+ } from "./chunk-MQ52AYJX.js";
14
14
  import "./chunk-EI44GEQ5.js";
15
15
  export {
16
16
  AgentTimeline,
@@ -1652,8 +1652,11 @@ function CheckRow({ check }) {
1652
1652
  }
1653
1653
 
1654
1654
  // src/workspace/task-board.tsx
1655
- import { useMemo as useMemo5 } from "react";
1655
+ import {
1656
+ useMemo as useMemo5
1657
+ } from "react";
1656
1658
  import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1659
+ var INTERACTIVE_SELECTOR = 'button, a, input, textarea, select, [role="button"], [role="link"]';
1657
1660
  function TaskBoard({
1658
1661
  items,
1659
1662
  columns,
@@ -1686,10 +1689,24 @@ function TaskBoard({
1686
1689
  colItems.length === 0 && columnEmptyState,
1687
1690
  colItems.map((item, index) => {
1688
1691
  const card = /* @__PURE__ */ jsxs11(
1689
- "button",
1692
+ "div",
1690
1693
  {
1691
- type: "button",
1692
- onClick: () => onClickItem?.(item),
1694
+ role: onClickItem ? "button" : void 0,
1695
+ tabIndex: onClickItem ? 0 : void 0,
1696
+ onClick: (event) => {
1697
+ if (!onClickItem) return;
1698
+ const target = event.target;
1699
+ const interactive = target.closest(INTERACTIVE_SELECTOR);
1700
+ if (interactive && interactive !== event.currentTarget) return;
1701
+ onClickItem(item);
1702
+ },
1703
+ onKeyDown: (event) => {
1704
+ if (!onClickItem || event.currentTarget !== event.target) return;
1705
+ if (event.key !== "Enter" && event.key !== " ") return;
1706
+ if (event.repeat) return;
1707
+ event.preventDefault();
1708
+ onClickItem(item);
1709
+ },
1693
1710
  className: "group w-full rounded-lg border border-border bg-card p-3 text-left transition-colors hover:border-accent/50",
1694
1711
  children: [
1695
1712
  /* @__PURE__ */ jsx11("p", { className: "text-sm font-medium text-foreground", children: item.title }),
@@ -121,6 +121,18 @@ function defaultScopeLabel(scope) {
121
121
  function normalizeRole(role) {
122
122
  return role === "assistant" || role === "system" ? role : "user";
123
123
  }
124
+ function createDockMessageId() {
125
+ const c = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
126
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
127
+ if (c && typeof c.getRandomValues === "function") {
128
+ const b = c.getRandomValues(new Uint8Array(16));
129
+ b[6] = b[6] & 15 | 64;
130
+ b[8] = b[8] & 63 | 128;
131
+ const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
132
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
133
+ }
134
+ throw new Error("crypto.getRandomValues is required for message ids");
135
+ }
124
136
  function ArtifactAgentDock({
125
137
  scope,
126
138
  open,
@@ -138,6 +150,7 @@ function ArtifactAgentDock({
138
150
  const [loading, setLoading] = React.useState(false);
139
151
  const abortRef = React.useRef(null);
140
152
  const scrollRef = React.useRef(null);
153
+ const scopeTokenRef = React.useRef(0);
141
154
  const reportError = React.useCallback(
142
155
  (message) => {
143
156
  if (onError) onError(message);
@@ -147,6 +160,9 @@ function ArtifactAgentDock({
147
160
  );
148
161
  React.useEffect(() => {
149
162
  if (!open) return;
163
+ scopeTokenRef.current += 1;
164
+ abortRef.current?.abort();
165
+ abortRef.current = null;
150
166
  let cancelled = false;
151
167
  async function bootstrap() {
152
168
  setLoading(true);
@@ -185,8 +201,9 @@ function ArtifactAgentDock({
185
201
  const send = React.useCallback(async () => {
186
202
  const text = composer.trim();
187
203
  if (!text || !threadId || sending) return;
204
+ const token = scopeTokenRef.current;
188
205
  const userMsg = {
189
- id: `local-${Date.now()}`,
206
+ id: createDockMessageId(),
190
207
  role: "user",
191
208
  content: text,
192
209
  createdAt: /* @__PURE__ */ new Date()
@@ -201,6 +218,7 @@ function ArtifactAgentDock({
201
218
  let streamFailed = false;
202
219
  try {
203
220
  for await (const event of transport.sendStream({ threadId, content: text, signal: controller.signal })) {
221
+ if (scopeTokenRef.current !== token) return;
204
222
  if (event.type === "delta") {
205
223
  assistantContent += event.text;
206
224
  setStreamContent(assistantContent);
@@ -209,11 +227,12 @@ function ArtifactAgentDock({
209
227
  reportError(event.message);
210
228
  }
211
229
  }
230
+ if (scopeTokenRef.current !== token) return;
212
231
  if (assistantContent && !streamFailed) {
213
232
  setMessages((prev) => [
214
233
  ...prev,
215
234
  {
216
- id: `assist-${Date.now()}`,
235
+ id: createDockMessageId(),
217
236
  role: "assistant",
218
237
  content: assistantContent,
219
238
  createdAt: /* @__PURE__ */ new Date()
@@ -223,17 +242,24 @@ function ArtifactAgentDock({
223
242
  setStreamContent("");
224
243
  } catch (err) {
225
244
  if (err?.name === "AbortError") return;
226
- reportError(err instanceof Error ? err.message : "Send failed");
245
+ if (scopeTokenRef.current === token) reportError(err instanceof Error ? err.message : "Send failed");
227
246
  } finally {
228
- setSending(false);
229
- abortRef.current = null;
247
+ if (scopeTokenRef.current === token) setSending(false);
248
+ if (abortRef.current === controller) abortRef.current = null;
230
249
  }
231
250
  }, [composer, threadId, sending, transport, reportError]);
232
251
  const cancel = React.useCallback(() => {
252
+ if (threadId && transport.cancel) {
253
+ try {
254
+ void Promise.resolve(transport.cancel({ threadId })).catch(() => {
255
+ });
256
+ } catch {
257
+ }
258
+ }
233
259
  abortRef.current?.abort();
234
260
  setSending(false);
235
261
  setStreamContent("");
236
- }, []);
262
+ }, [threadId, transport]);
237
263
  if (!open) return null;
238
264
  const heading = defaultScopeLabel(scope);
239
265
  return /* @__PURE__ */ jsxs2(
@@ -384,10 +410,12 @@ function createFetchTransport(opts) {
384
410
  if (!line.trim()) continue;
385
411
  try {
386
412
  const event = JSON.parse(line);
387
- if (event.type === "message.part.delta" && event.data) {
388
- const text = event.data.text;
389
- if (typeof text === "string" && text.length > 0) {
390
- yield { type: "delta", text };
413
+ if (event.type === "message.part.updated" && event.data) {
414
+ const delta = event.data.delta;
415
+ const part = event.data.part;
416
+ const isText = !part || typeof part !== "object" || part.type === void 0 || part.type === "text";
417
+ if (isText && typeof delta === "string" && delta.length > 0) {
418
+ yield { type: "delta", text: delta };
391
419
  }
392
420
  } else if (event.type === "error" && event.data) {
393
421
  const message = typeof event.data.message === "string" ? event.data.message : "Stream error";