@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 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
- <DesignCollaboratorsMenu
19496
- collaborators={designCollaborators}
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 if a parameter is unclear.
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
  `![alt](url)`.
73
- - For focused selected-text edits follow the `update-slide` / `get-deck` /
74
- `patch-deck` contract in `slide-editing` (one literal replacement,
75
- `expectedMatches: 1`, `baseContentHash` from `view-screen`); the same rule
76
- reaches external callers through this app's `mcp.instructions`.
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
- - When the user names no reference deck or design system, call
80
- `get-workspace-defaults` first (see `create-deck`).
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 text preview is not an exact range and 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.",
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("Replacement text (default: empty string)"),
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.object({
311
- op: z.literal("replace").optional(),
312
- find: z.string(),
313
- replace: z.string(),
314
- all: z.boolean().optional(),
315
- occurrence: z.number().int().positive().optional(),
316
- expectedMatches: z.number().int().nonnegative().optional(),
317
- required: z.boolean().optional(),
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 or current text replacement, use one literal replace with the exact text and expectedMatches=1. Each edit must match unless required=false.",
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 = find !== undefined || replace !== undefined;
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("One of --edits, --find, or --fullContent is required", {
422
- errorCode: "slide_edit_mode_required",
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 (replace !== undefined && find === undefined) {
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 that path.",
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) lines.push(`objectId: ${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 (item.text) {
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.textTruncated === true
329
- ? `textStatus: element preview may be truncated; use get-deck with slideId=${selectionSlide.id} before editing`
330
- : item.textTruncated === false
331
- ? `textStatus: element text is complete but is not an exact browser-range selection; use get-deck with slideId=${selectionSlide.id} before editing`
332
- : `textStatus: element preview status unknown; use get-deck with slideId=${selectionSlide.id} before editing`,
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: string;
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
- '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; do not load the full deck for that path. 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.',
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, currentSlideId, and exact selectedText from a browser range, call
200
- update-slide immediately with one literal edits replace using that exact text
201
- and expectedMatches=1;
202
- pass currentSlideContentHash as baseContentHash when available. Do not call
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, the literal match fails, or the request changes markup
206
- or layout, call get-deck
207
- with slideId only. First classify the remaining request scope. For a
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
  }>;
@@ -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 build an app this way."
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 app and call the same action from the agent and UI.
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 the same model supports apps led by a UI, chat, automation, or another
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 SaaS product instead of building up from Chat.
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-nightly-20260908183718",
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.26",
443
- "@agent-native/toolkit": "^0.19.4"
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",