@agent-native/core 0.161.9 → 0.161.10

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 (26) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/calendar/changelog/2026-08-18-calendar-event-time-editing-respects-the-event-timezone.md +6 -0
  3. package/corpus/templates/clips/app/components/player/video-player.tsx +1 -1
  4. package/corpus/templates/clips/app/i18n/en-US.ts +1 -1
  5. package/corpus/templates/clips/changelog/2026-08-18-centered-clip-play-button-and-comment-panel-hint.md +6 -0
  6. package/corpus/templates/factory/actions/start-builder-for-item.ts +241 -22
  7. package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -5
  8. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +50 -14
  9. package/corpus/templates/factory/server/triage/builder-executor.ts +1 -0
  10. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +231 -9
  11. package/corpus/templates/slides/app/context/DeckContext.tsx +99 -3
  12. package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
  13. package/corpus/templates/slides/app/pages/DeckEditor.tsx +8 -2
  14. package/dist/agent/chat-health-alert.d.ts +25 -0
  15. package/dist/agent/chat-health-alert.js +132 -0
  16. package/dist/collab/routes.d.ts +1 -1
  17. package/dist/collab/struct-routes.d.ts +1 -1
  18. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  19. package/dist/observability/routes.d.ts +5 -5
  20. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  21. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  22. package/dist/resources/handlers.d.ts +1 -1
  23. package/dist/secrets/routes.d.ts +9 -9
  24. package/dist/server/agent-chat-plugin.js +15 -2
  25. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  26. package/package.json +1 -1
