@agent-native/core 0.144.0 → 0.144.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.
Files changed (62) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +1 -1
  3. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +36 -14
  4. package/corpus/templates/clips/app/components/player/player-controls.tsx +0 -3
  5. package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -33
  6. package/corpus/templates/clips/app/components/player/video-player.tsx +58 -11
  7. package/corpus/templates/clips/changelog/2026-08-06-trimmed-clips-play-on-a-clean-timeline-without-cut-markers.md +6 -0
  8. package/corpus/templates/dispatch/AGENTS.md +8 -1
  9. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -0
  10. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +30 -15
  11. package/corpus/templates/dispatch/app/i18n-data.ts +131 -0
  12. package/corpus/templates/dispatch/app/routes/admin._index.tsx +4 -0
  13. package/corpus/templates/dispatch/app/routes/admin.agents.tsx +1 -0
  14. package/corpus/templates/dispatch/app/routes/admin.approvals.tsx +1 -0
  15. package/corpus/templates/dispatch/app/routes/admin.apps.$appId.tsx +1 -0
  16. package/corpus/templates/dispatch/app/routes/admin.apps.tsx +1 -0
  17. package/corpus/templates/dispatch/app/routes/admin.audit.tsx +1 -0
  18. package/corpus/templates/dispatch/app/routes/admin.automations.tsx +1 -0
  19. package/corpus/templates/dispatch/app/routes/admin.destinations.tsx +4 -0
  20. package/corpus/templates/dispatch/app/routes/admin.dreams.tsx +1 -0
  21. package/corpus/templates/dispatch/app/routes/admin.identities.tsx +1 -0
  22. package/corpus/templates/dispatch/app/routes/admin.integrations.tsx +4 -0
  23. package/corpus/templates/dispatch/app/routes/admin.messaging.tsx +1 -0
  24. package/corpus/templates/dispatch/app/routes/admin.metrics.tsx +1 -0
  25. package/corpus/templates/dispatch/app/routes/admin.new-app.tsx +1 -0
  26. package/corpus/templates/dispatch/app/routes/admin.operations.tsx +1 -0
  27. package/corpus/templates/dispatch/app/routes/admin.thread-debug.tsx +4 -0
  28. package/corpus/templates/dispatch/app/routes/admin.transactional-email.$appId.$id.tsx +4 -0
  29. package/corpus/templates/dispatch/app/routes/admin.transactional-email.tsx +4 -0
  30. package/corpus/templates/dispatch/app/routes/admin.tsx +1 -0
  31. package/corpus/templates/dispatch/app/routes/admin.vault.tsx +1 -0
  32. package/corpus/templates/dispatch/app/routes/admin.workspace.tsx +1 -0
  33. package/corpus/templates/dispatch/changelog/2026-08-06-dispatch-keeps-everyday-navigation-focused-while-management-.md +6 -0
  34. package/corpus/templates/slides/AGENTS.md +10 -12
  35. package/corpus/templates/slides/actions/export-pptx.ts +68 -0
  36. package/corpus/templates/slides/actions/get-deck.ts +12 -0
  37. package/corpus/templates/slides/actions/import-file.ts +130 -55
  38. package/corpus/templates/slides/actions/import-pptx.ts +108 -8
  39. package/corpus/templates/slides/actions/patch-deck.ts +51 -0
  40. package/corpus/templates/slides/actions/update-slide.ts +29 -3
  41. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +11 -0
  42. package/corpus/templates/slides/app/lib/create-deck-generation.ts +72 -11
  43. package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +89 -0
  44. package/corpus/templates/slides/app/pages/Index.tsx +85 -18
  45. package/corpus/templates/slides/changelog/2026-08-06-uploaded-decks-keep-their-slides-and-visuals-when-you-improv.md +6 -0
  46. package/corpus/templates/slides/server/handlers/import/html-converter.ts +13 -2
  47. package/corpus/templates/slides/server/lib/chat-attachments.ts +2 -2
  48. package/corpus/templates/slides/server/lib/source-import.ts +167 -0
  49. package/corpus/templates/slides/server/plugins/agent-chat.ts +8 -0
  50. package/dist/collab/routes.d.ts +1 -1
  51. package/dist/jobs/scheduler.js +79 -23
  52. package/dist/notifications/routes.d.ts +1 -1
  53. package/dist/observability/routes.d.ts +3 -3
  54. package/dist/progress/routes.d.ts +1 -1
  55. package/dist/secrets/routes.d.ts +6 -6
  56. package/dist/server/realtime-token.d.ts +1 -1
  57. package/docs/content/creating-templates.mdx +1 -1
  58. package/docs/content/getting-started-actions.mdx +38 -189
  59. package/docs/content/getting-started-database.mdx +80 -164
  60. package/docs/content/getting-started-pages.mdx +44 -155
  61. package/docs/content/getting-started.mdx +26 -106
  62. package/package.json +1 -1
