@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.
- package/dist/agent/engine-status.d.ts +2 -0
- package/dist/client/agent-run-status.d.ts +2 -0
- package/dist/client/agent-run-status.js +3 -0
- package/dist/client/chat/agentkit-agent-native.js +1 -0
- package/dist/client/use-agent-chat-running-threads.d.ts +4 -0
- package/dist/localization/default-messages.d.ts +5 -0
- package/dist/localization/default-messages.js +5 -0
- package/dist/server/builder-branch-files.d.ts +11 -1
- package/dist/server/builder-branch-files.js +192 -18
- package/dist/server/core-routes-plugin.js +6 -2
- package/dist/server/credential-provider.d.ts +5 -0
- package/dist/server/credential-provider.js +33 -3
- package/dist/server/deployment-protection.d.ts +1 -0
- package/dist/server/deployment-protection.js +6 -4
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +37 -8
- package/package.json +3 -3
- package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +37 -8
|
@@ -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<{
|
|
@@ -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 {
|
|
@@ -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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
53
|
-
|
|
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
|
-
|
|
61
|
+
isFusionPreviewRuntime());
|
|
60
62
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/server/index.js
CHANGED
|
@@ -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.
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
254
|
-
|
|
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
|
|
278
|
-
text, the diff, and the proof, and ask: does
|
|
279
|
-
reporter saw,
|
|
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.
|
|
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.
|
|
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": "
|
|
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.
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
254
|
-
|
|
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
|
|
278
|
-
text, the diff, and the proof, and ask: does
|
|
279
|
-
reporter saw,
|
|
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
|
|