@agentprojectcontext/apx 1.55.0 → 1.55.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.
@@ -18,7 +18,7 @@
18
18
  <link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
19
19
  <link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
20
20
  <link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
21
- <script type="module" crossorigin src="/assets/index-J3_Jhr99.js"></script>
21
+ <script type="module" crossorigin src="/assets/index-p33JHkov.js"></script>
22
22
  <link rel="stylesheet" crossorigin href="/assets/index-BwnMDZaD.css">
23
23
  </head>
24
24
  <body class="bg-background text-foreground antialiased">
@@ -126,10 +126,13 @@ export function ChatList({
126
126
  const [collapsed, setCollapsed] = useState<Partial<Record<ChannelGroupKey, boolean>>>({});
127
127
  const [byAgent, setByAgent] = useState<Record<string, ConversationListEntry[]>>({});
128
128
 
129
- // Super-agent channel threads (telegram, web quick-chat, desktop …) from the
130
- // global message ledger the chats that don't live in conversation files.
129
+ // Super-agent channel threads (telegram, web quick-chat, desktop …) come from
130
+ // the global message ledger, which is daemon-level and NOT project-scoped.
131
+ // Only surface them in the Base workspace (pid "0"); inside a real project the
132
+ // sidebar shows just that project's own agent conversations.
133
+ const isBase = String(pid) === "0";
131
134
  const threadsQ = useSWR(
132
- `/projects/${pid}/super-agent/threads`,
135
+ isBase ? `/projects/${pid}/super-agent/threads` : null,
133
136
  () => Conversations.threads(pid),
134
137
  { revalidateOnFocus: false },
135
138
  );
@@ -65,9 +65,13 @@ function useVisibleCount(
65
65
  const el = listRef.current;
66
66
  if (!el || !enabled) return;
67
67
  const measure = () => {
68
- const h = el.clientHeight;
69
- if (!h) return;
70
- const gap = parseFloat(getComputedStyle(el).rowGap) || 12;
68
+ const cs = getComputedStyle(el);
69
+ // clientHeight includes vertical padding; items lay out in the content box,
70
+ // so subtract the padding we added to give the active ring breathing room.
71
+ const padY = (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0);
72
+ const h = el.clientHeight - padY;
73
+ if (h <= 0) return;
74
+ const gap = parseFloat(cs.rowGap) || 12;
71
75
  // A hidden, always-present probe gives an accurate item height even on the
72
76
  // first paint or when zero real items currently fit.
73
77
  const probe = el.querySelector<HTMLElement>("[data-rail-probe]");
@@ -242,7 +246,7 @@ export function ProjectSidebar({ onSelect, onOpenRoby, onOpenAddProject }: Props
242
246
 
243
247
  <div
244
248
  ref={listRef}
245
- className="flex min-h-0 w-full flex-1 flex-col items-center gap-3 overflow-hidden"
249
+ className="flex min-h-0 w-full flex-1 flex-col items-center gap-3 overflow-hidden py-1.5"
246
250
  >
247
251
  {rest.length > 0 && collapsed && (
248
252
  <RailProjectMenu
@@ -250,6 +250,11 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
250
250
  const [conversationId, setConversationId] = useState<string | undefined>(undefined);
251
251
  const abortRef = useRef<AbortController | null>(null);
252
252
  const convoRef = useRef<string | undefined>(undefined);
253
+ // Monotonic token guarding async history loads. Every load()/loadThread()/
254
+ // clear() bumps it; a load only applies its result if it's still the latest.
255
+ // Without this, clicking chat A then B could land A's (slower) response last
256
+ // and paint A's messages under B's header.
257
+ const loadSeqRef = useRef(0);
253
258
 
254
259
  // Mutate the trailing assistant turn in place.
255
260
  const patchLast = useCallback((fn: (m: ChatMsg) => ChatMsg) => {
@@ -348,6 +353,7 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
348
353
  const stop = useCallback(() => abortRef.current?.abort(), []);
349
354
  const clear = useCallback(() => {
350
355
  if (streaming) return;
356
+ loadSeqRef.current++; // cancel any in-flight history load
351
357
  convoRef.current = undefined;
352
358
  setConversationId(undefined);
353
359
  setMsgs([]);
@@ -356,8 +362,13 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
356
362
  const load = useCallback(
357
363
  async (agentSlug: string, conversationId: string) => {
358
364
  if (streaming) return;
365
+ const seq = ++loadSeqRef.current;
366
+ // Blank the pane up front so it never shows the previous chat under the
367
+ // new header while the fetch is in flight.
368
+ setMsgs([]);
359
369
  try {
360
370
  const detail = await Conversations.get(pid, agentSlug, conversationId);
371
+ if (seq !== loadSeqRef.current) return; // superseded by a newer pick
361
372
  const loaded: ChatMsg[] = (detail.messages ?? [])
362
373
  .filter((m) => m.role === "user" || m.role === "assistant")
363
374
  .map((m) => ({
@@ -369,6 +380,10 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
369
380
  setConversationId(conversationId);
370
381
  setMsgs(loaded);
371
382
  } catch (e) {
383
+ if (seq !== loadSeqRef.current) return;
384
+ convoRef.current = undefined;
385
+ setConversationId(undefined);
386
+ setMsgs([]);
372
387
  onError?.((e as Error)?.message || t("shared_ui.err_load_conversation"));
373
388
  }
374
389
  },
@@ -378,8 +393,11 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
378
393
  const loadThread = useCallback(
379
394
  async (channel: string, threadId: string) => {
380
395
  if (streaming) return;
396
+ const seq = ++loadSeqRef.current;
397
+ setMsgs([]);
381
398
  try {
382
399
  const detail = await Conversations.thread(pid, channel, threadId);
400
+ if (seq !== loadSeqRef.current) return; // superseded by a newer pick
383
401
  const loaded: ChatMsg[] = (detail.messages ?? [])
384
402
  .filter((m) => m.role === "user" || m.role === "assistant")
385
403
  .map((m) => ({
@@ -393,6 +411,10 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
393
411
  setConversationId(undefined);
394
412
  setMsgs(loaded);
395
413
  } catch (e) {
414
+ if (seq !== loadSeqRef.current) return;
415
+ convoRef.current = undefined;
416
+ setConversationId(undefined);
417
+ setMsgs([]);
396
418
  onError?.((e as Error)?.message || t("shared_ui.err_load_conversation"));
397
419
  }
398
420
  },
@@ -65,8 +65,11 @@ export function ChatTab({ pid }: { pid: string }) {
65
65
  } else if (selected.kind === "thread") {
66
66
  void loadThread(selected.channel, selected.threadId);
67
67
  } else {
68
- // Switching to a live session = drop any previously bound conversation.
69
- if (conversationId) clear();
68
+ // Live session selected always start from a clean slate. (Threads leave
69
+ // conversationId undefined, so an `if (conversationId)` guard would skip
70
+ // clearing and the previous chat's messages would linger under the new
71
+ // header — the "title changes but content stays" bug.)
72
+ clear();
70
73
  }
71
74
  // eslint-disable-next-line react-hooks/exhaustive-deps
72
75
  }, [