@@ -0,0 +1,132 @@
1
+ import { getDbExec } from "../db/client.js";
2
+ import { notify } from "../notifications/registry.js";
3
+ import { getSetting, putSetting } from "../settings/store.js";
4
+ /**
5
+ * Pages the people who can act when an app's chat stops answering.
6
+ *
7
+ * The detector for this already existed as `scripts/chat-health.mjs --strict`,
8
+ * correctly calibrated and exiting 1 on a partial outage — but nothing ever ran
9
+ * it and nothing ever paged, so an app answering 11% of its turns was found by
10
+ * a user posting in Slack. This is the missing half: the same measurement, on
11
+ * the durable sweep that already drives stale reaping, scoped to the one app it
12
+ * runs in so no cross-app credential has to exist anywhere.
13
+ */
14
+ /** Turns are scored over this window on every sweep. */
15
+ const WINDOW_MS = 60 * 60_000;
16
+ /**
17
+ * Below this, a rate is noise: one failed turn out of two is 50% and means
18
+ * nothing. Chat-health's own fleet view showed apps sitting at 100% on a single
19
+ * turn all day.
20
+ */
21
+ const MIN_TURNS = 5;
22
+ /**
23
+ * Deliberately far above `chat-health`'s 0.1 review budget. That threshold
24
+ * answers "is this app degraded", which is a question for a dashboard. This one
25
+ * answers "is chat down", which is the only question worth waking someone for.
26
+ */
27
+ const BAD_RATE_THRESHOLD = 0.5;
28
+ /** One page per outage, not one per sweep. */
29
+ const COOLDOWN_MS = 60 * 60_000;
30
+ const LAST_ALERT_SETTING_KEY = "chat-health-alert:last-paged-at";
31
+ /**
32
+ * Scores the LAST run of each turn in the window, matching how
33
+ * `scripts/chat-health.mjs` reports so a page and the CLI never disagree.
34
+ * User-stopped turns are excluded: someone hitting Stop is not an outage.
35
+ */
36
+ async function countRecentTurns(since) {
37
+ const { rows } = await getDbExec().execute({
38
+ sql: `WITH ranked AS (
39
+ SELECT turn_id, status,
40
+ ROW_NUMBER() OVER (
41
+ PARTITION BY turn_id ORDER BY started_at DESC
42
+ ) AS rn
43
+ FROM agent_runs
44
+ WHERE started_at >= ?
45
+ AND turn_id IS NOT NULL
46
+ AND id NOT LIKE 'job-%'
47
+ )
48
+ SELECT COUNT(*) AS turns,
49
+ SUM(CASE WHEN status = 'errored' THEN 1 ELSE 0 END) AS bad
50
+ FROM ranked
51
+ WHERE rn = 1 AND status <> 'aborted'`,
52
+ args: [since],
53
+ });
54
+ const row = rows[0];
55
+ return {
56
+ turns: Number(row?.turns ?? 0),
57
+ bad: Number(row?.bad ?? 0),
58
+ };
59
+ }
60
+ /** Owners and admins — the people who can actually change a model or a key. */
61
+ async function alertRecipients() {
62
+ const { rows } = await getDbExec().execute({
63
+ sql: `SELECT DISTINCT email FROM org_members
64
+ WHERE role IN ('owner', 'admin')`,
65
+ args: [],
66
+ });
67
+ return rows
68
+ .map((r) => String(r.email ?? ""))
69
+ .filter(Boolean);
70
+ }
71
+ export async function checkChatHealthAndAlert(now = Date.now()) {
72
+ let counts;
73
+ try {
74
+ counts = await countRecentTurns(now - WINDOW_MS);
75
+ }
76
+ catch (error) {
77
+ // A check that could not read the ledger has not found the app healthy.
78
+ return { status: "check-failed", reason: String(error) };
79
+ }
80
+ if (counts.turns < MIN_TURNS) {
81
+ return { status: "insufficient-data", turns: counts.turns };
82
+ }
83
+ const badRate = counts.bad / counts.turns;
84
+ if (badRate < BAD_RATE_THRESHOLD) {
85
+ return { status: "healthy", turns: counts.turns, badRate };
86
+ }
87
+ const stored = await getSetting(LAST_ALERT_SETTING_KEY).catch(() => null);
88
+ const lastPagedAt = Number(stored?.at ?? 0);
89
+ if (Number.isFinite(lastPagedAt) && now - lastPagedAt < COOLDOWN_MS) {
90
+ return {
91
+ status: "cooldown",
92
+ retryAfterMs: COOLDOWN_MS - (now - lastPagedAt),
93
+ };
94
+ }
95
+ let recipients;
96
+ try {
97
+ recipients = await alertRecipients();
98
+ }
99
+ catch (error) {
100
+ return { status: "check-failed", reason: String(error) };
101
+ }
102
+ const pct = Math.round(badRate * 100);
103
+ for (const owner of recipients) {
104
+ await notify({
105
+ severity: "critical",
106
+ title: `Chat is failing: ${pct}% of turns ended without an answer`,
107
+ body: `${counts.bad} of ${counts.turns} turns in the last hour ended without ` +
108
+ `an answer. Run \`node scripts/chat-health.mjs --hours 1\` for the ` +
109
+ `per-reason breakdown.`,
110
+ metadata: {
111
+ turns: counts.turns,
112
+ bad: counts.bad,
113
+ badRate,
114
+ windowMs: WINDOW_MS,
115
+ },
116
+ }, { owner }).catch((error) => {
117
+ // One undeliverable recipient must not suppress the rest.
118
+ console.error("[chat-health-alert] notify failed for a recipient:", error);
119
+ });
120
+ }
121
+ // Stamped only after an attempt was actually made, so a failed page retries
122
+ // on the next sweep instead of being silenced by its own cooldown.
123
+ await putSetting(LAST_ALERT_SETTING_KEY, { at: now }).catch((error) => {
124
+ console.error("[chat-health-alert] could not stamp cooldown:", error);
125
+ });
126
+ return {
127
+ status: "alerted",
128
+ turns: counts.turns,
129
+ badRate,
130
+ recipients: recipients.length,
131
+ };
132
+ }
@@ -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
- error: string;
30
29
  ok?: undefined;
30
+ error: string;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
17
17
  id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
+ error?: undefined;
20
21
  configured?: undefined;
21
22
  connectPath?: undefined;
22
23
  url: string;
23
24
  id: string;
24
25
  provider: string;
25
- error?: undefined;
26
26
  }>;
27
27
  export default _default;
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
+ error?: undefined;
45
+ ok?: undefined;
44
46
  summary: import("./types.js").TraceSummary;
45
47
  spans: import("./types.js").TraceSpan[];
46
48
  id?: undefined;
