@agent-native/core 0.161.23 → 0.162.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +18 -2
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/calendar/server/handlers/google-auth.ts +1 -8
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +88 -35
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +36 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +4 -3
- package/corpus/templates/clips/desktop/src/app.tsx +30 -40
- package/corpus/templates/clips/desktop/src/components/MicOffConfirmation.tsx +47 -0
- package/corpus/templates/clips/desktop/src/styles.css +110 -0
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +114 -7
- package/corpus/templates/clips/shared/recording-capture.ts +18 -13
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -5
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +0 -3
- package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +0 -4
- package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +4 -1
- package/corpus/templates/content/app/hooks/use-notion.ts +17 -15
- package/corpus/templates/crm/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/design/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/dispatch/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/factory/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/macros/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +18 -3
- package/corpus/templates/mail/server/handlers/google-auth.ts +1 -8
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +4 -0
- package/corpus/templates/slides/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/slides/actions/_await-fit-check.ts +30 -5
- package/corpus/templates/slides/actions/add-slide.ts +6 -2
- package/corpus/templates/slides/actions/create-deck.ts +2 -1
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +34 -1
- package/corpus/templates/slides/app/routes/p.$id.tsx +3 -1
- package/corpus/templates/slides/changelog/2026-08-18-generated-slides-preserve-speaker-notes.md +6 -0
- package/corpus/templates/slides/server/handlers/assets.ts +23 -12
- package/corpus/templates/slides/server/handlers/design-system-decode-job-status.ts +15 -6
- package/corpus/templates/slides/server/handlers/design-system-upload-start.ts +15 -6
- package/corpus/templates/slides/server/handlers/index-design-system-sources.ts +34 -19
- package/corpus/templates/slides/server/handlers/share.ts +1 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/server/routes/api/deck-agent-context.json.get.ts +1 -1
- package/corpus/templates/slides/server/routes/api/exports/[filename].get.ts +9 -3
- package/corpus/templates/slides/server/routes/api/exports/google-slides.post.ts +12 -5
- package/corpus/templates/slides/server/routes/api/exports/html.post.ts +9 -7
- package/corpus/templates/slides/server/routes/api/exports/pptx.post.ts +9 -7
- package/corpus/templates/slides/shared/api.ts +7 -1
- package/corpus/templates/tasks/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/agent/engine/translate-anthropic.js +27 -2
- package/dist/agent/production-agent.d.ts +2 -2
- package/dist/agent/production-agent.js +16 -8
- package/dist/agent/thread-data-builder.d.ts +1 -0
- package/dist/agent/thread-data-builder.js +6 -4
- package/dist/agent/tool-approval-store.d.ts +10 -1
- package/dist/agent/tool-approval-store.js +19 -2
- package/dist/agent/types.d.ts +9 -0
- package/dist/client/AssistantChat.js +14 -6
- package/dist/client/EnvironmentBadge.d.ts +15 -0
- package/dist/client/EnvironmentBadge.js +79 -0
- package/dist/client/MultiTabAssistantChat.js +73 -17
- package/dist/client/agent-chat-adapter.js +11 -1
- package/dist/client/api-path.js +14 -1
- package/dist/client/app-providers.js +2 -1
- package/dist/client/chat/tool-call-display.d.ts +12 -2
- package/dist/client/chat/tool-call-display.js +17 -7
- package/dist/client/chat/widgets/WorkspaceFileWidget.js +1 -1
- package/dist/client/chat/widgets/builtin-tool-renderers.d.ts +6 -0
- package/dist/client/chat/widgets/builtin-tool-renderers.js +19 -0
- package/dist/client/onboarding/FirstRunOnboarding.js +46 -32
- package/dist/client/onboarding/use-onboarding.d.ts +8 -1
- package/dist/client/onboarding/use-onboarding.js +22 -7
- package/dist/client/session-replay.js +58 -15
- package/dist/client/settings/SettingsTabsPage.js +19 -6
- package/dist/client/sse-event-processor.d.ts +7 -0
- package/dist/client/sse-event-processor.js +4 -1
- package/dist/client/ui/index.d.ts +1 -0
- package/dist/client/ui/index.js +1 -0
- package/dist/client/use-action.js +26 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/config.d.ts +15 -0
- package/dist/config.js +53 -0
- package/dist/extensions/fetch-tool.js +8 -2
- package/dist/mcp/embed-app.js +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/provider-api/index.d.ts +10 -0
- package/dist/provider-api/index.js +45 -9
- package/dist/server/auth.js +10 -3
- package/dist/server/poll.js +7 -3
- package/dist/server/sentry.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/vite/client.d.ts +5 -0
- package/dist/vite/client.js +64 -2
- package/dist/workspace-files/actions.d.ts +1 -8
- package/dist/workspace-files/actions.js +5 -21
- package/dist/workspace-files/index.d.ts +1 -1
- package/dist/workspace-files/index.js +1 -1
- package/dist/workspace-files/store.d.ts +18 -0
- package/dist/workspace-files/store.js +24 -3
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
|
@@ -94,15 +94,40 @@ export function formatOverflowForTool(
|
|
|
94
94
|
deckId: string,
|
|
95
95
|
m: SlideFitMeasurement,
|
|
96
96
|
): string {
|
|
97
|
+
const hasHorizontal = (m.horizontalOverflow ?? 0) > 0;
|
|
98
|
+
const hasVertical = m.verticalOverflow > 0;
|
|
99
|
+
|
|
100
|
+
// A row of side-by-side cards/columns that's too wide is a *width formula*
|
|
101
|
+
// bug (fixed px flex-basis, a wrong container-width assumption, a missing
|
|
102
|
+
// `min-width: 0`), not a density bug. The old list was vertical-only —
|
|
103
|
+
// "tighten copy" / "fewer stacked cards" — which cannot shrink a fixed
|
|
104
|
+
// column width no matter how many times it's applied, so a horizontal
|
|
105
|
+
// overflow looped forever against fixes that could never touch the actual
|
|
106
|
+
// defect (see the Oliver Robertson Slack thread this regression test
|
|
107
|
+
// reproduces).
|
|
108
|
+
const fixes = [
|
|
109
|
+
...(hasHorizontal
|
|
110
|
+
? [
|
|
111
|
+
`Use percentage or flex-based column widths (e.g. \`flex: 1 1 0%\` with \`min-width: 0\`, or \`width: calc((100% - <gaps>) / <count>)\` measured against this slide's own content width) instead of a fixed pixel width per column.`,
|
|
112
|
+
`Reduce the number of side-by-side columns, or wrap to a second row, if the content cannot fit at the slide's actual content width.`,
|
|
113
|
+
]
|
|
114
|
+
: []),
|
|
115
|
+
...(hasVertical || !hasHorizontal
|
|
116
|
+
? [
|
|
117
|
+
`Tighten copy — shorter headings/bullets, drop low-value lines.`,
|
|
118
|
+
`Reduce vertical density — fewer stacked cards, smaller gaps, body font no smaller than 16px.`,
|
|
119
|
+
`Reduce slide padding (e.g. 40px top/bottom instead of 60-80px).`,
|
|
120
|
+
`Split across two slides only if the content cannot be compressed.`,
|
|
121
|
+
]
|
|
122
|
+
: []),
|
|
123
|
+
];
|
|
124
|
+
|
|
97
125
|
return [
|
|
98
126
|
``,
|
|
99
|
-
`⚠ Layout overflows the canvas${
|
|
127
|
+
`⚠ Layout overflows the canvas${hasVertical ? ` vertically by ${m.verticalOverflow}px` : ""}${hasHorizontal ? ` and horizontally by ${m.horizontalOverflow}px` : ""} — natural content is ${m.contentWidth ?? "unknown"}x${m.contentHeight}px inside a ${m.viewportWidth ?? "unknown"}x${m.viewportHeight}px content area.`,
|
|
100
128
|
``,
|
|
101
129
|
`Make one structural repair now with \`update-slide --deckId ${deckId} --slideId ${m.slideId}\`. Prefer small surgical patches (--find / --replace) over a full rewrite:`,
|
|
102
|
-
|
|
103
|
-
`2. Reduce vertical density — fewer stacked cards, smaller gaps, body font no smaller than 16px.`,
|
|
104
|
-
`3. Reduce slide padding (e.g. 40px top/bottom instead of 60-80px).`,
|
|
105
|
-
`4. Split across two slides only if the content cannot be compressed.`,
|
|
130
|
+
...fixes.map((fix, index) => `${index + 1}. ${fix}`),
|
|
106
131
|
``,
|
|
107
132
|
`Do **not** use zoom, \`transform: scale\`, clipping, \`overflow: scroll\`, or a smaller-than-16px body font. Preserve existing absolute text boxes and fix the normal-flow HTML. Verify the action result and then use \`view-screen\`; do not spin through another repair loop.`,
|
|
108
133
|
].join("\n");
|
|
@@ -97,6 +97,7 @@ export default defineAction({
|
|
|
97
97
|
"call it once per slide in slide order and wait for each result before adding the next slide. " +
|
|
98
98
|
"Avoid parallel add-slide calls for the same deck; sequential writes keep the editor and agent connection stable. " +
|
|
99
99
|
"If the deck has a designSystemId, first use `get-design-system` and apply its `agentContext` tokens/docs; do not use generic slide styling from the id alone. " +
|
|
100
|
+
"Pass presenter-only speaker notes in `notes`; keep them out of the slide HTML. " +
|
|
100
101
|
"Returns the new slide ID, 1-based slideNumber, and updated slide count.",
|
|
101
102
|
schema: z.object({
|
|
102
103
|
deckId: z.string().describe("Target deck ID"),
|
|
@@ -120,7 +121,10 @@ export default defineAction({
|
|
|
120
121
|
])
|
|
121
122
|
.optional()
|
|
122
123
|
.describe("Layout type hint"),
|
|
123
|
-
notes: z
|
|
124
|
+
notes: z
|
|
125
|
+
.string()
|
|
126
|
+
.optional()
|
|
127
|
+
.describe("Optional presenter-only speaker notes; keep them out of HTML"),
|
|
124
128
|
position: z
|
|
125
129
|
.preprocess((value) => {
|
|
126
130
|
if (typeof value !== "string") return value;
|
|
@@ -299,7 +303,7 @@ export default defineAction({
|
|
|
299
303
|
creativeContextReuseLabels: slideReuseLabels,
|
|
300
304
|
};
|
|
301
305
|
if (layout) newSlide.layout = layout;
|
|
302
|
-
if (notes) newSlide.notes = notes;
|
|
306
|
+
if (notes !== undefined) newSlide.notes = notes;
|
|
303
307
|
|
|
304
308
|
const insertIndex =
|
|
305
309
|
typeof position === "number"
|
|
@@ -96,12 +96,13 @@ export default defineAction({
|
|
|
96
96
|
"Create a new deck, optionally already populated with slides, or atomically replace all slides in an existing deck. " +
|
|
97
97
|
"For short AI-generated decks in MCP app hosts, pass all generated slides in this call so the real deck editor opens inline already populated. " +
|
|
98
98
|
"For longer decks or live in-app generation, create the deck with slides: [] and then use add-slide sequentially so progress appears live. " +
|
|
99
|
+
"Pass presenter-only speaker notes in each slide's `notes` field; keep them out of slide HTML. " +
|
|
99
100
|
"Pass deckId to replace an existing deck. " +
|
|
100
101
|
"Returns the deck id, title, and slide count.",
|
|
101
102
|
schema: z.object({
|
|
102
103
|
title: z.string().describe("Deck title"),
|
|
103
104
|
slides: SlidesSchema.describe(
|
|
104
|
-
"Array of slides with id, content (HTML), and
|
|
105
|
+
"Array of slides with id, content (HTML), optional layout, and presenter-only notes",
|
|
105
106
|
),
|
|
106
107
|
deckId: z
|
|
107
108
|
.string()
|
|
@@ -376,6 +376,7 @@ export async function startDeckGeneration({
|
|
|
376
376
|
`After reading any requested or attached reference material, but before adding the first slide, choose a concise, specific deck title from the user's request and source material. Never use the deck id, run id, file id, uploaded filename, or another opaque alphanumeric token as the title. Do not reuse a generic placeholder like "Untitled scene" when the content or reference context gives you a better title. Call \`patch-deck\` with \`deckId: \"${deckId}\"\` and \`operations: [{ \"op\": \"patch-deck-fields\", \"fields\": { \"title\": \"<generated title>\" } }]\`. Include only \`title\` in \`fields\`; omit all other optional fields. Never leave a generated deck named \"Untitled Deck\" or another placeholder.`,
|
|
377
377
|
"If the user asks for a standalone visual, diagram, hero, one-pager, poster, or a couple of visuals, create only the requested one/few polished visual slides. Do not pad the result into a full presentation.",
|
|
378
378
|
"If the request is for a presentation or deck and does not explicitly ask for one slide, infer a coherent multi-slide outline from the scope and keep adding slides until that outline is complete. Do not stop after the first slide just because the prompt has few explicit instructions.",
|
|
379
|
+
"When the user requests speaker notes, write presenter-only text into each slide's `notes` field and keep it out of the slide HTML.",
|
|
379
380
|
`Add slides ONE AT A TIME using the \`add-slide\` action with --deckId=${deckId}. Wait for each \`add-slide\` result before calling it again; do not batch or parallelize slide writes.`,
|
|
380
381
|
"Use create-deck and add-slide for this already-created deck. Do not call the legacy generate-slides-ai action: it returns Markdown drafts rather than persisted rendered slide HTML. Treat each successful add-slide result as confirmation to continue with the next planned slide.",
|
|
381
382
|
].join("\n");
|
|
@@ -174,6 +174,27 @@ function MissingDeckAccessPane({
|
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
// The Cmd/Ctrl+C-then-V slide-duplicate shortcut can only tell "this key
|
|
178
|
+
// event targets the slide rail/canvas" apart from "focus fell back to
|
|
179
|
+
// nothing because a panel/dialog elsewhere just closed" by checking a
|
|
180
|
+
// deny-list of known text surfaces — and that list can never be complete
|
|
181
|
+
// (see the Andrew Rohman Slack thread this guards against: a slide copied
|
|
182
|
+
// once early in a session kept silently re-duplicating on unrelated later
|
|
183
|
+
// pastes). Bounding how long a copy stays "armed" turns a missed deny-list
|
|
184
|
+
// entry from a silent, indefinite landmine into, at worst, a narrow window
|
|
185
|
+
// that still covers the real copy-then-paste gesture.
|
|
186
|
+
export const SLIDE_CLIPBOARD_ARM_WINDOW_MS = 30_000;
|
|
187
|
+
|
|
188
|
+
/** True when a Cmd/Ctrl+V should still be treated as "paste the slide that
|
|
189
|
+
* was just copied" rather than unrelated clipboard activity landing outside
|
|
190
|
+
* every recognized text field. */
|
|
191
|
+
export function isSlideClipboardStillArmed(
|
|
192
|
+
armedAt: number | null,
|
|
193
|
+
now: number = Date.now(),
|
|
194
|
+
): boolean {
|
|
195
|
+
return armedAt !== null && now - armedAt <= SLIDE_CLIPBOARD_ARM_WINDOW_MS;
|
|
196
|
+
}
|
|
197
|
+
|
|
177
198
|
export default function DeckEditor() {
|
|
178
199
|
const t = useT();
|
|
179
200
|
const { id } = useParams<{ id: string }>();
|
|
@@ -861,6 +882,11 @@ export default function DeckEditor() {
|
|
|
861
882
|
// (rather than just an id) so paste still works after Cut has already
|
|
862
883
|
// removed the original slide from the deck.
|
|
863
884
|
const slideClipboardRef = useRef<Slide | null>(null);
|
|
885
|
+
// Only gates the ambient document-level Cmd/Ctrl+V shortcut below — the
|
|
886
|
+
// rail's right-click "Paste" menu item is an explicit click with no
|
|
887
|
+
// ambiguity risk, so it keeps working off `hasSlideClipboard` alone however
|
|
888
|
+
// long ago the copy happened.
|
|
889
|
+
const slideClipboardArmedAtRef = useRef<number | null>(null);
|
|
864
890
|
const [hasSlideClipboard, setHasSlideClipboard] = useState(false);
|
|
865
891
|
|
|
866
892
|
const copySlide = useCallback(
|
|
@@ -868,6 +894,7 @@ export default function DeckEditor() {
|
|
|
868
894
|
const slide = deck?.slides.find((s) => s.id === slideId);
|
|
869
895
|
if (!slide) return;
|
|
870
896
|
slideClipboardRef.current = slide;
|
|
897
|
+
slideClipboardArmedAtRef.current = Date.now();
|
|
871
898
|
setHasSlideClipboard(true);
|
|
872
899
|
},
|
|
873
900
|
[deck],
|
|
@@ -879,6 +906,7 @@ export default function DeckEditor() {
|
|
|
879
906
|
const slide = deck.slides.find((s) => s.id === slideId);
|
|
880
907
|
if (!slide) return;
|
|
881
908
|
slideClipboardRef.current = slide;
|
|
909
|
+
slideClipboardArmedAtRef.current = Date.now();
|
|
882
910
|
setHasSlideClipboard(true);
|
|
883
911
|
const idx = deck.slides.findIndex((s) => s.id === slideId);
|
|
884
912
|
const nextSlide = deck.slides[idx + 1] || deck.slides[idx - 1];
|
|
@@ -1029,7 +1057,12 @@ export default function DeckEditor() {
|
|
|
1029
1057
|
return;
|
|
1030
1058
|
}
|
|
1031
1059
|
|
|
1032
|
-
if (
|
|
1060
|
+
if (
|
|
1061
|
+
!hasSlideClipboard ||
|
|
1062
|
+
!activeSlideId ||
|
|
1063
|
+
!isSlideClipboardStillArmed(slideClipboardArmedAtRef.current)
|
|
1064
|
+
)
|
|
1065
|
+
return;
|
|
1033
1066
|
e.preventDefault();
|
|
1034
1067
|
pasteSlideAfter(activeSlideId);
|
|
1035
1068
|
};
|
|
@@ -81,7 +81,9 @@ function toSharedDeck(row: {
|
|
|
81
81
|
return {
|
|
82
82
|
title: row.title || data.title || "Untitled",
|
|
83
83
|
slides: Array.isArray(data.slides)
|
|
84
|
-
? data.slides.map((slide, index) =>
|
|
84
|
+
? data.slides.map((slide, index) =>
|
|
85
|
+
toSharedDeckSlide(slide, index, { includeNotes: false }),
|
|
86
|
+
)
|
|
85
87
|
: [],
|
|
86
88
|
aspectRatio: data.aspectRatio,
|
|
87
89
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
|
|
3
3
|
import { uploadFile } from "@agent-native/core/file-upload";
|
|
4
|
-
import { getSession } from "@agent-native/core/server";
|
|
5
4
|
import { runWithRequestContext } from "@agent-native/core/server";
|
|
6
5
|
import { and, desc, eq } from "drizzle-orm";
|
|
7
6
|
import {
|
|
@@ -13,6 +12,12 @@ import {
|
|
|
13
12
|
import { nanoid } from "nanoid";
|
|
14
13
|
|
|
15
14
|
import { getDb, schema } from "../db/index.js";
|
|
15
|
+
import {
|
|
16
|
+
resolveSlidesRequestAuth,
|
|
17
|
+
type SlidesRequestAuthContext,
|
|
18
|
+
} from "./request-auth-context.js";
|
|
19
|
+
|
|
20
|
+
type AuthedSlidesSession = SlidesRequestAuthContext & { email: string };
|
|
16
21
|
|
|
17
22
|
export const MAX_ASSET_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
18
23
|
|
|
@@ -32,13 +37,19 @@ export interface ListedUploadedAsset {
|
|
|
32
37
|
createdAt: string;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
async function requireSession(
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
async function requireSession(
|
|
41
|
+
event: Parameters<typeof resolveSlidesRequestAuth>[0],
|
|
42
|
+
): Promise<{ session: AuthedSlidesSession | null; error: string | null }> {
|
|
43
|
+
const auth = await resolveSlidesRequestAuth(event);
|
|
44
|
+
if (!auth.ok) {
|
|
45
|
+
setResponseStatus(event, auth.statusCode);
|
|
46
|
+
return { session: null, error: auth.error };
|
|
47
|
+
}
|
|
48
|
+
if (!auth.context.email) {
|
|
38
49
|
setResponseStatus(event, 401);
|
|
39
|
-
return null;
|
|
50
|
+
return { session: null, error: "Unauthorized" };
|
|
40
51
|
}
|
|
41
|
-
return session;
|
|
52
|
+
return { session: auth.context as AuthedSlidesSession, error: null };
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
function isRasterAssetExtension(ext: string): boolean {
|
|
@@ -180,9 +191,9 @@ export async function uploadImageAsset(args: {
|
|
|
180
191
|
* framework provider chain, return its hosted URL.
|
|
181
192
|
*/
|
|
182
193
|
export const uploadAsset = defineEventHandler(async (event) => {
|
|
183
|
-
const session = await requireSession(event);
|
|
194
|
+
const { session, error: authError } = await requireSession(event);
|
|
184
195
|
if (!session) {
|
|
185
|
-
return { error:
|
|
196
|
+
return { error: authError };
|
|
186
197
|
}
|
|
187
198
|
|
|
188
199
|
const parts = await readMultipartFormData(event);
|
|
@@ -217,9 +228,9 @@ export const uploadAsset = defineEventHandler(async (event) => {
|
|
|
217
228
|
* GET /api/assets — list assets this user has uploaded, most recent first.
|
|
218
229
|
*/
|
|
219
230
|
export const listAssets = defineEventHandler(async (event) => {
|
|
220
|
-
const session = await requireSession(event);
|
|
231
|
+
const { session, error } = await requireSession(event);
|
|
221
232
|
if (!session) {
|
|
222
|
-
return { error
|
|
233
|
+
return { error };
|
|
223
234
|
}
|
|
224
235
|
const db = getDb();
|
|
225
236
|
const rows: ListedUploadedAsset[] = await db
|
|
@@ -244,9 +255,9 @@ export const listAssets = defineEventHandler(async (event) => {
|
|
|
244
255
|
* that provider's own API — but it no longer appears in this app's library.
|
|
245
256
|
*/
|
|
246
257
|
export const deleteAsset = defineEventHandler(async (event) => {
|
|
247
|
-
const session = await requireSession(event);
|
|
258
|
+
const { session, error } = await requireSession(event);
|
|
248
259
|
if (!session) {
|
|
249
|
-
return { error
|
|
260
|
+
return { error };
|
|
250
261
|
}
|
|
251
262
|
const id = getRouterParam(event, "id");
|
|
252
263
|
if (!id) {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FeatureNotConfiguredError,
|
|
3
3
|
fetchBuilderDesignSystemDecodeJobStatus,
|
|
4
|
-
getSession,
|
|
5
4
|
} from "@agent-native/core/server";
|
|
6
5
|
import { defineEventHandler, getQuery, setResponseStatus } from "h3";
|
|
7
6
|
|
|
8
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
resolveSlidesRequestAuth,
|
|
9
|
+
withSlidesRequestContext,
|
|
10
|
+
} from "./request-auth-context.js";
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Reads a Builder design-system decode job's status. The UI polls this after
|
|
@@ -13,8 +15,13 @@ import { withSlidesRequestContext } from "./request-auth-context.js";
|
|
|
13
15
|
* job leaves `pending` and exposes a `branchUrl` (or reports an `error`).
|
|
14
16
|
*/
|
|
15
17
|
export const designSystemDecodeJobStatus = defineEventHandler(async (event) => {
|
|
16
|
-
const
|
|
17
|
-
if (!
|
|
18
|
+
const auth = await resolveSlidesRequestAuth(event);
|
|
19
|
+
if (!auth.ok) {
|
|
20
|
+
setResponseStatus(event, auth.statusCode);
|
|
21
|
+
return { error: auth.error };
|
|
22
|
+
}
|
|
23
|
+
const session = auth.context;
|
|
24
|
+
if (!session.email) {
|
|
18
25
|
setResponseStatus(event, 401);
|
|
19
26
|
return { error: "Unauthorized" };
|
|
20
27
|
}
|
|
@@ -26,8 +33,10 @@ export const designSystemDecodeJobStatus = defineEventHandler(async (event) => {
|
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
try {
|
|
29
|
-
return await withSlidesRequestContext(
|
|
30
|
-
|
|
36
|
+
return await withSlidesRequestContext(
|
|
37
|
+
event,
|
|
38
|
+
() => fetchBuilderDesignSystemDecodeJobStatus(jobId.trim()),
|
|
39
|
+
session,
|
|
31
40
|
);
|
|
32
41
|
} catch (err) {
|
|
33
42
|
if (err instanceof FeatureNotConfiguredError) {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FeatureNotConfiguredError,
|
|
3
|
-
getSession,
|
|
4
3
|
startBuilderDesignSystemUpload,
|
|
5
4
|
} from "@agent-native/core/server";
|
|
6
5
|
import { defineEventHandler, readBody, setResponseStatus } from "h3";
|
|
7
6
|
|
|
8
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
resolveSlidesRequestAuth,
|
|
9
|
+
withSlidesRequestContext,
|
|
10
|
+
} from "./request-auth-context.js";
|
|
9
11
|
|
|
10
12
|
const MAX_FIG_BYTES = 512 * 1024 * 1024;
|
|
11
13
|
|
|
@@ -21,8 +23,13 @@ interface AttachmentInput {
|
|
|
21
23
|
* server; the file bytes never do.
|
|
22
24
|
*/
|
|
23
25
|
export const designSystemUploadStart = defineEventHandler(async (event) => {
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
26
|
+
const auth = await resolveSlidesRequestAuth(event);
|
|
27
|
+
if (!auth.ok) {
|
|
28
|
+
setResponseStatus(event, auth.statusCode);
|
|
29
|
+
return { error: auth.error };
|
|
30
|
+
}
|
|
31
|
+
const session = auth.context;
|
|
32
|
+
if (!session.email) {
|
|
26
33
|
setResponseStatus(event, 401);
|
|
27
34
|
return { error: "Unauthorized" };
|
|
28
35
|
}
|
|
@@ -66,8 +73,10 @@ export const designSystemUploadStart = defineEventHandler(async (event) => {
|
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
try {
|
|
69
|
-
const uploads = await withSlidesRequestContext(
|
|
70
|
-
|
|
76
|
+
const uploads = await withSlidesRequestContext(
|
|
77
|
+
event,
|
|
78
|
+
() => startBuilderDesignSystemUpload(attachments),
|
|
79
|
+
session,
|
|
71
80
|
);
|
|
72
81
|
return { uploads };
|
|
73
82
|
} catch (err) {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FeatureNotConfiguredError,
|
|
3
|
-
getSession,
|
|
4
3
|
indexBuilderDesignSystem,
|
|
5
4
|
} from "@agent-native/core/server";
|
|
6
5
|
import { defineEventHandler, readBody, setResponseStatus } from "h3";
|
|
7
6
|
|
|
8
7
|
import { upsertBuilderProxyDesignSystem } from "../lib/builder-design-system-proxy.js";
|
|
9
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
resolveSlidesRequestAuth,
|
|
10
|
+
withSlidesRequestContext,
|
|
11
|
+
} from "./request-auth-context.js";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Finalizes Builder DSI indexing from upload tokens produced by the
|
|
@@ -14,8 +16,14 @@ import { withSlidesRequestContext } from "./request-auth-context.js";
|
|
|
14
16
|
* storage; this endpoint only forwards the opaque tokens.
|
|
15
17
|
*/
|
|
16
18
|
export const indexDesignSystemSources = defineEventHandler(async (event) => {
|
|
17
|
-
const
|
|
18
|
-
if (!
|
|
19
|
+
const auth = await resolveSlidesRequestAuth(event);
|
|
20
|
+
if (!auth.ok) {
|
|
21
|
+
setResponseStatus(event, auth.statusCode);
|
|
22
|
+
return { error: auth.error };
|
|
23
|
+
}
|
|
24
|
+
const session = auth.context;
|
|
25
|
+
const sessionEmail = session.email;
|
|
26
|
+
if (!sessionEmail) {
|
|
19
27
|
setResponseStatus(event, 401);
|
|
20
28
|
return { error: "Unauthorized" };
|
|
21
29
|
}
|
|
@@ -45,21 +53,28 @@ export const indexDesignSystemSources = defineEventHandler(async (event) => {
|
|
|
45
53
|
}));
|
|
46
54
|
|
|
47
55
|
try {
|
|
48
|
-
return await withSlidesRequestContext(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
result
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
return await withSlidesRequestContext(
|
|
57
|
+
event,
|
|
58
|
+
async ({ email, orgId }) => {
|
|
59
|
+
const result = await indexBuilderDesignSystem({
|
|
60
|
+
sources,
|
|
61
|
+
projectName,
|
|
62
|
+
});
|
|
63
|
+
const proxy = await upsertBuilderProxyDesignSystem({
|
|
64
|
+
result,
|
|
65
|
+
ownerEmail: email ?? sessionEmail,
|
|
66
|
+
orgId: orgId ?? null,
|
|
67
|
+
projectName,
|
|
68
|
+
sourceKind: "figma",
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
...result,
|
|
72
|
+
...proxy,
|
|
73
|
+
uploadedFileCount: uploadTokens.length,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
session,
|
|
77
|
+
);
|
|
63
78
|
} catch (err) {
|
|
64
79
|
if (err instanceof FeatureNotConfiguredError) {
|
|
65
80
|
setResponseStatus(event, 412);
|
|
@@ -116,7 +116,7 @@ async function createShareLink(event: any, deckId: string) {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
const slides = storedDeck.slides.map((slide: unknown, index: number) =>
|
|
119
|
-
toSharedDeckSlide(slide, index),
|
|
119
|
+
toSharedDeckSlide(slide, index, { includeNotes: false }),
|
|
120
120
|
);
|
|
121
121
|
|
|
122
122
|
await db.insert(schema.deckShareLinks).values({
|
|
@@ -57,7 +57,7 @@ export default createAgentChatPlugin({
|
|
|
57
57
|
return authContext.orgId === undefined ? null : authContext.orgId;
|
|
58
58
|
},
|
|
59
59
|
prepareRequest: prepareSlidesChatAttachments,
|
|
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.
|
|
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. When speaker notes are requested, keep presenter-only text in each slide's notes field rather than the slide HTML, and preserve notes during source-preserving edits.
|
|
61
61
|
|
|
62
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.
|
|
63
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.
|
|
@@ -77,7 +77,7 @@ export default defineEventHandler(async (event) => {
|
|
|
77
77
|
const data = parseDeckData(deck.data);
|
|
78
78
|
const slides = Array.isArray(data.slides)
|
|
79
79
|
? data.slides.map((slide: unknown, index: number) =>
|
|
80
|
-
toSharedDeckSlide(slide, index),
|
|
80
|
+
toSharedDeckSlide(slide, index, { includeNotes: false }),
|
|
81
81
|
)
|
|
82
82
|
: [];
|
|
83
83
|
|
|
@@ -2,9 +2,10 @@ import { createReadStream } from "fs";
|
|
|
2
2
|
import { stat } from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { streamFile } from "@agent-native/core/server";
|
|
6
6
|
import { defineEventHandler, getRouterParam, setResponseStatus } from "h3";
|
|
7
7
|
|
|
8
|
+
import { resolveSlidesRequestAuth } from "../../../handlers/request-auth-context.js";
|
|
8
9
|
import { tenantExportDir } from "../../../lib/tenant-files.js";
|
|
9
10
|
|
|
10
11
|
const CONTENT_TYPES: Record<string, string> = {
|
|
@@ -15,8 +16,13 @@ const CONTENT_TYPES: Record<string, string> = {
|
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
export default defineEventHandler(async (event) => {
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
19
|
+
const auth = await resolveSlidesRequestAuth(event);
|
|
20
|
+
if (!auth.ok) {
|
|
21
|
+
setResponseStatus(event, auth.statusCode);
|
|
22
|
+
return { error: auth.error };
|
|
23
|
+
}
|
|
24
|
+
const session = auth.context;
|
|
25
|
+
if (!session.email) {
|
|
20
26
|
setResponseStatus(event, 401);
|
|
21
27
|
return { error: "Unauthorized" };
|
|
22
28
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runWithRequestContext } from "@agent-native/core/server";
|
|
2
2
|
import {
|
|
3
3
|
defineEventHandler,
|
|
4
4
|
readMultipartFormData,
|
|
5
5
|
setResponseStatus,
|
|
6
6
|
} from "h3";
|
|
7
7
|
|
|
8
|
+
import { resolveSlidesRequestAuth } from "../../../handlers/request-auth-context.js";
|
|
8
9
|
import { getGoogleDocsAccessToken } from "../../../lib/google-docs-oauth.js";
|
|
9
10
|
|
|
10
11
|
const PPTX_CONTENT_TYPE =
|
|
@@ -28,8 +29,14 @@ const UPLOAD_URL =
|
|
|
28
29
|
* See "Export Behavior" in `templates/slides/AGENTS.md` for the routing rule.
|
|
29
30
|
*/
|
|
30
31
|
export default defineEventHandler(async (event) => {
|
|
31
|
-
const
|
|
32
|
-
if (!
|
|
32
|
+
const auth = await resolveSlidesRequestAuth(event);
|
|
33
|
+
if (!auth.ok) {
|
|
34
|
+
setResponseStatus(event, auth.statusCode);
|
|
35
|
+
return { error: auth.error };
|
|
36
|
+
}
|
|
37
|
+
const session = auth.context;
|
|
38
|
+
const sessionEmail = session.email;
|
|
39
|
+
if (!sessionEmail) {
|
|
33
40
|
setResponseStatus(event, 401);
|
|
34
41
|
return { error: "Unauthorized" };
|
|
35
42
|
}
|
|
@@ -50,8 +57,8 @@ export default defineEventHandler(async (event) => {
|
|
|
50
57
|
// be org-scoped vault secrets, and resolving them without the org reports the
|
|
51
58
|
// integration as unconfigured.
|
|
52
59
|
const account = await runWithRequestContext(
|
|
53
|
-
{ userEmail:
|
|
54
|
-
() => getGoogleDocsAccessToken(
|
|
60
|
+
{ userEmail: sessionEmail, orgId: session.orgId },
|
|
61
|
+
() => getGoogleDocsAccessToken(sessionEmail),
|
|
55
62
|
);
|
|
56
63
|
if (!account) {
|
|
57
64
|
setResponseStatus(event, 409);
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
getSession,
|
|
5
|
-
readBody,
|
|
6
|
-
runWithRequestContext,
|
|
7
|
-
} from "@agent-native/core/server";
|
|
3
|
+
import { readBody, runWithRequestContext } from "@agent-native/core/server";
|
|
8
4
|
import { defineEventHandler, setResponseStatus } from "h3";
|
|
9
5
|
|
|
10
6
|
import exportHtmlAction from "../../../../actions/export-html.js";
|
|
7
|
+
import { resolveSlidesRequestAuth } from "../../../handlers/request-auth-context.js";
|
|
11
8
|
|
|
12
9
|
export default defineEventHandler(async (event) => {
|
|
13
|
-
const
|
|
14
|
-
if (!
|
|
10
|
+
const auth = await resolveSlidesRequestAuth(event);
|
|
11
|
+
if (!auth.ok) {
|
|
12
|
+
setResponseStatus(event, auth.statusCode);
|
|
13
|
+
return { error: auth.error };
|
|
14
|
+
}
|
|
15
|
+
const session = auth.context;
|
|
16
|
+
if (!session.email) {
|
|
15
17
|
setResponseStatus(event, 401);
|
|
16
18
|
return { error: "Unauthorized" };
|
|
17
19
|
}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
getSession,
|
|
5
|
-
readBody,
|
|
6
|
-
runWithRequestContext,
|
|
7
|
-
} from "@agent-native/core/server";
|
|
3
|
+
import { readBody, runWithRequestContext } from "@agent-native/core/server";
|
|
8
4
|
import { defineEventHandler, setResponseStatus } from "h3";
|
|
9
5
|
|
|
10
6
|
import exportPptxAction from "../../../../actions/export-pptx.js";
|
|
7
|
+
import { resolveSlidesRequestAuth } from "../../../handlers/request-auth-context.js";
|
|
11
8
|
|
|
12
9
|
const PPTX_CONTENT_TYPE =
|
|
13
10
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
14
11
|
|
|
15
12
|
export default defineEventHandler(async (event) => {
|
|
16
|
-
const
|
|
17
|
-
if (!
|
|
13
|
+
const auth = await resolveSlidesRequestAuth(event);
|
|
14
|
+
if (!auth.ok) {
|
|
15
|
+
setResponseStatus(event, auth.statusCode);
|
|
16
|
+
return { error: auth.error };
|
|
17
|
+
}
|
|
18
|
+
const session = auth.context;
|
|
19
|
+
if (!session.email) {
|
|
18
20
|
setResponseStatus(event, 401);
|
|
19
21
|
return { error: "Unauthorized" };
|
|
20
22
|
}
|
|
@@ -126,6 +126,11 @@ export interface SharedDeckSlide {
|
|
|
126
126
|
splitByParagraph?: boolean;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
export interface SharedDeckSlideOptions {
|
|
130
|
+
/** Include presenter-only notes for an authenticated internal read. */
|
|
131
|
+
includeNotes?: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
129
134
|
const SHARED_SLIDE_TRANSITIONS = new Set<SharedSlideTransition>([
|
|
130
135
|
"instant",
|
|
131
136
|
"none",
|
|
@@ -216,12 +221,13 @@ function normalizeSlideAnimation(
|
|
|
216
221
|
export function toSharedDeckSlide(
|
|
217
222
|
value: unknown,
|
|
218
223
|
index: number,
|
|
224
|
+
options: SharedDeckSlideOptions = {},
|
|
219
225
|
): SharedDeckSlide {
|
|
220
226
|
const slide = isRecord(value) ? value : {};
|
|
221
227
|
const shared: SharedDeckSlide = {
|
|
222
228
|
id: normalizeString(slide.id, `slide-${index + 1}`),
|
|
223
229
|
content: normalizeString(slide.content, ""),
|
|
224
|
-
notes: "",
|
|
230
|
+
notes: options.includeNotes ? normalizeString(slide.notes, "") : "",
|
|
225
231
|
layout: normalizeString(slide.layout, "content"),
|
|
226
232
|
};
|
|
227
233
|
|
|
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
50
50
|
|
|
51
51
|
For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
|
|
52
52
|
|
|
53
|
-
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It
|
|
53
|
+
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
|
|
54
54
|
|
|
55
55
|
5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
|
|
56
56
|
|