@agent-native/core 0.174.1 → 0.174.2-nightly-20260825204701

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 CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8614
34
+ - template files: 8615
@@ -68,6 +68,10 @@ Each field is a JSON object:
68
68
  - `label` — display label
69
69
  - `required` — boolean
70
70
 
71
+ Always pass fields as these complete objects. Never encode a field as shorthand
72
+ text such as `text: Enter a name`; `patch-form-fields` upserts also require the
73
+ field `id` so they cannot be ambiguous.
74
+
71
75
  ### Optional properties
72
76
  - `placeholder` — input placeholder text
73
77
  - `description` — help text below the field
@@ -49,7 +49,9 @@ export default defineAction({
49
49
  fields: z
50
50
  .union([z.string(), z.array(z.any())])
51
51
  .optional()
52
- .describe("Array of form fields (or JSON string of the same)"),
52
+ .describe(
53
+ "Array of complete field objects with id, type, label, and required (or JSON string of the same); never use shorthand strings such as 'text: Enter a name'.",
54
+ ),
53
55
  settings: z
54
56
  .union([z.string(), z.record(z.string(), z.any())])
55
57
  .optional()
@@ -24,7 +24,10 @@ import { z } from "zod";
24
24
  import { getDb, schema } from "../server/db/index.js";
25
25
  import { applyFieldOps } from "../server/lib/merge-fields.js";
26
26
  import { invalidatePublicFormCache } from "../server/lib/public-form-ssr.js";
27
- import { assertValidFields } from "../server/lib/validate-fields.js";
27
+ import {
28
+ assertValidFields,
29
+ normalizePersistedFields,
30
+ } from "../server/lib/validate-fields.js";
28
31
  import type { FormField } from "../shared/types.js";
29
32
  import { assertPublishableForm } from "./lib/assert-publishable-form.js";
30
33
 
@@ -61,7 +64,11 @@ export function withFormLock<T>(
61
64
  const fieldOpSchema = z.union([
62
65
  z.object({
63
66
  op: z.literal("upsert"),
64
- field: z.record(z.string(), z.any()),
67
+ field: z
68
+ .record(z.string(), z.any())
69
+ .describe(
70
+ "Complete field object with id, type, label, and required; never use shorthand strings.",
71
+ ),
65
72
  }),
66
73
  z.object({
67
74
  op: z.literal("remove"),
@@ -117,7 +124,9 @@ export default defineAction({
117
124
  // Parse current fields from the DB row.
118
125
  let currentFields: FormField[] = [];
119
126
  try {
120
- currentFields = JSON.parse(existing.fields) as FormField[];
127
+ currentFields = normalizePersistedFields(
128
+ JSON.parse(existing.fields),
129
+ ) as FormField[];
121
130
  } catch {
122
131
  currentFields = [];
123
132
  }
@@ -35,7 +35,9 @@ export default defineAction({
35
35
  fields: z
36
36
  .union([z.string(), z.array(z.any())])
37
37
  .optional()
38
- .describe("Array of form fields (or JSON string of the same)"),
38
+ .describe(
39
+ "Array of complete field objects with id, type, label, and required (or JSON string of the same); never use shorthand strings such as 'text: Enter a name'.",
40
+ ),
39
41
  settings: z
40
42
  .union([z.string(), z.record(z.string(), z.any())])
41
43
  .optional()
@@ -0,0 +1,5 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-25
4
+ ---
5
+ Forms agents now retry rejected field edits with valid field objects.
@@ -4,6 +4,19 @@
4
4
  // runtime — an unrestricted id like `x" onfocus="alert(1)` would otherwise
5
5
  // stored-XSS every anonymous submitter of a published form.
6
6
  export const FIELD_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
7
+ const FIELD_TYPES = new Set([
8
+ "text",
9
+ "email",
10
+ "number",
11
+ "textarea",
12
+ "select",
13
+ "multiselect",
14
+ "checkbox",
15
+ "radio",
16
+ "date",
17
+ "rating",
18
+ "scale",
19
+ ]);
7
20
  const CONDITIONAL_OPERATORS = new Set(["equals", "not_equals", "contains"]);
8
21
 
9
22
  /**
@@ -49,6 +62,28 @@ export function normalizeFieldIds(fields: unknown): unknown {
49
62
  });
50
63
  }
51
64
 
65
+ /**
66
+ * Keeps granular edits compatible with fields written before the current
67
+ * schema. The UI already renders unknown types as text and treats a missing
68
+ * required flag as false, so patch reads must make the same repair before the
69
+ * strict persistence check runs.
70
+ */
71
+ export function normalizePersistedFields(fields: unknown): unknown {
72
+ if (!Array.isArray(fields)) return fields;
73
+ return fields.map((field) => {
74
+ if (field == null || typeof field !== "object" || Array.isArray(field)) {
75
+ return field;
76
+ }
77
+ const f = field as Record<string, unknown>;
78
+ return {
79
+ ...f,
80
+ type:
81
+ typeof f.type === "string" && FIELD_TYPES.has(f.type) ? f.type : "text",
82
+ required: f.required === undefined ? false : f.required,
83
+ };
84
+ });
85
+ }
86
+
52
87
  export function assertValidFields(fields: unknown): void {
53
88
  if (!Array.isArray(fields)) {
54
89
  throw new Error("fields must be an array");
@@ -71,6 +106,18 @@ export function assertValidFields(fields: unknown): void {
71
106
  }
72
107
  seenIds.add(id);
73
108
 
109
+ if (typeof f.type !== "string" || !FIELD_TYPES.has(f.type)) {
110
+ throw new Error(
111
+ `field #${idx + 1} has an invalid type ${JSON.stringify(f.type)}`,
112
+ );
113
+ }
114
+ if (typeof f.label !== "string") {
115
+ throw new Error(`field #${idx + 1} label must be a string`);
116
+ }
117
+ if (typeof f.required !== "boolean") {
118
+ throw new Error(`field #${idx + 1} required must be a boolean`);
119
+ }
120
+
74
121
  const cond = f.conditional;
75
122
  if (cond !== undefined) {
76
123
  if (cond == null || typeof cond !== "object") {
@@ -11,6 +11,7 @@ export const FORMS_SYSTEM_PROMPT = `## Agent-Native Forms
11
11
  You are the in-app agent for Agent-Native Forms, a chat-first form builder and response workspace. Help users create, edit, publish, share, and analyze forms through the registered actions. The actions are the source of truth for database access, validation, permissions, UI sync, and business logic.
12
12
 
13
13
  Core rules:
14
+ - Build every field as a complete object with id, type, label, and required; never send shorthand strings such as "text: Enter a name". If a form or field action rejects its payload, correct the arguments and retry in the same turn, then verify the saved fields. If a draft was created before the failure, repair that draft instead of explaining the fix and stopping.
14
15
  - Use Forms actions instead of raw database access. Raw database tools are not available in this app.
15
16
  - Never hardcode API keys, tokens, webhook URLs, signing secrets, customer data, or credential-looking literals. Use placeholders or configured integrations.
16
17
  - Inspect the current state when it matters. Use \`view-screen\` when the active form, selected field, publish state, response table, or current route is unclear.
@@ -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;
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
+ error?: undefined;
44
45
  summary: import("./types.js").TraceSummary;
45
46
  spans: import("./types.js").TraceSpan[];
46
47
  id?: undefined;
47
- error?: undefined;
48
48
  ok?: undefined;
49
49
  } | {
50
+ error?: undefined;
50
51
  summary?: undefined;
51
52
  spans?: undefined;
52
53
  id: string;
53
- error?: undefined;
54
54
  ok?: undefined;
55
55
  } | {
56
56
  summary?: undefined;
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
59
59
  error: any;
60
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
  }>>;
@@ -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;
37
38
  ok: boolean;
38
39
  key: string;
39
40
  baseUrlKey?: string;
40
41
  scope: AgentEngineApiKeyScope;
41
- error?: undefined;
42
42
  }>>;
43
43
  export {};
@@ -2593,7 +2593,10 @@ ${signInJourneyInlineScript()}
2593
2593
  }
2594
2594
  }
2595
2595
  function __anFinishOAuthExchange(ret, flowId, sessionToken) {
2596
+ __anStopOAuthPopupWatch();
2597
+ __anStopNativeOAuthAbandonment();
2596
2598
  __anGoogleSignInInFlight = false;
2599
+ __anClearGoogleSignInFlow();
2597
2600
  __anMagicLinkInFlight = false;
2598
2601
  if (__anIsBuilderPreview()) {
2599
2602
  if (sessionToken) {
@@ -2863,8 +2866,18 @@ ${identitySsoScript}
2863
2866
  return __anIsAgentNativeDesktop() ? 'redirect' : 'popup';
2864
2867
  }
2865
2868
  var __anOAuthPollTimer = null;
2869
+ var __anOAuthPopupWatchTimer = null;
2870
+ var __anOAuthPopupCloseGraceTimer = null;
2871
+ var __anOAuthPopupCloseGraceMs = 5000;
2872
+ var __anNativeOAuthFlowId = null;
2873
+ var __anNativeOAuthRequestPending = false;
2874
+ var __anNativeOAuthReturnObserved = false;
2875
+ var __anNativeOAuthAbandonGraceTimer = null;
2876
+ var __anNativeOAuthAbandonGraceMs = 5000;
2866
2877
  var __anOAuthPollCount = 0;
2867
2878
  var __anGoogleSignInInFlight = false;
2879
+ var __anGoogleSignInFlowId = null;
2880
+ var __anGoogleSignInRecoveryFlowId = null;
2868
2881
  var __anMagicLinkInFlight = false;
2869
2882
  var __anGoogleRecoverBound = false;
2870
2883
  function __anNewOAuthFlowId() {
@@ -2921,8 +2934,139 @@ ${identitySsoScript}
2921
2934
  __anOAuthPollTimer = null;
2922
2935
  }
2923
2936
  }
2937
+ function __anStopOAuthPopupWatch() {
2938
+ if (__anOAuthPopupWatchTimer) {
2939
+ clearInterval(__anOAuthPopupWatchTimer);
2940
+ __anOAuthPopupWatchTimer = null;
2941
+ }
2942
+ if (__anOAuthPopupCloseGraceTimer) {
2943
+ clearTimeout(__anOAuthPopupCloseGraceTimer);
2944
+ __anOAuthPopupCloseGraceTimer = null;
2945
+ }
2946
+ }
2947
+ function __anStopNativeOAuthAbandonment() {
2948
+ __anCancelNativeOAuthAbandonment();
2949
+ __anNativeOAuthFlowId = null;
2950
+ __anNativeOAuthRequestPending = false;
2951
+ }
2952
+ function __anCancelNativeOAuthAbandonment() {
2953
+ if (__anNativeOAuthAbandonGraceTimer) {
2954
+ clearTimeout(__anNativeOAuthAbandonGraceTimer);
2955
+ __anNativeOAuthAbandonGraceTimer = null;
2956
+ }
2957
+ __anNativeOAuthReturnObserved = false;
2958
+ }
2959
+ function __anBeginNativeOAuth(flowId) {
2960
+ __anStopNativeOAuthAbandonment();
2961
+ __anNativeOAuthFlowId = flowId;
2962
+ __anNativeOAuthRequestPending = true;
2963
+ __anNativeOAuthReturnObserved = false;
2964
+ }
2965
+ function __anMarkNativeOAuthPolling(flowId) {
2966
+ if (__anNativeOAuthFlowId !== flowId) return;
2967
+ __anNativeOAuthRequestPending = false;
2968
+ }
2969
+ function __anIsCurrentNativeOAuth(flowId) {
2970
+ return __anNativeOAuthFlowId === flowId && __anIsCurrentGoogleSignInFlow(flowId);
2971
+ }
2972
+ function __anFinalizeNativeOAuthAbandonment(flowId) {
2973
+ if (!__anIsCurrentNativeOAuth(flowId) || !__anOAuthPollTimer) return;
2974
+ if (document.visibilityState === 'hidden') {
2975
+ __anCancelNativeOAuthAbandonment();
2976
+ return;
2977
+ }
2978
+ __anStopNativeOAuthAbandonment();
2979
+ if (__anInvalidateGoogleSignInFlow(flowId)) {
2980
+ __anStopOAuthExchangePolling();
2981
+ __anRecoverGoogleSignInAfterReturn(flowId);
2982
+ }
2983
+ }
2984
+ function __anScheduleNativeOAuthAbandonment(flowId) {
2985
+ // Returning focus is the only signal that the system-browser ceremony was
2986
+ // abandoned. Preserve the exchange poll briefly for a late deep-link.
2987
+ if (
2988
+ !__anIsCurrentNativeOAuth(flowId) ||
2989
+ __anNativeOAuthRequestPending ||
2990
+ !__anNativeOAuthReturnObserved ||
2991
+ !__anOAuthPollTimer ||
2992
+ __anNativeOAuthAbandonGraceTimer
2993
+ ) return;
2994
+ __anNativeOAuthAbandonGraceTimer = setTimeout(function() {
2995
+ __anNativeOAuthAbandonGraceTimer = null;
2996
+ __anFinalizeNativeOAuthAbandonment(flowId);
2997
+ }, __anNativeOAuthAbandonGraceMs);
2998
+ }
2999
+ function __anBeginGoogleSignInFlow(flowId) {
3000
+ __anGoogleSignInFlowId = flowId;
3001
+ __anGoogleSignInRecoveryFlowId = null;
3002
+ __anGoogleSignInInFlight = true;
3003
+ }
3004
+ function __anIsCurrentGoogleSignInFlow(flowId) {
3005
+ return __anGoogleSignInInFlight && __anGoogleSignInFlowId === flowId;
3006
+ }
3007
+ function __anClearGoogleSignInFlow() {
3008
+ __anGoogleSignInFlowId = null;
3009
+ __anGoogleSignInRecoveryFlowId = null;
3010
+ }
3011
+ function __anInvalidateGoogleSignInFlow(flowId) {
3012
+ if (!__anIsCurrentGoogleSignInFlow(flowId)) return false;
3013
+ __anGoogleSignInFlowId = null;
3014
+ __anGoogleSignInRecoveryFlowId = flowId;
3015
+ return true;
3016
+ }
3017
+ function __anCanRecoverGoogleSignInFlow(flowId, expectedFlowId) {
3018
+ // Focus can return while either the system-browser exchange or the popup
3019
+ // exchange is still active. Let that exchange finish before recovering.
3020
+ if (!flowId && (__anNativeOAuthFlowId || __anOAuthPollTimer || __anOAuthPopupWatchTimer)) return false;
3021
+ if (flowId) {
3022
+ return __anGoogleSignInInFlight &&
3023
+ !__anGoogleSignInFlowId &&
3024
+ __anGoogleSignInRecoveryFlowId === flowId;
3025
+ }
3026
+ return __anGoogleSignInInFlight && __anGoogleSignInFlowId === expectedFlowId;
3027
+ }
3028
+ function __anFinalizeOAuthPopupClose(flowId) {
3029
+ if (!__anIsCurrentGoogleSignInFlow(flowId)) return;
3030
+ __anOAuthPopupCloseGraceTimer = null;
3031
+ if (__anInvalidateGoogleSignInFlow(flowId)) {
3032
+ __anStopOAuthExchangePolling();
3033
+ __anRecoverGoogleSignInAfterReturn(flowId);
3034
+ }
3035
+ }
3036
+ function __anHandleOAuthPopupClosed(flowId) {
3037
+ __anStopOAuthPopupWatch();
3038
+ // The success callback closes its tab 250ms after staging the token, but
3039
+ // the opener polls once per second. Let an active exchange win that race.
3040
+ if (__anOAuthPollTimer) {
3041
+ __anOAuthPopupCloseGraceTimer = setTimeout(function() {
3042
+ __anFinalizeOAuthPopupClose(flowId);
3043
+ }, __anOAuthPopupCloseGraceMs);
3044
+ return;
3045
+ }
3046
+ __anFinalizeOAuthPopupClose(flowId);
3047
+ }
3048
+ function __anWatchOAuthPopupClose(popup, flowId) {
3049
+ __anStopOAuthPopupWatch();
3050
+ __anOAuthPopupWatchTimer = setInterval(function() {
3051
+ if (!__anIsCurrentGoogleSignInFlow(flowId)) {
3052
+ __anStopOAuthPopupWatch();
3053
+ return;
3054
+ }
3055
+ var closed = false;
3056
+ try {
3057
+ closed = popup.closed === true;
3058
+ } catch(e) {
3059
+ __anHandleOAuthPopupClosed(flowId);
3060
+ return;
3061
+ }
3062
+ if (!closed) return;
3063
+ __anHandleOAuthPopupClosed(flowId);
3064
+ }, 500);
3065
+ }
2924
3066
  function __anShowAuthExchangeError(err, btn, message, kind) {
2925
3067
  __anStopOAuthExchangePolling();
3068
+ __anStopOAuthPopupWatch();
3069
+ __anStopNativeOAuthAbandonment();
2926
3070
  err.textContent = message;
2927
3071
  err.classList.add('show', 'error');
2928
3072
  btn.disabled = false;
@@ -2931,22 +3075,30 @@ ${identitySsoScript}
2931
3075
  showMagicLinkForm();
2932
3076
  } else {
2933
3077
  __anGoogleSignInInFlight = false;
3078
+ __anClearGoogleSignInFlow();
2934
3079
  }
2935
3080
  }
2936
3081
  function __anShowOAuthError(err, btn, message) {
2937
3082
  __anShowAuthExchangeError(err, btn, message, 'google');
2938
3083
  }
2939
- function __anRecoverGoogleSignInAfterReturn() {
3084
+ function __anRecoverGoogleSignInAfterReturn(flowId) {
2940
3085
  // The user left for the Google sign-in window and came back. If the flow
2941
3086
  // never completed (e.g. they closed the window to switch profiles), the
2942
3087
  // button is stuck disabled with no error path firing for up to 5 minutes.
2943
3088
  // Re-enable it so they can retry. Wait briefly first so a genuinely
2944
3089
  // in-flight exchange can still finish and navigate without a flicker.
2945
- if (!__anGoogleSignInInFlight) return;
3090
+ if (!flowId && __anNativeOAuthFlowId) {
3091
+ __anNativeOAuthReturnObserved = true;
3092
+ __anScheduleNativeOAuthAbandonment(__anNativeOAuthFlowId);
3093
+ return;
3094
+ }
3095
+ var expectedFlowId = flowId || __anGoogleSignInFlowId;
3096
+ if (!__anCanRecoverGoogleSignInFlow(flowId, expectedFlowId)) return;
2946
3097
  setTimeout(function() {
3098
+ if (!__anCanRecoverGoogleSignInFlow(flowId, expectedFlowId)) return;
2947
3099
  __anMaybeRedirectSignedIn(__anResumeHref()).then(function(redirected) {
3100
+ if (!__anCanRecoverGoogleSignInFlow(flowId, expectedFlowId)) return;
2948
3101
  if (redirected) return;
2949
- if (!__anGoogleSignInInFlight) return;
2950
3102
  var btn = document.getElementById('google-btn');
2951
3103
  if (!btn || !btn.disabled) return;
2952
3104
  // Keep the desktop-exchange poll alive. Agent Native Desktop opens
@@ -2954,15 +3106,25 @@ ${identitySsoScript}
2954
3106
  // callback has stored the session token.
2955
3107
  btn.disabled = false;
2956
3108
  __anGoogleSignInInFlight = false;
3109
+ __anClearGoogleSignInFlow();
2957
3110
  });
2958
3111
  }, 1200);
2959
3112
  }
2960
3113
  function __anBindGoogleRecover() {
2961
3114
  if (__anGoogleRecoverBound) return;
2962
3115
  __anGoogleRecoverBound = true;
2963
- window.addEventListener('focus', __anRecoverGoogleSignInAfterReturn);
3116
+ window.addEventListener('focus', function() {
3117
+ __anRecoverGoogleSignInAfterReturn();
3118
+ });
3119
+ window.addEventListener('blur', function() {
3120
+ __anCancelNativeOAuthAbandonment();
3121
+ });
2964
3122
  document.addEventListener('visibilitychange', function() {
2965
- if (document.visibilityState === 'visible') __anRecoverGoogleSignInAfterReturn();
3123
+ if (document.visibilityState === 'visible') {
3124
+ __anRecoverGoogleSignInAfterReturn();
3125
+ } else {
3126
+ __anCancelNativeOAuthAbandonment();
3127
+ }
2966
3128
  });
2967
3129
  }
2968
3130
  function __anHandlePopupOAuthFailure(ret, btn, err, flowId, redirectReason, builderFrameMessage) {
@@ -2990,6 +3152,7 @@ ${identitySsoScript}
2990
3152
  var isMagicLink = kind === 'magic-link';
2991
3153
  __anOAuthPollCount = 0;
2992
3154
  async function check() {
3155
+ if (!isMagicLink && !__anIsCurrentGoogleSignInFlow(flowId)) return;
2993
3156
  __anOAuthPollCount++;
2994
3157
  try {
2995
3158
  var exchangeParams = '?flow_id=' + encodeURIComponent(flowId);
@@ -2999,6 +3162,7 @@ ${identitySsoScript}
2999
3162
  }
3000
3163
  var res = await fetch(__anPath('/_agent-native/auth/desktop-exchange') + exchangeParams, exchangeOptions);
3001
3164
  var data = await res.json().catch(function() { return {}; });
3165
+ if (!isMagicLink && !__anIsCurrentGoogleSignInFlow(flowId)) return;
3002
3166
  if (data && (data.email || data.token)) {
3003
3167
  if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);
3004
3168
  __anOAuthPollTimer = null;
@@ -3062,8 +3226,7 @@ ${identitySsoScript}
3062
3226
  }
3063
3227
  return data.url;
3064
3228
  }
3065
- function __anStartPopupOAuth(ret, btn, err) {
3066
- var flowId = __anNewOAuthFlowId();
3229
+ function __anStartPopupOAuth(ret, btn, err, flowId) {
3067
3230
  var verifier = __anNewOAuthVerifier();
3068
3231
  if (!verifier) {
3069
3232
  __anShowOAuthError(err, btn, __anT('failedToConnect'));
@@ -3080,11 +3243,13 @@ ${identitySsoScript}
3080
3243
  return;
3081
3244
  }
3082
3245
  try { popup.opener = null; } catch(e) {}
3246
+ __anWatchOAuthPopupClose(popup, flowId);
3083
3247
  } catch(e) {
3084
3248
  __anHandlePopupOAuthFailure(ret, btn, err, flowId, 'Could not open Google popup; falling back to redirect', 'Could not open Google popup.');
3085
3249
  return;
3086
3250
  }
3087
3251
  __anRequestDesktopOAuthUrl(flowId, verifier, oauthReturn).then(function(url) {
3252
+ if (!__anIsCurrentGoogleSignInFlow(flowId)) return;
3088
3253
  try {
3089
3254
  popup.location.href = url;
3090
3255
  __anSetOAuthDebug('Google popup opened; waiting for callback', flowId);
@@ -3093,24 +3258,29 @@ ${identitySsoScript}
3093
3258
  throw e;
3094
3259
  }
3095
3260
  }).catch(function(e) {
3261
+ if (!__anIsCurrentGoogleSignInFlow(flowId)) return;
3096
3262
  try { popup.close(); } catch(closeErr) {}
3097
3263
  __anShowOAuthError(err, btn, e && e.message ? e.message : __anT('failedToConnect'));
3098
3264
  });
3099
3265
  }
3100
- function __anStartNativeDesktopOAuth(ret, btn, err) {
3101
- var flowId = __anNewOAuthFlowId();
3266
+ function __anStartNativeDesktopOAuth(ret, btn, err, flowId) {
3102
3267
  var verifier = __anNewOAuthVerifier();
3103
3268
  if (!verifier) {
3104
3269
  __anShowOAuthError(err, btn, __anT('failedToConnect'));
3105
3270
  return;
3106
3271
  }
3107
3272
  var oauthReturn = ret;
3273
+ __anBeginNativeOAuth(flowId);
3108
3274
  __anSetOAuthDebug('Preparing Google sign-in in system browser', flowId);
3109
3275
  __anRequestDesktopOAuthUrl(flowId, verifier, oauthReturn).then(function(url) {
3276
+ if (!__anIsCurrentGoogleSignInFlow(flowId)) return;
3277
+ __anMarkNativeOAuthPolling(flowId);
3110
3278
  __anSetOAuthDebug('Opening Google sign-in in system browser', flowId);
3111
3279
  __anOpenOAuthUrl(url);
3112
3280
  __anWaitForOAuthExchange(flowId, ret, btn, err, 'google', verifier);
3281
+ __anScheduleNativeOAuthAbandonment(flowId);
3113
3282
  }).catch(function(e) {
3283
+ if (!__anIsCurrentGoogleSignInFlow(flowId)) return;
3114
3284
  __anShowOAuthError(err, btn, e && e.message ? e.message : __anT('failedToConnect'));
3115
3285
  });
3116
3286
  }
@@ -3831,20 +4001,20 @@ ${renderGoogleButton
3831
4001
  var btn = document.getElementById('google-btn');
3832
4002
  var err = document.getElementById('google-err');
3833
4003
  var ret = __anResumeHref();
4004
+ var flowId = __anNewOAuthFlowId();
3834
4005
  btn.disabled = true;
3835
- __anGoogleSignInInFlight = true;
4006
+ __anBeginGoogleSignInFlow(flowId);
3836
4007
  __anBindGoogleRecover();
3837
4008
  err.classList.remove('show');
3838
4009
  if (__anResolveAuthFlow() === 'popup') {
3839
- __anStartPopupOAuth(ret, btn, err);
4010
+ __anStartPopupOAuth(ret, btn, err, flowId);
3840
4011
  return;
3841
4012
  }
3842
4013
  if (__anIsAgentNativeDesktop()) {
3843
- __anStartNativeDesktopOAuth(ret, btn, err);
4014
+ __anStartNativeDesktopOAuth(ret, btn, err, flowId);
3844
4015
  return;
3845
4016
  }
3846
4017
  if (__anIsBuilderPreview()) {
3847
- var flowId = __anNewOAuthFlowId();
3848
4018
  __anStartRedirectOAuth(ret, btn, err, flowId, 'Opening Google sign-in redirect from Builder preview');
3849
4019
  return;
3850
4020
  }
@@ -3852,6 +4022,7 @@ ${renderGoogleButton
3852
4022
  var authUrl = __anGoogleAuthUrlPath() + '?return=' + encodeURIComponent(ret);
3853
4023
  var res = await fetch(authUrl);
3854
4024
  var data = await res.json();
4025
+ if (!__anIsCurrentGoogleSignInFlow(flowId)) return;
3855
4026
  if (data.url) {
3856
4027
  __anOpenOAuthUrl(data.url);
3857
4028
  } else {
@@ -3859,12 +4030,14 @@ ${renderGoogleButton
3859
4030
  err.classList.add('show');
3860
4031
  btn.disabled = false;
3861
4032
  __anGoogleSignInInFlight = false;
4033
+ __anClearGoogleSignInFlow();
3862
4034
  }
3863
4035
  } catch (e) {
3864
4036
  err.textContent = __anT('failedToConnect');
3865
4037
  err.classList.add('show');
3866
4038
  btn.disabled = false;
3867
4039
  __anGoogleSignInInFlight = false;
4040
+ __anClearGoogleSignInFlow();
3868
4041
  }
3869
4042
  }`
3870
4043
  : ""}
@@ -20,6 +20,6 @@ export declare function createTranscribeVoiceHandler(): import("h3").EventHandle
20
20
  error: string;
21
21
  text?: undefined;
22
22
  } | {
23
- text: string;
24
23
  error?: undefined;
24
+ text: string;
25
25
  }>>;
@@ -2254,7 +2254,11 @@ function isNitroEnvironmentUnavailable(error) {
2254
2254
  typeof candidate.message === "string" &&
2255
2255
  /Vite environment .+ is unavailable/.test(candidate.message));
2256
2256
  }
2257
- const NITRO_STARTUP_SETTLE_MS = 1_000;
2257
+ // Nitro's worker can expose a transformed module graph before its entry
2258
+ // module has finished importing. Keep the recovery page up for the same
2259
+ // cold-start window instead of letting the first poll render a 500 error.
2260
+ const NITRO_STARTUP_SETTLE_MS = 3_000;
2261
+ const NITRO_STARTUP_POLL_INTERVAL_MS = 100;
2258
2262
  const NITRO_STARTUP_TIMEOUT_MS = 30_000;
2259
2263
  const NITRO_STARTUP_RETRY_DELAY_MS = 1_000;
2260
2264
  const NITRO_STARTUP_RETRY_MAX_DELAY_MS = 3_000;
@@ -2366,15 +2370,24 @@ function nitroStartupGate(options = {}) {
2366
2370
  let graphSignature = null;
2367
2371
  let graphStableAt;
2368
2372
  let startupComplete = false;
2369
- server.middlewares.use((req, res, next) => {
2370
- if (startupComplete || !isHtmlDocumentRequest(req)) {
2371
- next();
2372
- return;
2373
+ let readinessTimer;
2374
+ const completeStartup = () => {
2375
+ startupComplete = true;
2376
+ if (readinessTimer) {
2377
+ clearInterval(readinessTimer);
2378
+ readinessTimer = undefined;
2373
2379
  }
2380
+ };
2381
+ // Observe the graph independently of the first document request. A
2382
+ // server can finish compiling while the shell is still on its loading
2383
+ // screen; measuring stability only from the first request adds the full
2384
+ // settle window to an otherwise-ready server.
2385
+ const observeStartup = () => {
2386
+ if (startupComplete)
2387
+ return;
2374
2388
  const timestamp = now();
2375
2389
  if (timestamp - startedAt >= timeoutMs) {
2376
- startupComplete = true;
2377
- next();
2390
+ completeStartup();
2378
2391
  return;
2379
2392
  }
2380
2393
  const nextGraphSignature = nitroModuleGraphSignature(server.environments?.nitro);
@@ -2385,15 +2398,43 @@ function nitroStartupGate(options = {}) {
2385
2398
  }
2386
2399
  else if (graphStableAt !== undefined &&
2387
2400
  timestamp - graphStableAt >= settleMs) {
2388
- startupComplete = true;
2389
- next();
2390
- return;
2401
+ completeStartup();
2391
2402
  }
2392
2403
  }
2393
2404
  else {
2394
2405
  graphSignature = null;
2395
2406
  graphStableAt = undefined;
2396
2407
  }
2408
+ };
2409
+ // Start watching before any HTML request arrives so readiness time is
2410
+ // measured from server startup, not from the user's first navigation.
2411
+ observeStartup();
2412
+ if (!startupComplete) {
2413
+ readinessTimer = setInterval(observeStartup, NITRO_STARTUP_POLL_INTERVAL_MS);
2414
+ readinessTimer.unref?.();
2415
+ server.httpServer?.once("close", () => {
2416
+ if (readinessTimer) {
2417
+ clearInterval(readinessTimer);
2418
+ readinessTimer = undefined;
2419
+ }
2420
+ });
2421
+ }
2422
+ server.middlewares.use((req, res, next) => {
2423
+ if (startupComplete || !isHtmlDocumentRequest(req)) {
2424
+ next();
2425
+ return;
2426
+ }
2427
+ const timestamp = now();
2428
+ if (timestamp - startedAt >= timeoutMs) {
2429
+ completeStartup();
2430
+ next();
2431
+ return;
2432
+ }
2433
+ observeStartup();
2434
+ if (startupComplete) {
2435
+ next();
2436
+ return;
2437
+ }
2397
2438
  sendNitroStartingResponse(req, res);
2398
2439
  });
2399
2440
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.174.1",
3
+ "version": "0.174.2-nightly-20260825204701",
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": {