@agent-native/core 0.222.1 → 0.222.2-nightly-20261029182928

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.
@@ -21,6 +21,8 @@ export interface AgentEngineStatusResult {
21
21
  export interface AgentEngineStatusResponse extends AgentEngineStatusResult {
22
22
  /** Fast chat setup snapshot; dispatch rechecks credentials before model use. */
23
23
  chatEligible: boolean;
24
+ /** The deploy's Builder.io credits pair is set but this runtime refuses it. */
25
+ builderCreditsRefused?: true;
24
26
  }
25
27
  export interface AgentEngineStatusDeps<E extends AgentEngineStatusEntry = AgentEngineStatusEntry> {
26
28
  readStoredEngine: () => Promise<{
@@ -5,6 +5,8 @@ export type AgentRunStatusRead = {
5
5
  runId?: string;
6
6
  turnId?: string;
7
7
  terminalReason?: string;
8
+ /** A truncated run whose continuation the server has not started yet. */
9
+ awaitingRedispatch?: boolean;
8
10
  } | {
9
11
  ok: false;
10
12
  };
@@ -38,6 +38,9 @@ export async function readAgentRunStatus(input) {
38
38
  ...(typeof state.terminalReason === "string" && state.terminalReason
39
39
  ? { terminalReason: state.terminalReason }
40
40
  : {}),
41
+ ...(state.awaitingRedispatch === true
42
+ ? { awaitingRedispatch: true }
43
+ : {}),
41
44
  };
42
45
  }
43
46
  catch {
@@ -3416,6 +3416,7 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3416
3416
  threadId: input.threadId,
3417
3417
  tabId: input.threadId,
3418
3418
  runId: run.runId,
3419
+ reason: "run.submitted",
3419
3420
  });
3420
3421
  return run;
3421
3422
  }
@@ -7,6 +7,10 @@ export interface AgentChatRunningEventDetail {
7
7
  tabId?: string;
8
8
  turnId?: string;
9
9
  runId?: string;
10
+ /**
11
+ * `"run.submitted"` marks the run this client just started for a submitted
12
+ * message, which carries no successor link to any earlier run.
13
+ */
10
14
  reason?: string;
11
15
  }