49
+ } | {
47
50
  error?: undefined;
48
51
  ok?: undefined;
49
- } | {
50
52
  summary?: undefined;
51
53
  spans?: undefined;
52
54
  id: string;
53
- error?: undefined;
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
+ error?: undefined;
62
63
  summary?: undefined;
63
64
  spans?: undefined;
64
65
  id?: undefined;
65
66
  ok: boolean;
66
- error?: undefined;
67
67
  }>>;
@@ -75,8 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- message?: undefined;
79
78
  id?: undefined;
79
+ provider?: undefined;
80
80
  deleted?: undefined;
81
81
  providers: {
82
82
  id: string;
@@ -88,45 +88,45 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
88
88
  updatedAt: number;
89
89
  }[];
90
90
  count: number;
91
- provider?: undefined;
92
91
  found?: undefined;
93
92
  registered?: undefined;
94
93
  label?: undefined;
95
- } | {
96
94
  message?: undefined;
97
- count?: undefined;
95
+ } | {
98
96
  id?: undefined;
99
97
  deleted?: undefined;
100
98
  providers?: undefined;
99
+ count?: undefined;
101
100
  found: boolean;
102
101
  provider: import("../custom-registry.js").CustomProviderConfig;
103
102
  registered?: undefined;
104
103
  label?: undefined;
105
- } | {
106
104
  message?: undefined;
107
- count?: undefined;
105
+ } | {
106
+ provider?: undefined;
108
107
  deleted?: undefined;
109
108
  providers?: undefined;
110
- provider?: undefined;
109
+ count?: undefined;
111
110
  found: boolean;
112
111
  id: string;
113
112
  registered?: undefined;
114
113
  label?: undefined;
115
- } | {
116
114
  message?: undefined;
117
- count?: undefined;
118
- providers?: undefined;
115
+ } | {
119
116
  provider?: undefined;
117
+ providers?: undefined;
118
+ count?: undefined;
120
119
  found?: undefined;
121
120
  deleted: boolean;
122
121
  id: string;
123
122
  registered?: undefined;
124
123
  label?: undefined;
124
+ message?: undefined;
125
125
  } | {
126
- count?: undefined;
126
+ provider?: undefined;
127
127
  deleted?: undefined;
128
128
  providers?: undefined;
129
- provider?: undefined;
129
+ count?: undefined;
130
130
  found?: undefined;
131
131
  registered: boolean;
132
132
  id: string;
@@ -312,8 +312,6 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
312
312
  scope?: "org" | "user" | undefined;
313
313
  }, {
314
314
  id?: undefined;
315
- deleted?: undefined;
316
- provider?: undefined;
317
315
  message?: undefined;
318
316
  providers: {
319
317
  id: string;
@@ -325,45 +323,47 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
325
323
  updatedAt: number;
326
324
  }[];
327
325
  count: number;
326
+ provider?: undefined;
328
327
  found?: undefined;
328
+ deleted?: undefined;
329
329
  registered?: undefined;
330
330
  label?: undefined;
331
331
  } | {
332
332
  id?: undefined;
333
- deleted?: undefined;
334
333
  message?: undefined;
335
334
  providers?: undefined;
336
335
  count?: undefined;
337
336
  found: boolean;
338
337
  provider: import("../custom-registry.js").CustomProviderConfig;
338
+ deleted?: undefined;
339
339
  registered?: undefined;
340
340
  label?: undefined;
341
341
  } | {
342
- deleted?: undefined;
343
- provider?: undefined;
344
342
  message?: undefined;
345
343
  providers?: undefined;
346
344
  count?: undefined;
345
+ provider?: undefined;
347
346
  found: boolean;
348
347
  id: string;
348
+ deleted?: undefined;
349
349
  registered?: undefined;
350
350
  label?: undefined;
351
351
  } | {
352
- provider?: undefined;
353
352
  message?: undefined;
354
353
  providers?: undefined;
355
354
  count?: undefined;
355
+ provider?: undefined;
356
356
  found?: undefined;
357
357
  deleted: boolean;
358
358
  id: string;
359
359
  registered?: undefined;
360
360
  label?: undefined;
361
361
  } | {
362
- deleted?: undefined;
363
- provider?: undefined;
364
362
  providers?: undefined;
365
363
  count?: undefined;
364
+ provider?: undefined;
366
365
  found?: undefined;
366
+ deleted?: undefined;
367
367
  registered: boolean;
368
368
  id: string;
369
369
  label: string;
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
48
48
  }>;
