@agent-native/core 0.149.4 → 0.149.6

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 (58) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +5 -0
  3. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +5 -0
  4. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +5 -0
  5. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +5 -0
  6. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -0
  7. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +5 -0
  8. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +5 -0
  9. package/corpus/templates/content/actions/_document-discovery-query.ts +127 -0
  10. package/corpus/templates/content/actions/list-documents.ts +189 -198
  11. package/corpus/templates/content/actions/search-documents.ts +95 -24
  12. package/corpus/templates/content/app/hooks/use-documents.ts +84 -6
  13. package/corpus/templates/content/shared/api.ts +10 -0
  14. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +5 -0
  15. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +5 -0
  16. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +5 -0
  17. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +5 -0
  18. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +5 -0
  19. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +5 -0
  20. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +5 -0
  21. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +5 -0
  22. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +23 -4
  23. package/corpus/templates/slides/actions/get-deck.ts +25 -9
  24. package/corpus/templates/slides/actions/patch-deck.ts +51 -0
  25. package/corpus/templates/slides/actions/update-slide.ts +144 -14
  26. package/corpus/templates/slides/app/lib/create-deck-generation.ts +12 -4
  27. package/corpus/templates/slides/changelog/2026-08-11-pdf-requests-to-restyle-and-preserve-a-source-deck-now-impor.md +6 -0
  28. package/corpus/templates/slides/changelog/2026-08-11-slide-edits-preserve-their-layout-without-introducing-hidden.md +6 -0
  29. package/corpus/templates/slides/changelog/2026-08-11-slides-reliably-imports-uploaded-pdfs-and-presentations.md +6 -0
  30. package/corpus/templates/slides/changelog/2026-08-11-slides-support-atomic-code-style-html-patches-without-regene.md +6 -0
  31. package/corpus/templates/slides/package.json +1 -0
  32. package/corpus/templates/slides/server/lib/slide-content-patch.ts +355 -0
  33. package/corpus/templates/slides/server/lib/uploaded-reference-storage.ts +28 -9
  34. package/corpus/templates/slides/server/plugins/agent-chat.ts +12 -3
  35. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +5 -0
  36. package/dist/cli/create.js +49 -0
  37. package/dist/client/chat-first/index.d.ts +1 -1
  38. package/dist/client/chat-first/index.js +1 -1
  39. package/dist/client/chat-first/primary-nav.d.ts +3 -1
  40. package/dist/client/chat-first/primary-nav.js +8 -2
  41. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  42. package/dist/notifications/routes.d.ts +3 -3
  43. package/dist/observability/routes.d.ts +3 -3
  44. package/dist/private-blob/registry.js +67 -4
  45. package/dist/resources/handlers.d.ts +1 -1
  46. package/dist/server/realtime-token.d.ts +1 -1
  47. package/dist/server/transcribe-voice.d.ts +1 -1
  48. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -0
  49. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +5 -0
  50. package/dist/templates/default/pnpm-workspace.yaml +44 -0
  51. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -0
  52. package/dist/templates/workspace-root/pnpm-workspace.yaml +44 -0
  53. package/package.json +1 -1
  54. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -0
  55. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +5 -0
  56. package/src/templates/default/pnpm-workspace.yaml +44 -0
  57. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -0
  58. package/src/templates/workspace-root/pnpm-workspace.yaml +44 -0
@@ -84,25 +84,44 @@ export async function readUploadedReferenceBlob(
84
84
  decryptSecretValue(reference.slice(UPLOADED_REFERENCE_PREFIX.length)),
85
85
  ) as UploadedReferenceDescriptor;
86
86
  } catch {
87
+ console.warn("[slides-upload] invalid uploaded reference descriptor", {
88
+ reason: "decrypt-or-parse",
89
+ });
87
90
  throw new Error("Invalid uploaded file reference");
88
91
  }
89
92
 
93
+ const requestOrgId = getRequestOrgId() ?? null;
94
+ const ownerMatches = descriptor?.ownerKey === tenantFileKey(email);
95
+ const orgMatches = descriptor?.orgId === requestOrgId;
96
+ const filename = descriptor?.filename;
97
+ const safeFilename =
98
+ typeof filename === "string" && filename === path.basename(filename)
99
+ ? filename
100
+ : null;
101
+ const handleIsOpaque = descriptor?.handle?.opaque === true;
90
102
  if (
91
103
  descriptor?.kind !== "slides-upload" ||
92
- descriptor.version !== 1 ||
93
- descriptor.ownerKey !== tenantFileKey(email) ||
94
- descriptor.orgId !== (getRequestOrgId() ?? null) ||
95
- typeof descriptor.filename !== "string" ||
96
- descriptor.filename !== path.basename(descriptor.filename) ||
97
- typeof descriptor.handle?.id !== "string" ||
98
- typeof descriptor.handle?.provider !== "string" ||
99
- descriptor.handle.opaque !== true
104
+ descriptor?.version !== 1 ||
105
+ !ownerMatches ||
106
+ !orgMatches ||
107
+ safeFilename === null ||
108
+ typeof descriptor?.handle?.id !== "string" ||
109
+ typeof descriptor?.handle?.provider !== "string" ||
110
+ !handleIsOpaque
100
111
  ) {
112
+ console.warn("[slides-upload] uploaded reference rejected", {
113
+ handleIsOpaque,
114
+ hasDescriptorOrg: descriptor?.orgId != null,
115
+ hasRequestOrg: requestOrgId != null,
116
+ orgMatches,
117
+ ownerMatches,
118
+ reason: "scope-or-handle-validation",
119
+ });
101
120
  throw new Error(
102
121
  "Access denied: uploaded file reference is not valid for this user or organization",
103
122
  );
104
123
  }
