@agent-native/core 0.181.0 → 0.181.1-nightly-20260916144018
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/clips/app/components/player/create-account-dialog.tsx +2 -1
- package/corpus/templates/clips/changelog/2026-09-15-google-sign-up-now-opens-reliably-in-embedded-browsers.md +6 -0
- package/corpus/templates/content/actions/resolve-preview-document-draft.ts +320 -60
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +3 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +349 -104
- package/corpus/templates/content/app/components/editor/DocumentReconcileRecovery.tsx +109 -0
- package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +31 -153
- package/corpus/templates/content/app/components/editor/RecoveryComparison.tsx +230 -0
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +46 -19
- package/corpus/templates/content/app/components/editor/editor-draft-save.ts +13 -0
- package/corpus/templates/content/app/components/editor/useDocumentReconcileRecovery.ts +248 -0
- package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +2 -1
- package/corpus/templates/content/app/i18n-data.ts +13 -0
- package/corpus/templates/content/changelog/2026-09-15-oauth-connections-now-open-reliably-in-embedded-browsers.md +6 -0
- package/corpus/templates/content/docs/solutions/2026-09-02-content-suggested-edits-parity-shape.md +84 -2
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +27 -7
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +45 -15
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +35 -8
- package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +14 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +18 -5
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +9 -4
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +3 -0
- package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +7 -2
- package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +2 -0
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +4 -0
- package/corpus/templates/design/app/pages/design-editor/pending-structure-verification.ts +197 -147
- package/corpus/templates/design/changelog/2026-09-16-design-editor-agent-chat-uses-shared-conversation-tabs-and-s.md +6 -0
- package/corpus/templates/design/changelog/2026-09-16-visual-edits-stay-applied-after-replacements.md +5 -0
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +2 -1
- package/corpus/templates/mail/changelog/2026-09-15-google-connections-now-open-reliably-in-embedded-browsers.md +6 -0
- package/dist/client/AgentPanel.d.ts +1 -1
- package/dist/client/AgentPanel.js +4 -4
- package/dist/client/auth/AuthPage.js +6 -1
- package/dist/client/oauth-popup.d.ts +7 -0
- package/dist/client/oauth-popup.js +14 -0
- package/dist/client/ocean/OceanBackground.d.ts +3 -1
- package/dist/client/ocean/OceanBackground.js +4 -3
- package/dist/client/resources/mcp-integration-catalog.js +7 -3
- package/dist/client/settings/useBuilderStatus.js +4 -3
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/app-roles.d.ts +8 -0
- package/dist/org/app-roles.js +7 -0
- package/dist/org/index.d.ts +1 -1
- package/dist/org/index.js +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +3 -0
- package/dist/server/core-routes-plugin.d.ts +3 -0
- package/dist/server/core-routes-plugin.js +17 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/package.json +5 -4
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
buildCodeLayerProjection,
|
|
3
3
|
type CodeLayerNode,
|
|
4
4
|
} from "@shared/code-layer";
|
|
5
|
+
import { parse, type DefaultTreeAdapterMap } from "parse5";
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
|
-
bridgeSourceIdForCodeLayerNode,
|
|
8
8
|
collapsedElementText,
|
|
9
9
|
resolveCodeLayerTargetFromBridge,
|
|
10
10
|
} from "./code-layer-state";
|
|
@@ -20,6 +20,8 @@ export type RuntimeStructureVerificationFailure =
|
|
|
20
20
|
| "ambiguous-subject"
|
|
21
21
|
| "subject-still-present"
|
|
22
22
|
| "missing-replacement"
|
|
23
|
+
| "missing-replacement-evidence"
|
|
24
|
+
| "replacement-context-changed"
|
|
23
25
|
| "ambiguous-replacement"
|
|
24
26
|
| "missing-anchor"
|
|
25
27
|
| "ambiguous-anchor"
|
|
@@ -37,7 +39,6 @@ type RuntimeStructureNodeRole = "subject" | "replacement" | "anchor";
|
|
|
37
39
|
interface RuntimeStructureNodeResolution {
|
|
38
40
|
node?: CodeLayerNode;
|
|
39
41
|
failure?: RuntimeStructureVerificationFailure;
|
|
40
|
-
matchedBy?: "identity" | "selector" | "signature";
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
function runtimeStructureResolutionFailure(
|
|
@@ -85,16 +86,11 @@ function resolveRuntimeStructureNode(args: {
|
|
|
85
86
|
args.sourceId ?? undefined,
|
|
86
87
|
);
|
|
87
88
|
if (direct.status === "resolved") {
|
|
88
|
-
const matchedBy =
|
|
89
|
-
args.sourceId &&
|
|
90
|
-
bridgeSourceIdForCodeLayerNode(direct.node) === args.sourceId
|
|
91
|
-
? "identity"
|
|
92
|
-
: "selector";
|
|
93
89
|
if (
|
|
94
90
|
!args.signature ||
|
|
95
91
|
runtimeStructureNodeMatchesSignature(direct.node, args.signature)
|
|
96
92
|
) {
|
|
97
|
-
return { node: direct.node
|
|
93
|
+
return { node: direct.node };
|
|
98
94
|
}
|
|
99
95
|
// A live identity is stronger evidence than a stale content signature.
|
|
100
96
|
// Do not let an unrelated sibling with the old signature validate this
|
|
@@ -108,75 +104,31 @@ function resolveRuntimeStructureNode(args: {
|
|
|
108
104
|
: "missing-anchor",
|
|
109
105
|
};
|
|
110
106
|
}
|
|
111
|
-
|
|
112
|
-
if (!args.signature) {
|
|
107
|
+
if (direct.status === "ambiguous") {
|
|
113
108
|
return {
|
|
114
|
-
failure: runtimeStructureResolutionFailure(
|
|
115
|
-
direct.status === "ambiguous" ? "ambiguous" : "absent",
|
|
116
|
-
args.role,
|
|
117
|
-
),
|
|
109
|
+
failure: runtimeStructureResolutionFailure("ambiguous", args.role),
|
|
118
110
|
};
|
|
119
111
|
}
|
|
120
112
|
|
|
121
|
-
|
|
122
|
-
runtimeStructureNodeMatchesSignature(node, args.signature!),
|
|
123
|
-
);
|
|
124
|
-
if (matches.length === 1) {
|
|
125
|
-
return { node: matches[0], matchedBy: "signature" };
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
failure: runtimeStructureResolutionFailure(
|
|
129
|
-
matches.length > 1 ? "ambiguous" : "absent",
|
|
130
|
-
args.role,
|
|
131
|
-
),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function resolveRuntimeStructureNodeByIdentity(args: {
|
|
136
|
-
projection: { nodes: CodeLayerNode[] };
|
|
137
|
-
selector?: string;
|
|
138
|
-
sourceId?: string | null;
|
|
139
|
-
role: RuntimeStructureNodeRole;
|
|
140
|
-
}): RuntimeStructureNodeResolution {
|
|
141
|
-
const direct = resolveCodeLayerTargetFromBridge(
|
|
142
|
-
args.projection,
|
|
143
|
-
args.selector,
|
|
144
|
-
args.sourceId ?? undefined,
|
|
145
|
-
);
|
|
146
|
-
if (direct.status === "resolved") {
|
|
147
|
-
return {
|
|
148
|
-
node: direct.node,
|
|
149
|
-
matchedBy:
|
|
150
|
-
args.sourceId &&
|
|
151
|
-
bridgeSourceIdForCodeLayerNode(direct.node) === args.sourceId
|
|
152
|
-
? "identity"
|
|
153
|
-
: "selector",
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
failure: runtimeStructureResolutionFailure(
|
|
158
|
-
direct.status === "ambiguous" ? "ambiguous" : "absent",
|
|
159
|
-
args.role,
|
|
160
|
-
),
|
|
161
|
-
};
|
|
113
|
+
return resolveRuntimeStructureNodeBySignature(args);
|
|
162
114
|
}
|
|
163
115
|
|
|
164
116
|
function resolveRuntimeStructureNodeBySignature(args: {
|
|
165
117
|
projection: { nodes: CodeLayerNode[] };
|
|
166
118
|
signature?: RuntimeStructureNodeSignature;
|
|
167
119
|
role: RuntimeStructureNodeRole;
|
|
168
|
-
|
|
120
|
+
excludedNode?: CodeLayerNode;
|
|
169
121
|
}): RuntimeStructureNodeResolution {
|
|
170
122
|
if (!args.signature) {
|
|
171
123
|
return { failure: runtimeStructureResolutionFailure("absent", args.role) };
|
|
172
124
|
}
|
|
173
125
|
const matches = args.projection.nodes.filter(
|
|
174
126
|
(node) =>
|
|
175
|
-
|
|
127
|
+
node !== args.excludedNode &&
|
|
176
128
|
runtimeStructureNodeMatchesSignature(node, args.signature!),
|
|
177
129
|
);
|
|
178
130
|
if (matches.length === 1) {
|
|
179
|
-
return { node: matches[0]
|
|
131
|
+
return { node: matches[0] };
|
|
180
132
|
}
|
|
181
133
|
return {
|
|
182
134
|
failure: runtimeStructureResolutionFailure(
|
|
@@ -186,18 +138,142 @@ function resolveRuntimeStructureNodeBySignature(args: {
|
|
|
186
138
|
};
|
|
187
139
|
}
|
|
188
140
|
|
|
189
|
-
function
|
|
190
|
-
projection: { nodes: CodeLayerNode[] }
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
141
|
+
function verifyRuntimeStructureSubjectAbsent(
|
|
142
|
+
projection: { nodes: CodeLayerNode[] },
|
|
143
|
+
edit: PendingLiveStructureEdit,
|
|
144
|
+
replacement?: CodeLayerNode,
|
|
145
|
+
): RuntimeStructureVerificationResult {
|
|
146
|
+
// Absence must inspect the old identity alone: a positional selector may
|
|
147
|
+
// now point at the replacement or an unrelated sibling.
|
|
148
|
+
const subject = resolveCodeLayerTargetFromBridge(
|
|
149
|
+
projection,
|
|
150
|
+
edit.sourceId ? undefined : edit.selector,
|
|
151
|
+
edit.sourceId ?? undefined,
|
|
152
|
+
);
|
|
153
|
+
if (subject.status === "ambiguous") {
|
|
154
|
+
return { ok: false, failure: "ambiguous-subject" };
|
|
199
155
|
}
|
|
200
|
-
|
|
156
|
+
if (
|
|
157
|
+
subject.status === "resolved" &&
|
|
158
|
+
(edit.sourceId ||
|
|
159
|
+
replacement === undefined ||
|
|
160
|
+
(subject.node === replacement &&
|
|
161
|
+
(!edit.subjectSignature ||
|
|
162
|
+
runtimeStructureNodeMatchesSignature(
|
|
163
|
+
subject.node,
|
|
164
|
+
edit.subjectSignature,
|
|
165
|
+
))))
|
|
166
|
+
) {
|
|
167
|
+
return { ok: false, failure: "subject-still-present" };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Signature-only fallback cannot distinguish unchanged old content. A
|
|
171
|
+
// unique identity or selector independently resolves the replacement;
|
|
172
|
+
// captured document evidence still has to prove the resulting structure.
|
|
173
|
+
const replacementTarget = resolveCodeLayerTargetFromBridge(
|
|
174
|
+
projection,
|
|
175
|
+
edit.replacementSourceId ? undefined : edit.replacementSelector,
|
|
176
|
+
edit.replacementSourceId ?? undefined,
|
|
177
|
+
);
|
|
178
|
+
const signature = resolveRuntimeStructureNodeBySignature({
|
|
179
|
+
projection,
|
|
180
|
+
signature: edit.subjectSignature,
|
|
181
|
+
role: "subject",
|
|
182
|
+
excludedNode:
|
|
183
|
+
replacementTarget.status === "resolved" &&
|
|
184
|
+
replacementTarget.node === replacement
|
|
185
|
+
? replacement
|
|
186
|
+
: undefined,
|
|
187
|
+
});
|
|
188
|
+
if (signature.failure === "ambiguous-subject") {
|
|
189
|
+
return { ok: false, failure: signature.failure };
|
|
190
|
+
}
|
|
191
|
+
return signature.node
|
|
192
|
+
? { ok: false, failure: "subject-still-present" }
|
|
193
|
+
: { ok: true };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// These attributes are injected by serializeRuntimeLayerSnapshot, not authored
|
|
197
|
+
// semantic state. Keep this list exact so other data-* attributes remain evidence.
|
|
198
|
+
const runtimeSnapshotMetadataAttributes = new Set([
|
|
199
|
+
"style",
|
|
200
|
+
"data-agent-native-node-id",
|
|
201
|
+
"data-agent-native-node-rewrite-proposal",
|
|
202
|
+
"data-agent-native-group-runtime-state",
|
|
203
|
+
"data-agent-native-runtime-hidden",
|
|
204
|
+
"data-agent-native-runtime-locked",
|
|
205
|
+
"data-agent-native-previous-display",
|
|
206
|
+
"data-agent-native-text-editing",
|
|
207
|
+
"data-an-runtime-layer-snapshot",
|
|
208
|
+
"data-an-pending-node-id",
|
|
209
|
+
"data-an-state-preview",
|
|
210
|
+
"data-an-state-preview-key",
|
|
211
|
+
"data-an-vector-logical-width",
|
|
212
|
+
"data-an-vector-stroke-defs",
|
|
213
|
+
"data-an-vector-stroke-geometry",
|
|
214
|
+
"data-an-vector-stroke-original-overflow",
|
|
215
|
+
"data-an-vector-stroke-original-overflow-priority",
|
|
216
|
+
"data-an-vector-stroke-overlay",
|
|
217
|
+
"data-an-vector-stroke-position",
|
|
218
|
+
"data-source-framework",
|
|
219
|
+
"data-source-file",
|
|
220
|
+
"data-source-line",
|
|
221
|
+
"data-source-method",
|
|
222
|
+
"data-source-column",
|
|
223
|
+
"data-component-name",
|
|
224
|
+
"data-source-owner-file",
|
|
225
|
+
"data-source-owner-line",
|
|
226
|
+
"data-source-owner-column",
|
|
227
|
+
"data-source-owner-component",
|
|
228
|
+
"data-source-owner-method",
|
|
229
|
+
"data-source-owner-key",
|
|
230
|
+
"data-source-unavailable",
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
/** Full visual-tree evidence: sibling order, nesting, content and semantic
|
|
234
|
+
* attributes must match; runtime identities and computed styles may change. */
|
|
235
|
+
export function runtimeStructureSnapshotSignature(
|
|
236
|
+
snapshotHtml: string,
|
|
237
|
+
): string {
|
|
238
|
+
function structure(node: DefaultTreeAdapterMap["node"]): unknown[] {
|
|
239
|
+
if ("value" in node && node.nodeName === "#text") {
|
|
240
|
+
const text = collapsedElementText(node.value);
|
|
241
|
+
return text ? [text] : [];
|
|
242
|
+
}
|
|
243
|
+
if ("tagName" in node && node.tagName === "head") return [];
|
|
244
|
+
const children =
|
|
245
|
+
"childNodes" in node ? node.childNodes.flatMap(structure) : [];
|
|
246
|
+
if (!("tagName" in node) || node.tagName === "html") return children;
|
|
247
|
+
return [
|
|
248
|
+
[
|
|
249
|
+
node.namespaceURI,
|
|
250
|
+
node.tagName,
|
|
251
|
+
normalizeRuntimeStructureClasses(
|
|
252
|
+
(
|
|
253
|
+
node.attrs.find((attribute) => attribute.name === "class")?.value ??
|
|
254
|
+
""
|
|
255
|
+
).split(/\s+/),
|
|
256
|
+
),
|
|
257
|
+
node.attrs
|
|
258
|
+
.filter(
|
|
259
|
+
(attribute) =>
|
|
260
|
+
attribute.namespace ||
|
|
261
|
+
(attribute.name !== "class" &&
|
|
262
|
+
!runtimeSnapshotMetadataAttributes.has(attribute.name)),
|
|
263
|
+
)
|
|
264
|
+
.map((attribute) =>
|
|
265
|
+
JSON.stringify([
|
|
266
|
+
attribute.namespace ?? "",
|
|
267
|
+
attribute.name,
|
|
268
|
+
attribute.value,
|
|
269
|
+
]),
|
|
270
|
+
)
|
|
271
|
+
.sort(),
|
|
272
|
+
children,
|
|
273
|
+
],
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
return JSON.stringify(structure(parse(snapshotHtml)));
|
|
201
277
|
}
|
|
202
278
|
|
|
203
279
|
function runtimeStructureNodeForNoOp(
|
|
@@ -229,9 +305,9 @@ export function verifyPendingStructureRuntime(
|
|
|
229
305
|
): RuntimeStructureVerificationResult {
|
|
230
306
|
const projection = buildCodeLayerProjection(snapshotHtml);
|
|
231
307
|
if (edit.replaced) {
|
|
232
|
-
const replacementResolution =
|
|
308
|
+
const replacementResolution = resolveRuntimeStructureNode({
|
|
233
309
|
projection,
|
|
234
|
-
selector: edit.replacementSelector,
|
|
310
|
+
selector: edit.replacementSourceId ? undefined : edit.replacementSelector,
|
|
235
311
|
sourceId: edit.replacementSourceId,
|
|
236
312
|
signature: edit.replacementSignature,
|
|
237
313
|
role: "replacement",
|
|
@@ -243,83 +319,22 @@ export function verifyPendingStructureRuntime(
|
|
|
243
319
|
};
|
|
244
320
|
}
|
|
245
321
|
|
|
246
|
-
const
|
|
322
|
+
const absence = verifyRuntimeStructureSubjectAbsent(
|
|
247
323
|
projection,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
role: "subject",
|
|
251
|
-
});
|
|
252
|
-
if (subjectIdentityResolution.failure === "ambiguous-subject") {
|
|
253
|
-
return { ok: false, failure: "ambiguous-subject" };
|
|
254
|
-
}
|
|
255
|
-
if (subjectIdentityResolution.node) {
|
|
256
|
-
const sameNode =
|
|
257
|
-
subjectIdentityResolution.node.id === replacementResolution.node.id;
|
|
258
|
-
const replacementIsProvenByPosition =
|
|
259
|
-
sameNode &&
|
|
260
|
-
subjectIdentityResolution.matchedBy === "selector" &&
|
|
261
|
-
replacementResolution.matchedBy === "selector";
|
|
262
|
-
if (!replacementIsProvenByPosition) {
|
|
263
|
-
return { ok: false, failure: "subject-still-present" };
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const subjectMatches = projection.nodes.filter((node) =>
|
|
268
|
-
edit.subjectSignature
|
|
269
|
-
? runtimeStructureNodeMatchesSignature(node, edit.subjectSignature)
|
|
270
|
-
: false,
|
|
324
|
+
edit,
|
|
325
|
+
replacementResolution.node,
|
|
271
326
|
);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const remainingSubjectMatches = replacementIsUniquelyLocated
|
|
276
|
-
? subjectMatches.filter(
|
|
277
|
-
(node) => node.id !== replacementResolution.node!.id,
|
|
278
|
-
)
|
|
279
|
-
: subjectMatches;
|
|
280
|
-
const replacementIsProvenByPosition =
|
|
281
|
-
subjectIdentityResolution.node?.id === replacementResolution.node.id &&
|
|
282
|
-
subjectIdentityResolution.matchedBy === "selector" &&
|
|
283
|
-
replacementResolution.matchedBy === "selector";
|
|
284
|
-
if (!replacementIsProvenByPosition) {
|
|
285
|
-
if (remainingSubjectMatches.length > 1) {
|
|
286
|
-
return { ok: false, failure: "ambiguous-subject" };
|
|
287
|
-
}
|
|
288
|
-
if (remainingSubjectMatches.length === 1) {
|
|
289
|
-
return { ok: false, failure: "subject-still-present" };
|
|
290
|
-
}
|
|
327
|
+
if (!absence.ok) return absence;
|
|
328
|
+
if (!edit.replacementSnapshotSignature) {
|
|
329
|
+
return { ok: false, failure: "missing-replacement-evidence" };
|
|
291
330
|
}
|
|
292
|
-
return
|
|
331
|
+
return runtimeStructureSnapshotSignature(snapshotHtml) ===
|
|
332
|
+
edit.replacementSnapshotSignature
|
|
333
|
+
? { ok: true }
|
|
334
|
+
: { ok: false, failure: "replacement-context-changed" };
|
|
293
335
|
}
|
|
294
|
-
// A removal proves itself by ABSENCE. Running it through the anchor/order
|
|
295
|
-
// checks below would report "missing-subject" for the exact outcome it
|
|
296
|
-
// asked for, and the apply flow would sit in awaiting-runtime until it
|
|
297
|
-
// timed out on a source write that actually succeeded.
|
|
298
336
|
if (edit.removed) {
|
|
299
|
-
|
|
300
|
-
projection,
|
|
301
|
-
selector: edit.selector,
|
|
302
|
-
sourceId: edit.sourceId,
|
|
303
|
-
role: "subject",
|
|
304
|
-
});
|
|
305
|
-
if (subjectIdentityResolution.failure === "ambiguous-subject") {
|
|
306
|
-
return { ok: false, failure: "ambiguous-subject" };
|
|
307
|
-
}
|
|
308
|
-
if (subjectIdentityResolution.node) {
|
|
309
|
-
return { ok: false, failure: "subject-still-present" };
|
|
310
|
-
}
|
|
311
|
-
const subjectSignatureResolution = resolveRuntimeStructureNodeBySignature({
|
|
312
|
-
projection,
|
|
313
|
-
signature: edit.subjectSignature,
|
|
314
|
-
role: "subject",
|
|
315
|
-
});
|
|
316
|
-
if (subjectSignatureResolution.failure === "ambiguous-subject") {
|
|
317
|
-
return { ok: false, failure: "ambiguous-subject" };
|
|
318
|
-
}
|
|
319
|
-
if (subjectSignatureResolution.node) {
|
|
320
|
-
return { ok: false, failure: "subject-still-present" };
|
|
321
|
-
}
|
|
322
|
-
return { ok: true };
|
|
337
|
+
return verifyRuntimeStructureSubjectAbsent(projection, edit);
|
|
323
338
|
}
|
|
324
339
|
|
|
325
340
|
const subjectResolution = resolveRuntimeStructureNode({
|
|
@@ -433,14 +448,30 @@ export function isPendingStructureDropNoOp(
|
|
|
433
448
|
);
|
|
434
449
|
}
|
|
435
450
|
|
|
451
|
+
function replacementSnapshotsByScreen(
|
|
452
|
+
edits: readonly PendingLiveStructureEdit[],
|
|
453
|
+
) {
|
|
454
|
+
// Later replacement gestures include the earlier optimistic replacements.
|
|
455
|
+
// Verify the composed screen without discarding each edit's identity checks.
|
|
456
|
+
return new Map(
|
|
457
|
+
edits
|
|
458
|
+
.filter((edit) => edit.replaced)
|
|
459
|
+
.map((edit) => [edit.screenId, edit.replacementSnapshotSignature]),
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
436
463
|
export function verifyPendingStructuresRuntime(
|
|
437
464
|
snapshots: Record<string, { html: string } | undefined>,
|
|
438
465
|
edits: readonly PendingLiveStructureEdit[],
|
|
439
466
|
): RuntimeStructureVerificationResult {
|
|
467
|
+
const replacementSnapshots = replacementSnapshotsByScreen(edits);
|
|
440
468
|
for (const edit of edits) {
|
|
441
469
|
const snapshot = snapshots[edit.screenId];
|
|
442
470
|
if (!snapshot) return { ok: false, failure: "missing-subject" };
|
|
443
|
-
const result = verifyPendingStructureRuntime(snapshot.html,
|
|
471
|
+
const result = verifyPendingStructureRuntime(snapshot.html, {
|
|
472
|
+
...edit,
|
|
473
|
+
replacementSnapshotSignature: replacementSnapshots.get(edit.screenId),
|
|
474
|
+
});
|
|
444
475
|
if (!result.ok) return result;
|
|
445
476
|
}
|
|
446
477
|
return { ok: true };
|
|
@@ -453,11 +484,30 @@ export function partitionPendingStructuresRuntime(
|
|
|
453
484
|
verified: PendingLiveStructureEdit[];
|
|
454
485
|
remaining: PendingLiveStructureEdit[];
|
|
455
486
|
} {
|
|
487
|
+
const replacementSnapshots = replacementSnapshotsByScreen(edits);
|
|
488
|
+
const results = edits.map((edit) => {
|
|
489
|
+
const snapshot = snapshots[edit.screenId];
|
|
490
|
+
return {
|
|
491
|
+
edit,
|
|
492
|
+
ok: Boolean(
|
|
493
|
+
snapshot &&
|
|
494
|
+
verifyPendingStructureRuntime(snapshot.html, {
|
|
495
|
+
...edit,
|
|
496
|
+
replacementSnapshotSignature: replacementSnapshots.get(edit.screenId),
|
|
497
|
+
}).ok,
|
|
498
|
+
),
|
|
499
|
+
};
|
|
500
|
+
});
|
|
501
|
+
// Keep the shared evidence until every replacement using it verifies.
|
|
502
|
+
const blockedScreens = new Set(
|
|
503
|
+
results
|
|
504
|
+
.filter(({ edit, ok }) => edit.replaced && !ok)
|
|
505
|
+
.map(({ edit }) => edit.screenId),
|
|
506
|
+
);
|
|
456
507
|
const verified: PendingLiveStructureEdit[] = [];
|
|
457
508
|
const remaining: PendingLiveStructureEdit[] = [];
|
|
458
|
-
for (const edit of
|
|
459
|
-
|
|
460
|
-
if (snapshot && verifyPendingStructureRuntime(snapshot.html, edit).ok) {
|
|
509
|
+
for (const { edit, ok } of results) {
|
|
510
|
+
if (ok && (!edit.replaced || !blockedScreens.has(edit.screenId))) {
|
|
461
511
|
verified.push(edit);
|
|
462
512
|
} else {
|
|
463
513
|
remaining.push(edit);
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "@agent-native/core/client/host";
|
|
7
7
|
import { useT } from "@agent-native/core/client/i18n";
|
|
8
8
|
import { startWorkspaceProviderOAuth } from "@agent-native/core/client/integrations";
|
|
9
|
+
import { openOAuthPopup } from "@agent-native/core/client/oauth-popup";
|
|
9
10
|
import {
|
|
10
11
|
IconMail,
|
|
11
12
|
IconX,
|
|
@@ -178,7 +179,7 @@ export function GoogleConnectBanner({
|
|
|
178
179
|
desktop: "1",
|
|
179
180
|
flow_id: flowId,
|
|
180
181
|
});
|
|
181
|
-
const popup =
|
|
182
|
+
const popup = openOAuthPopup();
|
|
182
183
|
if (!popup) {
|
|
183
184
|
setDesktopAuthIssue({
|
|
184
185
|
code: "popup_blocked",
|
|
@@ -55,7 +55,7 @@ export declare function shouldShowAgentPanelSidebarChatTabs(tabs: MultiTabAssist
|
|
|
55
55
|
export declare function shouldShowAgentPanelPageNewChatButton(tabs: MultiTabAssistantChatHeaderProps["tabs"], activeTabId: string, activeTabMessageCount: number): boolean;
|
|
56
56
|
export declare function shouldShowAgentPanelPageHeader(tabs: MultiTabAssistantChatHeaderProps["tabs"], activeTabId: string, activeTabMessageCount: number): boolean;
|
|
57
57
|
export declare function shouldShowAgentPanelCliTabBar(cliTabs: string[]): boolean;
|
|
58
|
-
export declare function shouldShowAgentPanelModeButtons(isSidebar: boolean): boolean;
|
|
58
|
+
export declare function shouldShowAgentPanelModeButtons(isSidebar: boolean, chatOnly?: boolean): boolean;
|
|
59
59
|
export declare function shouldShowAgentPanelFullViewAction(agentPageHref: string | undefined, mode: PanelMode, isSidebar?: boolean, currentPath?: string): boolean;
|
|
60
60
|
export declare function resolveAgentPanelFullViewAction(agentPageHref: string | undefined, onFullViewRequest: (() => void) | undefined, mode: PanelMode, isSidebar?: boolean, currentPath?: string): {
|
|
61
61
|
readonly kind: "callback";
|
|
@@ -342,8 +342,8 @@ export function shouldShowAgentPanelPageHeader(tabs, activeTabId, activeTabMessa
|
|
|
342
342
|
export function shouldShowAgentPanelCliTabBar(cliTabs) {
|
|
343
343
|
return cliTabs.length > 1;
|
|
344
344
|
}
|
|
345
|
-
export function shouldShowAgentPanelModeButtons(isSidebar) {
|
|
346
|
-
return !isSidebar;
|
|
345
|
+
export function shouldShowAgentPanelModeButtons(isSidebar, chatOnly = false) {
|
|
346
|
+
return !isSidebar && !chatOnly;
|
|
347
347
|
}
|
|
348
348
|
export function shouldShowAgentPanelFullViewAction(agentPageHref, mode, isSidebar = false, currentPath) {
|
|
349
349
|
return (Boolean(agentPageHref) &&
|
|
@@ -1018,7 +1018,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
|
|
|
1018
1018
|
borderRadius: "0 6px 6px 0",
|
|
1019
1019
|
background: "linear-gradient(to right, transparent, hsl(var(--accent)) 40%)",
|
|
1020
1020
|
}, children: _jsx(IconX, { size: 10 }) })] }, tab.id));
|
|
1021
|
-
}) })) : shouldShowAgentPanelModeButtons(Boolean(onCollapse)) ? (renderModeButtons(mode)) : null }), _jsx("div", { className: "flex items-center gap-0.5", children: renderHeaderActions({
|
|
1021
|
+
}) })) : shouldShowAgentPanelModeButtons(Boolean(onCollapse), chatOnly) ? (renderModeButtons(mode)) : null }), _jsx("div", { className: "flex items-center gap-0.5", children: renderHeaderActions({
|
|
1022
1022
|
activeChatSessionId: activeTabId,
|
|
1023
1023
|
activeTabId,
|
|
1024
1024
|
activeTabMessageCount,
|
|
@@ -1119,7 +1119,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
|
|
|
1119
1119
|
switchMode,
|
|
1120
1120
|
t,
|
|
1121
1121
|
]);
|
|
1122
|
-
return (_jsxs(ThinkingDisplayProvider, { value: assistantChatProps.thinkingDisplay, children: [_jsx(AgentPanelSettingsNavigation, { onOpenSettings: onOpenSettings }), _jsxs("div", { className: cn("agent-panel-root agent-kit-density flex flex-1 flex-col min-h-0 h-full antialiased", className), style: {
|
|
1122
|
+
return (_jsxs(ThinkingDisplayProvider, { value: assistantChatProps.thinkingDisplay, children: [_jsx(AgentPanelSettingsNavigation, { onOpenSettings: onOpenSettings }), _jsxs("div", { className: cn("agent-panel-root agent-kit-density flex flex-1 flex-col min-h-0 min-w-0 h-full antialiased", className), style: {
|
|
1123
1123
|
...AGENT_PANEL_ROOT_STYLE,
|
|
1124
1124
|
...style,
|
|
1125
1125
|
// The chat view-transition container otherwise traps fixed onboarding
|
|
@@ -7,6 +7,7 @@ import { AUTH_SIGNUP_INVITE_ONLY_CODE } from "../../shared/auth-copy.js";
|
|
|
7
7
|
import { isQaTestEmail } from "../../shared/qa-test-email.js";
|
|
8
8
|
import { signInJourney, } from "../../shared/sign-in-journey.js";
|
|
9
9
|
import { isSyntheticTrafficValue } from "../../shared/test-traffic.js";
|
|
10
|
+
import { openOAuthPopup } from "../oauth-popup.js";
|
|
10
11
|
import { OceanBackground } from "../ocean/OceanBackground.js";
|
|
11
12
|
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
12
13
|
const TAB_STORAGE_KEY = "an.onboarding.tab";
|
|
@@ -1180,7 +1181,10 @@ export function AuthPage(props) {
|
|
|
1180
1181
|
// redirect and hit that same 403 the moment the popup failed to open.
|
|
1181
1182
|
const redirectFallbackUnsafe = isInFrame();
|
|
1182
1183
|
try {
|
|
1183
|
-
popup =
|
|
1184
|
+
popup = openOAuthPopup({
|
|
1185
|
+
initialUrl: new URL(`${runtimeAppBasePath}/_agent-native/oauth/popup`, window.location.origin).href,
|
|
1186
|
+
features: "width=640,height=760",
|
|
1187
|
+
});
|
|
1184
1188
|
if (!popup) {
|
|
1185
1189
|
if (redirectFallbackUnsafe) {
|
|
1186
1190
|
setGoogleBusy(false);
|
|
@@ -1277,6 +1281,7 @@ export function AuthPage(props) {
|
|
|
1277
1281
|
googleViaIdentitySso,
|
|
1278
1282
|
resolveGoogleFlow,
|
|
1279
1283
|
resumeHref,
|
|
1284
|
+
runtimeAppBasePath,
|
|
1280
1285
|
setNotice,
|
|
1281
1286
|
showGoogle,
|
|
1282
1287
|
startOAuthExchange,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface OpenOAuthPopupOptions {
|
|
2
|
+
/** HTTP(S) URL loaded synchronously while the user gesture is active. */
|
|
3
|
+
initialUrl?: string;
|
|
4
|
+
features?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function oauthPopupWaitingUrl(): string;
|
|
7
|
+
export declare function openOAuthPopup({ initialUrl, features, }?: OpenOAuthPopupOptions): Window | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { agentNativePath } from "./api-path.js";
|
|
2
|
+
export function oauthPopupWaitingUrl() {
|
|
3
|
+
if (typeof window === "undefined")
|
|
4
|
+
return "";
|
|
5
|
+
return new URL(agentNativePath("/_agent-native/oauth/popup"), window.location.href).href;
|
|
6
|
+
}
|
|
7
|
+
export function openOAuthPopup({ initialUrl, features, } = {}) {
|
|
8
|
+
if (typeof window === "undefined")
|
|
9
|
+
return null;
|
|
10
|
+
const url = new URL(initialUrl || oauthPopupWaitingUrl(), window.location.href);
|
|
11
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
12
|
+
return null;
|
|
13
|
+
return window.open(url.href, "_blank", features);
|
|
14
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** @jsxRuntime classic */
|
|
2
|
+
import * as React from "react";
|
|
2
3
|
import { useCallback, useEffect, useState } from "react";
|
|
3
4
|
import { StarfieldBackground } from "../StarfieldBackground.js";
|
|
4
5
|
import { HeroOceanBackground } from "./hero-ocean-background.js";
|
|
@@ -41,7 +42,7 @@ export function OceanBackground({ className, frameRate = 30, }) {
|
|
|
41
42
|
}, []);
|
|
42
43
|
const handleOceanError = useCallback(() => setBackground("fallback"), []);
|
|
43
44
|
if (background === "ocean") {
|
|
44
|
-
return (
|
|
45
|
+
return (React.createElement(HeroOceanBackground, { className: className, frameRate: frameRate, onError: handleOceanError }));
|
|
45
46
|
}
|
|
46
|
-
return
|
|
47
|
+
return React.createElement(StarfieldBackground, { className: className, frameRate: frameRate });
|
|
47
48
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { normalizeMcpIntegrationsConfig, } from "../../shared/mcp-integration-config.js";
|
|
2
2
|
import { hostMatches, MCP_LINK_HOSTS, normalizeMcpUrl, } from "../../shared/mcp-provider-hosts.js";
|
|
3
3
|
import { mergeDefinitionsById } from "../../shared/merge-by-id.js";
|
|
4
|
+
import { agentNativePath } from "../api-path.js";
|
|
5
|
+
import { openOAuthPopup } from "../oauth-popup.js";
|
|
4
6
|
import { markMcpConnectionPending } from "./mcp-connection-refresh.js";
|
|
5
7
|
import { mcpIntegrationLogo } from "./mcp-integration-logos.js";
|
|
6
8
|
export const DEFAULT_MCP_INTEGRATIONS = [
|
|
@@ -877,17 +879,19 @@ export function buildMcpOAuthStartUrl({ name, url, description, scope, returnUrl
|
|
|
877
879
|
? { tracking_integration_id: trackingIntegrationId }
|
|
878
880
|
: {}),
|
|
879
881
|
});
|
|
880
|
-
return
|
|
882
|
+
return `${agentNativePath("/_agent-native/mcp/servers/oauth/start")}?${params.toString()}`;
|
|
881
883
|
}
|
|
882
884
|
export function navigateToMcpOAuthStart(url) {
|
|
883
885
|
if (typeof window === "undefined")
|
|
884
886
|
return false;
|
|
885
887
|
try {
|
|
886
|
-
const popup =
|
|
888
|
+
const popup = openOAuthPopup({
|
|
889
|
+
initialUrl: url,
|
|
890
|
+
features: "width=640,height=760",
|
|
891
|
+
});
|
|
887
892
|
if (!popup)
|
|
888
893
|
return false;
|
|
889
894
|
popup.opener = null;
|
|
890
|
-
popup.location.replace(url);
|
|
891
895
|
// The callback redirects the popup, not this window, so this marker is the
|
|
892
896
|
// only thing that tells the opener its cached server list is now suspect.
|
|
893
897
|
markMcpConnectionPending();
|
|
@@ -4,6 +4,7 @@ import { trackEvent } from "../analytics.js";
|
|
|
4
4
|
import { agentNativePath } from "../api-path.js";
|
|
5
5
|
import { getCallbackOrigin } from "../frame.js";
|
|
6
6
|
import { openMcpAppHostLink } from "../mcp-app-host.js";
|
|
7
|
+
import { oauthPopupWaitingUrl } from "../oauth-popup.js";
|
|
7
8
|
import { scheduleAfterPaint } from "../use-after-paint.js";
|
|
8
9
|
import { usePollLoop } from "../use-poll-loop.js";
|
|
9
10
|
/**
|
|
@@ -677,8 +678,8 @@ export function useBuilderConnectFlow(opts = {}) {
|
|
|
677
678
|
? statusConnectUrl
|
|
678
679
|
: null;
|
|
679
680
|
// popupUrl props and statusConnectUrl are signed URLs minted before the
|
|
680
|
-
// click. In web browsers, always refresh inside
|
|
681
|
-
// server/package restart cannot leave the user with
|
|
681
|
+
// click. In web browsers, always refresh inside the inert HTTP waiting
|
|
682
|
+
// page so a server/package restart cannot leave the user with stale state.
|
|
682
683
|
// Desktop keeps the direct path because the Electron shell owns the popup.
|
|
683
684
|
const signedPropUrl = hasSignedConnectToken(popupUrl) ? popupUrl : null;
|
|
684
685
|
const fallbackUrl = new URL(agentNativePath("/_agent-native/builder/connect"), origin).href;
|
|
@@ -710,7 +711,7 @@ export function useBuilderConnectFlow(opts = {}) {
|
|
|
710
711
|
}
|
|
711
712
|
else {
|
|
712
713
|
const opened = openBuilderConnectPopup({
|
|
713
|
-
url:
|
|
714
|
+
url: oauthPopupWaitingUrl(),
|
|
714
715
|
source: clickTrackingSource,
|
|
715
716
|
flow: clickTrackingFlow,
|
|
716
717
|
features: "width=600,height=700",
|