@@ -0,0 +1,167 @@
1
+ export type SourceImportFormat = "pdf" | "pptx";
2
+
3
+ export interface SourceImportSlideSnapshot {
4
+ id: string;
5
+ text: string;
6
+ notes: string;
7
+ imageUrls: string[];
8
+ editableText: boolean;
9
+ }
10
+
11
+ export interface SourceImportMetadata {
12
+ mode: "source-preserving";
13
+ format: SourceImportFormat;
14
+ fidelity: "source-faithful" | "partial";
15
+ importedAt: string;
16
+ slideCount: number;
17
+ slideIds: string[];
18
+ slides: SourceImportSlideSnapshot[];
19
+ imagesSkipped?: number;
20
+ }
21
+
22
+ export function buildSourceImportMetadata(args: {
23
+ format: SourceImportFormat;
24
+ importedAt?: string;
25
+ slides: SourceImportSlideSnapshot[];
26
+ imagesSkipped?: number;
27
+ }): SourceImportMetadata {
28
+ const importedAt = args.importedAt ?? new Date().toISOString();
29
+ const imagesSkipped = args.imagesSkipped ?? 0;
30
+ return {
31
+ mode: "source-preserving",
32
+ format: args.format,
33
+ fidelity: imagesSkipped > 0 ? "partial" : "source-faithful",
34
+ importedAt,
35
+ slideCount: args.slides.length,
36
+ slideIds: args.slides.map((slide) => slide.id),
37
+ slides: args.slides,
38
+ ...(imagesSkipped > 0 ? { imagesSkipped } : {}),
39
+ };
40
+ }
41
+
42
+ export function mergeSourceImportMetadata(
43
+ existing: SourceImportMetadata | null,
44
+ incoming: SourceImportMetadata,
45
+ ): SourceImportMetadata {
46
+ if (!existing) return incoming;
47
+ if (existing.format !== incoming.format) {
48
+ throw new Error(
49
+ `Cannot append a ${incoming.format.toUpperCase()} source import to a ${existing.format.toUpperCase()} source-imported deck. Import matching source formats separately so every slide keeps its provenance.`,
50
+ );
51
+ }
52
+
53
+ const slidesById = new Map<string, SourceImportSlideSnapshot>();
54
+ for (const slide of existing.slides) slidesById.set(slide.id, slide);
55
+ for (const slide of incoming.slides) slidesById.set(slide.id, slide);
56
+ const slides = [...slidesById.values()];
57
+ const imagesSkipped =
58
+ (existing.imagesSkipped ?? 0) + (incoming.imagesSkipped ?? 0);
59
+
60
+ return {
61
+ ...incoming,
62
+ fidelity:
63
+ existing.fidelity === "partial" || incoming.fidelity === "partial"
64
+ ? "partial"
65
+ : "source-faithful",
66
+ slideCount: slides.length,
67
+ slideIds: slides.map((slide) => slide.id),
68
+ slides,
69
+ ...(imagesSkipped > 0 ? { imagesSkipped } : {}),
70
+ };
71
+ }
72
+
73
+ export function sourceImportForDeck(
74
+ value: unknown,
75
+ ): SourceImportMetadata | null {
76
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
77
+ const record = value as Partial<SourceImportMetadata>;
78
+ if (
79
+ record.mode !== "source-preserving" ||
80
+ (record.format !== "pdf" && record.format !== "pptx") ||
81
+ !Array.isArray(record.slides)
82
+ ) {
83
+ return null;
84
+ }
85
+ return record as SourceImportMetadata;
86
+ }
87
+
88
+ function extractImageUrls(content: string): string[] {
89
+ const urls: string[] = [];
90
+ const seen = new Set<string>();
91
+ const imagePattern = /<img\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/gi;
92
+ for (const match of content.matchAll(imagePattern)) {
93
+ const url = match[1]
94
+ ?.replace(/&amp;/g, "&")
95
+ .replace(/&quot;/g, '"')
96
+ .trim();
97
+ if (url && !seen.has(url)) {
98
+ seen.add(url);
99
+ urls.push(url);
100
+ }
101
+ }
102
+ return urls;
103
+ }
104
+
105
+ function sourceWords(value: string): Set<string> {
106
+ return new Set(
107
+ value
108
+ .toLowerCase()
109
+ .match(/[a-z0-9][a-z0-9'-]{3,}/g)
110
+ ?.map((word) => word.replace(/^['-]+|['-]+$/g, "")) ?? [],
111
+ );
112
+ }
113
+
114
+ /**
115
+ * Protects an imported deck from the common "make it prettier" failure mode:
116
+ * the agent replaces a source slide with a generic card and silently drops the
117
+ * original artwork or most of its factual copy. Explicit rewrite requests can
118
+ * opt out with preserveSource=false.
119
+ */
120
+ export function assertSourceSlidePreserved(args: {
121
+ metadata: SourceImportMetadata | null;
122
+ slideId: string;
123
+ nextContent?: string;
124
+ nextNotes?: string;
125
+ preserveSource?: boolean;
126
+ }): void {
127
+ if (args.preserveSource === false || !args.metadata) return;
128
+ const snapshot = args.metadata.slides.find(
129
+ (slide) => slide.id === args.slideId,
130
+ );
131
+ if (!snapshot) return;
132
+
133
+ if (
134
+ args.nextNotes !== undefined &&
135
+ snapshot.notes.length > 0 &&
136
+ args.nextNotes !== snapshot.notes
137
+ ) {
138
+ throw new Error(
139
+ `Source-preserving edit would remove or change imported speaker notes on slide ${args.slideId}. Preserve the source notes, or pass preserveSource=false only when the user explicitly asks for a rewrite.`,
140
+ );
141
+ }
142
+
143
+ if (args.nextContent === undefined) return;
144
+
145
+ const nextImageUrls = new Set(extractImageUrls(args.nextContent));
146
+ const missingImages = snapshot.imageUrls.filter(
147
+ (url) => !nextImageUrls.has(url),
148
+ );
149
+ if (missingImages.length > 0) {
150
+ throw new Error(
151
+ `Source-preserving edit would remove ${missingImages.length} original image(s) from slide ${args.slideId}. Keep every existing source image, or pass preserveSource=false only when the user explicitly asks for a rewrite.`,
152
+ );
153
+ }
154
+
155
+ if (!snapshot.editableText) return;
156
+ const originalWords = sourceWords(snapshot.text);
157
+ if (originalWords.size < 12) return;
158
+ const nextWords = sourceWords(args.nextContent.replace(/<[^>]+>/g, " "));
159
+ const retainedWords = [...originalWords].filter((word) =>
160
+ nextWords.has(word),
161
+ ).length;
162
+ if (retainedWords / originalWords.size < 0.35) {
163
+ throw new Error(
164
+ `Source-preserving edit would drop most of the original factual copy from slide ${args.slideId}. Preserve the source text, or pass preserveSource=false only when the user explicitly asks for a rewrite.`,
165
+ );
166
+ }
167
+ }
@@ -15,6 +15,8 @@ const INITIAL_TOOL_NAMES = [
15
15
  "get-layout-overflows",
16
16
  "list-decks",
17
17
  "get-deck",
18
+ "get-design-system",
19
+ "get-workspace-defaults",
18
20
  "get-deck-reference-context",
19
21
  "create-deck",
20
22
  "add-slide",
@@ -50,6 +52,12 @@ export default createAgentChatPlugin({
50
52
  prepareRequest: prepareSlidesChatAttachments,
51
53
  systemPrompt: `You are an AI deck assistant. You create, edit, import, export, style, share, and navigate decks through actions and shared application state. For a newly created presentation, use create-deck with slides: [] only when you are creating the deck yourself, then add-slide sequentially with full rendered HTML. The legacy generate-slides-ai action returns Markdown drafts and is not part of the persisted presentation workflow.
52
54
 
55
+ 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. If get-deck.sourceImport exists, preserve its slide count, order, IDs, factual copy, notes, images, charts, tables, diagrams, freeform objects, and source aspect ratio. Use update-slide on existing slide IDs. Do not use add-slide, delete, reorder, or replace source imagery with generic cards for this workflow. Verify the same slide IDs and count with get-deck after editing. If sourceImport.fidelity is partial or imagesSkipped is nonzero, stop and report the exact fidelity warning instead of claiming a reliable improvement.
56
+
57
+ For source-faithful PDF slides, keep the original full-page image and style around it with restrained design-system chrome such as a frame, edge treatment, caption, or safe overlay that does not obscure source content; never OCR-reconstruct the page from extracted text. For PPTX slides, preserve the imported positioned HTML and every uploaded source image. The update-slide action enforces these preservation rules by default; pass preserveSource=false only when the user explicitly requests a rewrite of that slide.
58
+
59
+ If the deck has designSystemId, call get-design-system before writing and follow its exact agentContext tokens, assets, and custom instructions. If the user asks for on-brand styling and no design system is linked, call get-workspace-defaults, link its usable design system with patch-deck, then call get-design-system. Do not improvise a generic Builder-like palette when configured Builder.io design-system context is available.
60
+
53
61
  Layout-fit workflow is strict. When the user asks to fix overflow, first call view-screen and inspect the deck-wide layout-fit section. If it says measurements are unknown, do not claim the deck fits. Call get-layout-overflows when you need the structured per-slide results. Read the affected slides with get-deck, then make one bounded structural repair pass with one patch-slide operation per affected slide in a single patch-deck call. Wait for the action result and verify the persisted HTML with get-deck before saying it is fixed. If a fresh measurement still reports overflow, make at most one focused follow-up repair based on that measurement; never loop, repeatedly re-measure, or claim success after a chat response alone.
54
62
 
55
63
  Fit means the main content fits the native content area. A small outer-wrapper spill is tolerated by the measurement, but cards, text, columns, and other visible content must fit. Never use zoom, transform: scale(), overflow: hidden/scroll, clipping, or a smaller-than-16px body font to hide overflow. Preserve manually positioned freeform objects and their data-slide-object-id values; repair normal-flow structure, copy, gaps, or slide padding instead. A successful action result must include the affected slide IDs; if it does not, report that no verified write occurred.
@@ -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;
@@ -14,7 +14,10 @@ export function buildJobContent(meta, body) {
14
14
  return buildJobResourceContent(meta, body);
15
15
  }
16
16
  const MAX_CONCURRENT_SCHEDULED_JOBS = 8;
17
+ const MAX_IDENTITY_PREFLIGHTS_PER_TICK = MAX_CONCURRENT_SCHEDULED_JOBS * 4;
18
+ const IDENTITY_FAILURE_RETRY_MS = 5 * 60_000;
17
19
  const _activeScheduledJobs = new Set();
20
+ const _preflightingScheduledJobs = new Set();
18
21
  // Skip the DB query on every tick if we recently confirmed no jobs exist.
19
22
  // `_hasJobsCache` is invalidated whenever a `jobs/*` resource is written or
20
23
  // deleted (subscribed below), and refreshed at most every 5 minutes.
@@ -88,7 +91,7 @@ export async function processRecurringJobs(deps) {
88
91
  if (!_hasJobsCache)
89
92
  return;
90
93
  const now = new Date();
91
- const dueJobs = [];
94
+ const dueJobCandidates = [];
92
95
  for (const resource of jobResources) {
93
96
  // Skip non-markdown or .keep files
94
97
  if (!resource.path.endsWith(".md"))
@@ -136,14 +139,56 @@ export async function processRecurringJobs(deps) {
136
139
  // Skip if body is empty
137
140
  if (!body.trim())
138
141
  continue;
142
+ if (hasRecentIdentityFailure(meta, now))
143
+ continue;
139
144
  const key = `${resource.owner}:${resource.path}`;
140
- if (_activeScheduledJobs.has(key))
145
+ if (_activeScheduledJobs.has(key) ||
146
+ _preflightingScheduledJobs.has(key)) {
141
147
  continue;
142
- if (_activeScheduledJobs.size >= MAX_CONCURRENT_SCHEDULED_JOBS)
148
+ }
149
+ dueJobCandidates.push({ key, resource, meta, body });
150
+ }
151
+ const preflightCandidates = [];
152
+ for (const candidate of dueJobCandidates) {
153
+ if (_activeScheduledJobs.size >= MAX_CONCURRENT_SCHEDULED_JOBS ||
154
+ preflightCandidates.length >= MAX_IDENTITY_PREFLIGHTS_PER_TICK) {
155
+ break;
156
+ }
157
+ if (_activeScheduledJobs.has(candidate.key) ||
158
+ _preflightingScheduledJobs.has(candidate.key)) {
143
159
  continue;
144
- _activeScheduledJobs.add(key);
145
- reservedJobKeys.add(key);
146
- dueJobs.push({ key, resource, meta, body });
160
+ }
161
+ preflightCandidates.push(candidate);
162
+ }
163
+ // Identity checks can reject stale jobs that remain due for admin review.
164
+ // Bound the checks so a large blocked backlog cannot consume the whole
165
+ // invocation or make valid jobs wait behind an unbounded stale queue.
166
+ const dueJobs = [];
167
+ for (const candidate of preflightCandidates) {
168
+ _preflightingScheduledJobs.add(candidate.key);
169
+ try {
170
+ const identity = await resolveBackgroundAutomationIdentity({
171
+ name: candidate.resource.path
172
+ .replace(/^jobs\//, "")
173
+ .replace(/\.md$/, ""),
174
+ meta: candidate.meta,
175
+ body: candidate.body,
176
+ resource: candidate.resource,
177
+ });
178
+ if (!identity.ok) {
179
+ await recordIdentityFailure(candidate.resource, candidate.meta, candidate.body, now, identity.reason);
180
+ continue;
181
+ }
182
+ if (_activeScheduledJobs.size >= MAX_CONCURRENT_SCHEDULED_JOBS) {
183
+ break;
184
+ }
185
+ _activeScheduledJobs.add(candidate.key);
186
+ reservedJobKeys.add(candidate.key);
187
+ dueJobs.push(candidate);
188
+ }
189
+ finally {
190
+ _preflightingScheduledJobs.delete(candidate.key);
191
+ }
147
192
  }
148
193
  if (dueJobs.length > 0)
149
194
  dispatchedAt = Date.now();
@@ -219,6 +264,33 @@ async function recoverStaleAutomationHistory(owner, path) {
219
264
  }
220
265
  }
221
266
  export const jobRunCutOffReason = backgroundRunCutOffReason;
267
+ async function recordIdentityFailure(resource, meta, body, now, reason, historyId) {
268
+ const jobName = resource.path.replace(/^jobs\//, "").replace(/\.md$/, "");
269
+ console.warn(`[recurring-jobs] Skipping job "${jobName}": ${reason}. ` +
270
+ `User/membership no longer valid — leaving cron entry for admin review.`);
271
+ // Keep blocked jobs due so an admin can find them, but do not let their
272
+ // persistent failure consume an execution slot on every scheduler sweep.
273
+ const alreadyRecorded = meta.lastStatus === "skipped" && meta.lastError === reason;
274
+ meta.lastCheck = now.toISOString();
275
+ meta.lastStatus = "skipped";
276
+ meta.lastError = reason;
277
+ if (!alreadyRecorded)
278
+ await updateResource(resource, meta, body);
279
+ if (historyId) {
280
+ await finishAutomationRun(historyId, "error", `Automation did not run: ${reason}. No delivery was confirmed.`);
281
+ }
282
+ return { status: "skipped", error: reason };
283
+ }
284
+ function hasRecentIdentityFailure(meta, now) {
285
+ if (meta.lastStatus !== "skipped" || !meta.lastCheck || !meta.lastError) {
286
+ return false;
287
+ }
288
+ const lastCheckMs = Date.parse(meta.lastCheck);
289
+ const elapsedMs = now.getTime() - lastCheckMs;
290
+ return (Number.isFinite(lastCheckMs) &&
291
+ elapsedMs >= 0 &&
292
+ elapsedMs < IDENTITY_FAILURE_RETRY_MS);
293
+ }
222
294
  async function executeJob(resource, meta, body, deps, now, options = {}) {
223
295
  const jobName = resource.path.replace(/^jobs\//, "").replace(/\.md$/, "");
224
296
  const jobContext = {
@@ -234,23 +306,7 @@ async function executeJob(resource, meta, body, deps, now, options = {}) {
234
306
  // failure; leave the cron entry alone so an admin can purge after
235
307
  // investigation.
236
308
  if (!identity.ok) {
237
- console.warn(`[recurring-jobs] Skipping job "${jobName}": ${identity.reason}. ` +
238
- `User/membership no longer valid — leaving cron entry for admin review.`);
239
- // Mark as skipped without resetting nextRun so an admin can find it.
240
- // `lastRun` is deliberately untouched: the job did not run, and stamping
241
- // it here made a permanently blocked job look like it ran every minute.
242
- // Re-writing an unchanged resource on every tick also churns the poll
243
- // stream, so only persist when the failure state actually changed.
244
- const alreadyRecorded = meta.lastStatus === "skipped" && meta.lastError === identity.reason;
245
- meta.lastCheck = now.toISOString();
246
- meta.lastStatus = "skipped";
247
- meta.lastError = identity.reason;
248
- if (!alreadyRecorded)
249
- await updateResource(resource, meta, body);
250
- if (options.historyId) {
251
- await finishAutomationRun(options.historyId, "error", `Automation did not run: ${identity.reason}. No delivery was confirmed.`);
252
- }
253
- return { status: "skipped", error: identity.reason };
309
+ return recordIdentityFailure(resource, meta, body, now, identity.reason, options.historyId);
254
310
  }
255
311
  const jobUserEmail = identity.identity.userEmail;
256
312
  const jobOrgId = identity.identity.orgId;
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
28
28
  } | {
29
29
  count?: undefined;
30
30
  updated?: undefined;
31
- error?: undefined;
32
31
  ok: boolean;
32
+ error?: undefined;
33
33
  }>>;
@@ -42,22 +42,22 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
42
42
  avgEvalScore: number;
43
43
  } | {
44
44
  error?: undefined;
45
+ ok?: undefined;
45
46
  summary: import("./types.js").TraceSummary;
46
47
  spans: import("./types.js").TraceSpan[];
47
48
  id?: undefined;
48
- ok?: undefined;
49
49
  } | {
50
50
  error?: undefined;
51
+ ok?: undefined;
51
52
  summary?: undefined;
52
53
  spans?: undefined;
53
54
  id: string;
54
- ok?: undefined;
55
55
  } | {
56
+ ok?: undefined;
56
57
  summary?: undefined;
57
58
  spans?: undefined;
58
59
  id?: undefined;
59
60
  error: any;
60
- ok?: undefined;
61
61
  } | {
62
62
  error?: undefined;
63
63
  summary?: undefined;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- ok: boolean;
19
18
  error?: undefined;
19
+ ok: boolean;
20
20
  }>>;
@@ -37,17 +37,17 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
37
37
  ok?: undefined;
38
38
  status?: undefined;
39
39
  } | {
40
- error?: undefined;
41
40
  ok: boolean;
42
41
  status: string;
42
+ error?: undefined;
43
43
  } | {
44
44
  ok?: undefined;
45
45
  error: string;
46
46
  removed?: undefined;
47
47
  } | {
48
- error?: undefined;
49
48
  ok: boolean;
50
49
  removed: boolean;
50
+ error?: undefined;
51
51
  }>>;
52
52
  /**
53
53
  * POST /_agent-native/secrets/:key/test — validate an optional candidate value
@@ -58,13 +58,13 @@ export declare function createTestSecretHandler(): import("h3").EventHandlerWith
58
58
  error: string;
59
59
  note?: undefined;
60
60
  } | {
61
- error?: undefined;
62
61
  ok: boolean;
63
62
  note?: undefined;
64
- } | {
65
63
  error?: undefined;
64
+ } | {
66
65
  ok: boolean;
67
66
  note: string;
67
+ error?: undefined;
68
68
  } | {
69
69
  note?: undefined;
70
70
  ok: boolean;
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
95
95
  export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
96
96
  error: string;
97
97
  } | {
98
- error?: undefined;
99
98
  ok: boolean;
100
99
  key: string;
101
- } | {
102
100
  error?: undefined;
101
+ } | {
103
102
  ok: boolean;
104
103
  removed: boolean;
104
+ error?: undefined;
105
105
  }>>;
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
26
26
  expiresAt?: undefined;
27
27
  ttlSeconds?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  token: string;
30
31
  expiresAt: string;
31
32
  ttlSeconds: number;
32
- error?: undefined;
33
33
  }>>;
@@ -49,7 +49,7 @@ npx @agent-native/core@latest create my-platform
49
49
 
50
50
  Chat gives you auth, durable chat threads, SQL-backed resources, tools, application state, actions, and polling sync. You add the domain model and product UI.
51
51
 
52
- If you are not building a reusable UI template yet, use the headless on-ramp in [Getting Started](/docs/getting-started#create-your-app): define one action, run it with `pnpm agent`, and add UI later when the workflow needs a durable surface.
52
+ If you are not building a reusable UI template yet, use [Automation-First Apps](/docs/pure-agent-apps): define actions, run them without a browser, and add UI later when the workflow needs a durable surface.
53
53
 
54
54
  ## Project Structure {#project-structure}
55
55