@agent-native/core 0.225.1-nightly-20261106122820 → 0.225.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/optional-placeholders.js +24 -5
- package/dist/agent/production-agent.js +38 -34
- package/dist/agent/run-store.js +4 -1
- package/dist/client/integrations/workspace-provider-oauth.d.ts +24 -0
- package/dist/client/integrations/workspace-provider-oauth.js +110 -0
- package/dist/framework-package-versions.json +11 -11
- package/dist/server/agent-chat-plugin.js +29 -27
- package/dist/server/auth.js +46 -2
- package/dist/shared/attachments.d.ts +2 -1
- package/dist/shared/attachments.js +2 -13
- package/dist/shared/inline-bytes.js +5 -18
- package/package.json +4 -4
|
@@ -83,14 +83,14 @@ function resolveObjectSchemaBranch(schema, value) {
|
|
|
83
83
|
const index = discriminatedBranchIndex(branches, value);
|
|
84
84
|
return index >= 0 ? branches[index] : undefined;
|
|
85
85
|
}
|
|
86
|
-
function stripOptionalToolPlaceholders(schema, value, emptyValues) {
|
|
86
|
+
function stripOptionalToolPlaceholders(schema, value, emptyValues, depth = 0) {
|
|
87
87
|
if (!schema)
|
|
88
88
|
return { value, changed: false };
|
|
89
89
|
if (Array.isArray(value)) {
|
|
90
90
|
const itemSchema = leafBranches(schema).find((branch) => branch.items)?.items;
|
|
91
91
|
if (!itemSchema)
|
|
92
92
|
return { value, changed: false };
|
|
93
|
-
const results = value.map((item) => stripOptionalToolPlaceholders(itemSchema, item, emptyValues));
|
|
93
|
+
const results = value.map((item) => stripOptionalToolPlaceholders(itemSchema, item, emptyValues, depth + 1));
|
|
94
94
|
return results.some((result) => result.changed)
|
|
95
95
|
? { value: results.map((result) => result.value), changed: true }
|
|
96
96
|
: { value, changed: false };
|
|
@@ -119,6 +119,14 @@ function stripOptionalToolPlaceholders(schema, value, emptyValues) {
|
|
|
119
119
|
}
|
|
120
120
|
continue;
|
|
121
121
|
}
|
|
122
|
+
// Top-level only: a nested null may be a deliberate clear.
|
|
123
|
+
if (emptyValues.stripNull &&
|
|
124
|
+
depth === 0 &&
|
|
125
|
+
entry === null &&
|
|
126
|
+
!required.has(key)) {
|
|
127
|
+
placeholders.push(key);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
122
130
|
if (!required.has(key) && isStructurallyEmptyToolValue(entry)) {
|
|
123
131
|
if (schemaRejectsToolValue(propertySchema, entry)) {
|
|
124
132
|
if (emptyValues.nullOnly && entry !== null)
|
|
@@ -128,9 +136,12 @@ function stripOptionalToolPlaceholders(schema, value, emptyValues) {
|
|
|
128
136
|
continue;
|
|
129
137
|
}
|
|
130
138
|
// A valid `false` sets a value, and `[]` on an action that says an empty
|
|
131
|
-
// list erases nothing is padding.
|
|
139
|
+
// list erases nothing is padding. `null` on an optional field is absent:
|
|
140
|
+
// models fill every unused field with it, so it never marks a call as
|
|
141
|
+
// clearing data. Elsewhere `[]` could wipe a stored
|
|
132
142
|
// list. An empty equal to the schema's own default changes nothing.
|
|
133
143
|
if (entry !== false &&
|
|
144
|
+
entry !== null &&
|
|
134
145
|
!(emptyValues.listsEraseNothing &&
|
|
135
146
|
Array.isArray(entry) &&
|
|
136
147
|
entry.length === 0) &&
|
|
@@ -139,7 +150,7 @@ function stripOptionalToolPlaceholders(schema, value, emptyValues) {
|
|
|
139
150
|
JSON.stringify(propertySchema.default) === JSON.stringify(entry)))
|
|
140
151
|
emptyValues.valid = true;
|
|
141
152
|
}
|
|
142
|
-
const nested = stripOptionalToolPlaceholders(propertySchema, entry, emptyValues);
|
|
153
|
+
const nested = stripOptionalToolPlaceholders(propertySchema, entry, emptyValues, depth + 1);
|
|
143
154
|
if (nested.changed) {
|
|
144
155
|
normalized ??= { ...record };
|
|
145
156
|
normalized[key] = nested.value;
|
|
@@ -175,7 +186,15 @@ export function normalizeOptionalToolPlaceholders(schema, input, entry, { nullOn
|
|
|
175
186
|
nullOnly,
|
|
176
187
|
listsEraseNothing: entry.emptyListsEraseNothing === true,
|
|
177
188
|
};
|
|
178
|
-
|
|
189
|
+
let result = stripOptionalToolPlaceholders(schema, input, emptyValues);
|
|
190
|
+
// A padded call means its nulls are padding too, including on nullable
|
|
191
|
+
// fields where a lone null would be a deliberate clear.
|
|
192
|
+
if (emptyValues.invalid && !emptyValues.valid) {
|
|
193
|
+
result = stripOptionalToolPlaceholders(schema, input, {
|
|
194
|
+
...emptyValues,
|
|
195
|
+
stripNull: true,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
179
198
|
// Only a declared read is exempt: a dynamic plan-mode effect classifies the
|
|
180
199
|
// raw input, which defaults can turn into a write.
|
|
181
200
|
if (emptyValues.valid &&
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
-
import { AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY, AGENT_NATIVE_SUBMISSION_TTL_MS_METADATA_KEY, MAX_AGENT_REQUEST_ATTACHMENT_DATA_CHARS, parseQueueMessageInput, persistableFilePart, fingerprintAgentSubmissionAttachment, submissionAttachmentsMatch, } from "@agent-native/agentkit/protocol";
|
|
3
|
+
import { AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY, AGENT_NATIVE_SUBMISSION_TTL_MS_METADATA_KEY, MAX_AGENT_REQUEST_ATTACHMENT_DATA_CHARS, parseQueueMessageInput, persistableFilePart, fingerprintAgentSubmissionAttachment, isPersistableAttachmentUrl, submissionAttachmentsMatch, } from "@agent-native/agentkit/protocol";
|
|
4
4
|
import Ajv from "ajv";
|
|
5
5
|
import Ajv2020 from "ajv/dist/2020.js";
|
|
6
6
|
import { defineEventHandler, getHeader, setResponseHeader, setResponseStatus, getMethod, } from "h3";
|
|
@@ -1412,23 +1412,6 @@ export { DurableAttachmentReferenceRequiredError };
|
|
|
1412
1412
|
function isDataUrlReference(value) {
|
|
1413
1413
|
return typeof value === "string" && /^\s*data:/i.test(value);
|
|
1414
1414
|
}
|
|
1415
|
-
function hasDurableAttachmentUrl(value) {
|
|
1416
|
-
if (typeof value !== "string")
|
|
1417
|
-
return false;
|
|
1418
|
-
const normalized = value.trim();
|
|
1419
|
-
if (!normalized ||
|
|
1420
|
-
isDataUrlReference(normalized) ||
|
|
1421
|
-
isRawBase64Payload(normalized) ||
|
|
1422
|
-
!URL.canParse(normalized)) {
|
|
1423
|
-
return false;
|
|
1424
|
-
}
|
|
1425
|
-
const url = new URL(normalized);
|
|
1426
|
-
return (url.protocol === "https:" &&
|
|
1427
|
-
!url.username &&
|
|
1428
|
-
!url.password &&
|
|
1429
|
-
!url.search &&
|
|
1430
|
-
!url.hash);
|
|
1431
|
-
}
|
|
1432
1415
|
const OMIT_DURABLE_DISPATCH_VALUE = Symbol("omit-durable-dispatch-value");
|
|
1433
1416
|
const DURABLE_ATTACHMENT_PAYLOAD_FIELDS = /^(?:base64|bytes|body|data|dataurl|image|payload)$/i;
|
|
1434
1417
|
const DURABLE_ATTACHMENT_REFERENCE_FIELDS = /^(?:preview|referenceUrl|src|thumbnail|url)$/i;
|
|
@@ -1516,7 +1499,7 @@ function sanitizeDurableAttachment(value, attachmentContext = false, requiredAtt
|
|
|
1516
1499
|
(attachmentReferenceField &&
|
|
1517
1500
|
typeof value === "string" &&
|
|
1518
1501
|
(isInlineBase64Payload(value) ||
|
|
1519
|
-
(value.trim().length > 0 && !
|
|
1502
|
+
(value.trim().length > 0 && !isPersistableAttachmentUrl(value)))))) {
|
|
1520
1503
|
return OMIT_DURABLE_DISPATCH_VALUE;
|
|
1521
1504
|
}
|
|
1522
1505
|
if (Array.isArray(value)) {
|
|
@@ -1537,12 +1520,12 @@ function sanitizeDurableAttachment(value, attachmentContext = false, requiredAtt
|
|
|
1537
1520
|
const isAttachment = attachmentContext || typedAttachment;
|
|
1538
1521
|
const hasInlinePayload = isAttachment && containsDurableAttachmentPayload(item, true);
|
|
1539
1522
|
const hasDurableReference = item.type === "image"
|
|
1540
|
-
?
|
|
1541
|
-
:
|
|
1542
|
-
|
|
1523
|
+
? isPersistableAttachmentUrl(item.url)
|
|
1524
|
+
: isPersistableAttachmentUrl(item.url) ||
|
|
1525
|
+
isPersistableAttachmentUrl(item.referenceUrl);
|
|
1543
1526
|
const hasInlineReference = [item.url, item.referenceUrl].some((reference) => typeof reference === "string" &&
|
|
1544
1527
|
reference.trim().length > 0 &&
|
|
1545
|
-
!
|
|
1528
|
+
!isPersistableAttachmentUrl(reference));
|
|
1546
1529
|
if ((requiredAttachment || typedAttachment) &&
|
|
1547
1530
|
(hasInlinePayload || hasInlineReference) &&
|
|
1548
1531
|
!hasDurableReference) {
|
|
@@ -9040,11 +9023,12 @@ async function queueableAttachments(attachments, ownerEmail, requestAttachmentIn
|
|
|
9040
9023
|
? { fileId: record.id }
|
|
9041
9024
|
: {}),
|
|
9042
9025
|
});
|
|
9043
|
-
|
|
9044
|
-
|
|
9026
|
+
let uploadAttempted = false;
|
|
9027
|
+
if (inlineData &&
|
|
9045
9028
|
(!part.url || !record.uploadProvider || record.storageUploadFailed)) {
|
|
9046
9029
|
const parsedData = parseBase64DataUrl(inlineData);
|
|
9047
9030
|
if (parsedData) {
|
|
9031
|
+
uploadAttempted = true;
|
|
9048
9032
|
const uploaded = await uploadFile({
|
|
9049
9033
|
data: new Uint8Array(Buffer.from(parsedData.data, "base64")),
|
|
9050
9034
|
filename: attachment.name,
|
|
@@ -9059,15 +9043,16 @@ async function queueableAttachments(attachments, ownerEmail, requestAttachmentIn
|
|
|
9059
9043
|
name: attachment.name,
|
|
9060
9044
|
...(mediaType ? { mediaType } : {}),
|
|
9061
9045
|
url: uploaded.url,
|
|
9046
|
+
...(part.fileId ? { fileId: part.fileId } : {}),
|
|
9062
9047
|
});
|
|
9063
9048
|
}
|
|
9064
9049
|
}
|
|
9065
9050
|
}
|
|
9066
9051
|
if (requestAttachmentIndexes.has(index)) {
|
|
9067
|
-
if (!part.url || !
|
|
9052
|
+
if (!part.url || !isPersistableAttachmentUrl(part.url))
|
|
9068
9053
|
return null;
|
|
9069
9054
|
const referenceUrl = attachment.referenceUrl;
|
|
9070
|
-
if (referenceUrl && !
|
|
9055
|
+
if (referenceUrl && !isPersistableAttachmentUrl(referenceUrl))
|
|
9071
9056
|
return null;
|
|
9072
9057
|
requestFiles.push({
|
|
9073
9058
|
type: "image",
|
|
@@ -9078,8 +9063,13 @@ async function queueableAttachments(attachments, ownerEmail, requestAttachmentIn
|
|
|
9078
9063
|
});
|
|
9079
9064
|
continue;
|
|
9080
9065
|
}
|
|
9081
|
-
if (!part.url
|
|
9082
|
-
|
|
9066
|
+
if (!part.url) {
|
|
9067
|
+
// A failed upload is transient (null -> retryable); an id with no
|
|
9068
|
+
// uploadable bytes can never become a reference.
|
|
9069
|
+
if (uploadAttempted)
|
|
9070
|
+
return null;
|
|
9071
|
+
throw new DurableAttachmentReferenceRequiredError();
|
|
9072
|
+
}
|
|
9083
9073
|
files.push(part);
|
|
9084
9074
|
}
|
|
9085
9075
|
return { attachments: files, requestAttachments: requestFiles };
|
|
@@ -9128,12 +9118,12 @@ function validateQueuedPromotionRequestAttachments(value) {
|
|
|
9128
9118
|
return null;
|
|
9129
9119
|
}
|
|
9130
9120
|
}
|
|
9131
|
-
if ((typeof url === "string" && !
|
|
9121
|
+
if ((typeof url === "string" && !isPersistableAttachmentUrl(url)) ||
|
|
9132
9122
|
(typeof attachment.referenceUrl === "string" &&
|
|
9133
|
-
!
|
|
9123
|
+
!isPersistableAttachmentUrl(attachment.referenceUrl)) ||
|
|
9134
9124
|
(typeof data !== "string" &&
|
|
9135
|
-
!
|
|
9136
|
-
!
|
|
9125
|
+
!isPersistableAttachmentUrl(url) &&
|
|
9126
|
+
!isPersistableAttachmentUrl(attachment.referenceUrl))) {
|
|
9137
9127
|
return null;
|
|
9138
9128
|
}
|
|
9139
9129
|
attachments.push(attachment);
|
|
@@ -10339,7 +10329,21 @@ export function createProductionAgentHandler(options) {
|
|
|
10339
10329
|
setResponseStatus(event, 404);
|
|
10340
10330
|
return { error: "Thread not found" };
|
|
10341
10331
|
}
|
|
10342
|
-
|
|
10332
|
+
let attachmentsForQueue;
|
|
10333
|
+
try {
|
|
10334
|
+
attachmentsForQueue = await queueableAttachments(requestAttachments, ownerEmail, submissionRequestAttachmentIndexes);
|
|
10335
|
+
}
|
|
10336
|
+
catch (error) {
|
|
10337
|
+
if (!(error instanceof DurableAttachmentReferenceRequiredError)) {
|
|
10338
|
+
throw error;
|
|
10339
|
+
}
|
|
10340
|
+
setResponseStatus(event, 503);
|
|
10341
|
+
return {
|
|
10342
|
+
error: error.message,
|
|
10343
|
+
code: error.code,
|
|
10344
|
+
retryable: false,
|
|
10345
|
+
};
|
|
10346
|
+
}
|
|
10343
10347
|
if (!attachmentsForQueue) {
|
|
10344
10348
|
if (requestAttachments.some((attachment) => attachment.displayOnly !== true &&
|
|
10345
10349
|
(attachment.type === "image" ||
|
package/dist/agent/run-store.js
CHANGED
|
@@ -5138,13 +5138,16 @@ async function retryPendingStaleRunThreadProjections(client) {
|
|
|
5138
5138
|
const projection = row;
|
|
5139
5139
|
if (projection.thread_projection_error_code != null) {
|
|
5140
5140
|
failed += 1;
|
|
5141
|
+
console.error(`[run-store] stale projection stuck for run ${String(projection.id)}:`, projection.thread_projection_error_code);
|
|
5141
5142
|
continue;
|
|
5142
5143
|
}
|
|
5143
5144
|
const runId = projection.id;
|
|
5144
5145
|
if (typeof runId !== "string")
|
|
5145
5146
|
continue;
|
|
5146
|
-
if (!(await finalizeStaleRun(runId, "stale-projection-retry")))
|
|
5147
|
+
if (!(await finalizeStaleRun(runId, "stale-projection-retry"))) {
|
|
5147
5148
|
failed += 1;
|
|
5149
|
+
console.error(`[run-store] stale projection retry failed for run ${runId}`);
|
|
5150
|
+
}
|
|
5148
5151
|
}
|
|
5149
5152
|
return { failed, truncated };
|
|
5150
5153
|
}
|
|
@@ -15,4 +15,28 @@ export interface WorkspaceProviderOAuthOptions {
|
|
|
15
15
|
targetAccountEmail?: string;
|
|
16
16
|
}
|
|
17
17
|
export declare function workspaceProviderOAuthUrl(provider: string, options: WorkspaceProviderOAuthOptions): string;
|
|
18
|
+
/** True when this app runs inside another page's iframe (for example Dispatch). */
|
|
19
|
+
export declare function isFramedContext(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The completion route the OAuth popup returns to. Written in the
|
|
22
|
+
* mount-relative, internal `/_agent-native` form: the app's server joins it to
|
|
23
|
+
* its own mount (see `getAppUrl`), so a path-mounted deployment does not double
|
|
24
|
+
* the prefix. The completion page posts the workspace-connection message back
|
|
25
|
+
* to the opener.
|
|
26
|
+
*/
|
|
27
|
+
export declare const WORKSPACE_CONNECTION_COMPLETION_RETURN = "/_agent-native/oauth/popup?complete=workspace-connection";
|
|
28
|
+
/**
|
|
29
|
+
* The start URL for a popup that returns to the framework completion page, so
|
|
30
|
+
* an embedded app can learn the flow finished instead of navigating its frame
|
|
31
|
+
* to the provider (which providers refuse to be framed). The caller's own
|
|
32
|
+
* `returnPath` is preserved in the options and applied on completion.
|
|
33
|
+
*/
|
|
34
|
+
export declare function workspaceProviderOAuthPopupStartUrl(provider: string, options: WorkspaceProviderOAuthOptions): string;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve when the popup posts the framework completion message, or when it is
|
|
37
|
+
* closed without one. Accepts the completion page's origin, which can be a
|
|
38
|
+
* configured API origin distinct from the embedded UI origin, while still
|
|
39
|
+
* binding to the exact popup window. Returns a stop function.
|
|
40
|
+
*/
|
|
41
|
+
export declare function watchWorkspaceConnectionCompletion(popup: Window, onComplete: () => void, expectedOrigin?: string): () => void;
|
|
18
42
|
export declare function startWorkspaceProviderOAuth(provider: string, options: WorkspaceProviderOAuthOptions): void;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { FRAMEWORK_INTERNAL_ROUTE_PREFIX } from "../../shared/framework-route-prefix.js";
|
|
1
2
|
import { agentNativePath } from "../api-path.js";
|
|
3
|
+
import { openOAuthPopup } from "../oauth-popup.js";
|
|
2
4
|
import { serverMountReturnPath } from "../sign-in-return.js";
|
|
3
5
|
export function workspaceProviderOAuthUrl(provider, options) {
|
|
4
6
|
const params = new URLSearchParams({
|
|
@@ -22,6 +24,114 @@ export function workspaceProviderOAuthUrl(provider, options) {
|
|
|
22
24
|
}
|
|
23
25
|
return agentNativePath(`/_agent-native/connections/oauth/${provider}/start?${params.toString()}`);
|
|
24
26
|
}
|
|
27
|
+
/** True when this app runs inside another page's iframe (for example Dispatch). */
|
|
28
|
+
export function isFramedContext() {
|
|
29
|
+
if (typeof window === "undefined")
|
|
30
|
+
return false;
|
|
31
|
+
try {
|
|
32
|
+
return window.top !== window.self;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// A cross-origin ancestor makes `window.top` throw; that means framed.
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The completion route the OAuth popup returns to. Written in the
|
|
41
|
+
* mount-relative, internal `/_agent-native` form: the app's server joins it to
|
|
42
|
+
* its own mount (see `getAppUrl`), so a path-mounted deployment does not double
|
|
43
|
+
* the prefix. The completion page posts the workspace-connection message back
|
|
44
|
+
* to the opener.
|
|
45
|
+
*/
|
|
46
|
+
export const WORKSPACE_CONNECTION_COMPLETION_RETURN = `${FRAMEWORK_INTERNAL_ROUTE_PREFIX}/oauth/popup?complete=workspace-connection`;
|
|
47
|
+
/** The origin that will serve the completion page and post the message. */
|
|
48
|
+
function completionOrigin(startUrl) {
|
|
49
|
+
if (!URL.canParse(startUrl, window.location.href))
|
|
50
|
+
return undefined;
|
|
51
|
+
return new URL(startUrl, window.location.href).origin;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The start URL for a popup that returns to the framework completion page, so
|
|
55
|
+
* an embedded app can learn the flow finished instead of navigating its frame
|
|
56
|
+
* to the provider (which providers refuse to be framed). The caller's own
|
|
57
|
+
* `returnPath` is preserved in the options and applied on completion.
|
|
58
|
+
*/
|
|
59
|
+
export function workspaceProviderOAuthPopupStartUrl(provider, options) {
|
|
60
|
+
return workspaceProviderOAuthUrl(provider, {
|
|
61
|
+
...options,
|
|
62
|
+
serverReturnPath: WORKSPACE_CONNECTION_COMPLETION_RETURN,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve when the popup posts the framework completion message, or when it is
|
|
67
|
+
* closed without one. Accepts the completion page's origin, which can be a
|
|
68
|
+
* configured API origin distinct from the embedded UI origin, while still
|
|
69
|
+
* binding to the exact popup window. Returns a stop function.
|
|
70
|
+
*/
|
|
71
|
+
export function watchWorkspaceConnectionCompletion(popup, onComplete, expectedOrigin) {
|
|
72
|
+
let closedTicks = 0;
|
|
73
|
+
const stop = () => {
|
|
74
|
+
window.removeEventListener("message", onMessage);
|
|
75
|
+
window.clearInterval(closedTimer);
|
|
76
|
+
};
|
|
77
|
+
const onMessage = (event) => {
|
|
78
|
+
const originAccepted = event.origin === window.location.origin ||
|
|
79
|
+
(expectedOrigin !== undefined && event.origin === expectedOrigin);
|
|
80
|
+
if (!originAccepted || event.source !== popup)
|
|
81
|
+
return;
|
|
82
|
+
const data = event.data;
|
|
83
|
+
if (typeof data !== "object" ||
|
|
84
|
+
data === null ||
|
|
85
|
+
data.type !==
|
|
86
|
+
"agent-native:workspace-connection-complete")
|
|
87
|
+
return;
|
|
88
|
+
stop();
|
|
89
|
+
onComplete();
|
|
90
|
+
};
|
|
91
|
+
// The completion message is posted just before the popup closes; wait one
|
|
92
|
+
// extra tick so a close never wins the race against it.
|
|
93
|
+
const closedTimer = window.setInterval(() => {
|
|
94
|
+
if (!popup.closed) {
|
|
95
|
+
closedTicks = 0;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
closedTicks += 1;
|
|
99
|
+
if (closedTicks > 1)
|
|
100
|
+
stop();
|
|
101
|
+
}, 500);
|
|
102
|
+
window.addEventListener("message", onMessage);
|
|
103
|
+
return stop;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Apply the caller's return target on completion, so resume state carried in
|
|
107
|
+
* `returnPath` survives an embedded flow. Falls back to a reload.
|
|
108
|
+
*/
|
|
109
|
+
function applyReturnPath(options) {
|
|
110
|
+
if (options.returnPath) {
|
|
111
|
+
window.location.assign(options.returnPath);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
window.location.reload();
|
|
115
|
+
}
|
|
25
116
|
export function startWorkspaceProviderOAuth(provider, options) {
|
|
117
|
+
if (isFramedContext()) {
|
|
118
|
+
// Providers refuse to be framed (X-Frame-Options / frame-ancestors), so a
|
|
119
|
+
// same-window navigation inside Dispatch's iframe is refused. Leave the
|
|
120
|
+
// frame: run the flow in a popup whose completion page posts back.
|
|
121
|
+
const startUrl = workspaceProviderOAuthPopupStartUrl(provider, options);
|
|
122
|
+
const popup = openOAuthPopup({
|
|
123
|
+
initialUrl: startUrl,
|
|
124
|
+
features: "width=640,height=760",
|
|
125
|
+
});
|
|
126
|
+
if (popup) {
|
|
127
|
+
watchWorkspaceConnectionCompletion(popup, () => applyReturnPath(options), completionOrigin(startUrl));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Popup blocked while embedded. Never navigate this frame into the
|
|
131
|
+
// provider — the frame refuses it and the user cannot connect at all.
|
|
132
|
+
// Surface the recovery; the caller retries once popups are allowed.
|
|
133
|
+
console.warn("[agent-native] OAuth popup was blocked while embedded in another page; allow popups for this site to connect the provider.");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
26
136
|
window.location.assign(workspaceProviderOAuthUrl(provider, options));
|
|
27
137
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@agent-native/agentkit": "0.225.1
|
|
2
|
+
"@agent-native/agentkit": "0.225.1",
|
|
3
3
|
"@agent-native/code-agents-ui": "0.1.75",
|
|
4
|
-
"@agent-native/core": "0.225.1
|
|
5
|
-
"@agent-native/core-corpus": "0.225.1
|
|
6
|
-
"@agent-native/creative-context": "0.8.
|
|
7
|
-
"@agent-native/dispatch": "0.42.2
|
|
4
|
+
"@agent-native/core": "0.225.1",
|
|
5
|
+
"@agent-native/core-corpus": "0.225.1",
|
|
6
|
+
"@agent-native/creative-context": "0.8.67",
|
|
7
|
+
"@agent-native/dispatch": "0.42.2",
|
|
8
8
|
"@agent-native/embedding": "0.2.0",
|
|
9
9
|
"@agent-native/migrate": "0.0.7",
|
|
10
|
-
"@agent-native/otel": "0.1.
|
|
11
|
-
"@agent-native/pinpoint": "0.1.
|
|
12
|
-
"@agent-native/recap-cli": "0.5.
|
|
13
|
-
"@agent-native/scheduling": "0.2.
|
|
10
|
+
"@agent-native/otel": "0.1.34",
|
|
11
|
+
"@agent-native/pinpoint": "0.1.110",
|
|
12
|
+
"@agent-native/recap-cli": "0.5.98",
|
|
13
|
+
"@agent-native/scheduling": "0.2.54",
|
|
14
14
|
"@agent-native/shared-app-config": "0.1.2",
|
|
15
|
-
"@agent-native/skills": "0.4.2
|
|
16
|
-
"@agent-native/toolkit": "0.225.1
|
|
15
|
+
"@agent-native/skills": "0.4.2",
|
|
16
|
+
"@agent-native/toolkit": "0.225.1"
|
|
17
17
|
}
|
|
@@ -8167,28 +8167,38 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
8167
8167
|
jobsSkippedReason: "unclaimed-background-sweep-failed",
|
|
8168
8168
|
};
|
|
8169
8169
|
}
|
|
8170
|
+
const incompleteSteps = [
|
|
8171
|
+
staleRunReapIncomplete && "staleRunsReaped",
|
|
8172
|
+
agentHarnessFollowUpRecoveryIncomplete &&
|
|
8173
|
+
"expiredAgentHarnessFollowUps",
|
|
8174
|
+
agentHarnessApprovalRecoveryIncomplete &&
|
|
8175
|
+
"expiredAgentHarnessApprovals",
|
|
8176
|
+
appSweepHandlers.failed.length > 0 && "appSweepHandlers",
|
|
8177
|
+
automationFailureAlerts === null && "automationFailureAlerts",
|
|
8178
|
+
(queuedSubmissionDrain === null ||
|
|
8179
|
+
queuedSubmissionDrain.failed > 0) &&
|
|
8180
|
+
"queuedSubmissionDrain",
|
|
8181
|
+
(staleAgentTeamRuns === null || staleAgentTeamRuns.failed > 0) &&
|
|
8182
|
+
"staleAgentTeamRuns",
|
|
8183
|
+
].filter((step) => typeof step === "string");
|
|
8184
|
+
if (incompleteSteps.length > 0) {
|
|
8185
|
+
console.error("[recurring-jobs] Sweep incomplete:", {
|
|
8186
|
+
incompleteSteps,
|
|
8187
|
+
staleRunsReaped,
|
|
8188
|
+
expiredAgentHarnessFollowUps,
|
|
8189
|
+
expiredAgentHarnessApprovals,
|
|
8190
|
+
staleAgentTeamRuns,
|
|
8191
|
+
queuedSubmissionDrain,
|
|
8192
|
+
automationFailureAlerts,
|
|
8193
|
+
appSweepHandlers,
|
|
8194
|
+
});
|
|
8195
|
+
}
|
|
8170
8196
|
if (!triggerAvailability.available) {
|
|
8171
|
-
if (
|
|
8172
|
-
agentHarnessFollowUpRecoveryIncomplete ||
|
|
8173
|
-
agentHarnessApprovalRecoveryIncomplete ||
|
|
8174
|
-
appSweepHandlers.failed.length > 0 ||
|
|
8175
|
-
automationFailureAlerts === null ||
|
|
8176
|
-
queuedSubmissionDrain === null ||
|
|
8177
|
-
queuedSubmissionDrain.failed > 0 ||
|
|
8178
|
-
staleAgentTeamRuns === null ||
|
|
8179
|
-
staleAgentTeamRuns.failed > 0) {
|
|
8197
|
+
if (incompleteSteps.length > 0) {
|
|
8180
8198
|
setResponseStatus(event, 500);
|
|
8181
8199
|
}
|
|
8182
8200
|
return {
|
|
8183
|
-
ok:
|
|
8184
|
-
!agentHarnessFollowUpRecoveryIncomplete &&
|
|
8185
|
-
!agentHarnessApprovalRecoveryIncomplete &&
|
|
8186
|
-
appSweepHandlers.failed.length === 0 &&
|
|
8187
|
-
automationFailureAlerts !== null &&
|
|
8188
|
-
queuedSubmissionDrain !== null &&
|
|
8189
|
-
queuedSubmissionDrain.failed === 0 &&
|
|
8190
|
-
staleAgentTeamRuns !== null &&
|
|
8191
|
-
staleAgentTeamRuns.failed === 0,
|
|
8201
|
+
ok: incompleteSteps.length === 0,
|
|
8192
8202
|
staleRunsReaped,
|
|
8193
8203
|
expiredAgentHarnessFollowUps,
|
|
8194
8204
|
expiredAgentHarnessApprovals,
|
|
@@ -8204,15 +8214,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
8204
8214
|
}
|
|
8205
8215
|
try {
|
|
8206
8216
|
await processRecurringJobs(schedulerDeps);
|
|
8207
|
-
if (
|
|
8208
|
-
agentHarnessFollowUpRecoveryIncomplete ||
|
|
8209
|
-
agentHarnessApprovalRecoveryIncomplete ||
|
|
8210
|
-
appSweepHandlers.failed.length > 0 ||
|
|
8211
|
-
automationFailureAlerts === null ||
|
|
8212
|
-
queuedSubmissionDrain === null ||
|
|
8213
|
-
queuedSubmissionDrain.failed > 0 ||
|
|
8214
|
-
staleAgentTeamRuns === null ||
|
|
8215
|
-
staleAgentTeamRuns.failed > 0) {
|
|
8217
|
+
if (incompleteSteps.length > 0) {
|
|
8216
8218
|
setResponseStatus(event, 500);
|
|
8217
8219
|
return {
|
|
8218
8220
|
ok: false,
|
package/dist/server/auth.js
CHANGED
|
@@ -3211,11 +3211,38 @@ function createAuthGuardFn(app) {
|
|
|
3211
3211
|
if (session) {
|
|
3212
3212
|
const method = getMethod(event);
|
|
3213
3213
|
if (p.startsWith("/api/") || p.startsWith("/_agent-native/")) {
|
|
3214
|
+
// A fresh loopback dev workspace has no session org yet, and the
|
|
3215
|
+
// read-only session resolver never creates one, so the app-access check
|
|
3216
|
+
// below can deny before the ordinary org initializer runs. Resolve (or
|
|
3217
|
+
// create) the existing default org through the normal policy first and
|
|
3218
|
+
// hand its id to the unchanged check. The check still owns the verdict;
|
|
3219
|
+
// a missing or failed org initialization also denies protected requests,
|
|
3220
|
+
// while null keeps the wrapper's org-recovery exemption.
|
|
3221
|
+
let workspaceOrgId = session.orgId;
|
|
3222
|
+
let workspaceOrgInitializationFailed = false;
|
|
3223
|
+
if (!workspaceOrgId &&
|
|
3224
|
+
!hasExplicitPersonalOrgScope(event) &&
|
|
3225
|
+
isSyntheticAutoDevIdentity(session.email) &&
|
|
3226
|
+
isLoopbackDevRequest(event) &&
|
|
3227
|
+
getAppConfig().access.orgCreation === "open" &&
|
|
3228
|
+
getAppConfig().access.autoCreateDefaultOrg) {
|
|
3229
|
+
const { getOrgContext } = await import("../org/context.js");
|
|
3230
|
+
try {
|
|
3231
|
+
const org = await getOrgContext(event);
|
|
3232
|
+
if (org.orgId)
|
|
3233
|
+
workspaceOrgId = org.orgId;
|
|
3234
|
+
else
|
|
3235
|
+
workspaceOrgInitializationFailed = true;
|
|
3236
|
+
}
|
|
3237
|
+
catch {
|
|
3238
|
+
workspaceOrgInitializationFailed = true;
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3214
3241
|
let workspaceAppAccess = await checkWorkspaceAppAccessForRequest({
|
|
3215
3242
|
path: p,
|
|
3216
3243
|
method,
|
|
3217
3244
|
email: session.email,
|
|
3218
|
-
...(
|
|
3245
|
+
...(workspaceOrgId === undefined ? {} : { orgId: workspaceOrgId }),
|
|
3219
3246
|
});
|
|
3220
3247
|
if (workspaceAppAccess === false &&
|
|
3221
3248
|
!session.orgId &&
|
|
@@ -3246,7 +3273,8 @@ function createAuthGuardFn(app) {
|
|
|
3246
3273
|
setResponseStatus(event, 503);
|
|
3247
3274
|
return workspaceAppAccessUnavailableBody(workspaceAppAccess);
|
|
3248
3275
|
}
|
|
3249
|
-
if (workspaceAppAccess
|
|
3276
|
+
if ((workspaceOrgInitializationFailed && workspaceAppAccess !== null) ||
|
|
3277
|
+
workspaceAppAccess === false) {
|
|
3250
3278
|
setResponseStatus(event, 403);
|
|
3251
3279
|
return { error: "You do not have access to this workspace app." };
|
|
3252
3280
|
}
|
|
@@ -3408,6 +3436,22 @@ export function isLocalDevAuthAllowed(event) {
|
|
|
3408
3436
|
((!isHostedPreviewRequest(event) && isLoopbackRequest(event)) ||
|
|
3409
3437
|
isBuilderPreviewLocalDevRequest(event)));
|
|
3410
3438
|
}
|
|
3439
|
+
/**
|
|
3440
|
+
* A real loopback dev/test request. `isLocalDevAuthAllowed` also admits the
|
|
3441
|
+
* Builder-preview branch, which does not require loopback, so org
|
|
3442
|
+
* auto-initialization additionally requires the actual socket peer and
|
|
3443
|
+
* excludes hosted-preview hosts. Builder preview keeps the ordinary
|
|
3444
|
+
* app-access path.
|
|
3445
|
+
*/
|
|
3446
|
+
function isLoopbackDevRequest(event) {
|
|
3447
|
+
return (isLocalDevAuthAllowed(event) &&
|
|
3448
|
+
isLoopbackRequest(event) &&
|
|
3449
|
+
!isHostedPreviewRequest(event));
|
|
3450
|
+
}
|
|
3451
|
+
/** The framework's own throwaway local account, never a real signup. */
|
|
3452
|
+
function isSyntheticAutoDevIdentity(email) {
|
|
3453
|
+
return (email === AUTO_DEV_ACCOUNT_EMAIL || email === LEGACY_AUTO_DEV_ACCOUNT_EMAIL);
|
|
3454
|
+
}
|
|
3411
3455
|
async function createOrReuseAutoDevSession(auth, config) {
|
|
3412
3456
|
let session = await createBetterAuthSessionForEmail(AUTO_DEV_ACCOUNT_EMAIL, config);
|
|
3413
3457
|
if (!session) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPersistableAttachmentUrl } from "@agent-native/agentkit/protocol";
|
|
2
|
+
export { isPersistableAttachmentUrl };
|
|
1
3
|
export declare function isInlineDataUrl(value: unknown): value is string;
|
|
2
|
-
export declare function isPersistableAttachmentUrl(value: unknown): value is string;
|
|
3
4
|
export declare function stripInlineAttachmentPayloads(value: unknown, attachmentContext?: boolean): unknown;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isPersistableAttachmentUrl } from "@agent-native/agentkit/protocol";
|
|
2
|
+
export { isPersistableAttachmentUrl };
|
|
1
3
|
const ATTACHMENT_BODY_FIELDS = new Set([
|
|
2
4
|
"base64",
|
|
3
5
|
"bytes",
|
|
@@ -30,19 +32,6 @@ const ATTACHMENT_CONTEXT_FIELDS = new Set([
|
|
|
30
32
|
export function isInlineDataUrl(value) {
|
|
31
33
|
return typeof value === "string" && /^\s*data:/i.test(value);
|
|
32
34
|
}
|
|
33
|
-
export function isPersistableAttachmentUrl(value) {
|
|
34
|
-
if (typeof value !== "string" || !value.trim())
|
|
35
|
-
return false;
|
|
36
|
-
if (!URL.canParse(value))
|
|
37
|
-
return false;
|
|
38
|
-
const url = new URL(value);
|
|
39
|
-
return (url.protocol === "https:" &&
|
|
40
|
-
Boolean(url.hostname) &&
|
|
41
|
-
!url.username &&
|
|
42
|
-
!url.password &&
|
|
43
|
-
!url.search &&
|
|
44
|
-
!url.hash);
|
|
45
|
-
}
|
|
46
35
|
function isBase64Payload(value) {
|
|
47
36
|
return (typeof value === "string" &&
|
|
48
37
|
value.trim().length >= 64 &&
|
|
@@ -1,19 +1,6 @@
|
|
|
1
|
+
import { isPersistableAttachmentReferenceUrl } from "@agent-native/agentkit/protocol";
|
|
1
2
|
import { isInlineDataUrl } from "./attachments.js";
|
|
2
3
|
import { parseDataUrl } from "./data-url.js";
|
|
3
|
-
function isPersistableAttachmentReferenceUrl(value) {
|
|
4
|
-
if (typeof value !== "string" || !value.trim() || value.length > 8192) {
|
|
5
|
-
return false;
|
|
6
|
-
}
|
|
7
|
-
if (!URL.canParse(value))
|
|
8
|
-
return false;
|
|
9
|
-
const url = new URL(value);
|
|
10
|
-
return ((url.protocol === "http:" || url.protocol === "https:") &&
|
|
11
|
-
Boolean(url.hostname) &&
|
|
12
|
-
!url.username &&
|
|
13
|
-
!url.password &&
|
|
14
|
-
!url.search &&
|
|
15
|
-
!url.hash);
|
|
16
|
-
}
|
|
17
4
|
/**
|
|
18
5
|
* Inline file bytes reached a SQL write with no durable URL to stand in for
|
|
19
6
|
* them. Raised only by the `reject` policy; a durable dispatch payload must
|
|
@@ -177,10 +164,10 @@ function sanitize(value, policy, inAttachmentList, inheritedUrl, inAttachmentMet
|
|
|
177
164
|
const ownUrl = hasAttachmentContext ? durableUrl(item) : undefined;
|
|
178
165
|
const fallbackUrl = ownUrl ?? (hasAttachmentContext ? inheritedUrl : undefined);
|
|
179
166
|
const inline = hasAttachmentContext ? inlineKeys(item) : [];
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
167
|
+
// A `fileId` names the attachment; nothing resolves it back to bytes, so it
|
|
168
|
+
// never stands in for bytes that need a URL. The dispatch serializer agrees
|
|
169
|
+
// for parts that carry bytes.
|
|
170
|
+
const unreferenced = isAttachment && inline.length > 0 && !fallbackUrl;
|
|
184
171
|
if (unreferenced && policy === "reject") {
|
|
185
172
|
throw new DurableAttachmentReferenceRequiredError();
|
|
186
173
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.225.1
|
|
3
|
+
"version": "0.225.1",
|
|
4
4
|
"description": "The agentic application framework for building autonomous agents with intuitive UIs",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -760,8 +760,8 @@
|
|
|
760
760
|
"yaml": "^2.9.0",
|
|
761
761
|
"yjs": "^13.6.33",
|
|
762
762
|
"zod": "^4.3.6",
|
|
763
|
-
"@agent-native/
|
|
764
|
-
"@agent-native/
|
|
763
|
+
"@agent-native/agentkit": "^0.225.1",
|
|
764
|
+
"@agent-native/recap-cli": "^0.5.98"
|
|
765
765
|
},
|
|
766
766
|
"devDependencies": {
|
|
767
767
|
"@ai-sdk/anthropic": "^3.0.71",
|
|
@@ -833,7 +833,7 @@
|
|
|
833
833
|
"xlsx": "^0.18.5"
|
|
834
834
|
},
|
|
835
835
|
"peerDependencies": {
|
|
836
|
-
"@agent-native/core-corpus": "0.
|
|
836
|
+
"@agent-native/core-corpus": ">=0.196.0",
|
|
837
837
|
"@ai-sdk/anthropic": ">=3",
|
|
838
838
|
"@ai-sdk/cohere": ">=3",
|
|
839
839
|
"@ai-sdk/google": ">=3",
|