@agent-native/core 0.167.0 → 0.167.4

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.
Files changed (79) hide show
  1. package/corpus/templates/analytics/.agents/skills/agent-native-docs/SKILL.md +11 -0
  2. package/corpus/templates/assets/.agents/skills/agent-native-docs/SKILL.md +11 -0
  3. package/corpus/templates/brain/.agents/skills/agent-native-docs/SKILL.md +11 -0
  4. package/corpus/templates/calendar/.agents/skills/agent-native-docs/SKILL.md +11 -0
  5. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
  6. package/corpus/templates/clips/.agents/skills/agent-native-docs/SKILL.md +11 -0
  7. package/corpus/templates/clips/CHANGELOG.md +1 -0
  8. package/corpus/templates/clips/app/routes/download.tsx +78 -131
  9. package/corpus/templates/content/.agents/skills/agent-native-docs/SKILL.md +11 -0
  10. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +11 -0
  11. package/corpus/templates/design/.agents/skills/agent-native-docs/SKILL.md +11 -0
  12. package/corpus/templates/dispatch/.agents/skills/agent-native-docs/SKILL.md +11 -0
  13. package/corpus/templates/factory/.agents/skills/agent-native-docs/SKILL.md +11 -0
  14. package/corpus/templates/forms/.agents/skills/agent-native-docs/SKILL.md +11 -0
  15. package/corpus/templates/macros/.agents/skills/agent-native-docs/SKILL.md +11 -0
  16. package/corpus/templates/mail/.agents/skills/agent-native-docs/SKILL.md +11 -0
  17. package/corpus/templates/plan/.agents/skills/agent-native-docs/SKILL.md +11 -0
  18. package/corpus/templates/slides/.agents/skills/agent-native-docs/SKILL.md +11 -0
  19. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +11 -0
  20. package/dist/changelog/parse.js +219 -8
  21. package/dist/chat-threads/store.d.ts +7 -0
  22. package/dist/chat-threads/store.js +15 -0
  23. package/dist/client/AgentPanel.d.ts +5 -1
  24. package/dist/client/AgentPanel.js +8 -8
  25. package/dist/client/AssistantChat.d.ts +3 -1
  26. package/dist/client/AssistantChat.js +22 -6
  27. package/dist/client/MultiTabAssistantChat.d.ts +3 -1
  28. package/dist/client/MultiTabAssistantChat.js +5 -4
  29. package/dist/client/agent-page/AgentJobsTab.js +39 -6
  30. package/dist/client/agent-page/AutomationScheduleDialog.d.ts +10 -1
  31. package/dist/client/agent-page/AutomationScheduleDialog.js +3 -2
  32. package/dist/client/agent-page/ScheduledTriggerNotice.d.ts +22 -0
  33. package/dist/client/agent-page/ScheduledTriggerNotice.js +84 -0
  34. package/dist/client/agent-page/scheduled-trigger-state.d.ts +28 -0
  35. package/dist/client/agent-page/scheduled-trigger-state.js +10 -0
  36. package/dist/client/agent-page/use-jobs.d.ts +7 -0
  37. package/dist/client/agent-page/use-jobs.js +14 -0
  38. package/dist/client/blocks/library/diagram.config.d.ts +4 -1
  39. package/dist/client/blocks/library/diagram.js +1 -1
  40. package/dist/client/chat/run-recovery.js +15 -7
  41. package/dist/client/use-chat-threads.d.ts +7 -0
  42. package/dist/client/use-chat-threads.js +144 -40
  43. package/dist/deploy/build.d.ts +5 -0
  44. package/dist/deploy/build.js +12 -1
  45. package/dist/framework-tools.js +1 -0
  46. package/dist/jobs/actions/get-scheduled-trigger-status.d.ts +13 -0
  47. package/dist/jobs/actions/get-scheduled-trigger-status.js +21 -0
  48. package/dist/localization/default-messages.d.ts +14 -0
  49. package/dist/localization/default-messages.js +14 -0
  50. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  51. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  52. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  53. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  54. package/dist/secrets/routes.d.ts +9 -9
  55. package/dist/server/action-discovery.js +4 -0
  56. package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +42 -1
  57. package/dist/server/agent-chat/recurring-jobs-runtime.js +102 -9
  58. package/dist/server/agent-chat-plugin.d.ts +3 -1
  59. package/dist/server/agent-chat-plugin.js +115 -24
  60. package/dist/server/attribution.d.ts +29 -0
  61. package/dist/server/attribution.js +68 -0
  62. package/dist/server/auth.js +58 -15
  63. package/dist/server/better-auth-instance.js +16 -4
  64. package/dist/server/identity-sso.js +14 -2
  65. package/dist/server/realtime-token.d.ts +1 -1
  66. package/dist/server/request-context.d.ts +8 -0
  67. package/dist/server/request-context.js +4 -0
  68. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
  69. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +11 -0
  70. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +11 -0
  71. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +11 -0
  72. package/dist/vite/client.js +10 -0
  73. package/docs/content/deployment.mdx +3 -1
  74. package/docs/content/recurring-jobs.mdx +6 -0
  75. package/package.json +2 -2
  76. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
  77. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +11 -0
  78. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +11 -0
  79. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +11 -0
@@ -237,6 +237,213 @@ function normalizedChangelogText(value) {
237
237
  .replace(/\s+/g, " ")
238
238
  .trim();
239
239
  }