49
49
  /** DELETE /_agent-native/resources/:id — delete a resource */
50
50
  export declare function handleDeleteResource(event: any): Promise<{
51
- ok?: undefined;
52
51
  error: string;
52
+ ok?: undefined;
53
53
  } | {
54
54
  error?: undefined;
55
55
  ok: boolean;
@@ -34,37 +34,37 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
34
34
  /** POST /_agent-native/secrets/:key — write a secret. */
35
35
  export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
36
36
  error: string;
37
- status?: undefined;
38
37
  ok?: undefined;
38
+ status?: undefined;
39
39
  } | {
40
+ error?: undefined;
40
41
  ok: boolean;
41
42
  status: string;
42
- error?: undefined;
43
43
  } | {
44
+ ok?: undefined;
44
45
  error: string;
45
46
  removed?: undefined;
46
- ok?: undefined;
47
47
  } | {
48
+ error?: undefined;
48
49
  ok: boolean;
49
50
  removed: boolean;
50
- error?: undefined;
51
51
  }>>;
52
52
  /**
53
53
  * POST /_agent-native/secrets/:key/test — validate an optional candidate value
54
54
  * or the current stored value without changing anything.
55
55
  */
56
56
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
57
+ ok?: undefined;
57
58
  error: string;
58
59
  note?: undefined;
59
- ok?: undefined;
60
60
  } | {
61
+ error?: undefined;
61
62
  ok: boolean;
62
63
  note?: undefined;
63
- error?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  ok: boolean;
66
67
  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;
98
99
  ok: boolean;
99
100
  key: string;
100
- error?: undefined;
101
101
  } | {
102
+ error?: undefined;
102
103
  ok: boolean;
103
104
  removed: boolean;
104
- error?: undefined;
105
105
  }>>;
@@ -5094,18 +5094,31 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
5094
5094
  console.error("[agent-chat] durable stale-run reap failed:", error);
5095
5095
  return null;
5096
5096
  });
5097
+ // Rides the same site-tick as the reap above, for the same reason:
5098
+ // it is the only durable driver on serverless. Never fatal to the
5099
+ // job sweep, and its own failure is a distinguishable outcome
5100
+ // rather than a silent healthy.
5101
+ const { checkChatHealthAndAlert } = await import("../agent/chat-health-alert.js");
5102
+ const chatHealth = await checkChatHealthAndAlert().catch((error) => {
5103
+ console.error("[agent-chat] chat-health alert failed:", error);
5104
+ return null;
5105
+ });
5097
5106
  try {
5098
5107
  // Jobs may request MCP tools, and `getActions` is synchronous —
5099
5108
  // hydrate before the sweep so a serverless container that never
5100
5109
  // eagerly initialized still resolves them.
5101
5110
  await ensureMcpInitialized();
5102
5111
  await processRecurringJobs(schedulerDeps);
5103
- return { ok: true, staleRunsReaped };
5112
+ return { ok: true, staleRunsReaped, chatHealth };
5104
5113
  }
5105
5114
  catch (error) {
5106
5115
  console.error("[recurring-jobs] Sweep route failed:", error);
5107
5116
  setResponseStatus(event, 500);
5108
- return { error: "Recurring-job sweep failed", staleRunsReaped };
5117
+ return {
5118
+ error: "Recurring-job sweep failed",
5119
+ staleRunsReaped,
5120
+ chatHealth,
5121
+ };
5109
5122
  }
5110
5123
  }));
5111
5124
  if (disableRecurringJobsRuntime) {
@@ -27,10 +27,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
27
27
  export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
28
28
  error: any;
29
29
  } | {
30
+ error?: undefined;
30
31
  ok: boolean;
31
32
  key: string;
32
33
  baseUrlKey?: string;
33
34
  scope: AgentEngineApiKeyScope;
34
- error?: undefined;
35
35
  }>>;
36
36
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.161.9",
3
+ "version": "0.161.10",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {