@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +1 -1
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +36 -14
- package/corpus/templates/clips/app/components/player/player-controls.tsx +0 -3
- package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -33
- package/corpus/templates/clips/app/components/player/video-player.tsx +58 -11
- package/corpus/templates/clips/changelog/2026-08-06-trimmed-clips-play-on-a-clean-timeline-without-cut-markers.md +6 -0
- package/corpus/templates/dispatch/AGENTS.md +8 -1
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -0
- package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +30 -15
- package/corpus/templates/dispatch/app/i18n-data.ts +131 -0
- package/corpus/templates/dispatch/app/routes/admin._index.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.agents.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.approvals.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.apps.$appId.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.apps.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.audit.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.automations.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.destinations.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.dreams.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.identities.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.integrations.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.messaging.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.metrics.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.new-app.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.operations.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.thread-debug.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.transactional-email.$appId.$id.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.transactional-email.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.vault.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.workspace.tsx +1 -0
- package/corpus/templates/dispatch/changelog/2026-08-06-dispatch-keeps-everyday-navigation-focused-while-management-.md +6 -0
- package/corpus/templates/slides/AGENTS.md +10 -12
- package/corpus/templates/slides/actions/export-pptx.ts +68 -0
- package/corpus/templates/slides/actions/get-deck.ts +12 -0
- package/corpus/templates/slides/actions/import-file.ts +130 -55
- package/corpus/templates/slides/actions/import-pptx.ts +108 -8
- package/corpus/templates/slides/actions/patch-deck.ts +51 -0
- package/corpus/templates/slides/actions/update-slide.ts +29 -3
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +11 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +72 -11
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +89 -0
- package/corpus/templates/slides/app/pages/Index.tsx +85 -18
- package/corpus/templates/slides/changelog/2026-08-06-uploaded-decks-keep-their-slides-and-visuals-when-you-improv.md +6 -0
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +13 -2
- package/corpus/templates/slides/server/lib/chat-attachments.ts +2 -2
- package/corpus/templates/slides/server/lib/source-import.ts +167 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +8 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/jobs/scheduler.js +79 -23
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/realtime-token.d.ts +1 -1
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/getting-started-actions.mdx +38 -189
- package/docs/content/getting-started-database.mdx +80 -164
- package/docs/content/getting-started-pages.mdx +44 -155
- package/docs/content/getting-started.mdx +26 -106
- 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(/&/g, "&")
|
|
95
|
+
.replace(/"/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.
|
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;
|
package/dist/jobs/scheduler.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -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;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -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
|
|
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
|
|