@agent-native/core 0.177.1-nightly-20260908183718 → 0.177.1
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/README.md +54 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +13 -30
- package/corpus/templates/slides/AGENTS.md +7 -7
- package/corpus/templates/slides/actions/get-deck.ts +1 -1
- package/corpus/templates/slides/actions/update-slide.ts +97 -20
- package/corpus/templates/slides/actions/view-screen.ts +26 -8
- package/corpus/templates/slides/server/lib/slide-content-patch.ts +285 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +14 -8
- package/dist/automation/index.d.ts +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/docs/content/getting-started.mdx +5 -0
- package/docs/content/what-is-agent-native.mdx +5 -5
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -76,14 +76,20 @@ See Agent-Native in action:
|
|
|
76
76
|
|
|
77
77
|
https://github.com/user-attachments/assets/ef51644b-6506-46d8-8083-0af7b7e5b65c
|
|
78
78
|
|
|
79
|
+
<br />
|
|
80
|
+
|
|
79
81
|
## Open-source agents
|
|
80
82
|
|
|
81
83
|
Start from one of these agents or use it as an example for your own.
|
|
82
84
|
|
|
85
|
+
<br />
|
|
86
|
+
|
|
83
87
|
<table>
|
|
84
88
|
<tr>
|
|
85
89
|
<td width="33%" align="center" valign="top">
|
|
86
90
|
|
|
91
|
+
<br />
|
|
92
|
+
|
|
87
93
|
**Clips**
|
|
88
94
|
|
|
89
95
|
<a href="https://agent-native.com/apps/clips/">
|
|
@@ -95,9 +101,13 @@ Start from one of these agents or use it as an example for your own.
|
|
|
95
101
|
|
|
96
102
|
Record and understand meetings, screens, and voice notes.
|
|
97
103
|
|
|
104
|
+
<br />
|
|
105
|
+
|
|
98
106
|
</td>
|
|
99
107
|
<td width="33%" align="center" valign="top">
|
|
100
108
|
|
|
109
|
+
<br />
|
|
110
|
+
|
|
101
111
|
**Design**
|
|
102
112
|
|
|
103
113
|
<a href="https://agent-native.com/apps/design/">
|
|
@@ -109,9 +119,13 @@ Record and understand meetings, screens, and voice notes.
|
|
|
109
119
|
|
|
110
120
|
Generate and refine interactive designs.
|
|
111
121
|
|
|
122
|
+
<br />
|
|
123
|
+
|
|
112
124
|
</td>
|
|
113
125
|
<td width="33%" align="center" valign="top">
|
|
114
126
|
|
|
127
|
+
<br />
|
|
128
|
+
|
|
115
129
|
**Slides**
|
|
116
130
|
|
|
117
131
|
<a href="https://agent-native.com/apps/slides/">
|
|
@@ -123,11 +137,15 @@ Generate and refine interactive designs.
|
|
|
123
137
|
|
|
124
138
|
Create and edit on-brand presentations.
|
|
125
139
|
|
|
140
|
+
<br />
|
|
141
|
+
|
|
126
142
|
</td>
|
|
127
143
|
</tr>
|
|
128
144
|
<tr>
|
|
129
145
|
<td width="33%" align="center" valign="top">
|
|
130
146
|
|
|
147
|
+
<br />
|
|
148
|
+
|
|
131
149
|
**Analytics**
|
|
132
150
|
|
|
133
151
|
<a href="https://agent-native.com/apps/analytics/">
|
|
@@ -139,9 +157,13 @@ Create and edit on-brand presentations.
|
|
|
139
157
|
|
|
140
158
|
Ask questions of your data and build dashboards.
|
|
141
159
|
|
|
160
|
+
<br />
|
|
161
|
+
|
|
142
162
|
</td>
|
|
143
163
|
<td width="33%" align="center" valign="top">
|
|
144
164
|
|
|
165
|
+
<br />
|
|
166
|
+
|
|
145
167
|
**Calendar**
|
|
146
168
|
|
|
147
169
|
<a href="https://agent-native.com/apps/calendar/">
|
|
@@ -153,9 +175,13 @@ Ask questions of your data and build dashboards.
|
|
|
153
175
|
|
|
154
176
|
Find time, schedule events, and manage bookings.
|
|
155
177
|
|
|
178
|
+
<br />
|
|
179
|
+
|
|
156
180
|
</td>
|
|
157
181
|
<td width="33%" align="center" valign="top">
|
|
158
182
|
|
|
183
|
+
<br />
|
|
184
|
+
|
|
159
185
|
**Mail**
|
|
160
186
|
|
|
161
187
|
<a href="https://agent-native.com/apps/mail/">
|
|
@@ -167,11 +193,15 @@ Find time, schedule events, and manage bookings.
|
|
|
167
193
|
|
|
168
194
|
Prioritize email, draft replies, and follow up.
|
|
169
195
|
|
|
196
|
+
<br />
|
|
197
|
+
|
|
170
198
|
</td>
|
|
171
199
|
</tr>
|
|
172
200
|
<tr>
|
|
173
201
|
<td width="33%" align="center" valign="top">
|
|
174
202
|
|
|
203
|
+
<br />
|
|
204
|
+
|
|
175
205
|
**Assets**
|
|
176
206
|
|
|
177
207
|
<a href="https://agent-native.com/apps/assets/">
|
|
@@ -183,9 +213,13 @@ Prioritize email, draft replies, and follow up.
|
|
|
183
213
|
|
|
184
214
|
Create and organize on-brand media.
|
|
185
215
|
|
|
216
|
+
<br />
|
|
217
|
+
|
|
186
218
|
</td>
|
|
187
219
|
<td width="33%" align="center" valign="top">
|
|
188
220
|
|
|
221
|
+
<br />
|
|
222
|
+
|
|
189
223
|
**Content**
|
|
190
224
|
|
|
191
225
|
<a href="https://agent-native.com/apps/content/">
|
|
@@ -197,8 +231,27 @@ Create and organize on-brand media.
|
|
|
197
231
|
|
|
198
232
|
Draft, organize, and publish content.
|
|
199
233
|
|
|
234
|
+
<br />
|
|
235
|
+
|
|
236
|
+
</td>
|
|
237
|
+
<td width="33%" align="center" valign="top">
|
|
238
|
+
|
|
239
|
+
<br />
|
|
240
|
+
|
|
241
|
+
**Plans**
|
|
242
|
+
|
|
243
|
+
<a href="https://agent-native.com/apps/plan/">
|
|
244
|
+
<picture>
|
|
245
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fe89439e917044fc9ac9663737e35bf1f?format=webp&width=800">
|
|
246
|
+
<img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F98427229c8c84c30afee56172503c294?format=webp&width=800" alt="Plans app screenshot" width="100%">
|
|
247
|
+
</picture>
|
|
248
|
+
</a>
|
|
249
|
+
|
|
250
|
+
Create and review visual plans with diagrams, wireframes, and prototypes.
|
|
251
|
+
|
|
252
|
+
<br />
|
|
253
|
+
|
|
200
254
|
</td>
|
|
201
|
-
<td width="33%"></td>
|
|
202
255
|
</tr>
|
|
203
256
|
</table>
|
|
204
257
|
|
|
@@ -19,7 +19,6 @@ import { writeClipboardText } from "@agent-native/core/client/clipboard";
|
|
|
19
19
|
import {
|
|
20
20
|
useCollaborativeDoc,
|
|
21
21
|
isReconcileLeadClient,
|
|
22
|
-
dedupeCollabUsersByEmail,
|
|
23
22
|
emailToColor,
|
|
24
23
|
emailToName,
|
|
25
24
|
usePresence,
|
|
@@ -67,6 +66,7 @@ import {
|
|
|
67
66
|
} from "@agent-native/creative-context/client";
|
|
68
67
|
import {
|
|
69
68
|
LiveCursorOverlay,
|
|
69
|
+
PresenceBar,
|
|
70
70
|
RemoteSelectionRings,
|
|
71
71
|
RecentEditHighlights,
|
|
72
72
|
} from "@agent-native/toolkit/collab-ui";
|
|
@@ -225,8 +225,6 @@ import {
|
|
|
225
225
|
import { nextTextDecorationLineValue } from "@/components/design/edit-panel/typography-helpers";
|
|
226
226
|
import { AgentNativeMenuMark } from "@/components/design/editor/AgentNativeMenuMark";
|
|
227
227
|
import { DesignBottomToolbar } from "@/components/design/editor/DesignBottomToolbar";
|
|
228
|
-
import type { DesignCollaborator } from "@/components/design/editor/DesignCollaborators";
|
|
229
|
-
import { DesignCollaboratorsMenu } from "@/components/design/editor/DesignCollaborators";
|
|
230
228
|
import {
|
|
231
229
|
DesignWorkspaceRail,
|
|
232
230
|
INITIAL_GENERATION_DISABLED_LEFT_PANELS,
|
|
@@ -5276,7 +5274,7 @@ function DesignEditor() {
|
|
|
5276
5274
|
);
|
|
5277
5275
|
|
|
5278
5276
|
// Collaborative editing for the active file
|
|
5279
|
-
const { ydoc, awareness, isSynced, activeUsers, agentActive } =
|
|
5277
|
+
const { ydoc, awareness, isSynced, activeUsers, agentPresent, agentActive } =
|
|
5280
5278
|
useCollaborativeDoc({
|
|
5281
5279
|
docId:
|
|
5282
5280
|
isSignedIn && canEditDesign && viewMode === "single"
|
|
@@ -6245,29 +6243,6 @@ function DesignEditor() {
|
|
|
6245
6243
|
[followingEmail, stopFollowing],
|
|
6246
6244
|
);
|
|
6247
6245
|
|
|
6248
|
-
const designCollaborators = useMemo<DesignCollaborator[]>(() => {
|
|
6249
|
-
const currentEmail = currentUser?.email.trim().toLowerCase() ?? null;
|
|
6250
|
-
const humans = dedupeCollabUsersByEmail([
|
|
6251
|
-
...(currentUser ? [currentUser] : []),
|
|
6252
|
-
...activeUsers,
|
|
6253
|
-
]).filter((user) => user.email.trim().toLowerCase() !== "agent@system");
|
|
6254
|
-
const otherHumans = humans.filter(
|
|
6255
|
-
(user) => user.email.trim().toLowerCase() !== currentEmail,
|
|
6256
|
-
);
|
|
6257
|
-
const collaborators = otherHumans.map((user) => ({ user }));
|
|
6258
|
-
|
|
6259
|
-
if (!currentUser) return collaborators;
|
|
6260
|
-
|
|
6261
|
-
return [
|
|
6262
|
-
{
|
|
6263
|
-
user: currentUser,
|
|
6264
|
-
image: session?.image,
|
|
6265
|
-
isCurrent: true,
|
|
6266
|
-
},
|
|
6267
|
-
...collaborators,
|
|
6268
|
-
];
|
|
6269
|
-
}, [activeUsers, currentUser, session?.image]);
|
|
6270
|
-
|
|
6271
6246
|
// Resolve the content to render: prefer collab content only after the
|
|
6272
6247
|
// per-file reconcile state has reset for the current active file. Otherwise a
|
|
6273
6248
|
// file switch can render one frame with the previous file's Yjs text.
|
|
@@ -19492,11 +19467,19 @@ function DesignEditor() {
|
|
|
19492
19467
|
{minimalUi ? minimalRightSidebarToggle : null}
|
|
19493
19468
|
<div className="flex min-w-0 flex-1 items-center gap-[var(--design-baseline-half)]">
|
|
19494
19469
|
{hostEmbeddedEditor ? null : (
|
|
19495
|
-
<
|
|
19496
|
-
|
|
19470
|
+
<PresenceBar
|
|
19471
|
+
activeUsers={[
|
|
19472
|
+
...(currentUser ? [currentUser] : []),
|
|
19473
|
+
...(activeUsers ?? []),
|
|
19474
|
+
]}
|
|
19475
|
+
agentPresent={agentPresent}
|
|
19476
|
+
agentActive={agentActive}
|
|
19477
|
+
currentUserEmail={currentUser?.email}
|
|
19478
|
+
showCurrentUser
|
|
19497
19479
|
followingEmail={followingEmail}
|
|
19498
|
-
label={t("designEditor.collaborators")}
|
|
19499
19480
|
onAvatarClick={handleAvatarClick}
|
|
19481
|
+
disableAgentClick
|
|
19482
|
+
className="shrink-0"
|
|
19500
19483
|
/>
|
|
19501
19484
|
)}
|
|
19502
19485
|
</div>
|
|
@@ -44,7 +44,7 @@ Read the relevant skill before deeper work:
|
|
|
44
44
|
configuration and fake placeholders in examples.
|
|
45
45
|
- For external integrations, inspect the workspace/provider connection catalog first.
|
|
46
46
|
- Use actions (table above) for every deck/slide write; never write rows
|
|
47
|
-
directly. Read the schema
|
|
47
|
+
directly. Read the schema when unclear.
|
|
48
48
|
- Use `view-screen` before editing when the active deck, selected slide, or
|
|
49
49
|
current layout is unclear.
|
|
50
50
|
- Preserve deck structure and visual consistency. Prefer focused slide edits over
|
|
@@ -70,14 +70,14 @@ Read the relevant skill before deeper work:
|
|
|
70
70
|
- For per-click reveals, follow `slide-editing`'s click-to-reveal rules.
|
|
71
71
|
- For images, use `generate-image-api` with provenance; show results as
|
|
72
72
|
``.
|
|
73
|
-
- For focused
|
|
74
|
-
`
|
|
75
|
-
`
|
|
76
|
-
|
|
73
|
+
- For focused edits, prefer `view-screen`'s exact `selectedText` with `find`,
|
|
74
|
+
`expectedMatches: 1`, and `baseContentHash`; without it, use `objectId` with
|
|
75
|
+
`replace` and the same hash, else exact `find` and `expectedMatches: 1` (see
|
|
76
|
+
`slide-editing` and `mcp.instructions`).
|
|
77
77
|
- For data requests, follow `analytics-data-for-decks`; delegate via Analytics
|
|
78
78
|
over A2A, never write SQL or call providers directly.
|
|
79
|
-
-
|
|
80
|
-
|
|
79
|
+
- Without a reference deck or design system, call `get-workspace-defaults`
|
|
80
|
+
first (see `create-deck`).
|
|
81
81
|
- Before generation, follow `creative-context` for source order, `contextMode`,
|
|
82
82
|
and governed-context submission via `manage-context-membership`.
|
|
83
83
|
## Persistence Model
|
|
@@ -170,7 +170,7 @@ function sourceEditabilityForDeck(
|
|
|
170
170
|
export default defineAction({
|
|
171
171
|
title: "Read Slides deck",
|
|
172
172
|
description:
|
|
173
|
-
"Read a Slides deck or one slide. Pass the deck ID as `id` or `deckId` (either name works) and pass slideId for a targeted read; that returns only the slide's full HTML and contentHash. The result includes linked `designSystem.agentContext` when the deck has a readable design system; treat it as authoritative before authoring or restyling. If view-screen supplies an exact selectedText browser range and slide ID, do not call this without slideId for a focused text edit: call update-slide directly with one literal edits replacement and expectedMatches=1. An element
|
|
173
|
+
"Read a Slides deck or one slide. Pass the deck ID as `id` or `deckId` (either name works) and pass slideId for a targeted read; that returns only the slide's full HTML and contentHash. The result includes linked `designSystem.agentContext` when the deck has a readable design system; treat it as authoritative before authoring or restyling. If view-screen supplies an exact selectedText browser range and slide ID, do not call this without slideId for a focused text edit: call update-slide directly with one literal edits replacement and expectedMatches=1. If view-screen supplies a stable objectId for a selected element, call update-slide directly with that objectId to replace only the element's inner content. An element preview without objectId or an edit that changes markup needs a targeted read before text mutation. Use compact=true for a lightweight targeted check, or compact=false and format=true when markup or layout requires source inspection. For source-preserving work, sourceEditability states whether structural edits are blocked and names the patch-deck rewriteSource conversion path; the compact result also includes sourceCoverage. Do not claim completion until sourceCoverage.complete is true and its expectedSlideIds and actualSlideIds match in order. User-visible slide numbers are 1-based and match the UI. Use slideId for edits. Returns deckStyle (backgrounds, text and accent colors, fonts, heading sizes across slides, with deviating slides named) and representativeSlideId; before a structural or layout change, read that slide with slideId and compact='false' and mirror its structure and values.",
|
|
174
174
|
timeoutMs: 60_000,
|
|
175
175
|
schema: z.object({
|
|
176
176
|
id: z
|
|
@@ -288,7 +288,7 @@ function assertStyleOnlyEdit(
|
|
|
288
288
|
export default defineAction({
|
|
289
289
|
title: "Edit one Slides slide",
|
|
290
290
|
description:
|
|
291
|
-
"Edit exactly one Slides slide. For a focused edit or translation of current or selected text, use one literal replace item in edits with the exact text and expectedMatches=1; when view-screen already supplies the target, do not fetch the full deck, use fullContent, or wait for layout-fit. For a style request, get-deck's designSystem and deckStyle (also printed by view-screen) are authoritative; for anything beyond colors (spacing, element order, sizes) first read the representativeSlideId with a targeted get-deck and mirror its structure. Introduce colors or fonts the deck does not already use only when the user asks for them. Use targeted get-deck with slideId only if the selection text is missing, truncated, ambiguous, the literal match fails, or the edit changes markup or layout; then use ordered edits and an optional baseContentHash. Use exactly one input mode: edits, legacy find/replace, or fullContent. Mixed modes are rejected and write nothing. Prefer edits over fullContent so unrelated markup is not regenerated. For style-only requests, set styleOnly=true and use edits that change only the requested CSS declarations and preserve text and layout properties; the action rejects text or markup changes and fullContent in that mode. Never use unresolved placeholder markers as stand-ins for preserved content. Content edits clear existing click-reveal metadata; style-only CSS edits preserve it because the HTML structure remains stable. Use patch-deck with the complete animations list when a content edit intentionally changes both content and reveals. Source-imported slides preserve their original images and factual copy by default. The action returns immediately after persistence; layoutFit.status=pending means the open editor will measure the new content asynchronously, and get-layout-overflows can check the returned contentHash plus layoutFitRevision later.",
|
|
291
|
+
"Edit exactly one Slides slide. For a focused edit or translation of current or selected text, use one literal replace item in edits with the exact text and expectedMatches=1; when view-screen supplies an objectId for a selected element, use that objectId instead of find to replace only that element's inner content. The top-level objectId and replace fields are also supported as a compact alternative to edits. When view-screen already supplies the target, do not fetch the full deck, use fullContent, or wait for layout-fit. For a style request, get-deck's designSystem and deckStyle (also printed by view-screen) are authoritative; for anything beyond colors (spacing, element order, sizes) first read the representativeSlideId with a targeted get-deck and mirror its structure. Introduce colors or fonts the deck does not already use only when the user asks for them. Use targeted get-deck with slideId only if the selection text is missing, truncated, ambiguous, the literal match fails, or the edit changes markup or layout; then use ordered edits and an optional baseContentHash. Use exactly one input mode: edits, legacy find/replace or objectId/replace, or fullContent. Mixed modes are rejected and write nothing. Prefer edits over fullContent so unrelated markup is not regenerated. For style-only requests, set styleOnly=true and use edits that change only the requested CSS declarations and preserve text and layout properties; the action rejects text or markup changes and fullContent in that mode. Never use unresolved placeholder markers as stand-ins for preserved content. Content edits clear existing click-reveal metadata; style-only CSS edits preserve it because the HTML structure remains stable. Use patch-deck with the complete animations list when a content edit intentionally changes both content and reveals. Source-imported slides preserve their original images and factual copy by default. The action returns immediately after persistence; layoutFit.status=pending means the open editor will measure the new content asynchronously, and get-layout-overflows can check the returned contentHash plus layoutFitRevision later.",
|
|
292
292
|
schema: z.object({
|
|
293
293
|
deckId: z.string().describe("Deck ID"),
|
|
294
294
|
slideId: z.string().describe("Slide ID"),
|
|
@@ -296,10 +296,19 @@ export default defineAction({
|
|
|
296
296
|
.string()
|
|
297
297
|
.optional()
|
|
298
298
|
.describe("Text to find (for surgical search-replace edit)"),
|
|
299
|
+
objectId: z
|
|
300
|
+
.string()
|
|
301
|
+
.min(1)
|
|
302
|
+
.optional()
|
|
303
|
+
.describe(
|
|
304
|
+
"Stable data-slide-object-id for replacing the selected element's inner content",
|
|
305
|
+
),
|
|
299
306
|
replace: z
|
|
300
307
|
.string()
|
|
301
308
|
.optional()
|
|
302
|
-
.describe(
|
|
309
|
+
.describe(
|
|
310
|
+
"Replacement text; pass an empty string explicitly to clear it",
|
|
311
|
+
),
|
|
303
312
|
fullContent: z
|
|
304
313
|
.string()
|
|
305
314
|
.optional()
|
|
@@ -307,15 +316,46 @@ export default defineAction({
|
|
|
307
316
|
edits: z
|
|
308
317
|
.array(
|
|
309
318
|
z.union([
|
|
310
|
-
z
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
+
z
|
|
320
|
+
.object({
|
|
321
|
+
op: z.literal("replace").optional(),
|
|
322
|
+
find: z.string().optional(),
|
|
323
|
+
objectId: z.string().min(1).optional(),
|
|
324
|
+
replace: z.string(),
|
|
325
|
+
all: z.boolean().optional(),
|
|
326
|
+
occurrence: z.number().int().positive().optional(),
|
|
327
|
+
expectedMatches: z.number().int().nonnegative().optional(),
|
|
328
|
+
required: z.boolean().optional(),
|
|
329
|
+
})
|
|
330
|
+
.superRefine((edit, context) => {
|
|
331
|
+
if ((edit.find !== undefined) === (edit.objectId !== undefined)) {
|
|
332
|
+
context.addIssue({
|
|
333
|
+
code: "custom",
|
|
334
|
+
message:
|
|
335
|
+
"A replace edit requires exactly one of find or objectId",
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
if (
|
|
339
|
+
edit.objectId !== undefined &&
|
|
340
|
+
(edit.all !== undefined || edit.occurrence !== undefined)
|
|
341
|
+
) {
|
|
342
|
+
context.addIssue({
|
|
343
|
+
code: "custom",
|
|
344
|
+
message:
|
|
345
|
+
"objectId replacement does not support all or occurrence",
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (
|
|
349
|
+
edit.objectId !== undefined &&
|
|
350
|
+
edit.expectedMatches !== undefined &&
|
|
351
|
+
edit.expectedMatches !== 1
|
|
352
|
+
) {
|
|
353
|
+
context.addIssue({
|
|
354
|
+
code: "custom",
|
|
355
|
+
message: "objectId replacement expectedMatches must be 1",
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}),
|
|
319
359
|
z.object({
|
|
320
360
|
op: z.enum(["insert-before", "insert-after"]),
|
|
321
361
|
marker: z.string(),
|
|
@@ -347,7 +387,7 @@ export default defineAction({
|
|
|
347
387
|
.min(1)
|
|
348
388
|
.optional()
|
|
349
389
|
.describe(
|
|
350
|
-
"Ordered atomic edits against the current HTML. For one selected
|
|
390
|
+
"Ordered atomic edits against the current HTML. For one exact text replacement, use find and expectedMatches=1; for a selected element without exact selectedText, use its objectId to replace only the element's inner content. Each edit must match unless required=false.",
|
|
351
391
|
),
|
|
352
392
|
styleOnly: z
|
|
353
393
|
.boolean()
|
|
@@ -401,6 +441,7 @@ export default defineAction({
|
|
|
401
441
|
deckId,
|
|
402
442
|
slideId,
|
|
403
443
|
find,
|
|
444
|
+
objectId,
|
|
404
445
|
replace,
|
|
405
446
|
fullContent,
|
|
406
447
|
edits,
|
|
@@ -412,29 +453,50 @@ export default defineAction({
|
|
|
412
453
|
contextModeOverride,
|
|
413
454
|
reuseLabels,
|
|
414
455
|
} = args;
|
|
415
|
-
const hasLegacyMode =
|
|
456
|
+
const hasLegacyMode =
|
|
457
|
+
find !== undefined || objectId !== undefined || replace !== undefined;
|
|
416
458
|
const inputModeCount =
|
|
417
459
|
Number(Boolean(edits)) +
|
|
418
460
|
Number(hasLegacyMode) +
|
|
419
461
|
Number(fullContent !== undefined);
|
|
420
462
|
if (inputModeCount === 0) {
|
|
421
|
-
fail(
|
|
422
|
-
|
|
423
|
-
|
|
463
|
+
fail(
|
|
464
|
+
"One of --edits, --find/--replace, --objectId/--replace, or --fullContent is required",
|
|
465
|
+
{
|
|
466
|
+
errorCode: "slide_edit_mode_required",
|
|
467
|
+
},
|
|
468
|
+
);
|
|
424
469
|
}
|
|
425
470
|
if (inputModeCount > 1) {
|
|
426
471
|
fail(
|
|
427
|
-
"Use exactly one input mode: --edits, --find/--replace, or --fullContent; do not combine modes",
|
|
472
|
+
"Use exactly one input mode: --edits, --find/--replace, --objectId/--replace, or --fullContent; do not combine modes",
|
|
428
473
|
{ errorCode: "slide_edit_modes_conflict" },
|
|
429
474
|
);
|
|
430
475
|
}
|
|
431
476
|
if (styleOnly && !edits) {
|
|
432
477
|
fail(
|
|
433
|
-
"Style-only slide edits require --edits and cannot use --fullContent or legacy find/replace",
|
|
478
|
+
"Style-only slide edits require --edits and cannot use --fullContent or legacy find/replace/objectId",
|
|
434
479
|
{ errorCode: "style_only_slide_edits_required" },
|
|
435
480
|
);
|
|
436
481
|
}
|
|
437
|
-
if (
|
|
482
|
+
if (objectId !== undefined && find !== undefined) {
|
|
483
|
+
fail("Use either --find or --objectId for a legacy text edit, not both", {
|
|
484
|
+
errorCode: "slide_find_object_id_conflict",
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
if (objectId !== undefined && replace === undefined) {
|
|
488
|
+
fail(
|
|
489
|
+
"Legacy --objectId requires --replace; pass an empty string explicitly to clear the element",
|
|
490
|
+
{ errorCode: "slide_object_id_replace_required" },
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
if (find !== undefined && replace === undefined) {
|
|
494
|
+
fail(
|
|
495
|
+
"Legacy --find requires --replace; pass an empty string explicitly to clear the match",
|
|
496
|
+
{ errorCode: "slide_find_replace_required" },
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
if (replace !== undefined && find === undefined && objectId === undefined) {
|
|
438
500
|
fail("Legacy --replace requires --find", {
|
|
439
501
|
errorCode: "slide_replace_requires_find",
|
|
440
502
|
});
|
|
@@ -568,6 +630,21 @@ export default defineAction({
|
|
|
568
630
|
applied = patched.changed;
|
|
569
631
|
editResults = patched.applied;
|
|
570
632
|
if (!applied) slide.content = previousContent;
|
|
633
|
+
} else if (objectId !== undefined) {
|
|
634
|
+
const sourceContent = format
|
|
635
|
+
? await formatSlideHtml(previousContent)
|
|
636
|
+
: previousContent;
|
|
637
|
+
const patched = await applySlideContentEdits(
|
|
638
|
+
sourceContent,
|
|
639
|
+
[{ objectId, replace: replace! }],
|
|
640
|
+
format,
|
|
641
|
+
);
|
|
642
|
+
const nextContent = normalizeSlidePadding(patched.content);
|
|
643
|
+
validateNextContent(nextContent);
|
|
644
|
+
slide.content = nextContent;
|
|
645
|
+
applied = patched.changed;
|
|
646
|
+
editResults = patched.applied;
|
|
647
|
+
if (!applied) slide.content = previousContent;
|
|
571
648
|
} else if (find !== undefined) {
|
|
572
649
|
const idx = previousContent.indexOf(find);
|
|
573
650
|
if (idx === -1) {
|
|
@@ -817,7 +894,7 @@ export default defineAction({
|
|
|
817
894
|
});
|
|
818
895
|
|
|
819
896
|
console.log(
|
|
820
|
-
`update-slide: deck=${deckId} slide=${slideId} ${edits ? `edits=${edits.length}` : find !== undefined ? `find="${find.slice(0, 40)}"` : "fullContent"} applied=${applied}`,
|
|
897
|
+
`update-slide: deck=${deckId} slide=${slideId} ${edits ? `edits=${edits.length}` : objectId !== undefined ? `objectId="${objectId}"` : find !== undefined ? `find="${find.slice(0, 40)}"` : "fullContent"} applied=${applied}`,
|
|
821
898
|
);
|
|
822
899
|
|
|
823
900
|
const base = {
|
|
@@ -96,7 +96,7 @@ function getCurrentSlideFitMeasurement(
|
|
|
96
96
|
export default defineAction({
|
|
97
97
|
title: "Inspect current Slides screen",
|
|
98
98
|
description:
|
|
99
|
-
"Inspect the current Slides editor context when the active deck, slide, or selection is unknown. Returns the current deck and slide IDs, slide previews, current slide HTML, and matching visual selection metadata (or the deck list on the home page). For a short exact selectedText browser-range edit, use this result directly with one update-slide literal replacement and expectedMatches=1; do not load the full deck for
|
|
99
|
+
"Inspect the current Slides editor context when the active deck, slide, or selection is unknown. Returns the current deck and slide IDs, slide previews, current slide HTML, and matching visual selection metadata (or the deck list on the home page). For a short exact selectedText browser-range edit, use this result directly with one update-slide literal replacement and expectedMatches=1. When a selected element has an objectId but no exact selectedText, use that objectId with one update-slide replace edit to change only its inner content; do not load the full deck for either focused path.",
|
|
100
100
|
schema: z.object({}),
|
|
101
101
|
http: false,
|
|
102
102
|
run: async (_args) => {
|
|
@@ -304,14 +304,26 @@ export default defineAction({
|
|
|
304
304
|
? ` (matches currentSlideId)`
|
|
305
305
|
: ` (differs from currentSlideId ${currentSlide?.id ?? "(none)"} — use selectionSlideId, the slide this selection was made on)`),
|
|
306
306
|
);
|
|
307
|
+
if (selectionSlide.id !== currentSlide?.id) {
|
|
308
|
+
lines.push(
|
|
309
|
+
`selectionSlideContentHash: ${hashSlideContent(String(selectionSlide.content ?? ""))} ← use as baseContentHash with selectionSlideId`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
307
312
|
lines.push(`mode: ${selection.mode ?? "unknown"}`);
|
|
308
313
|
lines.push(`activeTool: ${selection.activeTool ?? "select"}`);
|
|
309
314
|
if (Array.isArray(selection.items) && selection.items.length > 0) {
|
|
310
315
|
for (const [index, item] of selection.items.entries()) {
|
|
316
|
+
const isImageSelection =
|
|
317
|
+
item.kind === "image" || item.tagName?.toLowerCase() === "img";
|
|
311
318
|
lines.push(
|
|
312
319
|
`selected ${index + 1}: ${item.kind ?? "element"} ${item.tagName ?? ""} selector=${item.selector ?? "(none)"}`,
|
|
313
320
|
);
|
|
314
|
-
if (item.objectId)
|
|
321
|
+
if (item.objectId && !isImageSelection) {
|
|
322
|
+
lines.push(`objectId: ${item.objectId}`);
|
|
323
|
+
lines.push(
|
|
324
|
+
"objectIdStatus: stable selected-element target; use it with one update-slide replace edit when selectedText is unavailable",
|
|
325
|
+
);
|
|
326
|
+
}
|
|
315
327
|
if (item.runtimeSelector) {
|
|
316
328
|
lines.push(`runtimeSelector: ${item.runtimeSelector}`);
|
|
317
329
|
}
|
|
@@ -321,15 +333,21 @@ export default defineAction({
|
|
|
321
333
|
"selectedTextStatus: exact browser range; use verbatim as edits.find with expectedMatches: 1",
|
|
322
334
|
);
|
|
323
335
|
}
|
|
324
|
-
if (
|
|
336
|
+
if (isImageSelection) {
|
|
337
|
+
lines.push(
|
|
338
|
+
"imageStatus: image selection has no editable text content; use the targeted image/markup workflow",
|
|
339
|
+
);
|
|
340
|
+
} else if (item.text) {
|
|
325
341
|
lines.push(`text: ${item.text}`);
|
|
326
342
|
if (!item.selectedText) {
|
|
327
343
|
lines.push(
|
|
328
|
-
item.
|
|
329
|
-
?
|
|
330
|
-
: item.textTruncated ===
|
|
331
|
-
? `textStatus: element
|
|
332
|
-
:
|
|
344
|
+
item.objectId
|
|
345
|
+
? "textStatus: element preview is not an exact browser-range selection; use objectId with update-slide for an element-only replacement"
|
|
346
|
+
: item.textTruncated === true
|
|
347
|
+
? `textStatus: element preview may be truncated; use get-deck with slideId=${selectionSlide.id} before editing`
|
|
348
|
+
: item.textTruncated === false
|
|
349
|
+
? `textStatus: element text is complete but is not an exact browser-range selection; use get-deck with slideId=${selectionSlide.id} before editing`
|
|
350
|
+
: `textStatus: element preview status unknown; use get-deck with slideId=${selectionSlide.id} before editing`,
|
|
333
351
|
);
|
|
334
352
|
} else {
|
|
335
353
|
lines.push(
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
export type SlideContentEdit =
|
|
11
11
|
| {
|
|
12
12
|
op?: "replace";
|
|
13
|
-
find
|
|
13
|
+
find?: string;
|
|
14
|
+
objectId?: string;
|
|
14
15
|
replace: string;
|
|
15
16
|
all?: boolean;
|
|
16
17
|
occurrence?: number;
|
|
@@ -167,6 +168,25 @@ function applyLiteralReplace(
|
|
|
167
168
|
content: string,
|
|
168
169
|
edit: Extract<SlideContentEdit, { op?: "replace" }>,
|
|
169
170
|
): { content: string; summary: string } {
|
|
171
|
+
if (edit.objectId !== undefined) {
|
|
172
|
+
if (edit.find !== undefined) {
|
|
173
|
+
throw new SlideContentEditError(
|
|
174
|
+
"A replace edit must use either find or objectId, not both",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (edit.all !== undefined || edit.occurrence !== undefined) {
|
|
178
|
+
throw new SlideContentEditError(
|
|
179
|
+
"objectId replacement does not support all or occurrence",
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
if (edit.expectedMatches !== undefined && edit.expectedMatches !== 1) {
|
|
183
|
+
throw new SlideContentEditError(
|
|
184
|
+
`objectId replacement expected 1 match(es), found ${edit.expectedMatches}`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return applyObjectReplace(content, edit.objectId, edit.replace);
|
|
188
|
+
}
|
|
189
|
+
|
|
170
190
|
if (!edit.find) {
|
|
171
191
|
throw new SlideContentEditError("Patch find/marker text cannot be empty");
|
|
172
192
|
}
|
|
@@ -201,6 +221,270 @@ function applyLiteralReplace(
|
|
|
201
221
|
return { content: result.content, summary };
|
|
202
222
|
}
|
|
203
223
|
|
|
224
|
+
const RAW_TEXT_TAG_NAMES = new Set(["script", "style", "textarea", "title"]);
|
|
225
|
+
const VOID_TAG_NAMES = new Set([
|
|
226
|
+
"area",
|
|
227
|
+
"base",
|
|
228
|
+
"br",
|
|
229
|
+
"col",
|
|
230
|
+
"embed",
|
|
231
|
+
"hr",
|
|
232
|
+
"img",
|
|
233
|
+
"input",
|
|
234
|
+
"link",
|
|
235
|
+
"meta",
|
|
236
|
+
"param",
|
|
237
|
+
"source",
|
|
238
|
+
"track",
|
|
239
|
+
"wbr",
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
function applyObjectReplace(
|
|
243
|
+
content: string,
|
|
244
|
+
objectId: string,
|
|
245
|
+
replacement: string,
|
|
246
|
+
): { content: string; summary: string } {
|
|
247
|
+
const targets = findObjectTargets(content, objectId);
|
|
248
|
+
if (targets.length === 0) {
|
|
249
|
+
throw new SlideContentEditError(
|
|
250
|
+
`objectId "${objectId}" found no matching slide object`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (targets.length > 1) {
|
|
254
|
+
throw new SlideContentEditError(
|
|
255
|
+
`objectId "${objectId}" matched ${targets.length} slide objects; object IDs must be unique`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const target = targets[0]!;
|
|
260
|
+
if (target.isVoid) {
|
|
261
|
+
throw new SlideContentEditError(
|
|
262
|
+
`objectId "${objectId}" targets <${target.tagName}>, which has no editable text content`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
content:
|
|
267
|
+
content.slice(0, target.innerStart) +
|
|
268
|
+
replacement +
|
|
269
|
+
content.slice(target.innerEnd),
|
|
270
|
+
summary: "replace:object",
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function findObjectTargets(
|
|
275
|
+
html: string,
|
|
276
|
+
objectId: string,
|
|
277
|
+
): Array<{
|
|
278
|
+
innerStart: number;
|
|
279
|
+
innerEnd: number;
|
|
280
|
+
tagName: string;
|
|
281
|
+
isVoid: boolean;
|
|
282
|
+
}> {
|
|
283
|
+
const targets: Array<{
|
|
284
|
+
innerStart: number;
|
|
285
|
+
innerEnd: number;
|
|
286
|
+
tagName: string;
|
|
287
|
+
isVoid: boolean;
|
|
288
|
+
}> = [];
|
|
289
|
+
let cursor = 0;
|
|
290
|
+
|
|
291
|
+
while (cursor < html.length) {
|
|
292
|
+
const tagStart = html.indexOf("<", cursor);
|
|
293
|
+
if (tagStart === -1) break;
|
|
294
|
+
if (html.startsWith("<!--", tagStart)) {
|
|
295
|
+
const commentEnd = html.indexOf("-->", tagStart + 4);
|
|
296
|
+
cursor = commentEnd === -1 ? html.length : commentEnd + 3;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const nameStart = tagStart + 1;
|
|
301
|
+
const tagName = tagNameAt(html, nameStart);
|
|
302
|
+
if (!tagName) {
|
|
303
|
+
cursor = tagStart + 1;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const tagEnd = tagEndIndex(html, nameStart + tagName.length);
|
|
307
|
+
if (tagEnd === -1) {
|
|
308
|
+
throw new SlideContentEditError(
|
|
309
|
+
`objectId "${objectId}" cannot be resolved because the slide HTML has an unclosed tag`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const openingTag = html.slice(tagStart, tagEnd + 1);
|
|
313
|
+
if (hasObjectId(openingTag, objectId)) {
|
|
314
|
+
const isVoid = isVoidTag(tagName);
|
|
315
|
+
if (isVoid) {
|
|
316
|
+
targets.push({
|
|
317
|
+
innerStart: tagEnd + 1,
|
|
318
|
+
innerEnd: tagEnd + 1,
|
|
319
|
+
tagName,
|
|
320
|
+
isVoid,
|
|
321
|
+
});
|
|
322
|
+
} else {
|
|
323
|
+
const closing = findMatchingCloseTag(html, tagName, tagEnd + 1);
|
|
324
|
+
if (!closing) {
|
|
325
|
+
throw new SlideContentEditError(
|
|
326
|
+
`objectId "${objectId}" targets <${tagName}> without a closing tag`,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
targets.push({
|
|
330
|
+
innerStart: tagEnd + 1,
|
|
331
|
+
innerEnd: closing.start,
|
|
332
|
+
tagName,
|
|
333
|
+
isVoid,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (RAW_TEXT_TAG_NAMES.has(tagName) && !isVoidTag(tagName)) {
|
|
339
|
+
const rawClose = rawTextCloseIndex(html, tagName, tagEnd + 1);
|
|
340
|
+
cursor = rawClose === -1 ? html.length : rawClose + tagName.length + 3;
|
|
341
|
+
} else {
|
|
342
|
+
cursor = tagEnd + 1;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return targets;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function hasObjectId(tag: string, objectId: string): boolean {
|
|
350
|
+
const tagName = /^<[A-Za-z][\w:-]*/.exec(tag)?.[0];
|
|
351
|
+
if (!tagName) return false;
|
|
352
|
+
|
|
353
|
+
let cursor = tagName.length;
|
|
354
|
+
while (cursor < tag.length) {
|
|
355
|
+
while (/\s/.test(tag[cursor] ?? "")) cursor += 1;
|
|
356
|
+
if (tag[cursor] === "/" || tag[cursor] === ">") break;
|
|
357
|
+
|
|
358
|
+
const attributeStart = cursor;
|
|
359
|
+
while (cursor < tag.length && !/[\s=>/]/.test(tag[cursor] ?? "")) {
|
|
360
|
+
cursor += 1;
|
|
361
|
+
}
|
|
362
|
+
const attributeName = tag.slice(attributeStart, cursor).toLowerCase();
|
|
363
|
+
while (/\s/.test(tag[cursor] ?? "")) cursor += 1;
|
|
364
|
+
if (tag[cursor] !== "=") {
|
|
365
|
+
while (cursor < tag.length && !/[\s>]/.test(tag[cursor] ?? "")) {
|
|
366
|
+
cursor += 1;
|
|
367
|
+
}
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
cursor += 1;
|
|
372
|
+
while (/\s/.test(tag[cursor] ?? "")) cursor += 1;
|
|
373
|
+
const quote =
|
|
374
|
+
tag[cursor] === '"' || tag[cursor] === "'" ? tag[cursor] : null;
|
|
375
|
+
if (quote) cursor += 1;
|
|
376
|
+
const valueStart = cursor;
|
|
377
|
+
if (quote) {
|
|
378
|
+
while (cursor < tag.length && tag[cursor] !== quote) cursor += 1;
|
|
379
|
+
} else {
|
|
380
|
+
while (cursor < tag.length && !/[\s>]/.test(tag[cursor] ?? "")) {
|
|
381
|
+
cursor += 1;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const value = tag.slice(valueStart, cursor);
|
|
385
|
+
if (quote && tag[cursor] === quote) cursor += 1;
|
|
386
|
+
if (attributeName === "data-slide-object-id" && value === objectId) {
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function findMatchingCloseTag(
|
|
395
|
+
html: string,
|
|
396
|
+
tagName: string,
|
|
397
|
+
start: number,
|
|
398
|
+
): { start: number; end: number } | null {
|
|
399
|
+
if (RAW_TEXT_TAG_NAMES.has(tagName)) {
|
|
400
|
+
const rawClose = rawTextCloseIndex(html, tagName, start);
|
|
401
|
+
return rawClose === -1
|
|
402
|
+
? null
|
|
403
|
+
: { start: rawClose, end: rawClose + tagName.length + 3 };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let depth = 1;
|
|
407
|
+
let cursor = start;
|
|
408
|
+
while (cursor < html.length) {
|
|
409
|
+
const tagStart = html.indexOf("<", cursor);
|
|
410
|
+
if (tagStart === -1) break;
|
|
411
|
+
if (html.startsWith("<!--", tagStart)) {
|
|
412
|
+
const commentEnd = html.indexOf("-->", tagStart + 4);
|
|
413
|
+
cursor = commentEnd === -1 ? html.length : commentEnd + 3;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const closing = html[tagStart + 1] === "/";
|
|
418
|
+
const nameStart = tagStart + (closing ? 2 : 1);
|
|
419
|
+
const nestedTagName = tagNameAt(html, nameStart);
|
|
420
|
+
if (!nestedTagName) {
|
|
421
|
+
cursor = tagStart + 1;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const tagEnd = tagEndIndex(html, nameStart + nestedTagName.length);
|
|
425
|
+
if (tagEnd === -1) return null;
|
|
426
|
+
|
|
427
|
+
if (closing && nestedTagName === tagName) {
|
|
428
|
+
depth -= 1;
|
|
429
|
+
if (depth === 0) {
|
|
430
|
+
return { start: tagStart, end: tagEnd + 1 };
|
|
431
|
+
}
|
|
432
|
+
} else if (
|
|
433
|
+
!closing &&
|
|
434
|
+
nestedTagName === tagName &&
|
|
435
|
+
!isVoidTag(nestedTagName)
|
|
436
|
+
) {
|
|
437
|
+
depth += 1;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!closing && RAW_TEXT_TAG_NAMES.has(nestedTagName)) {
|
|
441
|
+
const rawClose = rawTextCloseIndex(html, nestedTagName, tagEnd + 1);
|
|
442
|
+
cursor =
|
|
443
|
+
rawClose === -1 ? html.length : rawClose + nestedTagName.length + 3;
|
|
444
|
+
} else {
|
|
445
|
+
cursor = tagEnd + 1;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function isVoidTag(tagName: string): boolean {
|
|
453
|
+
return VOID_TAG_NAMES.has(tagName);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function tagEndIndex(html: string, start: number): number {
|
|
457
|
+
let quote: '"' | "'" | null = null;
|
|
458
|
+
for (let index = start; index < html.length; index += 1) {
|
|
459
|
+
const character = html[index];
|
|
460
|
+
if (quote) {
|
|
461
|
+
if (character === quote) quote = null;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (character === '"' || character === "'") {
|
|
465
|
+
quote = character;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (character === ">") return index;
|
|
469
|
+
}
|
|
470
|
+
return -1;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function tagNameAt(html: string, start: number): string | null {
|
|
474
|
+
const match = /^[A-Za-z][\w:-]*/.exec(html.slice(start));
|
|
475
|
+
return match?.[0]?.toLowerCase() ?? null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function rawTextCloseIndex(
|
|
479
|
+
html: string,
|
|
480
|
+
tagName: string,
|
|
481
|
+
start: number,
|
|
482
|
+
): number {
|
|
483
|
+
const pattern = new RegExp(`<\\/${tagName}\\s*>`, "gi");
|
|
484
|
+
pattern.lastIndex = start;
|
|
485
|
+
return pattern.exec(html)?.index ?? -1;
|
|
486
|
+
}
|
|
487
|
+
|
|
204
488
|
function applyInsert(
|
|
205
489
|
content: string,
|
|
206
490
|
edit: Extract<SlideContentEdit, { op: "insert-before" | "insert-after" }>,
|
|
@@ -153,7 +153,8 @@ export default createAgentChatPlugin({
|
|
|
153
153
|
mcp: {
|
|
154
154
|
connectorCatalog: EXTERNAL_CONNECTOR_TOOL_NAMES,
|
|
155
155
|
instructions:
|
|
156
|
-
|
|
156
|
+
"Cross-slide selection rule: when view-screen returns selectionSlideId different from currentSlideId, use selectionSlideId and selectionSlideContentHash for the update; never pair a selectionSlideId with currentSlideContentHash. " +
|
|
157
|
+
'Design system: every deck read (get-deck, view-screen, get-workspace-defaults, get-deck-reference-context) returns `designSystem` — a bounded summary with scope "summary" and a `next` line — and get-deck also returns `deckStyle` plus `representativeSlideId`. Before the first slide you author in a deck, call get-design-system { id } once for the full tokens, assets, docs, and custom instructions (create-deck already returns it in full); reuse it for every later slide instead of re-reading it. Apply designSystem.agentContext and deckStyle before authoring or restyling. If designSystem.status is "unavailable", follow its message; never invent a generic style. For a new deck, pass the exact title as `designSystem` or a designSystemId; omit both to get the caller\'s personal default, then the workspace default. When view-screen returns an exact selectedText range, edit immediately with one update-slide literal edits replacement and expectedMatches=1, passing currentSlideContentHash as baseContentHash when available; when it returns a stable objectId without exact selectedText, use one update-slide replace edit with that objectId and the same hash when available instead of fetching the full deck. Use targeted get-deck with slideId only for ambiguous, truncated, or structural text. Use patch-deck for slide deletion, reordering, deck-wide, or multi-slide changes, and delete-deck to remove a deck. Read back the same slide after writing; a delegated ask_app response is unverified until that readback confirms it.',
|
|
157
158
|
},
|
|
158
159
|
externalAgents: { writes: "allowlisted" },
|
|
159
160
|
durableBackgroundRuns: true,
|
|
@@ -196,15 +197,20 @@ When a request includes a public URL as source material, fetch it with web-reque
|
|
|
196
197
|
When the user asks to improve, beautify, restyle, or make an uploaded/existing deck on-brand, treat it as an in-place source-preserving edit unless the user explicitly asks to rewrite the story or change slide count. First call view-screen when the active deck is unclear, then get-deck with compact=true for deck orientation. If you need slide markup, call get-deck with compact=false explicitly and only when the edit requires the full HTML. If get-deck.sourceImport exists, preserve its slide count, order, IDs, factual copy, notes, images, charts, tables, diagrams, freeform objects, and source aspect ratio. The ordered source manifest is sourceImport.slideIds. For a deck-wide restyle, use one patch-deck call with requireAllSourceSlides=true and one patch-slide operation with fields.content for every source slide ID; the action rejects partial coverage. Do not split a full-deck restyle into arbitrary batches or use one-by-one update-slide calls - reserve update-slide for targeted one-slide edits. After the patch succeeds, verify with get-deck using compact=true so the verification does not retransmit every slide's HTML. Completion requires sourceCoverage.complete=true with expectedSlideIds and actualSlideIds matching in order. Do not claim a partial or initial pass. Do not use add-slide, delete, reorder, or replace source imagery with generic cards for this workflow. If sourceImport.fidelity is partial or imagesSkipped is nonzero, stop and report the exact fidelity warning instead of claiming a reliable improvement.
|
|
197
198
|
For a focused text edit or translation of the current selection, treat the
|
|
198
199
|
selection as the target. If view-screen or the request context provides
|
|
199
|
-
deckId,
|
|
200
|
-
update-slide immediately with one literal edits replace using that
|
|
201
|
-
and expectedMatches=1;
|
|
202
|
-
|
|
200
|
+
deckId, a selectionSlideId when present, and exact selectedText from a browser
|
|
201
|
+
range, call update-slide immediately with one literal edits replace using that
|
|
202
|
+
exact text and expectedMatches=1; use selectionSlideId as slideId when it is
|
|
203
|
+
present. Pass selectionSlideContentHash as baseContentHash when the selection
|
|
204
|
+
slide differs from currentSlideId; otherwise pass currentSlideContentHash when
|
|
205
|
+
available. Never pair a selectionSlideId with the current slide's hash. Do not call
|
|
203
206
|
get-deck without slideId, enumerate the deck, or request full HTML for this
|
|
204
207
|
path. If no exact selectedText range is available, the value is an element
|
|
205
|
-
preview or truncated,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
+
preview or truncated, call update-slide with the supplied objectId and the
|
|
209
|
+
selectionSlideId when present, passing selectionSlideContentHash when it differs
|
|
210
|
+
from currentSlideId or currentSlideContentHash otherwise. When the literal
|
|
211
|
+
match fails or the request changes markup or layout, call get-deck with slideId
|
|
212
|
+
only. First classify the
|
|
213
|
+
remaining request scope. For a
|
|
208
214
|
styling-only request, set styleOnly=true, change only the requested CSS
|
|
209
215
|
declarations on the identified elements, and preserve text, element order,
|
|
210
216
|
padding, margin, gap, font-size, line-height, dimensions, positioning, and
|
|
@@ -186,8 +186,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
|
|
|
186
186
|
duplicate?: undefined;
|
|
187
187
|
eventId?: undefined;
|
|
188
188
|
} | {
|
|
189
|
+
error?: undefined;
|
|
189
190
|
accepted: boolean;
|
|
190
191
|
duplicate: boolean;
|
|
191
192
|
eventId: string;
|
|
192
|
-
error?: undefined;
|
|
193
193
|
}>;
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
ok?: undefined;
|
|
30
29
|
error: string;
|
|
30
|
+
ok?: undefined;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
14
|
count: number;
|
|
15
15
|
updated?: undefined;
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error?: undefined;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
count?: undefined;
|
|
20
20
|
updated: number;
|
|
21
|
-
ok?: undefined;
|
|
22
21
|
error?: undefined;
|
|
22
|
+
ok?: undefined;
|
|
23
23
|
} | {
|
|
24
24
|
count?: undefined;
|
|
25
25
|
updated?: undefined;
|
|
@@ -43,21 +43,21 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
43
43
|
} | {
|
|
44
44
|
summary: import("./types.js").TraceSummary;
|
|
45
45
|
spans: import("./types.js").TraceSpan[];
|
|
46
|
-
ok?: undefined;
|
|
47
46
|
error?: undefined;
|
|
48
47
|
id?: undefined;
|
|
48
|
+
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
50
|
summary?: undefined;
|
|
51
51
|
spans?: undefined;
|
|
52
52
|
id: string;
|
|
53
|
-
ok?: undefined;
|
|
54
53
|
error?: undefined;
|
|
54
|
+
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
56
|
summary?: undefined;
|
|
57
57
|
spans?: undefined;
|
|
58
58
|
error: any;
|
|
59
|
-
ok?: undefined;
|
|
60
59
|
id?: undefined;
|
|
60
|
+
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
62
|
summary?: undefined;
|
|
63
63
|
spans?: undefined;
|
|
@@ -34,10 +34,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
|
|
|
34
34
|
export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
35
35
|
error: any;
|
|
36
36
|
} | {
|
|
37
|
-
error?: undefined;
|
|
38
37
|
ok: boolean;
|
|
39
38
|
key: string;
|
|
40
39
|
baseUrlKey?: string;
|
|
41
40
|
scope: AgentEngineApiKeyScope;
|
|
41
|
+
error?: undefined;
|
|
42
42
|
}>>;
|
|
43
43
|
export {};
|
|
@@ -7,6 +7,11 @@ description: "Create an agent with a chat UI and call the same action from the a
|
|
|
7
7
|
|
|
8
8
|
<GettingStartedPaths />
|
|
9
9
|
|
|
10
|
+
Agent-Native is an open-source TypeScript framework for building autonomous
|
|
11
|
+
agents with intuitive UIs. You define each capability once as an
|
|
12
|
+
[action](/docs/actions-overview)—a typed function the agent uses as a tool and
|
|
13
|
+
the UI calls from code.
|
|
14
|
+
|
|
10
15
|
In this guide, you'll create an agent with a chat UI and call its included
|
|
11
16
|
`hello` action from the agent and the UI.
|
|
12
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "What is Agent-Native?"
|
|
3
|
-
description: "What Agent-Native is, how its shared action architecture works, and when to
|
|
3
|
+
description: "What Agent-Native is, why agents need UIs, how its shared action architecture works, and when to use it."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# What is Agent-Native?
|
|
@@ -81,7 +81,7 @@ the same work.
|
|
|
81
81
|
|
|
82
82
|
### [Getting Started](/docs/getting-started)
|
|
83
83
|
|
|
84
|
-
Create an
|
|
84
|
+
Create an agent with a UI and call the same action from the agent and UI.
|
|
85
85
|
|
|
86
86
|
### [Key Concepts](/docs/key-concepts)
|
|
87
87
|
|
|
@@ -90,11 +90,11 @@ together.
|
|
|
90
90
|
|
|
91
91
|
### [Agent Surfaces](/docs/agent-surfaces)
|
|
92
92
|
|
|
93
|
-
See how
|
|
94
|
-
agent.
|
|
93
|
+
See how agents work through a UI, chat, automation, or another agent.
|
|
95
94
|
|
|
96
95
|
### [Templates](/docs/cloneable-saas)
|
|
97
96
|
|
|
98
|
-
Start from a complete
|
|
97
|
+
Start from a complete open-source agent with a UI instead of starting from
|
|
98
|
+
scratch.
|
|
99
99
|
|
|
100
100
|
</Cards>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.177.1
|
|
3
|
+
"version": "0.177.1",
|
|
4
4
|
"description": "The agentic application framework for building autonomous agents with intuitive UIs",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -439,8 +439,8 @@
|
|
|
439
439
|
"y-protocols": "^1.0.7",
|
|
440
440
|
"yjs": "^13.6.32",
|
|
441
441
|
"zod": "^4.3.6",
|
|
442
|
-
"@agent-native/recap-cli": "0.5.
|
|
443
|
-
"@agent-native/toolkit": "^0.19.
|
|
442
|
+
"@agent-native/recap-cli": "0.5.27",
|
|
443
|
+
"@agent-native/toolkit": "^0.19.5"
|
|
444
444
|
},
|
|
445
445
|
"devDependencies": {
|
|
446
446
|
"@ai-sdk/anthropic": "^3.0.71",
|