@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.
@@ -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. Elsewhere `[]` could wipe a stored
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
- const result = stripOptionalToolPlaceholders(schema, input, emptyValues);
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 && !hasDurableAttachmentUrl(value)))))) {
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
- ? hasDurableAttachmentUrl(item.url)
1541
- : hasDurableAttachmentUrl(item.url) ||
1542
- hasDurableAttachmentUrl(item.referenceUrl);
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
- !hasDurableAttachmentUrl(reference));
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
- if (!part.fileId &&
9044
- inlineData &&
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 || !hasDurableAttachmentUrl(part.url))
9052
+ if (!part.url || !isPersistableAttachmentUrl(part.url))
9068
9053
  return null;
9069
9054
  const referenceUrl = attachment.referenceUrl;
9070
- if (referenceUrl && !hasDurableAttachmentUrl(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 && !part.fileId)
9082
- return null;
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" && !hasDurableAttachmentUrl(url)) ||
9121
+ if ((typeof url === "string" && !isPersistableAttachmentUrl(url)) ||
9132
9122
  (typeof attachment.referenceUrl === "string" &&
9133
- !hasDurableAttachmentUrl(attachment.referenceUrl)) ||
9123
+ !isPersistableAttachmentUrl(attachment.referenceUrl)) ||
9134
9124
  (typeof data !== "string" &&
9135
- !hasDurableAttachmentUrl(url) &&
9136
- !hasDurableAttachmentUrl(attachment.referenceUrl))) {
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
- const attachmentsForQueue = await queueableAttachments(requestAttachments, ownerEmail, submissionRequestAttachmentIndexes);
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" ||
@@ -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-nightly-20261106122820",
2
+ "@agent-native/agentkit": "0.225.1",
3
3
  "@agent-native/code-agents-ui": "0.1.75",
4
- "@agent-native/core": "0.225.1-nightly-20261106122820",
5
- "@agent-native/core-corpus": "0.225.1-nightly-20261106122820",
6
- "@agent-native/creative-context": "0.8.66",
7
- "@agent-native/dispatch": "0.42.2-nightly-20261106122820",
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.33",
11
- "@agent-native/pinpoint": "0.1.109",
12
- "@agent-native/recap-cli": "0.5.97",
13
- "@agent-native/scheduling": "0.2.53",
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-nightly-20261106122820",
16
- "@agent-native/toolkit": "0.225.1-nightly-20261106122820"
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 (staleRunReapIncomplete ||
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: !staleRunReapIncomplete &&
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 (staleRunReapIncomplete ||
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,
@@ -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
- ...(session.orgId === undefined ? {} : { orgId: session.orgId }),
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 === false) {
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
- const unreferenced = isAttachment &&
181
- inline.length > 0 &&
182
- !fallbackUrl &&
183
- !(typeof item.fileId === "string" && item.fileId);
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-nightly-20261106122820",
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/recap-cli": "^0.5.97",
764
- "@agent-native/agentkit": "^0.225.1-nightly-20261106122820"
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.225.1-nightly-20261106122820",
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",