@skyramp/mcp 0.3.9-rc.1 → 0.4.0-rc.2
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/build/commands/commandLibrary.d.ts +1 -1
- package/build/commands/commandLibrary.js +3 -3
- package/build/commands/recommendTestsAndExecuteCommand.d.ts +1 -1
- package/build/commands/recommendTestsAndExecuteCommand.js +35 -20
- package/build/commands/testThisEndpointCommand.js +35 -19
- package/build/index.js +9 -3
- package/build/playwright/blueprintDigest.d.ts +15 -0
- package/build/playwright/blueprintDigest.js +152 -0
- package/build/playwright/blueprintDigestStore.d.ts +31 -0
- package/build/playwright/blueprintDigestStore.js +117 -0
- package/build/playwright/registerPlaywrightTools.js +59 -12
- package/build/playwright/traceRecordingPrompt.js +8 -7
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +9 -8
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +3 -2
- package/build/prompts/promptAssets.d.ts +20 -0
- package/build/prompts/promptAssets.js +55 -0
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +19 -5
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +0 -10
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +2 -11
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +5 -4
- package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +5 -22
- package/build/prompts/test-recommendation/diffExecutionPlan.js +37 -466
- package/build/prompts/test-recommendation/recommendationSections.d.ts +7 -17
- package/build/prompts/test-recommendation/recommendationSections.js +67 -309
- package/build/prompts/test-recommendation/recommendationShared.d.ts +19 -47
- package/build/prompts/test-recommendation/recommendationShared.js +49 -155
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +0 -5
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +10 -153
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -29
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +32 -457
- package/build/prompts/testbot/planDeclarations.d.ts +6 -0
- package/build/prompts/testbot/planDeclarations.js +9 -0
- package/build/prompts/testbot/testbot-prompts.d.ts +8 -0
- package/build/prompts/testbot/testbot-prompts.js +254 -382
- package/build/recommendation/answers.d.ts +35 -0
- package/build/recommendation/answers.js +96 -0
- package/build/recommendation/registerPlan.d.ts +49 -0
- package/build/recommendation/registerPlan.js +117 -0
- package/build/recommendation/runVerifiers.d.ts +10 -0
- package/build/recommendation/runVerifiers.js +49 -0
- package/build/recommendation/subjectStep.d.ts +42 -0
- package/build/recommendation/subjectStep.js +86 -0
- package/build/recommendation/types.d.ts +166 -0
- package/build/recommendation/types.js +20 -0
- package/build/recommendation/verifierContracts.d.ts +215 -0
- package/build/recommendation/verifierContracts.js +255 -0
- package/build/recommendation/verifiers/changedFile.d.ts +2 -0
- package/build/recommendation/verifiers/changedFile.js +82 -0
- package/build/recommendation/verifiers/citedPath.d.ts +12 -0
- package/build/recommendation/verifiers/citedPath.js +35 -0
- package/build/recommendation/verifiers/coverage.d.ts +7 -0
- package/build/recommendation/verifiers/coverage.js +617 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +11 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.js +33 -0
- package/build/recommendation/verifiers/endpointGrounded.d.ts +17 -0
- package/build/recommendation/verifiers/endpointGrounded.js +128 -0
- package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
- package/build/recommendation/verifiers/existingCoverage.js +51 -0
- package/build/recommendation/verifiers/expectedOutcome.d.ts +31 -0
- package/build/recommendation/verifiers/expectedOutcome.js +105 -0
- package/build/recommendation/verifiers/removedElementGuarded.d.ts +2 -0
- package/build/recommendation/verifiers/removedElementGuarded.js +111 -0
- package/build/recommendation/verifiers/reportedCategory.d.ts +26 -0
- package/build/recommendation/verifiers/reportedCategory.js +84 -0
- package/build/recommendation/verifiers/screenRoute.d.ts +10 -0
- package/build/recommendation/verifiers/screenRoute.js +118 -0
- package/build/recommendation/verifiers/statedDifference.d.ts +6 -0
- package/build/recommendation/verifiers/statedDifference.js +140 -0
- package/build/recommendation/verifiers/uiElementGrounded.d.ts +7 -0
- package/build/recommendation/verifiers/uiElementGrounded.js +318 -0
- package/build/resources/analysisResources.js +1 -114
- package/build/resources/testbotResource.js +23 -13
- package/build/services/ModularizationService.js +2 -1
- package/build/services/TestDiscoveryService.d.ts +3 -72
- package/build/services/TestDiscoveryService.js +10 -303
- package/build/skills/fixTestImportErrorsSkill.d.ts +13 -0
- package/build/skills/fixTestImportErrorsSkill.js +20 -0
- package/build/toolNames.d.ts +1 -0
- package/build/toolNames.js +1 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -3
- package/build/tools/code-refactor/modularizationTool.js +2 -1
- package/build/tools/executeSkyrampTestTool.js +8 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -0
- package/build/tools/generate-tests/generateContractRestTool.js +3 -3
- package/build/tools/generate-tests/planGuard.d.ts +2 -2
- package/build/tools/generate-tests/planGuard.js +78 -18
- package/build/tools/one-click/oneClickTool.d.ts +0 -1
- package/build/tools/one-click/oneClickTool.js +0 -5
- package/build/tools/submitReportTool.d.ts +43 -37
- package/build/tools/submitReportTool.js +571 -192
- package/build/tools/test-management/actionsTool.js +26 -5
- package/build/tools/test-management/analyzeChangesTool.d.ts +168 -57
- package/build/tools/test-management/analyzeChangesTool.js +194 -1266
- package/build/tools/test-management/analyzeTestHealthTool.js +13 -24
- package/build/tools/test-management/index.d.ts +1 -0
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +795 -172
- package/build/tools/test-management/registerTestPlanTool.js +610 -542
- package/build/tools/test-management/resolveScreenTool.d.ts +75 -0
- package/build/tools/test-management/resolveScreenTool.js +289 -0
- package/build/types/BlueprintDigest.d.ts +34 -0
- package/build/types/BlueprintDigest.js +1 -0
- package/build/types/RepositoryAnalysis.d.ts +20 -1559
- package/build/types/RepositoryAnalysis.js +2 -58
- package/build/types/StepMethod.d.ts +40 -0
- package/build/types/StepMethod.js +77 -0
- package/build/types/TestRecommendation.d.ts +24 -24
- package/build/types/TestRecommendation.js +91 -89
- package/build/types/TestbotPromptOptions.d.ts +0 -4
- package/build/types/TestbotReport.d.ts +64 -2
- package/build/utils/AnalysisStateManager.d.ts +76 -115
- package/build/utils/AnalysisStateManager.js +147 -57
- package/build/utils/assertion-verify/api-shared-lints.js +1 -1
- package/build/utils/assertion-verify/metrics.js +85 -36
- package/build/utils/branchDiff.d.ts +63 -31
- package/build/utils/branchDiff.js +242 -94
- package/build/utils/containedPath.d.ts +18 -0
- package/build/utils/containedPath.js +73 -0
- package/build/utils/dartRouteExtractor.d.ts +18 -34
- package/build/utils/dartRouteExtractor.js +101 -173
- package/build/utils/featureFlags.d.ts +12 -0
- package/build/utils/featureFlags.js +14 -0
- package/build/utils/frontendSelectors.d.ts +34 -37
- package/build/utils/frontendSelectors.js +104 -154
- package/build/utils/pathMatching.d.ts +2 -4
- package/build/utils/pathMatching.js +2 -4
- package/build/utils/planMatchKeys.d.ts +38 -47
- package/build/utils/planMatchKeys.js +143 -81
- package/build/utils/removedUiElements.d.ts +8 -13
- package/build/utils/removedUiElements.js +21 -55
- package/build/utils/screenRoutes.d.ts +66 -0
- package/build/utils/screenRoutes.js +727 -0
- package/build/utils/sourceRouteExtractor.js +320 -112
- package/build/utils/testFileClassification.d.ts +11 -2
- package/build/utils/testFileClassification.js +44 -2
- package/build/utils/testFixtures.d.ts +5 -0
- package/build/utils/testFixtures.js +13 -0
- package/build/utils/utils.d.ts +0 -1
- package/build/utils/utils.js +0 -11
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +12 -12
- package/package.json +3 -2
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/plugin.json +6 -0
- package/plugin/prompts/declaring-a-plan.md +20 -0
- package/plugin/prompts/generate-tests/context-fetching.md +4 -0
- package/plugin/prompts/generate-tests/execution-plan.md +63 -0
- package/plugin/prompts/generate-tests/generation.md +108 -0
- package/plugin/prompts/generate-tests/path-parameters.md +1 -0
- package/plugin/prompts/generate-tests/reasoning-protocol.md +17 -0
- package/plugin/prompts/generate-tests/tool-workflow-variants.md +61 -0
- package/plugin/prompts/generate-tests/tool-workflows.md +65 -0
- package/plugin/prompts/plan-tests.md +42 -0
- package/plugin/prompts/testbot-task1.md +82 -0
- package/plugin/skills/fix-test-import-errors/SKILL.md +98 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +0 -84
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +0 -369
- package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +0 -7
- package/build/prompts/test-recommendation/fullRepoCatalog.js +0 -283
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +0 -81
- package/build/prompts/test-recommendation/scopeAssessment.js +0 -359
- package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +0 -7
- package/build/recommendation/budgeters/diversityBalancedBudgeter.js +0 -105
- package/build/recommendation/budgeters/fixedNBudgeter.d.ts +0 -7
- package/build/recommendation/budgeters/fixedNBudgeter.js +0 -11
- package/build/recommendation/budgeters/shared.d.ts +0 -32
- package/build/recommendation/budgeters/shared.js +0 -246
- package/build/recommendation/discriminators.d.ts +0 -37
- package/build/recommendation/discriminators.js +0 -379
- package/build/recommendation/diversity.d.ts +0 -47
- package/build/recommendation/diversity.js +0 -101
- package/build/recommendation/planRanker.d.ts +0 -65
- package/build/recommendation/planRanker.js +0 -83
- package/build/recommendation/testFixtures.d.ts +0 -25
- package/build/recommendation/testFixtures.js +0 -45
- package/build/types/FrontendIntegration.d.ts +0 -28
- package/build/types/FrontendIntegration.js +0 -22
- package/build/types/Recommendation.d.ts +0 -146
- package/build/types/Recommendation.js +0 -74
- package/build/utils/changedRoutes.d.ts +0 -29
- package/build/utils/changedRoutes.js +0 -87
- package/build/utils/frontendIntegration.d.ts +0 -9
- package/build/utils/frontendIntegration.js +0 -243
- package/build/utils/importerHop.d.ts +0 -135
- package/build/utils/importerHop.js +0 -489
- package/build/utils/pathAffinityClassification.d.ts +0 -49
- package/build/utils/pathAffinityClassification.js +0 -180
- package/build/utils/pythonMountPrefixes.d.ts +0 -25
- package/build/utils/pythonMountPrefixes.js +0 -347
- package/build/utils/repoScanner.d.ts +0 -34
- package/build/utils/repoScanner.js +0 -300
- package/build/utils/routeParsers.d.ts +0 -95
- package/build/utils/routeParsers.js +0 -951
- package/build/utils/scenarioDrafting.d.ts +0 -92
- package/build/utils/scenarioDrafting.js +0 -951
- package/build/utils/subjectEndpoints.d.ts +0 -19
- package/build/utils/subjectEndpoints.js +0 -98
- package/build/utils/uiPageEnumerator.d.ts +0 -172
- package/build/utils/uiPageEnumerator.js +0 -474
|
@@ -8,5 +8,5 @@ export declare function getCommandIds(): OneClickCommandId[];
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function lookupCommand(id: string): OneClickCommandDef;
|
|
10
10
|
export { TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND } from "./testThisEndpointCommand.js";
|
|
11
|
-
export {
|
|
11
|
+
export { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
|
|
12
12
|
export { LOCAL_DEV_TEST_CHANGES_COMMAND } from "./localDevTestChangesCommand.js";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { isLocalDevEnabled, isOneClickEnabled } from "../utils/featureFlags.js";
|
|
2
2
|
import { TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND } from "./testThisEndpointCommand.js";
|
|
3
|
-
import {
|
|
3
|
+
import { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
|
|
4
4
|
import { LOCAL_DEV_TEST_CHANGES_COMMAND } from "./localDevTestChangesCommand.js";
|
|
5
5
|
/** Base predefined one-click commands */
|
|
6
6
|
const BASE_COMMAND_LIBRARY = {
|
|
7
7
|
test_given_endpoint_comprehensively: TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND,
|
|
8
|
-
full_repo_scan_recommend_generate_and_execute_top_n_tests:
|
|
8
|
+
full_repo_scan_recommend_generate_and_execute_top_n_tests: FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND,
|
|
9
9
|
};
|
|
10
10
|
/** All enabled predefined one-click commands */
|
|
11
11
|
function getCommandLibrary() {
|
|
@@ -39,5 +39,5 @@ export function lookupCommand(id) {
|
|
|
39
39
|
}
|
|
40
40
|
// Re-export commands for consumers that import from commandLibrary
|
|
41
41
|
export { TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND } from "./testThisEndpointCommand.js";
|
|
42
|
-
export {
|
|
42
|
+
export { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
|
|
43
43
|
export { LOCAL_DEV_TEST_CHANGES_COMMAND } from "./localDevTestChangesCommand.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { OneClickCommandDef } from "../types/OneClickCommands.js";
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND: OneClickCommandDef;
|
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
* Predefined One-Click Command: Recommend tests and generate and execute top recommended tests
|
|
3
3
|
*
|
|
4
4
|
* Spec order:
|
|
5
|
-
* skyramp_analyze_changes (combined analyze + discover +
|
|
6
|
-
* →
|
|
5
|
+
* skyramp_analyze_changes (combined analyze + discover + suggest)
|
|
6
|
+
* → Register a test plan, then generate the tests it holds
|
|
7
7
|
* → Execute each via skyramp_execute_test
|
|
8
|
+
*
|
|
9
|
+
* Step 2 works from the REGISTERED PLAN, not a ranked list. Full-repo scope follows
|
|
10
|
+
* the same path a diff does: the agent decides the candidate list itself and no
|
|
11
|
+
* server-side tier order picks the tests.
|
|
8
12
|
*/
|
|
9
|
-
const
|
|
13
|
+
const fullRepoRecommendGenerateExecuteSteps = [
|
|
10
14
|
{
|
|
11
15
|
stepIndex: 0,
|
|
12
16
|
title: "Ensure workspace is initialized",
|
|
@@ -22,24 +26,36 @@ const fullRepoRecommendGenerateExecuteTopNSteps = [
|
|
|
22
26
|
},
|
|
23
27
|
{
|
|
24
28
|
stepIndex: 1,
|
|
25
|
-
title: "Analyze
|
|
26
|
-
description: "Call skyramp_analyze_changes with repositoryPath and analysisScope from user. This single tool scans endpoints, discovers existing tests, and returns
|
|
29
|
+
title: "Analyze the repository and read the candidate suggestions",
|
|
30
|
+
description: "Call skyramp_analyze_changes with repositoryPath and analysisScope from user. This single tool scans endpoints, discovers existing tests, and returns candidate test suggestions inline. The suggestions are candidates, not a selection: nothing is ranked, approved or mandatory. Capture the stateFile path — step 2 needs it — and read the suggestions alongside the endpoint and existing-test data the response carries.",
|
|
27
31
|
toolCall: {
|
|
28
32
|
toolName: "skyramp_analyze_changes",
|
|
29
|
-
description: "Analyze repo and
|
|
33
|
+
description: "Analyze repo and return candidate suggestions inline; returns stateFile",
|
|
30
34
|
inputs: {
|
|
31
35
|
repositoryPath: { source: "user", paramKey: "repositoryPath" },
|
|
32
36
|
scope: { source: "literal", value: "full_repo" },
|
|
33
37
|
},
|
|
34
|
-
outputs: ["stateFile"
|
|
38
|
+
outputs: ["stateFile"],
|
|
35
39
|
},
|
|
36
|
-
conditionalGuidance: "Always call skyramp_analyze_changes — do not skip this step or use a pre-existing stateFile. Capture stateFile
|
|
40
|
+
conditionalGuidance: "Always call skyramp_analyze_changes — do not skip this step or use a pre-existing stateFile. Capture the stateFile for steps 2 and 3.",
|
|
37
41
|
},
|
|
38
42
|
{
|
|
39
43
|
stepIndex: 2,
|
|
40
|
-
title: "
|
|
41
|
-
description: "
|
|
42
|
-
|
|
44
|
+
title: "Register a test plan, then generate the tests it holds",
|
|
45
|
+
description: "Decide which tests this repository is missing, then register that decision with skyramp_register_test_plan (stateFile from step 1). Nothing in step 1 selected anything for you: keep a suggestion, keep none, keep all, or draft your own. How many distinct tests to write is your own judgment call — this command sets no ceiling and nothing supplies you with one. Each candidate declares what it asserts and whether it should fail or pass against the app as it stands; a full-repo scan has no diff, so no candidate needs a changed-line anchor. The tool stores every registration and returns objections — objections to a named candidate with the fact behind each one. Fix the plan and register it again (there is no limit), or answer an objection by its id through answers.\n\nThen generate one test per plan candidate. The generation tools admit a scenario the registered plan covers, so register the plan BEFORE the first generation call.\n\nIMPORTANT — UI or E2E candidates: treat each one independently. For each, first search the repository for existing trace files suitable for that candidate's endpoint or path (look for .zip or .json Skyramp trace files in the repo that reference it). If suitable traces are found, use them directly as inputs to skyramp_ui_test_generation (playwrightInput) or skyramp_e2e_test_generation (trace + playwrightInput) — skip trace collection. If no suitable traces exist in the repo for that candidate, collect them: call skyramp_start_trace_collection (playwright: true, outputDir: absolute path from workspace.yml), have the user interact with that endpoint or page in the browser, then call skyramp_stop_trace_collection (playwrightEnabled: true, same outputDir). Use the produced trace file and Playwright zip as inputs for generation for that specific candidate.\n\nFor every other candidate, call the generation tool its testType names, using an endpoint URL derived from the candidate's own steps (api.baseUrl + the step path). Also pass apiSchema, language, framework, and outputDir from workspace.yml. Capture each generated test file path for step 3.",
|
|
46
|
+
toolCall: {
|
|
47
|
+
toolName: "skyramp_register_test_plan",
|
|
48
|
+
description: "Register the complete test plan for this run, then generate from it. Every registration is stored and none is refused; the objections it returns are answered or fixed, never blocking.",
|
|
49
|
+
inputs: {
|
|
50
|
+
stateFile: { source: "step", stepIndex: 1, outputKey: "stateFile" },
|
|
51
|
+
candidates: {
|
|
52
|
+
source: "literal",
|
|
53
|
+
value: "the tests you decided to write, each with scenarioName, description, category, testType, steps and declarations — see the tool schema for each field's rule",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
outputs: ["objections"],
|
|
57
|
+
},
|
|
58
|
+
conditionalGuidance: "Register the plan first, then generate — a scenario the plan does not cover is rejected by the generation tools. Tool mapping by testType — contract: skyramp_contract_test_generation, integration: skyramp_batch_scenario_test_generation then skyramp_integration_test_generation, e2e: skyramp_e2e_test_generation, ui: skyramp_ui_test_generation. Only these four types: never smoke, never fuzz, never load. Generate exactly the candidates the registered plan holds — no more, and drop none of them silently; if the work shows you a test the plan lacks, register the plan again with it first. For UI/E2E candidates: (1) search the repo for existing traces for that candidate; (2) if found, use them directly; (3) if not found, run skyramp_start_trace_collection → user interaction → skyramp_stop_trace_collection, then use the produced traces. Record each generated test file path for step 3.",
|
|
43
59
|
},
|
|
44
60
|
{
|
|
45
61
|
stepIndex: 3,
|
|
@@ -62,24 +78,24 @@ const fullRepoRecommendGenerateExecuteTopNSteps = [
|
|
|
62
78
|
conditionalGuidance: "Skip if step 2 generated no tests. Iterate over each generated test file path returned directly from the tools invoked in step 2 and call skyramp_execute_test once per file. Token resolution: (1) user-provided token; (2) token from .skyramp/workspace.yml or repo config; (3) empty string '' — let skyramp_execute_test surface auth errors, then ask the user for a Bearer token to re-run.",
|
|
63
79
|
},
|
|
64
80
|
];
|
|
65
|
-
export const
|
|
81
|
+
export const FULLREPO_RECOMMEND_GENERATE_EXECUTE_TESTS_COMMAND = {
|
|
66
82
|
id: "full_repo_scan_recommend_generate_and_execute_top_n_tests",
|
|
67
|
-
name: "Full Repo: Recommend, Generate and Run
|
|
68
|
-
description: "Run skyramp_analyze_changes to scan the repo
|
|
83
|
+
name: "Full Repo: Recommend, Generate and Run Tests",
|
|
84
|
+
description: "Run skyramp_analyze_changes to scan the repo, register a test plan for the tests it is missing, generate the tests that plan holds, then execute them.",
|
|
69
85
|
intent: {
|
|
70
86
|
contextIndicators: [
|
|
71
|
-
"Use when the user wants to scan the entire repository with no specific endpoint or PR diff in mind — to
|
|
87
|
+
"Use when the user wants to scan the entire repository with no specific endpoint or PR diff in mind — to find the tests it is missing across all endpoints, generate them, and execute them",
|
|
72
88
|
"Scope is always the full repository — the user has not mentioned a specific endpoint URL, path, or name",
|
|
73
89
|
"Use when the user asks to recommend, generate, and execute tests for the whole repo or says something like 'run the full repo test workflow'",
|
|
74
90
|
"Do NOT use when the user specifies a particular endpoint — use test_given_endpoint_comprehensively instead",
|
|
75
91
|
"Do NOT use when the user asks about a PR diff or branch-scoped analysis — use skyramp_analyze_changes directly instead",
|
|
76
92
|
"Do NOT use for simple single-tool requests such as 'generate a smoke test' or 'recommend tests for this PR'",
|
|
77
93
|
],
|
|
78
|
-
purpose: "Full repo scan:
|
|
79
|
-
workflowSummary: "Full Repo Scan →
|
|
94
|
+
purpose: "Full repo scan: read the candidate suggestions → register a plan → generate the tests it holds → execute them (no specific endpoint, no PR diff). Cleanup is handled automatically.",
|
|
95
|
+
workflowSummary: "Full Repo Scan → Register plan → Generate the plan's tests → Execute each test (cleanup is automatic)",
|
|
80
96
|
examples: {
|
|
81
97
|
use: [
|
|
82
|
-
"scan the full repo and recommend and execute
|
|
98
|
+
"scan the full repo and recommend and execute the tests it needs",
|
|
83
99
|
"run the full repo test workflow",
|
|
84
100
|
"recommend generate and execute tests for the whole repo",
|
|
85
101
|
"find the best tests to write for this codebase and run them",
|
|
@@ -91,11 +107,10 @@ export const FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND = {
|
|
|
91
107
|
],
|
|
92
108
|
},
|
|
93
109
|
},
|
|
94
|
-
steps:
|
|
110
|
+
steps: fullRepoRecommendGenerateExecuteSteps,
|
|
95
111
|
inputParams: {
|
|
96
112
|
required: ["repositoryPath"],
|
|
97
113
|
optional: [
|
|
98
|
-
"topN",
|
|
99
114
|
"token",
|
|
100
115
|
"endpointURL",
|
|
101
116
|
"apiSchema",
|
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
* Predefined One-Click Command: Test this endpoint
|
|
3
3
|
*
|
|
4
4
|
* Spec order:
|
|
5
|
-
* skyramp_analyze_changes (combined analyze + discover +
|
|
6
|
-
* →
|
|
7
|
-
* → Generate
|
|
5
|
+
* skyramp_analyze_changes (combined analyze + discover + suggest)
|
|
6
|
+
* → Register a test plan for the target endpoint
|
|
7
|
+
* → Generate the tests the plan holds (by type)
|
|
8
8
|
* → Execute generated tests
|
|
9
9
|
* → [if existing tests found] Analyze test health → Optional batch execute → Actions
|
|
10
|
+
* Step 2 works from the REGISTERED PLAN, not a ranked list and not a server-side
|
|
11
|
+
* coverage pre-filter; the coverage question survives as a plan-time objection.
|
|
12
|
+
* KNOWN DEFECT, its own ticket: step 1 passes `scope: "full_repo"` so the scan
|
|
13
|
+
* finds a named endpoint — a scope flag doing endpoint discovery.
|
|
10
14
|
*/
|
|
11
15
|
const comprehensivelyTestGivenEndpointSteps = [
|
|
12
16
|
{
|
|
@@ -24,30 +28,42 @@ const comprehensivelyTestGivenEndpointSteps = [
|
|
|
24
28
|
},
|
|
25
29
|
{
|
|
26
30
|
stepIndex: 1,
|
|
27
|
-
title: "Analyze
|
|
28
|
-
description: "Call skyramp_analyze_changes with repositoryPath and scope from the one-click invocation. It scans endpoints, discovers existing tests, and returns
|
|
31
|
+
title: "Analyze the repository and read the candidate suggestions",
|
|
32
|
+
description: "Call skyramp_analyze_changes with repositoryPath and scope from the one-click invocation. It scans endpoints, discovers existing tests, and returns candidate test suggestions along with a stateFile path. The suggestions are candidates, not a selection: nothing is ranked, approved or mandatory. The response includes a JSON block with stateFile, sessionId, and a summary (including existingTestCount), plus the suggestions section. Capture the stateFile path — step 2 needs it — and read the suggestions and the endpoint data for the target endpoint. The full list of existing tests is not in the response — it is stored in the stateFile on disk.",
|
|
29
33
|
toolCall: {
|
|
30
34
|
toolName: "skyramp_analyze_changes",
|
|
31
|
-
description: "Analyze repo, discover tests, and
|
|
35
|
+
description: "Analyze repo, discover tests, and return candidate suggestions; returns stateFile",
|
|
32
36
|
inputs: {
|
|
33
37
|
repositoryPath: { source: "user", paramKey: "repositoryPath" },
|
|
34
38
|
scope: { source: "literal", value: "full_repo" },
|
|
35
39
|
},
|
|
36
|
-
outputs: ["stateFile"
|
|
40
|
+
outputs: ["stateFile"],
|
|
37
41
|
},
|
|
38
|
-
conditionalGuidance: "If endpointURL is provided by the user, note it
|
|
42
|
+
conditionalGuidance: "If endpointURL is provided by the user, note it — step 2's plan is about that endpoint. Capture the stateFile path from the response. The existing test list is not returned directly — it is embedded in the stateFile and will be used by skyramp_analyze_test_health in step 5.",
|
|
39
43
|
},
|
|
40
44
|
{
|
|
41
45
|
stepIndex: 2,
|
|
42
|
-
title: "
|
|
43
|
-
description: "
|
|
44
|
-
|
|
46
|
+
title: "Register a test plan for the target endpoint",
|
|
47
|
+
description: "Decide which tests the target endpoint is missing, then register that decision with skyramp_register_test_plan (stateFile from step 1). Every candidate must be about the target endpoint — that is what this workflow is for; use other endpoints only as setup or cleanup steps. Cover it the way its own source warrants: the success path, the error and boundary paths its validation implies, its auth boundary, and a multi-step scenario where it takes part in a real flow. Nothing in step 1 selected anything for you, and nothing removed a candidate for being already covered.\n\nEach candidate declares what it asserts and whether it should fail or pass against the app as it stands; this run has no diff, so no candidate needs a changed-line anchor. The tool stores every registration and returns objections. One of them names an endpoint an existing repository test already covers — answer it saying how your test differs, or make that candidate an in-place UPDATE of the existing test and drop it from the plan when you register it again. There is no registration limit and no objection blocks you.\n\nIf the plan ends up empty because every test the endpoint warrants already exists, skip steps 3 and 4 and go to step 5.",
|
|
48
|
+
toolCall: {
|
|
49
|
+
toolName: "skyramp_register_test_plan",
|
|
50
|
+
description: "Register the complete test plan for the target endpoint. Every registration is stored and none is refused; the objections it returns are answered or fixed, never blocking.",
|
|
51
|
+
inputs: {
|
|
52
|
+
stateFile: { source: "step", stepIndex: 1, outputKey: "stateFile" },
|
|
53
|
+
candidates: {
|
|
54
|
+
source: "literal",
|
|
55
|
+
value: "the tests you decided to write for the target endpoint, each with scenarioName, description, category, testType, steps and declarations — see the tool schema for each field's rule",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
outputs: ["objections"],
|
|
59
|
+
},
|
|
60
|
+
conditionalGuidance: "Register the plan before any generation call — a scenario the plan does not cover is rejected by the generation tools. Every candidate's subject step must be the target endpoint. Existing coverage is an objection on the registration, not a reason to plan nothing: answer it or convert that candidate to an UPDATE of the existing test. If the registered plan holds no candidates, skip steps 3 and 4 and go to step 5.",
|
|
45
61
|
},
|
|
46
62
|
{
|
|
47
63
|
stepIndex: 3,
|
|
48
|
-
title: "Generate
|
|
49
|
-
description: "IMPORTANT — UI or E2E
|
|
50
|
-
conditionalGuidance: "Tool mapping by
|
|
64
|
+
title: "Generate the tests your plan holds (by type)",
|
|
65
|
+
description: "IMPORTANT — UI or E2E candidates in the plan: first search the repository for existing trace files suitable for the target endpoint (look for .zip or .json Skyramp trace files in the repo that reference the endpoint path or URL). If suitable traces are found, use them directly as inputs to skyramp_ui_test_generation (playwrightInput) or skyramp_e2e_test_generation (trace + playwrightInput) — skip trace collection. If no suitable traces exist in the repo, collect them: call skyramp_start_trace_collection (playwright: true, outputDir: absolute path from workspace.yml), have the user interact with the target endpoint in the browser, then call skyramp_stop_trace_collection (playwrightEnabled: true, same outputDir). Use the produced trace file and Playwright zip as inputs for generation.\n\nFor every other candidate in the plan, call the generation tool its testType names, using endpointURL from the one-click invocation (or api.baseUrl + the candidate's own step path), apiSchema, language, framework, and outputDir from workspace.yml. Capture each generated test file path.",
|
|
66
|
+
conditionalGuidance: "Tool mapping by testType — contract: skyramp_contract_test_generation, integration: skyramp_batch_scenario_test_generation then skyramp_integration_test_generation, e2e: skyramp_e2e_test_generation, ui: skyramp_ui_test_generation. Only these four types: never smoke, never fuzz, never load. Generate exactly the candidates the plan registered in step 2 holds — a scenario the plan does not cover is rejected; if the work shows you a test the plan lacks, register the plan again with it first. For UI/E2E: (1) search repo for existing traces for the target endpoint; (2) if found, use them directly; (3) if not found, run skyramp_start_trace_collection → user interaction → skyramp_stop_trace_collection, then use produced traces. Record each generated test file path for step 4.",
|
|
51
67
|
},
|
|
52
68
|
{
|
|
53
69
|
stepIndex: 4,
|
|
@@ -119,17 +135,17 @@ const comprehensivelyTestGivenEndpointSteps = [
|
|
|
119
135
|
export const TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND = {
|
|
120
136
|
id: "test_given_endpoint_comprehensively",
|
|
121
137
|
name: "Test this endpoint comprehensively",
|
|
122
|
-
description: "Comprehensively test a REST API endpoint: analyze changes + discover existing tests +
|
|
138
|
+
description: "Comprehensively test a REST API endpoint: analyze changes + discover existing tests + read candidate suggestions in one step, then register a test plan for that endpoint, generate the tests the plan holds, execute them — then (if existing tests were found) run the test health analysis toolset (health assessment → optional batch execute → actions). State files stay in system temp.",
|
|
123
139
|
intent: {
|
|
124
140
|
contextIndicators: [
|
|
125
141
|
"Use when the user names or references a specific REST API endpoint (by path such as /api/products, full URL, or name such as 'products endpoint') and wants a complete multi-step test pipeline for it",
|
|
126
|
-
"Covers the full endpoint testing lifecycle: discover existing tests →
|
|
142
|
+
"Covers the full endpoint testing lifecycle: discover existing tests → register a test plan for the endpoint → generate the tests it holds → execute → health analysis → maintenance actions",
|
|
127
143
|
"Only use when the user explicitly signals a full or deep pipeline — words like 'comprehensively', 'thoroughly', 'deep test', 'full test pipeline', or 'all test types'. Do NOT trigger on bare phrases like 'test this endpoint' or 'test the products endpoint' alone — those are ambiguous and may only mean a single smoke test",
|
|
128
144
|
"Do NOT use for broad repo-level requests where no specific endpoint is named — use skyramp_analyze_changes directly instead",
|
|
129
145
|
"Do NOT use for simple single-tool requests such as 'generate a smoke test for this endpoint' — those go directly to the generation tool",
|
|
130
146
|
],
|
|
131
|
-
purpose: "Deep test a given endpoint: discover existing →
|
|
132
|
-
workflowSummary: "Analyze Changes →
|
|
147
|
+
purpose: "Deep test a given endpoint: discover existing → register a plan for the endpoint → generate the plan's tests → execute → (if existing found) health analysis → maintenance actions. Cleanup is handled automatically.",
|
|
148
|
+
workflowSummary: "Analyze Changes → Register plan → Generate the plan's tests → Execute generated → [if existing] Test Health → Batch execute → Actions (cleanup is automatic)",
|
|
133
149
|
examples: {
|
|
134
150
|
use: [
|
|
135
151
|
"comprehensively test the products endpoint",
|
|
@@ -148,6 +164,6 @@ export const TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND = {
|
|
|
148
164
|
steps: comprehensivelyTestGivenEndpointSteps,
|
|
149
165
|
inputParams: {
|
|
150
166
|
required: ["repositoryPath"],
|
|
151
|
-
optional: ["endpointURL", "apiSchema", "
|
|
167
|
+
optional: ["endpointURL", "apiSchema", "token"],
|
|
152
168
|
},
|
|
153
169
|
};
|
package/build/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import { registerEnhanceAssertionsTool } from "./tools/code-refactor/enhanceAsse
|
|
|
26
26
|
import { registerBatchScenarioTestTool } from "./tools/generate-tests/generateBatchScenarioRestTool.js";
|
|
27
27
|
import { registerMockTool } from "./tools/generate-tests/generateMockRestTool.js";
|
|
28
28
|
import { registerBatchMockTool } from "./tools/generate-tests/batchMockGenerationTool.js";
|
|
29
|
-
import { registerAnalyzeChangesTool, registerAnalyzeTestHealthTool, registerActionsTool, registerRegisterTestPlanTool, } from "./tools/test-management/index.js";
|
|
29
|
+
import { registerAnalyzeChangesTool, registerAnalyzeTestHealthTool, registerActionsTool, registerRegisterTestPlanTool, registerResolveScreenTool, } from "./tools/test-management/index.js";
|
|
30
30
|
import { registerTestbotPrompt } from "./prompts/testbot/testbot-prompts.js";
|
|
31
31
|
import { registerSutSetupResource } from "./resources/sutSetupResource.js";
|
|
32
32
|
import { registerTestbotResource } from "./resources/testbotResource.js";
|
|
@@ -44,7 +44,7 @@ import { registerAnalysisResources } from "./resources/analysisResources.js";
|
|
|
44
44
|
import { registerProgressResource } from "./resources/progressResource.js";
|
|
45
45
|
import { AnalyticsService } from "./services/AnalyticsService.js";
|
|
46
46
|
import { registerInitTriggerOnMCPInitialized } from "./utils/initAgent.js";
|
|
47
|
-
import { isTestbotEnabled, isLocalDevEnabled } from "./utils/featureFlags.js";
|
|
47
|
+
import { isTestbotEnabled, isLocalDevEnabled, isSkillsLoaded, } from "./utils/featureFlags.js";
|
|
48
48
|
import { findRoot, readTestIdAttribute } from "./utils/pom-scope/index.js";
|
|
49
49
|
import { discoverTestIdAttribute } from "./utils/pom-scope/testIdDiscovery.js";
|
|
50
50
|
import { registerPlaywrightTools, registerTraceRecordingPrompt, getPlaywrightTraceService, } from "./playwright/index.js";
|
|
@@ -170,10 +170,15 @@ testGenerationTools.forEach((registerTool) => registerTool(server));
|
|
|
170
170
|
// Register modularization and code quality tools
|
|
171
171
|
const codeQualityTools = [
|
|
172
172
|
registerModularizationTool,
|
|
173
|
-
registerFixErrorTool,
|
|
174
173
|
registerCodeReuseTool,
|
|
175
174
|
registerEnhanceAssertionsTool,
|
|
176
175
|
];
|
|
176
|
+
// With the Skyramp plugin loaded the agent already has the
|
|
177
|
+
// fix-test-import-errors skill; the tool would be a second copy of
|
|
178
|
+
// the same text (SKYR-4296).
|
|
179
|
+
if (!isSkillsLoaded()) {
|
|
180
|
+
codeQualityTools.push(registerFixErrorTool);
|
|
181
|
+
}
|
|
177
182
|
codeQualityTools.forEach((registerTool) => registerTool(server));
|
|
178
183
|
// Register analysis resources (MCP Resources for enriched data access)
|
|
179
184
|
registerAnalysisResources(server);
|
|
@@ -183,6 +188,7 @@ registerAnalyzeChangesTool(server);
|
|
|
183
188
|
registerAnalyzeTestHealthTool(server);
|
|
184
189
|
registerActionsTool(server);
|
|
185
190
|
registerRegisterTestPlanTool(server);
|
|
191
|
+
registerResolveScreenTool(server);
|
|
186
192
|
// Register workspace management tools
|
|
187
193
|
registerInitScanWorkspaceTool(server);
|
|
188
194
|
registerInitializeWorkspaceTool(server);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BlueprintCaptureDigest } from "../types/BlueprintDigest.js";
|
|
2
|
+
/** Where the JSON body of a `browser_blueprint` payload starts and ends. ONE
|
|
3
|
+
* reader for both users; a second copy would drift and deliver an unshrunk
|
|
4
|
+
* payload rather than fail. The body STOPS at the next `### ` header, since the
|
|
5
|
+
* fork appends sections after `### Result` and only that one holds JSON. */
|
|
6
|
+
export declare function blueprintJsonSpan(text: string): {
|
|
7
|
+
start: number;
|
|
8
|
+
end: number;
|
|
9
|
+
} | undefined;
|
|
10
|
+
export declare const MAX_ELEMENTS = 2000;
|
|
11
|
+
/** The digest of one payload, or undefined when nothing in it keys to a page.
|
|
12
|
+
* `previousUrl` is the url of the capture before this one: a repeat capture
|
|
13
|
+
* carries no url of its own, so without it every element an action revealed is
|
|
14
|
+
* dropped. Never throws — an unparseable payload costs a digest, not the capture. */
|
|
15
|
+
export declare function digestFromBlueprintText(payload: string, previousUrl?: string): BlueprintCaptureDigest | undefined;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/** Where the JSON body of a `browser_blueprint` payload starts and ends. ONE
|
|
2
|
+
* reader for both users; a second copy would drift and deliver an unshrunk
|
|
3
|
+
* payload rather than fail. The body STOPS at the next `### ` header, since the
|
|
4
|
+
* fork appends sections after `### Result` and only that one holds JSON. */
|
|
5
|
+
export function blueprintJsonSpan(text) {
|
|
6
|
+
if (typeof text !== "string")
|
|
7
|
+
return undefined;
|
|
8
|
+
const start = text.indexOf("{");
|
|
9
|
+
if (start === -1)
|
|
10
|
+
return undefined;
|
|
11
|
+
const nextSection = text.slice(start).search(/\n### /);
|
|
12
|
+
return { start, end: nextSection === -1 ? text.length : start + nextSection };
|
|
13
|
+
}
|
|
14
|
+
/** A capture is bounded, but the payload is not this module's to trust. */
|
|
15
|
+
const MAX_DEPTH = 12;
|
|
16
|
+
export const MAX_ELEMENTS = 2000;
|
|
17
|
+
function text(value) {
|
|
18
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
19
|
+
}
|
|
20
|
+
function rowText(value) {
|
|
21
|
+
if (!Array.isArray(value))
|
|
22
|
+
return undefined;
|
|
23
|
+
const rows = value.filter((entry) => typeof entry === "string");
|
|
24
|
+
return rows.length > 0 ? rows : undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Fill a repeating element's `{param}` placeholders from one row. A literal
|
|
27
|
+
* substitution of what the capture recorded, never a pattern match; an unfilled
|
|
28
|
+
* placeholder is left as written so the template still grounds a citation. */
|
|
29
|
+
function fillTemplate(template, parameters) {
|
|
30
|
+
if (!parameters || typeof parameters !== "object")
|
|
31
|
+
return template;
|
|
32
|
+
const values = parameters;
|
|
33
|
+
return template.replace(/\{([^{}]+)\}/g, (placeholder, name) => {
|
|
34
|
+
const value = values[name];
|
|
35
|
+
return typeof value === "string" || typeof value === "number" ? String(value) : placeholder;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function identity(element) {
|
|
39
|
+
// Serialised rather than joined on a separator: a row's context text is page
|
|
40
|
+
// content and can hold any character, so any separator could be in it.
|
|
41
|
+
return JSON.stringify([element.role, element.accessibleName, element.testId ?? "", element.stableId ?? "", element.contextText ?? []]);
|
|
42
|
+
}
|
|
43
|
+
/** Every element this record describes. A repeating one keeps BOTH its template
|
|
44
|
+
* and each filled-in row, because the agent lifts a row's rendered name. */
|
|
45
|
+
function elementsOf(record) {
|
|
46
|
+
const role = text(record.role);
|
|
47
|
+
if (!role)
|
|
48
|
+
return [];
|
|
49
|
+
const testId = text(record.testId);
|
|
50
|
+
const stableId = text(record.stableId);
|
|
51
|
+
const base = (accessibleName, contextText) => ({
|
|
52
|
+
role,
|
|
53
|
+
accessibleName,
|
|
54
|
+
...(testId ? { testId } : {}),
|
|
55
|
+
...(stableId ? { stableId } : {}),
|
|
56
|
+
...(contextText ? { contextText } : {}),
|
|
57
|
+
});
|
|
58
|
+
const name = text(record.accessibleName);
|
|
59
|
+
if (name)
|
|
60
|
+
return [base(name, rowText(record.contextText))];
|
|
61
|
+
const template = text(record.accessibleNameTemplate);
|
|
62
|
+
// An identifier with no name still counts: `uiElementGrounded` grounds on
|
|
63
|
+
// `testId` or `stableId` ALONE, so dropping it hid a citable capture.
|
|
64
|
+
if (!template)
|
|
65
|
+
return testId || stableId ? [base("", rowText(record.contextText))] : [];
|
|
66
|
+
const found = [base(template)];
|
|
67
|
+
for (const item of Array.isArray(record.items) ? record.items : []) {
|
|
68
|
+
if (!item || typeof item !== "object")
|
|
69
|
+
continue;
|
|
70
|
+
const row = item;
|
|
71
|
+
found.push(base(fillTemplate(template, row.parameters), rowText(row.contextText)));
|
|
72
|
+
}
|
|
73
|
+
return found;
|
|
74
|
+
}
|
|
75
|
+
/** Every element anywhere in the capture. WALKED structurally rather than read at
|
|
76
|
+
* known paths: the fork nests them in three places today and may add a fourth.
|
|
77
|
+
* The cost is an element-shaped record from somewhere unexpected, which only
|
|
78
|
+
* makes the pool larger and the check softer. */
|
|
79
|
+
function collectElements(root) {
|
|
80
|
+
const found = [];
|
|
81
|
+
let truncated = false;
|
|
82
|
+
const seen = new Set();
|
|
83
|
+
const stack = [{ value: root, depth: 0 }];
|
|
84
|
+
while (stack.length > 0) {
|
|
85
|
+
if (found.length >= MAX_ELEMENTS) {
|
|
86
|
+
truncated = true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
const { value, depth } = stack.pop();
|
|
90
|
+
if (!value || typeof value !== "object" || depth > MAX_DEPTH)
|
|
91
|
+
continue;
|
|
92
|
+
if (Array.isArray(value)) {
|
|
93
|
+
for (const entry of value)
|
|
94
|
+
stack.push({ value: entry, depth: depth + 1 });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const record = value;
|
|
98
|
+
for (const element of elementsOf(record)) {
|
|
99
|
+
// Per ELEMENT, not per record: one repeating element materialises a row per
|
|
100
|
+
// entry of an unbounded `items` array, so one record can blow the cap.
|
|
101
|
+
if (found.length >= MAX_ELEMENTS) {
|
|
102
|
+
truncated = true;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
const key = identity(element);
|
|
106
|
+
if (seen.has(key))
|
|
107
|
+
continue;
|
|
108
|
+
seen.add(key);
|
|
109
|
+
found.push(element);
|
|
110
|
+
}
|
|
111
|
+
for (const child of Object.values(record)) {
|
|
112
|
+
if (child && typeof child === "object")
|
|
113
|
+
stack.push({ value: child, depth: depth + 1 });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { elements: found, truncated };
|
|
117
|
+
}
|
|
118
|
+
function urlOf(parsed) {
|
|
119
|
+
const blueprint = parsed.blueprint;
|
|
120
|
+
const inner = blueprint && typeof blueprint === "object" ? blueprint : undefined;
|
|
121
|
+
return text(parsed.url) ?? (inner ? text(inner.url) : undefined);
|
|
122
|
+
}
|
|
123
|
+
function pageHashOf(parsed) {
|
|
124
|
+
const blueprint = parsed.blueprint;
|
|
125
|
+
const inner = blueprint && typeof blueprint === "object" ? blueprint : undefined;
|
|
126
|
+
return text(parsed.pageHash) ?? (inner ? text(inner.pageHash) : undefined);
|
|
127
|
+
}
|
|
128
|
+
/** The digest of one payload, or undefined when nothing in it keys to a page.
|
|
129
|
+
* `previousUrl` is the url of the capture before this one: a repeat capture
|
|
130
|
+
* carries no url of its own, so without it every element an action revealed is
|
|
131
|
+
* dropped. Never throws — an unparseable payload costs a digest, not the capture. */
|
|
132
|
+
export function digestFromBlueprintText(payload, previousUrl) {
|
|
133
|
+
const span = blueprintJsonSpan(payload);
|
|
134
|
+
if (!span)
|
|
135
|
+
return undefined;
|
|
136
|
+
let parsed;
|
|
137
|
+
try {
|
|
138
|
+
parsed = JSON.parse(payload.slice(span.start, span.end).trimEnd());
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
144
|
+
return undefined;
|
|
145
|
+
const record = parsed;
|
|
146
|
+
const url = urlOf(record) ?? text(previousUrl);
|
|
147
|
+
if (!url)
|
|
148
|
+
return undefined;
|
|
149
|
+
const pageHash = pageHashOf(record);
|
|
150
|
+
const { elements, truncated } = collectElements(record);
|
|
151
|
+
return { url, ...(pageHash ? { pageHash } : {}), elements, ...(truncated ? { elementsTruncated: true } : {}) };
|
|
152
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import type { BlueprintCaptureDigest } from "../types/BlueprintDigest.js";
|
|
3
|
+
/** A run that takes more captures than this has a browsing problem of its own;
|
|
4
|
+
* the cap is here so a runaway loop cannot grow the process without bound. */
|
|
5
|
+
export declare const MAX_STORED_CAPTURES = 200;
|
|
6
|
+
/** Record one capture, or do nothing. NOTHING HERE MAY COST THE CAPTURE: the
|
|
7
|
+
* payload the agent receives is untouched and every failure is swallowed with a
|
|
8
|
+
* log line. Gated on `currentRunStateFile()`, not `resolveRunStatePath()`, because
|
|
9
|
+
* this gates BEHAVIOUR and `RUNNER_TEMP` is set in this repository's own CI. */
|
|
10
|
+
export declare function recordBlueprintCapture(result: CallToolResult | undefined): void;
|
|
11
|
+
/**
|
|
12
|
+
* Record where a `browser_navigate` took the browser.
|
|
13
|
+
*
|
|
14
|
+
* The server forwards the navigation, so it knows the destination without asking
|
|
15
|
+
* the agent — which is the point: a tool that exists to correct an agent's idea of
|
|
16
|
+
* which page it is on cannot take that page from the agent. Swallows everything,
|
|
17
|
+
* like the capture recorder: nothing here may cost a navigation.
|
|
18
|
+
*/
|
|
19
|
+
export declare function recordBrowserNavigation(url: unknown): void;
|
|
20
|
+
/** The page the browser is on, and how the server came to know it: a navigation it
|
|
21
|
+
* forwarded (`currentUrl`) or the last capture that named a page (`blueprint`). */
|
|
22
|
+
export declare function lastKnownBrowserUrl(): {
|
|
23
|
+
url: string;
|
|
24
|
+
source: "currentUrl" | "blueprint";
|
|
25
|
+
} | undefined;
|
|
26
|
+
/** This run's digests, and no other run's — the same key the capture stored
|
|
27
|
+
* them under. A copy: a reader that spliced the array would delete the run's
|
|
28
|
+
* evidence. */
|
|
29
|
+
export declare function capturedBlueprintDigests(): BlueprintCaptureDigest[];
|
|
30
|
+
/** For tests, and as a safety hook between runs. */
|
|
31
|
+
export declare function clearBlueprintDigests(): void;
|