@iloveagents/foundry-web-shell 0.2.1 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # @iloveagents/foundry-web-shell
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 8184a8c: shell: URL is authoritative for the runtime threadId — fixes
8
+ resume-creates-new-conversation race
9
+
10
+ `ChatConversationAwareRuntime` previously computed
11
+ `effectiveThreadId = sticky ?? urlMatch ?? freshIdRef.current`.
12
+ When the user navigated from one chat to another via a Recents
13
+ click, the synchronous render that followed the URL change had:
14
+ - `urlMatch = B` (read from the now-updated `useLocation`)
15
+ - `sticky = A` (the active-chat-store hadn't been updated by
16
+ `useTrackActiveChatFromUrl`'s `useEffect` yet — effects run
17
+ AFTER the commit phase)
18
+
19
+ `sticky ?? urlMatch` picked `A` (the previous chat). The AG-UI
20
+ adapter was constructed with `threadId = A`. The first message the
21
+ user sent went to `/api/agent` with `thread_id = A`, the middleware
22
+ created a new row keyed by `A`... wait actually no, here's what
23
+ happens — the runtime mints a fresh runner UUID when given a
24
+ mismatched threadId; that fresh UUID became a new conversation row,
25
+ appearing as "Untitled chat" at the top of the sidebar while the
26
+ URL still said `/chat/B`. Two active-looking dots: one for `B`
27
+ (NavLink URL match), one for the new row (statusDot from the
28
+ updated active-chat-store after the lazy ensure ran).
29
+
30
+ Swap the priority: `urlMatch ?? sticky ?? freshIdRef.current`. The
31
+ URL is authoritative whenever it's set (i.e. on `/chat/<id>`
32
+ routes), eliminating the stale-sticky race entirely. `sticky` is
33
+ still consulted as the second-priority fallback for non-chat
34
+ routes (`/spaces`, `/tasks`) so the popout chat stays "live" while
35
+ the user browses workspaces — that's the original purpose of
36
+ `useStickyConversationId` and it's preserved.
37
+
38
+ - Updated dependencies [395f0cd]
39
+ - Updated dependencies [1ba01ef]
40
+ - Updated dependencies [8184a8c]
41
+ - @iloveagents/foundry-web-ui@0.2.2
42
+ - @iloveagents/foundry-agent@0.2.2
43
+ - @iloveagents/foundry-web-primitives@0.2.2
44
+
3
45
  ## 0.2.1
4
46
 
5
47
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iloveagents/foundry-web-shell",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "type": "module",
6
6
  "types": "./src/index.ts",
@@ -19,9 +19,9 @@
19
19
  "zustand": "^5.0.0"
20
20
  },
21
21
  "dependencies": {
22
- "@iloveagents/foundry-agent": "0.2.1",
23
- "@iloveagents/foundry-web-primitives": "0.2.1",
24
- "@iloveagents/foundry-web-ui": "0.2.1"
22
+ "@iloveagents/foundry-agent": "0.2.2",
23
+ "@iloveagents/foundry-web-primitives": "0.2.2",
24
+ "@iloveagents/foundry-web-ui": "0.2.2"
25
25
  },
26
26
  "devDependencies": {
27
27
  "typescript": "~5.9.3",
package/src/shell-app.tsx CHANGED
@@ -283,9 +283,39 @@ function RuntimeBody({
283
283
  prevStickyRef.current = sticky;
284
284
 
285
285
  // Effective id seen by AGUIRuntimeProvider — always defined.
286
- // sticky (current conversation) wins, else fall back to the
287
- // shell-minted UUID for fresh chats.
288
- const effectiveThreadId: string = sticky ?? urlMatch ?? freshIdRef.current;
286
+ //
287
+ // Priority order matters and is intentional:
288
+ // 1. ``urlMatch`` when the user is on ``/chat/<id>`` the URL is
289
+ // ALWAYS the authoritative conversation. This MUST come before
290
+ // ``sticky`` because of a stale-render race on cross-chat
291
+ // navigation:
292
+ // - User is on ``/chat/A``. ``active-chat-store`` holds ``A``.
293
+ // So ``sticky = A``.
294
+ // - User clicks a Recents row for ``B`` →
295
+ // ``react-router`` updates pathname to ``/chat/B``.
296
+ // - On the synchronous render that follows, ``urlMatch`` is
297
+ // ``B`` (read from ``useLocation``) but
298
+ // ``useTrackActiveChatFromUrl``'s ``useEffect`` hasn't run
299
+ // yet — so ``sticky`` is still ``A``.
300
+ // - With ``sticky ?? urlMatch`` we'd compute
301
+ // ``effectiveThreadId = A``, bind the AG-UI adapter to ``A``,
302
+ // and the first message the user sends would create a NEW
303
+ // row at ``A``'s old thread id while the URL says ``B``.
304
+ // Symptoms: TWO active dots in the sidebar (URL highlights
305
+ // row ``B``, the new row's id activates the dot on a
306
+ // freshly-appeared "Untitled chat"); resume appears to
307
+ // mint a new conversation; messages land on the wrong row.
308
+ // Putting ``urlMatch`` first eliminates the race entirely: when
309
+ // the URL says we're on chat ``B``, the runtime targets ``B``
310
+ // from the very first render, regardless of stale store state.
311
+ // 2. ``sticky`` — for non-chat URLs (``/spaces``, ``/tasks`` …)
312
+ // ``urlMatch`` is ``undefined``. ``sticky`` keeps the popout
313
+ // chat "live" while the user browses elsewhere.
314
+ // 3. ``freshIdRef.current`` — brand-new chat on ``/`` with no
315
+ // sticky and no URL match. Pre-minted so the runtime's
316
+ // ``threadId`` is defined from the first render (no
317
+ // ``undefined → defined`` remount of assistant-ui).
318
+ const effectiveThreadId: string = urlMatch ?? sticky ?? freshIdRef.current;
289
319
 
290
320
  const historyAdapterFactory = useCallback<AGUIHistoryAdapterFactory>(
291
321
  (args: AGUIChatConversationFactoryArgs) =>