@agent-native/core 0.161.6 → 0.161.8
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/content/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/content/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/content/shared/feature-flags.ts +15 -0
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +9 -0
- package/corpus/templates/slides/actions/patch-deck.ts +8 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +188 -66
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +102 -22
- package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +84 -2
- package/corpus/templates/slides/app/context/DeckContext.tsx +94 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +5 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +122 -8
- package/corpus/templates/videos/package.json +9 -0
- package/dist/a2a/correlation.d.ts +6 -6
- package/dist/a2a/correlation.js +8 -6
- package/dist/a2a/types.d.ts +7 -5
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/deploy/build.js +8 -0
- 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/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/call-agent.js +6 -5
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/action-filters-a2a.d.ts +6 -1
- package/dist/server/agent-chat/action-filters-a2a.js +29 -2
- package/dist/server/agent-chat/plugin-options.d.ts +6 -0
- package/dist/server/agent-chat-plugin.d.ts +2 -1
- package/dist/server/agent-chat-plugin.js +20 -4
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +3 -3
|
@@ -31,9 +31,32 @@ export default function PresenterView({
|
|
|
31
31
|
designSystem,
|
|
32
32
|
}: PresenterViewProps) {
|
|
33
33
|
const t = useT();
|
|
34
|
-
|
|
34
|
+
// `startIndex` is a raw index into the full (unfiltered) deck.slides array.
|
|
35
|
+
// Skipped slides are absent from safeSlides below, so translate it to the
|
|
36
|
+
// nearest visible slide's position within safeSlides — matching
|
|
37
|
+
// PresentationView, whose filtered currentIndex this view's `index` state
|
|
38
|
+
// otherwise mirrors via the BroadcastChannel.
|
|
39
|
+
const initialIndex = useMemo(() => {
|
|
40
|
+
const rawSlides = (Array.isArray(slides) ? slides : []).filter(Boolean);
|
|
41
|
+
if (rawSlides.length === 0) return 0;
|
|
42
|
+
const clampedRaw = Math.max(0, Math.min(startIndex, rawSlides.length - 1));
|
|
43
|
+
for (let i = clampedRaw; i < rawSlides.length; i++) {
|
|
44
|
+
if (!rawSlides[i]?.skipped) {
|
|
45
|
+
return rawSlides.slice(0, i).filter((s) => !s?.skipped).length;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (let i = clampedRaw - 1; i >= 0; i--) {
|
|
49
|
+
if (!rawSlides[i]?.skipped) {
|
|
50
|
+
return rawSlides.slice(0, i).filter((s) => !s?.skipped).length;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
}, [slides, startIndex]);
|
|
55
|
+
const [index, setIndex] = useState(initialIndex);
|
|
35
56
|
const [elapsed, setElapsed] = useState(0);
|
|
36
57
|
const channelRef = useRef<BroadcastChannel | null>(null);
|
|
58
|
+
const indexRef = useRef(index);
|
|
59
|
+
indexRef.current = index;
|
|
37
60
|
|
|
38
61
|
useEffect(() => {
|
|
39
62
|
const channel = openPresentChannel(deckId);
|
|
@@ -88,13 +111,72 @@ export default function PresenterView({
|
|
|
88
111
|
}, [goNext, goPrev]);
|
|
89
112
|
|
|
90
113
|
const safeSlides = useMemo(
|
|
91
|
-
() =>
|
|
114
|
+
() =>
|
|
115
|
+
Array.isArray(slides)
|
|
116
|
+
? slides.filter(
|
|
117
|
+
(slide): slide is Slide => Boolean(slide) && !slide.skipped,
|
|
118
|
+
)
|
|
119
|
+
: [],
|
|
92
120
|
[slides],
|
|
93
121
|
);
|
|
122
|
+
|
|
123
|
+
// One atomic effect handles both cases so they can't race each other:
|
|
124
|
+
// - A genuine deep link or deck switch (startIndex/deckId changed) reseeds
|
|
125
|
+
// from initialIndex. This route is reused across decks (see the
|
|
126
|
+
// BroadcastChannel effect above, keyed on deckId).
|
|
127
|
+
// - Otherwise, a skip toggle or reorder changed safeSlides without a new
|
|
128
|
+
// deep link. A length-only clamp would silently swap in a different
|
|
129
|
+
// slide at the same index, so follow the previously-shown slide's id to
|
|
130
|
+
// its new position, falling back to a raw clamp only when it's gone.
|
|
131
|
+
const prevDeepLinkKeyRef = useRef({ startIndex, deckId });
|
|
132
|
+
const prevSafeSlideIdsRef = useRef<string[]>(safeSlides.map((s) => s.id));
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
const prevKey = prevDeepLinkKeyRef.current;
|
|
135
|
+
const isDeepLinkChange =
|
|
136
|
+
prevKey.startIndex !== startIndex || prevKey.deckId !== deckId;
|
|
137
|
+
prevDeepLinkKeyRef.current = { startIndex, deckId };
|
|
138
|
+
|
|
139
|
+
if (isDeepLinkChange) {
|
|
140
|
+
prevSafeSlideIdsRef.current = safeSlides.map((s) => s.id);
|
|
141
|
+
setIndex(initialIndex);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Read the prior ids before overwriting the ref below — the lookup
|
|
146
|
+
// needs the slide order from before this update, not the one it's
|
|
147
|
+
// producing.
|
|
148
|
+
const activeId = prevSafeSlideIdsRef.current[indexRef.current];
|
|
149
|
+
const newIds = safeSlides.map((s) => s.id);
|
|
150
|
+
const followedIndex = activeId ? newIds.indexOf(activeId) : -1;
|
|
151
|
+
prevSafeSlideIdsRef.current = newIds;
|
|
152
|
+
setIndex(
|
|
153
|
+
followedIndex >= 0
|
|
154
|
+
? followedIndex
|
|
155
|
+
: Math.max(0, Math.min(indexRef.current, safeSlides.length - 1)),
|
|
156
|
+
);
|
|
157
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
158
|
+
}, [safeSlides, startIndex, deckId]);
|
|
159
|
+
|
|
94
160
|
const current = safeSlides[index];
|
|
95
161
|
const next = safeSlides[index + 1];
|
|
96
162
|
const notes = current?.notes?.trim();
|
|
97
163
|
|
|
164
|
+
if (safeSlides.length === 0) {
|
|
165
|
+
return (
|
|
166
|
+
// guard:allow-raw-color — matches the presenter's dedicated dark surface used throughout this file, not app chrome
|
|
167
|
+
<div className="fixed inset-0 flex items-center justify-center bg-[hsl(240,6%,6%)] text-white">
|
|
168
|
+
<button
|
|
169
|
+
type="button"
|
|
170
|
+
onClick={() => window.close()}
|
|
171
|
+
// guard:allow-raw-color — matches the presenter's dedicated dark surface used throughout this file, not app chrome
|
|
172
|
+
className="cursor-pointer rounded-lg bg-white/10 px-4 py-3 text-sm hover:bg-white/20"
|
|
173
|
+
>
|
|
174
|
+
{t("presentation.noSlides")}
|
|
175
|
+
</button>
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
98
180
|
return (
|
|
99
181
|
<div className="fixed inset-0 flex flex-col bg-[hsl(240,6%,6%)] text-white">
|
|
100
182
|
<header className="flex items-center justify-between gap-4 border-b border-white/10 px-5 py-3">
|
|
@@ -112,6 +112,8 @@ export interface Slide {
|
|
|
112
112
|
animations?: SlideAnimation[];
|
|
113
113
|
/** @deprecated Use animations instead */
|
|
114
114
|
splitByParagraph?: boolean;
|
|
115
|
+
/** Excluded from Present/Presenter mode playback, but stays in the deck. */
|
|
116
|
+
skipped?: boolean;
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
export type AnimationType = "appear" | "fade" | "slide-up" | "zoom";
|
|
@@ -237,6 +239,14 @@ interface DeckContextType {
|
|
|
237
239
|
) => void;
|
|
238
240
|
deleteSlide: (deckId: string, slideId: string) => void;
|
|
239
241
|
duplicateSlide: (deckId: string, slideId: string) => string | undefined;
|
|
242
|
+
/** Inserts a copy of arbitrary slide data after `afterSlideId`. Used for
|
|
243
|
+
* slide cut/paste, where the original may already be deleted so there is
|
|
244
|
+
* no live slide id left to duplicate from. */
|
|
245
|
+
pasteSlide: (
|
|
246
|
+
deckId: string,
|
|
247
|
+
afterSlideId: string,
|
|
248
|
+
slideFields: Omit<Slide, "id">,
|
|
249
|
+
) => string | undefined;
|
|
240
250
|
reorderSlides: (deckId: string, oldIndex: number, newIndex: number) => void;
|
|
241
251
|
setDeckSlides: (deckId: string, slides: Slide[]) => void;
|
|
242
252
|
/**
|
|
@@ -287,13 +297,54 @@ type DuplicateDeckActionResult = {
|
|
|
287
297
|
url?: string;
|
|
288
298
|
};
|
|
289
299
|
|
|
300
|
+
/** Per-slide fields `get-deck` computes for the agent (slide position/hash
|
|
301
|
+
* hints) that aren't part of the client's own `Slide` shape. Left on the
|
|
302
|
+
* fetched deck, they make every server refetch look "changed" relative to
|
|
303
|
+
* the client's slim optimistic copy — see `normalizeActionDeck`. */
|
|
304
|
+
const GET_DECK_ONLY_SLIDE_FIELDS = [
|
|
305
|
+
"slideNumber",
|
|
306
|
+
"zeroBasedIndex",
|
|
307
|
+
"contentHash",
|
|
308
|
+
] as const;
|
|
309
|
+
|
|
310
|
+
/** Deck-level fields `get-deck` computes for the agent (counts, deep links,
|
|
311
|
+
* the currently-selected slide) that aren't part of the client's own `Deck`
|
|
312
|
+
* shape. See `normalizeActionDeck`. */
|
|
313
|
+
const GET_DECK_ONLY_DECK_FIELDS = [
|
|
314
|
+
"slideCount",
|
|
315
|
+
"slideNumbering",
|
|
316
|
+
"deepLink",
|
|
317
|
+
"selectedSlideId",
|
|
318
|
+
] as const;
|
|
319
|
+
|
|
290
320
|
function normalizeActionDeck(value: unknown): Deck | null {
|
|
291
321
|
if (!value || typeof value !== "object") return null;
|
|
292
322
|
const deck = value as Partial<Deck>;
|
|
293
323
|
if (typeof deck.id !== "string") return null;
|
|
294
324
|
|
|
325
|
+
const deckRecord = deck as unknown as Record<string, unknown>;
|
|
326
|
+
const cleanedDeck = { ...deckRecord };
|
|
327
|
+
for (const field of GET_DECK_ONLY_DECK_FIELDS) delete cleanedDeck[field];
|
|
328
|
+
|
|
329
|
+
// Strip the same decorative fields from every slide, so a deck fetched from
|
|
330
|
+
// `get-deck` is structurally identical to one built by local mutations —
|
|
331
|
+
// otherwise `deckContentSignature` sees a "change" on every refetch of the
|
|
332
|
+
// open deck and spams the undo stack with no-op `replace-deck` entries.
|
|
333
|
+
const slides = Array.isArray(deck.slides)
|
|
334
|
+
? deck.slides.map((slide) => {
|
|
335
|
+
if (!slide || typeof slide !== "object") return slide;
|
|
336
|
+
const cleanedSlide = {
|
|
337
|
+
...(slide as unknown as Record<string, unknown>),
|
|
338
|
+
};
|
|
339
|
+
for (const field of GET_DECK_ONLY_SLIDE_FIELDS) {
|
|
340
|
+
delete cleanedSlide[field];
|
|
341
|
+
}
|
|
342
|
+
return cleanedSlide as unknown as Slide;
|
|
343
|
+
})
|
|
344
|
+
: [];
|
|
345
|
+
|
|
295
346
|
return {
|
|
296
|
-
...
|
|
347
|
+
...cleanedDeck,
|
|
297
348
|
id: deck.id,
|
|
298
349
|
title: typeof deck.title === "string" ? deck.title : "Untitled",
|
|
299
350
|
createdAt:
|
|
@@ -304,7 +355,7 @@ function normalizeActionDeck(value: unknown): Deck | null {
|
|
|
304
355
|
typeof deck.updatedAt === "string"
|
|
305
356
|
? deck.updatedAt
|
|
306
357
|
: deck.createdAt || "",
|
|
307
|
-
slides
|
|
358
|
+
slides,
|
|
308
359
|
} as Deck;
|
|
309
360
|
}
|
|
310
361
|
|
|
@@ -873,7 +924,14 @@ export function deriveInverseOp(
|
|
|
873
924
|
// restores exactly what changed (including clearing fields back to
|
|
874
925
|
// undefined).
|
|
875
926
|
if (!equalDeckValue(prior[key], op.fields[key])) {
|
|
876
|
-
|
|
927
|
+
let priorValue: unknown = prior[key];
|
|
928
|
+
// `skipped` is undefined on a slide that was never skipped, but
|
|
929
|
+
// `undefined` doesn't survive JSON transport to the server — its
|
|
930
|
+
// `patch-slide` handler treats an absent field as "don't touch",
|
|
931
|
+
// so the persisted deck would stay skipped after undo. `false` is
|
|
932
|
+
// equivalent for this boolean field and does survive.
|
|
933
|
+
if (key === "skipped" && priorValue === undefined) priorValue = false;
|
|
934
|
+
(priorFields as Record<string, unknown>)[key] = priorValue;
|
|
877
935
|
}
|
|
878
936
|
}
|
|
879
937
|
if (Object.keys(priorFields).length === 0) return null;
|
|
@@ -2346,6 +2404,38 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2346
2404
|
[markDeckDirty, recordUndo, setDecksLocal],
|
|
2347
2405
|
);
|
|
2348
2406
|
|
|
2407
|
+
const pasteSlide = useCallback(
|
|
2408
|
+
(deckId: string, afterSlideId: string, slideFields: Omit<Slide, "id">) => {
|
|
2409
|
+
const before = decksRef.current.find((d) => d.id === deckId);
|
|
2410
|
+
if (!before) return undefined;
|
|
2411
|
+
|
|
2412
|
+
markDeckDirty(deckId);
|
|
2413
|
+
const newSlide: Slide = { ...slideFields, id: nanoid(8) };
|
|
2414
|
+
setDecksLocal((prev) =>
|
|
2415
|
+
prev.map((d) => {
|
|
2416
|
+
if (d.id !== deckId) return d;
|
|
2417
|
+
const idx = d.slides.findIndex((s) => s.id === afterSlideId);
|
|
2418
|
+
const insertAt = idx === -1 ? d.slides.length : idx + 1;
|
|
2419
|
+
const slides = [...d.slides];
|
|
2420
|
+
slides.splice(insertAt, 0, newSlide);
|
|
2421
|
+
return { ...d, slides, updatedAt: new Date().toISOString() };
|
|
2422
|
+
}),
|
|
2423
|
+
);
|
|
2424
|
+
// Granular add-slide op, same as duplicateSlide — inserts after
|
|
2425
|
+
// afterSlideId regardless of whether that id is also the copy source.
|
|
2426
|
+
const op: PatchDeckOp = {
|
|
2427
|
+
op: "add-slide",
|
|
2428
|
+
slideId: newSlide.id,
|
|
2429
|
+
afterSlideId,
|
|
2430
|
+
fields: addSlideFields(newSlide),
|
|
2431
|
+
};
|
|
2432
|
+
enqueueDeckOp(deckId, op);
|
|
2433
|
+
recordUndo(before, op, { label: "Paste slide" });
|
|
2434
|
+
return newSlide.id;
|
|
2435
|
+
},
|
|
2436
|
+
[markDeckDirty, recordUndo, setDecksLocal],
|
|
2437
|
+
);
|
|
2438
|
+
|
|
2349
2439
|
const reorderSlides = useCallback(
|
|
2350
2440
|
(deckId: string, oldIndex: number, newIndex: number) => {
|
|
2351
2441
|
markDeckDirty(deckId);
|
|
@@ -2432,6 +2522,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2432
2522
|
updateSlide,
|
|
2433
2523
|
deleteSlide,
|
|
2434
2524
|
duplicateSlide,
|
|
2525
|
+
pasteSlide,
|
|
2435
2526
|
reorderSlides,
|
|
2436
2527
|
setDeckSlides,
|
|
2437
2528
|
markDeckDirty,
|
|
@@ -524,6 +524,11 @@ const messages = {
|
|
|
524
524
|
newSlide: "New slide",
|
|
525
525
|
closeAddSlides: "Close",
|
|
526
526
|
describeThisSlide: "Describe this slide",
|
|
527
|
+
cut: "Cut",
|
|
528
|
+
copy: "Copy",
|
|
529
|
+
paste: "Paste",
|
|
530
|
+
skipSlide: "Skip slide",
|
|
531
|
+
unskipSlide: "Unskip slide",
|
|
527
532
|
},
|
|
528
533
|
presentation: {
|
|
529
534
|
loadFailed: "Could not load this presentation.",
|
|
@@ -60,6 +60,7 @@ import { Button } from "@/components/ui/button";
|
|
|
60
60
|
import {
|
|
61
61
|
deckIdFromPathname,
|
|
62
62
|
hasUnsavedDeckChanges,
|
|
63
|
+
type Slide,
|
|
63
64
|
useDecks,
|
|
64
65
|
useSaveState,
|
|
65
66
|
} from "@/context/DeckContext";
|
|
@@ -185,6 +186,7 @@ export default function DeckEditor() {
|
|
|
185
186
|
updateSlide,
|
|
186
187
|
deleteSlide,
|
|
187
188
|
duplicateSlide,
|
|
189
|
+
pasteSlide,
|
|
188
190
|
duplicateDeck,
|
|
189
191
|
addSlide,
|
|
190
192
|
flushDeckSave,
|
|
@@ -852,11 +854,107 @@ export default function DeckEditor() {
|
|
|
852
854
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
853
855
|
}, [deck, id, activeSlideId, deleteSlideWithUndo, pinMode, drawMode]);
|
|
854
856
|
|
|
855
|
-
//
|
|
857
|
+
// Slide-level clipboard backing both the Cmd+C/Cmd+V shortcut below and the
|
|
858
|
+
// rail's right-click Cut/Copy/Paste menu. Holds a full slide snapshot
|
|
859
|
+
// (rather than just an id) so paste still works after Cut has already
|
|
860
|
+
// removed the original slide from the deck.
|
|
861
|
+
const slideClipboardRef = useRef<Slide | null>(null);
|
|
862
|
+
const [hasSlideClipboard, setHasSlideClipboard] = useState(false);
|
|
863
|
+
|
|
864
|
+
const copySlide = useCallback(
|
|
865
|
+
(slideId: string) => {
|
|
866
|
+
const slide = deck?.slides.find((s) => s.id === slideId);
|
|
867
|
+
if (!slide) return;
|
|
868
|
+
slideClipboardRef.current = slide;
|
|
869
|
+
setHasSlideClipboard(true);
|
|
870
|
+
},
|
|
871
|
+
[deck],
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
const cutSlide = useCallback(
|
|
875
|
+
(slideId: string) => {
|
|
876
|
+
if (!deck || !id || deck.slides.length <= 1) return; // don't cut the last slide
|
|
877
|
+
const slide = deck.slides.find((s) => s.id === slideId);
|
|
878
|
+
if (!slide) return;
|
|
879
|
+
slideClipboardRef.current = slide;
|
|
880
|
+
setHasSlideClipboard(true);
|
|
881
|
+
const idx = deck.slides.findIndex((s) => s.id === slideId);
|
|
882
|
+
const nextSlide = deck.slides[idx + 1] || deck.slides[idx - 1];
|
|
883
|
+
deleteSlideWithUndo(id, slideId);
|
|
884
|
+
if (activeSlideId === slideId && nextSlide) {
|
|
885
|
+
setActiveSlideId(nextSlide.id);
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
[deck, id, activeSlideId, deleteSlideWithUndo],
|
|
889
|
+
);
|
|
890
|
+
|
|
891
|
+
const pasteSlideAfter = useCallback(
|
|
892
|
+
(targetSlideId: string) => {
|
|
893
|
+
const clipboard = slideClipboardRef.current;
|
|
894
|
+
if (!clipboard || !id) return;
|
|
895
|
+
const { id: _clipboardId, ...fields } = clipboard;
|
|
896
|
+
const newId = pasteSlide(id, targetSlideId, fields);
|
|
897
|
+
if (newId) setActiveSlideId(newId);
|
|
898
|
+
},
|
|
899
|
+
[id, pasteSlide],
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
// Handlers backing the slide rail's right-click menu.
|
|
903
|
+
const handleDeleteSlideFromRail = useCallback(
|
|
904
|
+
(slideId: string) => {
|
|
905
|
+
if (!deck || !id || deck.slides.length <= 1) return; // don't delete the last slide
|
|
906
|
+
const idx = deck.slides.findIndex((s) => s.id === slideId);
|
|
907
|
+
const nextSlide = deck.slides[idx + 1] || deck.slides[idx - 1];
|
|
908
|
+
deleteSlideWithUndo(id, slideId);
|
|
909
|
+
if (activeSlideId === slideId && nextSlide) {
|
|
910
|
+
setActiveSlideId(nextSlide.id);
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
[deck, id, activeSlideId, deleteSlideWithUndo],
|
|
914
|
+
);
|
|
915
|
+
|
|
916
|
+
const handleDuplicateSlideFromRail = useCallback(
|
|
917
|
+
(slideId: string) => {
|
|
918
|
+
if (!id) return;
|
|
919
|
+
const newId = duplicateSlide(id, slideId);
|
|
920
|
+
if (newId) setActiveSlideId(newId);
|
|
921
|
+
},
|
|
922
|
+
[id, duplicateSlide],
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
const handleNewSlideAfter = useCallback(
|
|
926
|
+
(afterSlideId: string) => {
|
|
927
|
+
if (!deck || !id) return;
|
|
928
|
+
const afterIdx = deck.slides.findIndex((s) => s.id === afterSlideId);
|
|
929
|
+
// Immediate persistence: mirrors handleAddEmptySlide, since this also
|
|
930
|
+
// opens the "describe this slide" popover right away.
|
|
931
|
+
const newId = addSlide(
|
|
932
|
+
id,
|
|
933
|
+
"blank",
|
|
934
|
+
afterIdx >= 0 ? afterIdx : undefined,
|
|
935
|
+
{ persistence: "immediate" },
|
|
936
|
+
);
|
|
937
|
+
setActiveSlideId(newId);
|
|
938
|
+
setSidebarOpen(true);
|
|
939
|
+
setDescribeSlideId(newId);
|
|
940
|
+
},
|
|
941
|
+
[deck, id, addSlide],
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
const handleToggleSkipSlide = useCallback(
|
|
945
|
+
(slideId: string) => {
|
|
946
|
+
if (!deck || !id) return;
|
|
947
|
+
const slide = deck.slides.find((s) => s.id === slideId);
|
|
948
|
+
if (!slide) return;
|
|
949
|
+
updateSlide(id, slideId, { skipped: !slide.skipped });
|
|
950
|
+
},
|
|
951
|
+
[deck, id, updateSlide],
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
// Command/Ctrl+C then Command/Ctrl+V on the slide rail copies/pastes the
|
|
856
955
|
// selected slide directly below itself. Only claims the shortcut when no
|
|
857
956
|
// slide element is selected — SlideEditor owns Cmd+C/V for object copy/paste
|
|
858
957
|
// in that case.
|
|
859
|
-
const copiedSlideIdRef = useRef<string | null>(null);
|
|
860
958
|
useEffect(() => {
|
|
861
959
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
862
960
|
if (!deck || !id || !canEdit) return;
|
|
@@ -925,19 +1023,27 @@ export default function DeckEditor() {
|
|
|
925
1023
|
|
|
926
1024
|
if (key === "c") {
|
|
927
1025
|
if (!activeSlideId) return;
|
|
928
|
-
|
|
1026
|
+
copySlide(activeSlideId);
|
|
929
1027
|
return;
|
|
930
1028
|
}
|
|
931
1029
|
|
|
932
|
-
|
|
933
|
-
if (!copiedId || !deck.slides.some((s) => s.id === copiedId)) return;
|
|
1030
|
+
if (!hasSlideClipboard || !activeSlideId) return;
|
|
934
1031
|
e.preventDefault();
|
|
935
|
-
|
|
936
|
-
if (newId) setActiveSlideId(newId);
|
|
1032
|
+
pasteSlideAfter(activeSlideId);
|
|
937
1033
|
};
|
|
938
1034
|
document.addEventListener("keydown", handleKeyDown);
|
|
939
1035
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
940
|
-
}, [
|
|
1036
|
+
}, [
|
|
1037
|
+
deck,
|
|
1038
|
+
id,
|
|
1039
|
+
canEdit,
|
|
1040
|
+
activeSlideId,
|
|
1041
|
+
copySlide,
|
|
1042
|
+
hasSlideClipboard,
|
|
1043
|
+
pasteSlideAfter,
|
|
1044
|
+
pinMode,
|
|
1045
|
+
drawMode,
|
|
1046
|
+
]);
|
|
941
1047
|
|
|
942
1048
|
// Resolve the active slide from URL/deck state. Imports replace slide IDs, so
|
|
943
1049
|
// keep this valid after deck contents change instead of only on first load.
|
|
@@ -1371,6 +1477,14 @@ export default function DeckEditor() {
|
|
|
1371
1477
|
setGeneratingSlideSelected(true);
|
|
1372
1478
|
if (window.innerWidth < 768) setSidebarOpen(false);
|
|
1373
1479
|
}}
|
|
1480
|
+
hasSlideClipboard={hasSlideClipboard}
|
|
1481
|
+
onCutSlide={cutSlide}
|
|
1482
|
+
onCopySlide={copySlide}
|
|
1483
|
+
onPasteSlide={pasteSlideAfter}
|
|
1484
|
+
onDeleteSlide={handleDeleteSlideFromRail}
|
|
1485
|
+
onNewSlideAfter={handleNewSlideAfter}
|
|
1486
|
+
onDuplicateSlide={handleDuplicateSlideFromRail}
|
|
1487
|
+
onToggleSkipSlide={handleToggleSkipSlide}
|
|
1374
1488
|
/>
|
|
1375
1489
|
</DndContext>
|
|
1376
1490
|
</div>
|
|
@@ -3,11 +3,11 @@ export declare const MAX_A2A_CORRELATION_VALUE_CHARS = 200;
|
|
|
3
3
|
export declare const MAX_A2A_DELEGATION_HOPS = 3;
|
|
4
4
|
export declare function sanitizeA2ACorrelationId(value: unknown): string | undefined;
|
|
5
5
|
/**
|
|
6
|
-
* Keep only bounded, opaque ASCII correlation identifiers.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* Keep only bounded, opaque ASCII correlation and routing identifiers.
|
|
7
|
+
* Authentication continues to come exclusively from the verified A2A
|
|
8
|
+
* token/request context. A receiver may use `selectedReceiverApp` only to
|
|
9
|
+
* prioritize its matching local tool surface, and `callerModel` only to choose
|
|
10
|
+
* a model its own engine already offers. Neither reaches identity, data
|
|
11
|
+
* ownership, org, access, or approval resolution.
|
|
12
12
|
*/
|
|
13
13
|
export declare function sanitizeA2ACorrelationMetadata(value: unknown): A2ACorrelationMetadata;
|
package/dist/a2a/correlation.js
CHANGED
|
@@ -19,18 +19,19 @@ export function sanitizeA2ACorrelationId(value) {
|
|
|
19
19
|
return boundedIdentifier(value, CORRELATION_ID_PATTERN);
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* Keep only bounded, opaque ASCII correlation identifiers.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* Keep only bounded, opaque ASCII correlation and routing identifiers.
|
|
23
|
+
* Authentication continues to come exclusively from the verified A2A
|
|
24
|
+
* token/request context. A receiver may use `selectedReceiverApp` only to
|
|
25
|
+
* prioritize its matching local tool surface, and `callerModel` only to choose
|
|
26
|
+
* a model its own engine already offers. Neither reaches identity, data
|
|
27
|
+
* ownership, org, access, or approval resolution.
|
|
28
28
|
*/
|
|
29
29
|
export function sanitizeA2ACorrelationMetadata(value) {
|
|
30
30
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
31
31
|
return {};
|
|
32
32
|
const metadata = value;
|
|
33
33
|
const callerApp = boundedIdentifier(metadata.callerApp, APP_ID_PATTERN);
|
|
34
|
+
const selectedReceiverApp = boundedIdentifier(metadata.selectedReceiverApp, APP_ID_PATTERN);
|
|
34
35
|
const callerThreadId = sanitizeA2ACorrelationId(metadata.callerThreadId);
|
|
35
36
|
const parentRunId = sanitizeA2ACorrelationId(metadata.parentRunId);
|
|
36
37
|
const parentTurnId = sanitizeA2ACorrelationId(metadata.parentTurnId);
|
|
@@ -62,6 +63,7 @@ export function sanitizeA2ACorrelationMetadata(value) {
|
|
|
62
63
|
: undefined;
|
|
63
64
|
return {
|
|
64
65
|
...(callerApp ? { callerApp } : {}),
|
|
66
|
+
...(selectedReceiverApp ? { selectedReceiverApp } : {}),
|
|
65
67
|
...(callerThreadId ? { callerThreadId } : {}),
|
|
66
68
|
...(parentRunId ? { parentRunId } : {}),
|
|
67
69
|
...(parentTurnId ? { parentTurnId } : {}),
|
package/dist/a2a/types.d.ts
CHANGED
|
@@ -117,14 +117,16 @@ export interface A2ASourceContextReference {
|
|
|
117
117
|
integrationTaskId: string;
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
120
|
+
* Bounded cross-app correlation and routing preferences. Receivers must never
|
|
121
|
+
* use any caller-supplied value here for identity, data ownership, org scoping,
|
|
122
|
+
* access, or approval decisions. `selectedReceiverApp` may only prioritize the
|
|
123
|
+
* matching receiver's local tool surface, while `callerModel` may only pick a
|
|
124
|
+
* model the receiver's already-resolved engine advertises.
|
|
125
125
|
*/
|
|
126
126
|
export interface A2ACorrelationMetadata {
|
|
127
127
|
callerApp?: string;
|
|
128
|
+
/** App the caller deliberately selected for this delegated objective. */
|
|
129
|
+
selectedReceiverApp?: string;
|
|
128
130
|
callerThreadId?: string;
|
|
129
131
|
parentRunId?: string;
|
|
130
132
|
parentTurnId?: string;
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
states: {
|
|
66
67
|
clientId: number;
|
|
67
68
|
state: string;
|
|
68
69
|
}[];
|
|
69
|
-
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
+
error?: undefined;
|
|
80
81
|
users: {
|
|
81
82
|
clientId: number;
|
|
82
83
|
lastSeen: number;
|
|
83
84
|
}[];
|
|
84
|
-
error?: undefined;
|
|
85
85
|
}>>;
|
package/dist/deploy/build.js
CHANGED
|
@@ -268,6 +268,14 @@ export const CLOUDFLARE_WORKER_STUB_SUBPATH_MODULES = {
|
|
|
268
268
|
"export default { CanvasFactory, getData };",
|
|
269
269
|
"",
|
|
270
270
|
].join("\n"),
|
|
271
|
+
"pdfjs-dist/legacy/build/pdf.mjs": [
|
|
272
|
+
"const unavailable = () => { throw new Error('pdfjs-dist unavailable in Cloudflare Pages worker'); };",
|
|
273
|
+
"export const OPS = new Proxy({}, { get: unavailable });",
|
|
274
|
+
"export const Util = new Proxy({}, { get: unavailable });",
|
|
275
|
+
"export const getDocument = unavailable;",
|
|
276
|
+
"export default { OPS, Util, getDocument };",
|
|
277
|
+
"",
|
|
278
|
+
].join("\n"),
|
|
271
279
|
};
|
|
272
280
|
export function cloudflareWorkerStubAliasArgs(stubDir) {
|
|
273
281
|
const subpathAliases = Object.keys(CLOUDFLARE_WORKER_STUB_SUBPATH_MODULES)
|
|
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
error?: undefined;
|
|
45
|
-
ok?: undefined;
|
|
46
44
|
summary: import("./types.js").TraceSummary;
|
|
47
45
|
spans: import("./types.js").TraceSpan[];
|
|
48
46
|
id?: undefined;
|
|
49
|
-
} | {
|
|
50
47
|
error?: undefined;
|
|
51
48
|
ok?: undefined;
|
|
49
|
+
} | {
|
|
52
50
|
summary?: undefined;
|
|
53
51
|
spans?: undefined;
|
|
54
52
|
id: string;
|
|
55
|
-
|
|
53
|
+
error?: undefined;
|
|
56
54
|
ok?: undefined;
|
|
55
|
+
} | {
|
|
57
56
|
summary?: undefined;
|
|
58
57
|
spans?: undefined;
|
|
59
58
|
id?: undefined;
|
|
60
59
|
error: any;
|
|
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
|
}>>;
|