12
16
  export interface UseAgentChatRunningThreadsOptions {
@@ -104,6 +104,11 @@ declare const messages: {
104
104
  composer: {
105
105
  chatgptManageUsage: string;
106
106
  chatgptPlanUsing: string;
107
+ newChatPlaceholder: string;
108
+ };
109
+ setup: {
110
+ connectBuilder: string;
111
+ connectPlaceholder: string;
107
112
  };
108
113
  error: {
109
114
  chatgptPlanUsageLimit: string;
@@ -28,6 +28,11 @@ const messages = {
28
28
  composer: {
29
29
  chatgptManageUsage: "Manage usage",
30
30
  chatgptPlanUsing: "Using ChatGPT plan",
31
+ newChatPlaceholder: "Ask anything",
32
+ },
33
+ setup: {
34
+ connectBuilder: "Connect Builder.io",
35
+ connectPlaceholder: "Connect AI to start chatting...",
31
36
  },
32
37
  error: {
33
38
  chatgptPlanUsageLimit: "Your ChatGPT plan usage limit has been reached.",
@@ -1,4 +1,4 @@
1
- export type BuilderBranchReadbackStage = "status_request" | "status_shape" | "status_binding" | "status_currentness" | "post_read_status_request" | "post_read_status_shape" | "post_read_status_binding" | "post_read_status_currentness" | "download_url_request" | "download_url_shape" | "download_url_binding" | "bundle_download" | "bundle_read" | "bundle_digest" | "bundle_parse" | "target_subtree_missing" | "post_read_currentness";
1
+ export type BuilderBranchReadbackStage = "status_request" | "status_shape" | "status_binding" | "status_no_backup" | "status_currentness" | "post_read_status_request" | "post_read_status_shape" | "post_read_status_binding" | "post_read_status_currentness" | "download_url_request" | "download_url_shape" | "download_url_binding" | "bundle_download" | "bundle_read" | "bundle_digest" | "bundle_parse" | "target_subtree_missing" | "post_read_currentness";
2
2
  export declare class BuilderBranchFilesError extends Error {
3
3
  readonly code: "invalid_request" | "not_connected" | "forbidden" | "unavailable" | "unverified" | "integrity_failed";
4
4
  readonly readbackStage?: BuilderBranchReadbackStage | undefined;
@@ -8,6 +8,16 @@ type BranchRef = {
8
8
  projectId: string;
9
9
  branchName: string;
10
10
  };
11
+ export declare function readBuilderBranchBackupStatus(input: BranchRef): Promise<{
12
+ branchState: "active" | "archived" | "deleted" | "unknown";
13
+ backupPartial: "invalid" | "missing" | boolean;
14
+ backupValid: boolean;
15
+ backupComplete: boolean;
16
+ backupCurrent: boolean;
17
+ agentIdle: boolean;
18
+ hasUnpushedChanges: boolean;
19
+ verificationPerformed: false;
20
+ }>;
11
21
  export declare function readBuilderBranchFiles(input: BranchRef & {
12
22
  appPath: string;
13
23
  }): Promise<{
@@ -52,6 +52,78 @@ const statusSchema = z.object({
52
52
  }),
53
53
  }),
54
54
  });
55
+ const inspectedBackupSchema = z.object({
56
+ projectId: z.string().min(1).max(200),
57
+ ownerId: z.string().min(1).max(256),
58
+ branchName: z.string().min(1).max(256),
59
+ at: z.number().finite().positive(),
60
+ lastCommitHash: z.string().min(1).max(8192),
61
+ partial: z.unknown().optional(),
62
+ status: z.enum(["initiated", "completed"]),
63
+ });
64
+ const invalidBackupOutcomes = z.enum([
65
+ "no-backup",
66
+ "error",
67
+ "not-completed",
68
+ "no-session-id",
69
+ "no-last-commit-hash",
70
+ "no-backup-file",
71
+ "empty-full-backup",
72
+ "no-vcpCodeGenEvent",
73
+ "commits-not-in-repo",
74
+ "no-after-commit",
75
+ "no-signed-url",
76
+ "no-git-branch-name",
77
+ "repo-url-mismatch",
78
+ "branch-uninitialized",
79
+ "unexpected-partial-backup",
80
+ "unexpected-full-backup",
81
+ "no-backup-keys",
82
+ ]);
83
+ const backupCheckSchema = z.discriminatedUnion("state", [
84
+ z.object({
85
+ state: z.literal("valid"),
86
+ outcome: z.literal("valid"),
87
+ backup: inspectedBackupSchema,
88
+ }),
89
+ z.object({
90
+ state: z.literal("forced-backup"),
91
+ outcome: z.literal("forced-offline-full-backup"),
92
+ backup: inspectedBackupSchema,
93
+ }),
94
+ z.object({
95
+ state: z.literal("stale"),
96
+ outcome: z.literal("hash-mismatch"),
97
+ backup: inspectedBackupSchema,
98
+ }),
99
+ z.object({
100
+ state: z.literal("submodule-changes"),
101
+ outcome: z.literal("submodule-changes"),
102
+ backup: inspectedBackupSchema,
103
+ }),
104
+ z.object({
105
+ state: z.literal("invalid"),
106
+ outcome: invalidBackupOutcomes,
107
+ backup: inspectedBackupSchema.optional(),
108
+ }),
109
+ ]);
110
+ const backupInspectionSchema = z.object({
111
+ status: z.literal("ok"),
112
+ branch: z.object({
113
+ projectId: z.string().min(1).max(200),
114
+ name: z.string().min(1).max(256),
115
+ ownerId: z.string().min(1).max(256),
116
+ state: z.enum(["active", "archived", "deleted"]).optional(),
117
+ deleted: z.boolean().optional(),
118
+ agentState: z.enum(["running", "waiting", "idle"]),
119
+ hasUnpushedChanges: z.boolean(),
120
+ lastCommitHash: z.string().min(1).max(8192),
121
+ lastActivityAt: z.number().finite().nonnegative(),
122
+ }),
123
+ backupStatuses: z.object({
124
+ code: backupCheckSchema,
125
+ }),
126
+ });
55
127
  const downloadSchema = z.object({
56
128
  signedUrl: z.string().url(),
57
129
  filePath: z.string().min(1).max(4096),
@@ -86,6 +158,124 @@ function backupIdentityFingerprint(backup) {
86
158
  function unverified(stage) {
87
159
  throw new BuilderBranchFilesError("unverified", "Builder has no complete, current, identity-bound branch backup to verify.", stage);
88
160
  }
161
+ async function authorizeBranchRead() {
162
+ let authorization;
163
+ try {
164
+ authorization = await resolveBuilderRequestAuthorization({
165
+ requiredScope: "builder:projects:read",
166
+ legacyCredentialKeys: [],
167
+ });
168
+ }
169
+ catch (error) {
170
+ const requiresReauthorization = isBuilderReauthorizationRequired(error);
171
+ throw new BuilderBranchFilesError(requiresReauthorization ? "not_connected" : "unavailable", requiresReauthorization
172
+ ? "Connect Builder with project read access."
173
+ : "Builder read access could not be checked.");
174
+ }
175
+ if (!authorization || authorization.source !== "oauth") {
176
+ throw new BuilderBranchFilesError("not_connected", "Connect Builder with project read access.");
177
+ }
178
+ return { authorization: authorization.authorization };
179
+ }
180
+ async function requestBranchStatus(input, authorization) {
181
+ const url = new URL("/projects/branch/status", getBuilderApiHost());
182
+ url.searchParams.set("projectId", input.projectId);
183
+ url.searchParams.set("branchName", input.branchName);
184
+ let response;
185
+ try {
186
+ response = await fetch(url, {
187
+ method: "GET",
188
+ redirect: "error",
189
+ headers: { Authorization: authorization },
190
+ signal: AbortSignal.timeout(API_TIMEOUT_MS),
191
+ });
192
+ }
193
+ catch {
194
+ throw new BuilderBranchFilesError("unavailable", "Builder branch readback could not be reached.");
195
+ }
196
+ if (!response.ok) {
197
+ await response.body?.cancel().catch(() => { });
198
+ throw new BuilderBranchFilesError(response.status === 401 || response.status === 403
199
+ ? "forbidden"
200
+ : "unavailable", "Builder branch readback was rejected.");
201
+ }
202
+ const bytes = await readBytes(response, 512 * 1024);
203
+ try {
204
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
205
+ }
206
+ catch {
207
+ throw new BuilderBranchFilesError("unavailable", "Builder returned an invalid readback response.");
208
+ }
209
+ }
210
+ function validateBranchRef(input) {
211
+ if (!/^[A-Za-z0-9_-]{1,200}$/.test(input.projectId) ||
212
+ input.branchName.length > 256 ||
213
+ input.branchName
214
+ .split("/")
215
+ .some((part) => !/^[A-Za-z0-9_.-]+$/.test(part) || part === "." || part === "..")) {
216
+ throw new BuilderBranchFilesError("invalid_request", "A canonical project and branch are required.");
217
+ }
218
+ }
219
+ export async function readBuilderBranchBackupStatus(input) {
220
+ validateBranchRef(input);
221
+ const auth = await authorizeBranchRead();
222
+ let rawStatus;
223
+ try {
224
+ rawStatus = await requestBranchStatus(input, auth.authorization);
225
+ }
226
+ catch (error) {
227
+ if (error instanceof BuilderBranchFilesError) {
228
+ throw new BuilderBranchFilesError(error.code, error.message, "status_request");
229
+ }
230
+ throw error;
231
+ }
232
+ const result = backupInspectionSchema.safeParse(rawStatus);
233
+ if (!result.success)
234
+ return unverified("status_shape");
235
+ const { branch, backupStatuses } = result.data;
236
+ const backup = backupStatuses.code.backup;
237
+ if (branch.projectId !== input.projectId ||
238
+ branch.name !== input.branchName) {
239
+ return unverified("status_binding");
240
+ }
241
+ if (branch.deleted !== undefined &&
242
+ branch.state !== undefined &&
243
+ branch.deleted !== (branch.state === "deleted")) {
244
+ return unverified("status_binding");
245
+ }
246
+ if (!backup) {
247
+ if (backupStatuses.code.state === "invalid" &&
248
+ backupStatuses.code.outcome === "no-backup") {
249
+ return unverified("status_no_backup");
250
+ }
251
+ return unverified("status_shape");
252
+ }
253
+ if (backup.projectId !== input.projectId ||
254
+ backup.branchName !== input.branchName ||
255
+ branch.ownerId !== backup.ownerId) {
256
+ return unverified("status_binding");
257
+ }
258
+ const branchCommit = primaryCommit(branch.lastCommitHash, "status_currentness");
259
+ const backupCommit = primaryCommit(backup.lastCommitHash, "status_currentness");
260
+ if (!branchCommit || !backupCommit)
261
+ return unverified("status_currentness");
262
+ const branchState = branch.state ?? (branch.deleted === true ? "deleted" : "unknown");
263
+ const backupPartial = typeof backup.partial === "boolean"
264
+ ? backup.partial
265
+ : backup.partial === undefined
266
+ ? "missing"
267
+ : "invalid";
268
+ return {
269
+ branchState,
270
+ backupPartial,
271
+ backupValid: backupStatuses.code.state === "valid",
272
+ backupComplete: backup.status === "completed",
273
+ backupCurrent: backup.at >= branch.lastActivityAt && branchCommit === backupCommit,
274
+ agentIdle: branch.agentState === "idle",
275
+ hasUnpushedChanges: branch.hasUnpushedChanges,
276
+ verificationPerformed: false,
277
+ };
278
+ }
89
279
  function primaryCommit(snapshot, failureStage) {
90
280
  if (SHA.test(snapshot))
91
281
  return snapshot;
@@ -153,23 +343,7 @@ export async function readBuilderBranchFiles(input) {
153
343
  appId.length > 64) {
154
344
  throw new BuilderBranchFilesError("invalid_request", "A canonical project, branch and app path are required.");
155
345
  }
156
- let authorization;
157
- try {
158
- authorization = await resolveBuilderRequestAuthorization({
159
- requiredScope: "builder:projects:read",
160
- legacyCredentialKeys: [],
161
- });
162
- }
163
- catch (error) {
164
- const requiresReauthorization = isBuilderReauthorizationRequired(error);
165
- throw new BuilderBranchFilesError(requiresReauthorization ? "not_connected" : "unavailable", requiresReauthorization
166
- ? "Connect Builder with project read access."
167
- : "Builder read access could not be checked.");
168
- }
169
- if (!authorization || authorization.source !== "oauth") {
170
- throw new BuilderBranchFilesError("not_connected", "Connect Builder with project read access.");
171
- }
172
- const auth = authorization;
346
+ const auth = await authorizeBranchRead();
173
347
  async function request(pathname, body) {
174
348
  const url = new URL(pathname, getBuilderApiHost());
175
349
  if (!body) {
@@ -209,7 +383,7 @@ export async function readBuilderBranchFiles(input) {
209
383
  async function readStatus(phase) {
210
384
  let rawStatus;
211
385
  try {
212
- rawStatus = await request("/projects/branch/status");
386
+ rawStatus = await requestBranchStatus(input, auth.authorization);
213
387
  }
214
388
  catch (error) {
215
389
  if (error instanceof BuilderBranchFilesError) {
@@ -76,7 +76,7 @@ import { captureError, registerErrorCaptureProvider } from "./capture-error.js";
76
76
  import { createChatGPTSubscriptionOAuthCallbackHandler, createChatGPTSubscriptionOAuthStartHandler, } from "./chatgpt-subscription-oauth.js";
77
77
  import { resolveCoreRoutesMcpOptions, } from "./core-routes/mcp-connect-options.js";
78
78
  import { getAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js";
79
- import { assertCredentialStoreReadable, CredentialStoreUnavailableError, getBuilderKeyConnections, prefetchSecrets, resolveSecret, resolveSecretDetailed, resolveBuilderGatewayCredentialsDetailed, resolveBuilderPrivateKey, } from "./credential-provider.js";
79
+ import { assertCredentialStoreReadable, CredentialStoreUnavailableError, builderCreditsRefusalStatus, getBuilderKeyConnections, prefetchSecrets, resolveSecret, resolveSecretDetailed, resolveBuilderGatewayCredentialsDetailed, resolveBuilderPrivateKey, } from "./credential-provider.js";
80
80
  import { decideCredentialWriteScope, } from "./credential-write-scope.js";
81
81
  import { readDatabaseIdentity, resolveRunningAppIdentity, } from "./database-identity.js";
82
82
  import { probeDbPressure } from "./db-pressure.js";
@@ -4243,7 +4243,11 @@ export function createCoreRoutesPlugin(options = {}) {
4243
4243
  status: engineStatus,
4244
4244
  detectFromUserSecrets: deps.detectFromUserSecrets,
4245
4245
  });
4246
- return { ...engineStatus, chatEligible };
4246
+ return {
4247
+ ...engineStatus,
4248
+ chatEligible,
4249
+ ...builderCreditsRefusalStatus(chatEligible),
4250
+ };
4247
4251
  }));
4248
4252
  const statusMs = Date.now() - statusStartedAt;
4249
4253
  if (statusMs >= 1_000) {
@@ -60,6 +60,11 @@ export { BUILDER_CREDENTIAL_KEYS };
60
60
  export declare function hasPlatformRuntimeMarker(): boolean;
61
61
  export declare function isProductionLikeRuntime(): boolean;
62
62
  export declare function isTrustedSelfHostedRuntime(): boolean;
63
+ export declare function builderCreditsRefusalStatus(chatEligible: boolean): {
64
+ builderCreditsRefused?: true;
65
+ };
66
+ /** @internal Test isolation for the once-per-process refusal warning. */
67
+ export declare function resetBuilderCreditsRefusedWarningForTests(): void;
63
68
  type BuilderCredentialSource = "user" | "org" | "workspace" | "env";
64
69
  /**
65
70
  * Host hook: an app hosted inside Builder, with Builder as its identity
@@ -8,7 +8,7 @@ import { BUILDER_CREDENTIAL_KEYS } from "./builder-credential-keys.js";
8
8
  import { BuilderOAuthScopeError, BUILDER_OAUTH_SCOPE, getBuilderOAuthSession, hasBuilderOAuthSession, } from "./builder-oauth.js";
9
9
  import { readsOrgCredentialFirst } from "./credential-read-order.js";
10
10
  import { decideCredentialWriteScope } from "./credential-write-scope.js";
11
- import { isHostedWorkspaceRuntime } from "./deployment-protection.js";
11
+ import { isFusionPreviewRuntime, isHostedWorkspaceRuntime, } from "./deployment-protection.js";
12
12
  import { isPersonalProviderKeyUseRestricted, isPersonalProviderPolicyKey, } from "./personal-provider-key-policy.js";
13
13
  export { isHostedWorkspaceRuntime, resolveVercelDeploymentProtectionHeaders, } from "./deployment-protection.js";
14
14
  import { getRequestContext, getRequestUserEmail, getRequestOrgId, getRequestBuilderStorageCredentialOrgId, } from "./request-context.js";
@@ -211,7 +211,7 @@ export function canUseDeployCredentialFallbackForRequest(key) {
211
211
  if (key && isNetlifyAiGatewayInjected(key))
212
212
  return false;
213
213
  if (key && BUILDER_CREDITS_DEPLOY_ENV_KEYS.has(key)) {
214
- return isTrustedSelfHostedRuntime();
214
+ return isBuilderCreditsPairAllowedInRuntime();
215
215
  }
216
216
  if (key && DEPLOY_LLM_PROVIDER_ENV_KEYS.has(key)) {
217
217
  const hostedWorkspace = isHostedWorkspaceRuntime();
@@ -258,6 +258,27 @@ export function isTrustedSelfHostedRuntime() {
258
258
  return true;
259
259
  return isLocalDatabase();
260
260
  }
261
+ // Fusion mints the preview pod's pair for the project's own space, so the
262
+ // hosted-workspace block on deploy keys must not reach it.
263
+ function isBuilderCreditsPairAllowedInRuntime() {
264
+ return isTrustedSelfHostedRuntime() || isFusionPreviewRuntime();
265
+ }
266
+ /** Deployed but refused by runtime policy, which is not the same as absent. */
267
+ function isBuilderCreditsPairRefusedByRuntime() {
268
+ return (Boolean(process.env[BUILDER_GATEWAY_TOKEN_ENV_VAR]?.trim()) &&
269
+ Boolean(process.env[BUILDER_GATEWAY_SPACE_ID_ENV_VAR]?.trim()) &&
270
+ !isBuilderCreditsPairAllowedInRuntime());
271
+ }
272
+ export function builderCreditsRefusalStatus(chatEligible) {
273
+ return !chatEligible && isBuilderCreditsPairRefusedByRuntime()
274
+ ? { builderCreditsRefused: true }
275
+ : {};
276
+ }
277
+ let warnedBuilderCreditsRefused = false;
278
+ /** @internal Test isolation for the once-per-process refusal warning. */
279
+ export function resetBuilderCreditsRefusedWarningForTests() {
280
+ warnedBuilderCreditsRefused = false;
281
+ }
261
282
  function canUseBuilderDeployCredentialFallbackForRequest() {
262
283
  const email = getRequestUserEmail();
263
284
  if (!email)
@@ -951,8 +972,14 @@ export async function resolveUsableBuilderGatewayDeployCredentials() {
951
972
  const spaceId = canUseDeployCredentialFallbackForRequest(BUILDER_GATEWAY_SPACE_ID_ENV_VAR)
952
973
  ? readDeployCredentialEnv(BUILDER_GATEWAY_SPACE_ID_ENV_VAR)
953
974
  : undefined;
954
- if (!token || !spaceId)
975
+ if (!token || !spaceId) {
976
+ if (!warnedBuilderCreditsRefused &&
977
+ isBuilderCreditsPairRefusedByRuntime()) {
978
+ warnedBuilderCreditsRefused = true;
979
+ console.warn("[agent-native] BUILDER_GATEWAY_TOKEN and BUILDER_GATEWAY_SPACE_ID are set, but this runtime does not allow Builder.io credits, so chat will ask for an AI connection.");
980
+ }
955
981
  return null;
982
+ }
956
983
  const failure = await getProviderCredentialAuthFailure({
957
984
  key: BUILDER_GATEWAY_TOKEN_ENV_VAR,
958
985
  value: token,
@@ -960,6 +987,9 @@ export async function resolveUsableBuilderGatewayDeployCredentials() {
960
987
  return failure ? null : { token, spaceId };
961
988
  }
962
989
  export function isBuilderGatewayDeployConfigured() {
990
+ // The preview carries the published site's token but is read by the owner.
991
+ if (isFusionPreviewRuntime())
992
+ return false;
963
993
  return (canUseDeployCredentialFallbackForRequest(BUILDER_GATEWAY_TOKEN_ENV_VAR) &&
964
994
  Boolean(readDeployCredentialEnv(BUILDER_GATEWAY_TOKEN_ENV_VAR)));
965
995
  }
@@ -4,4 +4,5 @@
4
4
  * not receive it just because this deployment has the credential configured.
5
5
  */
6
6
  export declare function resolveVercelDeploymentProtectionHeaders(targetUrl: string): Record<string, string>;
7
+ export declare function isFusionPreviewRuntime(): boolean;
7
8
  export declare function isHostedWorkspaceRuntime(): boolean;
@@ -49,12 +49,14 @@ export function resolveVercelDeploymentProtectionHeaders(targetUrl) {
49
49
  return {};
50
50
  return { "x-vercel-protection-bypass": secret.trim() };
51
51
  }
52
- export function isHostedWorkspaceRuntime() {
53
- const workspace = getAppConfig().workspace;
54
- const hasFusionPreview = Boolean(process.env.FUSION_ENVIRONMENT || // config-ok: Fusion injects preview markers outside app configuration.
52
+ export function isFusionPreviewRuntime() {
53
+ return Boolean(process.env.FUSION_ENVIRONMENT || // config-ok: Fusion injects preview markers outside app configuration.
55
54
  process.env.FUSION_ENV_ORIGIN || // config-ok: Fusion injects preview markers outside app configuration.
56
55
  process.env.VITE_FUSION_ENV_ORIGIN);
56
+ }
57
+ export function isHostedWorkspaceRuntime() {
58
+ const workspace = getAppConfig().workspace;
57
59
  return (workspace.isWorkspace === true ||
58
60
  Boolean(workspace.appsJson?.trim()) ||
59
- hasFusionPreview);
61
+ isFusionPreviewRuntime());
60
62
  }
@@ -115,7 +115,7 @@ export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_CONTENT_READ_SCOPE, BUILDER_CONTENT
115
115
  export { assertBuilderDesignSystemCodeIndexingAllowed, builderDesignSystemUrl, builderProjectBranchUrl, buildBuilderDesignSystemIndexFiles, collectBuilderDesignSystemGitHubFiles, createBuilderDesignSystemProxyFields, designSystemTierUpgradeUrl, fetchBuilderDesignSystemDecodeJobStatus, fetchBuilderDesignSystemDocs, fetchBuilderDesignSystemDocumentCount, fetchBuilderDesignSystemRecord, fetchBuilderDesignSystemTierLimit, getBuilderDesignSystemsBaseUrl, hydrateBuilderDesignSystemReference, indexBuilderDesignSystem, isBuilderDesignSystemReadyByCount, localBuilderDesignSystemId, mimeTypeForBuilderDesignSystemFilename, parseBuilderDesignSystemProxyReference, startBuilderDesignSystemIndex, startBuilderDesignSystemUpload, type BuildBuilderDesignSystemIndexFilesOptions, type BuilderDesignSystemCodeFileInput, type BuilderDesignSystemDecodeJobStatus, type BuilderDesignSystemDocsOptions, type BuilderDesignSystemDocument, type BuilderDesignSystemDocumentCountResult, type BuilderDesignSystemHydratedReference, type BuilderDesignSystemIndexFile, type BuilderDesignSystemIndexFromSourcesOptions, type BuilderDesignSystemIndexOptions, type BuilderDesignSystemIndexResult, type BuilderDesignSystemRecord, type BuilderDesignSystemStatus, type BuilderDesignSystemTierLimit, type BuilderDesignSystemGitHubFile, type BuilderDesignSystemGitHubFileCollection, type BuilderDesignSystemGitHubSource, type BuilderDesignSystemUploadAttachment, type BuilderDesignSystemUploadSlot, type BuilderDesignSystemProxyFields, type BuilderDesignSystemProxyFieldsOptions, type BuilderDesignSystemProxyReference, type BuilderDesignSystemSourceKind, } from "./builder-design-systems.js";
116
116
  export { cdnSafeOriginStatus, createBuilderProject, ensureBuilderProject, findBuilderProjectForRepo, getBuilderBranchProjectId, isBuilderBranchingEnabled, registerBuilderBranchProject, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveBuilderHandoffProjectId, resolveIsBuilderBranchingEnabled, normalizeBuilderAgentAttachments, runBuilderAgent, type BuilderAgentAttachment, type BuilderAgentUploadAttachment, type BuilderAgentUrlAttachment, type BuilderProjectResult, type RunBuilderAgentResult, } from "./builder-browser.js";
117
117
  export { BuilderBranchError, getBuilderBranchStatus, sendBuilderBranchMessage, type BuilderBranchAgentState, type BuilderBranchErrorCode, type BuilderBranchOperation, type BuilderBranchPrStatus, type BuilderBranchRef, type BuilderBranchState, type BuilderBranchStatus, type GetBuilderBranchStatusOptions, type SendBuilderBranchMessageOptions, type SendBuilderBranchMessageResult, } from "./builder-branch.js";
118
- export { BuilderBranchFilesError, readBuilderBranchFiles, } from "./builder-branch-files.js";
118
+ export { BuilderBranchFilesError, readBuilderBranchBackupStatus, readBuilderBranchFiles, } from "./builder-branch-files.js";
119
119
  export { ensureFusionContainer, sendFusionBranchMessage, pushFusionBranch, reserveFusionHostingSlug, deployFusionProject, getFusionDeploys, getFusionBranchEditorUrl, getFusionHostingUrl, type FusionBranchRef, type EnsureFusionContainerResult, type SendFusionMessageResult, } from "./fusion-app.js";
120
120
  export { sendEmail, isEmailConfigured, getEmailReadiness, getEmailProvider, type EmailAttachment, type EmailReadiness, type EmailProvider, type SendEmailArgs, } from "./email.js";
121
121
  export { defineTransactionalEmail, defineTransactionalEmails, replaceTransactionalEmails, listTransactionalEmails, getTransactionalEmail, renderTransactionalEmailPreview, renderTransactionalEmailPreviewAsync, type TransactionalEmailDefinition, type RegisteredTransactionalEmail, } from "../email-catalog/registry.js";
@@ -108,7 +108,7 @@ export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_CONTENT_READ_SCOPE, BUILDER_CONTENT
108
108
  export { assertBuilderDesignSystemCodeIndexingAllowed, builderDesignSystemUrl, builderProjectBranchUrl, buildBuilderDesignSystemIndexFiles, collectBuilderDesignSystemGitHubFiles, createBuilderDesignSystemProxyFields, designSystemTierUpgradeUrl, fetchBuilderDesignSystemDecodeJobStatus, fetchBuilderDesignSystemDocs, fetchBuilderDesignSystemDocumentCount, fetchBuilderDesignSystemRecord, fetchBuilderDesignSystemTierLimit, getBuilderDesignSystemsBaseUrl, hydrateBuilderDesignSystemReference, indexBuilderDesignSystem, isBuilderDesignSystemReadyByCount, localBuilderDesignSystemId, mimeTypeForBuilderDesignSystemFilename, parseBuilderDesignSystemProxyReference, startBuilderDesignSystemIndex, startBuilderDesignSystemUpload, } from "./builder-design-systems.js";
109
109
  export { cdnSafeOriginStatus, createBuilderProject, ensureBuilderProject, findBuilderProjectForRepo, getBuilderBranchProjectId, isBuilderBranchingEnabled, registerBuilderBranchProject, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveBuilderHandoffProjectId, resolveIsBuilderBranchingEnabled, normalizeBuilderAgentAttachments, runBuilderAgent, } from "./builder-browser.js";
110
110
  export { BuilderBranchError, getBuilderBranchStatus, sendBuilderBranchMessage, } from "./builder-branch.js";
111
- export { BuilderBranchFilesError, readBuilderBranchFiles, } from "./builder-branch-files.js";
111
+ export { BuilderBranchFilesError, readBuilderBranchBackupStatus, readBuilderBranchFiles, } from "./builder-branch-files.js";
112
112
  export { ensureFusionContainer, sendFusionBranchMessage, pushFusionBranch, reserveFusionHostingSlug, deployFusionProject, getFusionDeploys, getFusionBranchEditorUrl, getFusionHostingUrl, } from "./fusion-app.js";
113
113
  export { sendEmail, isEmailConfigured, getEmailReadiness, getEmailProvider, } from "./email.js";
114
114
  export { defineTransactionalEmail, defineTransactionalEmails, replaceTransactionalEmails, listTransactionalEmails, getTransactionalEmail, renderTransactionalEmailPreview, renderTransactionalEmailPreviewAsync, } from "../email-catalog/registry.js";
@@ -163,9 +163,21 @@ checkout, or the expected worktree is missing, create one
163
163
  to stop.
164
164
 
165
165
  Window: roots posted in the last 5 days (or since the timestamp or permalink
166
- the invocation gives), for new reports and open items alike. Older threads
167
- are left as they are, except reports listed under `Reports` in a sweep PR
168
- opened or merged in that window: those always get their close-out.
166
+ the invocation gives), for new reports and open items alike. An older thread
167
+ is in the window too when we have already replied in it and a person has
168
+ replied since the window began: a channel read only shows roots, so search for
169
+ recent thread replies (`in:<#channel> is:thread after:<date>`) to find them.
170
+ On 10/29 a teammate's vote and question sat unanswered under a 3-week-old
171
+ root. Other older threads are left as they are, except reports listed under
172
+ `Reports` in a sweep PR opened or merged in that window: those always get
173
+ their close-out.
174
+
175
+ Reading a source is a good job for a helper: give each source below its own
176
+ helper on the cheapest model (Luna in Codex, Haiku in Claude Code), ask for a
177
+ short list of candidates with links, and close it as soon as it reports. On
178
+ 10/29 the three source reads cost $0.44 together; reusing those same helpers
179
+ for fixes afterwards cost $6.36, because each new job paid for its whole
180
+ growing history.
169
181
 
170
182
  - **Read the channels.** Use `slack_read_channel` with
171
183
  `response_format: detailed` (paginate) back 5 days. Its output shows
@@ -250,8 +262,9 @@ apart from Steve's own 👀.
250
262
  ## 3. Fix as many as you can
251
263
 
252
264
  - Group related reports so one cause gets one fix. Give each independent
253
- group a subagent on a cheaper model with disjoint files. The main thread
254
- plans, reviews diffs, and integrates.
265
+ group a fresh subagent on your own model and effort, with disjoint files,
266
+ or fix it in the main thread. Never hand a finished helper a new job; start
267
+ a fresh one. The main thread plans, reviews diffs, and integrates.
255
268
  - **Reproduce before you edit.** Get the failure on screen first: a test
256
269
  that fails, the action returning the wrong thing, a stack trace, or the
257
270
  flow broken in a signed-in local app. Reading code is not an attempt:
@@ -274,9 +287,12 @@ apart from Steve's own 👀.
274
287
  green, stop editing and capture the real failure first (stack, logs, the
275
288
  reporter's actual data). If that doesn't settle it within about 30
276
289
  minutes, it becomes 🎫 with one line on what you learned.
277
- - **Second opinion before the PR.** Give a fresh subagent only the report
278
- text, the diff, and the proof, and ask: does the check exercise what the
279
- reporter saw, and is this the cause or a symptom? Fix what it finds.
290
+ - **Second opinion before the PR, every time.** Give a fresh subagent on
291
+ your own model only the report text, the diff, and the proof, and ask: does
292
+ the check exercise what the reporter saw, is this the cause or a symptom,
293
+ and what edge case breaks it? Fix what it finds before you open the PR. The
294
+ 10/29 run skipped this, and PR review found the edge cases one round at a
295
+ time instead.
280
296
  - **Keep fixes small.** Each report's fix goes in its own commit so a bad
281
297
  one can be reverted alone. A fix that grows past about 300 lines, or into
282
298
  a new subsystem (timeouts, cancellation, retries) the reporter didn't ask
@@ -293,6 +309,19 @@ PR per item. End the PR body with a
293
309
  Slack thread, or the Sentry, Analytics, or GitHub issue. That list is the
294
310
  only set of threads a later `/ship` may close out as fixed.
295
311
 
312
+ Wait for CI with one blocking command, such as
313
+ `gh pr checks <number> --watch --required --interval 60`, and don't post a
314
+ status line while it runs: every turn re-sends your whole context, and on
315
+ 10/28 polling CI was half the main thread's cost.
316
+
317
+ Fix each review round's findings in one batch. If a second round still finds
318
+ a problem in the same fix, take that fix out of the PR, remove its whole entry
319
+ from the PR's `Reports` section: the link and its `fix-proof:` and `visual:`
320
+ lines (close-out marks every listed report fixed, and a stray evidence line
321
+ counts toward the report above it). Then 🎫 its
322
+ report with what review found, and merge the rest. Close the PR only when nothing verified
323
+ is left in it.
324
+
296
325
  Merge it the way this repo merges PRs. In a Factory workspace, that means
297
326
  the workflow's merge step after its merge gate, never a direct merge.
298
327
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.222.1",
3
+ "version": "0.222.2-nightly-20261029182928",
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": {
@@ -742,7 +742,7 @@
742
742
  "yaml": "^2.9.0",
743
743
  "yjs": "^13.6.33",
744
744
  "zod": "^4.3.6",
745
- "@agent-native/agentkit": "^0.222.1",
745
+ "@agent-native/agentkit": "^0.222.2-nightly-20261029182928",
746
746
  "@agent-native/recap-cli": "^0.5.92"
747
747
  },
748
748
  "devDependencies": {
@@ -814,7 +814,7 @@
814
814
  "xlsx": "^0.18.5"
815
815
  },
816
816
  "peerDependencies": {
817
- "@agent-native/core-corpus": ">=0.196.0",
817
+ "@agent-native/core-corpus": "0.222.2-nightly-20261029182928",
818
818
  "@ai-sdk/anthropic": ">=3",
819
819
  "@ai-sdk/cohere": ">=3",
820
820
  "@ai-sdk/google": ">=3",
@@ -163,9 +163,21 @@ checkout, or the expected worktree is missing, create one
163
163
  to stop.
164
164
 
165
165
  Window: roots posted in the last 5 days (or since the timestamp or permalink
166
- the invocation gives), for new reports and open items alike. Older threads
167
- are left as they are, except reports listed under `Reports` in a sweep PR
168
- opened or merged in that window: those always get their close-out.
166
+ the invocation gives), for new reports and open items alike. An older thread
167
+ is in the window too when we have already replied in it and a person has
168
+ replied since the window began: a channel read only shows roots, so search for
169
+ recent thread replies (`in:<#channel> is:thread after:<date>`) to find them.
170
+ On 10/29 a teammate's vote and question sat unanswered under a 3-week-old
171
+ root. Other older threads are left as they are, except reports listed under
172
+ `Reports` in a sweep PR opened or merged in that window: those always get
173
+ their close-out.
174
+
175
+ Reading a source is a good job for a helper: give each source below its own
176
+ helper on the cheapest model (Luna in Codex, Haiku in Claude Code), ask for a
177
+ short list of candidates with links, and close it as soon as it reports. On
178
+ 10/29 the three source reads cost $0.44 together; reusing those same helpers
179
+ for fixes afterwards cost $6.36, because each new job paid for its whole
180
+ growing history.
169
181
 
170
182
  - **Read the channels.** Use `slack_read_channel` with
171
183
  `response_format: detailed` (paginate) back 5 days. Its output shows
@@ -250,8 +262,9 @@ apart from Steve's own 👀.
250
262
  ## 3. Fix as many as you can
251
263
 
252
264
  - Group related reports so one cause gets one fix. Give each independent
253
- group a subagent on a cheaper model with disjoint files. The main thread
254
- plans, reviews diffs, and integrates.
265
+ group a fresh subagent on your own model and effort, with disjoint files,
266
+ or fix it in the main thread. Never hand a finished helper a new job; start
267
+ a fresh one. The main thread plans, reviews diffs, and integrates.
255
268
  - **Reproduce before you edit.** Get the failure on screen first: a test
256
269
  that fails, the action returning the wrong thing, a stack trace, or the
257
270
  flow broken in a signed-in local app. Reading code is not an attempt:
@@ -274,9 +287,12 @@ apart from Steve's own 👀.
274
287
  green, stop editing and capture the real failure first (stack, logs, the
275
288
  reporter's actual data). If that doesn't settle it within about 30
276
289
  minutes, it becomes 🎫 with one line on what you learned.
277
- - **Second opinion before the PR.** Give a fresh subagent only the report
278
- text, the diff, and the proof, and ask: does the check exercise what the
279
- reporter saw, and is this the cause or a symptom? Fix what it finds.
290
+ - **Second opinion before the PR, every time.** Give a fresh subagent on
291
+ your own model only the report text, the diff, and the proof, and ask: does
292
+ the check exercise what the reporter saw, is this the cause or a symptom,
293
+ and what edge case breaks it? Fix what it finds before you open the PR. The
294
+ 10/29 run skipped this, and PR review found the edge cases one round at a
295
+ time instead.
280
296
  - **Keep fixes small.** Each report's fix goes in its own commit so a bad
281
297
  one can be reverted alone. A fix that grows past about 300 lines, or into
282
298
  a new subsystem (timeouts, cancellation, retries) the reporter didn't ask
@@ -293,6 +309,19 @@ PR per item. End the PR body with a
293
309
  Slack thread, or the Sentry, Analytics, or GitHub issue. That list is the
294
310
  only set of threads a later `/ship` may close out as fixed.
295
311
 
312
+ Wait for CI with one blocking command, such as
313
+ `gh pr checks <number> --watch --required --interval 60`, and don't post a
314
+ status line while it runs: every turn re-sends your whole context, and on
315
+ 10/28 polling CI was half the main thread's cost.
316
+
317
+ Fix each review round's findings in one batch. If a second round still finds
318
+ a problem in the same fix, take that fix out of the PR, remove its whole entry
319
+ from the PR's `Reports` section: the link and its `fix-proof:` and `visual:`
320
+ lines (close-out marks every listed report fixed, and a stray evidence line
321
+ counts toward the report above it). Then 🎫 its
322
+ report with what review found, and merge the rest. Close the PR only when nothing verified
323
+ is left in it.
324
+
296
325
  Merge it the way this repo merges PRs. In a Factory workspace, that means
297
326
  the workflow's merge step after its merge gate, never a direct merge.
298
327