@agent-native/core 0.146.4 → 0.146.5

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 (86) hide show
  1. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +1 -1
  2. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
  3. package/corpus/templates/analytics/app/pages/Settings.tsx +5 -1
  4. package/corpus/templates/analytics/app/routes/agent.tsx +7 -5
  5. package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -1
  6. package/corpus/templates/assets/app/routes/agent.tsx +7 -5
  7. package/corpus/templates/assets/app/routes/settings.tsx +7 -2
  8. package/corpus/templates/assets/server/plugins/creative-context.ts +1 -1
  9. package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
  10. package/corpus/templates/brain/app/lib/brain.ts +4 -2
  11. package/corpus/templates/brain/app/root.tsx +1 -1
  12. package/corpus/templates/brain/app/routes/agent.tsx +10 -2
  13. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -1
  14. package/corpus/templates/calendar/app/root.tsx +1 -1
  15. package/corpus/templates/calendar/app/routes/_app.agent.tsx +8 -16
  16. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +5 -2
  17. package/corpus/templates/chat/app/routes/agent.tsx +7 -31
  18. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  19. package/corpus/templates/clips/app/root.tsx +1 -1
  20. package/corpus/templates/clips/app/routes/_app.agent.tsx +9 -7
  21. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
  22. package/corpus/templates/content/app/root.tsx +1 -1
  23. package/corpus/templates/content/app/routes/_app.agent.tsx +9 -8
  24. package/corpus/templates/content/app/routes/_app.settings.tsx +7 -2
  25. package/corpus/templates/content/server/plugins/creative-context.ts +1 -1
  26. package/corpus/templates/crm/app/components/crm/RecordActions.tsx +4 -1
  27. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +1 -1
  28. package/corpus/templates/crm/app/routes/agent.tsx +10 -2
  29. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -1
  30. package/corpus/templates/design/app/root.tsx +1 -1
  31. package/corpus/templates/design/app/routes/agent.tsx +9 -7
  32. package/corpus/templates/design/app/routes/settings.tsx +7 -2
  33. package/corpus/templates/design/server/plugins/creative-context.ts +1 -1
  34. package/corpus/templates/dispatch/app/root.tsx +5 -2
  35. package/corpus/templates/dispatch/app/routes/agent.tsx +7 -7
  36. package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
  37. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +5 -2
  38. package/corpus/templates/factory/app/root.tsx +1 -1
  39. package/corpus/templates/factory/app/routes/agent.tsx +9 -14
  40. package/corpus/templates/forms/app/components/layout/Layout.tsx +1 -1
  41. package/corpus/templates/forms/app/root.tsx +1 -1
  42. package/corpus/templates/forms/app/routes/_app.agent.tsx +9 -7
  43. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +5 -3
  44. package/corpus/templates/macros/app/root.tsx +1 -1
  45. package/corpus/templates/macros/app/routes/agent.tsx +9 -6
  46. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +8 -2
  47. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +1 -1
  48. package/corpus/templates/mail/app/routes/agent.tsx +9 -6
  49. package/corpus/templates/plan/app/components/layout/Layout.tsx +1 -1
  50. package/corpus/templates/plan/app/root.tsx +1 -1
  51. package/corpus/templates/plan/app/routes/agent.tsx +9 -6
  52. package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +8 -12
  53. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +39 -2
  54. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  55. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  56. package/corpus/templates/slides/app/lib/create-deck-generation.ts +22 -5
  57. package/corpus/templates/slides/app/pages/Index.tsx +20 -5
  58. package/corpus/templates/slides/app/root.tsx +1 -1
  59. package/corpus/templates/slides/app/routes/agent.tsx +6 -9
  60. package/corpus/templates/slides/app/routes/settings.tsx +7 -2
  61. package/corpus/templates/slides/server/plugins/creative-context.ts +1 -1
  62. package/corpus/templates/tasks/app/components/layout/Layout.tsx +1 -1
  63. package/corpus/templates/tasks/app/routes/agent.tsx +10 -2
  64. package/dist/client/index.d.ts +1 -1
  65. package/dist/client/navigation/index.d.ts +1 -1
  66. package/dist/client/navigation/index.js +1 -1
  67. package/dist/client/org/OrgSwitcher.js +1 -1
  68. package/dist/client/settings/SettingsPanel.d.ts +8 -0
  69. package/dist/client/settings/SettingsPanel.js +16 -1
  70. package/dist/client/settings/index.d.ts +1 -1
  71. package/dist/collab/awareness.d.ts +2 -2
  72. package/dist/navigation/index.d.ts +1 -0
  73. package/dist/navigation/index.js +34 -0
  74. package/dist/notifications/routes.d.ts +3 -3
  75. package/dist/observability/routes.d.ts +3 -3
  76. package/dist/progress/routes.d.ts +1 -1
  77. package/dist/server/transcribe-voice.d.ts +1 -1
  78. package/dist/templates/chat/app/hooks/use-navigation-state.ts +5 -2
  79. package/dist/templates/chat/app/routes/agent.tsx +7 -31
  80. package/dist/templates/default/app/routes/_index.tsx +1 -1
  81. package/dist/templates/default/app/routes/agent.tsx +7 -25
  82. package/package.json +3 -3
  83. package/src/templates/chat/app/hooks/use-navigation-state.ts +5 -2
  84. package/src/templates/chat/app/routes/agent.tsx +7 -31
  85. package/src/templates/default/app/routes/_index.tsx +1 -1
  86. package/src/templates/default/app/routes/agent.tsx +7 -25
