@agent-native/core 0.72.2 → 0.72.3
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +12 -1
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +263 -13
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +16 -4
- package/corpus/core/src/client/use-chat-threads.ts +86 -5
- package/corpus/templates/assets/actions/navigate.ts +2 -1
- package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -2
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +27 -4
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +35 -5
- package/corpus/templates/assets/app/routes/_index.tsx +12 -0
- package/corpus/templates/assets/app/routes/chat.$threadId.tsx +1 -0
- package/corpus/templates/chat/actions/navigate.ts +3 -0
- package/corpus/templates/chat/app/components/layout/Layout.tsx +7 -2
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +27 -4
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +38 -6
- package/corpus/templates/chat/app/routes/_index.tsx +13 -0
- package/corpus/templates/chat/app/routes/chat.$threadId.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/chat.$threadId.tsx +26 -0
- package/dist/client/AgentPanel.d.ts +6 -2
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +4 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +22 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +215 -13
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +7 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +9 -5
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts +6 -0
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +79 -5
- package/dist/client/use-chat-threads.js.map +1 -1
- package/package.json +1 -1
|
@@ -10,6 +10,21 @@ export interface ChatTab {
|
|
|
10
10
|
/** Short name for sub-agent tabs (e.g. "Research", "Draft email") */
|
|
11
11
|
subAgentName?: string;
|
|
12
12
|
}
|
|
13
|
+
export interface ChatThreadUrlSyncOptions {
|
|
14
|
+
/** Query-string parameter used by the generic URL adapter. Default: `thread`. */
|
|
15
|
+
paramName?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Route-owned thread id. Pass `null` for the create route and a string for
|
|
18
|
+
* thread routes like `/chat/:threadId`.
|
|
19
|
+
*/
|
|
20
|
+
routeThreadId?: string | null;
|
|
21
|
+
/** Build the URL path for a thread id, or for create mode when id is null. */
|
|
22
|
+
getPath?: (threadId: string | null) => string;
|
|
23
|
+
/** Optional router navigation callback used with `getPath`. */
|
|
24
|
+
navigate?: (path: string, options?: {
|
|
25
|
+
replace?: boolean;
|
|
26
|
+
}) => void;
|
|
27
|
+
}
|
|
13
28
|
export interface MultiTabAssistantChatHeaderProps {
|
|
14
29
|
tabs: ChatTab[];
|
|
15
30
|
activeTabId: string;
|
|
@@ -41,6 +56,12 @@ export type MultiTabAssistantChatProps = Omit<AssistantChatProps, "tabId" | "thr
|
|
|
41
56
|
restoreActiveThread?: boolean;
|
|
42
57
|
/** Stable browser tab id used for tab-scoped app-state context. */
|
|
43
58
|
browserTabId?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Keep the active thread in URL state. `true` uses the generic `?thread=id`
|
|
61
|
+
* adapter; passing `routeThreadId` + `getPath` lets an app bind chats to
|
|
62
|
+
* route params such as `/chat/:threadId`.
|
|
63
|
+
*/
|
|
64
|
+
threadUrlSync?: boolean | ChatThreadUrlSyncOptions;
|
|
44
65
|
/**
|
|
45
66
|
* Bind new chats to a resource (deck, design, dashboard, etc.). When set,
|
|
46
67
|
* new chats automatically inherit this scope and scoped chats tuck away when
|
|
@@ -50,5 +71,5 @@ export type MultiTabAssistantChatProps = Omit<AssistantChatProps, "tabId" | "thr
|
|
|
50
71
|
*/
|
|
51
72
|
scope?: ChatThreadScope | null;
|
|
52
73
|
};
|
|
53
|
-
export declare function MultiTabAssistantChat({ showTabBar, renderHeader, renderOverlay, contentHidden, apiUrl, storageKey, restoreActiveThread, browserTabId, scope, ...props }: MultiTabAssistantChatProps): import("react/jsx-runtime").JSX.Element;
|
|
74
|
+
export declare function MultiTabAssistantChat({ showTabBar, renderHeader, renderOverlay, contentHidden, apiUrl, storageKey, restoreActiveThread, browserTabId, threadUrlSync, scope, ...props }: MultiTabAssistantChatProps): import("react/jsx-runtime").JSX.Element;
|
|
54
75
|
//# sourceMappingURL=MultiTabAssistantChat.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiTabAssistantChat.d.ts","sourceRoot":"","sources":["../../src/client/MultiTabAssistantChat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAMN,MAAM,OAAO,CAAC;AAUf,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAe5B,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,uBAAuB,CAAC;AAqqB/B,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;IACzC,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"MultiTabAssistantChat.d.ts","sourceRoot":"","sources":["../../src/client/MultiTabAssistantChat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAMN,MAAM,OAAO,CAAC;AAUf,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAe5B,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,uBAAuB,CAAC;AAqqB/B,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;IACzC,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAsGD,MAAM,WAAW,wBAAwB;IACvC,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,8EAA8E;IAC9E,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,CAAC;IAC9C,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CACpE;AAyED,MAAM,WAAW,gCAAgC;IAC/C,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,+BAA+B;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAC3C,kBAAkB,EAClB,OAAO,GAAG,UAAU,CACrB,GAAG;IACF,sCAAsC;IACtC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,gCAAgC,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5E,2DAA2D;IAC3D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,gCAAgC,KAAK,KAAK,CAAC,SAAS,CAAC;IAC7E,sGAAsG;IACtG,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,wBAAwB,CAAC;IACnD;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EACpC,UAAiB,EACjB,YAAY,EACZ,aAAa,EACb,aAAqB,EACrB,MAAqD,EACrD,UAAU,EACV,mBAA0B,EAC1B,YAAY,EACZ,aAAqB,EACrB,KAAY,EACZ,GAAG,KAAK,EACT,EAAE,0BAA0B,2CA00D5B"}
|
|
@@ -7,7 +7,7 @@ import { cn } from "./utils.js";
|
|
|
7
7
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./components/ui/tooltip.js";
|
|
8
8
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger, } from "./components/ui/popover.js";
|
|
9
9
|
import { useChatThreads, } from "./use-chat-threads.js";
|
|
10
|
-
import { agentNativePath } from "./api-path.js";
|
|
10
|
+
import { agentNativePath, appPath } from "./api-path.js";
|
|
11
11
|
import { callAction } from "./use-action.js";
|
|
12
12
|
import { RunStuckBanner } from "./RunStuckBanner.js";
|
|
13
13
|
import { DEFAULT_MODEL } from "../agent/default-model.js";
|
|
@@ -300,6 +300,83 @@ function chatTabStatusFromAgentTeamStatus(status) {
|
|
|
300
300
|
return isActiveAgentTeamStatus(status) ? "running" : "completed";
|
|
301
301
|
}
|
|
302
302
|
const STALE_THREAD_THRESHOLD_MS = 12 * 60 * 60 * 1000;
|
|
303
|
+
const DEFAULT_THREAD_URL_PARAM = "thread";
|
|
304
|
+
const THREAD_URL_CHANGED_EVENT = "agent-chat:url-thread-changed";
|
|
305
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
306
|
+
// The history patch is installed once and shared via a ref count so that
|
|
307
|
+
// multiple synced chats (or a remount) don't restore a stale `pushState`
|
|
308
|
+
// reference and silently drop a wrapper installed by another instance.
|
|
309
|
+
let historyPatchRefCount = 0;
|
|
310
|
+
let restoreHistoryPatch = null;
|
|
311
|
+
function installHistoryThreadUrlPatch() {
|
|
312
|
+
if (typeof window === "undefined")
|
|
313
|
+
return () => { };
|
|
314
|
+
historyPatchRefCount += 1;
|
|
315
|
+
if (historyPatchRefCount === 1) {
|
|
316
|
+
const originalPushState = window.history.pushState;
|
|
317
|
+
const originalReplaceState = window.history.replaceState;
|
|
318
|
+
const dispatchUrlChange = () => {
|
|
319
|
+
window.dispatchEvent(new Event(THREAD_URL_CHANGED_EVENT));
|
|
320
|
+
};
|
|
321
|
+
window.history.pushState = function pushState(...args) {
|
|
322
|
+
const result = originalPushState.apply(this, args);
|
|
323
|
+
dispatchUrlChange();
|
|
324
|
+
return result;
|
|
325
|
+
};
|
|
326
|
+
window.history.replaceState = function replaceState(...args) {
|
|
327
|
+
const result = originalReplaceState.apply(this, args);
|
|
328
|
+
dispatchUrlChange();
|
|
329
|
+
return result;
|
|
330
|
+
};
|
|
331
|
+
restoreHistoryPatch = () => {
|
|
332
|
+
window.history.pushState = originalPushState;
|
|
333
|
+
window.history.replaceState = originalReplaceState;
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
return () => {
|
|
337
|
+
historyPatchRefCount = Math.max(0, historyPatchRefCount - 1);
|
|
338
|
+
if (historyPatchRefCount === 0) {
|
|
339
|
+
restoreHistoryPatch?.();
|
|
340
|
+
restoreHistoryPatch = null;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function normalizeUrlThreadId(value) {
|
|
345
|
+
if (typeof value !== "string")
|
|
346
|
+
return null;
|
|
347
|
+
const trimmed = value.trim();
|
|
348
|
+
return trimmed || null;
|
|
349
|
+
}
|
|
350
|
+
function resolveThreadUrlSync(value) {
|
|
351
|
+
if (!value)
|
|
352
|
+
return { enabled: false, paramName: DEFAULT_THREAD_URL_PARAM };
|
|
353
|
+
if (value === true) {
|
|
354
|
+
return { enabled: true, paramName: DEFAULT_THREAD_URL_PARAM };
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
enabled: true,
|
|
358
|
+
paramName: value.paramName?.trim() || DEFAULT_THREAD_URL_PARAM,
|
|
359
|
+
...(hasOwn.call(value, "routeThreadId")
|
|
360
|
+
? { routeThreadId: normalizeUrlThreadId(value.routeThreadId) }
|
|
361
|
+
: {}),
|
|
362
|
+
...(value.getPath ? { getPath: value.getPath } : {}),
|
|
363
|
+
...(value.navigate ? { navigate: value.navigate } : {}),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function readUrlThreadId(paramName) {
|
|
367
|
+
if (typeof window === "undefined")
|
|
368
|
+
return null;
|
|
369
|
+
try {
|
|
370
|
+
const params = new URLSearchParams(window.location.search);
|
|
371
|
+
return normalizeUrlThreadId(params.get(paramName) ??
|
|
372
|
+
(paramName === DEFAULT_THREAD_URL_PARAM
|
|
373
|
+
? params.get("threadId")
|
|
374
|
+
: null));
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
303
380
|
function runToAgentTeamTabInfo(run) {
|
|
304
381
|
if (run.sourceRecord?.type && run.sourceRecord.type !== "agent-team-task") {
|
|
305
382
|
return null;
|
|
@@ -321,8 +398,102 @@ function runToAgentTeamTabInfo(run) {
|
|
|
321
398
|
status: run.status ?? "unknown",
|
|
322
399
|
};
|
|
323
400
|
}
|
|
324
|
-
export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderOverlay, contentHidden = false, apiUrl = agentNativePath("/_agent-native/agent-chat"), storageKey, restoreActiveThread = true, browserTabId, scope = null, ...props }) {
|
|
325
|
-
const {
|
|
401
|
+
export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderOverlay, contentHidden = false, apiUrl = agentNativePath("/_agent-native/agent-chat"), storageKey, restoreActiveThread = true, browserTabId, threadUrlSync = false, scope = null, ...props }) {
|
|
402
|
+
const { enabled: threadUrlSyncEnabled, paramName: threadUrlParamName, routeThreadId, getPath: getThreadPath, navigate: navigateThreadUrl, } = resolveThreadUrlSync(threadUrlSync);
|
|
403
|
+
const threadRouteControlsActiveThread = threadUrlSyncEnabled &&
|
|
404
|
+
threadUrlSync !== true &&
|
|
405
|
+
typeof threadUrlSync === "object" &&
|
|
406
|
+
hasOwn.call(threadUrlSync, "routeThreadId");
|
|
407
|
+
const [urlThreadId, setUrlThreadId] = useState(() => threadUrlSyncEnabled
|
|
408
|
+
? threadRouteControlsActiveThread
|
|
409
|
+
? (routeThreadId ?? null)
|
|
410
|
+
: readUrlThreadId(threadUrlParamName)
|
|
411
|
+
: null);
|
|
412
|
+
const urlThreadIdRef = useRef(urlThreadId);
|
|
413
|
+
urlThreadIdRef.current = urlThreadId;
|
|
414
|
+
useEffect(() => {
|
|
415
|
+
if (!threadUrlSyncEnabled || threadRouteControlsActiveThread)
|
|
416
|
+
return;
|
|
417
|
+
const update = () => setUrlThreadId(readUrlThreadId(threadUrlParamName));
|
|
418
|
+
const uninstallHistoryPatch = installHistoryThreadUrlPatch();
|
|
419
|
+
update();
|
|
420
|
+
window.addEventListener("popstate", update);
|
|
421
|
+
window.addEventListener(THREAD_URL_CHANGED_EVENT, update);
|
|
422
|
+
return () => {
|
|
423
|
+
uninstallHistoryPatch();
|
|
424
|
+
window.removeEventListener("popstate", update);
|
|
425
|
+
window.removeEventListener(THREAD_URL_CHANGED_EVENT, update);
|
|
426
|
+
};
|
|
427
|
+
}, [
|
|
428
|
+
threadRouteControlsActiveThread,
|
|
429
|
+
threadUrlParamName,
|
|
430
|
+
threadUrlSyncEnabled,
|
|
431
|
+
]);
|
|
432
|
+
useEffect(() => {
|
|
433
|
+
if (!threadUrlSyncEnabled || !threadRouteControlsActiveThread)
|
|
434
|
+
return;
|
|
435
|
+
setUrlThreadId(routeThreadId ?? null);
|
|
436
|
+
}, [routeThreadId, threadRouteControlsActiveThread, threadUrlSyncEnabled]);
|
|
437
|
+
const writeThreadUrl = useCallback((threadId, options = {}) => {
|
|
438
|
+
if (!threadUrlSyncEnabled || typeof window === "undefined")
|
|
439
|
+
return;
|
|
440
|
+
try {
|
|
441
|
+
const normalizedThreadId = normalizeUrlThreadId(threadId);
|
|
442
|
+
let next;
|
|
443
|
+
if (getThreadPath) {
|
|
444
|
+
next = getThreadPath(normalizedThreadId);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
const url = new URL(window.location.href);
|
|
448
|
+
if (normalizedThreadId) {
|
|
449
|
+
url.searchParams.set(threadUrlParamName, normalizedThreadId);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
url.searchParams.delete(threadUrlParamName);
|
|
453
|
+
}
|
|
454
|
+
if (threadUrlParamName !== "threadId") {
|
|
455
|
+
url.searchParams.delete("threadId");
|
|
456
|
+
}
|
|
457
|
+
next = `${url.pathname}${url.search}${url.hash}`;
|
|
458
|
+
}
|
|
459
|
+
const current = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
460
|
+
if (next === current) {
|
|
461
|
+
setUrlThreadId(normalizedThreadId);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (getThreadPath && navigateThreadUrl) {
|
|
465
|
+
navigateThreadUrl(next, { replace: options.replace === true });
|
|
466
|
+
setUrlThreadId(normalizedThreadId);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const method = options.replace ? "replaceState" : "pushState";
|
|
470
|
+
// `getThreadPath` returns a router-local path (no app basename). When we
|
|
471
|
+
// fall back to the raw History API instead of a router navigate, resolve
|
|
472
|
+
// the basename so deep-link reloads work in mounted apps.
|
|
473
|
+
const historyTarget = getThreadPath ? appPath(next) : next;
|
|
474
|
+
window.history[method](window.history.state, "", historyTarget);
|
|
475
|
+
setUrlThreadId(normalizedThreadId);
|
|
476
|
+
window.dispatchEvent(new Event(THREAD_URL_CHANGED_EVENT));
|
|
477
|
+
const popstate = typeof PopStateEvent === "function"
|
|
478
|
+
? new PopStateEvent("popstate", { state: window.history.state })
|
|
479
|
+
: new Event("popstate");
|
|
480
|
+
window.dispatchEvent(popstate);
|
|
481
|
+
}
|
|
482
|
+
catch { }
|
|
483
|
+
}, [
|
|
484
|
+
getThreadPath,
|
|
485
|
+
navigateThreadUrl,
|
|
486
|
+
threadUrlParamName,
|
|
487
|
+
threadUrlSyncEnabled,
|
|
488
|
+
]);
|
|
489
|
+
const { threads, activeThreadId, isLoading, createThread, switchThread: switchThreadState, detachThread, forkThread, saveThreadData, generateTitle, searchThreads, refreshThreads, isNewThread, } = useChatThreads(apiUrl, storageKey, scope, {
|
|
490
|
+
restoreActiveThread,
|
|
491
|
+
routeThreadId: threadUrlSyncEnabled ? urlThreadId : undefined,
|
|
492
|
+
});
|
|
493
|
+
const switchThread = useCallback((threadId, options = {}) => {
|
|
494
|
+
switchThreadState(threadId);
|
|
495
|
+
writeThreadUrl(threadId, options);
|
|
496
|
+
}, [switchThreadState, writeThreadUrl]);
|
|
326
497
|
// Namespace all localStorage keys by storageKey when provided (for per-app isolation in frame)
|
|
327
498
|
const keyPrefix = storageKey ? `:${storageKey}` : "";
|
|
328
499
|
const modelSelectionKey = `${MODEL_SELECTION_STORAGE_KEY}${keyPrefix}`;
|
|
@@ -740,9 +911,19 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
740
911
|
});
|
|
741
912
|
// If active thread is stale, start fresh
|
|
742
913
|
if (!parentMap[activeThreadId] && isStale(activeThreadId)) {
|
|
743
|
-
createThread()
|
|
914
|
+
createThread().then((id) => {
|
|
915
|
+
if (id)
|
|
916
|
+
writeThreadUrl(null);
|
|
917
|
+
});
|
|
744
918
|
}
|
|
745
|
-
}, [
|
|
919
|
+
}, [
|
|
920
|
+
activeThreadId,
|
|
921
|
+
threads,
|
|
922
|
+
parentMap,
|
|
923
|
+
switchThread,
|
|
924
|
+
createThread,
|
|
925
|
+
writeThreadUrl,
|
|
926
|
+
]);
|
|
746
927
|
// Ensure active thread is always in open tabs.
|
|
747
928
|
// Use functional update to check inside the setter — avoids race with the
|
|
748
929
|
// initialization effect that may have already added the ID in the same batch.
|
|
@@ -777,10 +958,11 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
777
958
|
if (id) {
|
|
778
959
|
newThreadIds.current.add(id);
|
|
779
960
|
setOpenTabIds([id]);
|
|
961
|
+
writeThreadUrl(null, { replace: true });
|
|
780
962
|
}
|
|
781
963
|
});
|
|
782
964
|
}
|
|
783
|
-
}, [isLoading, openTabIds, activeThreadId, createThread]);
|
|
965
|
+
}, [isLoading, openTabIds, activeThreadId, createThread, writeThreadUrl]);
|
|
784
966
|
useEffect(() => {
|
|
785
967
|
let stopped = false;
|
|
786
968
|
let timer = null;
|
|
@@ -1046,9 +1228,12 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1046
1228
|
mountedTabsRef.current.add(newId);
|
|
1047
1229
|
}
|
|
1048
1230
|
setOpenTabIds((prev) => prev.includes(newId) ? prev : [...prev, newId]);
|
|
1231
|
+
if (!background) {
|
|
1232
|
+
writeThreadUrl(newId);
|
|
1233
|
+
}
|
|
1049
1234
|
sendToTab(newId);
|
|
1050
1235
|
if (background && previousTabId) {
|
|
1051
|
-
|
|
1236
|
+
switchThreadState(previousTabId);
|
|
1052
1237
|
}
|
|
1053
1238
|
}
|
|
1054
1239
|
});
|
|
@@ -1077,6 +1262,8 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1077
1262
|
removeContextInTab,
|
|
1078
1263
|
setContextInTab,
|
|
1079
1264
|
switchThread,
|
|
1265
|
+
switchThreadState,
|
|
1266
|
+
writeThreadUrl,
|
|
1080
1267
|
]);
|
|
1081
1268
|
// Replay submits posted before this lazy panel's listener attached. Dedup in
|
|
1082
1269
|
// the handler keeps a live + replayed message single.
|
|
@@ -1147,9 +1334,10 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1147
1334
|
const id = await createThread();
|
|
1148
1335
|
if (id) {
|
|
1149
1336
|
newThreadIds.current.add(id);
|
|
1337
|
+
writeThreadUrl(null);
|
|
1150
1338
|
}
|
|
1151
1339
|
return id;
|
|
1152
|
-
}, [createThread]);
|
|
1340
|
+
}, [createThread, writeThreadUrl]);
|
|
1153
1341
|
const cleanupClosedTab = useCallback((tabId) => {
|
|
1154
1342
|
if (parentMapRef.current[tabId]) {
|
|
1155
1343
|
dismissedSubAgentTabsRef.current.add(tabId);
|
|
@@ -1187,6 +1375,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1187
1375
|
if (newId) {
|
|
1188
1376
|
newThreadIds.current.add(newId);
|
|
1189
1377
|
setOpenTabIds([newId]);
|
|
1378
|
+
writeThreadUrl(null);
|
|
1190
1379
|
}
|
|
1191
1380
|
});
|
|
1192
1381
|
return prev; // Keep old tab until new one is ready
|
|
@@ -1199,7 +1388,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1199
1388
|
return next;
|
|
1200
1389
|
});
|
|
1201
1390
|
cleanupClosedTab(tabId);
|
|
1202
|
-
}, [switchThread, createThread, cleanupClosedTab]);
|
|
1391
|
+
}, [switchThread, createThread, cleanupClosedTab, writeThreadUrl]);
|
|
1203
1392
|
const closeOtherTabs = useCallback((tabId) => {
|
|
1204
1393
|
for (const id of openTabIdsRef.current) {
|
|
1205
1394
|
if (id !== tabId && parentMapRef.current[id]) {
|
|
@@ -1245,7 +1434,8 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1245
1434
|
if (id) {
|
|
1246
1435
|
newThreadIds.current.add(id);
|
|
1247
1436
|
setOpenTabIds([id]);
|
|
1248
|
-
|
|
1437
|
+
switchThreadState(id);
|
|
1438
|
+
writeThreadUrl(null);
|
|
1249
1439
|
dismissedSubAgentTabsRef.current.clear();
|
|
1250
1440
|
// Clean up all old refs
|
|
1251
1441
|
chatRefs.current.clear();
|
|
@@ -1256,7 +1446,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1256
1446
|
setSubAgentNames({});
|
|
1257
1447
|
setSubAgentStatuses({});
|
|
1258
1448
|
}
|
|
1259
|
-
}, [createThread,
|
|
1449
|
+
}, [createThread, switchThreadState, writeThreadUrl]);
|
|
1260
1450
|
// Keyboard shortcuts dispatched from AgentPanel based on the active mode
|
|
1261
1451
|
useEffect(() => {
|
|
1262
1452
|
const handleCloseCurrent = () => {
|
|
@@ -1287,13 +1477,20 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1287
1477
|
return;
|
|
1288
1478
|
if (detail?.newThread === true) {
|
|
1289
1479
|
newThreadIds.current.add(threadId);
|
|
1480
|
+
void createThread(threadId).then((createdId) => {
|
|
1481
|
+
if (!createdId)
|
|
1482
|
+
return;
|
|
1483
|
+
setOpenTabIds((prev) => prev.includes(createdId) ? prev : [...prev, createdId]);
|
|
1484
|
+
writeThreadUrl(null);
|
|
1485
|
+
});
|
|
1486
|
+
return;
|
|
1290
1487
|
}
|
|
1291
1488
|
setOpenTabIds((prev) => prev.includes(threadId) ? prev : [...prev, threadId]);
|
|
1292
1489
|
switchThread(threadId);
|
|
1293
1490
|
};
|
|
1294
1491
|
window.addEventListener("agent-chat:open-thread", handleOpenThread);
|
|
1295
1492
|
return () => window.removeEventListener("agent-chat:open-thread", handleOpenThread);
|
|
1296
|
-
}, [switchThread]);
|
|
1493
|
+
}, [createThread, switchThread, writeThreadUrl]);
|
|
1297
1494
|
const clearActiveTab = useCallback(() => {
|
|
1298
1495
|
const tabIdToClear = activeThreadIdRef.current;
|
|
1299
1496
|
void addTab().then((newTabId) => {
|
|
@@ -1421,7 +1618,12 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1421
1618
|
}, [generateTitle, saveThreadData]);
|
|
1422
1619
|
const handleSaveThread = useCallback((threadId, data) => {
|
|
1423
1620
|
saveThreadData(threadId, data);
|
|
1424
|
-
|
|
1621
|
+
if (data.messageCount > 0 &&
|
|
1622
|
+
threadId === activeThreadIdRef.current &&
|
|
1623
|
+
urlThreadIdRef.current !== threadId) {
|
|
1624
|
+
writeThreadUrl(threadId);
|
|
1625
|
+
}
|
|
1626
|
+
}, [saveThreadData, writeThreadUrl]);
|
|
1425
1627
|
// ─── Slash command handler ──────────────────────────────────────────
|
|
1426
1628
|
const [helpVisible, setHelpVisible] = useState(false);
|
|
1427
1629
|
const handleSlashCommand = useCallback((command) => {
|