@tangle-network/agent-app 0.43.4 → 0.43.6

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.
@@ -397,6 +397,10 @@ interface AssistantState {
397
397
  code: string;
398
398
  message: string;
399
399
  } | null;
400
+ /** True when the most-recent turn stopped at the per-turn step limit before the
401
+ * model finished (a partial reply). Drives the one-click "Continue" affordance;
402
+ * cleared when the next turn starts. */
403
+ capped: boolean;
400
404
  }
401
405
 
402
406
  /**
@@ -3,7 +3,7 @@ import {
3
3
  ChatMessages,
4
4
  ModelPicker,
5
5
  ProviderLogo
6
- } from "../chunk-VKHB7VP4.js";
6
+ } from "../chunk-4X7OOVII.js";
7
7
  import "../chunk-65P3HJY3.js";
8
8
  import "../chunk-2QI7XV2T.js";
9
9
  import "../chunk-E7QYOOON.js";
@@ -779,7 +779,8 @@ function initialAssistantState() {
779
779
  usage: null,
780
780
  model: null,
781
781
  reasoning: null,
782
- error: null
782
+ error: null,
783
+ capped: false
783
784
  };
784
785
  }
785
786
  function selectVisibleState(state, userId) {
@@ -915,13 +916,14 @@ function applyStreamEvent(state, event) {
915
916
  };
916
917
  case "done": {
917
918
  let messages = dropEmptyStreaming(state.messages, state.streamingId);
918
- if (event.data.capped) {
919
+ const capped = event.data.capped === true;
920
+ if (capped) {
919
921
  messages = capMessages([
920
922
  ...messages,
921
923
  {
922
924
  id: `cap-${event.data.turnId}`,
923
925
  role: "status",
924
- text: "I reached the step limit for this turn. Ask me to continue and I'll pick up where I left off."
926
+ text: "Paused after a lot of steps \u2014 continue when you're ready and I'll pick up where I left off."
925
927
  }
926
928
  ]);
927
929
  }
@@ -929,6 +931,7 @@ function applyStreamEvent(state, event) {
929
931
  ...state,
930
932
  messages,
931
933
  streamingId: null,
934
+ capped,
932
935
  status: state.pendingProposals.length > 0 ? "awaiting_confirm" : "idle"
933
936
  };
934
937
  }
@@ -963,6 +966,8 @@ function assistantReducer(state, action) {
963
966
  // from this base, so they stay unique across turns.
964
967
  streamBaseId: action.assistantId,
965
968
  segmentSeq: 0,
969
+ // A new turn clears the prior turn's cap state (and its Continue button).
970
+ capped: false,
966
971
  usage: null,
967
972
  reasoning: null,
968
973
  error: null
@@ -1520,12 +1525,12 @@ function useAssistantThreads(userId) {
1520
1525
  import { MessageSquare } from "lucide-react";
1521
1526
  import {
1522
1527
  useEffect as useEffect6,
1523
- useRef as useRef6
1528
+ useRef as useRef7
1524
1529
  } from "react";
1525
1530
 
1526
1531
  // src/assistant/AssistantPanel.tsx
1527
1532
  import { History, MessageSquarePlus, Minus, Plus, X } from "lucide-react";
1528
- import { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef4, useState as useState6 } from "react";
1533
+ import { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef5, useState as useState6 } from "react";
1529
1534
 
1530
1535
  // src/assistant/AssistantHistory.tsx
1531
1536
  import { Search, Trash2 } from "lucide-react";
@@ -2086,6 +2091,32 @@ function useIsDesktop() {
2086
2091
  return isDesktop;
2087
2092
  }
2088
2093
 
2094
+ // src/assistant/use-stick-to-bottom.ts
2095
+ import { useCallback as useCallback5, useLayoutEffect, useRef as useRef4 } from "react";
2096
+ var STICK_SLACK_PX = 48;
2097
+ function useStickToBottom(ref, { enabled, contentSignature, streamingId, threadId }) {
2098
+ const stuckRef = useRef4(true);
2099
+ const prevStreamingRef = useRef4(streamingId);
2100
+ const prevThreadRef = useRef4(threadId);
2101
+ const onScroll = useCallback5(() => {
2102
+ if (!enabled) return;
2103
+ const el = ref.current;
2104
+ if (!el) return;
2105
+ stuckRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < STICK_SLACK_PX;
2106
+ }, [ref, enabled]);
2107
+ useLayoutEffect(() => {
2108
+ const turnStarted = streamingId != null && streamingId !== prevStreamingRef.current;
2109
+ const threadChanged = threadId !== prevThreadRef.current;
2110
+ prevStreamingRef.current = streamingId;
2111
+ prevThreadRef.current = threadId;
2112
+ if (turnStarted || threadChanged) stuckRef.current = true;
2113
+ if (!enabled || !stuckRef.current) return;
2114
+ const el = ref.current;
2115
+ if (el) el.scrollTop = el.scrollHeight;
2116
+ }, [ref, enabled, contentSignature, streamingId, threadId]);
2117
+ return { onScroll };
2118
+ }
2119
+
2089
2120
  // src/assistant/AssistantPanel.tsx
2090
2121
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2091
2122
  var EMPTY_STATE = "Ask me to create a workflow, check your usage, or manage your API keys.";
@@ -2144,15 +2175,15 @@ function AssistantPanel({
2144
2175
  const threads = useAssistantThreads(userId);
2145
2176
  const font = useFontScale();
2146
2177
  const [view, setView] = useState6("chat");
2147
- const historyButtonRef = useRef4(null);
2148
- const logRef = useRef4(null);
2178
+ const historyButtonRef = useRef5(null);
2179
+ const logRef = useRef5(null);
2149
2180
  const pickerModels = useMemo3(
2150
2181
  () => toPickerModels(models, chat.selectedModel),
2151
2182
  [models, chat.selectedModel]
2152
2183
  );
2153
2184
  const pickerValue = chat.selectedModel ?? models.default ?? "";
2154
2185
  const { state } = chat;
2155
- const chatRef = useRef4(chat);
2186
+ const chatRef = useRef5(chat);
2156
2187
  chatRef.current = chat;
2157
2188
  useEffect5(() => {
2158
2189
  if (view !== "history") return;
@@ -2176,6 +2207,26 @@ function AssistantPanel({
2176
2207
  document.addEventListener("keydown", onKeyDownCapture, true);
2177
2208
  return () => document.removeEventListener("keydown", onKeyDownCapture, true);
2178
2209
  }, [view]);
2210
+ const contentSignature = useMemo3(() => {
2211
+ let sig = `${state.reasoning?.length ?? 0}|${state.status}`;
2212
+ for (const m of state.messages) {
2213
+ sig += `|${m.text?.length ?? 0}`;
2214
+ if (m.tool) sig += `:${m.tool.status}`;
2215
+ }
2216
+ for (const p of state.pendingProposals) sig += `|p:${p.callId}`;
2217
+ return sig;
2218
+ }, [
2219
+ state.messages,
2220
+ state.reasoning,
2221
+ state.status,
2222
+ state.pendingProposals
2223
+ ]);
2224
+ const { onScroll: handleConversationScroll } = useStickToBottom(logRef, {
2225
+ enabled: view === "chat",
2226
+ contentSignature,
2227
+ streamingId: state.streamingId,
2228
+ threadId: state.threadId
2229
+ });
2179
2230
  const effectiveBalance = state.usage?.balanceUsd ?? balanceUsd;
2180
2231
  const errorView = state.error ? presentError(state.error.code, state.error.message) : null;
2181
2232
  const low = isLowBalance(effectiveBalance) && !errorView;
@@ -2326,6 +2377,7 @@ function AssistantPanel({
2326
2377
  ref: logRef,
2327
2378
  tabIndex: -1,
2328
2379
  "aria-label": "Conversation",
2380
+ onScroll: handleConversationScroll,
2329
2381
  role: view === "chat" ? "log" : void 0,
2330
2382
  "aria-live": view === "chat" ? "polite" : void 0,
2331
2383
  className: "min-h-0 flex-1 overflow-y-auto focus:outline-none",
@@ -2403,6 +2455,28 @@ function AssistantPanel({
2403
2455
  ]
2404
2456
  }
2405
2457
  ),
2458
+ state.capped && state.status === "idle" && !chat.restoring && view === "chat" && /* @__PURE__ */ jsxs4(
2459
+ "div",
2460
+ {
2461
+ role: "status",
2462
+ className: "mx-4 mb-2 flex items-center gap-3 rounded-lg border border-border bg-muted/40 px-3 py-2 text-sm",
2463
+ children: [
2464
+ /* @__PURE__ */ jsx5("p", { className: "min-w-0 flex-1 text-foreground", children: "Paused after a lot of steps." }),
2465
+ /* @__PURE__ */ jsx5(
2466
+ "button",
2467
+ {
2468
+ type: "button",
2469
+ onClick: () => {
2470
+ setView("chat");
2471
+ chat.send("continue");
2472
+ },
2473
+ className: "shrink-0 rounded-lg bg-primary px-3 py-1.5 font-semibold text-primary-foreground text-xs transition hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2474
+ children: "Continue"
2475
+ }
2476
+ )
2477
+ ]
2478
+ }
2479
+ ),
2406
2480
  /* @__PURE__ */ jsxs4("div", { className: "border-border border-t p-2", children: [
2407
2481
  streaming && /* @__PURE__ */ jsx5("div", { className: "px-2 pb-1.5", "aria-label": "Assistant is working", children: /* @__PURE__ */ jsx5(WorkingIndicator, {}) }),
2408
2482
  /* @__PURE__ */ jsx5(
@@ -2433,7 +2507,7 @@ function AssistantPanel({
2433
2507
  // src/assistant/launcher.tsx
2434
2508
  import {
2435
2509
  createContext as createContext2,
2436
- useCallback as useCallback5,
2510
+ useCallback as useCallback6,
2437
2511
  useContext as useContext2,
2438
2512
  useMemo as useMemo4,
2439
2513
  useState as useState7
@@ -2445,12 +2519,12 @@ function AssistantLauncherProvider({
2445
2519
  }) {
2446
2520
  const [open, setOpen] = useState7(false);
2447
2521
  const [seed, setSeed] = useState7(null);
2448
- const openAssistant = useCallback5((next) => {
2522
+ const openAssistant = useCallback6((next) => {
2449
2523
  if (next != null) setSeed(next);
2450
2524
  setOpen(true);
2451
2525
  }, []);
2452
- const closeAssistant = useCallback5(() => setOpen(false), []);
2453
- const clearSeed = useCallback5(() => setSeed(null), []);
2526
+ const closeAssistant = useCallback6(() => setOpen(false), []);
2527
+ const clearSeed = useCallback6(() => setSeed(null), []);
2454
2528
  const value = useMemo4(
2455
2529
  () => ({ open, seed, openAssistant, closeAssistant, clearSeed }),
2456
2530
  [open, seed, openAssistant, closeAssistant, clearSeed]
@@ -2468,7 +2542,7 @@ function useAssistantLauncher() {
2468
2542
  }
2469
2543
 
2470
2544
  // src/assistant/ResizeHandle.tsx
2471
- import { useRef as useRef5 } from "react";
2545
+ import { useRef as useRef6 } from "react";
2472
2546
  import { jsx as jsx7 } from "react/jsx-runtime";
2473
2547
  function ResizeHandle({
2474
2548
  width,
@@ -2477,7 +2551,7 @@ function ResizeHandle({
2477
2551
  onCommit,
2478
2552
  onNudge
2479
2553
  }) {
2480
- const dragRef = useRef5(null);
2554
+ const dragRef = useRef6(null);
2481
2555
  const onPointerDown = (e) => {
2482
2556
  if (e.button !== 0) return;
2483
2557
  e.preventDefault();
@@ -2568,10 +2642,10 @@ function AssistantDock({
2568
2642
  const chat = useAssistantChat(userId, { onWorkflowMutation });
2569
2643
  const isDesktop = useIsDesktop();
2570
2644
  const { width, maxWidth, setWidth, previewWidth, nudgeWidth } = usePanelWidth();
2571
- const launcherRef = useRef6(null);
2572
- const dialogRef = useRef6(null);
2573
- const returnFocusRef = useRef6(null);
2574
- const wasOpenRef = useRef6(false);
2645
+ const launcherRef = useRef7(null);
2646
+ const dialogRef = useRef7(null);
2647
+ const returnFocusRef = useRef7(null);
2648
+ const wasOpenRef = useRef7(false);
2575
2649
  useEffect6(() => {
2576
2650
  if (!open) return;
2577
2651
  const onKeyDown = (e) => {