@@ -135,7 +135,7 @@ function AppContent() {
135
135
  {t("root.commandSearch")}
136
136
  </CommandMenu.Item>
137
137
  <CommandMenu.Item
138
- onSelect={() => navigate("/agent")}
138
+ onSelect={() => navigate("/settings/agent")}
139
139
  keywords={[
140
140
  "agent",
141
141
  "context",
@@ -1,11 +1,6 @@
1
- import {
2
- AgentChatSurface,
3
- AgentTabsPage,
4
- } from "@agent-native/core/client/agent-chat";
5
- import { useT } from "@agent-native/core/client/i18n";
6
- import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
1
+ import { buildLegacyAgentSettingsRoute } from "@agent-native/core/client/navigation";
2
+ import { Navigate, useLocation } from "react-router";
7
3
 
8
- import { resolveAgentPageComponent } from "@/lib/agent-page";
9
4
  import { APP_TITLE } from "@/lib/app-config";
10
5
 
11
6
  export function meta() {
@@ -13,12 +8,12 @@ export function meta() {
13
8
  }
14
9
 
15
10
  export default function AgentRoute() {
16
- const t = useT();
17
- useSetPageTitle(t("settings.agentTitle"));
11
+ const location = useLocation();
18
12
 
19
- const AgentPage = resolveAgentPageComponent({
20
- AgentChatSurface,
21
- AgentTabsPage,
22
- });
23
- return <AgentPage appName={APP_TITLE} />;
13
+ return (
14
+ <Navigate
15
+ to={buildLegacyAgentSettingsRoute(location.hash, location.search)}
16
+ replace
17
+ />
18
+ );
24
19
  }
@@ -89,7 +89,7 @@ export function Layout({ children }: LayoutProps) {
89
89
  ) : (
90
90
  <AgentSidebar
91
91
  position="right"
92
- agentPageHref="/agent"
92
+ agentPageHref="/settings/agent"
93
93
  defaultOpen={false}
94
94
  chatViewTransition
95
95
  chatViewTransitionHandoff={chatHomeHandoffPending}
@@ -244,7 +244,7 @@ function FormsCommandMenu({
244
244
  <CommandMenu.Item onSelect={() => {}}>
245
245
  {t("root.searchForms")}
246
246
  </CommandMenu.Item>
247
- <CommandMenu.Item onSelect={() => navigate("/agent")}>
247
+ <CommandMenu.Item onSelect={() => navigate("/settings/agent")}>
248
248
  <IconHierarchy2 size={16} />
249
249
  {t("root.openAgent")}
250
250
  </CommandMenu.Item>
@@ -1,6 +1,5 @@
1
- import { AgentTabsPage } from "@agent-native/core/client/agent-chat";
2
- import { useT } from "@agent-native/core/client/i18n";
3
- import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
1
+ import { buildLegacyAgentSettingsRoute } from "@agent-native/core/client/navigation";
2
+ import { Navigate, useLocation } from "react-router";
4
3
 
5
4
  import messages from "@/i18n/en-US";
6
5
 
@@ -9,8 +8,11 @@ export function meta() {
9
8
  }
10
9
 
11
10
  export default function AgentRoute() {
12
- const t = useT();
13
- useSetPageTitle(t("settings.agentTitle"));
14
-
15
- return <AgentTabsPage />;
11
+ const location = useLocation();
12
+ return (
13
+ <Navigate
14
+ to={buildLegacyAgentSettingsRoute(location.hash, location.search)}
15
+ replace
16
+ />
17
+ );
16
18
  }
@@ -62,7 +62,9 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
62
62
 
63
63
  const isAnalytics = location.pathname === "/analytics";
64
64
  const isSettings = location.pathname.startsWith("/settings");
65
- const isAgent = location.pathname.startsWith("/agent");
65
+ const isAgent =
66
+ location.pathname.startsWith("/settings/agent") ||
67
+ location.pathname.startsWith("/agent");
66
68
 
67
69
  // Auto-close sidebar on route change (mobile)
68
70
  useEffect(() => {
@@ -120,7 +122,7 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
120
122
  } else if (cmd.view === "settings") {
121
123
  navigate("/settings");
122
124
  } else if (cmd.view === "agent") {
123
- navigate("/agent");
125
+ navigate("/settings/agent");
124
126
  } else if (cmd.view === "entry") {
125
127
  navigate("/");
126
128
  }
@@ -144,7 +146,7 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
144
146
  t("agent.suggestionMacros"),
145
147
  t("agent.suggestionRun"),
146
148
  ]}
147
- agentPageHref="/agent"
149
+ agentPageHref="/settings/agent"
148
150
  >
149
151
  <div className="agent-layout-shell flex flex-1 overflow-hidden">
150
152
  {/* Desktop sidebar */}
@@ -166,7 +166,7 @@ function MacrosCommandMenu({
166
166
  {t("root.search")}
167
167
  </CommandMenu.Item>
168
168
  <CommandMenu.Item
169
- onSelect={() => navigate("/agent")}
169
+ onSelect={() => navigate("/settings/agent")}
170
170
  keywords={["agent", "context", "connections", "jobs", "access"]}
171
171
  >
172
172
  <IconHierarchy2 size={16} />
@@ -1,6 +1,5 @@
1
- import { AgentTabsPage } from "@agent-native/core/client/agent-chat";
2
- import { useT } from "@agent-native/core/client/i18n";
3
- import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
1
+ import { buildLegacyAgentSettingsRoute } from "@agent-native/core/client/navigation";
2
+ import { Navigate, useLocation } from "react-router";
4
3
 
5
4
  import messages from "@/i18n/en-US";
6
5
 
@@ -9,8 +8,12 @@ export function meta() {
9
8
  }
10
9
 
11
10
  export default function AgentRoute() {
12
- const t = useT();
13
- useSetPageTitle(t("settings.agentTitle"));
11
+ const location = useLocation();
14
12
 
15
- return <AgentTabsPage appName="Macros" />;
13
+ return (
14
+ <Navigate
15
+ to={buildLegacyAgentSettingsRoute(location.hash, location.search)}
16
+ replace
17
+ />
18
+ );
16
19
  }
@@ -149,6 +149,7 @@ function AccountAvatar({
149
149
  function isStandardLayoutPath(pathname: string): boolean {
150
150
  return (
151
151
  pathname === "/settings" ||
152
+ pathname.startsWith("/settings/") ||
152
153
  pathname === "/agent" ||
153
154
  pathname === "/team" ||
154
155
  pathname === "/draft-queue" ||
@@ -201,7 +202,7 @@ export function AppLayout({ children }: AppLayoutProps) {
201
202
  <AgentSidebar
202
203
  position="right"
203
204
  defaultOpen={false}
204
- agentPageHref="/agent"
205
+ agentPageHref="/settings/agent"
205
206
  emptyStateText={t("agent.emptyState")}
206
207
  suggestions={[
207
208
  t("agent.suggestionSummarize"),
@@ -2042,7 +2043,12 @@ function StandardLayout({ children }: AppLayoutProps) {
2042
2043
 
2043
2044
  const fallbackTitle = (() => {
2044
2045
  if (location.pathname === "/settings") return t("settings.title");
2045
- if (location.pathname === "/agent") return t("settings.agentTitle");
2046
+ if (
2047
+ location.pathname === "/agent" ||
2048
+ location.pathname.startsWith("/settings/agent")
2049
+ ) {
2050
+ return t("settings.agentTitle");
2051
+ }
2046
2052
  if (location.pathname === "/team") return t("mail.pages.team");
2047
2053
  if (location.pathname.startsWith("/draft-queue"))
2048
2054
  return t("mail.views.draftQueue");
@@ -79,7 +79,7 @@ const navCommands = [
79
79
  {
80
80
  labelKey: "settings.openAgentSettings",
81
81
  icon: IconHierarchy2,
82
- route: "/agent",
82
+ route: "/settings/agent",
83
83
  },
84
84
  ];
85
85
 
@@ -1,6 +1,5 @@
1
- import { AgentTabsPage } from "@agent-native/core/client/agent-chat";
2
- import { useT } from "@agent-native/core/client/i18n";
3
- import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
1
+ import { buildLegacyAgentSettingsRoute } from "@agent-native/core/client/navigation";
2
+ import { Navigate, useLocation } from "react-router";
4
3
 
5
4
  import messages from "@/i18n/en-US";
6
5
 
@@ -9,8 +8,12 @@ export function meta() {
9
8
  }
10
9
 
11
10
  export default function AgentRoute() {
12
- const t = useT();
13
- useSetPageTitle(t("settings.agentTitle"));
11
+ const location = useLocation();
14
12
 
15
- return <AgentTabsPage appName="Mail" />;
13
+ return (
14
+ <Navigate
15
+ to={buildLegacyAgentSettingsRoute(location.hash, location.search)}
16
+ replace
17
+ />
18
+ );
16
19
  }
@@ -211,7 +211,7 @@ export function Layout({ children }: LayoutProps) {
211
211
  chatViewTransitionHandoff={chatHomeHandoffPending}
212
212
  storageKey="plans"
213
213
  openOnChatRunning={chatHomeHandoffActive}
214
- agentPageHref="/agent"
214
+ agentPageHref="/settings/agent"
215
215
  emptyStateText={t("agent.emptyState")}
216
216
  suggestions={[
217
217
  t("agent.suggestionShipped"),
@@ -177,7 +177,7 @@ function AppContent() {
177
177
  {t("root.openRecaps")}
178
178
  </CommandMenu.Item>
179
179
  <CommandMenu.Item
180
- onSelect={() => go("/agent")}
180
+ onSelect={() => go("/settings/agent")}
181
181
  keywords={["agent", "context", "connections", "jobs", "access"]}
182
182
  >
183
183
  <IconHierarchy2 size={16} />
@@ -1,6 +1,5 @@
1
- import { AgentTabsPage } from "@agent-native/core/client/agent-chat";
2
- import { useT } from "@agent-native/core/client/i18n";
3
- import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
1
+ import { buildLegacyAgentSettingsRoute } from "@agent-native/core/client/navigation";
2
+ import { Navigate, useLocation } from "react-router";
4
3
 
5
4
  import messages from "@/i18n/en-US";
6
5
 
@@ -9,8 +8,12 @@ export function meta() {
9
8
  }
10
9
 
11
10
  export default function AgentRoute() {
12
- const t = useT();
13
- useSetPageTitle(t("settings.agentTitle"));
11
+ const location = useLocation();
14
12
 
15
- return <AgentTabsPage appName="Plan" />;
13
+ return (
14
+ <Navigate
15
+ to={buildLegacyAgentSettingsRoute(location.hash, location.search)}
16
+ replace
17
+ />
18
+ );
16
19
  }
@@ -4,7 +4,6 @@ import {
4
4
  normalizeReferenceUrls,
5
5
  } from "@shared/api";
6
6
  import { IconX } from "@tabler/icons-react";
7
- import { IconLoader2 } from "@tabler/icons-react";
8
7
  import { useState, useEffect, useRef } from "react";
9
8
  import { createPortal } from "react-dom";
10
9
 
@@ -98,7 +97,7 @@ export default function ImageGenPanel({
98
97
  const [disabledDefaults, setDisabledDefaults] = useState<Set<number>>(
99
98
  new Set(),
100
99
  );
101
- const { generating, submit: agentSubmit } = useAgentGenerating();
100
+ const { submit: agentSubmit } = useAgentGenerating();
102
101
  const panelRef = useRef<HTMLDivElement>(null);
103
102
 
104
103
  useEffect(() => {
@@ -148,6 +147,11 @@ export default function ImageGenPanel({
148
147
 
149
148
  agentSubmit(label, context);
150
149
  setPrompt("");
150
+ // Generation happens asynchronously in the agent chat, which already
151
+ // surfaces its own progress/failure state — keeping this popup open with
152
+ // a separate loading flag risks it getting stuck if the two states
153
+ // diverge (e.g. the chat run keeps going after this request finishes).
154
+ onOpenChange(false);
151
155
  };
152
156
 
153
157
  if (!open) return null;
@@ -246,17 +250,9 @@ export default function ImageGenPanel({
246
250
  {/* Generate button */}
247
251
  <button
248
252
  onClick={handleGenerate}
249
- disabled={generating}
250
- className="w-full px-4 py-2 rounded-lg bg-[#609FF8] hover:bg-[#7AB2FA] disabled:opacity-70 disabled:cursor-not-allowed text-black text-sm font-medium transition-colors flex items-center justify-center gap-2"
253
+ className="w-full px-4 py-2 rounded-lg bg-[#609FF8] hover:bg-[#7AB2FA] text-black text-sm font-medium transition-colors flex items-center justify-center gap-2"
251
254
  >
252
- {generating ? (
253
- <>
254
- <IconLoader2 className="w-4 h-4 animate-spin" />
255
- {t("raw.generatingImage")}
256
- </>
257
- ) : (
258
- "Generate"
259
- )}
255
+ Generate
260
256
  </button>
261
257
  </div>
262
258
  </div>,
@@ -17,7 +17,12 @@ import {
17
17
  } from "@agent-native/toolkit/collab-ui";
18
18
  import { appStateKeyForBrowserTab } from "@shared/app-state-tabs";
19
19
  import { hashSlideContent } from "@shared/slide-fit";
20
- import { IconMaximize, IconZoomIn, IconZoomOut } from "@tabler/icons-react";
20
+ import {
21
+ IconMaximize,
22
+ IconX,
23
+ IconZoomIn,
24
+ IconZoomOut,
25
+ } from "@tabler/icons-react";
21
26
  import {
22
27
  useState,
23
28
  useCallback,
@@ -136,6 +141,25 @@ import { SlideContextToolbar } from "./SlideContextToolbar";
136
141
  import { SlideOverflowWarning } from "./SlideOverflowWarning";
137
142
  import { SpeakerNotesPanel } from "./SpeakerNotesPanel";
138
143
 
144
+ function ExcalidrawExitButton(props: { onExit: () => void; label: string }) {
145
+ return (
146
+ <Tooltip>
147
+ <TooltipTrigger asChild>
148
+ <Button
149
+ variant="ghost"
150
+ size="icon"
151
+ className="absolute right-3 top-3 z-20 h-8 w-8 cursor-pointer border border-border bg-popover/95 shadow-lg backdrop-blur"
152
+ onClick={props.onExit}
153
+ aria-label={props.label}
154
+ >
155
+ <IconX className="h-4 w-4" />
156
+ </Button>
157
+ </TooltipTrigger>
158
+ <TooltipContent side="bottom">{props.label}</TooltipContent>
159
+ </Tooltip>
160
+ );
161
+ }
162
+
139
163
  let builderIdCounter = 0;
140
164
  const CANVAS_ZOOM_PRESETS = [10, 25, 50, 75, 100, 125, 150, 200] as const;
141
165
 
@@ -4184,10 +4208,23 @@ export default function SlideEditor({
4184
4208
  <div className="flex min-h-0 flex-1 overflow-hidden">
4185
4209
  <div className="min-w-0 flex-1 overflow-hidden">
4186
4210
  {slide.excalidrawData ? (
4187
- <div className="h-full bg-background">
4211
+ <div className="relative h-full bg-background">
4212
+ {!readOnly && (
4213
+ <ExcalidrawExitButton
4214
+ // JSON.stringify drops `undefined` properties before the patch
4215
+ // reaches the network request, so the server's
4216
+ // `fields.excalidrawData !== undefined` merge check never sees
4217
+ // the clear — the canvas would reappear after reload. "" is a
4218
+ // serializable value that survives the round trip and is
4219
+ // already treated as "no data" everywhere excalidrawData is read.
4220
+ onExit={() => onUpdateSlide({ excalidrawData: "" })}
4221
+ label={t("raw.exitExcalidrawCanvas")}
4222
+ />
4223
+ )}
4188
4224
  <ExcalidrawSlide
4189
4225
  initialData={slide.excalidrawData}
4190
4226
  onChange={(data) => onUpdateSlide({ excalidrawData: data })}
4227
+ readOnly={readOnly}
4191
4228
  />
4192
4229
  </div>
4193
4230
  ) : (
@@ -117,7 +117,7 @@ export function Layout({ children }: LayoutProps) {
117
117
  ]}
118
118
  scope={deckScope}
119
119
  browserTabId={TAB_ID}
120
- agentPageHref="/agent"
120
+ agentPageHref="/settings/agent"
121
121
  suppressFirstRunOnboarding={isSlidesEditorRoute(location.pathname)}
122
122
  composerSlot={<CreativeContextComposerChip />}
123
123
  threadFooterSlot={<GoogleDriveConnectionCta />}
@@ -77,6 +77,7 @@ const messages = {
77
77
  zoomIn: "Zoom in",
78
78
  fitSlideToScreen: "Fit slide to screen",
79
79
  fitToScreen: "Fit to screen",
80
+ exitExcalidrawCanvas: "Exit drawing canvas",
80
81
  agentWorking: "Agent is working",
81
82
  openChat: "Open chat",
82
83
  cloudAuthToken: "Auth token",
@@ -1,4 +1,8 @@
1
- import { callAction } from "@agent-native/core/client/hooks";
1
+ import {
2
+ callAction,
3
+ deleteClientAppState,
4
+ } from "@agent-native/core/client/hooks";
5
+ import { appStateKeyForBrowserTab } from "@shared/app-state-tabs";
2
6
  import { extractGoogleDocUrls } from "@shared/google-docs";
3
7
  import { flushSync } from "react-dom";
4
8
 
@@ -10,6 +14,7 @@ import {
10
14
  importUploadedDeckIntoDeck,
11
15
  type ImportedSourceDeck,
12
16
  } from "@/lib/import-uploaded-deck";
17
+ import { TAB_ID } from "@/lib/tab-id";
13
18
 
14
19
  interface DesignSystemGenerationContextResult {
15
20
  agentContext?: string;
@@ -103,7 +108,7 @@ function describeUploadedFilesForAgent(
103
108
  "",
104
109
  importedSourceDeck
105
110
  ? `The user uploaded ${files.length} file(s). The ${sourceFile?.originalName ?? importedSourceDeck.format.toUpperCase()} source deck has already been imported into target deck ${deckId} with ${importedSourceDeck.slideCount} source slide(s); do not import it again.`
106
- : `The user uploaded ${files.length} file(s). These paths are real uploaded files; process them with import actions before using their contents:`,
111
+ : `The user uploaded ${files.length} file(s). These are mandatory source material, not optional references — process every one of them with the matching import action BEFORE adding the first slide:`,
107
112
  fileList,
108
113
  "",
109
114
  "File handling rules:",
@@ -112,10 +117,11 @@ function describeUploadedFilesForAgent(
112
117
  : `- PPTX files: call \`import-pptx --filePath \"<path>\" --deckId ${deckId}\` before adding or editing slides.`,
113
118
  importedSourceDeck
114
119
  ? "- For a PDF source, keep the original full-page image in every slide and add restrained design-system chrome around it without obscuring source content. Never OCR-reconstruct a source-faithful page from extracted text."
115
- : `- PDF and DOCX files: call \`import-file --filePath \"<path>\" --format auto --deckId ${deckId}\` and use the returned extracted text as source material. For a visual PDF whose original layout should be preserved, pass \`--importIntoDeck true\` instead of rebuilding the pages from extracted text.`,
120
+ : `- PDF and DOCX files: call \`import-file --filePath \"<path>\" --format auto --deckId ${deckId}\` and use the returned extracted text as source material. The returned text is capped for reliability; re-run with maxChars only if more context is needed. For a visual PDF whose original layout should be preserved, pass \`--importIntoDeck true\` instead of rebuilding the pages from extracted text. Do not proceed to add-slide until this call has returned for every PDF/DOCX in the list above.`,
116
121
  "- Text-like files: use the uploaded-text-file blocks already included in the prompt; do not call import-file for them.",
117
- '- Image files with an embeddable URL can be inserted directly into slide HTML as `<img src="...">` or used as visual references.',
122
+ '- Image files with an embeddable URL are mandatory assets: if the user specified where to use one (e.g. "on the first and last slide"), embed it there with `<img src="...">` exactly as requested. Do not omit a requested image and continue silently — if it truly cannot be placed, say why in your final chat response.',
118
123
  "- Image files without a URL are visual/reference assets only; do not claim to have processed a PPTX/PDF/DOCX unless the relevant import action succeeds.",
124
+ "- Before your final response, verify every uploaded file above was either imported (PPTX/PDF/DOCX) or placed as requested (images). If any file's content or requested placement is missing from the deck, say so explicitly instead of reporting success.",
119
125
  ].join("\n");
120
126
  }
121
127
 
@@ -351,12 +357,23 @@ export async function startDeckGeneration({
351
357
  "",
352
358
  "Before generating, if the request or selected references leave a meaningful choice unresolved, use the `ask-question` tool to ask one concise, prompt-specific question in the inline guided-question flow. Generate the question wording and 2 to 4 options from the user's request and selected references, like Claude's design-question flow; do not use a fixed generic questionnaire. Ask only a choice that materially affects the deck, such as audience, tone, structure, or length. If the prompt already makes the choice clear, do not ask it again. Wait for the user's answer or skip before adding slides.",
353
359
  sourceModeInstructions,
354
- "If the user asked for a specific slide count, keep going sequentially until that count is reached unless a tool error blocks you.",
360
+ "If the user asked for a specific slide count, keep going sequentially until that count is reached unless a tool error blocks you. If no explicit count was given (including when the guided slide-count question was skipped), infer the count from the distinct topics/sections implied by the request — one slide per section plus a title and closing slide — and add slides for every section before considering the deck done. Do not stop at an arbitrary round number (e.g. 10) if sections remain uncovered, and never call `generate-slides-ai` for this flow; it is a legacy single-shot helper capped at 10 slides.",
355
361
  "Every slide is rendered into a fixed native canvas (default 16:9 is 960x540 CSS pixels, with 740x380px available inside standard 80px 110px padding). Keep the main content within that fit budget; split dense source material across more slides instead of packing it tightly. Never use zoom, transform: scale(), clipping, or scroll overflow to hide content overflow, and keep body text at least 16px.",
356
362
  "Each slide's --content must be full HTML. Slide HTML templates are in your AGENTS.md.",
357
363
  "Do NOT use create-deck (the deck already exists). Do NOT call db-schema, the resources tool, or search-files.",
358
364
  ].join("\n");
359
365
 
366
+ // A guided-question card from the previous deck's still-finishing agent run
367
+ // shares this browser tab's single "guided-questions" slot. Without
368
+ // clearing it here, a late answer to that stale question can render on top
369
+ // of the deck we're about to navigate to. Best-effort: if the previous
370
+ // run's question arrives after this clear, it can still reappear, but this
371
+ // closes the common case where it's already pending when a new deck starts.
372
+ deleteClientAppState(
373
+ appStateKeyForBrowserTab("guided-questions", TAB_ID),
374
+ ).catch(() => {});
375
+ deleteClientAppState("guided-questions").catch(() => {});
376
+
360
377
  navigate(`/deck/${deck.id}?generating=1`, {
361
378
  replace: true,
362
379
  flushSync: true,
@@ -1,10 +1,15 @@
1
- import { callAction, useSession } from "@agent-native/core/client/hooks";
1
+ import {
2
+ callAction,
3
+ deleteClientAppState,
4
+ useSession,
5
+ } from "@agent-native/core/client/hooks";
2
6
  import { useT } from "@agent-native/core/client/i18n";
3
7
  import { buildSignInReturnHref } from "@agent-native/core/client/ui";
4
8
  import {
5
9
  useSetHeaderActions,
6
10
  useSetPageTitle,
7
11
  } from "@agent-native/toolkit/app-shell";
12
+ import { appStateKeyForBrowserTab } from "@shared/app-state-tabs";
8
13
  import { extractGoogleDocUrls } from "@shared/google-docs";
9
14
  import {
10
15
  IconAlertTriangle,
@@ -61,6 +66,7 @@ import {
61
66
  rememberRecentReference,
62
67
  type RecentReference,
63
68
  } from "@/lib/recent-references";
69
+ import { TAB_ID } from "@/lib/tab-id";
64
70
 
65
71
  const NEW_DECK_DRAFT_SCOPE = "slides-new-deck";
66
72
  const PENDING_PROMPT_KEY = "slides:pending-deck-prompt";
@@ -198,7 +204,7 @@ function describeUploadedFilesForAgent(
198
204
  "",
199
205
  importedSourceDeck
200
206
  ? `The user uploaded ${files.length} file(s). The ${importedSourceDeck.file.originalName} source deck has already been imported into target deck ${deckId} with ${importedSourceDeck.slideCount} source slide(s); do not import it again.`
201
- : `The user uploaded ${files.length} file(s). These paths are real uploaded files; process them with import actions before using their contents:`,
207
+ : `The user uploaded ${files.length} file(s). These are mandatory source material, not optional references — process every one of them with the matching import action BEFORE adding the first slide:`,
202
208
  fileList,
203
209
  "",
204
210
  "File handling rules:",
@@ -207,10 +213,11 @@ function describeUploadedFilesForAgent(
207
213
  : `- PPTX files: call \`import-pptx --filePath "<path>" --deckId ${deckId}\` before adding or editing slides.`,
208
214
  importedSourceDeck
209
215
  ? "- For a PDF source, keep the original full-page image in every slide and add restrained design-system chrome around it without obscuring source content. Never OCR-reconstruct a source-faithful page from extracted text."
210
- : `- PDF and DOCX files: call \`import-file --filePath "<path>" --format auto --deckId ${deckId}\` and use the returned extracted text as source material. For a visual PDF whose original layout should be preserved, pass \`--importIntoDeck true\` instead of rebuilding the pages from extracted text.`,
216
+ : `- PDF and DOCX files: call \`import-file --filePath "<path>" --format auto --deckId ${deckId}\` and use the returned extracted text as source material. The returned text is capped for reliability; re-run with maxChars only if more context is needed. For a visual PDF whose original layout should be preserved, pass \`--importIntoDeck true\` instead of rebuilding the pages from extracted text. Do not proceed to add-slide until this call has returned for every PDF/DOCX in the list above.`,
211
217
  "- Text-like files: use the uploaded-text-file blocks already included in the prompt; do not call import-file for them.",
212
- '- Image files with an embeddable URL can be inserted directly into slide HTML as `<img src="...">` or used as visual references.',
218
+ '- Image files with an embeddable URL are mandatory assets: if the user specified where to use one (e.g. "on the first and last slide"), embed it there with `<img src="...">` exactly as requested. Do not omit a requested image and continue silently — if it truly cannot be placed, say why in your final chat response.',
213
219
  "- Image files without a URL are visual/reference assets only; do not claim to have processed a PPTX/PDF/DOCX unless the relevant import action succeeds.",
220
+ "- Before your final response, verify every uploaded file above was either imported (PPTX/PDF/DOCX) or placed as requested (images). If any file's content or requested placement is missing from the deck, say so explicitly instead of reporting success.",
214
221
  ].join("\n");
215
222
  }
216
223
 
@@ -731,12 +738,20 @@ export default function Index() {
731
738
  "",
732
739
  "Before generating, if the request or selected references leave a meaningful choice unresolved, use the `ask-question` tool to ask one concise, prompt-specific question in the inline guided-question flow. Generate the question wording and 2 to 4 options from the user's request and selected references, like Claude's design-question flow; do not use a fixed generic questionnaire. Ask only a choice that materially affects the deck, such as audience, tone, structure, or length. If the prompt already makes the choice clear, do not ask it again. Wait for the user's answer or skip before adding slides.",
733
740
  sourceModeInstructions,
734
- "If the user asked for a specific slide count, keep going sequentially until that count is reached unless a tool error blocks you.",
741
+ "If the user asked for a specific slide count, keep going sequentially until that count is reached unless a tool error blocks you. If no explicit count was given (including when the guided slide-count question was skipped), infer the count from the distinct topics/sections implied by the request — one slide per section plus a title and closing slide — and add slides for every section before considering the deck done. Do not stop at an arbitrary round number (e.g. 10) if sections remain uncovered, and never call `generate-slides-ai` for this flow; it is a legacy single-shot helper capped at 10 slides.",
735
742
  "Every slide is rendered into a fixed native canvas (default 16:9 is 960x540 CSS pixels, with 740x380px available inside standard 80px 110px padding). Keep the main content within that fit budget; split dense source material across more slides instead of packing it tightly. Never use zoom, transform: scale(), clipping, or scroll overflow to hide content overflow, and keep body text at least 16px.",
736
743
  "Each slide's --content must be full HTML. Slide HTML templates are in your AGENTS.md.",
737
744
  "Do NOT use create-deck (the deck already exists). Do NOT call db-schema, the resources tool, or search-files.",
738
745
  ].join("\n");
739
746
 
747
+ // See the matching comment in create-deck-generation.ts: clear any
748
+ // guided-question card left over from the previous deck's still-finishing
749
+ // run so it can't surface on top of the deck we're navigating to now.
750
+ deleteClientAppState(
751
+ appStateKeyForBrowserTab("guided-questions", TAB_ID),
752
+ ).catch(() => {});
753
+ deleteClientAppState("guided-questions").catch(() => {});
754
+
740
755
  navigate(`/deck/${deck.id}?generating=1`, {
741
756
  replace: true,
742
757
  flushSync: true,
@@ -229,7 +229,7 @@ function AppContent() {
229
229
  {t("root.searchDecks")}
230
230
  </CommandMenu.Item>
231
231
  <CommandMenu.Item
232
- onSelect={() => navigate("/agent")}
232
+ onSelect={() => navigate("/settings/agent")}
233
233
  keywords={["agent", "context", "connections", "jobs", "access"]}
234
234
  >
235
235
  <IconHierarchy2 size={16} />
@@ -1,7 +1,5 @@
1
- import { AgentTabsPage } from "@agent-native/core/client/agent-chat";
2
- import { useT } from "@agent-native/core/client/i18n";
3
- import { createCreativeContextAgentTab } from "@agent-native/creative-context/client";
4
- import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
1
+ import { buildLegacyAgentSettingsRoute } from "@agent-native/core/client/navigation";
2
+ import { Navigate, useLocation } from "react-router";
5
3
 
6
4
  import messages from "@/i18n/en-US";
7
5
 
@@ -10,13 +8,12 @@ export function meta() {
10
8
  }
11
9
 
12
10
  export default function AgentRoute() {
13
- const t = useT();
14
- useSetPageTitle(t("settings.agentTitle"));
11
+ const location = useLocation();
15
12
 
16
13
  return (
17
- <AgentTabsPage
18
- appName="Slides"
19
- extraTabFactories={[createCreativeContextAgentTab]}
14
+ <Navigate
15
+ to={buildLegacyAgentSettingsRoute(location.hash, location.search)}
16
+ replace
20
17
  />
21
18
  );
22
19
  }
@@ -9,7 +9,10 @@ import {
9
9
  useAgentSettingsTabs,
10
10
  type SettingsSearchEntry,
11
11
  } from "@agent-native/core/client/settings";
12
- import { CreativeContextSettingsLink } from "@agent-native/creative-context/client";
12
+ import {
13
+ CreativeContextSettingsLink,
14
+ createCreativeContextAgentTab,
15
+ } from "@agent-native/creative-context/client";
13
16
  import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
14
17
  import { useMemo } from "react";
15
18
  import { toast } from "sonner";
@@ -26,7 +29,9 @@ export function meta() {
26
29
 
27
30
  export default function SettingsRoute() {
28
31
  const t = useT();
29
- const agentSettingsTabs = useAgentSettingsTabs();
32
+ const agentSettingsTabs = useAgentSettingsTabs({
33
+ agentAdditionalTabFactories: [createCreativeContextAgentTab],
34
+ });
30
35
  useSetPageTitle(t("settings.title"));
31
36
  const { prefs, loading: prefsLoading, save: savePrefs } = useSlidesPrefs();
32
37
 
@@ -165,7 +165,7 @@ registerOnboardingStep({
165
165
  kind: "link",
166
166
  primary: true,
167
167
  label: "Open Library",
168
- payload: { url: "/agent#library", external: false },
168
+ payload: { url: "/settings/library", external: false },
169
169
  },
170
170
  ],
171
171
  isComplete: async () => {
@@ -113,7 +113,7 @@ export function Layout({ children }: LayoutProps) {
113
113
  chatViewTransition
114
114
  storageKey="tasks"
115
115
  browserTabId={TAB_ID}
116
- agentPageHref="/agent"
116
+ agentPageHref="/settings/agent"
117
117
  onFullscreenRequest={() => focusAgentChat()}
118
118
  emptyStateText={t("agent.emptyState")}
119
119
  dynamicSuggestions={false}