@agent-native/core 0.161.22 → 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/design/app/components/design/keyboard-shortcuts.ts +13 -7
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +18 -21
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +2 -5
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +4 -12
- package/corpus/templates/design/app/pages/design-editor/types.ts +38 -1
- package/corpus/templates/design/changelog/2026-08-18-advanced-design-editor-panels-hidden-by-default.md +6 -0
- package/corpus/templates/design/playwright.config.ts +19 -2
- 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/builder-engine.js +15 -8
- package/dist/agent/engine/translate-anthropic.js +27 -2
- package/dist/agent/production-agent.d.ts +17 -2
- package/dist/agent/production-agent.js +48 -17
- 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/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/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/mcp/embed-app.js +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/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/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- 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
|
@@ -22,9 +22,24 @@ const E2E_DATABASE_URL =
|
|
|
22
22
|
process.env.E2E_DATABASE_URL ??
|
|
23
23
|
`file:${path.join(import.meta.dirname, "data", "e2e.db")}`;
|
|
24
24
|
const BROWSER_CHANNEL = process.env.E2E_BROWSER_CHANNEL;
|
|
25
|
+
const SHOW_SECONDARY_PANELS_IN_E2E =
|
|
26
|
+
process.env.E2E_SHOW_DESIGN_SECONDARY_LEFT_PANELS !== "0";
|
|
27
|
+
const SECONDARY_PANELS_ENV = SHOW_SECONDARY_PANELS_IN_E2E
|
|
28
|
+
? "VITE_SHOW_DESIGN_SECONDARY_LEFT_PANELS=1 "
|
|
29
|
+
: "VITE_SHOW_DESIGN_SECONDARY_LEFT_PANELS=0 ";
|
|
30
|
+
const ADVANCED_PANEL_SPEC_FILES = [
|
|
31
|
+
/canvas-tools\.spec\.ts$/,
|
|
32
|
+
/code-native-deep-surfaces\.spec\.ts$/,
|
|
33
|
+
/code-native-pr-surfaces\.spec\.ts$/,
|
|
34
|
+
/code-workbench-local-files\.spec\.ts$/,
|
|
35
|
+
];
|
|
25
36
|
|
|
26
37
|
export default defineConfig({
|
|
27
38
|
testDir: "./e2e",
|
|
39
|
+
// These suites intentionally exercise panels that are not mounted in the
|
|
40
|
+
// disabled profile. Ignoring them keeps that profile focused on verifying
|
|
41
|
+
// the hidden-panel contract without failing on missing advanced controls.
|
|
42
|
+
testIgnore: SHOW_SECONDARY_PANELS_IN_E2E ? [] : ADVANCED_PANEL_SPEC_FILES,
|
|
28
43
|
// The editor is heavy (iframe bridge + polling); give generous budgets.
|
|
29
44
|
timeout: 90_000,
|
|
30
45
|
expect: { timeout: 15_000 },
|
|
@@ -56,9 +71,11 @@ export default defineConfig({
|
|
|
56
71
|
// generic DATABASE_URL, but set both to an absolute SQLite URL so a
|
|
57
72
|
// `.env` Postgres URL or a changed command cwd can never override this
|
|
58
73
|
// throwaway local db.
|
|
59
|
-
command: `APP_NAME=design DESIGN_DATABASE_URL=${JSON.stringify(E2E_DATABASE_URL)} DATABASE_URL=${JSON.stringify(E2E_DATABASE_URL)} PORT=${PORT} corepack pnpm dev`,
|
|
74
|
+
command: `APP_NAME=design ${SECONDARY_PANELS_ENV}DESIGN_DATABASE_URL=${JSON.stringify(E2E_DATABASE_URL)} DATABASE_URL=${JSON.stringify(E2E_DATABASE_URL)} PORT=${PORT} corepack pnpm dev`,
|
|
60
75
|
url: BASE_URL,
|
|
61
|
-
|
|
76
|
+
// The panel flag is compiled into the Vite bundle. Reusing a local
|
|
77
|
+
// server can therefore run this profile with the opposite setting.
|
|
78
|
+
reuseExistingServer: false,
|
|
62
79
|
// Cold Vite dep-optimization on first boot can exceed two minutes.
|
|
63
80
|
timeout: 300_000,
|
|
64
81
|
stdout: "ignore",
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -11,7 +11,10 @@ import { openCommandMenu } from "@agent-native/core/client/navigation";
|
|
|
11
11
|
import { InvitationBanner, OrgSwitcher } from "@agent-native/core/client/org";
|
|
12
12
|
import { FeedbackButton } from "@agent-native/core/client/ui";
|
|
13
13
|
import { SidebarFooterActions } from "@agent-native/toolkit/app-shell";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
isInboxScopedAppLabel,
|
|
16
|
+
normalizeMailLabel,
|
|
17
|
+
} from "@shared/gmail-labels";
|
|
15
18
|
import type { Label } from "@shared/types";
|
|
16
19
|
import {
|
|
17
20
|
IconMenu2,
|
|
@@ -171,6 +174,18 @@ function labelDepth(name: string): number {
|
|
|
171
174
|
return Math.max(0, name.split("/").length - 1);
|
|
172
175
|
}
|
|
173
176
|
|
|
177
|
+
// Gmail's inbox-only categories (important, social, promotions, ...) only
|
|
178
|
+
// ever exist inside the inbox, so their tab stays scoped there. Regular user
|
|
179
|
+
// labels are filed/archived independently of the inbox — routing them
|
|
180
|
+
// through /inbox forces `in:inbox` server-side and hides every message the
|
|
181
|
+
// user has archived out of the inbox while keeping the label, which reads as
|
|
182
|
+
// "label is empty" even though it has mail. Route those through /all so the
|
|
183
|
+
// label search is unscoped.
|
|
184
|
+
export function labelTabHref(labelId: string): string {
|
|
185
|
+
const view = isInboxScopedAppLabel(labelId) ? "inbox" : "all";
|
|
186
|
+
return `/${view}?label=${encodeURIComponent(labelId)}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
174
189
|
interface AppLayoutProps {
|
|
175
190
|
children: React.ReactNode;
|
|
176
191
|
}
|
|
@@ -590,7 +605,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
590
605
|
pinnedId: id,
|
|
591
606
|
label: aliasedName,
|
|
592
607
|
fullLabel: lbl.name,
|
|
593
|
-
href:
|
|
608
|
+
href: labelTabHref(lbl.id),
|
|
594
609
|
isActive: activeLabel === lbl.id,
|
|
595
610
|
color: lbl.color,
|
|
596
611
|
type: "label",
|
|
@@ -630,7 +645,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
630
645
|
id: active.id,
|
|
631
646
|
label: aliasedName,
|
|
632
647
|
fullLabel: active.name,
|
|
633
|
-
href:
|
|
648
|
+
href: labelTabHref(active.id),
|
|
634
649
|
isActive: true,
|
|
635
650
|
color: active.color,
|
|
636
651
|
type: "label",
|
|
@@ -58,23 +58,16 @@ function googleOAuthErrorPayload(
|
|
|
58
58
|
message: string;
|
|
59
59
|
code?: string;
|
|
60
60
|
accountId?: string;
|
|
61
|
-
existingOwner?: string;
|
|
62
|
-
attemptedOwner?: string;
|
|
63
61
|
} {
|
|
64
62
|
if (
|
|
65
63
|
error instanceof OAuthAccountOwnedByOtherUserError ||
|
|
66
64
|
error?.name === "OAuthAccountOwnedByOtherUserError"
|
|
67
65
|
) {
|
|
68
66
|
const account = error.accountId || "This Google account";
|
|
69
|
-
const existingOwner = error.existingOwner || undefined;
|
|
70
|
-
const attemptedOwner = error.attemptedOwner || undefined;
|
|
71
|
-
const message = `${account} is connected to another login. Sign out, then sign in with ${account}.`;
|
|
72
67
|
return {
|
|
73
|
-
message,
|
|
68
|
+
message: `${account} is already connected to another login. Sign out, then sign in with the login that originally connected it.`,
|
|
74
69
|
code: "account_owner_mismatch",
|
|
75
70
|
accountId: error.accountId,
|
|
76
|
-
existingOwner,
|
|
77
|
-
attemptedOwner,
|
|
78
71
|
};
|
|
79
72
|
}
|
|
80
73
|
|
|
@@ -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
|
|
|
@@ -25,6 +25,10 @@ Context, reference deck, or source material that the app already provides.
|
|
|
25
25
|
5. Navigate to the new deck.
|
|
26
26
|
6. Call `add-slide` once per slide in slide order, waiting for each result.
|
|
27
27
|
|
|
28
|
+
When speaker notes are requested, put presenter-only text in each slide's
|
|
29
|
+
`notes` field on `create-deck` or `add-slide`; keep it out of the slide HTML.
|
|
30
|
+
Preserve existing notes when editing or importing a source deck.
|
|
31
|
+
|
|
28
32
|
When the UI has already created the empty deck, keep its id and rename it before
|
|
29
33
|
adding the first slide. Call `patch-deck` with a `patch-deck-fields` operation
|
|
30
34
|
whose `fields.title` is the generated title. Do not leave the pre-created deck's
|
|
@@ -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
|
|
|
@@ -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) {
|