105
124
 
106
125
  const blob = await readPrivateBlob(descriptor.handle);
107
- return { data: Buffer.from(blob.data), filename: descriptor.filename };
126
+ return { data: Buffer.from(blob.data), filename: safeFilename };
108
127
  }
@@ -1,10 +1,10 @@
1
- import { getOrgContext } from "@agent-native/core/org";
2
1
  import {
3
2
  createAgentChatPlugin,
4
3
  loadActionsFromStaticRegistry,
5
4
  } from "@agent-native/core/server";
6
5
 
7
6
  import actionsRegistry from "../../.generated/actions-registry.js";
7
+ import { resolveSlidesRequestAuthContext } from "../handlers/request-auth-context.js";
8
8
  import { prepareSlidesChatAttachments } from "../lib/chat-attachments.js";
9
9
  import "../register-secrets.js";
10
10
 
@@ -48,12 +48,21 @@ export default createAgentChatPlugin({
48
48
  // paginate, and reduce provider data through providerFetch() without us
49
49
  // hardcoding one action per Google Drive endpoint.
50
50
  codeExecution: { production: "sandboxed" },
51
- resolveOrgId: async (event) => (await getOrgContext(event)).orgId,
51
+ // Upload routes and action routes must use the same session/org resolver.
52
+ // Reading getOrgContext directly here skipped the upload route's session
53
+ // fallback and could reject a freshly uploaded reference after a transient
54
+ // org lookup or active-org transition.
55
+ resolveOrgId: async (event) => {
56
+ const authContext = await resolveSlidesRequestAuthContext(event);
57
+ return authContext.orgId === undefined ? null : authContext.orgId;
58
+ },
52
59
  prepareRequest: prepareSlidesChatAttachments,
53
60
  systemPrompt: `You are an AI deck assistant. You create, edit, import, export, style, share, and navigate decks through actions and shared application state. For a newly created presentation, use create-deck with slides: [] only when you are creating the deck yourself, then add-slide sequentially with full rendered HTML. The legacy generate-slides-ai action returns Markdown drafts and is not part of the persisted presentation workflow.
54
61
 
55
62
  When the user asks to improve, beautify, restyle, or make an uploaded/existing deck on-brand, treat it as an in-place source-preserving edit unless the user explicitly asks to rewrite the story or change slide count. First call view-screen when the active deck is unclear, then get-deck with compact=true for deck orientation. If you need slide markup, call get-deck with compact=false explicitly and only when the edit requires the full HTML. If get-deck.sourceImport exists, preserve its slide count, order, IDs, factual copy, notes, images, charts, tables, diagrams, freeform objects, and source aspect ratio. For a deck-wide restyle, use one patch-deck call with requireAllSourceSlides=true and one patch-slide operation with fields.content for every source slide ID; the action rejects partial coverage. Do not split a full-deck restyle into arbitrary batches or use one-by-one update-slide calls - reserve update-slide for targeted one-slide edits. After the patch succeeds, verify with get-deck using compact=true so the verification does not retransmit every slide's HTML. Do not claim a partial or initial pass. Do not use add-slide, delete, reorder, or replace source imagery with generic cards for this workflow. If sourceImport.fidelity is partial or imagesSkipped is nonzero, stop and report the exact fidelity warning instead of claiming a reliable improvement.
56
- For a targeted one-slide edit, prefer get-deck with the stable slideId from view-screen or compact get-deck. It returns only that slide's full HTML by default, so do not load the full deck when one slide is enough. Pass compact=true when you only need a lightweight check of that slide.
63
+ For a targeted one-slide edit, prefer get-deck with the stable slideId from view-screen or compact get-deck. It returns only that slide's full HTML by default, so do not load the full deck when one slide is enough. For code-style edits, request compact=false and format=true, then call update-slide with that slide's contentHash as baseContentHash and an ordered edits list. Use exact replace, insert-before/after, replace-between, or regex-replace edits; include expectedMatches for ambiguous markers. The action applies the whole list atomically under the deck lock, so a failed required match writes nothing. Set format=true on update-slide when readable line breaks should be persisted. Use fullContent only for an intentional full rewrite. Pass compact=true when you only need a lightweight check of that slide.
64
+
65
+ For click-to-reveal animations, keep the slide HTML as the visual source and store reveals only in the slide's animations metadata. Read the target slide's full HTML, preserve its existing structure, and patch the complete ordered animations list with elementPath values from that final HTML. Leave labels and headings visible by omitting them from the list. Never simulate reveals by adding duplicate elements, visibility:hidden, fmd-layout-spacer, fmd-freeform-object, absolute positioning, transforms, or placeholder markup. If content and reveals both change, send them together in one patch-deck patch-slide operation. If a user asks to remove or revert reveals, send the existing content with animations: [], then re-read the slide; do not use a simplified replacement or rely on update-slide text alone.
57
66
 
58
67
  When the active Slides editor is already showing the deck you just changed, do not include an "Open the updated deck" or similar link in the final response. The deck is already open and the action updated it in place; say that plainly instead. Only provide an open-deck link when the user is elsewhere, the active deck is different, or the user explicitly asks for a link.
59
68
 
@@ -168,6 +168,11 @@ the useful information remains dense and scannable.
168
168
  - Do not build custom dropdowns, menus, popovers, modals, or confirmations with manual absolute positioning and click-outside effects.
169
169
  - Never use browser dialogs (`window.alert`, `window.confirm`, `window.prompt`). Use `AlertDialog`, `Dialog`, or app-specific confirmation UI.
170
170
  - Use Tabler icons for all first-party UI icons. Do not add Lucide, Heroicons, inline SVG icon sets, or emoji icons.
171
+ - Never guess a Tabler icon name. Names like `IconPartyPopper` or `IconConfettiCannon` feel
172
+ plausible but don't exist and crash Vite with a "Named export not found" error. Before importing
173
+ an icon you haven't used elsewhere in this app, confirm it exists by grepping
174
+ `node_modules/@tabler/icons-react/dist/tabler-icons-react.d.ts` (or the package's icon list)
175
+ for the exact name, and pick the closest real match if your first guess isn't there.
171
176
  - Keep inline help/info glyphs next to labels at `size-3` (12px) or smaller than the adjacent text. Preserve a larger hit area on the trigger, not the glyph. Use `guard:allow-large-help-icon` only for deliberate heading documentation or menu action exceptions.
172
177
  - Use `useActionQuery` and `useActionMutation` from `@agent-native/core/client` for action-backed UI. Standard CRUD should go through actions, not custom `/api/` routes.
173
178
  - Keep UI optimistic where possible: update cache and navigation immediately, then reconcile or roll back on mutation result.
@@ -17,6 +17,49 @@ const STANDALONE_EXACT_DEPENDENCY_OVERRIDES = {
17
17
  "@react-router/fs-routes": "8.1.0",
18
18
  "react-router": "8.1.0",
19
19
  };
20
+ const TIPTAP_WORKSPACE_OVERRIDES = {
21
+ '"@tiptap/core"': '"3.28.0"',
22
+ '"@tiptap/extension-blockquote"': '"3.28.0"',
23
+ '"@tiptap/extension-bold"': '"3.28.0"',
24
+ '"@tiptap/extension-bubble-menu"': '"3.28.0"',
25
+ '"@tiptap/extension-bullet-list"': '"3.28.0"',
26
+ '"@tiptap/extension-code"': '"3.28.0"',
27
+ '"@tiptap/extension-code-block"': '"3.28.0"',
28
+ '"@tiptap/extension-code-block-lowlight"': '"3.28.0"',
29
+ '"@tiptap/extension-collaboration"': '"3.28.0"',
30
+ '"@tiptap/extension-collaboration-caret"': '"3.28.0"',
31
+ '"@tiptap/extension-color"': '"3.28.0"',
32
+ '"@tiptap/extension-document"': '"3.28.0"',
33
+ '"@tiptap/extension-dropcursor"': '"3.28.0"',
34
+ '"@tiptap/extension-floating-menu"': '"3.28.0"',
35
+ '"@tiptap/extension-gapcursor"': '"3.28.0"',
36
+ '"@tiptap/extension-hard-break"': '"3.28.0"',
37
+ '"@tiptap/extension-heading"': '"3.28.0"',
38
+ '"@tiptap/extension-horizontal-rule"': '"3.28.0"',
39
+ '"@tiptap/extension-image"': '"3.28.0"',
40
+ '"@tiptap/extension-italic"': '"3.28.0"',
41
+ '"@tiptap/extension-link"': '"3.28.0"',
42
+ '"@tiptap/extension-list"': '"3.28.0"',
43
+ '"@tiptap/extension-list-item"': '"3.28.0"',
44
+ '"@tiptap/extension-list-keymap"': '"3.28.0"',
45
+ '"@tiptap/extension-ordered-list"': '"3.28.0"',
46
+ '"@tiptap/extension-paragraph"': '"3.28.0"',
47
+ '"@tiptap/extension-placeholder"': '"3.28.0"',
48
+ '"@tiptap/extension-strike"': '"3.28.0"',
49
+ '"@tiptap/extension-table"': '"3.28.0"',
50
+ '"@tiptap/extension-table-cell"': '"3.28.0"',
51
+ '"@tiptap/extension-table-header"': '"3.28.0"',
52
+ '"@tiptap/extension-table-row"': '"3.28.0"',
53
+ '"@tiptap/extension-task-item"': '"3.28.0"',
54
+ '"@tiptap/extension-task-list"': '"3.28.0"',
55
+ '"@tiptap/extension-text"': '"3.28.0"',
56
+ '"@tiptap/extension-text-style"': '"3.28.0"',
57
+ '"@tiptap/extension-underline"': '"3.28.0"',
58
+ '"@tiptap/extensions"': '"3.28.0"',
59
+ '"@tiptap/pm"': '"3.28.0"',
60
+ '"@tiptap/react"': '"3.28.0"',
61
+ '"@tiptap/starter-kit"': '"3.28.0"',
62
+ };
20
63
  const REACT_ROUTER_BUILD_DEPENDENCIES = [
21
64
  "@react-router/dev",
22
65
  "@react-router/fs-routes",
@@ -1457,6 +1500,12 @@ function postProcessStandalone(name, targetDir, templateName, resolution) {
1457
1500
  nf3: '"0.3.17"',
1458
1501
  };
1459
1502
  }
1503
+ if (templateName && getTemplate(templateName)) {
1504
+ sections.overrides = {
1505
+ ...sections.overrides,
1506
+ ...TIPTAP_WORKSPACE_OVERRIDES,
1507
+ };
1508
+ }
1460
1509
  const localToolkit = localToolkitOverride();
1461
1510
  if (localToolkit) {
1462
1511
  sections.overrides ??= {};
@@ -6,7 +6,7 @@ export { defaultChatFirstCopy } from "./copy.js";
6
6
  export { ChatFirstChatHistory, type ChatFirstChatHistoryProps, } from "./chat-history.js";
7
7
  export { ChatFirstSessionWatchPane } from "./session-watch-pane.js";
8
8
  export { ChatFirstSurfacePanel, type ChatFirstSurfacePanelProps, } from "./surface-panel.js";
9
- export { ChatFirstPrimaryNavigation } from "./primary-nav.js";
9
+ export { ChatFirstPrimaryNavigation, type ChatFirstPrimaryTab, } from "./primary-nav.js";
10
10
  export { emitChatFirstOpenApp, resolveChatFirstAppTarget, CHAT_FIRST_DEFAULT_APP_IDS, type ChatFirstOpenAppDetail, } from "../chat-first.js";
11
11
  export { buildChatFirstAppCreationPrompt, titleFromChatFirstAppPrompt, type ChatFirstAppCreationPromptInput, type ChatFirstAppCreationResource, type ChatFirstAppCreationVaultAccessMode, } from "../../shared/chat-first-app-creation.js";
12
12
  export { ChatFirstSurfaceContent, ChatFirstSurfaceTabs, } from "./surface-tabs.js";
@@ -6,7 +6,7 @@ export { defaultChatFirstCopy } from "./copy.js";
6
6
  export { ChatFirstChatHistory, } from "./chat-history.js";
7
7
  export { ChatFirstSessionWatchPane } from "./session-watch-pane.js";
8
8
  export { ChatFirstSurfacePanel, } from "./surface-panel.js";
9
- export { ChatFirstPrimaryNavigation } from "./primary-nav.js";
9
+ export { ChatFirstPrimaryNavigation, } from "./primary-nav.js";
10
10
  export { emitChatFirstOpenApp, resolveChatFirstAppTarget, CHAT_FIRST_DEFAULT_APP_IDS, } from "../chat-first.js";
11
11
  export { buildChatFirstAppCreationPrompt, titleFromChatFirstAppPrompt, } from "../../shared/chat-first-app-creation.js";
12
12
  export { ChatFirstSurfaceContent, ChatFirstSurfaceTabs, } from "./surface-tabs.js";
@@ -1,8 +1,10 @@
1
1
  import type { ChatFirstCopy } from "./types.js";
2
- export declare function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, copy, }: {
2
+ export type ChatFirstPrimaryTab = "new-chat" | "integrations" | "scheduled";
3
+ export declare function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, activeTab, copy, }: {
3
4
  onNewChat?: () => void;
4
5
  onOpenIntegrations: () => void;
5
6
  onOpenScheduled: () => void;
6
7
  onSearch?: () => void;
8
+ activeTab?: ChatFirstPrimaryTab;
7
9
  copy?: ChatFirstCopy;
8
10
  }): import("react").JSX.Element;
@@ -1,6 +1,12 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IconClock, IconPlus, IconPlugConnected, IconSearch, } from "@tabler/icons-react";
3
3
  import { defaultChatFirstCopy } from "./copy.js";
4
- export function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, copy = defaultChatFirstCopy, }) {
5
- return (_jsxs(_Fragment, { children: [onNewChat ? (_jsxs("button", { type: "button", className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-[13px] font-medium text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: onNewChat, children: [_jsx(IconPlus, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("newChat") })] })) : null, _jsxs("button", { type: "button", className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-[13px] font-medium text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: onOpenIntegrations, children: [_jsx(IconPlugConnected, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("integrations") })] }), _jsxs("button", { type: "button", className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-[13px] font-medium text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: onOpenScheduled, children: [_jsx(IconClock, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("scheduled") })] }), onSearch ? (_jsxs("button", { type: "button", className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-[13px] font-medium text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: onSearch, children: [_jsx(IconSearch, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("search") })] })) : null] }));
4
+ export function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, activeTab, copy = defaultChatFirstCopy, }) {
5
+ const tabClassName = (tab) => `flex h-8 w-full items-center gap-2 rounded-md px-2 text-[13px] font-medium transition-[background-color,color] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${activeTab === tab
6
+ ? "bg-sidebar-accent text-sidebar-accent-foreground"
7
+ : "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"}`;
8
+ const renderTab = (tab, content, onSelect) => (_jsx("button", { type: "button", role: "tab", "aria-selected": activeTab === tab, className: tabClassName(tab), onClick: onSelect, children: content }));
9
+ return (_jsxs("div", { children: [_jsxs("div", { role: "tablist", "aria-label": "Primary navigation", className: "grid gap-px", children: [onNewChat
10
+ ? renderTab("new-chat", _jsxs(_Fragment, { children: [_jsx(IconPlus, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("newChat") })] }), onNewChat)
11
+ : null, renderTab("integrations", _jsxs(_Fragment, { children: [_jsx(IconPlugConnected, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("integrations") })] }), onOpenIntegrations), renderTab("scheduled", _jsxs(_Fragment, { children: [_jsx(IconClock, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("scheduled") })] }), onOpenScheduled)] }), onSearch ? (_jsxs("button", { type: "button", className: "mt-px flex h-8 w-full items-center gap-2 rounded-md px-2 text-[13px] font-medium text-sidebar-foreground/80 transition-[background-color,color] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: onSearch, children: [_jsx(IconSearch, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("search") })] })) : null] }));
6
12
  }
@@ -137,13 +137,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
137
137
  inputSchema: {
138
138
  type: string;
139
139
  properties: {
140
+ count?: undefined;
140
141
  query?: undefined;
141
142
  minutes?: undefined;
142
143
  limit?: undefined;
143
144
  clientHint?: undefined;
144
145
  timestamp?: undefined;
145
146
  chapterId?: undefined;
146
- count?: undefined;
147
147
  startAt?: undefined;
148
148
  endAt?: undefined;
149
149
  reason?: undefined;
@@ -159,6 +159,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
159
159
  inputSchema: {
160
160
  type: string;
161
161
  properties: {
162
+ count?: undefined;
162
163
  query: {
163
164
  type: string;
164
165
  description: string;
@@ -174,7 +175,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
174
175
  clientHint?: undefined;
175
176
  timestamp?: undefined;
176
177
  chapterId?: undefined;
177
- count?: undefined;
178
178
  startAt?: undefined;
179
179
  endAt?: undefined;
180
180
  reason?: undefined;
@@ -190,6 +190,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
190
190
  inputSchema: {
191
191
  type: string;
192
192
  properties: {
193
+ count?: undefined;
193
194
  minutes: {
194
195
  type: string;
195
196
  description: string;
@@ -199,7 +200,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
199
200
  clientHint?: undefined;
200
201
  timestamp?: undefined;
201
202
  chapterId?: undefined;
202
- count?: undefined;
203
203
  startAt?: undefined;
204
204
  endAt?: undefined;
205
205
  reason?: undefined;
@@ -216,6 +216,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
216
216
  type: string;
217
217
  required: string[];
218
218
  properties: {
219
+ count?: undefined;
219
220
  query: {
220
221
  type: string;
221
222
  description: string;
@@ -234,7 +235,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
234
235
  };
235
236
  timestamp?: undefined;
236
237
  chapterId?: undefined;
237
- count?: undefined;
238
238
  startAt?: undefined;
239
239
  endAt?: undefined;
240
240
  reason?: undefined;
@@ -250,6 +250,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
250
250
  type: string;
251
251
  required: string[];
252
252
  properties: {
253
+ count?: undefined;
253
254
  query?: undefined;
254
255
  minutes?: undefined;
255
256
  limit?: undefined;
@@ -263,7 +264,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
263
264
  description: string;
264
265
  };
265
266
  chapterId?: undefined;
266
- count?: undefined;
267
267
  startAt?: undefined;
268
268
  endAt?: undefined;
269
269
  includeMicrophone?: undefined;
@@ -314,13 +314,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
314
314
  type: string;
315
315
  required: string[];
316
316
  properties: {
317
+ count?: undefined;
317
318
  query?: undefined;
318
319
  minutes?: undefined;
319
320
  limit?: undefined;
320
321
  clientHint?: undefined;
321
322
  timestamp?: undefined;
322
323
  chapterId?: undefined;
323
- count?: undefined;
324
324
  startAt: {
325
325
  type: string;
326
326
  description: string;
@@ -349,13 +349,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
349
349
  type: string;
350
350
  required: string[];
351
351
  properties: {
352
+ count?: undefined;
352
353
  query?: undefined;
353
354
  minutes?: undefined;
354
355
  limit?: undefined;
355
356
  clientHint?: undefined;
356
357
  timestamp?: undefined;
357
358
  chapterId?: undefined;
358
- count?: undefined;
359
359
  startAt?: undefined;
360
360
  endAt?: undefined;
361
361
  reason?: undefined;
@@ -16,18 +16,18 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
16
16
  error?: undefined;
17
17
  ok?: undefined;
18
18
  } | {
19
+ count?: undefined;
19
20
  updated: number;
20
21
  error?: undefined;
21
22
  ok?: undefined;
22
- count?: undefined;
23
23
  } | {
24
+ count?: undefined;
24
25
  updated?: undefined;
25
26
  error: string;
26
27
  ok?: undefined;
27
- count?: undefined;
28
28
  } | {
29
+ count?: undefined;
29
30
  updated?: undefined;
30
31
  error?: undefined;
31
32
  ok: boolean;
32
- count?: undefined;
33
33
  }>>;
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- error?: undefined;
45
44
  summary: import("./types.js").TraceSummary;
46
45
  spans: import("./types.js").TraceSpan[];
47
46
  id?: undefined;
47
+ error?: undefined;
48
48
  ok?: undefined;
49
49
  } | {
50
- error?: undefined;
51
50
  summary?: undefined;
52
51
  spans?: undefined;
53
52
  id: string;
53
+ error?: undefined;
54
54
  ok?: undefined;
55
55
  } | {
56
56
  summary?: undefined;
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
59
59
  error: any;
60
60
  ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
65
+ error?: undefined;
66
66
  ok: boolean;
67
67
  }>>;
@@ -2,6 +2,7 @@ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
2
  import { uploadFile } from "../file-upload/index.js";
3
3
  import { decryptSecretValue, encryptSecretValue, getSecretEncryptionKey, } from "../secrets/crypto.js";
4
4
  const PUBLIC_UPLOAD_HANDLE_PREFIX = "public-upload:v1:";
5
+ const PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS = [100, 250, 500];
5
6
  const globals = globalThis;
6
7
  const providers = (globals.__agentNativePrivateBlobProviders ??= new Map());
7
8
  const publicUploadFallbackRef = (globals.__agentNativePrivateBlobPublicUploadFallback ??= {
@@ -47,6 +48,16 @@ function decodePublicUploadDescriptor(id) {
47
48
  function isPublicUploadFallbackHandle(handle) {
48
49
  return handle.id.startsWith(PUBLIC_UPLOAD_HANDLE_PREFIX);
49
50
  }
51
+ function isRetryablePublicUploadStatus(status) {
52
+ return (status === 404 ||
53
+ status === 408 ||
54
+ status === 425 ||
55
+ status === 429 ||
56
+ (status >= 500 && status <= 599));
57
+ }
58
+ function waitForPublicUploadRetry(delayMs) {
59
+ return new Promise((resolve) => setTimeout(resolve, delayMs));
60
+ }
50
61
  async function putViaEncryptedPublicUpload(input) {
51
62
  const bytes = toBytes(input.data);
52
63
  const encrypted = encryptBytes(bytes);
@@ -71,7 +82,7 @@ async function putViaEncryptedPublicUpload(input) {
71
82
  size: bytes.byteLength,
72
83
  createdAt: new Date().toISOString(),
73
84
  };
74
- return {
85
+ const handle = {
75
86
  id: encodePublicUploadDescriptor(descriptor),
76
87
  provider: `public-upload:${uploaded.provider}`,
77
88
  opaque: true,
@@ -81,12 +92,64 @@ async function putViaEncryptedPublicUpload(input) {
81
92
  createdAt: descriptor.createdAt,
82
93
  metadata: input.metadata,
83
94
  };
95
+ // Do not hand callers a reference that the next request cannot read yet.
96
+ // The public-upload fallback is eventually consistent at the URL boundary,
97
+ // so readiness belongs to the write path as well as the later read path.
98
+ await readViaEncryptedPublicUpload(handle);
99
+ return handle;
84
100
  }
85
101
  async function readViaEncryptedPublicUpload(handle) {
86
102
  const descriptor = decodePublicUploadDescriptor(handle.id);
87
- const response = await fetch(descriptor.url);
88
- if (!response.ok) {
89
- throw new Error(`Private blob public-upload read failed (${response.status}): ${response.statusText}`);
103
+ const startedAt = Date.now();
104
+ let response;
105
+ let attempts = 0;
106
+ for (let attempt = 0; attempt <= PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS.length; attempt++) {
107
+ attempts = attempt + 1;
108
+ try {
109
+ response = await fetch(descriptor.url);
110
+ }
111
+ catch (error) {
112
+ if (attempt === PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS.length) {
113
+ console.warn("[private-blob] public-upload read failed", {
114
+ attempts: attempt + 1,
115
+ elapsedMs: Date.now() - startedAt,
116
+ provider: handle.provider,
117
+ reason: "network",
118
+ });
119
+ throw new Error(`Private blob public-upload read failed: ${error instanceof Error ? error.message : "network error"}`, { cause: error });
120
+ }
121
+ await waitForPublicUploadRetry(PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS[attempt]);
122
+ continue;
123
+ }
124
+ if (response.ok)
125
+ break;
126
+ if (!isRetryablePublicUploadStatus(response.status) ||
127
+ attempt === PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS.length) {
128
+ console.warn("[private-blob] public-upload read failed", {
129
+ attempts: attempt + 1,
130
+ elapsedMs: Date.now() - startedAt,
131
+ provider: handle.provider,
132
+ status: response.status,
133
+ });
134
+ throw new Error(`Private blob public-upload read failed (${response.status}): ${response.statusText}`);
135
+ }
136
+ await waitForPublicUploadRetry(PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS[attempt]);
137
+ }
138
+ if (!response?.ok) {
139
+ console.warn("[private-blob] public-upload read failed", {
140
+ attempts: PUBLIC_UPLOAD_READ_RETRY_DELAYS_MS.length + 1,
141
+ elapsedMs: Date.now() - startedAt,
142
+ provider: handle.provider,
143
+ reason: "no-response",
144
+ });
145
+ throw new Error("Private blob public-upload read failed without a response");
146
+ }
147
+ if (attempts > 1) {
148
+ console.info("[private-blob] public-upload read recovered after retry", {
149
+ attempts,
150
+ elapsedMs: Date.now() - startedAt,
151
+ provider: handle.provider,
152
+ });
90
153
  }
91
154
  // The uploaded ciphertext is intentionally opaque; the descriptor carries
92
155
  // auth tag + IV separately so the backing public URL is useless by itself.
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
48
48
  }>;
49
49
  /** DELETE /_agent-native/resources/:id — delete a resource */
50
50
  export declare function handleDeleteResource(event: any): Promise<{
51
- error: string;
52
51
  ok?: undefined;
52
+ error: string;
53
53
  } | {
54
54
  error?: undefined;
55
55
  ok: boolean;
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
26
26
  expiresAt?: undefined;
27
27
  ttlSeconds?: undefined;
28
28
  } | {
29
- error?: undefined;
30
29
  token: string;
31
30
  expiresAt: string;
32
31
  ttlSeconds: number;
32
+ error?: undefined;
33
33
  }>>;
@@ -20,6 +20,6 @@ export declare function createTranscribeVoiceHandler(): import("h3").EventHandle
20
20
  error: string;
21
21
  text?: undefined;
22
22
  } | {
23
- error?: undefined;
24
23
  text: string;
24
+ error?: undefined;
25
25
  }>>;
@@ -168,6 +168,11 @@ the useful information remains dense and scannable.
168
168
  - Do not build custom dropdowns, menus, popovers, modals, or confirmations with manual absolute positioning and click-outside effects.
169
169
  - Never use browser dialogs (`window.alert`, `window.confirm`, `window.prompt`). Use `AlertDialog`, `Dialog`, or app-specific confirmation UI.
170
170
  - Use Tabler icons for all first-party UI icons. Do not add Lucide, Heroicons, inline SVG icon sets, or emoji icons.
171
+ - Never guess a Tabler icon name. Names like `IconPartyPopper` or `IconConfettiCannon` feel
172
+ plausible but don't exist and crash Vite with a "Named export not found" error. Before importing
173
+ an icon you haven't used elsewhere in this app, confirm it exists by grepping
174
+ `node_modules/@tabler/icons-react/dist/tabler-icons-react.d.ts` (or the package's icon list)
175
+ for the exact name, and pick the closest real match if your first guess isn't there.
171
176
  - Keep inline help/info glyphs next to labels at `size-3` (12px) or smaller than the adjacent text. Preserve a larger hit area on the trigger, not the glyph. Use `guard:allow-large-help-icon` only for deliberate heading documentation or menu action exceptions.
172
177
  - Use `useActionQuery` and `useActionMutation` from `@agent-native/core/client` for action-backed UI. Standard CRUD should go through actions, not custom `/api/` routes.
173
178
  - Keep UI optimistic where possible: update cache and navigation immediately, then reconcile or roll back on mutation result.
@@ -168,6 +168,11 @@ the useful information remains dense and scannable.
168
168
  - Do not build custom dropdowns, menus, popovers, modals, or confirmations with manual absolute positioning and click-outside effects.
169
169
  - Never use browser dialogs (`window.alert`, `window.confirm`, `window.prompt`). Use `AlertDialog`, `Dialog`, or app-specific confirmation UI.
170
170
  - Use Tabler icons for all first-party UI icons. Do not add Lucide, Heroicons, inline SVG icon sets, or emoji icons.
171
+ - Never guess a Tabler icon name. Names like `IconPartyPopper` or `IconConfettiCannon` feel
172
+ plausible but don't exist and crash Vite with a "Named export not found" error. Before importing
173
+ an icon you haven't used elsewhere in this app, confirm it exists by grepping
174
+ `node_modules/@tabler/icons-react/dist/tabler-icons-react.d.ts` (or the package's icon list)
175
+ for the exact name, and pick the closest real match if your first guess isn't there.
171
176
  - Keep inline help/info glyphs next to labels at `size-3` (12px) or smaller than the adjacent text. Preserve a larger hit area on the trigger, not the glyph. Use `guard:allow-large-help-icon` only for deliberate heading documentation or menu action exceptions.
172
177
  - Use `useActionQuery` and `useActionMutation` from `@agent-native/core/client` for action-backed UI. Standard CRUD should go through actions, not custom `/api/` routes.
173
178
  - Keep UI optimistic where possible: update cache and navigation immediately, then reconcile or roll back on mutation result.
@@ -2,6 +2,50 @@ overrides:
2
2
  "@assistant-ui/store": ">=0.2.9 <0.2.14"
3
3
  "@assistant-ui/tap": "^0.5.14"
4
4
  nf3: "0.3.17"
5
+ # Tiptap 3.28 packages use caret ranges for their internal dependencies.
6
+ # Keep a fresh scaffold on one tested family so extension-list does not
7
+ # resolve to 3.30 alongside core 3.28 during a generated build.
8
+ "@tiptap/core": "3.28.0"
9
+ "@tiptap/extension-blockquote": "3.28.0"
10
+ "@tiptap/extension-bold": "3.28.0"
11
+ "@tiptap/extension-bubble-menu": "3.28.0"
12
+ "@tiptap/extension-bullet-list": "3.28.0"
13
+ "@tiptap/extension-code": "3.28.0"
14
+ "@tiptap/extension-code-block": "3.28.0"
15
+ "@tiptap/extension-code-block-lowlight": "3.28.0"
16
+ "@tiptap/extension-collaboration": "3.28.0"
17
+ "@tiptap/extension-collaboration-caret": "3.28.0"
18
+ "@tiptap/extension-color": "3.28.0"
19
+ "@tiptap/extension-document": "3.28.0"
20
+ "@tiptap/extension-dropcursor": "3.28.0"
21
+ "@tiptap/extension-floating-menu": "3.28.0"
22
+ "@tiptap/extension-gapcursor": "3.28.0"
23
+ "@tiptap/extension-hard-break": "3.28.0"
24
+ "@tiptap/extension-heading": "3.28.0"
25
+ "@tiptap/extension-horizontal-rule": "3.28.0"
26
+ "@tiptap/extension-image": "3.28.0"
27
+ "@tiptap/extension-italic": "3.28.0"
28
+ "@tiptap/extension-link": "3.28.0"
29
+ "@tiptap/extension-list": "3.28.0"
30
+ "@tiptap/extension-list-item": "3.28.0"
31
+ "@tiptap/extension-list-keymap": "3.28.0"
32
+ "@tiptap/extension-ordered-list": "3.28.0"
33
+ "@tiptap/extension-paragraph": "3.28.0"
34
+ "@tiptap/extension-placeholder": "3.28.0"
35
+ "@tiptap/extension-strike": "3.28.0"
36
+ "@tiptap/extension-table": "3.28.0"
37
+ "@tiptap/extension-table-cell": "3.28.0"
38
+ "@tiptap/extension-table-header": "3.28.0"
39
+ "@tiptap/extension-table-row": "3.28.0"
40
+ "@tiptap/extension-task-item": "3.28.0"
41
+ "@tiptap/extension-task-list": "3.28.0"
42
+ "@tiptap/extension-text": "3.28.0"
43
+ "@tiptap/extension-text-style": "3.28.0"
44
+ "@tiptap/extension-underline": "3.28.0"
45
+ "@tiptap/extensions": "3.28.0"
46
+ "@tiptap/pm": "3.28.0"
47
+ "@tiptap/react": "3.28.0"
48
+ "@tiptap/starter-kit": "3.28.0"
5
49
 
6
50
  allowBuilds:
7
51
  better-sqlite3: true
@@ -168,6 +168,11 @@ the useful information remains dense and scannable.
168
168
  - Do not build custom dropdowns, menus, popovers, modals, or confirmations with manual absolute positioning and click-outside effects.
169
169
  - Never use browser dialogs (`window.alert`, `window.confirm`, `window.prompt`). Use `AlertDialog`, `Dialog`, or app-specific confirmation UI.
170
170
  - Use Tabler icons for all first-party UI icons. Do not add Lucide, Heroicons, inline SVG icon sets, or emoji icons.
171
+ - Never guess a Tabler icon name. Names like `IconPartyPopper` or `IconConfettiCannon` feel
172
+ plausible but don't exist and crash Vite with a "Named export not found" error. Before importing
173
+ an icon you haven't used elsewhere in this app, confirm it exists by grepping
174
+ `node_modules/@tabler/icons-react/dist/tabler-icons-react.d.ts` (or the package's icon list)
175
+ for the exact name, and pick the closest real match if your first guess isn't there.
171
176
  - Keep inline help/info glyphs next to labels at `size-3` (12px) or smaller than the adjacent text. Preserve a larger hit area on the trigger, not the glyph. Use `guard:allow-large-help-icon` only for deliberate heading documentation or menu action exceptions.
172
177
  - Use `useActionQuery` and `useActionMutation` from `@agent-native/core/client` for action-backed UI. Standard CRUD should go through actions, not custom `/api/` routes.
173
178
  - Keep UI optimistic where possible: update cache and navigation immediately, then reconcile or roll back on mutation result.