@autonoma-ai/planner 0.1.13 → 0.1.14-canary.9e91c81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +79 -69
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -604,7 +604,7 @@ async function runAgent(config, prompt, extractResult) {
|
|
|
604
604
|
err
|
|
605
605
|
);
|
|
606
606
|
};
|
|
607
|
-
const
|
|
607
|
+
const YELLOW4 = "\x1B[33m";
|
|
608
608
|
const RESET8 = "\x1B[0m";
|
|
609
609
|
for (let modelIdx = 0; modelIdx < modelsToTry.length; modelIdx++) {
|
|
610
610
|
const model = modelsToTry[modelIdx];
|
|
@@ -630,7 +630,7 @@ async function runAgent(config, prompt, extractResult) {
|
|
|
630
630
|
while (extractResult() === void 0 && nudges < MAX_NUDGES) {
|
|
631
631
|
nudges++;
|
|
632
632
|
console.log(
|
|
633
|
-
` ${
|
|
633
|
+
` ${YELLOW4}[${config.id}] agent stopped without finishing \u2014 nudging (${nudges}/${MAX_NUDGES})...${RESET8}`
|
|
634
634
|
);
|
|
635
635
|
track("cli_agent_nudged", { agent: config.id, nudge: nudges });
|
|
636
636
|
messages.push(...generation.response.messages);
|
|
@@ -646,14 +646,14 @@ async function runAgent(config, prompt, extractResult) {
|
|
|
646
646
|
if (errorClass === "fatal") fail(err, model);
|
|
647
647
|
const msg = err instanceof Error ? err.message : String(err);
|
|
648
648
|
if (errorClass === "timeout") {
|
|
649
|
-
console.log(` ${
|
|
649
|
+
console.log(` ${YELLOW4}[${config.id}] step timed out after ${stepTimeout / 1e3}s${RESET8}`);
|
|
650
650
|
} else {
|
|
651
|
-
console.log(` ${
|
|
651
|
+
console.log(` ${YELLOW4}[${config.id}] provider error: ${msg}${RESET8}`);
|
|
652
652
|
}
|
|
653
653
|
track("cli_agent_retryable_error", { agent: config.id, error_class: errorClass, retry });
|
|
654
654
|
if (retry < RETRIES_BEFORE_FALLBACK - 1) {
|
|
655
655
|
console.log(
|
|
656
|
-
` ${
|
|
656
|
+
` ${YELLOW4}[${config.id}] retrying (${retry + 1}/${RETRIES_BEFORE_FALLBACK})...${RESET8}`
|
|
657
657
|
);
|
|
658
658
|
if (errorClass === "transient") {
|
|
659
659
|
await sleep(Math.min(2e3 * 2 ** retry, 1e4));
|
|
@@ -663,7 +663,7 @@ async function runAgent(config, prompt, extractResult) {
|
|
|
663
663
|
if (modelIdx < modelsToTry.length - 1) {
|
|
664
664
|
const nextModel = FALLBACK_MODELS[modelIdx];
|
|
665
665
|
console.log(
|
|
666
|
-
` ${
|
|
666
|
+
` ${YELLOW4}[${config.id}] ${RETRIES_BEFORE_FALLBACK} failed attempts, switching to ${nextModel}${RESET8}`
|
|
667
667
|
);
|
|
668
668
|
break;
|
|
669
669
|
}
|
|
@@ -2839,19 +2839,10 @@ async function parseScenario(outputDir) {
|
|
|
2839
2839
|
}
|
|
2840
2840
|
try {
|
|
2841
2841
|
const data = matter3(raw).data;
|
|
2842
|
-
const varsByEntity = /* @__PURE__ */ new Map();
|
|
2843
|
-
for (const vf of data.variable_fields ?? []) {
|
|
2844
|
-
const entity = vf?.entity != null ? String(vf.entity) : "";
|
|
2845
|
-
const field = vf?.field != null ? String(vf.field) : "";
|
|
2846
|
-
if (!entity || !field) continue;
|
|
2847
|
-
const list = varsByEntity.get(entity) ?? [];
|
|
2848
|
-
list.push(field);
|
|
2849
|
-
varsByEntity.set(entity, list);
|
|
2850
|
-
}
|
|
2851
2842
|
const entityTypes = (data.entity_types ?? []).map((e) => {
|
|
2852
2843
|
const name = e?.name != null ? String(e.name).trim() : "";
|
|
2853
2844
|
const count = typeof e?.count === "number" ? e.count : Number(e?.count ?? 0) || 0;
|
|
2854
|
-
return { name, count
|
|
2845
|
+
return { name, count };
|
|
2855
2846
|
}).filter((e) => e.name.length > 0);
|
|
2856
2847
|
const scenarioNames = (data.scenarios ?? []).map((s) => s?.name != null ? String(s.name).trim() : "").filter((n) => n.length > 0);
|
|
2857
2848
|
return { scenarioNames, entityTypes };
|
|
@@ -2859,6 +2850,23 @@ async function parseScenario(outputDir) {
|
|
|
2859
2850
|
return { scenarioNames: [], entityTypes: [] };
|
|
2860
2851
|
}
|
|
2861
2852
|
}
|
|
2853
|
+
function validateScenarioIsConcrete(content) {
|
|
2854
|
+
const errors = [];
|
|
2855
|
+
const checks = [
|
|
2856
|
+
{ pattern: /\{\{[a-zA-Z0-9_]+\}\}/g, label: "{{token}} placeholder" },
|
|
2857
|
+
{ pattern: /(?<!\{)\{[a-z][a-zA-Z]*\}(?!\})/g, label: "bare {variable} placeholder" },
|
|
2858
|
+
{ pattern: /^\s*variable_fields\s*:/m, label: "variable_fields block" }
|
|
2859
|
+
];
|
|
2860
|
+
for (const { pattern, label } of checks) {
|
|
2861
|
+
const match = content.match(pattern);
|
|
2862
|
+
if (match && match.length > 0) {
|
|
2863
|
+
errors.push(
|
|
2864
|
+
`${label}: "${match[0].trim()}" \u2014 scenario values must be concrete. Replace it with the exact static value (there is no variable mechanism).`
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
return errors;
|
|
2869
|
+
}
|
|
2862
2870
|
function truncate3(s, max) {
|
|
2863
2871
|
if (s.length <= max) return s;
|
|
2864
2872
|
return s.slice(0, max - 1).trimEnd() + "\u2026";
|
|
@@ -2869,40 +2877,32 @@ function pad3(s, width) {
|
|
|
2869
2877
|
function renderScenarioTable(parsed) {
|
|
2870
2878
|
if (parsed.entityTypes.length === 0) return "";
|
|
2871
2879
|
const NAME_MAX = 32;
|
|
2872
|
-
const VARS_MAX = 40;
|
|
2873
2880
|
const rows = parsed.entityTypes.map((e, i) => ({
|
|
2874
2881
|
num: String(i + 1),
|
|
2875
2882
|
name: truncate3(e.name, NAME_MAX),
|
|
2876
|
-
count: String(e.count)
|
|
2877
|
-
vars: e.variableFields.length ? truncate3(e.variableFields.join(", "), VARS_MAX) : "\u2014",
|
|
2878
|
-
hasVars: e.variableFields.length > 0
|
|
2883
|
+
count: String(e.count)
|
|
2879
2884
|
}));
|
|
2880
2885
|
const numW = Math.max(1, ...rows.map((r) => r.num.length));
|
|
2881
2886
|
const nameW = Math.max("Entity".length, ...rows.map((r) => r.name.length));
|
|
2882
2887
|
const countW = Math.max("Count".length, ...rows.map((r) => r.count.length));
|
|
2883
2888
|
const totalRecords = parsed.entityTypes.reduce((sum, e) => sum + e.count, 0);
|
|
2884
|
-
const
|
|
2885
|
-
const
|
|
2886
|
-
const
|
|
2887
|
-
const body = rows.map((r) => {
|
|
2888
|
-
const line = `${pad3(r.num, numW)} ${pad3(r.name, nameW)} ${pad3(r.count, countW)} ${r.vars}`;
|
|
2889
|
-
return r.hasVars ? `${YELLOW4}${line}${RESET6}` : line;
|
|
2890
|
-
}).join("\n");
|
|
2889
|
+
const header = `${BOLD4}${pad3("#", numW)} ${pad3("Entity", nameW)} ${pad3("Count", countW)}${RESET6}`;
|
|
2890
|
+
const sep = `${DIM6}${"\u2500".repeat(numW + nameW + countW + 4)}${RESET6}`;
|
|
2891
|
+
const body = rows.map((r) => `${pad3(r.num, numW)} ${pad3(r.name, nameW)} ${pad3(r.count, countW)}`).join("\n");
|
|
2891
2892
|
const scenarioLabel = parsed.scenarioNames.length ? `scenario: ${parsed.scenarioNames.join(", ")} \xB7 ` : "";
|
|
2892
|
-
const caption = `${DIM6}${scenarioLabel}${parsed.entityTypes.length} entity types \xB7 ${totalRecords} records
|
|
2893
|
+
const caption = `${DIM6}${scenarioLabel}${parsed.entityTypes.length} entity types \xB7 ${totalRecords} records${RESET6}`;
|
|
2893
2894
|
return `${header}
|
|
2894
2895
|
${sep}
|
|
2895
2896
|
${body}
|
|
2896
2897
|
${caption}`;
|
|
2897
2898
|
}
|
|
2898
|
-
var RESET6, DIM6,
|
|
2899
|
+
var RESET6, DIM6, BOLD4;
|
|
2899
2900
|
var init_scenario_table = __esm({
|
|
2900
2901
|
"src/agents/03-scenario-recipe/scenario-table.ts"() {
|
|
2901
2902
|
"use strict";
|
|
2902
2903
|
init_esm_shims();
|
|
2903
2904
|
RESET6 = "\x1B[0m";
|
|
2904
2905
|
DIM6 = "\x1B[2m";
|
|
2905
|
-
YELLOW4 = "\x1B[33m";
|
|
2906
2906
|
BOLD4 = "\x1B[1m";
|
|
2907
2907
|
}
|
|
2908
2908
|
});
|
|
@@ -2948,27 +2948,23 @@ entity_types:
|
|
|
2948
2948
|
count: 3
|
|
2949
2949
|
- name: Project
|
|
2950
2950
|
count: 5
|
|
2951
|
-
variable_fields:
|
|
2952
|
-
- token: "{{user_email_1}}"
|
|
2953
|
-
entity: User
|
|
2954
|
-
field: email
|
|
2955
|
-
test_reference: "primary test user email"
|
|
2956
2951
|
---
|
|
2957
2952
|
\`\`\`
|
|
2958
2953
|
|
|
2959
2954
|
After frontmatter, write entity tables showing the data for the standard scenario.
|
|
2960
2955
|
Use markdown tables. Show enough detail that a recipe builder can generate the exact records.
|
|
2961
2956
|
|
|
2962
|
-
##
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2957
|
+
## Data values
|
|
2958
|
+
Every value in the scenario is concrete and static \u2014 write the actual data the
|
|
2959
|
+
records should hold. Do NOT use placeholders, tokens, or templated/"dynamic"
|
|
2960
|
+
values; the recipe builder generates the exact records from what you write.`;
|
|
2966
2961
|
}
|
|
2967
2962
|
});
|
|
2968
2963
|
|
|
2969
2964
|
// src/agents/03-scenario-recipe/index.ts
|
|
2970
2965
|
var scenario_recipe_exports = {};
|
|
2971
2966
|
__export(scenario_recipe_exports, {
|
|
2967
|
+
buildFinishTool: () => buildFinishTool3,
|
|
2972
2968
|
feedbackToScenario: () => feedbackToScenario,
|
|
2973
2969
|
runScenarioRecipe: () => runScenarioRecipe
|
|
2974
2970
|
});
|
|
@@ -2985,23 +2981,30 @@ function buildFinishTool3(requiredEntities, outputDir, onFinish) {
|
|
|
2985
2981
|
artifacts: z13.array(z13.string()).describe("Files written")
|
|
2986
2982
|
}),
|
|
2987
2983
|
execute: async (input) => {
|
|
2984
|
+
let content;
|
|
2985
|
+
try {
|
|
2986
|
+
content = await readFile13(join19(outputDir, "scenarios.md"), "utf-8");
|
|
2987
|
+
} catch {
|
|
2988
|
+
return { error: "Cannot finish: scenarios.md not found. Write it first." };
|
|
2989
|
+
}
|
|
2988
2990
|
if (requiredEntities.length > 0) {
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
);
|
|
2994
|
-
if (missing.length > 0) {
|
|
2995
|
-
return {
|
|
2996
|
-
error: `Cannot finish: ${missing.length} entities from the entity audit are missing from scenarios.md.
|
|
2991
|
+
const missing = requiredEntities.filter((e) => !content.includes(e));
|
|
2992
|
+
if (missing.length > 0) {
|
|
2993
|
+
return {
|
|
2994
|
+
error: `Cannot finish: ${missing.length} entities from the entity audit are missing from scenarios.md.
|
|
2997
2995
|
Add these entities to the scenario before calling finish:
|
|
2998
2996
|
` + missing.map((e) => ` - ${e}`).join("\n")
|
|
2999
|
-
|
|
3000
|
-
}
|
|
3001
|
-
} catch {
|
|
3002
|
-
return { error: "Cannot finish: scenarios.md not found. Write it first." };
|
|
2997
|
+
};
|
|
3003
2998
|
}
|
|
3004
2999
|
}
|
|
3000
|
+
const concretenessErrors = validateScenarioIsConcrete(content);
|
|
3001
|
+
if (concretenessErrors.length > 0) {
|
|
3002
|
+
return {
|
|
3003
|
+
error: `Cannot finish: scenario data must be fully concrete \u2014 no variables, tokens, or placeholders.
|
|
3004
|
+
Fix these before calling finish:
|
|
3005
|
+
` + concretenessErrors.map((e) => ` - ${e}`).join("\n")
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3005
3008
|
onFinish({
|
|
3006
3009
|
success: true,
|
|
3007
3010
|
artifacts: input.artifacts,
|
|
@@ -3033,7 +3036,6 @@ The scenario should:
|
|
|
3033
3036
|
1. Cover ALL ${requiredEntities.length || ""} entity types from the entity audit \u2014 no exceptions
|
|
3034
3037
|
2. Use realistic data volumes (not just 1 of each)
|
|
3035
3038
|
3. Cover all enum values (at least one record per value)
|
|
3036
|
-
4. Include variable_fields for values that must change between runs
|
|
3037
3039
|
|
|
3038
3040
|
When done, call finish.`;
|
|
3039
3041
|
const agentConfig = {
|
|
@@ -3059,7 +3061,7 @@ When done, call finish.`;
|
|
|
3059
3061
|
const parsed = await parseScenario(input.outputDir);
|
|
3060
3062
|
return parsed.entityTypes.length ? renderScenarioTable(parsed) : void 0;
|
|
3061
3063
|
},
|
|
3062
|
-
reviewGuidance: "The scenario defines test data that will exist in the database during E2E tests.\nEach entity_type should have a realistic count and data values.\n\nWhat to check:\n - Every entity from the entity audit should appear here\n - Counts should be realistic (not just 1 of each)\n - Enum fields should have diverse values (not all the same)\n -
|
|
3064
|
+
reviewGuidance: "The scenario defines test data that will exist in the database during E2E tests.\nEach entity_type should have a realistic count and data values.\n\nWhat to check:\n - Every entity from the entity audit should appear here\n - Counts should be realistic (not just 1 of each)\n - Enum fields should have diverse values (not all the same)\n - Data values should match your actual database patterns",
|
|
3063
3065
|
onFeedback: async (feedback) => {
|
|
3064
3066
|
result = void 0;
|
|
3065
3067
|
const feedbackPrompt = `The user reviewed the scenario design and has this feedback:
|
|
@@ -5535,6 +5537,22 @@ import { dirname as dirname3, join as join28 } from "path";
|
|
|
5535
5537
|
import { hasToolCall as hasToolCall3, stepCountIs as stepCountIs3, tool as tool18, ToolLoopAgent as ToolLoopAgent3 } from "ai";
|
|
5536
5538
|
import matter5 from "gray-matter";
|
|
5537
5539
|
import { z as z21 } from "zod";
|
|
5540
|
+
function findForbiddenPlaceholder(stepsSection) {
|
|
5541
|
+
const placeholderPatterns = [
|
|
5542
|
+
{ pattern: /Dynamic:\s/gi, name: '"Dynamic:" placeholder' },
|
|
5543
|
+
{ pattern: /\{\{[a-zA-Z0-9_]+\}\}/g, name: "{{token}} placeholder" },
|
|
5544
|
+
{ pattern: /(?<!\{)\{[a-z][a-zA-Z]*\}(?!\})/g, name: "bare {variable}" },
|
|
5545
|
+
{ pattern: /\(e\.g\./gi, name: '"(e.g." example' },
|
|
5546
|
+
{ pattern: /(?:^|\s)e\.g\.,?\s/gim, name: '"e.g." example' }
|
|
5547
|
+
];
|
|
5548
|
+
for (const { pattern, name } of placeholderPatterns) {
|
|
5549
|
+
const matches = stepsSection.match(pattern);
|
|
5550
|
+
if (matches && matches.length > 0) {
|
|
5551
|
+
return { name, match: matches[0] };
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
return null;
|
|
5555
|
+
}
|
|
5538
5556
|
function buildWriteTestTool(state, outputDir) {
|
|
5539
5557
|
return tool18({
|
|
5540
5558
|
description: "Write a test file to qa-tests/{folder}/{filename}.md. Validates frontmatter before writing. Returns error if frontmatter is invalid.",
|
|
@@ -5577,19 +5595,11 @@ function buildWriteTestTool(state, outputDir) {
|
|
|
5577
5595
|
const bodyStart = input.content.indexOf("---", 3);
|
|
5578
5596
|
const body = bodyStart > -1 ? input.content.slice(bodyStart + 3) : input.content;
|
|
5579
5597
|
const stepsSection = body.slice(body.indexOf("**Steps**") || 0);
|
|
5580
|
-
const
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
];
|
|
5586
|
-
for (const { pattern, name } of placeholderPatterns) {
|
|
5587
|
-
const matches = stepsSection.match(pattern);
|
|
5588
|
-
if (matches && matches.length > 0) {
|
|
5589
|
-
return {
|
|
5590
|
-
error: `Test steps contain ${name}: "${matches[0]}". Use EXACT values from scenarios.md \u2014 not placeholders or examples.`
|
|
5591
|
-
};
|
|
5592
|
-
}
|
|
5598
|
+
const placeholder = findForbiddenPlaceholder(stepsSection);
|
|
5599
|
+
if (placeholder) {
|
|
5600
|
+
return {
|
|
5601
|
+
error: `Test steps contain ${placeholder.name}: "${placeholder.match}". Use EXACT values from scenarios.md \u2014 not placeholders or examples.`
|
|
5602
|
+
};
|
|
5593
5603
|
}
|
|
5594
5604
|
const relPath = join28("qa-tests", input.folder, input.filename);
|
|
5595
5605
|
const absPath = join28(outputDir, relPath);
|
|
@@ -6012,7 +6022,7 @@ EVERY assertion MUST include location context \u2014 where on the page the eleme
|
|
|
6012
6022
|
- Be specific: use exact button text, field names, toast messages FROM THE CODE
|
|
6013
6023
|
- One test per file
|
|
6014
6024
|
- Never write meta-tests that "audit" scenario/fixture contents
|
|
6015
|
-
- Reference scenario data when needed for real user flows, using
|
|
6025
|
+
- Reference scenario data when needed for real user flows, using the exact values from the scenario
|
|
6016
6026
|
- Do NOT write tests that verify the test infrastructure itself
|
|
6017
6027
|
- Every step must be concrete and reproducible. "assert: text 'Deal Created' is visible in toast" is GOOD. "assert: success indicator appears" is BAD.
|
|
6018
6028
|
|
|
@@ -6041,8 +6051,8 @@ Therefore:
|
|
|
6041
6051
|
The scenarios define EXACTLY what data exists in the database. Since WE control the test data, assertions should reference EXACT values from the scenario.
|
|
6042
6052
|
- When a test needs to verify data is displayed, use the EXACT values from the scenario (names, emails, titles, counts)
|
|
6043
6053
|
- Read scenarios.md carefully and use the exact entity names, counts, and field values in your assertions
|
|
6044
|
-
-
|
|
6045
|
-
- NEVER use "Dynamic:", "{variableName}", or "e.g." in steps or assertions. You have exact data \u2014 use it.
|
|
6054
|
+
- Do NOT assert on values that are auto-generated or vary at runtime (like database IDs); assert on the stable scenario values instead
|
|
6055
|
+
- NEVER use "Dynamic:", "{variableName}", "{{token}}", or "e.g." in steps or assertions. You have exact data \u2014 use it.
|
|
6046
6056
|
- NEVER assume facts not stated in the scenario data.
|
|
6047
6057
|
|
|
6048
6058
|
## Test generation ordering (for consistency)
|