@autonoma-ai/planner 0.1.24 → 0.1.25
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/README.md +122 -23
- package/dist/index.js +2457 -768
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -42,6 +42,11 @@ var init_env = __esm({
|
|
|
42
42
|
AUTONOMA_API_URL: z.string().optional(),
|
|
43
43
|
AUTONOMA_API_TOKEN: z.string().optional(),
|
|
44
44
|
AUTONOMA_GENERATION_ID: z.string().optional(),
|
|
45
|
+
// The Autonoma application this run belongs to. Unlike AUTONOMA_GENERATION_ID
|
|
46
|
+
// (which identifies one setup's uploads) this identifies the app itself, and is
|
|
47
|
+
// what the onboarding calls - reading state, minting a pairing code for an agent
|
|
48
|
+
// this CLI spawns - are keyed by.
|
|
49
|
+
AUTONOMA_APPLICATION_ID: z.string().optional(),
|
|
45
50
|
AUTONOMA_POSTHOG_KEY: z.string().optional(),
|
|
46
51
|
AUTONOMA_POSTHOG_HOST: z.string().optional(),
|
|
47
52
|
AUTONOMA_DISTINCT_ID: z.string().optional(),
|
|
@@ -498,13 +503,13 @@ function wrapStyledLines(lines, width) {
|
|
|
498
503
|
continue;
|
|
499
504
|
}
|
|
500
505
|
const leading = leadingSpaces(line);
|
|
501
|
-
const
|
|
506
|
+
const indent2 = leading > 0 && leading <= Math.floor(width / 2) ? leading : 0;
|
|
502
507
|
const chars = [];
|
|
503
508
|
for (const span of line) for (const ch of span.text) chars.push({ ch, span });
|
|
504
509
|
let start = 0;
|
|
505
510
|
let first = true;
|
|
506
511
|
while (start < chars.length) {
|
|
507
|
-
const avail = first ? width : width -
|
|
512
|
+
const avail = first ? width : width - indent2;
|
|
508
513
|
let end = Math.min(start + avail, chars.length);
|
|
509
514
|
if (end < chars.length) {
|
|
510
515
|
let back = end;
|
|
@@ -518,7 +523,7 @@ function wrapStyledLines(lines, width) {
|
|
|
518
523
|
if (last != null && chars[i - 1]?.span === span && i > start) last.text += ch;
|
|
519
524
|
else folded.push({ text: ch, color: span.color, bold: span.bold, dim: span.dim });
|
|
520
525
|
}
|
|
521
|
-
if (!first &&
|
|
526
|
+
if (!first && indent2 > 0) folded.unshift({ text: " ".repeat(indent2) });
|
|
522
527
|
out.push(folded.length ? folded : [{ text: "" }]);
|
|
523
528
|
start = end;
|
|
524
529
|
while (start < chars.length && chars[start].ch === " ") start++;
|
|
@@ -2017,7 +2022,7 @@ var init_evidence_tokens = __esm({
|
|
|
2017
2022
|
|
|
2018
2023
|
// ../../packages/types/src/schemas/investigation-report.ts
|
|
2019
2024
|
import { z as z6 } from "zod";
|
|
2020
|
-
var investigationEvidenceSchema, investigationPointSchema, investigationRunStepSchema, investigationFindingSchema,
|
|
2025
|
+
var investigationEvidenceSchema, investigationPointSchema, investigationRunStepSchema, investigationFindingSchema, investigationDeployedPerTestSchema, investigationDeployedComparisonSchema;
|
|
2021
2026
|
var init_investigation_report = __esm({
|
|
2022
2027
|
"../../packages/types/src/schemas/investigation-report.ts"() {
|
|
2023
2028
|
"use strict";
|
|
@@ -2096,7 +2101,9 @@ var init_investigation_report = __esm({
|
|
|
2096
2101
|
/** Browser-openable HTTPS URL of the dead-time-stripped mp4 recording, signed on read. When present, the
|
|
2097
2102
|
* finding page shows an Optimized/Original toggle; absent for runs recorded before the optimizer landed. */
|
|
2098
2103
|
optimizedVideoUrl: z6.string().optional(),
|
|
2099
|
-
|
|
2104
|
+
/** Browser-openable HTTPS URL of the frame the classifier chose (`keyStepIndex`), signed on read. Absent when
|
|
2105
|
+
* it chose none, in which case the finding deliberately shows no still. */
|
|
2106
|
+
keyScreenshotUrl: z6.string().optional(),
|
|
2100
2107
|
/** Present instead of the verdict fields when the model failed to classify this test. */
|
|
2101
2108
|
error: z6.string().optional(),
|
|
2102
2109
|
/**
|
|
@@ -2113,17 +2120,6 @@ var init_investigation_report = __esm({
|
|
|
2113
2120
|
issueId: z6.string().optional(),
|
|
2114
2121
|
issueTitle: z6.string().optional()
|
|
2115
2122
|
});
|
|
2116
|
-
investigationValidationSchema = z6.object({
|
|
2117
|
-
passed: z6.boolean(),
|
|
2118
|
-
iterations: z6.number(),
|
|
2119
|
-
failureReason: z6.string().optional()
|
|
2120
|
-
});
|
|
2121
|
-
investigationSuggestedTestSchema = z6.object({
|
|
2122
|
-
name: z6.string(),
|
|
2123
|
-
instruction: z6.string(),
|
|
2124
|
-
reasoning: z6.string(),
|
|
2125
|
-
validation: investigationValidationSchema.optional()
|
|
2126
|
-
});
|
|
2127
2123
|
investigationDeployedPerTestSchema = z6.object({
|
|
2128
2124
|
testSlug: z6.string(),
|
|
2129
2125
|
affectedReason: z6.string().optional(),
|
|
@@ -2138,20 +2134,6 @@ var init_investigation_report = __esm({
|
|
|
2138
2134
|
failureReason: z6.string().optional(),
|
|
2139
2135
|
perTest: z6.array(investigationDeployedPerTestSchema)
|
|
2140
2136
|
});
|
|
2141
|
-
investigationReportDataSchema = z6.object({
|
|
2142
|
-
client: z6.string(),
|
|
2143
|
-
appSlug: z6.string(),
|
|
2144
|
-
prNumber: z6.number(),
|
|
2145
|
-
prTitle: z6.string().optional(),
|
|
2146
|
-
prBody: z6.string().optional(),
|
|
2147
|
-
/** owner/repo for the app's GitHub repository - used to build code permalinks. Absent on legacy reports. */
|
|
2148
|
-
repoFullName: z6.string().optional(),
|
|
2149
|
-
/** The PR head commit the run tested - the permalink ref. Absent on legacy reports. */
|
|
2150
|
-
commitSha: z6.string().optional(),
|
|
2151
|
-
findings: z6.array(investigationFindingSchema),
|
|
2152
|
-
suggested: z6.array(investigationSuggestedTestSchema),
|
|
2153
|
-
deployed: investigationDeployedComparisonSchema.optional()
|
|
2154
|
-
});
|
|
2155
2137
|
}
|
|
2156
2138
|
});
|
|
2157
2139
|
|
|
@@ -2187,7 +2169,7 @@ function analysisVerdictPlane(category) {
|
|
|
2187
2169
|
const tier = analysisFindingTier(category);
|
|
2188
2170
|
return tier === "bug" || tier === "passed" ? "app_health" : "coverage";
|
|
2189
2171
|
}
|
|
2190
|
-
var analysisVerdictSchema, ANALYSIS_VERDICT, VERDICT_TIER, coverageVerdicts, analysisTestOriginSchema, coverageCategoryCountSchema, coverageSummarySchema, analysisClassificationReportSchema, analysisClassificationSummarySchema, analysisFindingViewSchema, analysisReportDataSchema, analysisIssueKindSchema, analysisIssueSeveritySchema, analysisIssueStatusSchema, resolvedPrimaryScreenshotSchema, analysisIssueSummarySchema, analysisIssueFindingInstanceSchema, analysisIssueDetailSchema, analysisPrCoveredTestSchema, analysisPrIssueSchema, analysisPrNewerRunSchema, analysisForPrSchema, analysisSnapshotIssueChangesSchema;
|
|
2172
|
+
var analysisVerdictSchema, ANALYSIS_VERDICT, VERDICT_TIER, coverageVerdicts, analysisTestOriginSchema, coverageCategoryCountSchema, coverageSummarySchema, analysisClassificationReportSchema, analysisClassificationSummarySchema, analysisFindingViewSchema, analysisReportDataSchema, analysisIssueKindSchema, analysisIssueSeveritySchema, analysisIssueStatusSchema, resolvedPrimaryScreenshotSchema, analysisIssueSummarySchema, analysisIssueFindingInstanceSchema, analysisIssueDetailSchema, analysisPrCoveredTestSchema, analysisPrIssueSchema, analysisPrNewerRunSchema, analysisForPrSchema, mainProblemSourceSchema, mainOpenProblemSchema, mainOpenProblemsSchema, analysisSnapshotIssueChangesSchema;
|
|
2191
2173
|
var init_analysis = __esm({
|
|
2192
2174
|
"../../packages/types/src/schemas/analysis.ts"() {
|
|
2193
2175
|
"use strict";
|
|
@@ -2445,6 +2427,21 @@ var init_analysis = __esm({
|
|
|
2445
2427
|
newerRun: analysisPrNewerRunSchema.optional()
|
|
2446
2428
|
})
|
|
2447
2429
|
]);
|
|
2430
|
+
mainProblemSourceSchema = z8.enum(["legacy_bug", "analysis_issue"]);
|
|
2431
|
+
mainOpenProblemSchema = z8.object({
|
|
2432
|
+
id: z8.string(),
|
|
2433
|
+
title: z8.string(),
|
|
2434
|
+
kind: analysisIssueKindSchema,
|
|
2435
|
+
severity: analysisIssueSeveritySchema,
|
|
2436
|
+
/** The problem's own account of what went wrong (a bug's description, an issue's actual behavior). */
|
|
2437
|
+
detail: z8.string().optional(),
|
|
2438
|
+
occurrences: z8.number().int().nonnegative(),
|
|
2439
|
+
lastSeenAt: z8.date()
|
|
2440
|
+
});
|
|
2441
|
+
mainOpenProblemsSchema = z8.object({
|
|
2442
|
+
source: mainProblemSourceSchema,
|
|
2443
|
+
problems: z8.array(mainOpenProblemSchema)
|
|
2444
|
+
});
|
|
2448
2445
|
analysisSnapshotIssueChangesSchema = z8.object({
|
|
2449
2446
|
opened: z8.array(analysisIssueSummarySchema),
|
|
2450
2447
|
carriedForward: z8.array(analysisIssueSummarySchema),
|
|
@@ -2542,7 +2539,7 @@ function findRecipeCreateGraphProblems(create, declaredTokens = /* @__PURE__ */
|
|
|
2542
2539
|
}
|
|
2543
2540
|
return problems;
|
|
2544
2541
|
}
|
|
2545
|
-
var SdkFieldSchema, SdkModelSchema, SdkEdgeSchema, SdkRelationSchema, SdkSchemaSchema, SdkDiscoverResponseSchema, ScenarioRecipeValidationSchema, ScenarioVariableScalarSchema, ScenarioVariableDefinitionSchema, ScenarioRecipeVariablesSchema, ScenarioStructureModelSchema, ScenarioStructureJsonSchema, ScenarioRecipeSchema, ScenarioCreateGraphSchema, TEST_RUN_ID_TOKEN, TEST_RUN_SHORT_ID_TOKEN, BUILT_IN_RECIPE_TOKENS, BUILT_IN_RECIPE_TOKEN_LIST, RECIPE_TEMPLATE_PATTERN, ScenarioRecipesFileSchema, AuthCookieSchema, AuthHeadersSchema, AuthCredentialsSchema, AuthPayloadSchema, RefsSchema, UpResponseSchema, DownResponseSchema, ConfigureWebhookInputSchema, RemoveWebhookInputSchema, DiscoverInputSchema, ListScenariosInputSchema, ListInstancesInputSchema, ListWebhookCallsInputSchema, DryRunInputSchema, GetRecipeInputSchema, UpdateRecipeInputSchema, PreviewkitEnvFactoryOptionsInputSchema, PreviewkitEnvFactoryUpInputSchema, PreviewkitEnvFactoryDownInputSchema, TestUserOptionsInputSchema, TestUserProvisionInputSchema, TestUserTeardownInputSchema;
|
|
2542
|
+
var SdkFieldSchema, SdkModelSchema, SdkEdgeSchema, SdkRelationSchema, SdkSchemaSchema, SdkDiscoverResponseSchema, SCENARIO_VALIDATION_METHODS, ScenarioRecipeValidationSchema, ScenarioVariableScalarSchema, ScenarioVariableDefinitionSchema, ScenarioRecipeVariablesSchema, ScenarioStructureModelSchema, ScenarioStructureJsonSchema, ScenarioRecipeSchema, ScenarioCreateGraphSchema, TEST_RUN_ID_TOKEN, TEST_RUN_SHORT_ID_TOKEN, BUILT_IN_RECIPE_TOKENS, BUILT_IN_RECIPE_TOKEN_LIST, RECIPE_TEMPLATE_PATTERN, ScenarioRecipesFileSchema, AuthCookieSchema, AuthHeadersSchema, AuthCredentialsSchema, AuthPayloadSchema, RefsSchema, UpResponseSchema, DownResponseSchema, ConfigureWebhookInputSchema, RemoveWebhookInputSchema, DiscoverInputSchema, ListScenariosInputSchema, ListInstancesInputSchema, ListWebhookCallsInputSchema, DryRunInputSchema, GetRecipeInputSchema, UpdateRecipeInputSchema, PreviewkitEnvFactoryOptionsInputSchema, PreviewkitEnvFactoryUpInputSchema, PreviewkitEnvFactoryDownInputSchema, TestUserOptionsInputSchema, TestUserProvisionInputSchema, TestUserTeardownInputSchema;
|
|
2546
2543
|
var init_scenarios = __esm({
|
|
2547
2544
|
"../../packages/types/src/schemas/scenarios.ts"() {
|
|
2548
2545
|
"use strict";
|
|
@@ -2582,9 +2579,10 @@ var init_scenarios = __esm({
|
|
|
2582
2579
|
sdk: z9.record(z9.string(), z9.unknown()).optional(),
|
|
2583
2580
|
schema: SdkSchemaSchema
|
|
2584
2581
|
}).passthrough();
|
|
2582
|
+
SCENARIO_VALIDATION_METHODS = ["checkScenario", "checkAllScenarios", "endpoint-up-down"];
|
|
2585
2583
|
ScenarioRecipeValidationSchema = z9.object({
|
|
2586
2584
|
status: z9.literal("validated"),
|
|
2587
|
-
method: z9.enum(
|
|
2585
|
+
method: z9.enum(SCENARIO_VALIDATION_METHODS),
|
|
2588
2586
|
phase: z9.literal("ok"),
|
|
2589
2587
|
up_ms: z9.number().int().nonnegative().optional(),
|
|
2590
2588
|
down_ms: z9.number().int().nonnegative().optional()
|
|
@@ -3162,33 +3160,11 @@ var init_secrets = __esm({
|
|
|
3162
3160
|
}
|
|
3163
3161
|
});
|
|
3164
3162
|
|
|
3165
|
-
// ../../packages/types/src/schemas/
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
var init_org_secrets = __esm({
|
|
3169
|
-
"../../packages/types/src/schemas/org-secrets.ts"() {
|
|
3163
|
+
// ../../packages/types/src/schemas/previewkit-node-pm.ts
|
|
3164
|
+
var init_previewkit_node_pm = __esm({
|
|
3165
|
+
"../../packages/types/src/schemas/previewkit-node-pm.ts"() {
|
|
3170
3166
|
"use strict";
|
|
3171
3167
|
init_esm_shims();
|
|
3172
|
-
init_secrets();
|
|
3173
|
-
ORG_SECRET_NAME_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
3174
|
-
OrgSecretNameSchema = z17.string().min(2).max(63).regex(
|
|
3175
|
-
ORG_SECRET_NAME_REGEX,
|
|
3176
|
-
"Org secret name must be lowercase alphanumeric with hyphens (matches the preview config auth_secret field)"
|
|
3177
|
-
);
|
|
3178
|
-
ListOrgSecretsInputSchema = z17.object({
|
|
3179
|
-
name: OrgSecretNameSchema
|
|
3180
|
-
});
|
|
3181
|
-
UpsertOrgSecretInputSchema = z17.object({
|
|
3182
|
-
name: OrgSecretNameSchema,
|
|
3183
|
-
// Same item shape as per-app secrets: { key, value } where the AWS SM
|
|
3184
|
-
// SecretString stores them as a flat JSON map. Providers pick the keys
|
|
3185
|
-
// they care about (NeonProvider expects `token`).
|
|
3186
|
-
items: z17.array(SecretItemSchema).min(1).max(50)
|
|
3187
|
-
});
|
|
3188
|
-
DeleteOrgSecretKeyInputSchema = z17.object({
|
|
3189
|
-
name: OrgSecretNameSchema,
|
|
3190
|
-
key: z17.string().min(1)
|
|
3191
|
-
});
|
|
3192
3168
|
}
|
|
3193
3169
|
});
|
|
3194
3170
|
|
|
@@ -3348,8 +3324,268 @@ var init_previewkit_runtimes = __esm({
|
|
|
3348
3324
|
}
|
|
3349
3325
|
});
|
|
3350
3326
|
|
|
3327
|
+
// ../../packages/types/src/schemas/previewkit-presets.ts
|
|
3328
|
+
function previewkitPresetSpec(preset) {
|
|
3329
|
+
return PREVIEWKIT_PRESET_CATALOG[preset];
|
|
3330
|
+
}
|
|
3331
|
+
var PREVIEWKIT_PRESET_IDS, SERVER_TREE, PREVIEWKIT_PRESET_CATALOG, PREVIEWKIT_PRESETS;
|
|
3332
|
+
var init_previewkit_presets = __esm({
|
|
3333
|
+
"../../packages/types/src/schemas/previewkit-presets.ts"() {
|
|
3334
|
+
"use strict";
|
|
3335
|
+
init_esm_shims();
|
|
3336
|
+
init_previewkit_runtimes();
|
|
3337
|
+
PREVIEWKIT_PRESET_IDS = [
|
|
3338
|
+
"nextjs",
|
|
3339
|
+
"nuxt",
|
|
3340
|
+
"sveltekit",
|
|
3341
|
+
"remix",
|
|
3342
|
+
"hono",
|
|
3343
|
+
"express",
|
|
3344
|
+
"astro",
|
|
3345
|
+
"vite",
|
|
3346
|
+
"django",
|
|
3347
|
+
"fastapi",
|
|
3348
|
+
"python",
|
|
3349
|
+
"rails",
|
|
3350
|
+
"ruby",
|
|
3351
|
+
"node",
|
|
3352
|
+
"static"
|
|
3353
|
+
];
|
|
3354
|
+
SERVER_TREE = { mode: "server", copy: "tree" };
|
|
3355
|
+
PREVIEWKIT_PRESET_CATALOG = {
|
|
3356
|
+
nextjs: {
|
|
3357
|
+
id: "nextjs",
|
|
3358
|
+
label: "Next.js",
|
|
3359
|
+
toolchain: "node",
|
|
3360
|
+
detect: {
|
|
3361
|
+
dependencies: ["next"],
|
|
3362
|
+
configFiles: ["next.config.js", "next.config.mjs", "next.config.ts"]
|
|
3363
|
+
},
|
|
3364
|
+
buildCommand: "run build",
|
|
3365
|
+
runCommand: "run start",
|
|
3366
|
+
defaultPort: 3e3,
|
|
3367
|
+
// `next start` needs `.next` + node_modules + config, so carry the whole
|
|
3368
|
+
// built tree. (Standalone-output slimming is a later optimization.)
|
|
3369
|
+
output: SERVER_TREE
|
|
3370
|
+
},
|
|
3371
|
+
nuxt: {
|
|
3372
|
+
id: "nuxt",
|
|
3373
|
+
label: "Nuxt",
|
|
3374
|
+
toolchain: "node",
|
|
3375
|
+
detect: {
|
|
3376
|
+
dependencies: ["nuxt", "nuxt3"],
|
|
3377
|
+
configFiles: ["nuxt.config.js", "nuxt.config.ts", "nuxt.config.mjs"]
|
|
3378
|
+
},
|
|
3379
|
+
buildCommand: "run build",
|
|
3380
|
+
// Nuxt 3's default Nitro preset is a self-contained node server under
|
|
3381
|
+
// `.output`; run it directly rather than through a package script.
|
|
3382
|
+
runCommand: "node .output/server/index.mjs",
|
|
3383
|
+
defaultPort: 3e3,
|
|
3384
|
+
output: SERVER_TREE
|
|
3385
|
+
},
|
|
3386
|
+
sveltekit: {
|
|
3387
|
+
id: "sveltekit",
|
|
3388
|
+
label: "SvelteKit",
|
|
3389
|
+
toolchain: "node",
|
|
3390
|
+
detect: {
|
|
3391
|
+
dependencies: ["@sveltejs/kit"],
|
|
3392
|
+
configFiles: ["svelte.config.js"]
|
|
3393
|
+
},
|
|
3394
|
+
buildCommand: "run build",
|
|
3395
|
+
// Assumes adapter-node (the previewkit-supported adapter): it emits a
|
|
3396
|
+
// `build/` server started with `node build`. Apps on another adapter must
|
|
3397
|
+
// override run_command / output_directory.
|
|
3398
|
+
runCommand: "node build",
|
|
3399
|
+
defaultPort: 3e3,
|
|
3400
|
+
output: SERVER_TREE
|
|
3401
|
+
},
|
|
3402
|
+
remix: {
|
|
3403
|
+
id: "remix",
|
|
3404
|
+
label: "Remix",
|
|
3405
|
+
toolchain: "node",
|
|
3406
|
+
detect: {
|
|
3407
|
+
dependencies: ["@remix-run/dev", "@remix-run/node", "@remix-run/serve"],
|
|
3408
|
+
configFiles: ["remix.config.js"]
|
|
3409
|
+
},
|
|
3410
|
+
buildCommand: "run build",
|
|
3411
|
+
runCommand: "run start",
|
|
3412
|
+
defaultPort: 3e3,
|
|
3413
|
+
output: SERVER_TREE
|
|
3414
|
+
},
|
|
3415
|
+
hono: {
|
|
3416
|
+
id: "hono",
|
|
3417
|
+
label: "Hono",
|
|
3418
|
+
toolchain: "node",
|
|
3419
|
+
detect: {
|
|
3420
|
+
dependencies: ["hono"],
|
|
3421
|
+
configFiles: []
|
|
3422
|
+
},
|
|
3423
|
+
buildCommand: "run build",
|
|
3424
|
+
runCommand: "run start",
|
|
3425
|
+
defaultPort: 3e3,
|
|
3426
|
+
// Assumes the Node adapter (@hono/node-server). Edge / Workers / Bun
|
|
3427
|
+
// deploys build differently and would override the run command.
|
|
3428
|
+
output: SERVER_TREE
|
|
3429
|
+
},
|
|
3430
|
+
express: {
|
|
3431
|
+
id: "express",
|
|
3432
|
+
label: "Express",
|
|
3433
|
+
toolchain: "node",
|
|
3434
|
+
detect: {
|
|
3435
|
+
dependencies: ["express"],
|
|
3436
|
+
configFiles: []
|
|
3437
|
+
},
|
|
3438
|
+
// A plain-JS Express API: the derived dependency install is the only prep, so
|
|
3439
|
+
// there is no build step. Assumes a `start` script; an app without one overrides
|
|
3440
|
+
// run_command with its entry file (e.g. `node server.js`).
|
|
3441
|
+
buildCommand: "",
|
|
3442
|
+
runCommand: "run start",
|
|
3443
|
+
defaultPort: 3e3,
|
|
3444
|
+
output: SERVER_TREE
|
|
3445
|
+
},
|
|
3446
|
+
astro: {
|
|
3447
|
+
id: "astro",
|
|
3448
|
+
label: "Astro",
|
|
3449
|
+
toolchain: "node",
|
|
3450
|
+
detect: {
|
|
3451
|
+
dependencies: ["astro"],
|
|
3452
|
+
configFiles: ["astro.config.mjs", "astro.config.ts", "astro.config.js"]
|
|
3453
|
+
},
|
|
3454
|
+
buildCommand: "run build",
|
|
3455
|
+
runCommand: "",
|
|
3456
|
+
defaultPort: 80,
|
|
3457
|
+
// Default Astro output is a static site in `dist`. An SSR Astro app (with a
|
|
3458
|
+
// server adapter) must override output to server mode.
|
|
3459
|
+
output: { mode: "static", dir: "dist" }
|
|
3460
|
+
},
|
|
3461
|
+
vite: {
|
|
3462
|
+
id: "vite",
|
|
3463
|
+
label: "Vite",
|
|
3464
|
+
toolchain: "node",
|
|
3465
|
+
detect: {
|
|
3466
|
+
dependencies: ["vite"],
|
|
3467
|
+
configFiles: ["vite.config.js", "vite.config.ts", "vite.config.mjs"]
|
|
3468
|
+
},
|
|
3469
|
+
buildCommand: "run build",
|
|
3470
|
+
runCommand: "",
|
|
3471
|
+
defaultPort: 80,
|
|
3472
|
+
output: { mode: "static", dir: "dist" }
|
|
3473
|
+
},
|
|
3474
|
+
django: {
|
|
3475
|
+
id: "django",
|
|
3476
|
+
label: "Django",
|
|
3477
|
+
toolchain: "python",
|
|
3478
|
+
detect: {
|
|
3479
|
+
dependencies: ["django", "Django"],
|
|
3480
|
+
configFiles: ["manage.py"]
|
|
3481
|
+
},
|
|
3482
|
+
// No build step in dev mode (runserver serves static with DEBUG=True). For
|
|
3483
|
+
// production, override build_command with `python manage.py collectstatic --noinput`.
|
|
3484
|
+
buildCommand: "",
|
|
3485
|
+
runCommand: "python manage.py runserver 0.0.0.0:$PORT",
|
|
3486
|
+
defaultPort: 8e3,
|
|
3487
|
+
output: SERVER_TREE
|
|
3488
|
+
},
|
|
3489
|
+
fastapi: {
|
|
3490
|
+
id: "fastapi",
|
|
3491
|
+
label: "FastAPI",
|
|
3492
|
+
toolchain: "python",
|
|
3493
|
+
detect: {
|
|
3494
|
+
dependencies: ["fastapi"],
|
|
3495
|
+
configFiles: []
|
|
3496
|
+
},
|
|
3497
|
+
buildCommand: "",
|
|
3498
|
+
// Assumes the app object is `main:app`; override for another module path
|
|
3499
|
+
// (e.g. `uvicorn app.main:app --host 0.0.0.0 --port $PORT`).
|
|
3500
|
+
runCommand: "uvicorn main:app --host 0.0.0.0 --port $PORT",
|
|
3501
|
+
defaultPort: 8e3,
|
|
3502
|
+
output: SERVER_TREE
|
|
3503
|
+
},
|
|
3504
|
+
python: {
|
|
3505
|
+
id: "python",
|
|
3506
|
+
label: "Python",
|
|
3507
|
+
toolchain: "python",
|
|
3508
|
+
detect: {
|
|
3509
|
+
dependencies: [],
|
|
3510
|
+
configFiles: ["pyproject.toml", "requirements.txt", "Pipfile"]
|
|
3511
|
+
},
|
|
3512
|
+
// Dependency install (uv) is derived from the toolchain; most Python web
|
|
3513
|
+
// apps have no separate build step.
|
|
3514
|
+
buildCommand: "",
|
|
3515
|
+
// Generic default - override run_command for your framework (e.g.
|
|
3516
|
+
// `python manage.py runserver 0.0.0.0:$PORT`, `uvicorn main:app --port $PORT`).
|
|
3517
|
+
runCommand: "python main.py",
|
|
3518
|
+
defaultPort: 8e3,
|
|
3519
|
+
output: SERVER_TREE
|
|
3520
|
+
},
|
|
3521
|
+
rails: {
|
|
3522
|
+
id: "rails",
|
|
3523
|
+
label: "Ruby on Rails",
|
|
3524
|
+
toolchain: "ruby",
|
|
3525
|
+
detect: {
|
|
3526
|
+
dependencies: ["rails"],
|
|
3527
|
+
configFiles: ["bin/rails", "config/application.rb"]
|
|
3528
|
+
},
|
|
3529
|
+
// No build step in development mode (assets compile on the fly). For
|
|
3530
|
+
// production, override with `SECRET_KEY_BASE_DUMMY=1 bin/rails assets:precompile`.
|
|
3531
|
+
buildCommand: "",
|
|
3532
|
+
runCommand: "bin/rails server -b 0.0.0.0 -p $PORT",
|
|
3533
|
+
defaultPort: 3e3,
|
|
3534
|
+
output: SERVER_TREE
|
|
3535
|
+
},
|
|
3536
|
+
ruby: {
|
|
3537
|
+
id: "ruby",
|
|
3538
|
+
label: "Ruby",
|
|
3539
|
+
toolchain: "ruby",
|
|
3540
|
+
detect: {
|
|
3541
|
+
dependencies: [],
|
|
3542
|
+
configFiles: ["Gemfile", "config.ru", "Rakefile"]
|
|
3543
|
+
},
|
|
3544
|
+
// `bundle install` is derived from the toolchain; no separate build step.
|
|
3545
|
+
buildCommand: "",
|
|
3546
|
+
// Rack default - override for Rails (`bin/rails server -b 0.0.0.0 -p $PORT`).
|
|
3547
|
+
runCommand: "bundle exec rackup --host 0.0.0.0 --port $PORT",
|
|
3548
|
+
defaultPort: 9292,
|
|
3549
|
+
output: SERVER_TREE
|
|
3550
|
+
},
|
|
3551
|
+
node: {
|
|
3552
|
+
id: "node",
|
|
3553
|
+
label: "Node.js",
|
|
3554
|
+
toolchain: "node",
|
|
3555
|
+
// Fallback for any package.json that matched no specific framework above.
|
|
3556
|
+
detect: {
|
|
3557
|
+
dependencies: [],
|
|
3558
|
+
configFiles: ["package.json"]
|
|
3559
|
+
},
|
|
3560
|
+
buildCommand: "run build",
|
|
3561
|
+
runCommand: "run start",
|
|
3562
|
+
defaultPort: 3e3,
|
|
3563
|
+
output: SERVER_TREE
|
|
3564
|
+
},
|
|
3565
|
+
static: {
|
|
3566
|
+
id: "static",
|
|
3567
|
+
label: "Static",
|
|
3568
|
+
toolchain: "node",
|
|
3569
|
+
// Last-resort fallback: a plain static site with no build step. The output
|
|
3570
|
+
// directory is the most common thing to override.
|
|
3571
|
+
detect: {
|
|
3572
|
+
dependencies: [],
|
|
3573
|
+
configFiles: ["index.html"]
|
|
3574
|
+
},
|
|
3575
|
+
buildCommand: "",
|
|
3576
|
+
runCommand: "",
|
|
3577
|
+
defaultPort: 80,
|
|
3578
|
+
output: { mode: "static", dir: "." }
|
|
3579
|
+
}
|
|
3580
|
+
};
|
|
3581
|
+
PREVIEWKIT_PRESETS = PREVIEWKIT_PRESET_IDS.map(
|
|
3582
|
+
(id) => PREVIEWKIT_PRESET_CATALOG[id]
|
|
3583
|
+
);
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3586
|
+
|
|
3351
3587
|
// ../../packages/types/src/schemas/previewkit-config.ts
|
|
3352
|
-
import { z as
|
|
3588
|
+
import { z as z17 } from "zod";
|
|
3353
3589
|
function buildResourcesSchema(role, allowCustomResources) {
|
|
3354
3590
|
return resourcesInput.transform((input) => {
|
|
3355
3591
|
if (!allowCustomResources || input == null) {
|
|
@@ -3363,65 +3599,96 @@ function buildResourcesSchema(role, allowCustomResources) {
|
|
|
3363
3599
|
};
|
|
3364
3600
|
});
|
|
3365
3601
|
}
|
|
3602
|
+
function singleLineCommand(label) {
|
|
3603
|
+
return z17.string().min(1).regex(/^[^\r\n]+$/, `${label} must be a single line (no line breaks)`);
|
|
3604
|
+
}
|
|
3605
|
+
function heredocSafeScript(label) {
|
|
3606
|
+
return z17.string().min(1).refine(
|
|
3607
|
+
(value) => !value.split("\n").includes(PREVIEWKIT_BUILD_SCRIPT_HEREDOC),
|
|
3608
|
+
`${label} cannot contain a line equal to "${PREVIEWKIT_BUILD_SCRIPT_HEREDOC}" (reserved heredoc delimiter)`
|
|
3609
|
+
);
|
|
3610
|
+
}
|
|
3366
3611
|
function nodeFrameworkBuildSchema(framework) {
|
|
3367
|
-
return
|
|
3368
|
-
framework:
|
|
3369
|
-
package_manager:
|
|
3370
|
-
node_version:
|
|
3371
|
-
install_command:
|
|
3372
|
-
build_command:
|
|
3373
|
-
run_command:
|
|
3612
|
+
return z17.object({
|
|
3613
|
+
framework: z17.literal(framework),
|
|
3614
|
+
package_manager: z17.enum(["npm", "pnpm", "yarn"]).default("pnpm"),
|
|
3615
|
+
node_version: z17.string().regex(nodeVersionRegex, "must look like 22, 22.5, or 22.5.0").default("22"),
|
|
3616
|
+
install_command: z17.string().min(1).optional(),
|
|
3617
|
+
build_command: z17.string().min(1).optional(),
|
|
3618
|
+
run_command: z17.string().min(1).optional(),
|
|
3374
3619
|
build_context: buildContextSchema
|
|
3375
3620
|
});
|
|
3376
3621
|
}
|
|
3377
3622
|
function buildPreviewConfigSchema(build, allowCustomResources) {
|
|
3378
|
-
const appSchema =
|
|
3379
|
-
name:
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3623
|
+
const appSchema = z17.object({
|
|
3624
|
+
name: z17.string().regex(k8sNameRegex, "Must be a valid Kubernetes name"),
|
|
3625
|
+
repository: z17.string().regex(repoFullNameRegex, "Must be an owner/repo full name").describe(
|
|
3626
|
+
"The owner/repo full name of the GitHub repository this app builds from. Mandatory even in single-repo setups. Any value other than the application's own repository makes this app a multirepo dependency: that repo is cloned at the branch the branch_convention (or its repositories[] fallback_branch) resolves to."
|
|
3627
|
+
),
|
|
3628
|
+
path: z17.string().default("."),
|
|
3629
|
+
build_context: z17.string().optional(),
|
|
3630
|
+
dockerfile: z17.string().optional(),
|
|
3383
3631
|
build: build.optional(),
|
|
3632
|
+
// The preset-based deploy model, mutually exclusive with `build` (enforced
|
|
3633
|
+
// by the superRefine below). Lowered to a `runtime` Build by the generator.
|
|
3634
|
+
blueprint: blueprintSchema.optional(),
|
|
3384
3635
|
// The AWS-secret keys to also inject at build time (Docker build args).
|
|
3385
3636
|
// Runtime secret values live in AWS Secrets Manager, never in this document.
|
|
3386
|
-
build_secrets:
|
|
3387
|
-
port:
|
|
3637
|
+
build_secrets: z17.array(z17.string()).default([]),
|
|
3638
|
+
port: z17.number().int().positive(),
|
|
3388
3639
|
// Non-secret variables wired to the topology, resolved at deploy time.
|
|
3389
3640
|
// All user-typed values are secrets (AWS), so they never appear here.
|
|
3390
|
-
connections:
|
|
3391
|
-
command:
|
|
3392
|
-
health_check:
|
|
3393
|
-
primary:
|
|
3641
|
+
connections: z17.array(connectionSchema).default([]),
|
|
3642
|
+
command: z17.string().optional(),
|
|
3643
|
+
health_check: z17.string().optional(),
|
|
3644
|
+
primary: z17.boolean().optional(),
|
|
3394
3645
|
// This app serves the Environment Factory handler (`/api/autonoma`), so
|
|
3395
3646
|
// scenario up/down calls go to its preview URL. Independent of `primary`:
|
|
3396
3647
|
// a full-stack app (Next.js, Rails) is both the browsed frontend and the
|
|
3397
3648
|
// SDK host, while a split topology mounts the handler on its API service.
|
|
3398
|
-
sdk_implemented:
|
|
3649
|
+
sdk_implemented: z17.boolean().optional(),
|
|
3399
3650
|
resources: buildResourcesSchema("app", allowCustomResources),
|
|
3400
|
-
depends_on:
|
|
3651
|
+
depends_on: z17.array(z17.string()).optional()
|
|
3652
|
+
}).superRefine((app, ctx) => {
|
|
3653
|
+
if (app.build != null && app.blueprint != null) {
|
|
3654
|
+
ctx.addIssue({
|
|
3655
|
+
code: z17.ZodIssueCode.custom,
|
|
3656
|
+
message: "an app cannot set both `build` and `blueprint` - `blueprint` is the preset-based deploy model, `build` is the manual one",
|
|
3657
|
+
path: ["blueprint"]
|
|
3658
|
+
});
|
|
3659
|
+
}
|
|
3401
3660
|
});
|
|
3402
|
-
const serviceSchema =
|
|
3403
|
-
name:
|
|
3404
|
-
recipe:
|
|
3405
|
-
version:
|
|
3661
|
+
const serviceSchema = z17.object({
|
|
3662
|
+
name: z17.string().regex(k8sNameRegex, "Must be a valid Kubernetes name"),
|
|
3663
|
+
recipe: z17.string(),
|
|
3664
|
+
version: z17.string().optional(),
|
|
3406
3665
|
// Recipe-functional knobs (e.g. postgres user/database, or a docker-image
|
|
3407
3666
|
// service's image/ports/env) live in `options`, validated per-recipe.
|
|
3408
|
-
options:
|
|
3667
|
+
options: z17.record(z17.string(), z17.unknown()).default({}),
|
|
3409
3668
|
// Guided setup for database-recipe services (schema, seed, migrations),
|
|
3410
3669
|
// run with the repo checked out. Empty for non-database services.
|
|
3411
|
-
setup_tasks:
|
|
3670
|
+
setup_tasks: z17.array(databaseSetupTaskSchema).default([]),
|
|
3412
3671
|
resources: buildResourcesSchema("service", allowCustomResources),
|
|
3413
|
-
s3:
|
|
3414
|
-
sqs:
|
|
3415
|
-
sns:
|
|
3672
|
+
s3: z17.boolean().optional(),
|
|
3673
|
+
sqs: z17.boolean().optional(),
|
|
3674
|
+
sns: z17.boolean().optional()
|
|
3416
3675
|
});
|
|
3417
|
-
return
|
|
3418
|
-
version:
|
|
3419
|
-
domain:
|
|
3420
|
-
registry:
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3676
|
+
return z17.object({
|
|
3677
|
+
version: z17.literal(2),
|
|
3678
|
+
domain: z17.string().optional(),
|
|
3679
|
+
registry: z17.string().optional(),
|
|
3680
|
+
// Per-repository overrides + deploy provenance; the repo set itself
|
|
3681
|
+
// is derived from `apps[].repository`. See repositorySettingsSchema.
|
|
3682
|
+
repositories: z17.array(repositorySettingsSchema).default([]).describe(
|
|
3683
|
+
"Optional per-repository settings. The topology's repositories are derived from apps[].repository - an entry here only overrides defaults (fallback_branch: which branch to clone when the PR's branch does not exist in that repo; default main)."
|
|
3684
|
+
),
|
|
3685
|
+
// Topology-wide: how a dependency repo's branch is derived from the
|
|
3686
|
+
// PR's branch. Defaults to same_branch_name behavior when absent.
|
|
3687
|
+
branch_convention: branchConventionSchema.optional().describe(
|
|
3688
|
+
"How a dependency repo's branch is derived from the PR branch: same_branch_name (default), regex (pattern + replacement rewrite), or manual (always the fallback_branch)."
|
|
3689
|
+
),
|
|
3690
|
+
apps: z17.array(appSchema).min(1, "At least one app is required"),
|
|
3691
|
+
services: z17.array(serviceSchema).default([]),
|
|
3425
3692
|
hooks: hooksSchema
|
|
3426
3693
|
}).superRefine((cfg, ctx) => {
|
|
3427
3694
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -3429,8 +3696,8 @@ function buildPreviewConfigSchema(build, allowCustomResources) {
|
|
|
3429
3696
|
const existing = seen.get(name);
|
|
3430
3697
|
if (existing != null) {
|
|
3431
3698
|
ctx.addIssue({
|
|
3432
|
-
code:
|
|
3433
|
-
message: `Name "${name}" is used by both a ${existing} and an ${kind} - names must be unique across apps
|
|
3699
|
+
code: z17.ZodIssueCode.custom,
|
|
3700
|
+
message: `Name "${name}" is used by both a ${existing} and an ${kind} - names must be unique across apps and services`
|
|
3434
3701
|
});
|
|
3435
3702
|
return;
|
|
3436
3703
|
}
|
|
@@ -3438,7 +3705,18 @@ function buildPreviewConfigSchema(build, allowCustomResources) {
|
|
|
3438
3705
|
};
|
|
3439
3706
|
for (const app of cfg.apps) check(app.name, "app");
|
|
3440
3707
|
for (const service of cfg.services) check(service.name, "service");
|
|
3441
|
-
|
|
3708
|
+
const seenRepos = /* @__PURE__ */ new Set();
|
|
3709
|
+
cfg.repositories.forEach((settings, index) => {
|
|
3710
|
+
const key = settings.repo.toLowerCase();
|
|
3711
|
+
if (seenRepos.has(key)) {
|
|
3712
|
+
ctx.addIssue({
|
|
3713
|
+
code: z17.ZodIssueCode.custom,
|
|
3714
|
+
path: ["repositories", index, "repo"],
|
|
3715
|
+
message: `Repository "${settings.repo}" has more than one settings entry`
|
|
3716
|
+
});
|
|
3717
|
+
}
|
|
3718
|
+
seenRepos.add(key);
|
|
3719
|
+
});
|
|
3442
3720
|
});
|
|
3443
3721
|
}
|
|
3444
3722
|
function standardResources(role) {
|
|
@@ -3449,12 +3727,14 @@ function standardResources(role) {
|
|
|
3449
3727
|
memoryLimit: standard.memoryLimit
|
|
3450
3728
|
};
|
|
3451
3729
|
}
|
|
3452
|
-
var STANDARD_RESOURCES, k8sNameRegex,
|
|
3730
|
+
var STANDARD_RESOURCES, k8sNameRegex, repoFullNameRegex, resourcesInput, branchConventionSchema, repositorySettingsSchema, hookStepSchema, hooksSchema, databaseSetupLocationSchema, databaseSetupTaskSchema, nodeVersionRegex, buildContextSchema, PREVIEWKIT_BUILD_SCRIPT_HEREDOC, imageTagSchema, DEPRECATED_BUILD_FRAMEWORKS, UNSUPPORTED_BUILD_METHOD_MESSAGE, deprecatedBuildArms, authoredBuildArms, authoredBuildSchema, storedBuildSchema, presetBlueprintSchema, dockerfileBlueprintSchema, blueprintSchema, connectionSchema, previewConfigSchema, trustedPreviewConfigSchema, authoringPreviewConfigSchema;
|
|
3453
3731
|
var init_previewkit_config = __esm({
|
|
3454
3732
|
"../../packages/types/src/schemas/previewkit-config.ts"() {
|
|
3455
3733
|
"use strict";
|
|
3456
3734
|
init_esm_shims();
|
|
3457
3735
|
init_previewkit_builtins();
|
|
3736
|
+
init_previewkit_node_pm();
|
|
3737
|
+
init_previewkit_presets();
|
|
3458
3738
|
init_previewkit_runtimes();
|
|
3459
3739
|
init_secrets();
|
|
3460
3740
|
STANDARD_RESOURCES = {
|
|
@@ -3462,23 +3742,18 @@ var init_previewkit_config = __esm({
|
|
|
3462
3742
|
service: { cpu: "100m", memoryRequest: "256Mi", memoryLimit: "1Gi" }
|
|
3463
3743
|
};
|
|
3464
3744
|
k8sNameRegex = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3745
|
+
repoFullNameRegex = /^[^/\s]+\/[^/\s]+$/;
|
|
3746
|
+
resourcesInput = z17.object({
|
|
3747
|
+
cpu: z17.string().optional(),
|
|
3748
|
+
memory: z17.string().optional(),
|
|
3749
|
+
memoryRequest: z17.string().optional(),
|
|
3750
|
+
memoryLimit: z17.string().optional()
|
|
3470
3751
|
}).optional();
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
});
|
|
3477
|
-
branchConventionSchema = z18.discriminatedUnion("type", [
|
|
3478
|
-
z18.object({ type: z18.literal("same_branch_name") }),
|
|
3479
|
-
z18.object({
|
|
3480
|
-
type: z18.literal("regex"),
|
|
3481
|
-
pattern: z18.string().refine((pattern) => {
|
|
3752
|
+
branchConventionSchema = z17.discriminatedUnion("type", [
|
|
3753
|
+
z17.object({ type: z17.literal("same_branch_name") }),
|
|
3754
|
+
z17.object({
|
|
3755
|
+
type: z17.literal("regex"),
|
|
3756
|
+
pattern: z17.string().refine((pattern) => {
|
|
3482
3757
|
try {
|
|
3483
3758
|
new RegExp(pattern);
|
|
3484
3759
|
return true;
|
|
@@ -3486,121 +3761,145 @@ var init_previewkit_config = __esm({
|
|
|
3486
3761
|
return false;
|
|
3487
3762
|
}
|
|
3488
3763
|
}, "Invalid regular expression pattern"),
|
|
3489
|
-
replacement:
|
|
3764
|
+
replacement: z17.string()
|
|
3490
3765
|
}),
|
|
3491
|
-
|
|
3766
|
+
z17.object({ type: z17.literal("manual") })
|
|
3492
3767
|
]);
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
fallback_branch: z18.string().default("main"),
|
|
3768
|
+
repositorySettingsSchema = z17.object({
|
|
3769
|
+
repo: z17.string().regex(repoFullNameRegex, "Must be an owner/repo full name"),
|
|
3770
|
+
fallback_branch: z17.string().default("main"),
|
|
3497
3771
|
/**
|
|
3498
|
-
* The concrete commit SHA the
|
|
3499
|
-
* user-authored config: previewkit resolves
|
|
3500
|
-
* commit at deploy time and records it here by enriching the stored
|
|
3772
|
+
* The concrete commit SHA the repository was deployed at. Absent in
|
|
3773
|
+
* user-authored config: previewkit resolves each dependency repo's branch to
|
|
3774
|
+
* a commit at deploy time and records it here by enriching the stored
|
|
3501
3775
|
* `resolvedConfig` (deploy provenance, not authored input). Multi-repo
|
|
3502
3776
|
* grounding reads this back to inspect the exact code that was live.
|
|
3503
3777
|
*/
|
|
3504
|
-
sha:
|
|
3505
|
-
});
|
|
3506
|
-
multirepoConfigSchema = z18.object({
|
|
3507
|
-
branch_convention: branchConventionSchema.optional(),
|
|
3508
|
-
repos: z18.array(repoDependencySchema).default([])
|
|
3778
|
+
sha: z17.string().optional()
|
|
3509
3779
|
});
|
|
3510
|
-
|
|
3511
|
-
|
|
3780
|
+
hookStepSchema = z17.object({
|
|
3781
|
+
app: z17.string(),
|
|
3782
|
+
command: z17.string()
|
|
3512
3783
|
});
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
});
|
|
3517
|
-
hooksSchema = z18.object({
|
|
3518
|
-
pre_deploy: z18.array(hookStepSchema).default([]),
|
|
3519
|
-
post_deploy: z18.array(hookStepSchema).default([])
|
|
3784
|
+
hooksSchema = z17.object({
|
|
3785
|
+
pre_deploy: z17.array(hookStepSchema).default([]),
|
|
3786
|
+
post_deploy: z17.array(hookStepSchema).default([])
|
|
3520
3787
|
}).default({ pre_deploy: [], post_deploy: [] });
|
|
3521
|
-
databaseSetupLocationSchema =
|
|
3522
|
-
|
|
3523
|
-
type:
|
|
3524
|
-
app:
|
|
3525
|
-
position:
|
|
3788
|
+
databaseSetupLocationSchema = z17.discriminatedUnion("type", [
|
|
3789
|
+
z17.object({
|
|
3790
|
+
type: z17.literal("in_build"),
|
|
3791
|
+
app: z17.string().min(1, "an app is required for an in-build task"),
|
|
3792
|
+
position: z17.enum(["before", "after"])
|
|
3526
3793
|
}),
|
|
3527
|
-
|
|
3528
|
-
type:
|
|
3529
|
-
repo:
|
|
3794
|
+
z17.object({
|
|
3795
|
+
type: z17.literal("separate_job"),
|
|
3796
|
+
repo: z17.string().optional()
|
|
3530
3797
|
})
|
|
3531
3798
|
]);
|
|
3532
|
-
databaseSetupTaskSchema =
|
|
3533
|
-
command:
|
|
3534
|
-
frequency:
|
|
3799
|
+
databaseSetupTaskSchema = z17.object({
|
|
3800
|
+
command: z17.string(),
|
|
3801
|
+
frequency: z17.enum(["on_create", "every_commit"]),
|
|
3535
3802
|
location: databaseSetupLocationSchema
|
|
3536
3803
|
});
|
|
3537
3804
|
nodeVersionRegex = /^\d+(\.\d+)?(\.\d+)?$/;
|
|
3538
|
-
buildContextSchema =
|
|
3805
|
+
buildContextSchema = z17.enum(["app", "root"]).default("app");
|
|
3539
3806
|
PREVIEWKIT_BUILD_SCRIPT_HEREDOC = "AUTONOMA_BUILD_EOF";
|
|
3807
|
+
imageTagSchema = z17.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "must be a valid image tag");
|
|
3540
3808
|
DEPRECATED_BUILD_FRAMEWORKS = ["node", "next", "vite", "bun"];
|
|
3541
3809
|
UNSUPPORTED_BUILD_METHOD_MESSAGE = `Unsupported build method. Use "runtime" (pick a runtime, then write a bash build_script and a single-line entrypoint) or "dockerfile" (build a Dockerfile committed in the repo). The ${DEPRECATED_BUILD_FRAMEWORKS.join(" / ")} framework presets are retired and can no longer be saved - express them as "runtime" with the install and build commands in build_script and the start command as entrypoint.`;
|
|
3542
3810
|
deprecatedBuildArms = [
|
|
3543
3811
|
nodeFrameworkBuildSchema("node"),
|
|
3544
3812
|
nodeFrameworkBuildSchema("next"),
|
|
3545
3813
|
nodeFrameworkBuildSchema("vite"),
|
|
3546
|
-
|
|
3547
|
-
framework:
|
|
3548
|
-
install_command:
|
|
3549
|
-
build_command:
|
|
3550
|
-
run_command:
|
|
3814
|
+
z17.object({
|
|
3815
|
+
framework: z17.literal("bun"),
|
|
3816
|
+
install_command: z17.string().min(1).optional(),
|
|
3817
|
+
build_command: z17.string().min(1).optional(),
|
|
3818
|
+
run_command: z17.string().min(1).optional(),
|
|
3551
3819
|
build_context: buildContextSchema
|
|
3552
3820
|
})
|
|
3553
3821
|
];
|
|
3554
3822
|
authoredBuildArms = [
|
|
3555
|
-
|
|
3556
|
-
framework:
|
|
3557
|
-
dockerfile:
|
|
3558
|
-
target:
|
|
3823
|
+
z17.object({
|
|
3824
|
+
framework: z17.literal("dockerfile"),
|
|
3825
|
+
dockerfile: z17.string().min(1, "dockerfile path is required"),
|
|
3826
|
+
target: z17.string().min(1).optional(),
|
|
3559
3827
|
build_context: buildContextSchema
|
|
3560
3828
|
}),
|
|
3561
|
-
|
|
3562
|
-
framework:
|
|
3563
|
-
runtime:
|
|
3829
|
+
z17.object({
|
|
3830
|
+
framework: z17.literal("runtime"),
|
|
3831
|
+
runtime: z17.enum(PREVIEWKIT_RUNTIME_IDS),
|
|
3564
3832
|
// Image tag version, e.g. "22" for node. Optional - defaults to the
|
|
3565
3833
|
// catalog's default per runtime. The user picks it so a repo pinned to an
|
|
3566
3834
|
// older toolchain is not forced onto our default (which would defeat the
|
|
3567
3835
|
// escape hatch). Constrained to a safe tag charset so it can never break
|
|
3568
3836
|
// out of the generated `FROM` line.
|
|
3569
|
-
version:
|
|
3837
|
+
version: imageTagSchema.optional(),
|
|
3570
3838
|
// Both are raw bash. `build_script` bakes into the image (cached); the
|
|
3571
3839
|
// entrypoint is the container start command. `build_script` is optional
|
|
3572
3840
|
// (some apps need no build step); `entrypoint` is required - the
|
|
3573
3841
|
// container has to start somehow. `app.command` still overrides it at
|
|
3574
3842
|
// deploy time.
|
|
3575
|
-
build_script:
|
|
3576
|
-
|
|
3577
|
-
`build script cannot contain a line equal to "${PREVIEWKIT_BUILD_SCRIPT_HEREDOC}" (reserved heredoc delimiter)`
|
|
3578
|
-
).optional(),
|
|
3579
|
-
// The entrypoint is baked verbatim into a single-line `CMD`, so a newline
|
|
3580
|
-
// would break out of the CMD and inject a bogus Dockerfile instruction
|
|
3581
|
-
// (e.g. "npm start\nnode server.js"). Constrain it to one line; use a
|
|
3582
|
-
// start script referenced from here if you need multiple commands.
|
|
3583
|
-
entrypoint: z18.string().min(1, "entrypoint is required").regex(/^[^\r\n]+$/, "entrypoint must be a single line (no line breaks)"),
|
|
3843
|
+
build_script: heredocSafeScript("build script").optional(),
|
|
3844
|
+
entrypoint: singleLineCommand("entrypoint"),
|
|
3584
3845
|
build_context: buildContextSchema
|
|
3585
3846
|
})
|
|
3586
3847
|
];
|
|
3587
|
-
authoredBuildSchema =
|
|
3848
|
+
authoredBuildSchema = z17.discriminatedUnion("framework", authoredBuildArms, {
|
|
3588
3849
|
error: () => UNSUPPORTED_BUILD_METHOD_MESSAGE
|
|
3589
3850
|
});
|
|
3590
|
-
storedBuildSchema =
|
|
3591
|
-
|
|
3851
|
+
storedBuildSchema = z17.discriminatedUnion("framework", [...authoredBuildArms, ...deprecatedBuildArms]);
|
|
3852
|
+
presetBlueprintSchema = z17.object({
|
|
3853
|
+
preset: z17.enum(PREVIEWKIT_PRESET_IDS),
|
|
3854
|
+
// Overridable per app; each defaults from the preset (version from the runtime catalog).
|
|
3855
|
+
version: imageTagSchema.optional(),
|
|
3856
|
+
// These overrides flow into the generated Dockerfile via blueprintToBuild, so they
|
|
3857
|
+
// carry the same guards as the `runtime` build arm: install/build concatenate into
|
|
3858
|
+
// the build_script heredoc; run_command and output_directory reach the single-line CMD.
|
|
3859
|
+
install_command: heredocSafeScript("install_command").optional(),
|
|
3860
|
+
build_command: heredocSafeScript("build_command").optional(),
|
|
3861
|
+
run_command: singleLineCommand("run_command").optional(),
|
|
3862
|
+
// For static presets: the built output directory to serve (defaults to the preset's).
|
|
3863
|
+
output_directory: singleLineCommand("output_directory").optional(),
|
|
3864
|
+
// Monorepo: `root` builds from the repo root so sibling workspace packages resolve.
|
|
3865
|
+
// Works for every preset: commands run in the app directory; node installs at the
|
|
3866
|
+
// repo root (workspace linking) and builds through turbo's `--filter` when the repo
|
|
3867
|
+
// has turbo. Absent = app context; no default is stamped in.
|
|
3868
|
+
build_context: buildContextSchema.removeDefault().optional()
|
|
3869
|
+
}).strict().superRefine((blueprint, ctx) => {
|
|
3870
|
+
const toolchain = previewkitPresetSpec(blueprint.preset).toolchain;
|
|
3871
|
+
if (toolchain === "node" && blueprint.version != null && !nodeVersionRegex.test(blueprint.version)) {
|
|
3872
|
+
ctx.addIssue({
|
|
3873
|
+
code: z17.ZodIssueCode.custom,
|
|
3874
|
+
message: "version must look like 22, 22.5, or 22.5.0 for a node preset",
|
|
3875
|
+
path: ["version"]
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
});
|
|
3879
|
+
dockerfileBlueprintSchema = z17.object({
|
|
3880
|
+
// Path to a committed Dockerfile, built as-is - ALWAYS relative to the app dir,
|
|
3881
|
+
// whatever the build context (the pipeline resolves it against the context).
|
|
3882
|
+
// `target` selects a stage in a multi-stage build.
|
|
3883
|
+
dockerfile: z17.string().min(1, "dockerfile path is required"),
|
|
3884
|
+
target: z17.string().min(1).optional(),
|
|
3885
|
+
// Monorepo: `root` builds from the repo root (so a workspace-aware Dockerfile can reach
|
|
3886
|
+
// sibling packages); `app` (absent) builds from the app dir.
|
|
3887
|
+
build_context: buildContextSchema.removeDefault().optional()
|
|
3888
|
+
}).strict();
|
|
3889
|
+
blueprintSchema = z17.union([presetBlueprintSchema, dockerfileBlueprintSchema]);
|
|
3890
|
+
connectionSchema = z17.object({
|
|
3592
3891
|
key: SecretKeySchema.superRefine((key, ctx) => {
|
|
3593
3892
|
if (isReservedPreviewkitEnvKey(key)) {
|
|
3594
3893
|
ctx.addIssue({
|
|
3595
|
-
code:
|
|
3894
|
+
code: z17.ZodIssueCode.custom,
|
|
3596
3895
|
message: `${key} is a reserved built-in variable and cannot be set.`
|
|
3597
3896
|
});
|
|
3598
3897
|
}
|
|
3599
3898
|
}).describe("The env var name to inject into the app at runtime (e.g. DATABASE_URL)."),
|
|
3600
|
-
value:
|
|
3601
|
-
"Template resolved at deploy time. {{name.property}} tokens reference apps/services
|
|
3899
|
+
value: z17.string().min(1, "value is required").describe(
|
|
3900
|
+
"Template resolved at deploy time. {{name.property}} tokens reference apps/services in this config by name. For a service: {{db.url}} = the full canonical connection string (postgres -> postgresql://preview:preview@<host>:<port>/preview; redis -> redis://...; mongodb -> mongodb://...), {{db.host}} = in-cluster DNS name, {{db.port}}. For an app: {{api.url}} = its public HTTPS URL, {{api.hostname}}. Also available: {{pr}}, {{namespace}}, {{owner}}. Tokens can mix with literal text ('postgresql://user:pass@{{db.host}}:5432/mydb'), or be a plain literal ('production'). Prefer {{db.url}} to wire a database."
|
|
3602
3901
|
),
|
|
3603
|
-
build_time:
|
|
3902
|
+
build_time: z17.boolean().default(false).describe("Also pass the resolved value as a Docker build arg.")
|
|
3604
3903
|
});
|
|
3605
3904
|
previewConfigSchema = buildPreviewConfigSchema(storedBuildSchema, false);
|
|
3606
3905
|
trustedPreviewConfigSchema = buildPreviewConfigSchema(storedBuildSchema, true);
|
|
@@ -3608,6 +3907,68 @@ var init_previewkit_config = __esm({
|
|
|
3608
3907
|
}
|
|
3609
3908
|
});
|
|
3610
3909
|
|
|
3910
|
+
// ../../packages/types/src/schemas/previewkit-job-spec.ts
|
|
3911
|
+
import { z as z18 } from "zod";
|
|
3912
|
+
var deployTargetSchema, redeployTargetSchema, teardownTargetSchema, previewJobSpecSchema;
|
|
3913
|
+
var init_previewkit_job_spec = __esm({
|
|
3914
|
+
"../../packages/types/src/schemas/previewkit-job-spec.ts"() {
|
|
3915
|
+
"use strict";
|
|
3916
|
+
init_esm_shims();
|
|
3917
|
+
deployTargetSchema = z18.object({
|
|
3918
|
+
repoFullName: z18.string().min(1),
|
|
3919
|
+
prNumber: z18.number().int().nonnegative(),
|
|
3920
|
+
organizationId: z18.string().min(1),
|
|
3921
|
+
githubRepositoryId: z18.number().int(),
|
|
3922
|
+
headSha: z18.string().min(1),
|
|
3923
|
+
headRef: z18.string().min(1),
|
|
3924
|
+
branchId: z18.string().optional()
|
|
3925
|
+
});
|
|
3926
|
+
redeployTargetSchema = z18.object({
|
|
3927
|
+
repoFullName: z18.string().min(1),
|
|
3928
|
+
prNumber: z18.number().int().nonnegative(),
|
|
3929
|
+
organizationId: z18.string().min(1),
|
|
3930
|
+
githubRepositoryId: z18.number().int(),
|
|
3931
|
+
headSha: z18.string().min(1),
|
|
3932
|
+
headRef: z18.string().min(1)
|
|
3933
|
+
});
|
|
3934
|
+
teardownTargetSchema = z18.object({
|
|
3935
|
+
repoFullName: z18.string().min(1),
|
|
3936
|
+
prNumber: z18.number().int().nonnegative(),
|
|
3937
|
+
organizationId: z18.string().min(1),
|
|
3938
|
+
headSha: z18.string().min(1).optional()
|
|
3939
|
+
});
|
|
3940
|
+
previewJobSpecSchema = z18.discriminatedUnion("mode", [
|
|
3941
|
+
z18.object({ mode: z18.literal("deploy"), target: deployTargetSchema }),
|
|
3942
|
+
z18.object({ mode: z18.literal("teardown"), target: teardownTargetSchema }),
|
|
3943
|
+
z18.object({
|
|
3944
|
+
mode: z18.literal("redeploy-app"),
|
|
3945
|
+
target: redeployTargetSchema,
|
|
3946
|
+
namespace: z18.string().min(1),
|
|
3947
|
+
appName: z18.string().min(1),
|
|
3948
|
+
redeployMode: z18.enum(["rebuild", "restart"])
|
|
3949
|
+
})
|
|
3950
|
+
]);
|
|
3951
|
+
}
|
|
3952
|
+
});
|
|
3953
|
+
|
|
3954
|
+
// ../../packages/types/src/schemas/previewkit-node-build.ts
|
|
3955
|
+
var init_previewkit_node_build = __esm({
|
|
3956
|
+
"../../packages/types/src/schemas/previewkit-node-build.ts"() {
|
|
3957
|
+
"use strict";
|
|
3958
|
+
init_esm_shims();
|
|
3959
|
+
init_previewkit_node_pm();
|
|
3960
|
+
}
|
|
3961
|
+
});
|
|
3962
|
+
|
|
3963
|
+
// ../../packages/types/src/schemas/previewkit-authorable-config.ts
|
|
3964
|
+
var init_previewkit_authorable_config = __esm({
|
|
3965
|
+
"../../packages/types/src/schemas/previewkit-authorable-config.ts"() {
|
|
3966
|
+
"use strict";
|
|
3967
|
+
init_esm_shims();
|
|
3968
|
+
init_previewkit_node_build();
|
|
3969
|
+
}
|
|
3970
|
+
});
|
|
3971
|
+
|
|
3611
3972
|
// ../../packages/types/src/schemas/previewkit-redeploy-app.ts
|
|
3612
3973
|
import { z as z19 } from "zod";
|
|
3613
3974
|
var RedeployPreviewkitAppInputSchema;
|
|
@@ -3769,138 +4130,15 @@ var init_snapshot_dependency_pin = __esm({
|
|
|
3769
4130
|
}
|
|
3770
4131
|
});
|
|
3771
4132
|
|
|
3772
|
-
// ../../packages/types/src/schemas/healing-actions.ts
|
|
3773
|
-
import { z as z24 } from "zod";
|
|
3774
|
-
var reviewSeveritySchema, primaryScreenshotRefSchema, evidenceItemSchema, healingReviewLinkSchema, updatePlanActionSchema, reportBugActionSchema, reportEngineLimitationActionSchema, reportUnknownIssueActionSchema, reportScenarioUnsupportedActionSchema, removeTestActionSchema, healingActionSchema, issueReportInputSchema, updatePlanInputSchema, reportBugInputSchema, reportEngineLimitationInputSchema, reportUnknownIssueInputSchema, reportScenarioUnsupportedInputSchema, removeTestInputSchema;
|
|
3775
|
-
var init_healing_actions = __esm({
|
|
3776
|
-
"../../packages/types/src/schemas/healing-actions.ts"() {
|
|
3777
|
-
"use strict";
|
|
3778
|
-
init_esm_shims();
|
|
3779
|
-
init_issue_report();
|
|
3780
|
-
init_suspected_cause();
|
|
3781
|
-
reviewSeveritySchema = z24.enum(["critical", "high", "medium", "low"]);
|
|
3782
|
-
primaryScreenshotRefSchema = z24.object({
|
|
3783
|
-
stepOrder: z24.number().int().min(0).describe("The step order (from the failure's Execution steps / fetch_step_evidence) whose frame to feature."),
|
|
3784
|
-
timing: z24.enum(["before", "after"]).describe("Which captured frame of that step best shows the bug: the screenshot before or after the step ran.")
|
|
3785
|
-
});
|
|
3786
|
-
evidenceItemSchema = z24.object({
|
|
3787
|
-
type: z24.enum(["screenshot", "video", "conversation", "step_output"]),
|
|
3788
|
-
description: z24.string(),
|
|
3789
|
-
s3Key: z24.string().optional()
|
|
3790
|
-
});
|
|
3791
|
-
healingReviewLinkSchema = z24.object({ generationReviewId: z24.string() });
|
|
3792
|
-
updatePlanActionSchema = z24.object({
|
|
3793
|
-
kind: z24.literal("update_plan"),
|
|
3794
|
-
planId: z24.string().describe("ID of the test plan to update"),
|
|
3795
|
-
testCaseId: z24.string().describe("ID of the test case the plan belongs to"),
|
|
3796
|
-
newPrompt: z24.string().describe("Replacement plan prompt - the natural language test instruction"),
|
|
3797
|
-
reasoning: z24.string().describe("Why this rewrite addresses the failure")
|
|
3798
|
-
});
|
|
3799
|
-
reportBugActionSchema = z24.object({
|
|
3800
|
-
kind: z24.literal("report_bug"),
|
|
3801
|
-
testCaseId: z24.string().describe("ID of the test case that surfaced the bug"),
|
|
3802
|
-
title: z24.string().describe("Short bug title"),
|
|
3803
|
-
description: z24.string().describe("Full bug description with reproduction steps and root cause hypothesis"),
|
|
3804
|
-
severity: reviewSeveritySchema,
|
|
3805
|
-
evidence: z24.array(evidenceItemSchema).describe("Screenshots, videos, step outputs supporting the bug report"),
|
|
3806
|
-
reasoning: z24.string().describe("Why this is an application bug rather than a test or engine issue"),
|
|
3807
|
-
suspectedCause: suspectedCauseSchema.describe(
|
|
3808
|
-
"The concrete code cause you re-grounded independently (>= 1 code reference). If you cannot reproduce the cause in the checked-out code, downgrade to report_unknown_issue instead of reporting a bug."
|
|
3809
|
-
),
|
|
3810
|
-
// The persisted report carries the full shape (authored text + the
|
|
3811
|
-
// system-derived evidenceManifest the report tool attaches from the agent's
|
|
3812
|
-
// actual fetches). Optional so actions persisted without a report still parse
|
|
3813
|
-
// (loadPriorActions / eval fixtures); the tool input (reportBugInputSchema)
|
|
3814
|
-
// re-declares an authored (manifest-free) report as required, so every new
|
|
3815
|
-
// report_bug carries one and the manifest is never model-authored.
|
|
3816
|
-
report: issueReportSchema.optional().describe(
|
|
3817
|
-
"The customer-facing, evidence-grounded report shown on the bug page: Expected vs Actual plus a rich narrative. Author it from the evidence you fetched with fetch_step_evidence, not from the plan text alone."
|
|
3818
|
-
),
|
|
3819
|
-
reviewLink: healingReviewLinkSchema
|
|
3820
|
-
});
|
|
3821
|
-
reportEngineLimitationActionSchema = z24.object({
|
|
3822
|
-
kind: z24.literal("report_engine_limitation"),
|
|
3823
|
-
testCaseId: z24.string().describe("ID of the test case that surfaced the limitation"),
|
|
3824
|
-
title: z24.string(),
|
|
3825
|
-
description: z24.string().describe("What the engine/agent could not do, and why no workaround is feasible"),
|
|
3826
|
-
severity: reviewSeveritySchema,
|
|
3827
|
-
evidence: z24.array(evidenceItemSchema),
|
|
3828
|
-
reasoning: z24.string(),
|
|
3829
|
-
reviewLink: healingReviewLinkSchema
|
|
3830
|
-
});
|
|
3831
|
-
reportUnknownIssueActionSchema = z24.object({
|
|
3832
|
-
kind: z24.literal("report_unknown_issue"),
|
|
3833
|
-
testCaseId: z24.string().describe("ID of the test case that surfaced the suspected issue"),
|
|
3834
|
-
title: z24.string(),
|
|
3835
|
-
description: z24.string().describe("What the application appeared to do wrong, and why the cause could not be grounded in the code"),
|
|
3836
|
-
severity: reviewSeveritySchema,
|
|
3837
|
-
evidence: z24.array(evidenceItemSchema),
|
|
3838
|
-
reasoning: z24.string().describe("Why this is a suspected issue you could not ground in code rather than a confirmed application bug"),
|
|
3839
|
-
reviewLink: healingReviewLinkSchema
|
|
3840
|
-
});
|
|
3841
|
-
reportScenarioUnsupportedActionSchema = z24.object({
|
|
3842
|
-
kind: z24.literal("report_scenario_unsupported"),
|
|
3843
|
-
testCaseId: z24.string().describe("ID of the test case that is impossible given the current scenario data"),
|
|
3844
|
-
title: z24.string(),
|
|
3845
|
-
description: z24.string().describe(
|
|
3846
|
-
"What the test needs that the scenario data cannot currently provide, with the proposed scenario extension woven in as prose - surfaced verbatim to a human, who decides whether to extend the scenario"
|
|
3847
|
-
),
|
|
3848
|
-
severity: reviewSeveritySchema,
|
|
3849
|
-
evidence: z24.array(evidenceItemSchema),
|
|
3850
|
-
reasoning: z24.string().describe(
|
|
3851
|
-
"Why this test is impossible given the current scenario data (a true data gap) rather than a stale plan that should be rewritten"
|
|
3852
|
-
),
|
|
3853
|
-
reviewLink: healingReviewLinkSchema
|
|
3854
|
-
});
|
|
3855
|
-
removeTestActionSchema = z24.object({
|
|
3856
|
-
kind: z24.literal("remove_test"),
|
|
3857
|
-
testCaseId: z24.string().describe("ID of the test case to delete from the suite"),
|
|
3858
|
-
reason: z24.string().describe(
|
|
3859
|
-
"Why this test should be removed: either it is invalid (not a viable flow, never useful without becoming a different test) or its feature was deleted from the app"
|
|
3860
|
-
),
|
|
3861
|
-
evidence: z24.array(evidenceItemSchema).optional().describe("Optional screenshots, videos, step outputs supporting the removal"),
|
|
3862
|
-
reviewLink: healingReviewLinkSchema
|
|
3863
|
-
});
|
|
3864
|
-
healingActionSchema = z24.discriminatedUnion("kind", [
|
|
3865
|
-
updatePlanActionSchema,
|
|
3866
|
-
reportBugActionSchema,
|
|
3867
|
-
reportEngineLimitationActionSchema,
|
|
3868
|
-
reportUnknownIssueActionSchema,
|
|
3869
|
-
reportScenarioUnsupportedActionSchema,
|
|
3870
|
-
removeTestActionSchema
|
|
3871
|
-
]);
|
|
3872
|
-
issueReportInputSchema = authoredIssueReportSchema.extend({
|
|
3873
|
-
primaryScreenshot: primaryScreenshotRefSchema.optional().describe(
|
|
3874
|
-
"Optional: the frame that best shows the bug, referenced by the step you inspected with fetch_step_evidence (its order + before/after). Designate one when a step's frame shows the bug more clearly than the mechanical failing step; omit it to let the page fall back to the failing-step screenshot. Do not invent a step you did not fetch."
|
|
3875
|
-
)
|
|
3876
|
-
});
|
|
3877
|
-
updatePlanInputSchema = updatePlanActionSchema.omit({ kind: true });
|
|
3878
|
-
reportBugInputSchema = reportBugActionSchema.omit({ kind: true, reviewLink: true }).extend({ report: issueReportInputSchema });
|
|
3879
|
-
reportEngineLimitationInputSchema = reportEngineLimitationActionSchema.omit({
|
|
3880
|
-
kind: true,
|
|
3881
|
-
reviewLink: true
|
|
3882
|
-
});
|
|
3883
|
-
reportUnknownIssueInputSchema = reportUnknownIssueActionSchema.omit({
|
|
3884
|
-
kind: true,
|
|
3885
|
-
reviewLink: true
|
|
3886
|
-
});
|
|
3887
|
-
reportScenarioUnsupportedInputSchema = reportScenarioUnsupportedActionSchema.omit({
|
|
3888
|
-
kind: true,
|
|
3889
|
-
reviewLink: true
|
|
3890
|
-
});
|
|
3891
|
-
removeTestInputSchema = removeTestActionSchema.omit({ kind: true, reviewLink: true });
|
|
3892
|
-
}
|
|
3893
|
-
});
|
|
3894
|
-
|
|
3895
4133
|
// ../../packages/types/src/schemas/checkpoint-summary.ts
|
|
3896
|
-
import { z as
|
|
4134
|
+
import { z as z24 } from "zod";
|
|
3897
4135
|
var checkpointToneSchema, checkpointExecutionStateSchema, checkpointTestCountsSchema, checkpointFailingByKindSchema, checkpointAnalysisSummarySchema, checkpointPresentationSummarySchema;
|
|
3898
4136
|
var init_checkpoint_summary = __esm({
|
|
3899
4137
|
"../../packages/types/src/schemas/checkpoint-summary.ts"() {
|
|
3900
4138
|
"use strict";
|
|
3901
4139
|
init_esm_shims();
|
|
3902
|
-
checkpointToneSchema =
|
|
3903
|
-
checkpointExecutionStateSchema =
|
|
4140
|
+
checkpointToneSchema = z24.enum(["success", "critical", "warning", "neutral"]);
|
|
4141
|
+
checkpointExecutionStateSchema = z24.enum([
|
|
3904
4142
|
"not_started",
|
|
3905
4143
|
"running",
|
|
3906
4144
|
"stale",
|
|
@@ -3909,43 +4147,43 @@ var init_checkpoint_summary = __esm({
|
|
|
3909
4147
|
"pipeline_failed",
|
|
3910
4148
|
"unknown"
|
|
3911
4149
|
]);
|
|
3912
|
-
checkpointTestCountsSchema =
|
|
3913
|
-
assigned:
|
|
3914
|
-
run:
|
|
3915
|
-
passed:
|
|
3916
|
-
failed:
|
|
4150
|
+
checkpointTestCountsSchema = z24.object({
|
|
4151
|
+
assigned: z24.number(),
|
|
4152
|
+
run: z24.number(),
|
|
4153
|
+
passed: z24.number(),
|
|
4154
|
+
failed: z24.number(),
|
|
3917
4155
|
// Tests that never ran because their scenario setup failed.
|
|
3918
|
-
setupFailed:
|
|
3919
|
-
running:
|
|
3920
|
-
notRun:
|
|
4156
|
+
setupFailed: z24.number(),
|
|
4157
|
+
running: z24.number(),
|
|
4158
|
+
notRun: z24.number()
|
|
3921
4159
|
});
|
|
3922
|
-
checkpointFailingByKindSchema =
|
|
3923
|
-
engine:
|
|
3924
|
-
app:
|
|
4160
|
+
checkpointFailingByKindSchema = z24.object({
|
|
4161
|
+
engine: z24.number(),
|
|
4162
|
+
app: z24.number()
|
|
3925
4163
|
});
|
|
3926
|
-
checkpointAnalysisSummarySchema =
|
|
4164
|
+
checkpointAnalysisSummarySchema = z24.object({
|
|
3927
4165
|
// The AnalysisJob lifecycle. Mirrors the `AnalysisJobStatus` db enum (types cannot import it).
|
|
3928
|
-
jobStatus:
|
|
4166
|
+
jobStatus: z24.enum(["running", "completed", "failed"]),
|
|
3929
4167
|
// Client-bug findings - the only plane that counts against the PR (turns the checkpoint red).
|
|
3930
|
-
bugCount:
|
|
4168
|
+
bugCount: z24.number().int().nonnegative(),
|
|
3931
4169
|
// Findings that passed on the app-health plane.
|
|
3932
|
-
passedCount:
|
|
4170
|
+
passedCount: z24.number().int().nonnegative(),
|
|
3933
4171
|
// Coverage-plane findings (engine_artifact / environment_failure / scenario_issue / delete) - never a
|
|
3934
4172
|
// failure; surfaced as "couldn't confirm".
|
|
3935
|
-
coverageCount:
|
|
4173
|
+
coverageCount: z24.number().int().nonnegative()
|
|
3936
4174
|
});
|
|
3937
|
-
checkpointPresentationSummarySchema =
|
|
4175
|
+
checkpointPresentationSummarySchema = z24.object({
|
|
3938
4176
|
tone: checkpointToneSchema,
|
|
3939
|
-
label:
|
|
3940
|
-
reason:
|
|
4177
|
+
label: z24.string(),
|
|
4178
|
+
reason: z24.string().optional(),
|
|
3941
4179
|
executionState: checkpointExecutionStateSchema,
|
|
3942
4180
|
// Unique open application bugs.
|
|
3943
|
-
openBugCount:
|
|
4181
|
+
openBugCount: z24.number(),
|
|
3944
4182
|
// Raw application-issue occurrences.
|
|
3945
|
-
issueOccurrenceCount:
|
|
4183
|
+
issueOccurrenceCount: z24.number(),
|
|
3946
4184
|
testCounts: checkpointTestCountsSchema,
|
|
3947
4185
|
failingByKind: checkpointFailingByKindSchema,
|
|
3948
|
-
suiteChangeCount:
|
|
4186
|
+
suiteChangeCount: z24.number(),
|
|
3949
4187
|
// Set only for authoritative-analysis snapshots; absent for legacy diffs/shadow snapshots.
|
|
3950
4188
|
analysis: checkpointAnalysisSummarySchema.optional()
|
|
3951
4189
|
});
|
|
@@ -3953,96 +4191,82 @@ var init_checkpoint_summary = __esm({
|
|
|
3953
4191
|
});
|
|
3954
4192
|
|
|
3955
4193
|
// ../../packages/types/src/schemas/snapshot-report.ts
|
|
3956
|
-
import { z as
|
|
3957
|
-
var reportHealthSchema, reportTestStatusSchema, reportCommitFileSchema, snapshotReportTriggerSchema,
|
|
4194
|
+
import { z as z25 } from "zod";
|
|
4195
|
+
var reportHealthSchema, reportTestStatusSchema, reportCommitFileSchema, snapshotReportTriggerSchema, snapshotReportTestResultSchema, snapshotReportResultsSchema, snapshotReportBugSchema, snapshotReportHealthCountsSchema, snapshotReportSchema;
|
|
3958
4196
|
var init_snapshot_report = __esm({
|
|
3959
4197
|
"../../packages/types/src/schemas/snapshot-report.ts"() {
|
|
3960
4198
|
"use strict";
|
|
3961
4199
|
init_esm_shims();
|
|
3962
4200
|
init_checkpoint_summary();
|
|
3963
|
-
reportHealthSchema =
|
|
3964
|
-
reportTestStatusSchema =
|
|
3965
|
-
reportCommitFileSchema =
|
|
3966
|
-
filename:
|
|
3967
|
-
status:
|
|
3968
|
-
additions:
|
|
3969
|
-
deletions:
|
|
3970
|
-
});
|
|
3971
|
-
snapshotReportTriggerSchema =
|
|
3972
|
-
headSha:
|
|
3973
|
-
baseSha:
|
|
3974
|
-
source:
|
|
3975
|
-
createdAt:
|
|
3976
|
-
commit:
|
|
3977
|
-
filesChanged:
|
|
3978
|
-
filesChangedTruncated:
|
|
3979
|
-
});
|
|
3980
|
-
|
|
3981
|
-
testCaseId:
|
|
3982
|
-
name:
|
|
3983
|
-
slug:
|
|
3984
|
-
affectedReason: z26.string().optional(),
|
|
3985
|
-
reasoning: z26.string().optional()
|
|
3986
|
-
});
|
|
3987
|
-
snapshotReportSelectionSchema = z26.object({
|
|
3988
|
-
totalSuiteTests: z26.number(),
|
|
3989
|
-
selected: z26.array(snapshotReportSelectedTestSchema),
|
|
3990
|
-
analysisReasoning: z26.string().optional()
|
|
3991
|
-
});
|
|
3992
|
-
snapshotReportTestResultSchema = z26.object({
|
|
3993
|
-
testCaseId: z26.string(),
|
|
3994
|
-
name: z26.string(),
|
|
3995
|
-
slug: z26.string(),
|
|
4201
|
+
reportHealthSchema = z25.enum(["healthy", "critical", "running", "unknown"]);
|
|
4202
|
+
reportTestStatusSchema = z25.enum(["passed", "failed", "setup_failed", "running", "pending"]);
|
|
4203
|
+
reportCommitFileSchema = z25.object({
|
|
4204
|
+
filename: z25.string(),
|
|
4205
|
+
status: z25.string(),
|
|
4206
|
+
additions: z25.number(),
|
|
4207
|
+
deletions: z25.number()
|
|
4208
|
+
});
|
|
4209
|
+
snapshotReportTriggerSchema = z25.object({
|
|
4210
|
+
headSha: z25.string().optional(),
|
|
4211
|
+
baseSha: z25.string().optional(),
|
|
4212
|
+
source: z25.string(),
|
|
4213
|
+
createdAt: z25.date(),
|
|
4214
|
+
commit: z25.object({ message: z25.string(), authorLogin: z25.string().optional() }).optional(),
|
|
4215
|
+
filesChanged: z25.array(reportCommitFileSchema),
|
|
4216
|
+
filesChangedTruncated: z25.boolean()
|
|
4217
|
+
});
|
|
4218
|
+
snapshotReportTestResultSchema = z25.object({
|
|
4219
|
+
testCaseId: z25.string(),
|
|
4220
|
+
name: z25.string(),
|
|
4221
|
+
slug: z25.string(),
|
|
3996
4222
|
status: reportTestStatusSchema,
|
|
3997
|
-
runId:
|
|
3998
|
-
durationMs:
|
|
3999
|
-
});
|
|
4000
|
-
snapshotReportResultsSchema =
|
|
4001
|
-
durationMs:
|
|
4002
|
-
passed:
|
|
4003
|
-
failed:
|
|
4004
|
-
setupFailed:
|
|
4005
|
-
pending:
|
|
4006
|
-
running:
|
|
4007
|
-
total:
|
|
4008
|
-
tests:
|
|
4009
|
-
});
|
|
4010
|
-
snapshotReportBugSchema =
|
|
4011
|
-
bugId:
|
|
4012
|
-
title:
|
|
4013
|
-
description:
|
|
4014
|
-
severity:
|
|
4015
|
-
status:
|
|
4016
|
-
occurrences:
|
|
4017
|
-
testSlug:
|
|
4018
|
-
stepIndex:
|
|
4019
|
-
stepTotal:
|
|
4020
|
-
screenshotUrl:
|
|
4021
|
-
issueId:
|
|
4022
|
-
});
|
|
4023
|
-
snapshotReportHealthCountsSchema =
|
|
4024
|
-
failing:
|
|
4025
|
-
passing:
|
|
4026
|
-
running:
|
|
4027
|
-
setupFailed:
|
|
4028
|
-
notAffected:
|
|
4029
|
-
totalTests:
|
|
4030
|
-
});
|
|
4031
|
-
snapshotReportSchema =
|
|
4032
|
-
snapshot:
|
|
4033
|
-
id:
|
|
4034
|
-
status:
|
|
4035
|
-
source:
|
|
4036
|
-
headSha:
|
|
4037
|
-
baseSha:
|
|
4038
|
-
createdAt:
|
|
4039
|
-
branch:
|
|
4223
|
+
runId: z25.string().optional(),
|
|
4224
|
+
durationMs: z25.number().optional()
|
|
4225
|
+
});
|
|
4226
|
+
snapshotReportResultsSchema = z25.object({
|
|
4227
|
+
durationMs: z25.number().optional(),
|
|
4228
|
+
passed: z25.number(),
|
|
4229
|
+
failed: z25.number(),
|
|
4230
|
+
setupFailed: z25.number(),
|
|
4231
|
+
pending: z25.number(),
|
|
4232
|
+
running: z25.number(),
|
|
4233
|
+
total: z25.number(),
|
|
4234
|
+
tests: z25.array(snapshotReportTestResultSchema)
|
|
4235
|
+
});
|
|
4236
|
+
snapshotReportBugSchema = z25.object({
|
|
4237
|
+
bugId: z25.string(),
|
|
4238
|
+
title: z25.string(),
|
|
4239
|
+
description: z25.string(),
|
|
4240
|
+
severity: z25.string(),
|
|
4241
|
+
status: z25.string(),
|
|
4242
|
+
occurrences: z25.number(),
|
|
4243
|
+
testSlug: z25.string().optional(),
|
|
4244
|
+
stepIndex: z25.number().optional(),
|
|
4245
|
+
stepTotal: z25.number().optional(),
|
|
4246
|
+
screenshotUrl: z25.string().optional(),
|
|
4247
|
+
issueId: z25.string().optional()
|
|
4248
|
+
});
|
|
4249
|
+
snapshotReportHealthCountsSchema = z25.object({
|
|
4250
|
+
failing: z25.number(),
|
|
4251
|
+
passing: z25.number(),
|
|
4252
|
+
running: z25.number(),
|
|
4253
|
+
setupFailed: z25.number(),
|
|
4254
|
+
notAffected: z25.number(),
|
|
4255
|
+
totalTests: z25.number()
|
|
4256
|
+
});
|
|
4257
|
+
snapshotReportSchema = z25.object({
|
|
4258
|
+
snapshot: z25.object({
|
|
4259
|
+
id: z25.string(),
|
|
4260
|
+
status: z25.string(),
|
|
4261
|
+
source: z25.string(),
|
|
4262
|
+
headSha: z25.string().optional(),
|
|
4263
|
+
baseSha: z25.string().optional(),
|
|
4264
|
+
createdAt: z25.date(),
|
|
4265
|
+
branch: z25.object({ id: z25.string(), name: z25.string(), prNumber: z25.number().optional() })
|
|
4040
4266
|
}),
|
|
4041
4267
|
trigger: snapshotReportTriggerSchema,
|
|
4042
|
-
selection: snapshotReportSelectionSchema,
|
|
4043
4268
|
results: snapshotReportResultsSchema,
|
|
4044
|
-
bugs:
|
|
4045
|
-
firstIterationReasoning: z26.string().optional(),
|
|
4269
|
+
bugs: z25.array(snapshotReportBugSchema),
|
|
4046
4270
|
health: reportHealthSchema,
|
|
4047
4271
|
healthCounts: snapshotReportHealthCountsSchema,
|
|
4048
4272
|
summary: checkpointPresentationSummarySchema.optional()
|
|
@@ -4051,55 +4275,74 @@ var init_snapshot_report = __esm({
|
|
|
4051
4275
|
});
|
|
4052
4276
|
|
|
4053
4277
|
// ../../packages/types/src/schemas/pr-pipeline-status.ts
|
|
4054
|
-
import { z as
|
|
4278
|
+
import { z as z26 } from "zod";
|
|
4055
4279
|
var prPipelineStatusSchema;
|
|
4056
4280
|
var init_pr_pipeline_status = __esm({
|
|
4057
4281
|
"../../packages/types/src/schemas/pr-pipeline-status.ts"() {
|
|
4058
4282
|
"use strict";
|
|
4059
4283
|
init_esm_shims();
|
|
4060
4284
|
init_checkpoint_summary();
|
|
4061
|
-
prPipelineStatusSchema =
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4285
|
+
prPipelineStatusSchema = z26.discriminatedUnion("kind", [
|
|
4286
|
+
z26.object({ kind: z26.literal("checkpoint"), summary: checkpointPresentationSummarySchema }),
|
|
4287
|
+
z26.object({ kind: z26.literal("building") }),
|
|
4288
|
+
z26.object({ kind: z26.literal("pending_checks") }),
|
|
4289
|
+
z26.object({ kind: z26.literal("analyzing") }),
|
|
4290
|
+
z26.object({ kind: z26.literal("analysis_failed") }),
|
|
4291
|
+
z26.object({ kind: z26.literal("build_failed") }),
|
|
4292
|
+
z26.object({ kind: z26.literal("none") })
|
|
4069
4293
|
]);
|
|
4070
4294
|
}
|
|
4071
4295
|
});
|
|
4072
4296
|
|
|
4073
4297
|
// ../../packages/types/src/schemas/bug-detail.ts
|
|
4074
|
-
import { z as
|
|
4298
|
+
import { z as z27 } from "zod";
|
|
4075
4299
|
var runAnalysisSchema, pointSchema2, stepOutputDataSchema, bugOccurrenceSchema;
|
|
4076
4300
|
var init_bug_detail = __esm({
|
|
4077
4301
|
"../../packages/types/src/schemas/bug-detail.ts"() {
|
|
4078
4302
|
"use strict";
|
|
4079
4303
|
init_esm_shims();
|
|
4080
4304
|
init_generation_verdict();
|
|
4081
|
-
runAnalysisSchema =
|
|
4305
|
+
runAnalysisSchema = z27.object({
|
|
4082
4306
|
failurePoint: failurePointSchema.optional(),
|
|
4083
|
-
evidence:
|
|
4307
|
+
evidence: z27.array(reviewEvidenceSchema).default([])
|
|
4084
4308
|
});
|
|
4085
|
-
pointSchema2 =
|
|
4086
|
-
stepOutputDataSchema =
|
|
4087
|
-
outcome:
|
|
4309
|
+
pointSchema2 = z27.object({ x: z27.number(), y: z27.number() });
|
|
4310
|
+
stepOutputDataSchema = z27.object({
|
|
4311
|
+
outcome: z27.string().optional(),
|
|
4088
4312
|
point: pointSchema2.optional(),
|
|
4089
4313
|
startPoint: pointSchema2.optional(),
|
|
4090
4314
|
endPoint: pointSchema2.optional()
|
|
4091
4315
|
});
|
|
4092
|
-
bugOccurrenceSchema =
|
|
4093
|
-
issueId:
|
|
4094
|
-
source:
|
|
4095
|
-
runId:
|
|
4096
|
-
generationId:
|
|
4097
|
-
createdAt:
|
|
4098
|
-
isLatest:
|
|
4099
|
-
snapshotId:
|
|
4100
|
-
sha:
|
|
4101
|
-
prNumber:
|
|
4102
|
-
branchName:
|
|
4316
|
+
bugOccurrenceSchema = z27.object({
|
|
4317
|
+
issueId: z27.string(),
|
|
4318
|
+
source: z27.enum(["run", "generation"]),
|
|
4319
|
+
runId: z27.string().optional(),
|
|
4320
|
+
generationId: z27.string().optional(),
|
|
4321
|
+
createdAt: z27.date(),
|
|
4322
|
+
isLatest: z27.boolean(),
|
|
4323
|
+
snapshotId: z27.string().optional(),
|
|
4324
|
+
sha: z27.string().optional(),
|
|
4325
|
+
prNumber: z27.number().optional(),
|
|
4326
|
+
branchName: z27.string().optional()
|
|
4327
|
+
});
|
|
4328
|
+
}
|
|
4329
|
+
});
|
|
4330
|
+
|
|
4331
|
+
// ../../packages/types/src/schemas/activation-triggers.ts
|
|
4332
|
+
import { z as z28 } from "zod";
|
|
4333
|
+
var MAX_LABEL_LENGTH, CONTROL_CHARS, AnalysisTriggerLabelSchema, TriggerConfigSchema;
|
|
4334
|
+
var init_activation_triggers = __esm({
|
|
4335
|
+
"../../packages/types/src/schemas/activation-triggers.ts"() {
|
|
4336
|
+
"use strict";
|
|
4337
|
+
init_esm_shims();
|
|
4338
|
+
MAX_LABEL_LENGTH = 50;
|
|
4339
|
+
CONTROL_CHARS = /[\u0000-\u001F\u007F]/;
|
|
4340
|
+
AnalysisTriggerLabelSchema = z28.string().trim().min(1, "Label cannot be empty").max(MAX_LABEL_LENGTH, `Label must be at most ${MAX_LABEL_LENGTH} characters`).refine((value) => !CONTROL_CHARS.test(value), "Label cannot contain control characters");
|
|
4341
|
+
TriggerConfigSchema = z28.object({
|
|
4342
|
+
/** Whether marking a PR ready-for-review automatically starts an analysis run. */
|
|
4343
|
+
autoRunOnReadyForReview: z28.boolean(),
|
|
4344
|
+
/** The PR label whose addition starts an analysis run. */
|
|
4345
|
+
analysisTriggerLabel: AnalysisTriggerLabelSchema
|
|
4103
4346
|
});
|
|
4104
4347
|
}
|
|
4105
4348
|
});
|
|
@@ -4254,21 +4497,25 @@ var init_schemas = __esm({
|
|
|
4254
4497
|
init_generation();
|
|
4255
4498
|
init_api_key();
|
|
4256
4499
|
init_secrets();
|
|
4257
|
-
init_org_secrets();
|
|
4258
4500
|
init_previewkit_builtins();
|
|
4259
4501
|
init_previewkit_config();
|
|
4502
|
+
init_previewkit_job_spec();
|
|
4503
|
+
init_previewkit_node_build();
|
|
4504
|
+
init_previewkit_node_pm();
|
|
4505
|
+
init_previewkit_presets();
|
|
4506
|
+
init_previewkit_authorable_config();
|
|
4260
4507
|
init_previewkit_runtimes();
|
|
4261
4508
|
init_previewkit_redeploy_app();
|
|
4262
4509
|
init_previewkit_service_suggestion();
|
|
4263
4510
|
init_previewkit_env_suggestion();
|
|
4264
4511
|
init_previewkit_diagnosis();
|
|
4265
4512
|
init_snapshot_dependency_pin();
|
|
4266
|
-
init_healing_actions();
|
|
4267
4513
|
init_snapshot_report();
|
|
4268
4514
|
init_checkpoint_summary();
|
|
4269
4515
|
init_pr_pipeline_status();
|
|
4270
4516
|
init_bug_detail();
|
|
4271
4517
|
init_investigation_report();
|
|
4518
|
+
init_activation_triggers();
|
|
4272
4519
|
init_suite_health();
|
|
4273
4520
|
init_suite_health_fix_plan();
|
|
4274
4521
|
PlatformSchema = z31.enum(["web", "ios", "android"]);
|
|
@@ -4463,6 +4710,14 @@ var init_designated_run = __esm({
|
|
|
4463
4710
|
}
|
|
4464
4711
|
});
|
|
4465
4712
|
|
|
4713
|
+
// ../../packages/types/src/deployment-signal-template.ts
|
|
4714
|
+
var init_deployment_signal_template = __esm({
|
|
4715
|
+
"../../packages/types/src/deployment-signal-template.ts"() {
|
|
4716
|
+
"use strict";
|
|
4717
|
+
init_esm_shims();
|
|
4718
|
+
}
|
|
4719
|
+
});
|
|
4720
|
+
|
|
4466
4721
|
// ../../packages/types/src/constants/pipeline-labels.ts
|
|
4467
4722
|
var init_pipeline_labels = __esm({
|
|
4468
4723
|
"../../packages/types/src/constants/pipeline-labels.ts"() {
|
|
@@ -4497,6 +4752,32 @@ var init_previewkit = __esm({
|
|
|
4497
4752
|
}
|
|
4498
4753
|
});
|
|
4499
4754
|
|
|
4755
|
+
// ../../packages/types/src/types/parse-string-record.ts
|
|
4756
|
+
var init_parse_string_record = __esm({
|
|
4757
|
+
"../../packages/types/src/types/parse-string-record.ts"() {
|
|
4758
|
+
"use strict";
|
|
4759
|
+
init_esm_shims();
|
|
4760
|
+
}
|
|
4761
|
+
});
|
|
4762
|
+
|
|
4763
|
+
// ../../packages/types/src/types/previewkit-manifest.ts
|
|
4764
|
+
var init_previewkit_manifest = __esm({
|
|
4765
|
+
"../../packages/types/src/types/previewkit-manifest.ts"() {
|
|
4766
|
+
"use strict";
|
|
4767
|
+
init_esm_shims();
|
|
4768
|
+
init_previewkit_config();
|
|
4769
|
+
}
|
|
4770
|
+
});
|
|
4771
|
+
|
|
4772
|
+
// ../../packages/types/src/types/previewkit-preview-urls.ts
|
|
4773
|
+
var init_previewkit_preview_urls = __esm({
|
|
4774
|
+
"../../packages/types/src/types/previewkit-preview-urls.ts"() {
|
|
4775
|
+
"use strict";
|
|
4776
|
+
init_esm_shims();
|
|
4777
|
+
init_previewkit_config();
|
|
4778
|
+
}
|
|
4779
|
+
});
|
|
4780
|
+
|
|
4500
4781
|
// ../../packages/types/src/index.ts
|
|
4501
4782
|
var init_src = __esm({
|
|
4502
4783
|
"../../packages/types/src/index.ts"() {
|
|
@@ -4510,10 +4791,14 @@ var init_src = __esm({
|
|
|
4510
4791
|
init_preview_url();
|
|
4511
4792
|
init_app_links();
|
|
4512
4793
|
init_designated_run();
|
|
4794
|
+
init_deployment_signal_template();
|
|
4513
4795
|
init_constants();
|
|
4514
4796
|
init_architecture();
|
|
4515
4797
|
init_step_overlay_points();
|
|
4516
4798
|
init_previewkit();
|
|
4799
|
+
init_parse_string_record();
|
|
4800
|
+
init_previewkit_manifest();
|
|
4801
|
+
init_previewkit_preview_urls();
|
|
4517
4802
|
}
|
|
4518
4803
|
});
|
|
4519
4804
|
|
|
@@ -4521,7 +4806,9 @@ var init_src = __esm({
|
|
|
4521
4806
|
import { readFile as readFile2 } from "fs/promises";
|
|
4522
4807
|
import { join as join6 } from "path";
|
|
4523
4808
|
async function loadRecipe(outputDir) {
|
|
4524
|
-
|
|
4809
|
+
return await loadRecipeFile(join6(outputDir, RECIPE_FILE));
|
|
4810
|
+
}
|
|
4811
|
+
async function loadRecipeFile(path3) {
|
|
4525
4812
|
let raw;
|
|
4526
4813
|
try {
|
|
4527
4814
|
raw = await readFile2(path3, "utf-8");
|
|
@@ -4535,19 +4822,35 @@ async function loadRecipe(outputDir) {
|
|
|
4535
4822
|
} catch (err) {
|
|
4536
4823
|
const message = err instanceof Error ? err.message : String(err);
|
|
4537
4824
|
debugLog("recipe.json is not valid JSON", { path: path3, err });
|
|
4538
|
-
return { status: "invalid", problems: [`${
|
|
4825
|
+
return { status: "invalid", problems: [`${path3} is not valid JSON: ${message}`] };
|
|
4539
4826
|
}
|
|
4540
|
-
const
|
|
4827
|
+
const normalized = withValidationCeremony(json);
|
|
4828
|
+
const parsed = ScenarioRecipesFileSchema.safeParse(normalized);
|
|
4541
4829
|
if (!parsed.success) {
|
|
4542
|
-
const problems = parsed.error.issues.map((issue) =>
|
|
4543
|
-
const path4 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
4544
|
-
return `${path4}: ${issue.message}`;
|
|
4545
|
-
});
|
|
4830
|
+
const problems = parsed.error.issues.map((issue) => describeIssue(issue, normalized));
|
|
4546
4831
|
debugLog("recipe.json failed schema validation", { path: path3, problems });
|
|
4547
4832
|
return { status: "invalid", problems };
|
|
4548
4833
|
}
|
|
4549
4834
|
return { status: "ok", recipe: parsed.data };
|
|
4550
4835
|
}
|
|
4836
|
+
function describeIssue(issue, root) {
|
|
4837
|
+
const field = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
4838
|
+
const found = valueAtPath(root, issue.path);
|
|
4839
|
+
if (found === void 0) return `${field}: ${issue.message}`;
|
|
4840
|
+
return `${field}: ${issue.message} (found: ${JSON.stringify(found)})`;
|
|
4841
|
+
}
|
|
4842
|
+
function valueAtPath(root, path3) {
|
|
4843
|
+
let current = root;
|
|
4844
|
+
for (const key of path3) {
|
|
4845
|
+
if (Array.isArray(current) && typeof key === "number") {
|
|
4846
|
+
current = current[key];
|
|
4847
|
+
continue;
|
|
4848
|
+
}
|
|
4849
|
+
if (!isRecord(current)) return void 0;
|
|
4850
|
+
current = current[String(key)];
|
|
4851
|
+
}
|
|
4852
|
+
return current;
|
|
4853
|
+
}
|
|
4551
4854
|
function findRecipeUploadProblems(recipe) {
|
|
4552
4855
|
return recipe.recipes.flatMap((entry) => {
|
|
4553
4856
|
const declaredTokens = new Set(Object.keys(entry.variables ?? {}));
|
|
@@ -4569,14 +4872,18 @@ function withValidationCeremony(json) {
|
|
|
4569
4872
|
...entry,
|
|
4570
4873
|
validation: {
|
|
4571
4874
|
...validation,
|
|
4572
|
-
status:
|
|
4573
|
-
phase:
|
|
4875
|
+
status: VALIDATION_STATUS,
|
|
4876
|
+
phase: VALIDATION_PHASE,
|
|
4877
|
+
method: isKnownValidationMethod(validation.method) ? validation.method : VALIDATION_METHOD
|
|
4574
4878
|
}
|
|
4575
4879
|
};
|
|
4576
4880
|
});
|
|
4577
4881
|
return { ...json, recipes };
|
|
4578
4882
|
}
|
|
4579
|
-
|
|
4883
|
+
function isKnownValidationMethod(method) {
|
|
4884
|
+
return typeof method === "string" && VALIDATION_METHODS.has(method);
|
|
4885
|
+
}
|
|
4886
|
+
var RECIPE_FILE, VALIDATION_STATUS, VALIDATION_PHASE, VALIDATION_METHOD, VALIDATION_METHODS;
|
|
4580
4887
|
var init_recipe = __esm({
|
|
4581
4888
|
"src/agents/04-recipe-builder/recipe.ts"() {
|
|
4582
4889
|
"use strict";
|
|
@@ -4586,6 +4893,8 @@ var init_recipe = __esm({
|
|
|
4586
4893
|
RECIPE_FILE = "recipe.json";
|
|
4587
4894
|
VALIDATION_STATUS = "validated";
|
|
4588
4895
|
VALIDATION_PHASE = "ok";
|
|
4896
|
+
VALIDATION_METHOD = "endpoint-up-down";
|
|
4897
|
+
VALIDATION_METHODS = new Set(SCENARIO_VALIDATION_METHODS);
|
|
4589
4898
|
}
|
|
4590
4899
|
});
|
|
4591
4900
|
|
|
@@ -4700,6 +5009,11 @@ var init_submit = __esm({
|
|
|
4700
5009
|
function resolveApiUrl(override) {
|
|
4701
5010
|
return (override ?? DEFAULT_API_URL).replace(/\/+$/, "");
|
|
4702
5011
|
}
|
|
5012
|
+
function resolveMcpUrl(apiUrl) {
|
|
5013
|
+
const base = apiUrl.replace(/\/+$/, "");
|
|
5014
|
+
if (base !== DEFAULT_API_URL) return `${base}/v1/mcp`;
|
|
5015
|
+
return `https://api.${new URL(DEFAULT_API_URL).hostname}/v1/mcp`;
|
|
5016
|
+
}
|
|
4703
5017
|
var DEFAULT_API_URL;
|
|
4704
5018
|
var init_api_url = __esm({
|
|
4705
5019
|
"src/core/api-url.ts"() {
|
|
@@ -4958,6 +5272,37 @@ var init_errors = __esm({
|
|
|
4958
5272
|
}
|
|
4959
5273
|
});
|
|
4960
5274
|
|
|
5275
|
+
// src/core/onboarding-phase.ts
|
|
5276
|
+
function isStepAtOrPast(step, target) {
|
|
5277
|
+
return STEP_ORDER2.indexOf(step) >= STEP_ORDER2.indexOf(target);
|
|
5278
|
+
}
|
|
5279
|
+
function resolveEntryPhase(state) {
|
|
5280
|
+
if (!isStepAtOrPast(state.step, PREVIEW_DONE_STEP)) return "preview";
|
|
5281
|
+
if (!state.artifactsUploaded || !state.sdkConfigured) return "planner";
|
|
5282
|
+
if (!state.dryRunPassed) return "dryRun";
|
|
5283
|
+
return "done";
|
|
5284
|
+
}
|
|
5285
|
+
var STEP_ORDER2, PREVIEW_DONE_STEP, LIVE_STEP;
|
|
5286
|
+
var init_onboarding_phase = __esm({
|
|
5287
|
+
"src/core/onboarding-phase.ts"() {
|
|
5288
|
+
"use strict";
|
|
5289
|
+
init_esm_shims();
|
|
5290
|
+
STEP_ORDER2 = [
|
|
5291
|
+
"github",
|
|
5292
|
+
"preview_environment",
|
|
5293
|
+
"previewkit_configuring",
|
|
5294
|
+
"previewkit_deploying",
|
|
5295
|
+
"existing_deploys_configuring",
|
|
5296
|
+
"existing_deploys_waiting",
|
|
5297
|
+
"preview_verified",
|
|
5298
|
+
"diff_trigger",
|
|
5299
|
+
"completed"
|
|
5300
|
+
];
|
|
5301
|
+
PREVIEW_DONE_STEP = "preview_verified";
|
|
5302
|
+
LIVE_STEP = "completed";
|
|
5303
|
+
}
|
|
5304
|
+
});
|
|
5305
|
+
|
|
4961
5306
|
// src/replay/replay-transport.ts
|
|
4962
5307
|
async function flushReplay(timeoutMs = 1500) {
|
|
4963
5308
|
if (pending2.size === 0) return;
|
|
@@ -4993,52 +5338,520 @@ var init_replay_transport = __esm({
|
|
|
4993
5338
|
this.buffer.push(event);
|
|
4994
5339
|
this.bufferBytes += JSON.stringify(event).length;
|
|
4995
5340
|
}
|
|
4996
|
-
if (this.bufferBytes >= MAX_BATCH_BYTES) this.send();
|
|
5341
|
+
if (this.bufferBytes >= MAX_BATCH_BYTES) this.send();
|
|
5342
|
+
}
|
|
5343
|
+
flush() {
|
|
5344
|
+
this.send();
|
|
5345
|
+
}
|
|
5346
|
+
send() {
|
|
5347
|
+
if (this.buffer.length === 0) return;
|
|
5348
|
+
const events = this.buffer;
|
|
5349
|
+
const bytes = this.bufferBytes;
|
|
5350
|
+
this.buffer = [];
|
|
5351
|
+
this.bufferBytes = 0;
|
|
5352
|
+
this.sessionBytes += bytes;
|
|
5353
|
+
if (this.sessionBytes > MAX_SESSION_BYTES) {
|
|
5354
|
+
this.exhausted = true;
|
|
5355
|
+
debugLog("Session replay cap reached; stopping capture", {
|
|
5356
|
+
sessionBytes: this.sessionBytes,
|
|
5357
|
+
cap: MAX_SESSION_BYTES
|
|
5358
|
+
});
|
|
5359
|
+
return;
|
|
5360
|
+
}
|
|
5361
|
+
const body = JSON.stringify([
|
|
5362
|
+
{
|
|
5363
|
+
api_key: this.config.apiKey,
|
|
5364
|
+
event: "$snapshot",
|
|
5365
|
+
distinct_id: this.config.distinctId,
|
|
5366
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5367
|
+
properties: {
|
|
5368
|
+
$session_id: this.config.sessionId,
|
|
5369
|
+
$window_id: this.config.windowId,
|
|
5370
|
+
$snapshot_data: events,
|
|
5371
|
+
$snapshot_bytes: bytes,
|
|
5372
|
+
// The player treats a recording as web; the synthesized DOM is
|
|
5373
|
+
// a real DOM as far as it is concerned.
|
|
5374
|
+
$lib: "web",
|
|
5375
|
+
$lib_version: "1.0.0"
|
|
5376
|
+
}
|
|
5377
|
+
}
|
|
5378
|
+
]);
|
|
5379
|
+
const promise = fetch(`${this.config.host}/s/`, {
|
|
5380
|
+
method: "POST",
|
|
5381
|
+
headers: { "Content-Type": "application/json" },
|
|
5382
|
+
body
|
|
5383
|
+
}).catch((err) => {
|
|
5384
|
+
debugLog("Session replay upload failed (ignored)", { err });
|
|
5385
|
+
}).finally(() => pending2.delete(promise));
|
|
5386
|
+
pending2.add(promise);
|
|
5387
|
+
}
|
|
5388
|
+
};
|
|
5389
|
+
}
|
|
5390
|
+
});
|
|
5391
|
+
|
|
5392
|
+
// src/core/autonoma-client.ts
|
|
5393
|
+
import { createTRPCClient, httpLink } from "@trpc/client";
|
|
5394
|
+
import superjson from "superjson";
|
|
5395
|
+
function buildClient(apiUrl, apiToken, timeoutMs) {
|
|
5396
|
+
return createTRPCClient({
|
|
5397
|
+
links: [
|
|
5398
|
+
// httpLink, not httpBatchLink: the CLI makes a handful of calls minutes
|
|
5399
|
+
// apart, so batching has nothing to batch and only widens the blast
|
|
5400
|
+
// radius of one slow procedure.
|
|
5401
|
+
httpLink({
|
|
5402
|
+
url: `${apiUrl}${TRPC_PATH}`,
|
|
5403
|
+
transformer: superjson,
|
|
5404
|
+
headers: () => ({ Authorization: `Bearer ${apiToken}` }),
|
|
5405
|
+
// AbortSignal.timeout rather than a manual controller: the timer is
|
|
5406
|
+
// cleared with the signal, so a slow-but-successful call cannot leave
|
|
5407
|
+
// a pending timeout holding the event loop open at exit.
|
|
5408
|
+
fetch: (url, options) => fetch(url, { ...options, signal: AbortSignal.timeout(timeoutMs) })
|
|
5409
|
+
})
|
|
5410
|
+
]
|
|
5411
|
+
});
|
|
5412
|
+
}
|
|
5413
|
+
var TRPC_PATH, REQUEST_TIMEOUT_MS, SDK_REQUEST_TIMEOUT_MS, AutonomaClient;
|
|
5414
|
+
var init_autonoma_client = __esm({
|
|
5415
|
+
"src/core/autonoma-client.ts"() {
|
|
5416
|
+
"use strict";
|
|
5417
|
+
init_esm_shims();
|
|
5418
|
+
init_debug();
|
|
5419
|
+
init_logs();
|
|
5420
|
+
TRPC_PATH = "/v1/trpc";
|
|
5421
|
+
REQUEST_TIMEOUT_MS = 15e3;
|
|
5422
|
+
SDK_REQUEST_TIMEOUT_MS = 5 * 6e4;
|
|
5423
|
+
AutonomaClient = class {
|
|
5424
|
+
/** Status reads and other calls the API answers on its own. */
|
|
5425
|
+
trpc;
|
|
5426
|
+
/** Calls that reach through the API into the customer's deployed app. */
|
|
5427
|
+
slowTrpc;
|
|
5428
|
+
constructor(apiUrl, apiToken) {
|
|
5429
|
+
this.trpc = buildClient(apiUrl, apiToken, REQUEST_TIMEOUT_MS);
|
|
5430
|
+
this.slowTrpc = buildClient(apiUrl, apiToken, SDK_REQUEST_TIMEOUT_MS);
|
|
5431
|
+
}
|
|
5432
|
+
/** Onboarding state for the app, as the platform currently sees it. */
|
|
5433
|
+
async getOnboardingState(applicationId) {
|
|
5434
|
+
debugLog("Reading onboarding state", { applicationId });
|
|
5435
|
+
const state = await this.trpc.onboarding.getState.query({ applicationId });
|
|
5436
|
+
captureLog("info", "Read onboarding state", {
|
|
5437
|
+
source: "onboarding",
|
|
5438
|
+
step: state.step,
|
|
5439
|
+
preview_mode: state.previewEnvironmentMode ?? "unset",
|
|
5440
|
+
sdk_configured: state.sdkConfigured,
|
|
5441
|
+
dry_run_passed: state.dryRunPassed
|
|
5442
|
+
});
|
|
5443
|
+
return state;
|
|
5444
|
+
}
|
|
5445
|
+
/**
|
|
5446
|
+
* Re-check whether this app's preview environment is up.
|
|
5447
|
+
*
|
|
5448
|
+
* Called for its SIDE EFFECT as much as its answer: reading readiness is what
|
|
5449
|
+
* stamps a preview that has come up as `preview_verified`, and nothing else does.
|
|
5450
|
+
* `getOnboardingState` only reports the step someone else already stamped, so a
|
|
5451
|
+
* preview that goes ready after the agent stops polling would never be noticed by
|
|
5452
|
+
* a caller watching the step alone.
|
|
5453
|
+
*/
|
|
5454
|
+
async refreshPreviewReadiness(applicationId) {
|
|
5455
|
+
debugLog("Refreshing preview readiness", { applicationId });
|
|
5456
|
+
await this.trpc.onboarding.getPreviewReadiness.query({ applicationId });
|
|
5457
|
+
}
|
|
5458
|
+
/**
|
|
5459
|
+
* Take the app live: from a verified preview through to Autonoma reviewing its
|
|
5460
|
+
* pull requests. Idempotent, and called without asking whether anyone already did.
|
|
5461
|
+
*
|
|
5462
|
+
* The CLI does this itself rather than leaving it to the coding agent, for the
|
|
5463
|
+
* same reason it makes the SDK and dry-run calls itself: no judgement is involved,
|
|
5464
|
+
* and asking an agent to make it is one more way for it not to happen. Here that
|
|
5465
|
+
* is not hypothetical - the preview phase stops the agent as soon as the platform
|
|
5466
|
+
* reports the preview verified, which is the exact moment the agent would have
|
|
5467
|
+
* gone live. Left to the agent, an app finishes a whole run one step short.
|
|
5468
|
+
*/
|
|
5469
|
+
async takeAppLive(applicationId) {
|
|
5470
|
+
debugLog("Taking the app live", { applicationId });
|
|
5471
|
+
const result = await this.trpc.onboarding.takeLive.mutate({ applicationId });
|
|
5472
|
+
captureLog("info", "Took the app live", {
|
|
5473
|
+
source: "onboarding",
|
|
5474
|
+
step: result.step,
|
|
5475
|
+
already_live: result.alreadyLive
|
|
5476
|
+
});
|
|
5477
|
+
return { alreadyLive: result.alreadyLive, step: result.step };
|
|
5478
|
+
}
|
|
5479
|
+
/**
|
|
5480
|
+
* Mint a single-use pairing code for a coding agent this CLI is about to spawn.
|
|
5481
|
+
* Codes expire, so mint one per handoff rather than reusing an earlier one.
|
|
5482
|
+
*/
|
|
5483
|
+
async createAgentPairing(applicationId) {
|
|
5484
|
+
debugLog("Minting agent pairing code", { applicationId });
|
|
5485
|
+
const { code } = await this.trpc.onboarding.createAgentPairing.mutate({ applicationId });
|
|
5486
|
+
captureLog("info", "Minted agent pairing code", { source: "onboarding" });
|
|
5487
|
+
return code;
|
|
5488
|
+
}
|
|
5489
|
+
/**
|
|
5490
|
+
* The preview environments the SDK validation and the dry run can run against:
|
|
5491
|
+
* the app's main preview plus one per open pull request, each carrying whether it
|
|
5492
|
+
* is deployed yet and which one the platform recognizes as the SDK handler's PR.
|
|
5493
|
+
*/
|
|
5494
|
+
async listDryRunTargets(applicationId) {
|
|
5495
|
+
debugLog("Listing SDK dry-run targets", { applicationId });
|
|
5496
|
+
const targets = await this.trpc.onboarding.listSdkDryRunTargets.query({ applicationId });
|
|
5497
|
+
captureLog("info", "Listed SDK dry-run targets", {
|
|
5498
|
+
source: "dry_run",
|
|
5499
|
+
target_count: targets.targets.length,
|
|
5500
|
+
auto_detected: targets.autoDetectedTargetId != null
|
|
5501
|
+
});
|
|
5502
|
+
return targets;
|
|
5503
|
+
}
|
|
5504
|
+
/**
|
|
5505
|
+
* Provision a managed preview's Autonoma secrets so it can be validated. Returns
|
|
5506
|
+
* `redeploy_started` when mounting them changed the running app, in which case the
|
|
5507
|
+
* preview has to come back up before anything else is worth trying.
|
|
5508
|
+
*/
|
|
5509
|
+
async prepareSdkTarget(applicationId, targetId) {
|
|
5510
|
+
debugLog("Preparing SDK target", { applicationId, targetId });
|
|
5511
|
+
return this.slowTrpc.onboarding.prepareSdkTarget.mutate({ applicationId, targetId });
|
|
5512
|
+
}
|
|
5513
|
+
/**
|
|
5514
|
+
* Call the app's SDK handler and store the schema it reports.
|
|
5515
|
+
*
|
|
5516
|
+
* `allowSelfHeal` lets the API redeploy once when the handler rejects our
|
|
5517
|
+
* signature, which for a managed preview can only be the platform's own secret
|
|
5518
|
+
* drift. Pass it on the first attempt and not on the retry, so a rejection that
|
|
5519
|
+
* survives a redeploy surfaces instead of looping.
|
|
5520
|
+
*/
|
|
5521
|
+
async configureAndDiscoverSdkTarget(applicationId, targetId, allowSelfHeal) {
|
|
5522
|
+
debugLog("Discovering the SDK schema", { applicationId, targetId, allowSelfHeal });
|
|
5523
|
+
return this.slowTrpc.onboarding.configureAndDiscoverSdkTarget.mutate({
|
|
5524
|
+
applicationId,
|
|
5525
|
+
targetId,
|
|
5526
|
+
allowSelfHeal
|
|
5527
|
+
});
|
|
5528
|
+
}
|
|
5529
|
+
/** The app's scenarios - the named states its tests depend on. */
|
|
5530
|
+
async listScenarios(applicationId) {
|
|
5531
|
+
debugLog("Listing scenarios", { applicationId });
|
|
5532
|
+
return this.trpc.scenarios.list.query({ applicationId });
|
|
5533
|
+
}
|
|
5534
|
+
/**
|
|
5535
|
+
* Provision a scenario against a preview and tear it back down. The platform
|
|
5536
|
+
* records the app's dry run as passed once every scenario completes that cycle.
|
|
5537
|
+
*/
|
|
5538
|
+
async runScenarioDryRun(applicationId, scenarioId, targetId) {
|
|
5539
|
+
debugLog("Running a scenario dry run", { applicationId, scenarioId, targetId });
|
|
5540
|
+
return this.slowTrpc.onboarding.runScenarioDryRun.mutate({ applicationId, scenarioId, targetId });
|
|
5541
|
+
}
|
|
5542
|
+
/**
|
|
5543
|
+
* Take the onboarding config mutex for this run, so the web app stops offering the
|
|
5544
|
+
* steps this run is about to do and points the user at their terminal instead.
|
|
5545
|
+
*
|
|
5546
|
+
* This is the same mutation behind the UI's "hand it back to the agent" control:
|
|
5547
|
+
* handing the mutex over is one state change whoever asks for it, and a second
|
|
5548
|
+
* procedure that set the same column would only be a way for the two to drift.
|
|
5549
|
+
*
|
|
5550
|
+
* Unconditional by design - a user starting this run IS the handoff, including
|
|
5551
|
+
* after an earlier take-over. What makes take-over stick is that this is called
|
|
5552
|
+
* once at the start of a run rather than polled, so a user who takes the config
|
|
5553
|
+
* back mid-run keeps it.
|
|
5554
|
+
*/
|
|
5555
|
+
async claimAgentHold(applicationId) {
|
|
5556
|
+
debugLog("Claiming the onboarding config for this run", { applicationId });
|
|
5557
|
+
await this.trpc.onboarding.resumeAgent.mutate({ applicationId });
|
|
5558
|
+
captureLog("info", "Claimed the onboarding config for this run", { source: "onboarding" });
|
|
5559
|
+
}
|
|
5560
|
+
};
|
|
5561
|
+
}
|
|
5562
|
+
});
|
|
5563
|
+
|
|
5564
|
+
// src/core/coding-agent.ts
|
|
5565
|
+
var coding_agent_exports = {};
|
|
5566
|
+
__export(coding_agent_exports, {
|
|
5567
|
+
BaseLauncher: () => BaseLauncher,
|
|
5568
|
+
ClaudeLauncher: () => ClaudeLauncher,
|
|
5569
|
+
CodexLauncher: () => CodexLauncher,
|
|
5570
|
+
DEFAULT_PERMISSION_MODE: () => DEFAULT_PERMISSION_MODE,
|
|
5571
|
+
PERMISSION_MODE_LABELS: () => PERMISSION_MODE_LABELS,
|
|
5572
|
+
SPAWNED_BY_PLANNER_ENV: () => SPAWNED_BY_PLANNER_ENV,
|
|
5573
|
+
buildAllLaunchers: () => buildAllLaunchers,
|
|
5574
|
+
isSpawnedByPlanner: () => isSpawnedByPlanner,
|
|
5575
|
+
parsePermissionMode: () => parsePermissionMode,
|
|
5576
|
+
selectLauncher: () => selectLauncher,
|
|
5577
|
+
selectPermissionMode: () => selectPermissionMode
|
|
5578
|
+
});
|
|
5579
|
+
import spawn from "cross-spawn";
|
|
5580
|
+
import which from "which";
|
|
5581
|
+
function isSpawnedByPlanner(env = process.env) {
|
|
5582
|
+
return env[SPAWNED_BY_PLANNER_ENV] != null && env[SPAWNED_BY_PLANNER_ENV] !== "";
|
|
5583
|
+
}
|
|
5584
|
+
function buildAllLaunchers(opts) {
|
|
5585
|
+
return [new ClaudeLauncher(opts), new CodexLauncher(opts)];
|
|
5586
|
+
}
|
|
5587
|
+
async function selectLauncher(launchers, presetId, interactive = true) {
|
|
5588
|
+
const availability = await Promise.all(launchers.map((l) => l.isAvailable()));
|
|
5589
|
+
const available = launchers.filter((_, i) => availability[i]);
|
|
5590
|
+
debugLog("Detected available agents", { available: available.map((l) => l.id), presetId, interactive });
|
|
5591
|
+
if (presetId != null) {
|
|
5592
|
+
const preset = available.find((l) => l.id === presetId);
|
|
5593
|
+
if (preset != null) return preset;
|
|
5594
|
+
log.warn(`Requested agent "${presetId}" is not installed or not supported.`);
|
|
5595
|
+
}
|
|
5596
|
+
if (available.length === 0) return void 0;
|
|
5597
|
+
if (available.length === 1) {
|
|
5598
|
+
const only = available[0];
|
|
5599
|
+
log.info(`Found ${only.label} - will use it for the integration.`);
|
|
5600
|
+
return only;
|
|
5601
|
+
}
|
|
5602
|
+
if (!interactive) {
|
|
5603
|
+
const first = available[0];
|
|
5604
|
+
log.warn(
|
|
5605
|
+
`Several coding agents are installed and there is nobody to ask - using ${first.label}. Pass --agent to choose.`
|
|
5606
|
+
);
|
|
5607
|
+
return first;
|
|
5608
|
+
}
|
|
5609
|
+
const selected = await select({
|
|
5610
|
+
message: "Which agent should implement the integration?",
|
|
5611
|
+
options: available.map((l) => ({ value: l.id, label: l.label }))
|
|
5612
|
+
});
|
|
5613
|
+
if (isCancel(selected)) throw new Error("Agent selection cancelled");
|
|
5614
|
+
return available.find((l) => l.id === selected);
|
|
5615
|
+
}
|
|
5616
|
+
async function selectPermissionMode(preset) {
|
|
5617
|
+
if (preset != null) return preset;
|
|
5618
|
+
const selected = await select({
|
|
5619
|
+
message: "How much autonomy should the agent have?",
|
|
5620
|
+
options: [
|
|
5621
|
+
{ value: "bypassPermissions", label: PERMISSION_MODE_LABELS.bypassPermissions },
|
|
5622
|
+
{ value: "acceptEdits", label: PERMISSION_MODE_LABELS.acceptEdits },
|
|
5623
|
+
{ value: "default", label: PERMISSION_MODE_LABELS.default }
|
|
5624
|
+
],
|
|
5625
|
+
initialValue: DEFAULT_PERMISSION_MODE
|
|
5626
|
+
});
|
|
5627
|
+
if (isCancel(selected)) throw new Error("Permission mode selection cancelled");
|
|
5628
|
+
return selected;
|
|
5629
|
+
}
|
|
5630
|
+
function parsePermissionMode(value) {
|
|
5631
|
+
if (value === "default" || value === "acceptEdits" || value === "bypassPermissions") return value;
|
|
5632
|
+
return void 0;
|
|
5633
|
+
}
|
|
5634
|
+
function firstLine(output) {
|
|
5635
|
+
return output.split("\n").find((line) => line.trim().length > 0)?.trim() ?? "";
|
|
5636
|
+
}
|
|
5637
|
+
var MCP_COMMAND_TIMEOUT_MS, SPAWNED_BY_PLANNER_ENV, CODEX_BEARER_TOKEN_ENV, CLAUDE_CONNECTED_MARKER, DEFAULT_PERMISSION_MODE, PERMISSION_MODE_LABELS, CLAUDE_ID, CODEX_ID, BaseLauncher, ClaudeLauncher, CodexLauncher;
|
|
5638
|
+
var init_coding_agent = __esm({
|
|
5639
|
+
"src/core/coding-agent.ts"() {
|
|
5640
|
+
"use strict";
|
|
5641
|
+
init_esm_shims();
|
|
5642
|
+
init_prompts();
|
|
5643
|
+
init_debug();
|
|
5644
|
+
MCP_COMMAND_TIMEOUT_MS = 6e4;
|
|
5645
|
+
SPAWNED_BY_PLANNER_ENV = "AUTONOMA_PLANNER_SPAWNED_AGENT";
|
|
5646
|
+
CODEX_BEARER_TOKEN_ENV = "AUTONOMA_MCP_TOKEN";
|
|
5647
|
+
CLAUDE_CONNECTED_MARKER = "Connected";
|
|
5648
|
+
DEFAULT_PERMISSION_MODE = "bypassPermissions";
|
|
5649
|
+
PERMISSION_MODE_LABELS = {
|
|
5650
|
+
default: "Approve each command",
|
|
5651
|
+
acceptEdits: "Auto-edit files, approve commands",
|
|
5652
|
+
bypassPermissions: "Fully autonomous"
|
|
5653
|
+
};
|
|
5654
|
+
CLAUDE_ID = "claude";
|
|
5655
|
+
CODEX_ID = "codex";
|
|
5656
|
+
BaseLauncher = class {
|
|
5657
|
+
constructor(opts) {
|
|
5658
|
+
this.opts = opts;
|
|
5659
|
+
}
|
|
5660
|
+
async isAvailable() {
|
|
5661
|
+
const resolved = await which(this.command, { nothrow: true });
|
|
5662
|
+
debugLog("Probed for agent on PATH", { command: this.command, found: resolved != null });
|
|
5663
|
+
return resolved != null;
|
|
5664
|
+
}
|
|
5665
|
+
async launch(request) {
|
|
5666
|
+
if (isSpawnedByPlanner(this.opts.env)) {
|
|
5667
|
+
throw new Error(
|
|
5668
|
+
`Refusing to launch ${this.label}: this planner run was itself started by an agent the planner spawned (${SPAWNED_BY_PLANNER_ENV} is set). Run the planner directly instead.`
|
|
5669
|
+
);
|
|
5670
|
+
}
|
|
5671
|
+
debugLog(`Launching ${this.label}`, {
|
|
5672
|
+
permissionMode: request.permissionMode,
|
|
5673
|
+
interactive: request.interactive
|
|
5674
|
+
});
|
|
5675
|
+
const args = this.buildArgs(request.message, request.permissionMode, request.interactive);
|
|
5676
|
+
const stdio = request.interactive ? "inherit" : ["ignore", "inherit", "inherit"];
|
|
5677
|
+
return new Promise((resolve6) => {
|
|
5678
|
+
const proc = spawn(this.command, args, {
|
|
5679
|
+
cwd: this.opts.cwd,
|
|
5680
|
+
env: this.spawnEnv(request.env),
|
|
5681
|
+
stdio
|
|
5682
|
+
});
|
|
5683
|
+
const stopWatching = request.watch?.(proc) ?? (() => {
|
|
5684
|
+
});
|
|
5685
|
+
proc.on("error", (err) => {
|
|
5686
|
+
debugLog(`${this.label} failed to spawn`, { err });
|
|
5687
|
+
log.error(`Couldn't launch ${this.label}: ${err.message}`);
|
|
5688
|
+
stopWatching();
|
|
5689
|
+
resolve6(void 0);
|
|
5690
|
+
});
|
|
5691
|
+
proc.on("close", (code) => {
|
|
5692
|
+
debugLog(`${this.label} exited`, { code });
|
|
5693
|
+
stopWatching();
|
|
5694
|
+
resolve6(code ?? void 0);
|
|
5695
|
+
});
|
|
5696
|
+
});
|
|
5697
|
+
}
|
|
5698
|
+
/**
|
|
5699
|
+
* The env the agent runs with: what the CLI was given (so the canonical shared
|
|
5700
|
+
* secret reaches the app and the signed `sdk` calls), whatever this run adds
|
|
5701
|
+
* (an MCP registration's token), plus the recursion marker.
|
|
5702
|
+
*/
|
|
5703
|
+
spawnEnv(extra) {
|
|
5704
|
+
return { ...this.opts.env, ...extra, [SPAWNED_BY_PLANNER_ENV]: "1" };
|
|
5705
|
+
}
|
|
5706
|
+
/**
|
|
5707
|
+
* Run one of this client's own subcommands and capture its output. Never attached
|
|
5708
|
+
* to the terminal: these are configuration calls whose result the CLI inspects,
|
|
5709
|
+
* not the handoff itself.
|
|
5710
|
+
*/
|
|
5711
|
+
runCommand(args) {
|
|
5712
|
+
debugLog(`Running ${this.command} ${args[0] ?? ""}`, { args });
|
|
5713
|
+
return new Promise((resolve6) => {
|
|
5714
|
+
const proc = spawn(this.command, args, {
|
|
5715
|
+
cwd: this.opts.cwd,
|
|
5716
|
+
env: this.opts.env,
|
|
5717
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5718
|
+
timeout: MCP_COMMAND_TIMEOUT_MS
|
|
5719
|
+
});
|
|
5720
|
+
let stdout = "";
|
|
5721
|
+
let stderr = "";
|
|
5722
|
+
proc.stdout?.on("data", (chunk) => stdout += chunk.toString());
|
|
5723
|
+
proc.stderr?.on("data", (chunk) => stderr += chunk.toString());
|
|
5724
|
+
proc.on("error", (err) => {
|
|
5725
|
+
debugLog(`${this.command} ${args[0] ?? ""} failed to spawn`, { err });
|
|
5726
|
+
resolve6({ code: void 0, stdout, stderr: err.message });
|
|
5727
|
+
});
|
|
5728
|
+
proc.on("close", (code) => resolve6({ code: code ?? void 0, stdout, stderr }));
|
|
5729
|
+
});
|
|
5730
|
+
}
|
|
5731
|
+
/**
|
|
5732
|
+
* Sign this client in to a server through its own browser flow.
|
|
5733
|
+
*
|
|
5734
|
+
* Refused outright without a terminal. The flow prints a URL, opens a browser and
|
|
5735
|
+
* blocks on the callback, so a run with nobody at the keyboard would not fail - it
|
|
5736
|
+
* would hang, indefinitely, on a prompt nobody can see. A run without a terminal
|
|
5737
|
+
* is meant to have passed an API token instead, and the error says so rather than
|
|
5738
|
+
* describing the sign-in that could not happen.
|
|
5739
|
+
*/
|
|
5740
|
+
async signIn(serverName) {
|
|
5741
|
+
if (process.stdin.isTTY !== true) {
|
|
5742
|
+
throw new Error(
|
|
5743
|
+
`Cannot sign ${this.label} in to the ${serverName} MCP server without a terminal: that flow opens a browser and waits for it. Set AUTONOMA_API_TOKEN so the run authorizes with an API key instead.`
|
|
5744
|
+
);
|
|
5745
|
+
}
|
|
5746
|
+
log.info(`Authorizing ${this.label} with Autonoma - approve it in the browser that opens.`);
|
|
5747
|
+
const login = await this.runAttached(["mcp", "login", serverName]);
|
|
5748
|
+
if (login !== 0) {
|
|
5749
|
+
throw new Error(
|
|
5750
|
+
`${this.label} could not sign in to the ${serverName} MCP server (exit ${login ?? "unknown"}).`
|
|
5751
|
+
);
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
/**
|
|
5755
|
+
* Run a subcommand that hands the terminal over - an interactive OAuth sign-in.
|
|
5756
|
+
* The client prints a URL, opens a browser and waits on the callback, so it needs
|
|
5757
|
+
* this process's stdio rather than pipes.
|
|
5758
|
+
*/
|
|
5759
|
+
runAttached(args) {
|
|
5760
|
+
debugLog(`Running ${this.command} ${args.join(" ")} attached`);
|
|
5761
|
+
return new Promise((resolve6) => {
|
|
5762
|
+
const proc = spawn(this.command, args, { cwd: this.opts.cwd, env: this.opts.env, stdio: "inherit" });
|
|
5763
|
+
proc.on("error", (err) => {
|
|
5764
|
+
debugLog(`${this.command} ${args[0] ?? ""} failed to spawn`, { err });
|
|
5765
|
+
resolve6(void 0);
|
|
5766
|
+
});
|
|
5767
|
+
proc.on("close", (code) => resolve6(code ?? void 0));
|
|
5768
|
+
});
|
|
5769
|
+
}
|
|
5770
|
+
};
|
|
5771
|
+
ClaudeLauncher = class extends BaseLauncher {
|
|
5772
|
+
id = CLAUDE_ID;
|
|
5773
|
+
label = "Claude Code";
|
|
5774
|
+
command = CLAUDE_ID;
|
|
5775
|
+
buildArgs(message, permissionMode, interactive) {
|
|
5776
|
+
return interactive ? ["--permission-mode", permissionMode, message] : ["-p", message, "--permission-mode", permissionMode, "--verbose"];
|
|
5777
|
+
}
|
|
5778
|
+
/**
|
|
5779
|
+
* `--scope user` and not the default (`local`): the default binds the server to the
|
|
5780
|
+
* directory the command ran in. The planner can be invoked from anywhere in a repo,
|
|
5781
|
+
* and a server registered against the wrong directory looks exactly like one that
|
|
5782
|
+
* was never authorized - the agent simply has no Autonoma tools.
|
|
5783
|
+
*
|
|
5784
|
+
* With a token the header goes into Claude's own config and there is nothing to
|
|
5785
|
+
* sign in to. Without one, the browser sign-in runs attached to the terminal.
|
|
5786
|
+
*/
|
|
5787
|
+
async registerMcpServer(spec) {
|
|
5788
|
+
const add = ["mcp", "add", "--transport", "http", "--scope", "user", spec.name, spec.url];
|
|
5789
|
+
if (spec.apiToken != null) add.push("--header", `Authorization: Bearer ${spec.apiToken}`);
|
|
5790
|
+
const existing = await this.runCommand(["mcp", "get", spec.name]);
|
|
5791
|
+
if (existing.code === 0 && !existing.stdout.includes(spec.url)) {
|
|
5792
|
+
debugLog("Replacing an MCP registration that points elsewhere", { server: spec.name, url: spec.url });
|
|
5793
|
+
await this.runCommand(["mcp", "remove", spec.name, "-s", "user"]);
|
|
5794
|
+
}
|
|
5795
|
+
await this.runCommand(add);
|
|
5796
|
+
const registered = await this.runCommand(["mcp", "get", spec.name]);
|
|
5797
|
+
if (registered.code !== 0) {
|
|
5798
|
+
throw new Error(
|
|
5799
|
+
`Could not register the ${spec.name} MCP server with ${this.label}: ${firstLine(registered.stderr) || firstLine(registered.stdout) || "unknown error"}`
|
|
5800
|
+
);
|
|
5801
|
+
}
|
|
5802
|
+
if (spec.apiToken == null && !registered.stdout.includes(CLAUDE_CONNECTED_MARKER)) {
|
|
5803
|
+
await this.signIn(spec.name);
|
|
5804
|
+
}
|
|
5805
|
+
return { env: {} };
|
|
4997
5806
|
}
|
|
4998
|
-
|
|
4999
|
-
|
|
5807
|
+
};
|
|
5808
|
+
CodexLauncher = class extends BaseLauncher {
|
|
5809
|
+
id = CODEX_ID;
|
|
5810
|
+
label = "Codex CLI";
|
|
5811
|
+
command = CODEX_ID;
|
|
5812
|
+
/**
|
|
5813
|
+
* Codex's autonomy is two orthogonal axes - `--sandbox` (what it may touch) and
|
|
5814
|
+
* `--ask-for-approval` (when it pauses) - so we translate the shared,
|
|
5815
|
+
* Claude-flavoured `PermissionMode` onto them.
|
|
5816
|
+
*
|
|
5817
|
+
* The handoff's whole job is to install the SDK, boot the app, and validate
|
|
5818
|
+
* against a live DB, so the sandbox is always `danger-full-access` (Codex's
|
|
5819
|
+
* `workspace-write` disables network, which breaks the install). The only real
|
|
5820
|
+
* knob is approval strictness, which exists only interactively: headless `exec`
|
|
5821
|
+
* can't prompt, so `default`/`acceptEdits` collapse to the same autonomous run.
|
|
5822
|
+
*/
|
|
5823
|
+
buildArgs(message, permissionMode, interactive) {
|
|
5824
|
+
if (permissionMode === "bypassPermissions") {
|
|
5825
|
+
const bypass = ["--dangerously-bypass-approvals-and-sandbox"];
|
|
5826
|
+
return interactive ? [...bypass, message] : ["exec", ...bypass, message];
|
|
5827
|
+
}
|
|
5828
|
+
const sandbox = ["--sandbox", "danger-full-access"];
|
|
5829
|
+
if (!interactive) return ["exec", ...sandbox, message];
|
|
5830
|
+
const approval = permissionMode === "acceptEdits" ? "on-failure" : "untrusted";
|
|
5831
|
+
return [...sandbox, "--ask-for-approval", approval, message];
|
|
5000
5832
|
}
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5833
|
+
/**
|
|
5834
|
+
* Codex speaks streamable HTTP natively, so this needs no `mcp-remote` bridge.
|
|
5835
|
+
* `--bearer-token-env-var` stores the NAME of the variable rather than the token,
|
|
5836
|
+
* which is why the registration hands an env back for the spawn to carry.
|
|
5837
|
+
*
|
|
5838
|
+
* `mcp add` is idempotent here in the way that matters: re-adding an existing
|
|
5839
|
+
* server overwrites its entry, so a re-run cannot leave a stale URL behind.
|
|
5840
|
+
*/
|
|
5841
|
+
async registerMcpServer(spec) {
|
|
5842
|
+
const add = ["mcp", "add", spec.name, "--url", spec.url];
|
|
5843
|
+
if (spec.apiToken != null) add.push("--bearer-token-env-var", CODEX_BEARER_TOKEN_ENV);
|
|
5844
|
+
const added = await this.runCommand(add);
|
|
5845
|
+
if (added.code !== 0) {
|
|
5846
|
+
throw new Error(
|
|
5847
|
+
`Could not register the ${spec.name} MCP server with ${this.label}: ${firstLine(added.stderr) || firstLine(added.stdout) || "unknown error"}`
|
|
5848
|
+
);
|
|
5015
5849
|
}
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5022
|
-
properties: {
|
|
5023
|
-
$session_id: this.config.sessionId,
|
|
5024
|
-
$window_id: this.config.windowId,
|
|
5025
|
-
$snapshot_data: events,
|
|
5026
|
-
$snapshot_bytes: bytes,
|
|
5027
|
-
// The player treats a recording as web; the synthesized DOM is
|
|
5028
|
-
// a real DOM as far as it is concerned.
|
|
5029
|
-
$lib: "web",
|
|
5030
|
-
$lib_version: "1.0.0"
|
|
5031
|
-
}
|
|
5032
|
-
}
|
|
5033
|
-
]);
|
|
5034
|
-
const promise = fetch(`${this.config.host}/s/`, {
|
|
5035
|
-
method: "POST",
|
|
5036
|
-
headers: { "Content-Type": "application/json" },
|
|
5037
|
-
body
|
|
5038
|
-
}).catch((err) => {
|
|
5039
|
-
debugLog("Session replay upload failed (ignored)", { err });
|
|
5040
|
-
}).finally(() => pending2.delete(promise));
|
|
5041
|
-
pending2.add(promise);
|
|
5850
|
+
if (spec.apiToken == null) {
|
|
5851
|
+
await this.signIn(spec.name);
|
|
5852
|
+
return { env: {} };
|
|
5853
|
+
}
|
|
5854
|
+
return { env: { [CODEX_BEARER_TOKEN_ENV]: spec.apiToken } };
|
|
5042
5855
|
}
|
|
5043
5856
|
};
|
|
5044
5857
|
}
|
|
@@ -5216,6 +6029,309 @@ var init_interrupt = __esm({
|
|
|
5216
6029
|
}
|
|
5217
6030
|
});
|
|
5218
6031
|
|
|
6032
|
+
// src/core/preview-phase.ts
|
|
6033
|
+
function previewPrompt(code, interactive) {
|
|
6034
|
+
const instruction = `set up my preview environments with the ${MCP_SERVER_NAME} MCP, code ${code}`;
|
|
6035
|
+
return interactive ? instruction : `${instruction}.${HEADLESS_GUIDANCE}`;
|
|
6036
|
+
}
|
|
6037
|
+
async function runPreviewPhase(deps) {
|
|
6038
|
+
const timing = deps.timing ?? DEFAULT_PHASE_TIMING;
|
|
6039
|
+
log.info(`Connecting ${deps.launcher.label} to Autonoma...`);
|
|
6040
|
+
const registration = await deps.launcher.registerMcpServer({
|
|
6041
|
+
name: MCP_SERVER_NAME,
|
|
6042
|
+
url: deps.mcpUrl,
|
|
6043
|
+
apiToken: deps.apiToken
|
|
6044
|
+
});
|
|
6045
|
+
const code = await deps.client.createAgentPairing(deps.applicationId);
|
|
6046
|
+
captureLog("info", "Handing the preview environment to a coding agent", {
|
|
6047
|
+
source: "preview_phase",
|
|
6048
|
+
agent: deps.launcher.id
|
|
6049
|
+
});
|
|
6050
|
+
log.info("Your agent is setting up your preview environment - watch it work, and steer it if you want to.");
|
|
6051
|
+
await deps.launcher.launch({
|
|
6052
|
+
message: previewPrompt(code, deps.interactive),
|
|
6053
|
+
permissionMode: deps.permissionMode,
|
|
6054
|
+
interactive: deps.interactive,
|
|
6055
|
+
env: registration.env,
|
|
6056
|
+
// Both modes, not just interactive. An interactive session plainly never exits
|
|
6057
|
+
// on its own - it sits open after its final message - but a headless one is no
|
|
6058
|
+
// safer to wait on: observed in practice finishing the actual work and then
|
|
6059
|
+
// continuing to potter, with the run blocked behind it. Onboarding state is
|
|
6060
|
+
// what says "done", so it ends the handoff either way.
|
|
6061
|
+
watch: (proc) => watchForPreviewPhase(deps.client, deps.applicationId, proc, timing)
|
|
6062
|
+
});
|
|
6063
|
+
const state = await readVerifiedState(deps.client, deps.applicationId);
|
|
6064
|
+
if (isStepAtOrPast(state.step, PREVIEW_DONE_STEP)) {
|
|
6065
|
+
log.success("Your preview environment is up.");
|
|
6066
|
+
captureLog("info", "Preview phase complete", { source: "preview_phase", step: state.step });
|
|
6067
|
+
return { kind: "verified" };
|
|
6068
|
+
}
|
|
6069
|
+
captureLog("warn", "Preview phase ended without a verified preview", {
|
|
6070
|
+
source: "preview_phase",
|
|
6071
|
+
step: state.step
|
|
6072
|
+
});
|
|
6073
|
+
return { kind: "incomplete", step: state.step };
|
|
6074
|
+
}
|
|
6075
|
+
function watchForPreviewPhase(client, applicationId, proc, timing = DEFAULT_PHASE_TIMING) {
|
|
6076
|
+
let graceTimer;
|
|
6077
|
+
let killTimer;
|
|
6078
|
+
const poll = setInterval(() => {
|
|
6079
|
+
void readVerifiedState(client, applicationId).then((state) => {
|
|
6080
|
+
if (!isStepAtOrPast(state.step, PREVIEW_DONE_STEP) || graceTimer != null) return;
|
|
6081
|
+
debugLog("Preview verified while the agent runs; scheduling terminal reclaim", { step: state.step });
|
|
6082
|
+
clearInterval(poll);
|
|
6083
|
+
graceTimer = setTimeout(() => {
|
|
6084
|
+
proc.kill("SIGTERM");
|
|
6085
|
+
killTimer = setTimeout(() => proc.kill("SIGKILL"), timing.killMs);
|
|
6086
|
+
}, timing.graceMs);
|
|
6087
|
+
}).catch((err) => {
|
|
6088
|
+
debugLog("Could not read onboarding state while the agent runs", { err });
|
|
6089
|
+
});
|
|
6090
|
+
}, timing.pollMs);
|
|
6091
|
+
return () => {
|
|
6092
|
+
clearInterval(poll);
|
|
6093
|
+
if (graceTimer != null) clearTimeout(graceTimer);
|
|
6094
|
+
if (killTimer != null) clearTimeout(killTimer);
|
|
6095
|
+
};
|
|
6096
|
+
}
|
|
6097
|
+
async function readVerifiedState(client, applicationId) {
|
|
6098
|
+
await client.refreshPreviewReadiness(applicationId).catch((err) => {
|
|
6099
|
+
debugLog("Could not refresh preview readiness", { err });
|
|
6100
|
+
});
|
|
6101
|
+
return client.getOnboardingState(applicationId);
|
|
6102
|
+
}
|
|
6103
|
+
var MCP_SERVER_NAME, PHASE_POLL_MS, PHASE_EXIT_GRACE_MS, KILL_ESCALATION_MS, DEFAULT_PHASE_TIMING, HEADLESS_GUIDANCE;
|
|
6104
|
+
var init_preview_phase = __esm({
|
|
6105
|
+
"src/core/preview-phase.ts"() {
|
|
6106
|
+
"use strict";
|
|
6107
|
+
init_esm_shims();
|
|
6108
|
+
init_prompts();
|
|
6109
|
+
init_debug();
|
|
6110
|
+
init_logs();
|
|
6111
|
+
init_onboarding_phase();
|
|
6112
|
+
MCP_SERVER_NAME = "autonoma";
|
|
6113
|
+
PHASE_POLL_MS = 5e3;
|
|
6114
|
+
PHASE_EXIT_GRACE_MS = 15e3;
|
|
6115
|
+
KILL_ESCALATION_MS = 1e4;
|
|
6116
|
+
DEFAULT_PHASE_TIMING = {
|
|
6117
|
+
pollMs: PHASE_POLL_MS,
|
|
6118
|
+
graceMs: PHASE_EXIT_GRACE_MS,
|
|
6119
|
+
killMs: KILL_ESCALATION_MS
|
|
6120
|
+
};
|
|
6121
|
+
HEADLESS_GUIDANCE = " You are running with no human to answer you, so where you would ask a question, choose the safer option and say which you chose. You are not finished when you have tried - you are finished when the preview is verified. If a deploy fails, read its logs, fix the cause and deploy again. Never report success you have not confirmed.";
|
|
6122
|
+
}
|
|
6123
|
+
});
|
|
6124
|
+
|
|
6125
|
+
// src/core/sdk-repair-phase.ts
|
|
6126
|
+
function repairPrompt(code, interactive) {
|
|
6127
|
+
const instruction = `use the ${MCP_SERVER_NAME} MCP, code ${code}, to get my Autonoma SDK answering and my scenario dry run passing - fix whatever is in the way, in this repo or in the config, and keep going until Autonoma reports both`;
|
|
6128
|
+
return interactive ? instruction : `${instruction}.${HEADLESS_GUIDANCE2}`;
|
|
6129
|
+
}
|
|
6130
|
+
function isDone(state) {
|
|
6131
|
+
return state.sdkConfigured && state.dryRunPassed;
|
|
6132
|
+
}
|
|
6133
|
+
async function runSdkRepairPhase(deps) {
|
|
6134
|
+
const timing = deps.timing ?? DEFAULT_PHASE_TIMING;
|
|
6135
|
+
const before = await deps.client.getOnboardingState(deps.applicationId);
|
|
6136
|
+
if (isDone(before)) return { kind: "passed" };
|
|
6137
|
+
log.info(`Connecting ${deps.launcher.label} to Autonoma...`);
|
|
6138
|
+
const registration = await deps.launcher.registerMcpServer({
|
|
6139
|
+
name: MCP_SERVER_NAME,
|
|
6140
|
+
url: deps.mcpUrl,
|
|
6141
|
+
apiToken: deps.apiToken
|
|
6142
|
+
});
|
|
6143
|
+
const code = await deps.client.createAgentPairing(deps.applicationId);
|
|
6144
|
+
captureLog("info", "Handing the SDK and dry run to a coding agent", {
|
|
6145
|
+
source: "sdk_repair_phase",
|
|
6146
|
+
agent: deps.launcher.id,
|
|
6147
|
+
sdk_configured: before.sdkConfigured,
|
|
6148
|
+
dry_run_passed: before.dryRunPassed
|
|
6149
|
+
});
|
|
6150
|
+
log.info("Your agent is getting your test data working - watch it, and steer it if you want to.");
|
|
6151
|
+
await deps.launcher.launch({
|
|
6152
|
+
message: repairPrompt(code, deps.interactive),
|
|
6153
|
+
permissionMode: deps.permissionMode,
|
|
6154
|
+
interactive: deps.interactive,
|
|
6155
|
+
env: registration.env,
|
|
6156
|
+
watch: (proc) => watchForSdkRepair(deps.client, deps.applicationId, proc, timing)
|
|
6157
|
+
});
|
|
6158
|
+
const after = await deps.client.getOnboardingState(deps.applicationId);
|
|
6159
|
+
if (isDone(after)) {
|
|
6160
|
+
log.success("Your test data provisions against your preview.");
|
|
6161
|
+
captureLog("info", "SDK repair phase complete", { source: "sdk_repair_phase" });
|
|
6162
|
+
return { kind: "passed" };
|
|
6163
|
+
}
|
|
6164
|
+
captureLog("warn", "SDK repair phase ended with work outstanding", {
|
|
6165
|
+
source: "sdk_repair_phase",
|
|
6166
|
+
sdk_configured: after.sdkConfigured,
|
|
6167
|
+
dry_run_passed: after.dryRunPassed
|
|
6168
|
+
});
|
|
6169
|
+
return { kind: "incomplete", sdkConfigured: after.sdkConfigured, dryRunPassed: after.dryRunPassed };
|
|
6170
|
+
}
|
|
6171
|
+
function watchForSdkRepair(client, applicationId, proc, timing = DEFAULT_PHASE_TIMING) {
|
|
6172
|
+
let graceTimer;
|
|
6173
|
+
let killTimer;
|
|
6174
|
+
const poll = setInterval(() => {
|
|
6175
|
+
void client.getOnboardingState(applicationId).then((state) => {
|
|
6176
|
+
if (!isDone(state) || graceTimer != null) return;
|
|
6177
|
+
debugLog("SDK and dry run both good while the agent runs; scheduling terminal reclaim");
|
|
6178
|
+
clearInterval(poll);
|
|
6179
|
+
graceTimer = setTimeout(() => {
|
|
6180
|
+
proc.kill("SIGTERM");
|
|
6181
|
+
killTimer = setTimeout(() => proc.kill("SIGKILL"), timing.killMs);
|
|
6182
|
+
}, timing.graceMs);
|
|
6183
|
+
}).catch((err) => {
|
|
6184
|
+
debugLog("Could not read onboarding state while the agent runs", { err });
|
|
6185
|
+
});
|
|
6186
|
+
}, timing.pollMs);
|
|
6187
|
+
return () => {
|
|
6188
|
+
clearInterval(poll);
|
|
6189
|
+
if (graceTimer != null) clearTimeout(graceTimer);
|
|
6190
|
+
if (killTimer != null) clearTimeout(killTimer);
|
|
6191
|
+
};
|
|
6192
|
+
}
|
|
6193
|
+
var HEADLESS_GUIDANCE2;
|
|
6194
|
+
var init_sdk_repair_phase = __esm({
|
|
6195
|
+
"src/core/sdk-repair-phase.ts"() {
|
|
6196
|
+
"use strict";
|
|
6197
|
+
init_esm_shims();
|
|
6198
|
+
init_prompts();
|
|
6199
|
+
init_debug();
|
|
6200
|
+
init_logs();
|
|
6201
|
+
init_preview_phase();
|
|
6202
|
+
HEADLESS_GUIDANCE2 = " You are running with no human to answer you, so where you would ask a question, choose the safer option and say which you chose. You are not finished when you have tried - you are finished when Autonoma reports the scenario dry run passed. Never report success you have not confirmed.";
|
|
6203
|
+
}
|
|
6204
|
+
});
|
|
6205
|
+
|
|
6206
|
+
// src/core/front-door.ts
|
|
6207
|
+
var front_door_exports = {};
|
|
6208
|
+
__export(front_door_exports, {
|
|
6209
|
+
describeIncompletePreview: () => describeIncompletePreview,
|
|
6210
|
+
planFrontDoor: () => planFrontDoor,
|
|
6211
|
+
runPreviewHandoff: () => runPreviewHandoff,
|
|
6212
|
+
runSdkRepairHandoff: () => runSdkRepairHandoff
|
|
6213
|
+
});
|
|
6214
|
+
async function planFrontDoor(config) {
|
|
6215
|
+
const { autonomaApplicationId: applicationId, autonomaApiToken: apiToken } = config;
|
|
6216
|
+
if (applicationId == null || apiToken == null) {
|
|
6217
|
+
debugLog("No application id or token; running the pipeline standalone");
|
|
6218
|
+
return void 0;
|
|
6219
|
+
}
|
|
6220
|
+
const client = new AutonomaClient(config.autonomaApiUrl, apiToken);
|
|
6221
|
+
try {
|
|
6222
|
+
const state = await client.getOnboardingState(applicationId);
|
|
6223
|
+
const phase = resolveEntryPhase(state);
|
|
6224
|
+
await claimHoldForRun(client, applicationId, phase);
|
|
6225
|
+
return { client, applicationId, phase };
|
|
6226
|
+
} catch (err) {
|
|
6227
|
+
log.warn("Couldn't read your setup status from Autonoma - continuing with the test-suite run.");
|
|
6228
|
+
debugLog("Front-door planning failed", { err });
|
|
6229
|
+
captureLog("warn", "Could not resolve the onboarding entry phase", { source: "front_door" });
|
|
6230
|
+
return void 0;
|
|
6231
|
+
}
|
|
6232
|
+
}
|
|
6233
|
+
async function claimHoldForRun(client, applicationId, phase) {
|
|
6234
|
+
if (phase === "preview" || phase === "done") return;
|
|
6235
|
+
try {
|
|
6236
|
+
await client.claimAgentHold(applicationId);
|
|
6237
|
+
} catch (err) {
|
|
6238
|
+
debugLog("Could not claim the onboarding config for this run", { err });
|
|
6239
|
+
captureLog("warn", "Could not claim the onboarding config for this run", { source: "front_door" });
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
async function runPreviewHandoff(deps) {
|
|
6243
|
+
const { plan, config, nonInteractive } = deps;
|
|
6244
|
+
const interactive = !nonInteractive;
|
|
6245
|
+
const launchers = deps.launchers ?? buildAllLaunchers({ cwd: config.projectRoot, env: process.env });
|
|
6246
|
+
const launcher = await selectLauncher(launchers, config.agent, interactive);
|
|
6247
|
+
if (launcher == null) {
|
|
6248
|
+
captureLog("warn", "No coding agent available for the preview phase", { source: "front_door" });
|
|
6249
|
+
return { kind: "no-agent" };
|
|
6250
|
+
}
|
|
6251
|
+
const preset = parsePermissionMode(config.permissionMode);
|
|
6252
|
+
const permissionMode = interactive ? await selectPermissionMode(preset) : preset ?? DEFAULT_PERMISSION_MODE;
|
|
6253
|
+
suspend();
|
|
6254
|
+
let outcome;
|
|
6255
|
+
try {
|
|
6256
|
+
outcome = await runPreviewPhase({
|
|
6257
|
+
client: plan.client,
|
|
6258
|
+
applicationId: plan.applicationId,
|
|
6259
|
+
launcher,
|
|
6260
|
+
permissionMode,
|
|
6261
|
+
apiToken: interactive ? void 0 : config.autonomaApiToken,
|
|
6262
|
+
mcpUrl: resolveMcpUrl(config.autonomaApiUrl),
|
|
6263
|
+
interactive
|
|
6264
|
+
});
|
|
6265
|
+
} finally {
|
|
6266
|
+
resume();
|
|
6267
|
+
}
|
|
6268
|
+
if (outcome.kind === "verified") await takeAppLive(plan);
|
|
6269
|
+
return outcome;
|
|
6270
|
+
}
|
|
6271
|
+
async function takeAppLive(plan) {
|
|
6272
|
+
try {
|
|
6273
|
+
const { alreadyLive } = await plan.client.takeAppLive(plan.applicationId);
|
|
6274
|
+
log.success(
|
|
6275
|
+
alreadyLive ? "Autonoma is reviewing your pull requests." : "Autonoma is now reviewing your pull requests."
|
|
6276
|
+
);
|
|
6277
|
+
} catch (err) {
|
|
6278
|
+
debugLog("Could not take the app live", { err });
|
|
6279
|
+
captureLog("warn", "Could not take the app live", { source: "front_door" });
|
|
6280
|
+
log.warn(
|
|
6281
|
+
"Your preview is up, but Autonoma is not reviewing your pull requests yet - take the app live in the Autonoma app to finish."
|
|
6282
|
+
);
|
|
6283
|
+
}
|
|
6284
|
+
}
|
|
6285
|
+
async function runSdkRepairHandoff(deps) {
|
|
6286
|
+
const { plan, config, nonInteractive } = deps;
|
|
6287
|
+
const interactive = !nonInteractive;
|
|
6288
|
+
const launchers = deps.launchers ?? buildAllLaunchers({ cwd: config.projectRoot, env: process.env });
|
|
6289
|
+
const launcher = await selectLauncher(launchers, config.agent, interactive);
|
|
6290
|
+
if (launcher == null) {
|
|
6291
|
+
captureLog("warn", "No coding agent available for the SDK repair phase", { source: "front_door" });
|
|
6292
|
+
return void 0;
|
|
6293
|
+
}
|
|
6294
|
+
const preset = parsePermissionMode(config.permissionMode);
|
|
6295
|
+
const permissionMode = interactive ? await selectPermissionMode(preset) : preset ?? DEFAULT_PERMISSION_MODE;
|
|
6296
|
+
suspend();
|
|
6297
|
+
try {
|
|
6298
|
+
return await runSdkRepairPhase({
|
|
6299
|
+
client: plan.client,
|
|
6300
|
+
applicationId: plan.applicationId,
|
|
6301
|
+
launcher,
|
|
6302
|
+
permissionMode,
|
|
6303
|
+
apiToken: interactive ? void 0 : config.autonomaApiToken,
|
|
6304
|
+
mcpUrl: resolveMcpUrl(config.autonomaApiUrl),
|
|
6305
|
+
interactive
|
|
6306
|
+
});
|
|
6307
|
+
} finally {
|
|
6308
|
+
resume();
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6311
|
+
function describeIncompletePreview(result) {
|
|
6312
|
+
if (result.kind === "verified") return void 0;
|
|
6313
|
+
if (result.kind === "no-agent") {
|
|
6314
|
+
return "No supported coding agent was found on your PATH, so the preview environment was skipped. Install Claude Code or the Codex CLI and run again, or set the preview up in the Autonoma app.";
|
|
6315
|
+
}
|
|
6316
|
+
return "Your preview environment isn't confirmed yet, so scenario dry runs will have nothing to run against. Finish it in the Autonoma app (or run again) - the rest of the run continues either way.";
|
|
6317
|
+
}
|
|
6318
|
+
var init_front_door = __esm({
|
|
6319
|
+
"src/core/front-door.ts"() {
|
|
6320
|
+
"use strict";
|
|
6321
|
+
init_esm_shims();
|
|
6322
|
+
init_prompts();
|
|
6323
|
+
init_api_url();
|
|
6324
|
+
init_autonoma_client();
|
|
6325
|
+
init_coding_agent();
|
|
6326
|
+
init_debug();
|
|
6327
|
+
init_interrupt();
|
|
6328
|
+
init_logs();
|
|
6329
|
+
init_onboarding_phase();
|
|
6330
|
+
init_preview_phase();
|
|
6331
|
+
init_sdk_repair_phase();
|
|
6332
|
+
}
|
|
6333
|
+
});
|
|
6334
|
+
|
|
5219
6335
|
// src/core/model.ts
|
|
5220
6336
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
5221
6337
|
function getProvider() {
|
|
@@ -8093,26 +9209,7 @@ var init_completion = __esm({
|
|
|
8093
9209
|
}
|
|
8094
9210
|
});
|
|
8095
9211
|
|
|
8096
|
-
// src/agents/04-recipe-builder/
|
|
8097
|
-
var launcher_exports = {};
|
|
8098
|
-
__export(launcher_exports, {
|
|
8099
|
-
BaseLauncher: () => BaseLauncher,
|
|
8100
|
-
ClaudeLauncher: () => ClaudeLauncher,
|
|
8101
|
-
CodexLauncher: () => CodexLauncher,
|
|
8102
|
-
DEFAULT_PERMISSION_MODE: () => DEFAULT_PERMISSION_MODE,
|
|
8103
|
-
PERMISSION_MODE_LABELS: () => PERMISSION_MODE_LABELS,
|
|
8104
|
-
buildAllLaunchers: () => buildAllLaunchers,
|
|
8105
|
-
parsePermissionMode: () => parsePermissionMode,
|
|
8106
|
-
selectLauncher: () => selectLauncher,
|
|
8107
|
-
selectPermissionMode: () => selectPermissionMode,
|
|
8108
|
-
watchForCompletion: () => watchForCompletion
|
|
8109
|
-
});
|
|
8110
|
-
import { dirname as dirname3 } from "path";
|
|
8111
|
-
import spawn from "cross-spawn";
|
|
8112
|
-
import which from "which";
|
|
8113
|
-
function launchMessage(promptFile) {
|
|
8114
|
-
return `Read the file ${promptFile} and follow its instructions exactly to integrate Autonoma into this application. It is your complete spec. Do not stop until every item in it is done and you have written the completion marker it describes.`;
|
|
8115
|
-
}
|
|
9212
|
+
// src/agents/04-recipe-builder/completion-watch.ts
|
|
8116
9213
|
function watchForCompletion(outputDir, proc, timing = DEFAULT_WATCH_TIMING) {
|
|
8117
9214
|
let graceTimer;
|
|
8118
9215
|
let killTimer;
|
|
@@ -8131,148 +9228,26 @@ function watchForCompletion(outputDir, proc, timing = DEFAULT_WATCH_TIMING) {
|
|
|
8131
9228
|
}, timing.graceMs);
|
|
8132
9229
|
});
|
|
8133
9230
|
}, timing.pollMs);
|
|
8134
|
-
return () => {
|
|
8135
|
-
clearInterval(poll);
|
|
8136
|
-
if (graceTimer != null) clearTimeout(graceTimer);
|
|
8137
|
-
if (killTimer != null) clearTimeout(killTimer);
|
|
8138
|
-
};
|
|
8139
|
-
}
|
|
8140
|
-
function buildAllLaunchers(opts) {
|
|
8141
|
-
return [new ClaudeLauncher(opts), new CodexLauncher(opts)];
|
|
8142
|
-
}
|
|
8143
|
-
async function selectLauncher(launchers, presetId, interactive = true) {
|
|
8144
|
-
const availability = await Promise.all(launchers.map((l) => l.isAvailable()));
|
|
8145
|
-
const available = launchers.filter((_, i) => availability[i]);
|
|
8146
|
-
debugLog("Detected available agents", { available: available.map((l) => l.id), presetId, interactive });
|
|
8147
|
-
if (presetId != null) {
|
|
8148
|
-
const preset = available.find((l) => l.id === presetId);
|
|
8149
|
-
if (preset != null) return preset;
|
|
8150
|
-
log.warn(`Requested agent "${presetId}" is not installed or not supported.`);
|
|
8151
|
-
}
|
|
8152
|
-
if (available.length === 0) return void 0;
|
|
8153
|
-
if (available.length === 1) {
|
|
8154
|
-
const only = available[0];
|
|
8155
|
-
log.info(`Found ${only.label} - will use it for the integration.`);
|
|
8156
|
-
return only;
|
|
8157
|
-
}
|
|
8158
|
-
if (!interactive) return void 0;
|
|
8159
|
-
const selected = await select({
|
|
8160
|
-
message: "Which agent should implement the integration?",
|
|
8161
|
-
options: available.map((l) => ({ value: l.id, label: l.label }))
|
|
8162
|
-
});
|
|
8163
|
-
if (isCancel(selected)) throw new Error("Agent selection cancelled");
|
|
8164
|
-
return available.find((l) => l.id === selected);
|
|
8165
|
-
}
|
|
8166
|
-
async function selectPermissionMode(preset) {
|
|
8167
|
-
if (preset != null) return preset;
|
|
8168
|
-
const selected = await select({
|
|
8169
|
-
message: "How much autonomy should the agent have?",
|
|
8170
|
-
options: [
|
|
8171
|
-
{ value: "bypassPermissions", label: PERMISSION_MODE_LABELS.bypassPermissions },
|
|
8172
|
-
{ value: "acceptEdits", label: PERMISSION_MODE_LABELS.acceptEdits },
|
|
8173
|
-
{ value: "default", label: PERMISSION_MODE_LABELS.default }
|
|
8174
|
-
],
|
|
8175
|
-
initialValue: DEFAULT_PERMISSION_MODE
|
|
8176
|
-
});
|
|
8177
|
-
if (isCancel(selected)) throw new Error("Permission mode selection cancelled");
|
|
8178
|
-
return selected;
|
|
8179
|
-
}
|
|
8180
|
-
function parsePermissionMode(value) {
|
|
8181
|
-
if (value === "default" || value === "acceptEdits" || value === "bypassPermissions") return value;
|
|
8182
|
-
return void 0;
|
|
9231
|
+
return () => {
|
|
9232
|
+
clearInterval(poll);
|
|
9233
|
+
if (graceTimer != null) clearTimeout(graceTimer);
|
|
9234
|
+
if (killTimer != null) clearTimeout(killTimer);
|
|
9235
|
+
};
|
|
8183
9236
|
}
|
|
8184
|
-
var MARKER_POLL_MS, MARKER_EXIT_GRACE_MS,
|
|
8185
|
-
var
|
|
8186
|
-
"src/agents/04-recipe-builder/
|
|
9237
|
+
var MARKER_POLL_MS, MARKER_EXIT_GRACE_MS, KILL_ESCALATION_MS2, DEFAULT_WATCH_TIMING;
|
|
9238
|
+
var init_completion_watch = __esm({
|
|
9239
|
+
"src/agents/04-recipe-builder/completion-watch.ts"() {
|
|
8187
9240
|
"use strict";
|
|
8188
9241
|
init_esm_shims();
|
|
8189
9242
|
init_debug();
|
|
8190
|
-
init_prompts();
|
|
8191
9243
|
init_completion();
|
|
8192
9244
|
MARKER_POLL_MS = 2e3;
|
|
8193
9245
|
MARKER_EXIT_GRACE_MS = 3e4;
|
|
8194
|
-
|
|
8195
|
-
DEFAULT_PERMISSION_MODE = "bypassPermissions";
|
|
8196
|
-
PERMISSION_MODE_LABELS = {
|
|
8197
|
-
default: "Approve each command",
|
|
8198
|
-
acceptEdits: "Auto-edit files, approve commands",
|
|
8199
|
-
bypassPermissions: "Fully autonomous"
|
|
8200
|
-
};
|
|
8201
|
-
CLAUDE_ID = "claude";
|
|
8202
|
-
CODEX_ID = "codex";
|
|
8203
|
-
BaseLauncher = class {
|
|
8204
|
-
constructor(opts) {
|
|
8205
|
-
this.opts = opts;
|
|
8206
|
-
}
|
|
8207
|
-
async isAvailable() {
|
|
8208
|
-
const resolved = await which(this.command, { nothrow: true });
|
|
8209
|
-
debugLog("Probed for agent on PATH", { command: this.command, found: resolved != null });
|
|
8210
|
-
return resolved != null;
|
|
8211
|
-
}
|
|
8212
|
-
launch(promptFile, permissionMode, interactive) {
|
|
8213
|
-
debugLog(`Launching ${this.label}`, { promptFile, permissionMode, interactive });
|
|
8214
|
-
const args = this.buildArgs(launchMessage(promptFile), permissionMode, interactive);
|
|
8215
|
-
const stdio = interactive ? "inherit" : ["ignore", "inherit", "inherit"];
|
|
8216
|
-
return new Promise((resolve6) => {
|
|
8217
|
-
const proc = spawn(this.command, args, {
|
|
8218
|
-
cwd: this.opts.cwd,
|
|
8219
|
-
env: this.opts.env,
|
|
8220
|
-
stdio
|
|
8221
|
-
});
|
|
8222
|
-
const stopWatching = interactive ? watchForCompletion(dirname3(promptFile), proc) : () => {
|
|
8223
|
-
};
|
|
8224
|
-
proc.on("error", (err) => {
|
|
8225
|
-
debugLog(`${this.label} failed to spawn`, { err });
|
|
8226
|
-
log.error(`Couldn't launch ${this.label}: ${err.message}`);
|
|
8227
|
-
stopWatching();
|
|
8228
|
-
resolve6(void 0);
|
|
8229
|
-
});
|
|
8230
|
-
proc.on("close", (code) => {
|
|
8231
|
-
debugLog(`${this.label} exited`, { code });
|
|
8232
|
-
stopWatching();
|
|
8233
|
-
resolve6(code ?? void 0);
|
|
8234
|
-
});
|
|
8235
|
-
});
|
|
8236
|
-
}
|
|
8237
|
-
};
|
|
8238
|
-
ClaudeLauncher = class extends BaseLauncher {
|
|
8239
|
-
id = CLAUDE_ID;
|
|
8240
|
-
label = "Claude Code";
|
|
8241
|
-
command = CLAUDE_ID;
|
|
8242
|
-
buildArgs(message, permissionMode, interactive) {
|
|
8243
|
-
return interactive ? ["--permission-mode", permissionMode, message] : ["-p", message, "--permission-mode", permissionMode, "--verbose"];
|
|
8244
|
-
}
|
|
8245
|
-
};
|
|
8246
|
-
CodexLauncher = class extends BaseLauncher {
|
|
8247
|
-
id = CODEX_ID;
|
|
8248
|
-
label = "Codex CLI";
|
|
8249
|
-
command = CODEX_ID;
|
|
8250
|
-
/**
|
|
8251
|
-
* Codex's autonomy is two orthogonal axes - `--sandbox` (what it may touch) and
|
|
8252
|
-
* `--ask-for-approval` (when it pauses) - so we translate the shared,
|
|
8253
|
-
* Claude-flavoured `PermissionMode` onto them.
|
|
8254
|
-
*
|
|
8255
|
-
* The handoff's whole job is to install the SDK, boot the app, and validate
|
|
8256
|
-
* against a live DB, so the sandbox is always `danger-full-access` (Codex's
|
|
8257
|
-
* `workspace-write` disables network, which breaks the install). The only real
|
|
8258
|
-
* knob is approval strictness, which exists only interactively: headless `exec`
|
|
8259
|
-
* can't prompt, so `default`/`acceptEdits` collapse to the same autonomous run.
|
|
8260
|
-
*/
|
|
8261
|
-
buildArgs(message, permissionMode, interactive) {
|
|
8262
|
-
if (permissionMode === "bypassPermissions") {
|
|
8263
|
-
const bypass = ["--dangerously-bypass-approvals-and-sandbox"];
|
|
8264
|
-
return interactive ? [...bypass, message] : ["exec", ...bypass, message];
|
|
8265
|
-
}
|
|
8266
|
-
const sandbox = ["--sandbox", "danger-full-access"];
|
|
8267
|
-
if (!interactive) return ["exec", ...sandbox, message];
|
|
8268
|
-
const approval = permissionMode === "acceptEdits" ? "on-failure" : "untrusted";
|
|
8269
|
-
return [...sandbox, "--ask-for-approval", approval, message];
|
|
8270
|
-
}
|
|
8271
|
-
};
|
|
9246
|
+
KILL_ESCALATION_MS2 = 1e4;
|
|
8272
9247
|
DEFAULT_WATCH_TIMING = {
|
|
8273
9248
|
pollMs: MARKER_POLL_MS,
|
|
8274
9249
|
graceMs: MARKER_EXIT_GRACE_MS,
|
|
8275
|
-
killMs:
|
|
9250
|
+
killMs: KILL_ESCALATION_MS2
|
|
8276
9251
|
};
|
|
8277
9252
|
}
|
|
8278
9253
|
});
|
|
@@ -8287,6 +9262,10 @@ ${params.priorFailure}
|
|
|
8287
9262
|
Re-read your IMPLEMENTATION.md checklist, pick up at the first unfinished entity,
|
|
8288
9263
|
and do NOT redo entities already validated. Finish every remaining item, then write
|
|
8289
9264
|
the completion marker.
|
|
9265
|
+
` : "";
|
|
9266
|
+
const userGuidanceSection = params.userGuidance != null ? `
|
|
9267
|
+
\u2550\u2550\u2550 THE DEVELOPER ASKED FOR THIS - IT OVERRIDES YOUR OWN PLAN \u2550\u2550\u2550
|
|
9268
|
+
${params.userGuidance}
|
|
8290
9269
|
` : "";
|
|
8291
9270
|
return `<!-- Autonoma integration prompt v${INTEGRATION_PROMPT_VERSION} -->
|
|
8292
9271
|
You are integrating Autonoma into THIS application, working in a LOCAL checkout of
|
|
@@ -8296,7 +9275,7 @@ the repo. The Autonoma planner has ALREADY run locally and produced its artifact
|
|
|
8296
9275
|
You are the developer picking up exactly where the planner hands off: implement the
|
|
8297
9276
|
test-data layer (the SDK integration), GENERATE the test-data recipe, and validate
|
|
8298
9277
|
it. Do NOT re-run the planner; read its artifacts as your spec.
|
|
8299
|
-
${priorFailureSection}
|
|
9278
|
+
${priorFailureSection}${userGuidanceSection}
|
|
8300
9279
|
Work without asking questions. Make reasonable, codebase-grounded decisions. Only
|
|
8301
9280
|
stop for missing secrets, credentials, or external services that genuinely cannot
|
|
8302
9281
|
be mocked or run locally - and when you do, say exactly what you need and why.
|
|
@@ -8448,7 +9427,8 @@ Before implementing, write a checklist file inside the app (e.g. IMPLEMENTATION.
|
|
|
8448
9427
|
and keep it updated. It must enumerate, as explicit checkboxes: EVERY entity the
|
|
8449
9428
|
entity audit says needs a factory (by name, copied from the audit), plus the
|
|
8450
9429
|
endpoint, teardown, the auth callback, the maintenance note, the full-recipe pass,
|
|
8451
|
-
the
|
|
9430
|
+
the concurrent-instances proof, a clean \`sdk check\` on the recipe file, and the
|
|
9431
|
+
pushed branch + opened pull request.
|
|
8452
9432
|
Check items off only when actually done and verified. The single most common failure is stopping with entities left uncovered.
|
|
8453
9433
|
|
|
8454
9434
|
\u2550\u2550\u2550 VALIDATE - ENTITY BY ENTITY, THEN THE WHOLE RECIPE \u2550\u2550\u2550
|
|
@@ -8459,6 +9439,12 @@ from the environment, so you never construct signatures yourself. The commands:
|
|
|
8459
9439
|
\u2022 ${params.cliCommand} sdk up --url <endpoint-url> --recipe <file> [--test-run-id <id>] [--timeout <seconds>]
|
|
8460
9440
|
(prints JSON; the response body includes a "refsToken")
|
|
8461
9441
|
\u2022 ${params.cliCommand} sdk down --url <endpoint-url> --refs-token <token-from-up>
|
|
9442
|
+
\u2022 ${params.cliCommand} sdk check --recipe <file>
|
|
9443
|
+
(no url, no request: holds the FILE to the format Autonoma accepts and prints
|
|
9444
|
+
every problem it finds. Run it whenever you edit the recipe.)
|
|
9445
|
+
\u2022 ${params.cliCommand} sdk up --url <endpoint-url> --recipe <file> --repeat <n>
|
|
9446
|
+
(seeds the recipe n times over WITHOUT tearing down in between, so every
|
|
9447
|
+
instance is live at once - the concurrency proof below)
|
|
8462
9448
|
The --recipe file may be your full recipe.json or a slice containing just the
|
|
8463
9449
|
entities under test. Each request times out after 120s by default; a cold
|
|
8464
9450
|
full-recipe up (first compile + many real-service inserts) can exceed that, so
|
|
@@ -8490,19 +9476,34 @@ Once every entity passes independently, run the FULL recipe as one pass:
|
|
|
8490
9476
|
\u2022 confirm a WRONG signature is rejected (the SDK does this for you - do not disable it)
|
|
8491
9477
|
\u2022 confirm the up response's auth payload contains real credentials, not a placeholder
|
|
8492
9478
|
|
|
8493
|
-
\u2550\u2550\u2550
|
|
9479
|
+
\u2550\u2550\u2550 CHECK THE RECIPE FILE - THE GATE YOU CANNOT SKIP \u2550\u2550\u2550
|
|
9480
|
+
A recipe that seeds a database perfectly can still be a file Autonoma refuses, because
|
|
9481
|
+
\`sdk up\` only ever reads the "create" graph out of it - it never looks at the envelope
|
|
9482
|
+
around it. The planner DOES, the moment you exit, and a file it rejects there costs a
|
|
9483
|
+
whole re-launch. So before you write the completion marker, run:
|
|
9484
|
+
${params.cliCommand} sdk check --recipe ${params.recipePath}
|
|
9485
|
+
It exits 0 and prints "ok": true only when the file is submittable. Anything else prints
|
|
9486
|
+
a "problems" array naming the exact field and what is wrong with it - fix each one in
|
|
9487
|
+
${params.recipePath} and run it again. Do NOT write the completion marker until it is
|
|
9488
|
+
clean; a passing \`up\` is not a substitute for it.
|
|
9489
|
+
|
|
9490
|
+
\u2550\u2550\u2550 PROVE MANY INSTANCES CAN COEXIST - MANDATORY, LAST \u2550\u2550\u2550
|
|
8494
9491
|
Every check above tears down before the next up, so a recipe whose unique columns hold
|
|
8495
|
-
hardcoded values passes all of them. Real test runs OVERLAP: the customer runs
|
|
8496
|
-
at once and the second seed hits the first one's rows. Prove yours survives that:
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
9492
|
+
hardcoded values passes all of them. Real test runs OVERLAP: the customer runs several
|
|
9493
|
+
tests at once and the second seed hits the first one's rows. Prove yours survives that:
|
|
9494
|
+
${params.cliCommand} sdk up --url <url> --recipe ${params.recipePath} --repeat 3
|
|
9495
|
+
It seeds the recipe three times over WITHOUT tearing down in between, so all three are
|
|
9496
|
+
live at once, then removes every instance it created and reports the teardown. It exits 0
|
|
9497
|
+
and prints "ok": true only when every instance came up.
|
|
9498
|
+
A failure here is a unique-constraint violation, and the endpoint's error names the exact
|
|
9499
|
+
column that is not per-run. Fix it where it lives: put a token in that field if the recipe
|
|
9500
|
+
supplies it, or derive it from the "testRunId" your handler receives if your factory
|
|
9501
|
+
generates it. Then run it again, and keep going until it passes - each run surfaces the
|
|
9502
|
+
next reused value. Do not weaken the constraint and do not disable the check.
|
|
9503
|
+
|
|
9504
|
+
If the teardown it reports did NOT complete, go clean those rows out of the database by
|
|
9505
|
+
hand before re-running: leftovers from a failed attempt collide with the next one and
|
|
9506
|
+
look like a defect in whatever you just changed.
|
|
8506
9507
|
|
|
8507
9508
|
Escape hatch, only after honest attempts: if an entity truly cannot be seeded twice
|
|
8508
9509
|
concurrently because the app's own schema forces a global singleton, write that in
|
|
@@ -8533,9 +9534,10 @@ sitting uncommitted in the working tree:
|
|
|
8533
9534
|
|
|
8534
9535
|
\u2550\u2550\u2550 FINISH - THE LAST THING YOU DO \u2550\u2550\u2550
|
|
8535
9536
|
Write the completion marker once ALL of these hold:
|
|
8536
|
-
\u2022 every entity, the full-recipe pass, and the
|
|
9537
|
+
\u2022 every entity, the full-recipe pass, and the concurrent-instances proof are green
|
|
8537
9538
|
- or the blocking constraint is documented in IMPLEMENTATION.md
|
|
8538
|
-
\u2022 ${params.recipePath} holds the recipe you validated
|
|
9539
|
+
\u2022 ${params.recipePath} holds the recipe you validated, and \`sdk check\` on it prints
|
|
9540
|
+
"ok": true - this one has no escape hatch; a rejected file blocks the whole setup
|
|
8539
9541
|
\u2022 your work is committed, and pushed with a pull request open - or the reason you could
|
|
8540
9542
|
not push / open one is documented in IMPLEMENTATION.md
|
|
8541
9543
|
A step you documented as genuinely blocked NEVER justifies withholding the marker; a
|
|
@@ -8564,7 +9566,7 @@ var init_integration_prompt = __esm({
|
|
|
8564
9566
|
init_esm_shims();
|
|
8565
9567
|
init_src();
|
|
8566
9568
|
init_completion();
|
|
8567
|
-
INTEGRATION_PROMPT_VERSION =
|
|
9569
|
+
INTEGRATION_PROMPT_VERSION = 10;
|
|
8568
9570
|
INTEGRATION_PROMPT_FILE = "integration-prompt.md";
|
|
8569
9571
|
DEFAULT_ENDPOINT_PATH = "/api/autonoma";
|
|
8570
9572
|
INTEGRATION_BRANCH = "autonoma-integration";
|
|
@@ -8605,6 +9607,9 @@ var init_state2 = __esm({
|
|
|
8605
9607
|
// src/agents/04-recipe-builder/phases/handoff.ts
|
|
8606
9608
|
import { rm as rm2 } from "fs/promises";
|
|
8607
9609
|
import { join as join27 } from "path";
|
|
9610
|
+
function launchMessage(promptFile) {
|
|
9611
|
+
return `Read the file ${promptFile} and follow its instructions exactly to integrate Autonoma into this application. It is your complete spec. Do not stop until every item in it is done and you have written the completion marker it describes.`;
|
|
9612
|
+
}
|
|
8608
9613
|
async function runHandoffPhase(state, deps, outputDir) {
|
|
8609
9614
|
const recipePath = state.recipePath ?? join27(outputDir, RECIPE_FILE);
|
|
8610
9615
|
state.recipePath = recipePath;
|
|
@@ -8626,7 +9631,9 @@ async function runHandoffPhase(state, deps, outputDir) {
|
|
|
8626
9631
|
outputDir,
|
|
8627
9632
|
recipePath,
|
|
8628
9633
|
cliCommand: deps.cliCommand,
|
|
8629
|
-
interactive: deps.interactive
|
|
9634
|
+
interactive: deps.interactive,
|
|
9635
|
+
priorFailure: state.priorFailure,
|
|
9636
|
+
userGuidance: state.userGuidance
|
|
8630
9637
|
});
|
|
8631
9638
|
state.launchAttempts = (state.launchAttempts ?? 0) + 1;
|
|
8632
9639
|
await saveRecipeState(outputDir, state);
|
|
@@ -8642,7 +9649,7 @@ async function runCompletionPhase(state, deps, outputDir) {
|
|
|
8642
9649
|
log.success("The agent reported the integration complete and validated.");
|
|
8643
9650
|
return { kind: "advance" };
|
|
8644
9651
|
}
|
|
8645
|
-
const failure = describeIncompleteRecipe(read, uploadProblems);
|
|
9652
|
+
const failure = describeIncompleteRecipe(read, uploadProblems, recipePath, deps.cliCommand);
|
|
8646
9653
|
const launcher = await selectLauncher(deps.launchers, state.agentId ?? deps.presetAgentId, deps.interactive);
|
|
8647
9654
|
if (launcher != null && (state.launchAttempts ?? 0) < MAX_LAUNCH_ATTEMPTS) {
|
|
8648
9655
|
log.warn("The integration isn't complete yet. Launching the agent to finish it...");
|
|
@@ -8654,7 +9661,8 @@ async function runCompletionPhase(state, deps, outputDir) {
|
|
|
8654
9661
|
recipePath,
|
|
8655
9662
|
cliCommand: deps.cliCommand,
|
|
8656
9663
|
interactive: deps.interactive,
|
|
8657
|
-
priorFailure: failure
|
|
9664
|
+
priorFailure: failure,
|
|
9665
|
+
userGuidance: state.userGuidance
|
|
8658
9666
|
});
|
|
8659
9667
|
state.launchAttempts = (state.launchAttempts ?? 0) + 1;
|
|
8660
9668
|
await saveRecipeState(outputDir, state);
|
|
@@ -8673,7 +9681,8 @@ async function launchAgent(launcher, permissionMode, target) {
|
|
|
8673
9681
|
outputDir: target.outputDir,
|
|
8674
9682
|
recipePath: target.recipePath,
|
|
8675
9683
|
cliCommand: target.cliCommand,
|
|
8676
|
-
priorFailure: target.priorFailure
|
|
9684
|
+
priorFailure: target.priorFailure,
|
|
9685
|
+
userGuidance: target.userGuidance
|
|
8677
9686
|
});
|
|
8678
9687
|
await rm2(join27(target.outputDir, COMPLETION_MARKER_FILE), { force: true }).catch((err) => {
|
|
8679
9688
|
debugLog("Could not clear a stale completion marker", { err });
|
|
@@ -8693,23 +9702,35 @@ async function launchAgent(launcher, permissionMode, target) {
|
|
|
8693
9702
|
}
|
|
8694
9703
|
suspend();
|
|
8695
9704
|
try {
|
|
8696
|
-
const exitCode = await launcher.launch(
|
|
9705
|
+
const exitCode = await launcher.launch({
|
|
9706
|
+
message: launchMessage(promptFile),
|
|
9707
|
+
permissionMode,
|
|
9708
|
+
interactive: target.interactive,
|
|
9709
|
+
// An interactive session sits open after its final message, so the marker
|
|
9710
|
+
// the agent writes is the real "done" signal. A headless run exits on its
|
|
9711
|
+
// own and needs no watcher.
|
|
9712
|
+
watch: target.interactive ? (proc) => watchForCompletion(target.outputDir, proc) : void 0
|
|
9713
|
+
});
|
|
8697
9714
|
log.info(`${launcher.label} exited (code ${exitCode ?? "unknown"}). Back to the planner.`);
|
|
8698
9715
|
} finally {
|
|
8699
9716
|
resume();
|
|
8700
9717
|
}
|
|
8701
9718
|
}
|
|
8702
|
-
function describeIncompleteRecipe(read, uploadProblems) {
|
|
9719
|
+
function describeIncompleteRecipe(read, uploadProblems, recipePath, cliCommand) {
|
|
9720
|
+
const recheck = `Fix ${recipePath}, then re-check it with:
|
|
9721
|
+
${cliCommand} sdk check --recipe ${recipePath}`;
|
|
8703
9722
|
if (read.status === "absent") {
|
|
8704
|
-
return
|
|
9723
|
+
return `No completed recipe.json was produced, so there is nothing validated to submit. It belongs at ${recipePath}.`;
|
|
8705
9724
|
}
|
|
8706
9725
|
if (read.status === "invalid") {
|
|
8707
|
-
return `${
|
|
8708
|
-
` + read.problems.map((problem) => ` - ${problem}`).join("\n")
|
|
9726
|
+
return `${recipePath} does not match the recipe format Autonoma accepts:
|
|
9727
|
+
` + read.problems.map((problem) => ` - ${problem}`).join("\n") + `
|
|
9728
|
+
${recheck}`;
|
|
8709
9729
|
}
|
|
8710
9730
|
if (uploadProblems.length > 0) {
|
|
8711
|
-
return
|
|
8712
|
-
` + uploadProblems.map((problem) => ` - ${problem}`).join("\n")
|
|
9731
|
+
return `${RECIPE_FILE} will be rejected on upload:
|
|
9732
|
+
` + uploadProblems.map((problem) => ` - ${problem}`).join("\n") + `
|
|
9733
|
+
${recheck}`;
|
|
8713
9734
|
}
|
|
8714
9735
|
return "The agent exited without marking the integration complete (see its IMPLEMENTATION.md for what's left).";
|
|
8715
9736
|
}
|
|
@@ -8732,13 +9753,14 @@ var init_handoff = __esm({
|
|
|
8732
9753
|
"src/agents/04-recipe-builder/phases/handoff.ts"() {
|
|
8733
9754
|
"use strict";
|
|
8734
9755
|
init_esm_shims();
|
|
9756
|
+
init_coding_agent();
|
|
8735
9757
|
init_debug();
|
|
8736
9758
|
init_interrupt();
|
|
8737
9759
|
init_notify();
|
|
8738
9760
|
init_prompts();
|
|
8739
9761
|
init_completion();
|
|
9762
|
+
init_completion_watch();
|
|
8740
9763
|
init_integration_prompt();
|
|
8741
|
-
init_launcher();
|
|
8742
9764
|
init_recipe();
|
|
8743
9765
|
init_state2();
|
|
8744
9766
|
MAX_LAUNCH_ATTEMPTS = 2;
|
|
@@ -8771,6 +9793,9 @@ function outcomeToResult(outcome) {
|
|
|
8771
9793
|
async function runRecipeBuilder(input) {
|
|
8772
9794
|
const { outputDir } = input;
|
|
8773
9795
|
const state = await loadRecipeState(outputDir) ?? initialRecipeState();
|
|
9796
|
+
state.launchAttempts = 0;
|
|
9797
|
+
if (input.retryGuidance != null) state.userGuidance = input.retryGuidance;
|
|
9798
|
+
await saveRecipeState(outputDir, state);
|
|
8774
9799
|
if (state.sharedSecret == null) {
|
|
8775
9800
|
state.sharedSecret = input.config.sharedSecret ?? generateSharedSecret();
|
|
8776
9801
|
await saveRecipeState(outputDir, state);
|
|
@@ -8839,9 +9864,9 @@ var init_recipe_builder = __esm({
|
|
|
8839
9864
|
"src/agents/04-recipe-builder/index.ts"() {
|
|
8840
9865
|
"use strict";
|
|
8841
9866
|
init_esm_shims();
|
|
9867
|
+
init_coding_agent();
|
|
8842
9868
|
init_prompts();
|
|
8843
9869
|
init_entity_order();
|
|
8844
|
-
init_launcher();
|
|
8845
9870
|
init_handoff();
|
|
8846
9871
|
init_submit();
|
|
8847
9872
|
init_state2();
|
|
@@ -9707,7 +10732,7 @@ var init_recipe_context = __esm({
|
|
|
9707
10732
|
|
|
9708
10733
|
// src/agents/05-test-generator/restore-deleted-test.ts
|
|
9709
10734
|
import { access, mkdir as mkdir3, writeFile as writeFile11 } from "fs/promises";
|
|
9710
|
-
import { dirname as
|
|
10735
|
+
import { dirname as dirname3 } from "path";
|
|
9711
10736
|
async function restoreDeletedTest(testPath, originalContent) {
|
|
9712
10737
|
try {
|
|
9713
10738
|
await access(testPath);
|
|
@@ -9716,7 +10741,7 @@ async function restoreDeletedTest(testPath, originalContent) {
|
|
|
9716
10741
|
debugLog("Test absent after its fix pass - restoring the pre-review content", { testPath, err });
|
|
9717
10742
|
}
|
|
9718
10743
|
try {
|
|
9719
|
-
await mkdir3(
|
|
10744
|
+
await mkdir3(dirname3(testPath), { recursive: true });
|
|
9720
10745
|
await writeFile11(testPath, originalContent, "utf-8");
|
|
9721
10746
|
return true;
|
|
9722
10747
|
} catch (err) {
|
|
@@ -10454,7 +11479,7 @@ var init_test_spec = __esm({
|
|
|
10454
11479
|
|
|
10455
11480
|
// src/agents/05-test-generator/tools.ts
|
|
10456
11481
|
import { mkdir as mkdir4, writeFile as writeFile12 } from "fs/promises";
|
|
10457
|
-
import { dirname as
|
|
11482
|
+
import { dirname as dirname4, join as join32 } from "path";
|
|
10458
11483
|
import { hasToolCall as hasToolCall3, stepCountIs as stepCountIs3, tool as tool16, ToolLoopAgent as ToolLoopAgent3 } from "ai";
|
|
10459
11484
|
import { z as z56 } from "zod";
|
|
10460
11485
|
function buildWriteTestTool(state, outputDir, onWritten) {
|
|
@@ -10489,7 +11514,7 @@ function buildWriteTestTool(state, outputDir, onWritten) {
|
|
|
10489
11514
|
};
|
|
10490
11515
|
}
|
|
10491
11516
|
try {
|
|
10492
|
-
await mkdir4(
|
|
11517
|
+
await mkdir4(dirname4(absPath), { recursive: true });
|
|
10493
11518
|
await writeFile12(absPath, content, "utf-8");
|
|
10494
11519
|
state.markTested(nodeId, [relPath]);
|
|
10495
11520
|
await saveBfsState(outputDir, state);
|
|
@@ -11878,7 +12903,7 @@ var init_countdown = __esm({
|
|
|
11878
12903
|
});
|
|
11879
12904
|
|
|
11880
12905
|
// src/ui/draw/help.ts
|
|
11881
|
-
function
|
|
12906
|
+
function wrap2(text2, maxW) {
|
|
11882
12907
|
const words = text2.split(/\s+/);
|
|
11883
12908
|
const lines = [];
|
|
11884
12909
|
let cur = "";
|
|
@@ -11900,7 +12925,7 @@ function drawHelpModal(g, state) {
|
|
|
11900
12925
|
const innerW = w - 6;
|
|
11901
12926
|
const step = state.currentStep;
|
|
11902
12927
|
const intro2 = step != null ? STEP_INTROS[step] : "The run has no active step right now.";
|
|
11903
|
-
const introLines =
|
|
12928
|
+
const introLines = wrap2(intro2, innerW);
|
|
11904
12929
|
const docsUrl = step != null ? STEP_DOCS[step] : void 0;
|
|
11905
12930
|
const pipelineLines = state.stepOrder.length;
|
|
11906
12931
|
const keys = [
|
|
@@ -13577,6 +14602,114 @@ function shortHash(seed) {
|
|
|
13577
14602
|
// src/agents/04-recipe-builder/sdk-command.ts
|
|
13578
14603
|
import { z as z35 } from "zod";
|
|
13579
14604
|
|
|
14605
|
+
// src/core/cli-args.ts
|
|
14606
|
+
init_esm_shims();
|
|
14607
|
+
var CliArgs = class _CliArgs {
|
|
14608
|
+
/** Absent value means the flag was given with no value at all. */
|
|
14609
|
+
constructor(flags) {
|
|
14610
|
+
this.flags = flags;
|
|
14611
|
+
}
|
|
14612
|
+
static parse(argv) {
|
|
14613
|
+
const flags = /* @__PURE__ */ new Map();
|
|
14614
|
+
for (let i = 0; i < argv.length; i++) {
|
|
14615
|
+
const arg = argv[i] ?? "";
|
|
14616
|
+
if (!arg.startsWith("--")) continue;
|
|
14617
|
+
const body = arg.slice(2);
|
|
14618
|
+
const equals = body.indexOf("=");
|
|
14619
|
+
if (equals !== -1) {
|
|
14620
|
+
push(flags, body.slice(0, equals), body.slice(equals + 1));
|
|
14621
|
+
continue;
|
|
14622
|
+
}
|
|
14623
|
+
const next = argv[i + 1];
|
|
14624
|
+
if (next != null && next.length > 0 && !next.startsWith("--")) {
|
|
14625
|
+
push(flags, body, next);
|
|
14626
|
+
i++;
|
|
14627
|
+
continue;
|
|
14628
|
+
}
|
|
14629
|
+
push(flags, body, void 0);
|
|
14630
|
+
}
|
|
14631
|
+
return new _CliArgs(flags);
|
|
14632
|
+
}
|
|
14633
|
+
/** Whether any of these spellings was given at all, with or without a value. */
|
|
14634
|
+
has(...names) {
|
|
14635
|
+
return names.some((name) => this.flags.has(name));
|
|
14636
|
+
}
|
|
14637
|
+
/**
|
|
14638
|
+
* The value of the first of these spellings that carries one. A flag given with
|
|
14639
|
+
* no value has none - `--project` alone means the caller forgot the path, not
|
|
14640
|
+
* that the path is "true".
|
|
14641
|
+
*/
|
|
14642
|
+
value(...names) {
|
|
14643
|
+
for (const name of names) {
|
|
14644
|
+
const values = this.flags.get(name);
|
|
14645
|
+
if (values != null && values.length > 0) return values[values.length - 1];
|
|
14646
|
+
}
|
|
14647
|
+
return void 0;
|
|
14648
|
+
}
|
|
14649
|
+
/**
|
|
14650
|
+
* Every value given across these spellings, comma-separated lists expanded. So
|
|
14651
|
+
* `--backend api --backend db` and `--backends api,db` mean the same thing, and
|
|
14652
|
+
* neither has to be the one the caller happened to guess.
|
|
14653
|
+
*
|
|
14654
|
+
* Undefined when the flag was never given - which is different from given-empty,
|
|
14655
|
+
* because a caller who explicitly asked for no backends should get none rather
|
|
14656
|
+
* than the ones something else inferred.
|
|
14657
|
+
*/
|
|
14658
|
+
list(...names) {
|
|
14659
|
+
const given = names.flatMap((name) => this.flags.get(name) ?? []);
|
|
14660
|
+
if (!this.has(...names)) return void 0;
|
|
14661
|
+
return given.flatMap((value) => value.split(",")).map((value) => value.trim()).filter((value) => value.length > 0);
|
|
14662
|
+
}
|
|
14663
|
+
/**
|
|
14664
|
+
* Flags that are not in `known`, with the closest thing that is.
|
|
14665
|
+
*
|
|
14666
|
+
* A misspelled `--non-interactive` is the worst kind of typo here: nothing
|
|
14667
|
+
* refuses it, and the run instead waits on questions nobody will answer. Naming
|
|
14668
|
+
* it back turns a silent stall into one line of output.
|
|
14669
|
+
*/
|
|
14670
|
+
unrecognized(known) {
|
|
14671
|
+
const found = [];
|
|
14672
|
+
for (const name of this.flags.keys()) {
|
|
14673
|
+
if (known.has(name)) continue;
|
|
14674
|
+
const meant = closestMatch(name, known);
|
|
14675
|
+
found.push(meant != null ? { given: name, meant } : { given: name });
|
|
14676
|
+
}
|
|
14677
|
+
return found;
|
|
14678
|
+
}
|
|
14679
|
+
};
|
|
14680
|
+
function push(flags, name, value) {
|
|
14681
|
+
const existing = flags.get(name) ?? [];
|
|
14682
|
+
if (value != null) existing.push(value);
|
|
14683
|
+
flags.set(name, existing);
|
|
14684
|
+
}
|
|
14685
|
+
var MAX_TYPO_DISTANCE = 3;
|
|
14686
|
+
function closestMatch(name, known) {
|
|
14687
|
+
let best;
|
|
14688
|
+
let bestDistance = MAX_TYPO_DISTANCE + 1;
|
|
14689
|
+
for (const candidate of known) {
|
|
14690
|
+
const distance = editDistance(name, candidate);
|
|
14691
|
+
if (distance < bestDistance) {
|
|
14692
|
+
best = candidate;
|
|
14693
|
+
bestDistance = distance;
|
|
14694
|
+
}
|
|
14695
|
+
}
|
|
14696
|
+
return bestDistance <= MAX_TYPO_DISTANCE ? best : void 0;
|
|
14697
|
+
}
|
|
14698
|
+
function editDistance(a, b) {
|
|
14699
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
14700
|
+
for (let i = 1; i <= a.length; i++) {
|
|
14701
|
+
const current = [i];
|
|
14702
|
+
for (let j = 1; j <= b.length; j++) {
|
|
14703
|
+
const substitution = (previous[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
14704
|
+
const deletion = (previous[j] ?? 0) + 1;
|
|
14705
|
+
const insertion = (current[j - 1] ?? 0) + 1;
|
|
14706
|
+
current.push(Math.min(substitution, deletion, insertion));
|
|
14707
|
+
}
|
|
14708
|
+
previous = current;
|
|
14709
|
+
}
|
|
14710
|
+
return previous[b.length] ?? 0;
|
|
14711
|
+
}
|
|
14712
|
+
|
|
13580
14713
|
// src/agents/04-recipe-builder/http-client.ts
|
|
13581
14714
|
init_esm_shims();
|
|
13582
14715
|
import { createHmac } from "crypto";
|
|
@@ -13623,7 +14756,9 @@ async function down(config, refsToken) {
|
|
|
13623
14756
|
}
|
|
13624
14757
|
|
|
13625
14758
|
// src/agents/04-recipe-builder/sdk-command.ts
|
|
14759
|
+
init_recipe();
|
|
13626
14760
|
var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
14761
|
+
var MIN_REPEAT_INSTANCES = 2;
|
|
13627
14762
|
var createSchema = z35.record(z35.string(), z35.array(z35.unknown()));
|
|
13628
14763
|
var recipeSliceSchema = z35.object({ create: createSchema, variables: ScenarioRecipeVariablesSchema.optional() });
|
|
13629
14764
|
var envelopeSchema = z35.object({ recipes: z35.array(recipeSliceSchema).min(1) });
|
|
@@ -13632,16 +14767,40 @@ var flagsSchema = z35.object({
|
|
|
13632
14767
|
recipe: z35.string().min(1).optional(),
|
|
13633
14768
|
"refs-token": z35.string().min(1).optional(),
|
|
13634
14769
|
"test-run-id": z35.string().min(1).optional(),
|
|
13635
|
-
timeout: z35.coerce.number().int().positive().optional()
|
|
13636
|
-
|
|
14770
|
+
timeout: z35.coerce.number().int().positive().optional(),
|
|
14771
|
+
repeat: z35.coerce.number().int().min(MIN_REPEAT_INSTANCES).optional()
|
|
14772
|
+
});
|
|
14773
|
+
var SDK_FLAGS = new Set(Object.keys(flagsSchema.shape));
|
|
13637
14774
|
async function runSdkCommand(argv, io) {
|
|
13638
14775
|
const action = argv[0];
|
|
14776
|
+
const args = CliArgs.parse(argv.slice(1));
|
|
14777
|
+
const unknown = args.unrecognized(SDK_FLAGS);
|
|
14778
|
+
if (unknown.length > 0) {
|
|
14779
|
+
io.stderr(`Unknown flag(s): ${unknown.map(describeUnknownFlag).join("; ")}
|
|
14780
|
+
`);
|
|
14781
|
+
return 2;
|
|
14782
|
+
}
|
|
14783
|
+
if (action === "check") {
|
|
14784
|
+
const recipe = args.value("recipe");
|
|
14785
|
+
if (recipe == null || recipe === "") {
|
|
14786
|
+
io.stderr("--recipe <file> is required for `check`.\n");
|
|
14787
|
+
return 2;
|
|
14788
|
+
}
|
|
14789
|
+
return await runCheck(recipe, io);
|
|
14790
|
+
}
|
|
13639
14791
|
const sharedSecret = io.env.AUTONOMA_SHARED_SECRET;
|
|
13640
14792
|
if (sharedSecret == null || sharedSecret === "") {
|
|
13641
14793
|
io.stderr("AUTONOMA_SHARED_SECRET is not set in the environment.\n");
|
|
13642
14794
|
return 2;
|
|
13643
14795
|
}
|
|
13644
|
-
const parsedFlags = flagsSchema.safeParse(
|
|
14796
|
+
const parsedFlags = flagsSchema.safeParse({
|
|
14797
|
+
url: args.value("url"),
|
|
14798
|
+
recipe: args.value("recipe"),
|
|
14799
|
+
"refs-token": args.value("refs-token"),
|
|
14800
|
+
"test-run-id": args.value("test-run-id"),
|
|
14801
|
+
timeout: args.value("timeout"),
|
|
14802
|
+
repeat: args.value("repeat")
|
|
14803
|
+
});
|
|
13645
14804
|
if (!parsedFlags.success) {
|
|
13646
14805
|
io.stderr(`Invalid flags: ${formatIssues(parsedFlags.error)}
|
|
13647
14806
|
`);
|
|
@@ -13661,16 +14820,17 @@ async function runSdkCommand(argv, io) {
|
|
|
13661
14820
|
io.stderr("--recipe <file> is required for `up`.\n");
|
|
13662
14821
|
return 2;
|
|
13663
14822
|
}
|
|
13664
|
-
const
|
|
14823
|
+
const baseRunId = flags["test-run-id"] ?? `cli-${Date.now()}`;
|
|
13665
14824
|
const slice = await loadRecipeSlice(flags.recipe);
|
|
14825
|
+
if (flags.repeat != null) return await runRepeatedUps(config, slice, baseRunId, flags.repeat, io);
|
|
13666
14826
|
const resolution = resolveRecipeCreateGraph({
|
|
13667
14827
|
create: slice.create,
|
|
13668
14828
|
variables: slice.variables,
|
|
13669
|
-
testRunId
|
|
14829
|
+
testRunId: baseRunId
|
|
13670
14830
|
});
|
|
13671
14831
|
const create = createSchema.parse(resolution.createPayload);
|
|
13672
|
-
return emit2(io, await up(config, create,
|
|
13673
|
-
testRunId,
|
|
14832
|
+
return emit2(io, await up(config, create, baseRunId), {
|
|
14833
|
+
testRunId: baseRunId,
|
|
13674
14834
|
resolvedVariables: resolution.resolvedVariables
|
|
13675
14835
|
});
|
|
13676
14836
|
}
|
|
@@ -13681,7 +14841,7 @@ async function runSdkCommand(argv, io) {
|
|
|
13681
14841
|
}
|
|
13682
14842
|
return emit2(io, await down(config, flags["refs-token"]));
|
|
13683
14843
|
}
|
|
13684
|
-
io.stderr(`Unknown sdk action "${action ?? ""}". Use one of: discover | up | down.
|
|
14844
|
+
io.stderr(`Unknown sdk action "${action ?? ""}". Use one of: discover | up | down | check.
|
|
13685
14845
|
`);
|
|
13686
14846
|
return 2;
|
|
13687
14847
|
} catch (err) {
|
|
@@ -13693,6 +14853,116 @@ async function runSdkCommand(argv, io) {
|
|
|
13693
14853
|
return 1;
|
|
13694
14854
|
}
|
|
13695
14855
|
}
|
|
14856
|
+
async function runRepeatedUps(config, slice, baseRunId, instances, io) {
|
|
14857
|
+
const seeded = [];
|
|
14858
|
+
for (let index = 1; index <= instances; index++) {
|
|
14859
|
+
const testRunId = `${baseRunId}-${index}`;
|
|
14860
|
+
const resolution = resolveRecipeCreateGraph({
|
|
14861
|
+
create: slice.create,
|
|
14862
|
+
variables: slice.variables,
|
|
14863
|
+
testRunId
|
|
14864
|
+
});
|
|
14865
|
+
const create = createSchema.parse(resolution.createPayload);
|
|
14866
|
+
const res = await up(config, create, testRunId);
|
|
14867
|
+
const refsToken = readRefsToken(res.body);
|
|
14868
|
+
seeded.push({
|
|
14869
|
+
instance: index,
|
|
14870
|
+
testRunId,
|
|
14871
|
+
ok: res.ok,
|
|
14872
|
+
status: res.status,
|
|
14873
|
+
refsToken,
|
|
14874
|
+
body: res.ok ? void 0 : res.body
|
|
14875
|
+
});
|
|
14876
|
+
if (!res.ok) break;
|
|
14877
|
+
}
|
|
14878
|
+
const failed = seeded.find((instance) => !instance.ok);
|
|
14879
|
+
const teardown2 = await tearDownInstances(config, seeded);
|
|
14880
|
+
io.stdout(
|
|
14881
|
+
JSON.stringify(
|
|
14882
|
+
{
|
|
14883
|
+
ok: failed == null,
|
|
14884
|
+
action: "up",
|
|
14885
|
+
repeat: instances,
|
|
14886
|
+
instances: seeded,
|
|
14887
|
+
teardown: teardown2,
|
|
14888
|
+
hint: repeatHint(failed, instances, teardown2)
|
|
14889
|
+
},
|
|
14890
|
+
null,
|
|
14891
|
+
2
|
|
14892
|
+
) + "\n"
|
|
14893
|
+
);
|
|
14894
|
+
return failed == null && teardown2.every((result) => result.ok) ? 0 : 1;
|
|
14895
|
+
}
|
|
14896
|
+
async function tearDownInstances(config, seeded) {
|
|
14897
|
+
const results = [];
|
|
14898
|
+
for (const instance of [...seeded].reverse()) {
|
|
14899
|
+
if (instance.refsToken == null) {
|
|
14900
|
+
results.push({
|
|
14901
|
+
testRunId: instance.testRunId,
|
|
14902
|
+
ok: false,
|
|
14903
|
+
detail: "no refsToken in the up response - anything it wrote before failing is still there"
|
|
14904
|
+
});
|
|
14905
|
+
continue;
|
|
14906
|
+
}
|
|
14907
|
+
try {
|
|
14908
|
+
const res = await down(config, instance.refsToken);
|
|
14909
|
+
results.push({
|
|
14910
|
+
testRunId: instance.testRunId,
|
|
14911
|
+
ok: res.ok,
|
|
14912
|
+
detail: res.ok ? void 0 : `down returned HTTP ${res.status}`
|
|
14913
|
+
});
|
|
14914
|
+
} catch (err) {
|
|
14915
|
+
results.push({
|
|
14916
|
+
testRunId: instance.testRunId,
|
|
14917
|
+
ok: false,
|
|
14918
|
+
detail: `down failed: ${err instanceof Error ? err.message : String(err)}`
|
|
14919
|
+
});
|
|
14920
|
+
}
|
|
14921
|
+
}
|
|
14922
|
+
return results;
|
|
14923
|
+
}
|
|
14924
|
+
function repeatHint(failed, instances, teardown2) {
|
|
14925
|
+
const leaked = teardown2.filter((result) => !result.ok).map((result) => result.testRunId);
|
|
14926
|
+
const leakNote = leaked.length > 0 ? ` Teardown did NOT complete for ${leaked.join(", ")} - check the database by hand.` : "";
|
|
14927
|
+
if (failed == null) {
|
|
14928
|
+
return `All ${instances} instances were live at the same time, so nothing in this recipe is single-instance.${leakNote}`;
|
|
14929
|
+
}
|
|
14930
|
+
return `Instance ${failed.instance} failed to seed while instance ${failed.instance - 1} was still up (HTTP ${failed.status}). That is a value this recipe reuses across runs: read the error above for the constraint it violated, then put {{testRunId}} or {{testRunShortId}} inside that field - or derive it from the testRunId your factory receives. Do not weaken the constraint.${leakNote}`;
|
|
14931
|
+
}
|
|
14932
|
+
function readRefsToken(body) {
|
|
14933
|
+
if (!isRecord(body)) return void 0;
|
|
14934
|
+
return typeof body.refsToken === "string" ? body.refsToken : void 0;
|
|
14935
|
+
}
|
|
14936
|
+
async function runCheck(recipePath, io) {
|
|
14937
|
+
const read = await loadRecipeFile(recipePath);
|
|
14938
|
+
if (read.status === "absent") {
|
|
14939
|
+
io.stdout(
|
|
14940
|
+
JSON.stringify(
|
|
14941
|
+
{ ok: false, recipe: recipePath, problems: [`${recipePath} does not exist - write it first.`] },
|
|
14942
|
+
null,
|
|
14943
|
+
2
|
|
14944
|
+
) + "\n"
|
|
14945
|
+
);
|
|
14946
|
+
return 1;
|
|
14947
|
+
}
|
|
14948
|
+
if (read.status === "invalid") {
|
|
14949
|
+
io.stdout(JSON.stringify({ ok: false, recipe: recipePath, problems: read.problems }, null, 2) + "\n");
|
|
14950
|
+
return 1;
|
|
14951
|
+
}
|
|
14952
|
+
const problems = findRecipeUploadProblems(read.recipe);
|
|
14953
|
+
const summary = read.recipe.recipes.map((entry) => ({
|
|
14954
|
+
name: entry.name,
|
|
14955
|
+
entities: Object.keys(entry.create).length,
|
|
14956
|
+
records: Object.values(entry.create).reduce(
|
|
14957
|
+
(total, rows) => total + (Array.isArray(rows) ? rows.length : 0),
|
|
14958
|
+
0
|
|
14959
|
+
)
|
|
14960
|
+
}));
|
|
14961
|
+
io.stdout(
|
|
14962
|
+
JSON.stringify({ ok: problems.length === 0, recipe: recipePath, problems, recipes: summary }, null, 2) + "\n"
|
|
14963
|
+
);
|
|
14964
|
+
return problems.length === 0 ? 0 : 1;
|
|
14965
|
+
}
|
|
13696
14966
|
function emit2(io, res, resolution) {
|
|
13697
14967
|
const output = resolution == null ? { ok: res.ok, status: res.status, body: res.body } : {
|
|
13698
14968
|
ok: res.ok,
|
|
@@ -13722,26 +14992,8 @@ async function loadRecipeSlice(file) {
|
|
|
13722
14992
|
`${file} is not a valid recipe: expected a full recipe envelope, a { create } object, or a bare { Entity: [records] } map.`
|
|
13723
14993
|
);
|
|
13724
14994
|
}
|
|
13725
|
-
function
|
|
13726
|
-
|
|
13727
|
-
for (let i = 0; i < argv.length; i++) {
|
|
13728
|
-
const arg = argv[i];
|
|
13729
|
-
if (!arg.startsWith("--")) continue;
|
|
13730
|
-
const body = arg.slice(2);
|
|
13731
|
-
const eq = body.indexOf("=");
|
|
13732
|
-
if (eq !== -1) {
|
|
13733
|
-
flags[body.slice(0, eq)] = body.slice(eq + 1);
|
|
13734
|
-
continue;
|
|
13735
|
-
}
|
|
13736
|
-
const next = argv[i + 1];
|
|
13737
|
-
if (next != null && !next.startsWith("--")) {
|
|
13738
|
-
flags[body] = next;
|
|
13739
|
-
i++;
|
|
13740
|
-
} else {
|
|
13741
|
-
flags[body] = "true";
|
|
13742
|
-
}
|
|
13743
|
-
}
|
|
13744
|
-
return flags;
|
|
14995
|
+
function describeUnknownFlag(unknown) {
|
|
14996
|
+
return unknown.meant != null ? `--${unknown.given} (did you mean --${unknown.meant}?)` : `--${unknown.given}`;
|
|
13745
14997
|
}
|
|
13746
14998
|
function formatIssues(error) {
|
|
13747
14999
|
return error.issues.map((i) => i.path.length > 0 ? `--${i.path.join(".")}: ${i.message}` : i.message).join("; ");
|
|
@@ -13833,6 +15085,7 @@ function loadConfig(args) {
|
|
|
13833
15085
|
autonomaApiUrl: resolveApiUrl(env.AUTONOMA_API_URL),
|
|
13834
15086
|
autonomaApiToken: env.AUTONOMA_API_TOKEN,
|
|
13835
15087
|
autonomaGenerationId: env.AUTONOMA_GENERATION_ID,
|
|
15088
|
+
autonomaApplicationId: env.AUTONOMA_APPLICATION_ID,
|
|
13836
15089
|
agent: args.agent,
|
|
13837
15090
|
permissionMode: args.permissionMode
|
|
13838
15091
|
};
|
|
@@ -13841,8 +15094,228 @@ function loadConfig(args) {
|
|
|
13841
15094
|
// src/index.ts
|
|
13842
15095
|
init_analytics();
|
|
13843
15096
|
init_colors();
|
|
15097
|
+
|
|
15098
|
+
// src/core/dry-run-phase.ts
|
|
15099
|
+
init_esm_shims();
|
|
15100
|
+
init_prompts();
|
|
15101
|
+
init_debug();
|
|
15102
|
+
init_errors();
|
|
15103
|
+
init_logs();
|
|
15104
|
+
var TARGET_POLL_MS = 1e4;
|
|
15105
|
+
var TARGET_READY_TIMEOUT_MS = 20 * 6e4;
|
|
15106
|
+
var NO_PREVIEW_GRACE_MS = 6e4;
|
|
15107
|
+
var MAX_QUOTED_FAILURES = 3;
|
|
15108
|
+
var DEFAULT_TIMING = {
|
|
15109
|
+
pollMs: TARGET_POLL_MS,
|
|
15110
|
+
readyTimeoutMs: TARGET_READY_TIMEOUT_MS,
|
|
15111
|
+
noPreviewGraceMs: NO_PREVIEW_GRACE_MS
|
|
15112
|
+
};
|
|
15113
|
+
async function runDryRunPhase(deps) {
|
|
15114
|
+
const timing = deps.timing ?? DEFAULT_TIMING;
|
|
15115
|
+
const { client, applicationId } = deps;
|
|
15116
|
+
const [scenarios, listing] = await Promise.all([
|
|
15117
|
+
client.listScenarios(applicationId),
|
|
15118
|
+
client.listDryRunTargets(applicationId)
|
|
15119
|
+
]);
|
|
15120
|
+
if (scenarios.length === 0) {
|
|
15121
|
+
captureLog("info", "No scenarios to dry run", { source: "dry_run" });
|
|
15122
|
+
return { kind: "no-scenarios" };
|
|
15123
|
+
}
|
|
15124
|
+
const chosen = pickDryRunTarget(listing, deps.checkedOutBranch);
|
|
15125
|
+
if (chosen == null) {
|
|
15126
|
+
return {
|
|
15127
|
+
kind: "no-target",
|
|
15128
|
+
reason: "Autonoma has no preview environment to run your scenarios against yet. Open a pull request (or wait for your main preview to deploy) and run again."
|
|
15129
|
+
};
|
|
15130
|
+
}
|
|
15131
|
+
const unsupported = describeUnsupportedTarget(chosen);
|
|
15132
|
+
if (unsupported != null) return { kind: "unsupported-target", reason: unsupported };
|
|
15133
|
+
log.info(`Checking your Autonoma SDK against the "${chosen.label}" preview...`);
|
|
15134
|
+
const target = await waitForReadyTarget(client, applicationId, chosen, timing);
|
|
15135
|
+
if (target.kind === "gone") return { kind: "no-target", reason: target.reason };
|
|
15136
|
+
const discovery = await discoverSdk(client, applicationId, target.target, timing);
|
|
15137
|
+
if (discovery != null) return { kind: "discovery-failed", reason: discovery };
|
|
15138
|
+
return await dryRunScenarios(client, applicationId, target.target.id, scenarios);
|
|
15139
|
+
}
|
|
15140
|
+
function pickDryRunTarget(listing, checkedOutBranch) {
|
|
15141
|
+
const onCheckedOutBranch = checkedOutBranch == null ? void 0 : listing.targets.find((target) => target.branchName === checkedOutBranch);
|
|
15142
|
+
if (onCheckedOutBranch != null) return onCheckedOutBranch;
|
|
15143
|
+
const detected = listing.targets.find((target) => target.id === listing.autoDetectedTargetId);
|
|
15144
|
+
if (detected != null) return detected;
|
|
15145
|
+
return listing.targets.find((target) => target.availability === "ready");
|
|
15146
|
+
}
|
|
15147
|
+
function describeUnsupportedTarget(target) {
|
|
15148
|
+
if (target.source === "previewkit") return void 0;
|
|
15149
|
+
if (target.source === "vercel") {
|
|
15150
|
+
return `Your previews are built by Vercel, so the scenario dry run runs against the deployment you pick. Choose it on the SDK step in the Autonoma app and validate there - everything else in this run is done.`;
|
|
15151
|
+
}
|
|
15152
|
+
return `Your previews come from your own pipeline, and validating one needs the signing secret your pipeline signs with - which never leaves your side. Enter it on the SDK step in the Autonoma app to finish - everything else in this run is done.`;
|
|
15153
|
+
}
|
|
15154
|
+
async function waitForReadyTarget(client, applicationId, initial, timing) {
|
|
15155
|
+
const startedAt = Date.now();
|
|
15156
|
+
const deadline = startedAt + timing.readyTimeoutMs;
|
|
15157
|
+
const noPreviewDeadline = startedAt + Math.min(timing.noPreviewGraceMs, timing.readyTimeoutMs);
|
|
15158
|
+
let target = initial;
|
|
15159
|
+
while (true) {
|
|
15160
|
+
if (target.availability === "ready") return { kind: "ready", target };
|
|
15161
|
+
if (target.availability === "failed") {
|
|
15162
|
+
return {
|
|
15163
|
+
kind: "gone",
|
|
15164
|
+
reason: `The "${target.label}" preview failed to deploy, so there is nothing to run your scenarios against${target.error != null ? `: ${target.error}` : "."}`
|
|
15165
|
+
};
|
|
15166
|
+
}
|
|
15167
|
+
if (target.availability === "no_preview" && Date.now() >= noPreviewDeadline) {
|
|
15168
|
+
return {
|
|
15169
|
+
kind: "gone",
|
|
15170
|
+
reason: `The "${target.label}" pull request has no preview environment, so there is nothing to run your scenarios against. A draft pull request does not get one - mark it ready for review so a preview builds, then run again.`
|
|
15171
|
+
};
|
|
15172
|
+
}
|
|
15173
|
+
if (Date.now() >= deadline) {
|
|
15174
|
+
return {
|
|
15175
|
+
kind: "gone",
|
|
15176
|
+
reason: `The "${target.label}" preview was still building after ${Math.round(timing.readyTimeoutMs / 6e4)} minutes, so the scenario dry run was skipped. Run again once it is up.`
|
|
15177
|
+
};
|
|
15178
|
+
}
|
|
15179
|
+
debugLog("Waiting for the dry-run preview to deploy", {
|
|
15180
|
+
target: target.id,
|
|
15181
|
+
availability: target.availability
|
|
15182
|
+
});
|
|
15183
|
+
await sleep(timing.pollMs);
|
|
15184
|
+
const listing = await client.listDryRunTargets(applicationId).catch((err) => {
|
|
15185
|
+
debugLog("Could not re-read the dry-run targets", { err });
|
|
15186
|
+
return void 0;
|
|
15187
|
+
});
|
|
15188
|
+
const refreshed = listing?.targets.find((candidate) => candidate.id === target.id);
|
|
15189
|
+
if (refreshed != null) target = refreshed;
|
|
15190
|
+
}
|
|
15191
|
+
}
|
|
15192
|
+
async function discoverSdk(client, applicationId, target, timing) {
|
|
15193
|
+
try {
|
|
15194
|
+
const prepared = await client.prepareSdkTarget(applicationId, target.id);
|
|
15195
|
+
if (prepared.status === "redeploy_started") {
|
|
15196
|
+
log.info(`Redeploying the "${target.label}" preview with its Autonoma secrets...`);
|
|
15197
|
+
const back = await waitForRedeploy(client, applicationId, target, timing);
|
|
15198
|
+
if (back != null) return back;
|
|
15199
|
+
}
|
|
15200
|
+
for (const allowSelfHeal of [true, false]) {
|
|
15201
|
+
const result = await client.configureAndDiscoverSdkTarget(applicationId, target.id, allowSelfHeal);
|
|
15202
|
+
if (result.status === "discovered") {
|
|
15203
|
+
log.success("Your Autonoma SDK answered - Autonoma knows your data models.");
|
|
15204
|
+
captureLog("info", "SDK discovery succeeded", { source: "dry_run", target: target.id });
|
|
15205
|
+
return void 0;
|
|
15206
|
+
}
|
|
15207
|
+
log.info(`Refreshing the "${target.label}" preview's Autonoma secrets...`);
|
|
15208
|
+
const back = await waitForRedeploy(client, applicationId, target, timing);
|
|
15209
|
+
if (back != null) return back;
|
|
15210
|
+
}
|
|
15211
|
+
return `The "${target.label}" preview kept redeploying instead of answering. Try again from the Autonoma app.`;
|
|
15212
|
+
} catch (err) {
|
|
15213
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15214
|
+
captureLog("warn", "SDK discovery failed", { source: "dry_run", target: target.id });
|
|
15215
|
+
return `Your Autonoma SDK endpoint didn't answer, so the scenario dry run was skipped: ${message}
|
|
15216
|
+
Its own logs are on the SDK step in the Autonoma app, which is also where you can retry.`;
|
|
15217
|
+
}
|
|
15218
|
+
}
|
|
15219
|
+
async function waitForRedeploy(client, applicationId, target, timing) {
|
|
15220
|
+
const back = await waitForReadyTarget(client, applicationId, { ...target, availability: "building" }, timing);
|
|
15221
|
+
return back.kind === "ready" ? void 0 : back.reason;
|
|
15222
|
+
}
|
|
15223
|
+
async function dryRunScenarios(client, applicationId, targetId, scenarios) {
|
|
15224
|
+
log.info(`Running ${scenarios.length} scenario${scenarios.length === 1 ? "" : "s"} against your preview...`);
|
|
15225
|
+
const failures = [];
|
|
15226
|
+
for (const scenario of scenarios) {
|
|
15227
|
+
const result = await client.runScenarioDryRun(applicationId, scenario.id, targetId).catch((err) => ({ success: false, phase: void 0, error: err }));
|
|
15228
|
+
if (result.success) {
|
|
15229
|
+
log.success(`${scenario.name} - provisioned and torn down.`);
|
|
15230
|
+
continue;
|
|
15231
|
+
}
|
|
15232
|
+
const failure = {
|
|
15233
|
+
scenario: scenario.name,
|
|
15234
|
+
phase: result.phase,
|
|
15235
|
+
reason: formatDryRunError(result.error)
|
|
15236
|
+
};
|
|
15237
|
+
failures.push(failure);
|
|
15238
|
+
log.warn(`${scenario.name} - failed${failure.reason != null ? `: ${failure.reason}` : "."}`);
|
|
15239
|
+
}
|
|
15240
|
+
const passed = scenarios.length - failures.length;
|
|
15241
|
+
captureLog(failures.length > 0 ? "warn" : "info", "Scenario dry run finished", {
|
|
15242
|
+
source: "dry_run",
|
|
15243
|
+
scenario_count: scenarios.length,
|
|
15244
|
+
passed_count: passed
|
|
15245
|
+
});
|
|
15246
|
+
if (failures.length === 0) return { kind: "passed", scenarios: scenarios.length };
|
|
15247
|
+
return { kind: "failed", passed, failures };
|
|
15248
|
+
}
|
|
15249
|
+
function formatDryRunError(error) {
|
|
15250
|
+
if (error == null) return void 0;
|
|
15251
|
+
if (typeof error === "string") return error.length > 0 ? error : void 0;
|
|
15252
|
+
if (error instanceof Error) return error.message;
|
|
15253
|
+
return JSON.stringify(error);
|
|
15254
|
+
}
|
|
15255
|
+
function describeDryRunOutcome(outcome) {
|
|
15256
|
+
if (outcome.kind === "passed") return void 0;
|
|
15257
|
+
if (outcome.kind === "no-scenarios") {
|
|
15258
|
+
return "There were no scenarios to provision, so nothing was dry run. Autonoma's tests will run against whatever state your app is already in.";
|
|
15259
|
+
}
|
|
15260
|
+
if (outcome.kind === "no-target" || outcome.kind === "unsupported-target") return outcome.reason;
|
|
15261
|
+
if (outcome.kind === "discovery-failed") return outcome.reason;
|
|
15262
|
+
const quoted = outcome.failures.slice(0, MAX_QUOTED_FAILURES).map((failure) => {
|
|
15263
|
+
const where = failure.phase != null ? ` (${failure.phase})` : "";
|
|
15264
|
+
return ` - ${failure.scenario}${where}${failure.reason != null ? `: ${failure.reason}` : ""}`;
|
|
15265
|
+
}).join("\n");
|
|
15266
|
+
const rest = outcome.failures.length - MAX_QUOTED_FAILURES;
|
|
15267
|
+
return `${outcome.failures.length} of ${outcome.failures.length + outcome.passed} scenarios could not be provisioned:
|
|
15268
|
+
${quoted}${rest > 0 ? `
|
|
15269
|
+
...and ${rest} more` : ""}
|
|
15270
|
+
Fix them on the dry-run step in the Autonoma app, where a coding agent can read your SDK's own logs.`;
|
|
15271
|
+
}
|
|
15272
|
+
|
|
15273
|
+
// src/index.ts
|
|
13844
15274
|
init_errors();
|
|
13845
15275
|
|
|
15276
|
+
// src/core/finish-phase.ts
|
|
15277
|
+
init_esm_shims();
|
|
15278
|
+
init_debug();
|
|
15279
|
+
init_logs();
|
|
15280
|
+
init_onboarding_phase();
|
|
15281
|
+
async function runFinishPhase(deps) {
|
|
15282
|
+
const dryRun = await runDryRunPhase({
|
|
15283
|
+
client: deps.client,
|
|
15284
|
+
applicationId: deps.applicationId,
|
|
15285
|
+
checkedOutBranch: deps.checkedOutBranch,
|
|
15286
|
+
timing: deps.timing
|
|
15287
|
+
});
|
|
15288
|
+
const repair = dryRun.kind === "passed" ? void 0 : await deps.repair?.(dryRun);
|
|
15289
|
+
const state = await deps.client.getOnboardingState(deps.applicationId);
|
|
15290
|
+
const live = isStepAtOrPast(state.step, LIVE_STEP);
|
|
15291
|
+
debugLog("Finish phase complete", { dryRun: dryRun.kind, repair: repair?.kind, step: state.step, live });
|
|
15292
|
+
captureLog("info", "Front-door run finished", {
|
|
15293
|
+
source: "finish_phase",
|
|
15294
|
+
dry_run: dryRun.kind,
|
|
15295
|
+
repair: repair?.kind ?? "not_needed",
|
|
15296
|
+
step: state.step,
|
|
15297
|
+
live,
|
|
15298
|
+
artifacts_uploaded: state.artifactsUploaded,
|
|
15299
|
+
sdk_configured: state.sdkConfigured,
|
|
15300
|
+
dry_run_passed: state.dryRunPassed
|
|
15301
|
+
});
|
|
15302
|
+
return { dryRun, state, live };
|
|
15303
|
+
}
|
|
15304
|
+
function describeFinishPhase(result) {
|
|
15305
|
+
const { state } = result;
|
|
15306
|
+
return [
|
|
15307
|
+
`Test suite: ${state.artifactsUploaded ? "uploaded to Autonoma" : "not uploaded yet"}`,
|
|
15308
|
+
`Autonoma SDK: ${state.sdkConfigured ? "connected to your app" : "not answering yet"}`,
|
|
15309
|
+
`Scenario data: ${describeDryRunState(result)}`,
|
|
15310
|
+
result.live ? "Autonoma is reviewing your pull requests." : "Autonoma is not reviewing your pull requests yet - take your app live in the Autonoma app to finish."
|
|
15311
|
+
];
|
|
15312
|
+
}
|
|
15313
|
+
function describeDryRunState(result) {
|
|
15314
|
+
if (result.state.dryRunPassed) return "provisions against your preview";
|
|
15315
|
+
if (result.dryRun.kind === "no-scenarios") return "no scenarios to provision";
|
|
15316
|
+
return "not confirmed yet";
|
|
15317
|
+
}
|
|
15318
|
+
|
|
13846
15319
|
// src/core/flush-telemetry.ts
|
|
13847
15320
|
init_esm_shims();
|
|
13848
15321
|
init_replay_transport();
|
|
@@ -13852,6 +15325,176 @@ async function flushTelemetry() {
|
|
|
13852
15325
|
await Promise.all([flushAnalytics(), flushLogs(), flushReplay()]);
|
|
13853
15326
|
}
|
|
13854
15327
|
|
|
15328
|
+
// src/index.ts
|
|
15329
|
+
init_front_door();
|
|
15330
|
+
|
|
15331
|
+
// src/core/help.ts
|
|
15332
|
+
init_esm_shims();
|
|
15333
|
+
init_colors();
|
|
15334
|
+
var DOCS_URL = "https://docs.autonoma.app/test-planner/";
|
|
15335
|
+
var LLMS_URL = "https://docs.autonoma.app/llms.txt";
|
|
15336
|
+
var LLMS_FULL_URL = "https://docs.autonoma.app/llms-full.txt";
|
|
15337
|
+
var FLAGS = [
|
|
15338
|
+
{
|
|
15339
|
+
names: ["project"],
|
|
15340
|
+
argument: "<path>",
|
|
15341
|
+
summary: "The repository to plan tests for. Defaults to the current directory."
|
|
15342
|
+
},
|
|
15343
|
+
{
|
|
15344
|
+
names: ["non-interactive"],
|
|
15345
|
+
summary: "Never ask a question. Every input has to be a flag, the coding agent runs headless and autonomous, and the run authorizes with an API key instead of a browser."
|
|
15346
|
+
},
|
|
15347
|
+
{
|
|
15348
|
+
names: ["frontend"],
|
|
15349
|
+
argument: "<path>",
|
|
15350
|
+
summary: "The one frontend directory to plan tests for. Only needed in a repository with more than one - a single-app repository resolves itself."
|
|
15351
|
+
},
|
|
15352
|
+
{
|
|
15353
|
+
names: ["backend", "backends"],
|
|
15354
|
+
argument: "<path>",
|
|
15355
|
+
summary: "A backend or data layer that frontend talks to. Repeatable, and also accepts a comma-separated list. Omit to use the dependencies the run infers."
|
|
15356
|
+
},
|
|
15357
|
+
{
|
|
15358
|
+
names: ["agent", "coding-agent"],
|
|
15359
|
+
argument: "<claude|codex>",
|
|
15360
|
+
summary: "Which coding agent to hand the preview environment and the SDK integration to. Omit to use whichever is installed; required when both are and nobody can be asked."
|
|
15361
|
+
},
|
|
15362
|
+
{
|
|
15363
|
+
names: ["permission-mode"],
|
|
15364
|
+
argument: "<default|acceptEdits|bypassPermissions>",
|
|
15365
|
+
summary: "How much autonomy that agent runs with. Defaults to bypassPermissions, which is the only one that means anything without a human at the keyboard."
|
|
15366
|
+
},
|
|
15367
|
+
{
|
|
15368
|
+
names: ["resume"],
|
|
15369
|
+
summary: "Continue a previous run from where it stopped instead of starting over."
|
|
15370
|
+
},
|
|
15371
|
+
{
|
|
15372
|
+
names: ["fresh"],
|
|
15373
|
+
summary: "Discard a previous run's output and start over. The opposite of --resume."
|
|
15374
|
+
},
|
|
15375
|
+
{
|
|
15376
|
+
names: ["step"],
|
|
15377
|
+
argument: "<name>",
|
|
15378
|
+
summary: "Run a single step and stop. For debugging one step in isolation - it is not how a caller sequences a run, which happens in one invocation."
|
|
15379
|
+
},
|
|
15380
|
+
{
|
|
15381
|
+
names: ["model"],
|
|
15382
|
+
argument: "<id>",
|
|
15383
|
+
summary: "Override the model the analysis runs on."
|
|
15384
|
+
},
|
|
15385
|
+
{
|
|
15386
|
+
names: ["slug"],
|
|
15387
|
+
argument: "<name>",
|
|
15388
|
+
summary: "Override the output folder name under ~/.autonoma/."
|
|
15389
|
+
},
|
|
15390
|
+
{
|
|
15391
|
+
names: ["help"],
|
|
15392
|
+
summary: "Print this."
|
|
15393
|
+
}
|
|
15394
|
+
];
|
|
15395
|
+
var KNOWN_FLAGS = new Set(FLAGS.flatMap((flag) => flag.names));
|
|
15396
|
+
var FLAG_COLUMN = 34;
|
|
15397
|
+
var WHAT_IT_DOES = [
|
|
15398
|
+
"Autonoma's planner reads a codebase and produces the end-to-end test suite for it - the pages,",
|
|
15399
|
+
"the data models, the flows a user actually takes - and hands the parts that need judgement to a",
|
|
15400
|
+
"coding agent you already have installed. One invocation does the whole thing:",
|
|
15401
|
+
"",
|
|
15402
|
+
" 1. Preview environment Your coding agent sets up a real deployment of your app, built per",
|
|
15403
|
+
" pull request, that Autonoma tests against. Skipped when you already",
|
|
15404
|
+
" have one.",
|
|
15405
|
+
" 2. Project map Which directories are frontends, which are backends, which to ignore.",
|
|
15406
|
+
" 3. Pages and flows What your app can do, read from its source rather than guessed.",
|
|
15407
|
+
" 4. Knowledge base A written account of the app the later steps plan against.",
|
|
15408
|
+
" 5. Data models An audit of the entities your test data has to create.",
|
|
15409
|
+
" 6. Test scenarios The named app states your tests depend on.",
|
|
15410
|
+
" 7. Test data Your coding agent wires the Autonoma SDK into your repo and writes a",
|
|
15411
|
+
" factory per entity, validating each against your running app.",
|
|
15412
|
+
" 8. Test suite The test cases themselves, then uploaded to Autonoma.",
|
|
15413
|
+
"",
|
|
15414
|
+
"Nothing here is a step list to run one at a time. There is one command, it runs to the end, and",
|
|
15415
|
+
"it resumes from where it stopped if it is interrupted."
|
|
15416
|
+
];
|
|
15417
|
+
var HEADLESS = [
|
|
15418
|
+
"With --non-interactive there is nobody to answer a question, so anything that would have been",
|
|
15419
|
+
"asked has to arrive as a flag. What the run does about that:",
|
|
15420
|
+
"",
|
|
15421
|
+
" - It never opens a browser. The coding agent's Autonoma connection is authorized with the",
|
|
15422
|
+
" AUTONOMA_API_TOKEN this run already holds.",
|
|
15423
|
+
" - It never blocks on a question. Where it has to assume an answer it says so on stdout,",
|
|
15424
|
+
" naming what was asked and what it took.",
|
|
15425
|
+
" - It reports each step as it starts and finishes, with how long it took, so the process that",
|
|
15426
|
+
" launched it can follow along.",
|
|
15427
|
+
" - It refuses rather than guesses when a choice would be arbitrary - two coding agents",
|
|
15428
|
+
" installed and no --agent, several frontends and no --frontend."
|
|
15429
|
+
];
|
|
15430
|
+
var ENVIRONMENT = [
|
|
15431
|
+
" AUTONOMA_API_TOKEN Required. The run's credential. Create one in Settings -> API keys.",
|
|
15432
|
+
" AUTONOMA_APPLICATION_ID The Autonoma app this run belongs to. Set it and the run also sets",
|
|
15433
|
+
" up the preview environment and validates the result; leave it unset",
|
|
15434
|
+
" and the planner runs standalone against any repository.",
|
|
15435
|
+
" AUTONOMA_API_URL Point at a non-production Autonoma. Defaults to production.",
|
|
15436
|
+
" AUTONOMA_DEBUG Set to anything for verbose diagnostics on stderr.",
|
|
15437
|
+
" DONT_TRACK Set to anything to turn off analytics, logs and session replay."
|
|
15438
|
+
];
|
|
15439
|
+
function renderHelp() {
|
|
15440
|
+
return [
|
|
15441
|
+
`${BOLD}autonoma-planner${RESET} - generate an end-to-end test suite from your codebase`,
|
|
15442
|
+
"",
|
|
15443
|
+
`${BOLD}USAGE${RESET}`,
|
|
15444
|
+
" autonoma-planner [run] [flags] Plan and generate the suite. `run` may be omitted.",
|
|
15445
|
+
" autonoma-planner status Show what a previous run completed.",
|
|
15446
|
+
" autonoma-planner upload Re-upload an already-generated suite.",
|
|
15447
|
+
" autonoma-planner help Print this.",
|
|
15448
|
+
"",
|
|
15449
|
+
`${BOLD}WHAT IT DOES${RESET}`,
|
|
15450
|
+
...WHAT_IT_DOES.map(indent),
|
|
15451
|
+
"",
|
|
15452
|
+
`${BOLD}FLAGS${RESET}`,
|
|
15453
|
+
...FLAGS.map(renderFlag),
|
|
15454
|
+
"",
|
|
15455
|
+
`${BOLD}RUNNING WITHOUT A HUMAN${RESET}`,
|
|
15456
|
+
...HEADLESS.map(indent),
|
|
15457
|
+
"",
|
|
15458
|
+
`${BOLD}ENVIRONMENT${RESET}`,
|
|
15459
|
+
...ENVIRONMENT,
|
|
15460
|
+
"",
|
|
15461
|
+
`${BOLD}DOCUMENTATION${RESET}`,
|
|
15462
|
+
` ${DOCS_URL}`,
|
|
15463
|
+
` ${DIM}${LLMS_URL} - every page, as a list, for an agent to read${RESET}`,
|
|
15464
|
+
` ${DIM}${LLMS_FULL_URL} - all of it in one file${RESET}`,
|
|
15465
|
+
""
|
|
15466
|
+
].join("\n");
|
|
15467
|
+
}
|
|
15468
|
+
function indent(line) {
|
|
15469
|
+
return line.length > 0 ? ` ${line}` : line;
|
|
15470
|
+
}
|
|
15471
|
+
function renderFlag(flag) {
|
|
15472
|
+
const spellings = flag.names.map((name) => `--${name}`).join(", ");
|
|
15473
|
+
const left = ` ${spellings}${flag.argument != null ? ` ${flag.argument}` : ""}`;
|
|
15474
|
+
const indented = wrap(flag.summary, 92 - FLAG_COLUMN).map((line) => `${" ".repeat(FLAG_COLUMN)}${line}`);
|
|
15475
|
+
if (left.length >= FLAG_COLUMN) return [left, ...indented].join("\n");
|
|
15476
|
+
const [first, ...rest] = indented;
|
|
15477
|
+
return [`${left}${(first ?? "").slice(left.length)}`, ...rest].join("\n");
|
|
15478
|
+
}
|
|
15479
|
+
function wrap(text2, width) {
|
|
15480
|
+
const lines = [];
|
|
15481
|
+
let current = "";
|
|
15482
|
+
for (const word of text2.split(" ")) {
|
|
15483
|
+
if (current.length === 0) {
|
|
15484
|
+
current = word;
|
|
15485
|
+
continue;
|
|
15486
|
+
}
|
|
15487
|
+
if (current.length + 1 + word.length > width) {
|
|
15488
|
+
lines.push(current);
|
|
15489
|
+
current = word;
|
|
15490
|
+
continue;
|
|
15491
|
+
}
|
|
15492
|
+
current = `${current} ${word}`;
|
|
15493
|
+
}
|
|
15494
|
+
if (current.length > 0) lines.push(current);
|
|
15495
|
+
return lines;
|
|
15496
|
+
}
|
|
15497
|
+
|
|
13855
15498
|
// src/index.ts
|
|
13856
15499
|
init_interrupt();
|
|
13857
15500
|
init_logs();
|
|
@@ -14033,12 +15676,13 @@ async function uploadArtifacts(config, outputDir) {
|
|
|
14033
15676
|
await postJson(`${setupUrl}/artifacts`, autonomaApiToken, { testCases, artifacts, commitSha: gitInfo?.sha });
|
|
14034
15677
|
await patchJson(setupUrl, autonomaApiToken, { status: "completed" });
|
|
14035
15678
|
log.success(
|
|
14036
|
-
`Uploaded ${testCases.length} test case${testCases.length === 1 ? "" : "s"} and ${artifacts.length} artifact${artifacts.length === 1 ? "" : "s"}
|
|
15679
|
+
`Uploaded ${testCases.length} test case${testCases.length === 1 ? "" : "s"} and ${artifacts.length} artifact${artifacts.length === 1 ? "" : "s"}.`
|
|
14037
15680
|
);
|
|
14038
15681
|
}
|
|
14039
15682
|
|
|
14040
15683
|
// src/index.ts
|
|
14041
15684
|
init_version();
|
|
15685
|
+
init_eta();
|
|
14042
15686
|
init_steps();
|
|
14043
15687
|
init_store();
|
|
14044
15688
|
process.env.NODE_ENV ??= "production";
|
|
@@ -14074,26 +15718,10 @@ async function collectRunStats(outputDir) {
|
|
|
14074
15718
|
stat3(tests, "E2E test", "E2E tests")
|
|
14075
15719
|
];
|
|
14076
15720
|
}
|
|
14077
|
-
function
|
|
14078
|
-
const args
|
|
14079
|
-
|
|
14080
|
-
const arg = argv[i];
|
|
14081
|
-
if (arg.startsWith("--")) {
|
|
14082
|
-
const key = arg.slice(2);
|
|
14083
|
-
const next = argv[i + 1];
|
|
14084
|
-
if (next && !next.startsWith("--")) {
|
|
14085
|
-
args[key] = next;
|
|
14086
|
-
i++;
|
|
14087
|
-
} else {
|
|
14088
|
-
args[key] = true;
|
|
14089
|
-
}
|
|
14090
|
-
}
|
|
15721
|
+
function warnAboutUnknownFlags(args) {
|
|
15722
|
+
for (const { given, meant } of args.unrecognized(KNOWN_FLAGS)) {
|
|
15723
|
+
log.warn(`Unknown flag --${given}${meant != null ? `. Did you mean --${meant}?` : "."} Ignoring it.`);
|
|
14091
15724
|
}
|
|
14092
|
-
return args;
|
|
14093
|
-
}
|
|
14094
|
-
function strArg(args, key) {
|
|
14095
|
-
const value = args[key];
|
|
14096
|
-
return typeof value === "string" ? value : void 0;
|
|
14097
15725
|
}
|
|
14098
15726
|
var STEP_LABELS = {
|
|
14099
15727
|
projectMapper: "Map your project structure",
|
|
@@ -14140,8 +15768,9 @@ async function promptScopeSelection(map) {
|
|
|
14140
15768
|
}
|
|
14141
15769
|
async function runStep(step, outputDir, state, config, nonInteractive, retryGuidance) {
|
|
14142
15770
|
const label = STEP_LABELS[step];
|
|
14143
|
-
note(STEP_INTROS[step], `Step: ${label}`);
|
|
15771
|
+
note(STEP_INTROS[step], `Step ${STEP_ORDER.indexOf(step) + 1}/${STEP_ORDER.length}: ${label}`);
|
|
14144
15772
|
const stepStartedAt = Date.now();
|
|
15773
|
+
const elapsed = () => formatClock(Date.now() - stepStartedAt);
|
|
14145
15774
|
track("cli_step_started", { step });
|
|
14146
15775
|
captureLog("info", `Step started: ${label}`, {
|
|
14147
15776
|
source: "pipeline",
|
|
@@ -14251,7 +15880,7 @@ async function runStep(step, outputDir, state, config, nonInteractive, retryGuid
|
|
|
14251
15880
|
}
|
|
14252
15881
|
case "recipeBuilder": {
|
|
14253
15882
|
const { runRecipeBuilder: runRecipeBuilder2 } = await Promise.resolve().then(() => (init_recipe_builder(), recipe_builder_exports));
|
|
14254
|
-
const { parsePermissionMode: parsePermissionMode2 } = await Promise.resolve().then(() => (
|
|
15883
|
+
const { parsePermissionMode: parsePermissionMode2 } = await Promise.resolve().then(() => (init_coding_agent(), coding_agent_exports));
|
|
14255
15884
|
result = await runRecipeBuilder2({
|
|
14256
15885
|
projectRoot: config.projectRoot,
|
|
14257
15886
|
outputDir,
|
|
@@ -14283,26 +15912,26 @@ async function runStep(step, outputDir, state, config, nonInteractive, retryGuid
|
|
|
14283
15912
|
if (result && !result.success) {
|
|
14284
15913
|
if (result.paused) {
|
|
14285
15914
|
state = await markStep(outputDir, state, step, "paused");
|
|
14286
|
-
log.info(`Paused: ${label} - ${result.summary}`);
|
|
15915
|
+
log.info(`Paused after ${elapsed()}: ${label} - ${result.summary}`);
|
|
14287
15916
|
} else {
|
|
14288
15917
|
state = await markStep(outputDir, state, step, "failed");
|
|
14289
|
-
log.error(`Failed: ${label} - ${result.summary}`);
|
|
15918
|
+
log.error(`Failed after ${elapsed()}: ${label} - ${result.summary}`);
|
|
14290
15919
|
trackError(new Error(result.summary), { step, source: "step_result" });
|
|
14291
15920
|
}
|
|
14292
15921
|
} else {
|
|
14293
15922
|
state = await markStep(outputDir, state, step, "done");
|
|
14294
|
-
log.success(`Completed: ${label}`);
|
|
15923
|
+
log.success(`Completed in ${elapsed()}: ${label}`);
|
|
14295
15924
|
}
|
|
14296
15925
|
} catch (err) {
|
|
14297
15926
|
if (isUserCancellation(err)) throw err;
|
|
14298
15927
|
state = await markStep(outputDir, state, step, "failed");
|
|
14299
15928
|
const known = describeKnownError(err);
|
|
14300
15929
|
if (known) {
|
|
14301
|
-
log.error(`Failed: ${label} - ${known.title}`);
|
|
15930
|
+
log.error(`Failed after ${elapsed()}: ${label} - ${known.title}`);
|
|
14302
15931
|
log.info(known.hint);
|
|
14303
15932
|
} else {
|
|
14304
15933
|
const message = err instanceof Error ? err.message : String(err);
|
|
14305
|
-
log.error(`Failed: ${label} - ${message}`);
|
|
15934
|
+
log.error(`Failed after ${elapsed()}: ${label} - ${message}`);
|
|
14306
15935
|
console.error(`\x1B[2m${formatException(err)}\x1B[0m`);
|
|
14307
15936
|
console.error(`\x1B[2m${supportReference({ step })}\x1B[0m`);
|
|
14308
15937
|
log.info("If you report this, please include the error output above.");
|
|
@@ -14416,16 +16045,45 @@ function ensureAutonomaAuth() {
|
|
|
14416
16045
|
);
|
|
14417
16046
|
return false;
|
|
14418
16047
|
}
|
|
16048
|
+
async function finishFrontDoorRun(frontDoor, config, nonInteractive) {
|
|
16049
|
+
try {
|
|
16050
|
+
const gitInfo = await readGitInfo(config.projectRoot);
|
|
16051
|
+
const result = await runFinishPhase({
|
|
16052
|
+
client: frontDoor.client,
|
|
16053
|
+
applicationId: frontDoor.applicationId,
|
|
16054
|
+
checkedOutBranch: gitInfo?.branch,
|
|
16055
|
+
// Only reached when the direct calls did not pass. What is wrong then needs
|
|
16056
|
+
// the repo and a decision, which is an agent's job rather than an API call's.
|
|
16057
|
+
repair: () => runSdkRepairHandoff({ plan: frontDoor, config, nonInteractive })
|
|
16058
|
+
});
|
|
16059
|
+
track("cli_dry_run_finished", { outcome: result.dryRun.kind, live: result.live });
|
|
16060
|
+
const problem = describeDryRunOutcome(result.dryRun);
|
|
16061
|
+
if (problem != null) log.warn(problem);
|
|
16062
|
+
note(describeFinishPhase(result).join("\n"), "Where your app stands");
|
|
16063
|
+
return result;
|
|
16064
|
+
} catch (err) {
|
|
16065
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
16066
|
+
log.warn(`Couldn't confirm your scenarios against a preview: ${message}`);
|
|
16067
|
+
log.info("Your test suite is uploaded either way - finish the dry run in the Autonoma app.");
|
|
16068
|
+
trackError(err, { source: "finish_phase" });
|
|
16069
|
+
return void 0;
|
|
16070
|
+
}
|
|
16071
|
+
}
|
|
16072
|
+
function nextStepLine(finish, nonInteractive) {
|
|
16073
|
+
if (finish?.live === true) return "Autonoma is reviewing your pull requests - open one to see it work.";
|
|
16074
|
+
if (nonInteractive) return "Setup is not finished - the steps above that are still outstanding say why.";
|
|
16075
|
+
return "Next: continue on autonoma.app";
|
|
16076
|
+
}
|
|
14419
16077
|
async function main() {
|
|
14420
16078
|
installTerminationDiagnostics();
|
|
14421
|
-
const args =
|
|
16079
|
+
const args = CliArgs.parse(process.argv.slice(2));
|
|
14422
16080
|
const command = process.argv[2];
|
|
14423
16081
|
if (command === "status") {
|
|
14424
16082
|
const config2 = loadConfig({
|
|
14425
|
-
project:
|
|
14426
|
-
slug:
|
|
16083
|
+
project: args.value("project"),
|
|
16084
|
+
slug: args.value("slug")
|
|
14427
16085
|
});
|
|
14428
|
-
if (!args.project) {
|
|
16086
|
+
if (!args.has("project")) {
|
|
14429
16087
|
console.log(`No --project flag passed; using current working directory: ${config2.projectRoot}`);
|
|
14430
16088
|
}
|
|
14431
16089
|
const outputDir2 = await ensureOutputDir(config2.projectSlug);
|
|
@@ -14434,8 +16092,8 @@ async function main() {
|
|
|
14434
16092
|
}
|
|
14435
16093
|
if (command === "upload") {
|
|
14436
16094
|
const config2 = loadConfig({
|
|
14437
|
-
project:
|
|
14438
|
-
slug:
|
|
16095
|
+
project: args.value("project"),
|
|
16096
|
+
slug: args.value("slug")
|
|
14439
16097
|
});
|
|
14440
16098
|
const outputDir2 = await ensureOutputDir(config2.projectSlug);
|
|
14441
16099
|
const recipeUploaded = await uploadRecipeFromDisk(outputDir2, config2);
|
|
@@ -14451,19 +16109,13 @@ async function main() {
|
|
|
14451
16109
|
});
|
|
14452
16110
|
process.exit(exitCode);
|
|
14453
16111
|
}
|
|
14454
|
-
if (command === "help" || args.help) {
|
|
14455
|
-
console.log(
|
|
14456
|
-
console.log(
|
|
14457
|
-
" test-planner [run] [--project <path>] [--frontend <path>] [--backends <path,path>] [--model <id>] [--step <name>] [--resume] [--non-interactive] [--agent <claude|codex>] [--permission-mode <default|acceptEdits|bypassPermissions>]"
|
|
14458
|
-
);
|
|
14459
|
-
console.log(" test-planner status [--project <path>]");
|
|
14460
|
-
console.log(" test-planner upload [--project <path>] # re-upload already-generated recipe + artifacts");
|
|
14461
|
-
console.log("");
|
|
14462
|
-
console.log("`run` is the default command; it may be omitted.");
|
|
16112
|
+
if (command === "help" || args.has("help")) {
|
|
16113
|
+
console.log(renderHelp());
|
|
14463
16114
|
return;
|
|
14464
16115
|
}
|
|
14465
16116
|
console.log(BANNER);
|
|
14466
|
-
const
|
|
16117
|
+
const projectArg = args.value("project");
|
|
16118
|
+
const resumeCommand = `autonoma-planner --resume` + (projectArg != null ? ` --project ${projectArg}` : "");
|
|
14467
16119
|
let mountedUi;
|
|
14468
16120
|
installInterruptHandler({
|
|
14469
16121
|
// exitCode defaults to 0 for a user-initiated Ctrl+C (progress saved, a clean stop);
|
|
@@ -14481,24 +16133,24 @@ async function main() {
|
|
|
14481
16133
|
void flushTelemetry().finally(() => process.exit(exitCode));
|
|
14482
16134
|
}
|
|
14483
16135
|
});
|
|
14484
|
-
const backendsArg = strArg(args, "backends");
|
|
14485
16136
|
const config = loadConfig({
|
|
14486
|
-
project:
|
|
14487
|
-
model:
|
|
14488
|
-
slug:
|
|
14489
|
-
frontend:
|
|
14490
|
-
backends:
|
|
14491
|
-
agent:
|
|
14492
|
-
permissionMode:
|
|
16137
|
+
project: projectArg,
|
|
16138
|
+
model: args.value("model"),
|
|
16139
|
+
slug: args.value("slug"),
|
|
16140
|
+
frontend: args.value("frontend"),
|
|
16141
|
+
backends: args.list("backend", "backends"),
|
|
16142
|
+
agent: args.value("agent", "coding-agent"),
|
|
16143
|
+
permissionMode: args.value("permission-mode")
|
|
14493
16144
|
});
|
|
14494
16145
|
initSession({ generationId: config.autonomaGenerationId, projectSlug: config.projectSlug });
|
|
14495
16146
|
intro("Let's generate your test suite");
|
|
14496
16147
|
if (!ensureAutonomaAuth()) {
|
|
14497
16148
|
return;
|
|
14498
16149
|
}
|
|
14499
|
-
|
|
16150
|
+
warnAboutUnknownFlags(args);
|
|
16151
|
+
const nonInteractive = args.has("non-interactive");
|
|
14500
16152
|
const modelName = config.modelId ?? readEnv().OPENROUTER_MODEL ?? DEFAULT_MODEL;
|
|
14501
|
-
if (
|
|
16153
|
+
if (projectArg == null) {
|
|
14502
16154
|
log.info(`No --project flag passed; using current working directory.`);
|
|
14503
16155
|
}
|
|
14504
16156
|
log.info(`Project: ${config.projectRoot}`);
|
|
@@ -14519,12 +16171,26 @@ async function main() {
|
|
|
14519
16171
|
}
|
|
14520
16172
|
}
|
|
14521
16173
|
if (!nonInteractive) mountedUi = await mountDashboard(outputDir, config.projectSlug);
|
|
14522
|
-
|
|
16174
|
+
if (args.has("fresh") && args.has("resume")) {
|
|
16175
|
+
mountedUi?.unmount();
|
|
16176
|
+
mountedUi = void 0;
|
|
16177
|
+
log.error("--fresh and --resume ask for opposite things. Pass one of them, not both.");
|
|
16178
|
+
process.exitCode = 1;
|
|
16179
|
+
return;
|
|
16180
|
+
}
|
|
16181
|
+
let isResuming = args.has("resume") || args.has("step");
|
|
14523
16182
|
const hasProgress = Object.values(state.steps).some((s) => s === "done" || s === "running");
|
|
16183
|
+
const { planFrontDoor: planFrontDoor2 } = await Promise.resolve().then(() => (init_front_door(), front_door_exports));
|
|
16184
|
+
const frontDoor = await planFrontDoor2(config);
|
|
16185
|
+
const previewPending = frontDoor?.phase === "preview";
|
|
14524
16186
|
if (!nonInteractive && !isResuming && !hasProgress) {
|
|
14525
16187
|
await welcome({
|
|
14526
|
-
title: "Let's build your test suite.",
|
|
14527
|
-
lines: [
|
|
16188
|
+
title: previewPending ? "Let's get Autonoma running on your app." : "Let's build your test suite.",
|
|
16189
|
+
lines: previewPending ? [
|
|
16190
|
+
"First your coding agent sets up a preview environment - a real deployment of your app, built per pull request, that Autonoma tests against. It reads your repo and works out how to build and run it.",
|
|
16191
|
+
"Then Autonoma analyzes your codebase - its pages, data models, and user flows - and generates a full suite of end-to-end test cases that cover them.",
|
|
16192
|
+
"It all happens right here, in this terminal, so you can watch and steer it."
|
|
16193
|
+
] : [
|
|
14528
16194
|
"Autonoma analyzes your codebase - its pages, data models, and user flows - and generates a full suite of end-to-end test cases that cover them, so you get real test coverage without writing a single test yourself.",
|
|
14529
16195
|
"It takes a little while, and the whole run happens right here so you can watch it work.",
|
|
14530
16196
|
"This analysis is free for new accounts."
|
|
@@ -14532,7 +16198,23 @@ async function main() {
|
|
|
14532
16198
|
cta: "Press enter to begin"
|
|
14533
16199
|
});
|
|
14534
16200
|
}
|
|
14535
|
-
if (
|
|
16201
|
+
if (frontDoor != null && previewPending) {
|
|
16202
|
+
const { runPreviewHandoff: runPreviewHandoff2, describeIncompletePreview: describeIncompletePreview2 } = await Promise.resolve().then(() => (init_front_door(), front_door_exports));
|
|
16203
|
+
const result = await runPreviewHandoff2({ plan: frontDoor, config, nonInteractive });
|
|
16204
|
+
track("cli_preview_phase_finished", { outcome: result.kind });
|
|
16205
|
+
const problem = describeIncompletePreview2(result);
|
|
16206
|
+
if (problem != null) log.warn(problem);
|
|
16207
|
+
}
|
|
16208
|
+
if (args.has("fresh") && hasProgress) {
|
|
16209
|
+
await clearOutputDir(config.projectSlug);
|
|
16210
|
+
state = initialState();
|
|
16211
|
+
await saveState(outputDir, state);
|
|
16212
|
+
log.info(`Cleared ${displayPath(outputDir)} - starting from scratch.`);
|
|
16213
|
+
} else if (!isResuming && nonInteractive && hasProgress) {
|
|
16214
|
+
log.info(
|
|
16215
|
+
"A previous run left output in this folder and no flag said what to do with it, so this run continues from it. Pass --fresh to discard it, or --resume to say so deliberately."
|
|
16216
|
+
);
|
|
16217
|
+
} else if (!isResuming && !nonInteractive && hasProgress) {
|
|
14536
16218
|
const completedSteps = Object.entries(state.steps).filter(([, s]) => s === "done").map(([name]) => isStepName(name) ? STEP_LABELS[name] : name).join(", ");
|
|
14537
16219
|
const resume2 = await confirm({
|
|
14538
16220
|
message: `Found a previous run${completedSteps ? ` (completed: ${completedSteps})` : ""}. Resume from where you left off?`,
|
|
@@ -14561,7 +16243,7 @@ It's a hidden folder in your home directory - in Finder/Explorer use "Go to fold
|
|
|
14561
16243
|
or reveal hidden files (macOS: Cmd+Shift+. ) to see it.`,
|
|
14562
16244
|
"Output folder"
|
|
14563
16245
|
);
|
|
14564
|
-
const stepArg =
|
|
16246
|
+
const stepArg = args.value("step");
|
|
14565
16247
|
const targetStep = stepArg != null && isStepName(stepArg) ? stepArg : void 0;
|
|
14566
16248
|
if (stepArg != null && targetStep == null) {
|
|
14567
16249
|
mountedUi?.unmount();
|
|
@@ -14580,7 +16262,7 @@ or reveal hidden files (macOS: Cmd+Shift+. ) to see it.`,
|
|
|
14580
16262
|
mountedUi?.unmount();
|
|
14581
16263
|
mountedUi = void 0;
|
|
14582
16264
|
if (state.steps[targetStep] === "failed") {
|
|
14583
|
-
const retryCommand = `autonoma-planner --step ${targetStep}` + (
|
|
16265
|
+
const retryCommand = `autonoma-planner --step ${targetStep}` + (projectArg != null ? ` --project ${projectArg}` : "");
|
|
14584
16266
|
log.warn(`Your progress is saved. To retry this step, run:
|
|
14585
16267
|
${retryCommand}`);
|
|
14586
16268
|
process.exitCode = 1;
|
|
@@ -14597,7 +16279,12 @@ or reveal hidden files (macOS: Cmd+Shift+. ) to see it.`,
|
|
|
14597
16279
|
}
|
|
14598
16280
|
const steps = STEP_ORDER;
|
|
14599
16281
|
const startIdx = steps.indexOf(startStep);
|
|
14600
|
-
note(
|
|
16282
|
+
note(
|
|
16283
|
+
`${steps.map((s, idx) => `${idx + 1}. ${STEP_LABELS[s]} - ${STEP_SUMMARIES[s]}`).join("\n")}
|
|
16284
|
+
|
|
16285
|
+
All of it runs from this one command - there is nothing to launch step by step.`,
|
|
16286
|
+
"Here's the plan"
|
|
16287
|
+
);
|
|
14601
16288
|
try {
|
|
14602
16289
|
for (let i = startIdx; i < steps.length; i++) {
|
|
14603
16290
|
const step = steps[i];
|
|
@@ -14643,13 +16330,14 @@ or reveal hidden files (macOS: Cmd+Shift+. ) to see it.`,
|
|
|
14643
16330
|
trackError(err, { source: "artifact_upload" });
|
|
14644
16331
|
}
|
|
14645
16332
|
}
|
|
16333
|
+
const finish = frontDoor != null && allStepsDone ? await finishFrontDoorRun(frontDoor, config, nonInteractive) : void 0;
|
|
14646
16334
|
const anyFailed = Object.values(state.steps).some((s) => s === "failed");
|
|
14647
16335
|
getActiveStore()?.finish({ kind: allStepsDone ? "complete" : anyFailed ? "failed" : "paused" });
|
|
14648
16336
|
if (allStepsDone) {
|
|
14649
16337
|
const choice = await completion({
|
|
14650
16338
|
title: "Your test suite is ready.",
|
|
14651
16339
|
stats: await collectRunStats(outputDir),
|
|
14652
|
-
lines: [`Saved in ${displayPath(outputDir)}`,
|
|
16340
|
+
lines: [`Saved in ${displayPath(outputDir)}`, nextStepLine(finish, nonInteractive)]
|
|
14653
16341
|
});
|
|
14654
16342
|
track("cli_completion_choice", { choice });
|
|
14655
16343
|
if (choice === "browse") {
|
|
@@ -14662,7 +16350,8 @@ or reveal hidden files (macOS: Cmd+Shift+. ) to see it.`,
|
|
|
14662
16350
|
if (allStepsDone) {
|
|
14663
16351
|
log.success("Your test suite is ready.");
|
|
14664
16352
|
log.info(`Saved in ${displayPath(outputDir)}`);
|
|
14665
|
-
|
|
16353
|
+
if (finish != null) for (const line of describeFinishPhase(finish)) log.info(line);
|
|
16354
|
+
log.info(nextStepLine(finish, nonInteractive));
|
|
14666
16355
|
}
|
|
14667
16356
|
outro("Done");
|
|
14668
16357
|
}
|