@agent-native/core 0.149.3 → 0.149.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +23 -4
- package/corpus/templates/slides/actions/get-deck.ts +25 -9
- package/corpus/templates/slides/actions/patch-deck.ts +51 -0
- package/corpus/templates/slides/actions/update-slide.ts +144 -14
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +12 -4
- package/corpus/templates/slides/changelog/2026-08-11-pdf-requests-to-restyle-and-preserve-a-source-deck-now-impor.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slide-edits-preserve-their-layout-without-introducing-hidden.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slides-reliably-imports-uploaded-pdfs-and-presentations.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slides-support-atomic-code-style-html-patches-without-regene.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/server/lib/slide-content-patch.ts +355 -0
- package/corpus/templates/slides/server/lib/uploaded-reference-storage.ts +28 -9
- package/corpus/templates/slides/server/plugins/agent-chat.ts +12 -3
- package/dist/cli/create.js +49 -0
- package/dist/client/chat-first/index.d.ts +1 -1
- package/dist/client/chat-first/index.js +1 -1
- package/dist/client/chat-first/primary-nav.d.ts +3 -1
- package/dist/client/chat-first/primary-nav.js +8 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +4 -0
- package/dist/deploy/build.js +5 -0
- package/dist/deploy/workspace-deploy.js +5 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/private-blob/registry.js +67 -4
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/templates/default/pnpm-workspace.yaml +44 -0
- package/dist/templates/workspace-root/pnpm-workspace.yaml +44 -0
- package/package.json +1 -1
- package/src/templates/default/pnpm-workspace.yaml +44 -0
- package/src/templates/workspace-root/pnpm-workspace.yaml +44 -0
package/corpus/README.md
CHANGED
|
@@ -72,10 +72,17 @@ To edit a slide's content:
|
|
|
72
72
|
3. **Modify the content** HTML string for the intended slide. Preserve an
|
|
73
73
|
approved native template or component when it already fits; generate
|
|
74
74
|
net-new structure only when the relevant corpus is empty.
|
|
75
|
-
4. **Update the slide** with
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
4. **Update the slide** with `update-slide` using `deckId`, `slideId`, and
|
|
76
|
+
ordered `edits`. For code-style work, first read with `get-deck` using the
|
|
77
|
+
target `slideId`, `compact=false`, and `format=true`, then send the returned
|
|
78
|
+
`contentHash` as `baseContentHash`. Use exact replace, insert before/after,
|
|
79
|
+
replace between markers, or regex replace. Set `expectedMatches` whenever a
|
|
80
|
+
marker could be ambiguous. All edits are applied in memory under the deck
|
|
81
|
+
lock; if one required edit fails, nothing is written. Set `format=true` on
|
|
82
|
+
`update-slide` to persist readable Prettier line breaks. Use `fullContent`
|
|
83
|
+
only for an intentional full rewrite - do not regenerate a slide to make a
|
|
84
|
+
small change. Do not write deck rows directly or add raw full-deck writes;
|
|
85
|
+
use `patch-deck` for browser/editor changes.
|
|
79
86
|
5. For browser/editor code, enqueue granular deck operations through
|
|
80
87
|
`patch-deck` / `DeckContext.tsx` instead of replacing the whole deck JSON.
|
|
81
88
|
|
|
@@ -83,6 +90,18 @@ To edit a slide's content:
|
|
|
83
90
|
preserve quote, speaker, date, metric, and uncertainty status. Existing HTML
|
|
84
91
|
or visual similarity is not proof of source fidelity.
|
|
85
92
|
|
|
93
|
+
## Click-to-reveal animations
|
|
94
|
+
|
|
95
|
+
Animations are metadata over the final slide HTML, not alternate slide markup.
|
|
96
|
+
Read the full target slide, keep its existing visual structure, and patch the
|
|
97
|
+
complete ordered `animations` list with `elementPath` values from that exact
|
|
98
|
+
HTML. Elements omitted from the list remain visible immediately, so labels and
|
|
99
|
+
headings need no duplicate markup. Do not add hidden duplicates, layout
|
|
100
|
+
spacers, absolute-positioned copies, transforms, or placeholder content to
|
|
101
|
+
simulate reveals. When content and reveals change together, send both fields in
|
|
102
|
+
one `patch-deck` operation. To remove reveals, send `animations: []` with the
|
|
103
|
+
existing content and verify the persisted slide afterward.
|
|
104
|
+
|
|
86
105
|
If retrieval produces a new immutable context pack, keep its `contextPackId`
|
|
87
106
|
and reuse labels with the deck provenance. Existing slide HTML is not proof of
|
|
88
107
|
which source version influenced it.
|
|
@@ -4,7 +4,9 @@ import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
|
4
4
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
|
+
import { formatSlideHtml } from "../server/lib/slide-content-patch.js";
|
|
7
8
|
import { summarizeSlideAnimationTargets } from "../server/lib/validate-slide-animations.js";
|
|
9
|
+
import { hashSlideContent } from "../shared/slide-fit.js";
|
|
8
10
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
9
11
|
|
|
10
12
|
function stripHtml(html: string): string {
|
|
@@ -76,6 +78,12 @@ export default defineAction({
|
|
|
76
78
|
.describe(
|
|
77
79
|
"Set to 'true' for compact slide summaries, or 'false' for full slide HTML. In-app agent calls without slideId default to compact output.",
|
|
78
80
|
),
|
|
81
|
+
format: z
|
|
82
|
+
.enum(["true", "false"])
|
|
83
|
+
.optional()
|
|
84
|
+
.describe(
|
|
85
|
+
"Set to 'true' to return full slide HTML formatted with Prettier for code-style patches. The contentHash still identifies the persisted source.",
|
|
86
|
+
),
|
|
79
87
|
}),
|
|
80
88
|
http: { method: "GET" },
|
|
81
89
|
mcpApp: {
|
|
@@ -169,6 +177,22 @@ export default defineAction({
|
|
|
169
177
|
const deckMetadata = { ...data };
|
|
170
178
|
delete deckMetadata.slides;
|
|
171
179
|
|
|
180
|
+
const formatHtml = args.format === "true";
|
|
181
|
+
const fullSlides = await Promise.all(
|
|
182
|
+
slideEntries.map(async ({ slide: s, index: i }) => ({
|
|
183
|
+
...s,
|
|
184
|
+
slideNumber: i + 1,
|
|
185
|
+
zeroBasedIndex: i,
|
|
186
|
+
id: s.id,
|
|
187
|
+
layout: s.layout ?? null,
|
|
188
|
+
content: formatHtml
|
|
189
|
+
? await formatSlideHtml(String(s.content ?? ""))
|
|
190
|
+
: s.content,
|
|
191
|
+
contentHash: hashSlideContent(String(s.content ?? "")),
|
|
192
|
+
notes: s.notes ?? null,
|
|
193
|
+
})),
|
|
194
|
+
);
|
|
195
|
+
|
|
172
196
|
return {
|
|
173
197
|
...deckMetadata,
|
|
174
198
|
id: row.id,
|
|
@@ -184,15 +208,7 @@ export default defineAction({
|
|
|
184
208
|
updatedAt: row.updatedAt,
|
|
185
209
|
deepLink: deckDeepLink(row.id),
|
|
186
210
|
...(selectedSlide ? { selectedSlideId: selectedSlide.id } : {}),
|
|
187
|
-
slides:
|
|
188
|
-
...s,
|
|
189
|
-
slideNumber: i + 1,
|
|
190
|
-
zeroBasedIndex: i,
|
|
191
|
-
id: s.id,
|
|
192
|
-
layout: s.layout ?? null,
|
|
193
|
-
content: s.content,
|
|
194
|
-
notes: s.notes ?? null,
|
|
195
|
-
})),
|
|
211
|
+
slides: fullSlides,
|
|
196
212
|
};
|
|
197
213
|
},
|
|
198
214
|
link: ({ result, args }) => {
|
|
@@ -434,6 +434,52 @@ export function applyOperation(deck: any, op: Operation): void {
|
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Agent content rewrites must not inherit click-reveal paths implicitly. A
|
|
439
|
+
* caller that wants to revise both HTML and reveals sends `animations` in the
|
|
440
|
+
* same patch, including the complete ordered list. Source-preserving edits are
|
|
441
|
+
* the exception: they retain imported reveal metadata unless the caller opts
|
|
442
|
+
* into an explicit source rewrite.
|
|
443
|
+
*/
|
|
444
|
+
export function clearOmittedAnimationsForAgentContentPatches(
|
|
445
|
+
deck: any,
|
|
446
|
+
operations: readonly Operation[],
|
|
447
|
+
options?: { sourceImport?: SourceImportMetadata | null },
|
|
448
|
+
): void {
|
|
449
|
+
const explicitAnimationSlideIds = new Set(
|
|
450
|
+
operations.flatMap((operation) =>
|
|
451
|
+
operation.op === "patch-slide" &&
|
|
452
|
+
operation.fields.animations !== undefined
|
|
453
|
+
? [operation.slideId]
|
|
454
|
+
: [],
|
|
455
|
+
),
|
|
456
|
+
);
|
|
457
|
+
const sourceSlideIds = new Set(
|
|
458
|
+
options?.sourceImport?.slideIds ??
|
|
459
|
+
options?.sourceImport?.slides.map((slide) => slide.id) ??
|
|
460
|
+
[],
|
|
461
|
+
);
|
|
462
|
+
const slideIds = new Set(
|
|
463
|
+
operations.flatMap((operation) =>
|
|
464
|
+
operation.op === "patch-slide" &&
|
|
465
|
+
operation.fields.content !== undefined &&
|
|
466
|
+
operation.fields.animations === undefined &&
|
|
467
|
+
!explicitAnimationSlideIds.has(operation.slideId) &&
|
|
468
|
+
(operation.preserveSource === false ||
|
|
469
|
+
!sourceSlideIds.has(operation.slideId))
|
|
470
|
+
? [operation.slideId]
|
|
471
|
+
: [],
|
|
472
|
+
),
|
|
473
|
+
);
|
|
474
|
+
if (!slideIds.size) return;
|
|
475
|
+
|
|
476
|
+
for (const slide of Array.isArray(deck.slides) ? deck.slides : []) {
|
|
477
|
+
if (slideIds.has(slide?.id) && Array.isArray(slide.animations)) {
|
|
478
|
+
delete slide.animations;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
437
483
|
/**
|
|
438
484
|
* Content and animation metadata are one contract. Validate only slides whose
|
|
439
485
|
* content or animation list changed so unrelated note/title writes do not
|
|
@@ -620,6 +666,11 @@ export default defineAction({
|
|
|
620
666
|
for (const op of operations) {
|
|
621
667
|
applyOperation(deck, op);
|
|
622
668
|
}
|
|
669
|
+
if (isAgentCaller) {
|
|
670
|
+
clearOmittedAnimationsForAgentContentPatches(deck, operations, {
|
|
671
|
+
sourceImport,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
623
674
|
assertPatchedSlideAnimationsResolve(deck, operations, {
|
|
624
675
|
requireElementPaths: isAgentCaller,
|
|
625
676
|
});
|
|
@@ -16,6 +16,11 @@ import { normalizeSlidePadding } from "../app/lib/normalize-slide-padding.js";
|
|
|
16
16
|
import { getDb, schema } from "../server/db/index.js"; // ensure registerShareableResource runs
|
|
17
17
|
import { notifyClients } from "../server/handlers/decks.js";
|
|
18
18
|
import { createDeckVersionSnapshot } from "../server/lib/deck-versions.js";
|
|
19
|
+
import {
|
|
20
|
+
applySlideContentEdits,
|
|
21
|
+
formatSlideHtml,
|
|
22
|
+
type SlideContentEdit,
|
|
23
|
+
} from "../server/lib/slide-content-patch.js";
|
|
19
24
|
import {
|
|
20
25
|
assertSourceSlidePreserved,
|
|
21
26
|
sourceImportForDeck,
|
|
@@ -92,8 +97,7 @@ function storedCreativeContext(value: unknown): {
|
|
|
92
97
|
|
|
93
98
|
export default defineAction({
|
|
94
99
|
description:
|
|
95
|
-
"
|
|
96
|
-
"Syncs live to open editors. Prefer this over full deck rewrites. Source-imported slides preserve their original images and factual copy by default.",
|
|
100
|
+
"Atomically patch a slide's HTML like a code editor: send several exact edits against the current source, optionally format it with Prettier, and sync the result live to open editors. Prefer edits over fullContent so unrelated markup is not regenerated. Use baseContentHash from get-deck to reject stale patches. Content edits clear existing click-reveal metadata; use patch-deck with the complete animations list when the edit intentionally changes both content and reveals. Source-imported slides preserve their original images and factual copy by default.",
|
|
97
101
|
schema: z.object({
|
|
98
102
|
deckId: z.string().describe("Deck ID"),
|
|
99
103
|
slideId: z.string().describe("Slide ID"),
|
|
@@ -109,6 +113,64 @@ export default defineAction({
|
|
|
109
113
|
.string()
|
|
110
114
|
.optional()
|
|
111
115
|
.describe("Full HTML to replace entire slide content"),
|
|
116
|
+
edits: z
|
|
117
|
+
.array(
|
|
118
|
+
z.union([
|
|
119
|
+
z.object({
|
|
120
|
+
op: z.literal("replace").optional(),
|
|
121
|
+
find: z.string(),
|
|
122
|
+
replace: z.string(),
|
|
123
|
+
all: z.boolean().optional(),
|
|
124
|
+
occurrence: z.number().int().positive().optional(),
|
|
125
|
+
expectedMatches: z.number().int().nonnegative().optional(),
|
|
126
|
+
required: z.boolean().optional(),
|
|
127
|
+
}),
|
|
128
|
+
z.object({
|
|
129
|
+
op: z.enum(["insert-before", "insert-after"]),
|
|
130
|
+
marker: z.string(),
|
|
131
|
+
content: z.string(),
|
|
132
|
+
occurrence: z.number().int().positive().optional(),
|
|
133
|
+
expectedMatches: z.number().int().nonnegative().optional(),
|
|
134
|
+
required: z.boolean().optional(),
|
|
135
|
+
}),
|
|
136
|
+
z.object({
|
|
137
|
+
op: z.literal("replace-between"),
|
|
138
|
+
start: z.string(),
|
|
139
|
+
end: z.string(),
|
|
140
|
+
content: z.string(),
|
|
141
|
+
includeDelimiters: z.boolean().optional(),
|
|
142
|
+
expectedMatches: z.number().int().nonnegative().optional(),
|
|
143
|
+
required: z.boolean().optional(),
|
|
144
|
+
}),
|
|
145
|
+
z.object({
|
|
146
|
+
op: z.literal("regex-replace"),
|
|
147
|
+
pattern: z.string(),
|
|
148
|
+
replace: z.string(),
|
|
149
|
+
flags: z.string().optional(),
|
|
150
|
+
all: z.boolean().optional(),
|
|
151
|
+
expectedMatches: z.number().int().nonnegative().optional(),
|
|
152
|
+
required: z.boolean().optional(),
|
|
153
|
+
}),
|
|
154
|
+
]),
|
|
155
|
+
)
|
|
156
|
+
.min(1)
|
|
157
|
+
.optional()
|
|
158
|
+
.describe(
|
|
159
|
+
"Ordered atomic edits against the current HTML. Each edit must match unless required=false. Use expectedMatches to make ambiguity explicit.",
|
|
160
|
+
),
|
|
161
|
+
baseContentHash: z
|
|
162
|
+
.string()
|
|
163
|
+
.optional()
|
|
164
|
+
.describe(
|
|
165
|
+
"Hash returned by get-deck for the exact slide source being patched. The edit is rejected if the source changed since it was read.",
|
|
166
|
+
),
|
|
167
|
+
format: z
|
|
168
|
+
.boolean()
|
|
169
|
+
.optional()
|
|
170
|
+
.default(false)
|
|
171
|
+
.describe(
|
|
172
|
+
"Format the resulting HTML with Prettier before persisting it.",
|
|
173
|
+
),
|
|
112
174
|
preserveSource: z
|
|
113
175
|
.boolean()
|
|
114
176
|
.optional()
|
|
@@ -142,13 +204,24 @@ export default defineAction({
|
|
|
142
204
|
find,
|
|
143
205
|
replace,
|
|
144
206
|
fullContent,
|
|
207
|
+
edits,
|
|
208
|
+
baseContentHash,
|
|
209
|
+
format,
|
|
145
210
|
preserveSource,
|
|
146
211
|
contextPackId,
|
|
147
212
|
contextModeOverride,
|
|
148
213
|
reuseLabels,
|
|
149
214
|
} = args;
|
|
150
|
-
if (!find &&
|
|
151
|
-
throw new Error("
|
|
215
|
+
if (!edits && find === undefined && fullContent === undefined) {
|
|
216
|
+
throw new Error("One of --edits, --find, or --fullContent is required");
|
|
217
|
+
}
|
|
218
|
+
if (find !== undefined && find.length === 0) {
|
|
219
|
+
throw new Error("find must not be empty for legacy search/replace");
|
|
220
|
+
}
|
|
221
|
+
if (edits && (find !== undefined || fullContent !== undefined)) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
"Use --edits instead of combining it with --find or --fullContent",
|
|
224
|
+
);
|
|
152
225
|
}
|
|
153
226
|
await assertAccess("deck", deckId, "editor");
|
|
154
227
|
|
|
@@ -203,6 +276,15 @@ export default defineAction({
|
|
|
203
276
|
throw new Error(`Slide ${slideId} not found in deck ${deckId}`);
|
|
204
277
|
}
|
|
205
278
|
|
|
279
|
+
if (
|
|
280
|
+
baseContentHash !== undefined &&
|
|
281
|
+
hashSlideContent(String(slide.content ?? "")) !== baseContentHash
|
|
282
|
+
) {
|
|
283
|
+
throw new Error(
|
|
284
|
+
"Slide content changed since it was read. Call get-deck with this slideId again and rebase the patch.",
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
206
288
|
// ─── Apply the edit to the slide content in decks.data ────────────────
|
|
207
289
|
//
|
|
208
290
|
// The agent edits the canonical slide HTML stored in `decks.data` (SQL is
|
|
@@ -215,8 +297,9 @@ export default defineAction({
|
|
|
215
297
|
// structure renders and merges with concurrent typing via the Yjs CRDT.
|
|
216
298
|
let applied = false;
|
|
217
299
|
let notFound = false;
|
|
300
|
+
const previousContent = String(slide.content ?? "");
|
|
218
301
|
|
|
219
|
-
if (fullContent) {
|
|
302
|
+
if (fullContent !== undefined) {
|
|
220
303
|
const nextContent = normalizeSlidePadding(fullContent);
|
|
221
304
|
assertSourceSlidePreserved({
|
|
222
305
|
metadata: sourceImportForDeck(deck.sourceImport),
|
|
@@ -225,16 +308,35 @@ export default defineAction({
|
|
|
225
308
|
preserveSource,
|
|
226
309
|
});
|
|
227
310
|
slide.content = nextContent;
|
|
228
|
-
applied =
|
|
229
|
-
} else if (
|
|
230
|
-
const
|
|
311
|
+
applied = nextContent !== previousContent;
|
|
312
|
+
} else if (edits) {
|
|
313
|
+
const sourceContent = format
|
|
314
|
+
? await formatSlideHtml(previousContent)
|
|
315
|
+
: previousContent;
|
|
316
|
+
const patched = await applySlideContentEdits(
|
|
317
|
+
sourceContent,
|
|
318
|
+
edits as SlideContentEdit[],
|
|
319
|
+
format,
|
|
320
|
+
);
|
|
321
|
+
const nextContent = normalizeSlidePadding(patched.content);
|
|
322
|
+
assertSourceSlidePreserved({
|
|
323
|
+
metadata: sourceImportForDeck(deck.sourceImport),
|
|
324
|
+
slideId,
|
|
325
|
+
nextContent,
|
|
326
|
+
preserveSource,
|
|
327
|
+
});
|
|
328
|
+
slide.content = nextContent;
|
|
329
|
+
applied = patched.changed;
|
|
330
|
+
if (!applied) slide.content = previousContent;
|
|
331
|
+
} else if (find !== undefined) {
|
|
332
|
+
const idx = previousContent.indexOf(find);
|
|
231
333
|
if (idx === -1) {
|
|
232
334
|
notFound = true;
|
|
233
335
|
} else {
|
|
234
336
|
const nextContent =
|
|
235
|
-
|
|
337
|
+
previousContent.slice(0, idx) +
|
|
236
338
|
(replace ?? "") +
|
|
237
|
-
|
|
339
|
+
previousContent.slice(idx + find.length);
|
|
238
340
|
assertSourceSlidePreserved({
|
|
239
341
|
metadata: sourceImportForDeck(deck.sourceImport),
|
|
240
342
|
slideId,
|
|
@@ -242,10 +344,19 @@ export default defineAction({
|
|
|
242
344
|
preserveSource,
|
|
243
345
|
});
|
|
244
346
|
slide.content = nextContent;
|
|
245
|
-
applied =
|
|
347
|
+
applied = nextContent !== previousContent;
|
|
246
348
|
}
|
|
247
349
|
}
|
|
248
350
|
|
|
351
|
+
// Animation targets are paths into the persisted HTML. A content edit
|
|
352
|
+
// can keep every path valid while changing which visual element lives at
|
|
353
|
+
// that path, so preserving the old list would reveal the wrong content.
|
|
354
|
+
// patch-deck is the explicit escape hatch when content and animations
|
|
355
|
+
// are intentionally revised together.
|
|
356
|
+
if (applied && Array.isArray(slide.animations)) {
|
|
357
|
+
delete slide.animations;
|
|
358
|
+
}
|
|
359
|
+
|
|
249
360
|
// ─── Persist to SQL ───────────────────────────────────────────────────
|
|
250
361
|
//
|
|
251
362
|
// The fresh `updatedAt` (on both the deck JSON and the row) is the signal
|
|
@@ -352,23 +463,41 @@ export default defineAction({
|
|
|
352
463
|
notFound,
|
|
353
464
|
slide,
|
|
354
465
|
slideIndex,
|
|
466
|
+
contentHash: hashSlideContent(String(slide.content ?? "")),
|
|
355
467
|
contextMode,
|
|
356
468
|
contextPackId: validated.contextPackId,
|
|
357
469
|
reuseLabels: slideReuseLabels,
|
|
358
470
|
};
|
|
359
471
|
}
|
|
360
472
|
|
|
361
|
-
return {
|
|
473
|
+
return {
|
|
474
|
+
applied,
|
|
475
|
+
notFound,
|
|
476
|
+
slide,
|
|
477
|
+
slideIndex,
|
|
478
|
+
contentHash: hashSlideContent(String(slide.content ?? "")),
|
|
479
|
+
};
|
|
362
480
|
});
|
|
363
481
|
|
|
364
482
|
if (rmw.notFound) {
|
|
365
483
|
return {
|
|
366
484
|
ok: false,
|
|
367
|
-
message: `Text not found in slide: "${find!.slice(0, 60)}". Use get-deck with
|
|
485
|
+
message: `Text not found in slide: "${find!.slice(0, 60)}". Use get-deck with this slideId to see the current slide HTML.`,
|
|
368
486
|
};
|
|
369
487
|
}
|
|
370
488
|
|
|
371
489
|
const { applied } = rmw;
|
|
490
|
+
if (!applied) {
|
|
491
|
+
return {
|
|
492
|
+
ok: true,
|
|
493
|
+
deckId,
|
|
494
|
+
slideId,
|
|
495
|
+
applied: false,
|
|
496
|
+
contentHash: rmw.contentHash,
|
|
497
|
+
deepLink: deckDeepLink(deckId),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
372
501
|
// Start the freshness window after the SQL write and before notifying the
|
|
373
502
|
// editor. This keeps a fast render from being rejected as stale.
|
|
374
503
|
const fitSince = Date.now();
|
|
@@ -390,7 +519,7 @@ export default defineAction({
|
|
|
390
519
|
notifyClients(deckId, { slideId, actor: "agent" });
|
|
391
520
|
|
|
392
521
|
console.log(
|
|
393
|
-
`update-slide: deck=${deckId} slide=${slideId} ${find ? `find="${find.slice(0, 40)}"` : "fullContent"} applied=${applied}`,
|
|
522
|
+
`update-slide: deck=${deckId} slide=${slideId} ${edits ? `edits=${edits.length}` : find !== undefined ? `find="${find.slice(0, 40)}"` : "fullContent"} applied=${applied}`,
|
|
394
523
|
);
|
|
395
524
|
|
|
396
525
|
// Wait briefly for the editor to re-render and measure. If the patched
|
|
@@ -408,6 +537,7 @@ export default defineAction({
|
|
|
408
537
|
deckId,
|
|
409
538
|
slideId,
|
|
410
539
|
applied,
|
|
540
|
+
contentHash: rmw.contentHash,
|
|
411
541
|
deepLink: deckDeepLink(deckId),
|
|
412
542
|
...(rmw.contextMode
|
|
413
543
|
? {
|
|
@@ -108,11 +108,19 @@ export function isSourceImprovementRequest(
|
|
|
108
108
|
/\bmake\b[\s\S]{0,40}\b(better|modern|professional|polished|prett\w*)\b/.test(
|
|
109
109
|
normalized,
|
|
110
110
|
);
|
|
111
|
+
const asksToPreserveSource =
|
|
112
|
+
/(?:\b(turn|convert|transform)\b[\s\S]{0,50}\b(into|to)\b[\s\S]{0,30}\b(deck|presentation|slides?)\b|\b(create|build|make|generate)\b[\s\S]{0,80}\b(deck|presentation|slides?)\b[\s\S]{0,80}\bfrom\b)/.test(
|
|
113
|
+
normalized,
|
|
114
|
+
) &&
|
|
115
|
+
/\b(copy|slide[- ]for[- ]slide|preserv\w*|same order|before\s*\/?\s*after|placeholder\w*|out of order)\b/.test(
|
|
116
|
+
normalized,
|
|
117
|
+
);
|
|
111
118
|
// A source deck attachment is the object being improved even when the
|
|
112
|
-
// prompt uses an implicit phrase such as "make this prettier"
|
|
113
|
-
// source noun here silently falls back to
|
|
114
|
-
// discard the uploaded deck's slide IDs
|
|
115
|
-
|
|
119
|
+
// prompt uses an implicit phrase such as "make this prettier" or asks to
|
|
120
|
+
// copy and restyle it. Requiring a source noun here silently falls back to
|
|
121
|
+
// reference-only generation and can discard the uploaded deck's slide IDs
|
|
122
|
+
// and content.
|
|
123
|
+
return asksToImprove || asksToPreserveSource;
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
function describeUploadedFilesForAgent(
|