@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 +1 -1
- package/corpus/templates/forms/.agents/skills/form-building/SKILL.md +4 -0
- package/corpus/templates/forms/actions/create-form.ts +3 -1
- package/corpus/templates/forms/actions/patch-form-fields.ts +12 -3
- package/corpus/templates/forms/actions/update-form.ts +3 -1
- package/corpus/templates/forms/changelog/2026-08-25-forms-agents-retry-invalid-field-edits.md +5 -0
- package/corpus/templates/forms/server/lib/validate-fields.ts +47 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/onboarding-html.js +186 -13
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/vite/client.js +51 -10
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -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(
|
|
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 {
|
|
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
|
|
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 =
|
|
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(
|
|
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()
|
|
@@ -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 (!
|
|
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',
|
|
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')
|
|
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
|
-
|
|
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
|
: ""}
|
package/dist/vite/client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|