240
+ function stripHtmlComments(line, inComment, initialInlineCodeMarker) {
241
+ let cursor = 0;
242
+ let visibleLine = "";
243
+ const comments = [];
244
+ let inlineCodeMarker = initialInlineCodeMarker;
245
+ while (cursor < line.length) {
246
+ if (inComment) {
247
+ const commentEnd = line.indexOf("-->", cursor);
248
+ if (commentEnd === -1) {
249
+ return {
250
+ line: visibleLine,
251
+ inComment: true,
252
+ inlineCodeMarker,
253
+ comments,
254
+ };
255
+ }
256
+ cursor = commentEnd + 3;
257
+ inComment = false;
258
+ continue;
259
+ }
260
+ if (inlineCodeMarker) {
261
+ const codeEnd = line.indexOf(inlineCodeMarker, cursor);
262
+ if (codeEnd === -1) {
263
+ visibleLine += line.slice(cursor);
264
+ break;
265
+ }
266
+ visibleLine += line.slice(cursor, codeEnd + inlineCodeMarker.length);
267
+ cursor = codeEnd + inlineCodeMarker.length;
268
+ inlineCodeMarker = undefined;
269
+ continue;
270
+ }
271
+ const commentStart = line.indexOf("<!--", cursor);
272
+ const codeStartIndex = line.indexOf("`", cursor);
273
+ if (codeStartIndex !== -1 &&
274
+ (commentStart === -1 || codeStartIndex < commentStart)) {
275
+ visibleLine += line.slice(cursor, codeStartIndex);
276
+ const codeStart = /^`+/.exec(line.slice(codeStartIndex))?.[0] ?? "`";
277
+ visibleLine += codeStart;
278
+ cursor = codeStartIndex + codeStart.length;
279
+ inlineCodeMarker = codeStart;
280
+ continue;
281
+ }
282
+ if (commentStart === -1) {
283
+ visibleLine += line.slice(cursor);
284
+ break;
285
+ }
286
+ visibleLine += line.slice(cursor, commentStart);
287
+ const commentEnd = line.indexOf("-->", commentStart + 4);
288
+ if (commentEnd !== -1) {
289
+ comments.push(line.slice(commentStart, commentEnd + 3));
290
+ }
291
+ cursor = commentStart + 4;
292
+ inComment = true;
293
+ }
294
+ return { line: visibleLine, inComment, inlineCodeMarker, comments };
295
+ }
296
+ function splitChangelogBodyGroups(body) {
297
+ const matches = [];
298
+ let offset = 0;
299
+ let fenceMarker;
300
+ let htmlComment = false;
301
+ let inlineCodeMarker;
302
+ for (const line of body.split(/\r?\n/)) {
303
+ const lineEnd = offset + line.length;
304
+ if (fenceMarker) {
305
+ const fence = /^\s*(`{3,}|~{3,})(.*)$/.exec(line)?.[1];
306
+ const closingFence = /^\s*(`{3,}|~{3,})\s*$/.exec(line)?.[1];
307
+ if (fence &&
308
+ closingFence &&
309
+ fenceMarker.character === fence[0] &&
310
+ closingFence.length >= fenceMarker.length) {
311
+ fenceMarker = undefined;
312
+ }
313
+ }
314
+ else {
315
+ let visibleLine;
316
+ let headingComments = [];
317
+ if (!htmlComment && !inlineCodeMarker) {
318
+ const fence = /^\s*(`{3,}|~{3,})(.*)$/.exec(line)?.[1];
319
+ if (fence) {
320
+ fenceMarker = { character: fence[0], length: fence.length };
321
+ }
322
+ else {
323
+ const commentResult = stripHtmlComments(line, false, inlineCodeMarker);
324
+ htmlComment = commentResult.inComment;
325
+ inlineCodeMarker = commentResult.inlineCodeMarker;
326
+ visibleLine = commentResult.line;
327
+ headingComments = commentResult.comments;
328
+ }
329
+ }
330
+ else {
331
+ const commentResult = stripHtmlComments(line, htmlComment, inlineCodeMarker);
332
+ htmlComment = commentResult.inComment;
333
+ inlineCodeMarker = commentResult.inlineCodeMarker;
334
+ visibleLine = commentResult.line;
335
+ headingComments = commentResult.comments;
336
+ }
337
+ if (visibleLine !== undefined && !fenceMarker) {
338
+ const fence = /^\s*(`{3,}|~{3,})(.*)$/.exec(visibleLine)?.[1];
339
+ if (fence) {
340
+ fenceMarker = { character: fence[0], length: fence.length };
341
+ }
342
+ else {
343
+ const heading = /^ {0,3}###\s+(.+?)(?:\s+#+)?\s*$/.exec(visibleLine);
344
+ if (heading &&
345
+ CHANGELOG_GROUP_ORDER.some((type) => GROUP_LABELS[type].toLowerCase() ===
346
+ heading[1].trim().toLowerCase())) {
347
+ matches.push({
348
+ title: heading[1].trim(),
349
+ headingComments,
350
+ start: offset,
351
+ headingEnd: lineEnd,
352
+ });
353
+ }
354
+ }
355
+ }
356
+ }
357
+ const newlineLength = body.startsWith("\r\n", lineEnd)
358
+ ? 2
359
+ : lineEnd < body.length
360
+ ? 1
361
+ : 0;
362
+ offset = lineEnd + newlineLength;
363
+ }
364
+ if (matches.length === 0)
365
+ return { prefix: body.trim(), groups: [] };
366
+ const prefix = body.slice(0, matches[0].start).trim();
367
+ const groups = matches.map((match, index) => {
368
+ const end = matches[index + 1]?.start ?? body.length;
369
+ return {
370
+ title: match.title,
371
+ headingComments: match.headingComments,
372
+ body: body.slice(match.headingEnd, end).trim(),
373
+ index,
374
+ };
375
+ });
376
+ return { prefix, groups };
377
+ }
378
+ function changelogBodyGroupOrder(title) {
379
+ const normalizedTitle = title.toLowerCase();
380
+ const index = CHANGELOG_GROUP_ORDER.findIndex((type) => GROUP_LABELS[type].toLowerCase() === normalizedTitle);
381
+ return index === -1 ? CHANGELOG_GROUP_ORDER.length : index;
382
+ }
383
+ function normalizeChangelogBody(body) {
384
+ const { prefix, groups } = splitChangelogBodyGroups(body);
385
+ if (groups.length < 2)
386
+ return body.trim();
387
+ const uniqueGroups = new Map();
388
+ for (const group of groups) {
389
+ const key = group.title.toLowerCase();
390
+ const current = uniqueGroups.get(key);
391
+ const headingComments = current
392
+ ? [
393
+ ...current.headingComments,
394
+ ...group.headingComments.filter((comment) => !current.headingComments.includes(comment)),
395
+ ]
396
+ : group.headingComments;
397
+ uniqueGroups.set(key, {
398
+ title: current?.title ?? group.title,
399
+ headingComments,
400
+ body: [current?.body, group.body].filter(Boolean).join("\n"),
401
+ index: current?.index ?? group.index,
402
+ });
403
+ }
404
+ const renderedGroups = [...uniqueGroups.values()]
405
+ .sort((a, b) => changelogBodyGroupOrder(a.title) - changelogBodyGroupOrder(b.title) ||
406
+ a.index - b.index)
407
+ .map((group) => `### ${group.title}${group.headingComments.length ? ` ${group.headingComments.join(" ")}` : ""}\n\n${group.body}`);
408
+ return [prefix, ...renderedGroups].filter(Boolean).join("\n\n");
409
+ }
410
+ function mergeChangelogBodies(pendingBody, existingBody) {
411
+ const pending = splitChangelogBodyGroups(pendingBody);
412
+ const existing = splitChangelogBodyGroups(existingBody);
413
+ if (pending.groups.length === 0) {
414
+ return normalizeChangelogBody(existingBody);
415
+ }
416
+ if (existing.groups.length === 0) {
417
+ return [pendingBody, existingBody]
418
+ .map((body) => body.trim())
419
+ .filter(Boolean)
420
+ .join("\n\n");
421
+ }
422
+ const groups = new Map();
423
+ for (const group of [...pending.groups, ...existing.groups]) {
424
+ const key = group.title.toLowerCase();
425
+ const current = groups.get(key);
426
+ const headingComments = current
427
+ ? [
428
+ ...current.headingComments,
429
+ ...group.headingComments.filter((comment) => !current.headingComments.includes(comment)),
430
+ ]
431
+ : group.headingComments;
432
+ groups.set(key, {
433
+ title: current?.title ?? group.title,
434
+ headingComments,
435
+ body: [current?.body, group.body].filter(Boolean).join("\n"),
436
+ index: current?.index ?? group.index,
437
+ });
438
+ }
439
+ const prefix = [pending.prefix, existing.prefix].filter(Boolean).join("\n\n");
440
+ return normalizeChangelogBody([
441
+ prefix,
442
+ ...[...groups.values()].map((group) => `### ${group.title}${group.headingComments.length ? ` ${group.headingComments.join(" ")}` : ""}\n\n${group.body}`),
443
+ ]
444
+ .filter(Boolean)
445
+ .join("\n\n"));
446
+ }
240
447
  /**
241
448
  * Render an app-facing changelog that includes both released CHANGELOG.md
242
449
  * sections and adjacent folder-backed `changelog/*.md` entries. This is pure
@@ -246,10 +453,19 @@ function normalizedChangelogText(value) {
246
453
  export function mergePendingChangelog(existing, pending) {
247
454
  const cleanExisting = stripChangelogArchiveNotes(existing);
248
455
  const existingEntries = parseChangelog(cleanExisting);
456
+ const pendingSeen = new Set();
249
457
  const pendingWithText = pending.filter((entry) => {
250
458
  const text = entry.text.trim();
251
459
  if (!text)
252
460
  return false;
461
+ const pendingKey = [
462
+ entry.date ?? "",
463
+ entry.type,
464
+ normalizedChangelogText(text),
465
+ ].join("\u0000");
466
+ if (pendingSeen.has(pendingKey))
467
+ return false;
468
+ pendingSeen.add(pendingKey);
253
469
  return !existingEntries.some((existingEntry) => {
254
470
  if (entry.date &&
255
471
  existingEntry.date &&
@@ -261,7 +477,7 @@ export function mergePendingChangelog(existing, pending) {
261
477
  });
262
478
  if (pendingWithText.length === 0) {
263
479
  const sections = existingEntries
264
- .map((entry) => `## ${entry.title}\n\n${cleanChangelogBody(entry.body)}`)
480
+ .map((entry) => `## ${entry.title}\n\n${normalizeChangelogBody(cleanChangelogBody(entry.body))}`)
265
481
  .join("\n\n");
266
482
  return `${changelogHeader(cleanExisting)}${sections ? `\n\n${sections}` : ""}\n\n${CHANGELOG_ARCHIVE_NOTE}\n`;
267
483
  }
@@ -273,7 +489,7 @@ export function mergePendingChangelog(existing, pending) {
273
489
  const sections = existingEntries.map((entry) => ({
274
490
  title: entry.title,
275
491
  date: entry.date,
276
- body: cleanChangelogBody(entry.body),
492
+ body: normalizeChangelogBody(cleanChangelogBody(entry.body)),
277
493
  }));
278
494
  for (const title of [...pendingByTitle.keys()].sort(pendingSectionSort)) {
279
495
  const body = renderReleaseBody(pendingByTitle.get(title) ?? []);
@@ -283,12 +499,7 @@ export function mergePendingChangelog(existing, pending) {
283
499
  return entry.date === title || entry.title === title;
284
500
  });
285
501
  if (existingIndex !== -1) {
286
- sections[existingIndex].body = [
287
- body,
288
- cleanChangelogBody(sections[existingIndex].body),
289
- ]
290
- .filter(Boolean)
291
- .join("\n\n");
502
+ sections[existingIndex].body = mergeChangelogBodies(body, cleanChangelogBody(sections[existingIndex].body));
292
503
  continue;
293
504
  }
294
505
  const newSection = { title, date: title.match(ISO_DATE)?.[1], body };
@@ -26,6 +26,13 @@ export interface ChatThreadScope {
26
26
  id: string;
27
27
  label?: string;
28
28
  }
29
+ export declare function isAppOwnedChatScope(scope?: ChatThreadScope | null): boolean;
30
+ /**
31
+ * A scoped rail may claim a legacy unscoped thread on its first write, but
32
+ * once a thread has a scope, a non-null incoming scope must match it. App-
33
+ * owned threads additionally require a scope on every subsequent write.
34
+ */
35
+ export declare function threadScopeMismatch(existing?: ChatThreadScope | null, incoming?: ChatThreadScope | null): boolean;
29
36
  export interface ChatThreadSource {
30
37
  platform?: string | null;
31
38
  appId?: string | null;
@@ -241,6 +241,21 @@ export async function repairLegacyChatThreadMessageCounts(options = {}) {
241
241
  function generateId() {
242
242
  return `thread-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
243
243
  }
244
+ export function isAppOwnedChatScope(scope) {
245
+ return scope?.type === "workspace-app" || scope?.type === "desktop-app";
246
+ }
247
+ /**
248
+ * A scoped rail may claim a legacy unscoped thread on its first write, but
249
+ * once a thread has a scope, a non-null incoming scope must match it. App-
250
+ * owned threads additionally require a scope on every subsequent write.
251
+ */
252
+ export function threadScopeMismatch(existing, incoming) {
253
+ if (!existing)
254
+ return false;
255
+ if (!incoming)
256
+ return isAppOwnedChatScope(existing);
257
+ return existing.type !== incoming.type || existing.id !== incoming.id;
258
+ }
244
259
  function readScope(r) {
245
260
  const type = r.scope_type;
246
261
  const id = r.scope_id;
@@ -107,6 +107,8 @@ export interface AgentPanelProps extends Omit<AssistantChatProps, "onSwitchToCli
107
107
  restoreActiveThread?: boolean;
108
108
  /** Ambient resource context rendered as a composer chip. */
109
109
  scope?: import("./use-chat-threads.js").ChatThreadScope | null;
110
+ /** Keep app-owned chat history isolated to the supplied scope. */
111
+ isolateHistoryByScope?: boolean;
110
112
  /** @deprecated Scope context now appears inside the composer. */
111
113
  showScopeBadge?: MultiTabAssistantChatProps["showScopeBadge"];
112
114
  /** Stable browser tab id used for tab-scoped app-state context. */
@@ -239,6 +241,8 @@ export interface AgentSidebarProps {
239
241
  onFullscreenRequest?: () => void;
240
242
  /** Ambient resource context rendered as a composer chip. */
241
243
  scope?: import("./use-chat-threads.js").ChatThreadScope | null;
244
+ /** Keep app-owned chat history isolated to the supplied scope. */
245
+ isolateHistoryByScope?: boolean;
242
246
  /** @deprecated Scope context now appears inside the composer. */
243
247
  showScopeBadge?: MultiTabAssistantChatProps["showScopeBadge"];
244
248
  /** Stable browser tab id used for tab-scoped app-state context. */
@@ -254,7 +258,7 @@ export interface AgentSidebarProps {
254
258
  * Wraps app content with a toggleable agent sidebar.
255
259
  * Use AgentToggleButton in your header to open/close it.
256
260
  */
257
- export declare function AgentSidebar({ children, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
261
+ export declare function AgentSidebar({ children, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, isolateHistoryByScope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
258
262
  /**
259
263
  * Focus the agent chat composer input.
260
264
  * Opens the sidebar if closed, then focuses the text input.
@@ -436,7 +436,7 @@ function CodeAccessUnavailablePanel({ title, description, ctaLabel, ctaHref, sec
436
436
  });
437
437
  }, className: "inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent", children: secondaryCtaLabel })] })] }));
438
438
  }
439
- function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, emptyStateText, emptyStateAddon, suggestions, dynamicSuggestions, showHeader = true, onCollapse, isFullscreen, onToggleFullscreen, onFullViewRequest, onSnapTo75Percent, isWideDrawer, onExitWideDrawer, devAppUrl, storageKey, restoreActiveThread = true, scope, showScopeBadge, browserTabId, threadUrlSync, chatNotice, showTabBar = true, showPageNewChatButton = false, allowSettingsMode = true, chatOnly = false, agentPageHref, codeAccess, ...assistantChatProps }) {
439
+ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, emptyStateText, emptyStateAddon, suggestions, dynamicSuggestions, showHeader = true, onCollapse, isFullscreen, onToggleFullscreen, onFullViewRequest, onSnapTo75Percent, isWideDrawer, onExitWideDrawer, devAppUrl, storageKey, restoreActiveThread = true, scope, isolateHistoryByScope = false, showScopeBadge, browserTabId, threadUrlSync, chatNotice, showTabBar = true, showPageNewChatButton = false, allowSettingsMode = true, chatOnly = false, agentPageHref, codeAccess, ...assistantChatProps }) {
440
440
  const t = useT();
441
441
  const navigate = useNavigate();
442
442
  const location = useLocation();
@@ -838,7 +838,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
838
838
  const isActive = tab.id === activeTabId ||
839
839
  (tab.id === focusParentId &&
840
840
  activeTab?.parentThreadId === tab.id);
841
- return (_jsxs("div", { role: "button", tabIndex: 0, ref: isActive ? activeTabRefCb : undefined, onClick: () => setActiveTabId(tab.id), onKeyDown: activateOnKeyDown(() => setActiveTabId(tab.id)), className: cn("agent-tab relative flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1.5 text-[11px] font-medium cursor-pointer max-w-[150px]", isActive
841
+ return (_jsxs("div", { role: "button", tabIndex: 0, ref: isActive ? activeTabRefCb : undefined, onClick: () => setActiveTabId(tab.id), onKeyDown: activateOnKeyDown(() => setActiveTabId(tab.id)), className: cn("agent-tab relative flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1.5 text-[11px] font-medium cursor-pointer min-w-[56px] max-w-[150px]", isActive
842
842
  ? "bg-accent text-foreground"
843
843
  : "text-muted-foreground hover:bg-accent hover:text-foreground"), children: [_jsx("span", { className: "truncate pe-1", children: tab.label }), tab.status === "running" && (_jsx("span", { className: "h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/50 animate-pulse" })), _jsx("button", { type: "button", "aria-label": t("agentPanel.closeTab"), onClick: (e) => {
844
844
  e.stopPropagation();
@@ -881,7 +881,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
881
881
  const isActive = tab.id === activeTabId ||
882
882
  (tab.id === focusParentId &&
883
883
  activeTab?.parentThreadId === tab.id);
884
- return (_jsxs("div", { role: "button", tabIndex: 0, ref: isActive ? activeTabRefCb : undefined, onClick: () => setActiveTabId(tab.id), onKeyDown: activateOnKeyDown(() => setActiveTabId(tab.id)), className: cn("agent-tab relative flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1.5 text-[11px] font-medium cursor-pointer max-w-[150px]", isActive
884
+ return (_jsxs("div", { role: "button", tabIndex: 0, ref: isActive ? activeTabRefCb : undefined, onClick: () => setActiveTabId(tab.id), onKeyDown: activateOnKeyDown(() => setActiveTabId(tab.id)), className: cn("agent-tab relative flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1.5 text-[11px] font-medium cursor-pointer min-w-[56px] max-w-[150px]", isActive
885
885
  ? "bg-accent text-foreground"
886
886
  : "text-muted-foreground hover:bg-accent hover:text-foreground"), children: [_jsx("span", { className: "truncate pe-1", children: tab.label }), tab.status === "running" && (_jsx("span", { className: "h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/50 animate-pulse" })), _jsx("button", { type: "button", "aria-label": t("agentPanel.closeTab"), onClick: (e) => {
887
887
  e.stopPropagation();
@@ -899,7 +899,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
899
899
  })
900
900
  : cliTabs.map((id, i) => (_jsxs("div", { role: "button", tabIndex: 0, ref: id === activeCliTab
901
901
  ? activeTabRefCb
902
- : undefined, onClick: () => setActiveCliTab(id), onKeyDown: activateOnKeyDown(() => setActiveCliTab(id)), className: cn("agent-tab relative flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1.5 text-[11px] font-medium cursor-pointer", id === activeCliTab
902
+ : undefined, onClick: () => setActiveCliTab(id), onKeyDown: activateOnKeyDown(() => setActiveCliTab(id)), className: cn("agent-tab relative flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1.5 text-[11px] font-medium cursor-pointer min-w-[56px]", id === activeCliTab
903
903
  ? "bg-accent text-foreground"
904
904
  : "text-muted-foreground hover:bg-accent hover:text-foreground"), children: [_jsxs("span", { children: ["Terminal ", i + 1] }), _jsx("button", { type: "button", "aria-label": t("agentPanel.closeTab"), onClick: (e) => {
905
905
  e.stopPropagation();
@@ -917,7 +917,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
917
917
  ? "bg-accent text-foreground"
918
918
  : "text-muted-foreground hover:bg-accent hover:text-foreground"), children: "Main" }), childTabs.map((tab) => (_jsxs("div", { role: "button", tabIndex: 0, ref: tab.id === activeTabId
919
919
  ? activeTabRefCb
920
- : undefined, onClick: () => setActiveTabId(tab.id), onKeyDown: activateOnKeyDown(() => setActiveTabId(tab.id)), className: cn("agent-tab relative flex shrink-0 items-center gap-1 rounded-md px-2 py-1 text-[10px] font-medium cursor-pointer max-w-[140px]", tab.id === activeTabId
920
+ : undefined, onClick: () => setActiveTabId(tab.id), onKeyDown: activateOnKeyDown(() => setActiveTabId(tab.id)), className: cn("agent-tab relative flex shrink-0 items-center gap-1 rounded-md px-2 py-1 text-[10px] font-medium cursor-pointer min-w-[48px] max-w-[140px]", tab.id === activeTabId
921
921
  ? "bg-accent text-foreground"
922
922
  : "text-muted-foreground hover:bg-accent hover:text-foreground"), children: [_jsx("span", { className: "truncate pe-1", children: tab.subAgentName || tab.label }), tab.status === "running" && (_jsx("span", { className: "h-1 w-1 shrink-0 rounded-full bg-muted-foreground/50 animate-pulse" })), _jsx("button", { type: "button", "aria-label": t("agentPanel.closeTab"), onClick: (e) => {
923
923
  e.stopPropagation();
@@ -981,7 +981,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
981
981
  `margin-left:auto;margin-right:auto;width:100%;}`,
982
982
  } }), SHOW_ONBOARDING && mounted && (_jsx(Suspense, { fallback: null, children: _jsx(OnboardingPanel, {}) })), showFirstRunOnboarding && mounted && !insideAgentSidebar && (_jsx(Suspense, { fallback: null, children: _jsx(FirstRunOnboarding, {}) })), _jsx("div", { className: cn("flex flex-col min-h-0", mode === "chat" ? "flex-1" : "shrink-0"), children: mounted && (_jsx(Suspense, { fallback: _jsx(ChatLoadingSkeleton, { renderHeader: showHeader ? renderChatHeader : undefined, centerComposerWhenEmpty: assistantChatProps.centerComposerWhenEmpty, composerSlot: assistantChatProps.composerSlot, composerAreaClassName: assistantChatProps.composerAreaClassName, composerLayoutVariant: assistantChatProps.composerLayoutVariant }), children: _jsx(MultiTabAssistantChatLazy, { ...assistantChatProps, agentChatSurface: effectiveAgentChatSurface, apiUrl: apiUrl, showHeader: false, renderHeader: showHeader ? renderChatHeader : undefined, showTabBar: showTabBar, renderOverlay: showPageNewChatButton && !showHeader
983
983
  ? renderPageChatOverlay
984
- : undefined, contentHidden: mode !== "chat", emptyStateText: emptyStateText, emptyStateAddon: emptyStateAddon, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, onSwitchToCli: () => switchMode("cli"), execMode: execMode, onExecModeChange: switchExecMode, storageKey: storageKey, restoreActiveThread: restoreActiveThread, scope: scope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync }) })) }), canUseCodeTools
984
+ : undefined, contentHidden: mode !== "chat", emptyStateText: emptyStateText, emptyStateAddon: emptyStateAddon, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, onSwitchToCli: () => switchMode("cli"), execMode: execMode, onExecModeChange: switchExecMode, storageKey: storageKey, restoreActiveThread: restoreActiveThread, scope: scope, isolateHistoryByScope: isolateHistoryByScope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync }) })) }), canUseCodeTools
985
985
  ? mode === "cli" &&
986
986
  cliTabs.map((id) => (_jsx("div", { className: "min-h-0 relative flex-1", style: {
987
987
  display: id === activeCliTab ? undefined : "none",
@@ -1418,7 +1418,7 @@ export function AgentChatSurface({ mode = "panel", className, defaultMode = "cha
1418
1418
  * Wraps app content with a toggleable agent sidebar.
1419
1419
  * Use AgentToggleButton in your header to open/close it.
1420
1420
  */
1421
- export function AgentSidebar({ children, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
1421
+ export function AgentSidebar({ children, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, isolateHistoryByScope = false, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
1422
1422
  const staticHostedHarnessEnabled = isHostedHarnessConfigured(injectedAgentNativeConfig().harness);
1423
1423
  const hostedHarnessQuery = useActionQuery("get-hosted-harness-config", undefined, { enabled: staticHostedHarnessEnabled });
1424
1424
  const hostedHarnessStatus = hostedHarnessQuery.data;
@@ -2059,7 +2059,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
2059
2059
  ? "desktop"
2060
2060
  : undefined, "data-agent-sidebar-layout": panelLayout, "data-agent-sidebar-position": effectivePosition, "data-agent-native-hosted-harness-ui": hostedHarnessUi ? "desktop" : undefined, "data-agent-sidebar-state": panelOpen ? "open" : "closed", "data-agent-sidebar-per-app-chat": isPerAppChatSidebar ? "true" : undefined, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, "data-agent-sidebar-chat-handoff": chatViewTransitionHandoff ? "true" : undefined, style: chatViewTransition
2061
2061
  ? getAgentChatViewTransitionStyle(panelStyle)
2062
- : panelStyle, inert: sidebarAnimationEnabled && !panelOpen ? true : undefined, "aria-hidden": sidebarAnimationEnabled && !panelOpen ? true : undefined, children: _jsx("div", { className: "agent-sidebar-panel-inner flex min-h-0 flex-1 flex-col", children: _jsx(AgentPanel, { emptyStateText: emptyStateText, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, composerToolbarSlot: composerToolbarSlot, composerSlot: composerSlot, onComposerTextChange: onComposerTextChange, imageModelMenu: imageModelMenu, availableAgents: effectiveAvailableAgents, availableModels: availableModels, modelListLoading: modelListLoading, selectedAgent: effectiveSelectedAgent, onAgentChange: effectiveOnAgentChange, hostedHarness: hostedHarnessEnabled, onConnectLocalRuntime: onConnectLocalRuntime, runtime: runtime, adapterReloadKey: adapterReloadKey, threadFooterSlot: threadFooterSlot, apiUrl: apiUrl, agentChatSurface: agentChatSurface, desktopIdentityUnauthenticated: desktopIdentityUnauthenticated, desktopIdentityAuthenticated: desktopIdentityAuthenticated, showTabBar: effectiveShowTabBar, suppressInlineOpenApp: suppressInlineOpenApp, composerPlaceholder: composerPlaceholder, missingApiKeySetupLayout: "sidebar", onCollapse: () => setOpenPersisted(false), onSnapTo75Percent: isMobile ? undefined : snapTo75Percent, isWideDrawer: isMobile ? false : isWideDrawer, onExitWideDrawer: isMobile ? undefined : exitWideDrawer, onFullViewRequest: onFullscreenRequest, storageKey: storageKey, scope: scope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync, agentPageHref: agentPageHref, allowSettingsMode: false, chatOnly: true }) }) }), showResizeHandle && isLeft && (_jsx(ResizeHandle, { position: effectivePosition, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd }))] })) : null;
2062
+ : panelStyle, inert: sidebarAnimationEnabled && !panelOpen ? true : undefined, "aria-hidden": sidebarAnimationEnabled && !panelOpen ? true : undefined, children: _jsx("div", { className: "agent-sidebar-panel-inner flex min-h-0 flex-1 flex-col", children: _jsx(AgentPanel, { emptyStateText: emptyStateText, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, composerToolbarSlot: composerToolbarSlot, composerSlot: composerSlot, onComposerTextChange: onComposerTextChange, imageModelMenu: imageModelMenu, availableAgents: effectiveAvailableAgents, availableModels: availableModels, modelListLoading: modelListLoading, selectedAgent: effectiveSelectedAgent, onAgentChange: effectiveOnAgentChange, hostedHarness: hostedHarnessEnabled, onConnectLocalRuntime: onConnectLocalRuntime, runtime: runtime, adapterReloadKey: adapterReloadKey, threadFooterSlot: threadFooterSlot, apiUrl: apiUrl, agentChatSurface: agentChatSurface, desktopIdentityUnauthenticated: desktopIdentityUnauthenticated, desktopIdentityAuthenticated: desktopIdentityAuthenticated, showTabBar: effectiveShowTabBar, suppressInlineOpenApp: suppressInlineOpenApp, composerPlaceholder: composerPlaceholder, missingApiKeySetupLayout: "sidebar", onCollapse: () => setOpenPersisted(false), onSnapTo75Percent: isMobile ? undefined : snapTo75Percent, isWideDrawer: isMobile ? false : isWideDrawer, onExitWideDrawer: isMobile ? undefined : exitWideDrawer, onFullViewRequest: onFullscreenRequest, storageKey: storageKey, scope: scope, isolateHistoryByScope: isolateHistoryByScope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync, agentPageHref: agentPageHref, allowSettingsMode: false, chatOnly: true }) }) }), showResizeHandle && isLeft && (_jsx(ResizeHandle, { position: effectivePosition, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd }))] })) : null;
2063
2063
  const drawerPlaceholder = wideDrawerEnabled && !presentationMode && panelOpen ? (_jsx("div", { "aria-hidden": "true", className: "agent-sidebar-drawer-placeholder shrink-0", "data-agent-sidebar-placeholder": "true", style: { width: drawerPlaceholderWidth + 1 } })) : null;
2064
2064
  return (_jsx(AgentSidebarOnboardingContext.Provider, { value: true, children: _jsxs(RealtimeVoiceModeProvider, { browserTabId: browserTabId, children: [showFirstRunOnboarding && (_jsx(Suspense, { fallback: null, children: _jsx(FirstRunOnboarding, {}) })), _jsxs("div", { className: "agent-sidebar-shell flex min-w-0 flex-1 h-screen overflow-hidden", "data-agent-sidebar-position": effectivePosition, "data-agent-native-hosted-harness-ui": hostedHarnessUi ? "desktop" : undefined, "data-agent-native-hosted-chat": isPerAppChatHosted ? "true" : undefined, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: [isMobile &&
2065
2065
  !isPerAppChatHosted &&
@@ -11,7 +11,7 @@ import { type AgentChatRuntime } from "./chat/runtime.js";
11
11
  import { type AgentComposerLayoutVariant, type ComposerSubmitIntent, type Reference } from "./composer/index.js";
12
12
  import { type AgentDynamicSuggestionsOption } from "./dynamic-suggestions.js";
13
13
  import { type ContentPart } from "./sse-event-processor.js";
14
- import type { ChatThreadScope, ChatThreadSnapshot } from "./use-chat-threads.js";
14
+ import { type ChatThreadScope, type ChatThreadSnapshot } from "./use-chat-threads.js";
15
15
  export { AssistantMessageListErrorBoundary, AssistantUiStaleIndexErrorBoundary, assistantUiRecoverableRenderErrorKind, isAssistantUiRecoverableRenderError, isAssistantUiStaleIndexError, } from "./assistant-ui-recovery.js";
16
16
  export { displayableUserMessageText } from "./chat/message-components.js";
17
17
  export declare function shouldSuppressUnauthenticatedDesktopThreadRestore(surface: AgentChatSurfaceKind, status: number, desktopIdentityUnauthenticated?: boolean): boolean;
@@ -269,6 +269,8 @@ export interface AssistantChatProps {
269
269
  threadId?: string;
270
270
  /** Resource scope to include with chat requests for server-side context. */
271
271
  contextScope?: ChatThreadScope | null;
272
+ /** Restrict server-side thread restores to the supplied app scope. */
273
+ isolateHistoryByScope?: boolean;
272
274
  /** Namespace used to hide ambient composer context from other host surfaces. */
273
275
  contextNamespace?: string;
274
276
  /** Whether this chat owns the active visible composer context snapshot. */
@@ -39,6 +39,7 @@ import { McpConnectionSuggestion } from "./resources/McpConnectionSuggestion.js"
39
39
  import { claimRunStream, createRunStreamToken, ownsRunStream, releaseRunStream, } from "./run-stream-ownership.js";
40
40
  import { AgentAutoContinueSignal, readSSEStreamRaw, settleInterruptedToolCalls, } from "./sse-event-processor.js";
41
41
  import { useAgentEngineConfigured } from "./use-agent-engine-configured.js";
42
+ import { appendChatThreadScopeParams, } from "./use-chat-threads.js";
42
43
  import { useDevMode } from "./use-dev-mode.js";
43
44
  import { useRunStuckDetection } from "./use-run-stuck-detection.js";
44
45
  import { cn } from "./utils.js";
@@ -1386,7 +1387,7 @@ function approvalResolutionIdentity(approvalKey, toolCallId,
1386
1387
  askId) {
1387
1388
  return `${toolCallId ?? ""}\u0000${approvalKey}\u0000${askId ?? ""}`;
1388
1389
  }
1389
- const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateText, suggestions, dynamicSuggestions, threadFooterSlot, emptyStateAddon, showHeader = true, onSwitchToCli, className, apiUrl, tabId, browserTabId, threadId, contextScope, contextNamespace, isActiveComposer = true, onMessageCountChange, onSaveThread, onGenerateTitle, composerSlot, onComposerTextChange, composerAreaClassName, composerPlaceholder, missingApiKeySetupLayout = "default", composerLayoutVariant = "default", centerComposerWhenEmpty = false, emptyStateDisplay = "default", composerToolbarSlot, composerExtraActionButton, showModelSelector = true, composerDisabled = false, composerDisabledPlaceholder, isNewThread, isThreadStateLoading, onSlashCommand, execMode, onExecModeChange, approvalActions, planModeDisabled, planModeDisabledReason, selectedModel, defaultModel, selectedEngine, selectedEffort, availableModels, modelListLoading, onModelChange, onEffortChange, availableAgents, selectedAgent, hostedHarness, onAgentChange, imageModelMenu, onForkChat, onConnectProvider, onConnectLocalRuntime, plusMenuMode = "full", providerStatusChecksEnabled = true, loadHistoryRepository, historyReloadKey, externalStreaming = false, agentChatSurface = "app", desktopIdentityUnauthenticated = false, desktopIdentityAuthenticated = false, suppressInlineOpenApp = false, }, ref) {
1390
+ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateText, suggestions, dynamicSuggestions, threadFooterSlot, emptyStateAddon, showHeader = true, onSwitchToCli, className, apiUrl, tabId, browserTabId, threadId, contextScope, isolateHistoryByScope = false, contextNamespace, isActiveComposer = true, onMessageCountChange, onSaveThread, onGenerateTitle, composerSlot, onComposerTextChange, composerAreaClassName, composerPlaceholder, missingApiKeySetupLayout = "default", composerLayoutVariant = "default", centerComposerWhenEmpty = false, emptyStateDisplay = "default", composerToolbarSlot, composerExtraActionButton, showModelSelector = true, composerDisabled = false, composerDisabledPlaceholder, isNewThread, isThreadStateLoading, onSlashCommand, execMode, onExecModeChange, approvalActions, planModeDisabled, planModeDisabledReason, selectedModel, defaultModel, selectedEngine, selectedEffort, availableModels, modelListLoading, onModelChange, onEffortChange, availableAgents, selectedAgent, hostedHarness, onAgentChange, imageModelMenu, onForkChat, onConnectProvider, onConnectLocalRuntime, plusMenuMode = "full", providerStatusChecksEnabled = true, loadHistoryRepository, historyReloadKey, externalStreaming = false, agentChatSurface = "app", desktopIdentityUnauthenticated = false, desktopIdentityAuthenticated = false, suppressInlineOpenApp = false, }, ref) {
1390
1391
  const t = useT();
1391
1392
  const thread = useThread();
1392
1393
  const threadRuntime = useThreadRuntime();
@@ -1418,6 +1419,14 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
1418
1419
  enabled: messages.length === 0,
1419
1420
  });
1420
1421
  const messageListResetKey = useMemo(() => assistantUiMessageListStructureKey(messages), [messages]);
1422
+ const threadScopeQuery = useMemo(() => {
1423
+ if (!isolateHistoryByScope || !contextScope)
1424
+ return "";
1425
+ const params = new URLSearchParams();
1426
+ appendChatThreadScopeParams(params, contextScope);
1427
+ const query = params.toString();
1428
+ return query ? `?${query}` : "";
1429
+ }, [contextScope?.id, contextScope?.type, isolateHistoryByScope]);
1421
1430
  // Chat-wide drag-and-drop: users expect to drop a file anywhere on the agent
1422
1431
  // sidebar (thread, header, composer) and have it attach — same as ChatGPT,
1423
1432
  // Claude.ai, Linear, Slack, etc. Tiptap's own `handleDrop` only fires inside
@@ -1989,7 +1998,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
1989
1998
  ? setTimeout(() => ownAbort.abort(), getPollAbortMs(2000))
1990
1999
  : null;
1991
2000
  try {
1992
- const refreshRes = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}`, { signal: signal ?? ownAbort?.signal });
2001
+ const refreshRes = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}${threadScopeQuery}`, { signal: signal ?? ownAbort?.signal });
1993
2002
  if (!refreshRes.ok)
1994
2003
  return null;
1995
2004
  const refreshData = await refreshRes.json();
@@ -2006,7 +2015,13 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2006
2015
  if (ownAbortTimer)
2007
2016
  clearTimeout(ownAbortTimer);
2008
2017
  }
2009
- }, [apiUrl, importThreadData, loadHistoryRepository, threadId]);
2018
+ }, [
2019
+ apiUrl,
2020
+ importThreadData,
2021
+ loadHistoryRepository,
2022
+ threadId,
2023
+ threadScopeQuery,
2024
+ ]);
2010
2025
  const exportCleanThreadRepo = useCallback(() => ensureMessageMetadata(normalizeThreadRepository(threadRuntime.export())), [threadRuntime]);
2011
2026
  const exportPersistableThreadRepo = useCallback(() => withLastAssistantRunDuration(exportCleanThreadRepo(), showRunningInUI ? null : lastChatRunDurationMs), [exportCleanThreadRepo, lastChatRunDurationMs, showRunningInUI]);
2012
2027
  useEffect(() => {
@@ -2698,7 +2713,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2698
2713
  (async () => {
2699
2714
  let canReconnect = false;
2700
2715
  try {
2701
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}`);
2716
+ const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}${threadScopeQuery}`);
2702
2717
  if (!res.ok) {
2703
2718
  if (!cancelled) {
2704
2719
  setThreadRestoreError(shouldSuppressUnauthenticatedDesktopThreadRestore(agentChatSurface, res.status, desktopIdentityUnauthenticated)
@@ -2811,6 +2826,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2811
2826
  isThreadStateLoading,
2812
2827
  desktopIdentityUnauthenticated,
2813
2828
  restoreAttempt,
2829
+ threadScopeQuery,
2814
2830
  ]);
2815
2831
  useEffect(() => {
2816
2832
  if (!loadHistoryRepository ||
@@ -2992,7 +3008,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2992
3008
  const timer = setTimeout(() => {
2993
3009
  (async () => {
2994
3010
  try {
2995
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}/queued`, {
3011
+ const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}/queued${threadScopeQuery}`, {
2996
3012
  method: "POST",
2997
3013
  headers: { "Content-Type": "application/json" },
2998
3014
  body: JSON.stringify({ queuedMessages }),
@@ -3010,7 +3026,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3010
3026
  })();
3011
3027
  }, 300);
3012
3028
  return () => clearTimeout(timer);
3013
- }, [queuedMessages, threadId, apiUrl]);
3029
+ }, [queuedMessages, threadId, apiUrl, threadScopeQuery]);
3014
3030
  // Nudge the shared hook to re-check after a Builder connect.
3015
3031
  const handleBuilderConnected = useCallback(() => {
3016
3032
  focusComposerAfterConnectRef.current = true;
@@ -64,9 +64,11 @@ export type MultiTabAssistantChatProps = Omit<AssistantChatProps, "tabId" | "thr
64
64
  threadUrlSync?: boolean | ChatThreadUrlSyncOptions;
65
65
  /** Ambient resource context to show as a composer chip. */
66
66
  scope?: ChatThreadScope | null;
67
+ /** Keep app-owned chat history isolated to the supplied scope. */
68
+ isolateHistoryByScope?: boolean;
67
69
  /** @deprecated Scope context is now rendered in the composer. */
68
70
  showScopeBadge?: boolean;
69
71
  /** Cadence for hydrating agent-team sub-agent tab status. Default: 3000. */
70
72
  agentTeamPollMs?: number;
71
73
  };
72
- export declare function MultiTabAssistantChat({ showTabBar, renderHeader, renderOverlay, contentHidden, apiUrl, storageKey, restoreActiveThread, browserTabId, threadUrlSync, scope, agentTeamPollMs, ...props }: MultiTabAssistantChatProps): React.JSX.Element;
74
+ export declare function MultiTabAssistantChat({ showTabBar, renderHeader, renderOverlay, contentHidden, apiUrl, storageKey, restoreActiveThread, browserTabId, threadUrlSync, scope, isolateHistoryByScope, agentTeamPollMs, ...props }: MultiTabAssistantChatProps): React.JSX.Element;
@@ -402,7 +402,7 @@ function runToAgentTeamTabInfo(run) {
402
402
  status: run.status ?? "unknown",
403
403
  };
404
404
  }
405
- export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderOverlay, contentHidden = false, apiUrl = agentNativePath("/_agent-native/agent-chat"), storageKey, restoreActiveThread = true, browserTabId, threadUrlSync = false, scope = null, agentTeamPollMs = DEFAULT_AGENT_TEAM_POLL_MS, ...props }) {
405
+ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderOverlay, contentHidden = false, apiUrl = agentNativePath("/_agent-native/agent-chat"), storageKey, restoreActiveThread = true, browserTabId, threadUrlSync = false, scope = null, isolateHistoryByScope = false, agentTeamPollMs = DEFAULT_AGENT_TEAM_POLL_MS, ...props }) {
406
406
  const translate = useT();
407
407
  const contextNamespace = scope
408
408
  ? scope.contextKey?.trim() || `scope:${scope.type}:${scope.id}`
@@ -520,6 +520,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
520
520
  routeThreadId: threadUrlSyncEnabled
521
521
  ? urlThreadId
522
522
  : (activeDeepLinkedThreadId ?? undefined),
523
+ isolateHistoryByScope,
523
524
  });
524
525
  const switchThread = useCallback((threadId, options = {}) => {
525
526
  switchThreadState(threadId);
@@ -1911,7 +1912,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
1911
1912
  const isActive = tab.id === activeThreadId ||
1912
1913
  (tab.id === focusParentId &&
1913
1914
  activeTab?.parentThreadId === tab.id);
1914
- return (_jsxs("div", { ref: isActive ? activeTabRefCb : undefined, className: cn("agent-tab relative flex items-center rounded-md text-[11px] font-medium shrink-0 max-w-[130px]", isActive
1915
+ return (_jsxs("div", { ref: isActive ? activeTabRefCb : undefined, className: cn("agent-tab relative flex items-center rounded-md text-[11px] font-medium shrink-0 min-w-[56px] max-w-[130px]", isActive
1915
1916
  ? "bg-accent text-foreground ring-1 ring-inset ring-border/60 shadow-sm"
1916
1917
  : "text-muted-foreground hover:text-foreground hover:bg-accent/50"), children: [_jsxs("button", { type: "button", onClick: () => switchThread(tab.id), className: "flex items-center gap-1 px-2.5 py-1.5 min-w-0 flex-1 text-start", children: [_jsx("span", { className: "truncate pe-1", children: tab.label }), tab.status === "running" && (_jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-muted-foreground/50 shrink-0 animate-pulse" }))] }), _jsx("button", { type: "button", "aria-label": translate("agentChat.tabs.closeTab"), onClick: (e) => {
1917
1918
  e.stopPropagation();
@@ -1931,7 +1932,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
1931
1932
  ? "bg-accent text-foreground"
1932
1933
  : "text-muted-foreground hover:bg-accent hover:text-foreground"), children: translate("agentChat.tabs.main") }), childTabs.map((tab) => (_jsxs("div", { ref: tab.id === activeThreadId
1933
1934
  ? activeTabRefCb
1934
- : undefined, className: cn("agent-tab relative flex shrink-0 items-center rounded-md text-[10px] font-medium max-w-[130px]", tab.id === activeThreadId
1935
+ : undefined, className: cn("agent-tab relative flex shrink-0 items-center rounded-md text-[10px] font-medium min-w-[48px] max-w-[130px]", tab.id === activeThreadId
1935
1936
  ? "bg-accent text-foreground"
1936
1937
  : "text-muted-foreground hover:bg-accent hover:text-foreground"), children: [_jsxs("button", { type: "button", onClick: () => switchThread(tab.id), className: "flex items-center gap-1 px-2 py-1 min-w-0 flex-1 text-start", children: [_jsx("span", { className: "truncate pe-1", children: tab.subAgentName || tab.label }), tab.status === "running" && (_jsx("span", { className: "w-1 h-1 rounded-full bg-muted-foreground/50 shrink-0 animate-pulse" }))] }), _jsx("button", { type: "button", "aria-label": translate("agentChat.tabs.closeTab"), onClick: (e) => {
1937
1938
  e.stopPropagation();
@@ -1969,7 +1970,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
1969
1970
  else {
1970
1971
  chatRefs.current.delete(tabId);
1971
1972
  }
1972
- }, threadId: tabId, tabId: tabId, browserTabId: browserTabId, contextNamespace: contextNamespace, isActiveComposer: tabId === activeThreadId, apiUrl: apiUrl, isNewThread: newThreadIds.current.has(tabId) || isNewThread(tabId), isThreadStateLoading: isLoading, onMessageCountChange: (count) => setMessageCounts((prev) => prev[tabId] === count
1973
+ }, threadId: tabId, tabId: tabId, browserTabId: browserTabId, contextScope: scope, contextNamespace: contextNamespace, isolateHistoryByScope: isolateHistoryByScope, isActiveComposer: tabId === activeThreadId, apiUrl: apiUrl, isNewThread: newThreadIds.current.has(tabId) || isNewThread(tabId), isThreadStateLoading: isLoading, onMessageCountChange: (count) => setMessageCounts((prev) => prev[tabId] === count
1973
1974
  ? prev
1974
1975
  : { ...prev, [tabId]: count }), onSaveThread: handleSaveThread, onGenerateTitle: handleGenerateTitle, onSlashCommand: handleSlashCommand, selectedModel: modelSelection?.model, selectedEngine: modelSelection?.engine, selectedEffort: modelSelection?.effort ?? DEFAULT_REASONING_EFFORT, composerSlot: props.composerSlot, defaultModel: defaultModel, availableModels: availableModels, modelListLoading: modelListLoading, onModelChange: handleModelChange, onEffortChange: handleEffortChange, onForkChat: () => handleForkChat(tabId),
1975
1976
  // Sub-agent tabs are read-only: sending a new message from the