@skyramp/mcp 0.1.6 → 0.1.8
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/prompts/initialize-workspace/initializeWorkspacePrompt.js +150 -153
- package/build/prompts/test-recommendation/diffExecutionPlan.js +290 -0
- package/build/prompts/test-recommendation/fullRepoCatalog.js +271 -0
- package/build/prompts/test-recommendation/recommendationShared.js +68 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +8 -640
- package/build/prompts/testbot/testbot-prompts.js +12 -2
- package/build/prompts/testbot/testbot-prompts.test.js +20 -3
- package/build/services/ScenarioGenerationService.js +3 -0
- package/build/services/TestGenerationService.js +3 -0
- package/build/tools/code-refactor/codeReuseTool.js +3 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -0
- package/build/tools/code-refactor/modularizationTool.js +3 -0
- package/build/tools/workspace/initializeWorkspaceTool.js +1 -1
- package/build/utils/docker.test.js +1 -1
- package/build/utils/featureFlags.test.js +2 -2
- package/build/utils/gitStaging.js +18 -0
- package/build/utils/gitStaging.test.js +87 -0
- package/build/utils/utils.js +2 -2
- package/build/utils/versions.js +1 -1
- package/node_modules/playwright/lib/mcp/browser/context.js +2 -0
- package/node_modules/playwright/lib/mcp/browser/tools/keyboard.js +2 -2
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +17 -26
- package/package.json +2 -2
|
@@ -291,7 +291,7 @@ function buildServiceContext(services) {
|
|
|
291
291
|
if (svc.api?.baseUrl)
|
|
292
292
|
parts.push(` <base_url>${escapeXml(svc.api.baseUrl)}</base_url>`);
|
|
293
293
|
if (svc.testDirectory)
|
|
294
|
-
parts.push(` <
|
|
294
|
+
parts.push(` <test_directory>${escapeXml(svc.testDirectory)}</test_directory>`);
|
|
295
295
|
parts.push('</service>');
|
|
296
296
|
return parts.join('\n');
|
|
297
297
|
});
|
|
@@ -305,6 +305,9 @@ export async function readWorkspaceServices(repositoryPath) {
|
|
|
305
305
|
const rawConfig = await readWorkspaceConfigRaw(repositoryPath);
|
|
306
306
|
return (rawConfig?.services ?? []);
|
|
307
307
|
}
|
|
308
|
+
export function buildWorkspaceRecoveryPrefix(repositoryPath) {
|
|
309
|
+
return `IMPORTANT: The existing .skyramp/workspace.yml failed to parse or validate. Before proceeding with any tasks below, you MUST call skyramp_init_scan with workspacePath "${repositoryPath}" and force: true, then call skyramp_init_workspace with workspacePath "${repositoryPath}", the discovered services, scanToken, and force: true to regenerate the workspace file.\n\n`;
|
|
310
|
+
}
|
|
308
311
|
export function registerTestbotPrompt(server) {
|
|
309
312
|
logger.info("Registering testbot prompt");
|
|
310
313
|
server.registerPrompt("skyramp_testbot", {
|
|
@@ -351,10 +354,17 @@ export function registerTestbotPrompt(server) {
|
|
|
351
354
|
.string()
|
|
352
355
|
.optional()
|
|
353
356
|
.describe("Browser login credentials for UI test recording (format: 'username:password', one per line). Injected into the prompt as a <ui-credentials> block so the agent logs in before recording traces."),
|
|
357
|
+
workspaceValidationFailed: z
|
|
358
|
+
.boolean()
|
|
359
|
+
.default(false)
|
|
360
|
+
.describe("Set to true when the testbot detected that .skyramp/workspace.yml exists but failed schema validation. Instructs the agent to regenerate the workspace file before proceeding."),
|
|
354
361
|
},
|
|
355
362
|
}, async (args) => {
|
|
356
363
|
const services = await readWorkspaceServices(args.repositoryPath);
|
|
357
|
-
|
|
364
|
+
let prompt = getTestbotPrompt(args.prTitle, args.prDescription, args.summaryOutputFile, args.repositoryPath, args.baseBranch, args.maxRecommendations, args.maxGenerate, args.maxCritical, args.prNumber, args.userPrompt, services.length ? services : undefined, args.stateOutputFile, args.uiCredentials);
|
|
365
|
+
if (args.workspaceValidationFailed) {
|
|
366
|
+
prompt = buildWorkspaceRecoveryPrefix(args.repositoryPath) + prompt;
|
|
367
|
+
}
|
|
358
368
|
AnalyticsService.pushMCPToolEvent("skyramp_testbot_prompt", undefined, {}).catch(() => { });
|
|
359
369
|
return {
|
|
360
370
|
messages: [
|
|
@@ -59,7 +59,7 @@ describe("buildServiceContext (via getTestbotPrompt)", () => {
|
|
|
59
59
|
expect(prompt).toContain("<language>python</language>");
|
|
60
60
|
expect(prompt).toContain("<framework>pytest</framework>");
|
|
61
61
|
expect(prompt).toContain("<base_url>http://localhost:8000</base_url>");
|
|
62
|
-
expect(prompt).toContain("<
|
|
62
|
+
expect(prompt).toContain("<test_directory>tests/python</test_directory>");
|
|
63
63
|
expect(prompt).toContain("</service>");
|
|
64
64
|
expect(prompt).toContain("<services>");
|
|
65
65
|
expect(prompt).toContain("</services>");
|
|
@@ -70,7 +70,7 @@ describe("buildServiceContext (via getTestbotPrompt)", () => {
|
|
|
70
70
|
expect(prompt).not.toContain("<language>");
|
|
71
71
|
expect(prompt).not.toContain("<framework>");
|
|
72
72
|
expect(prompt).not.toContain("<base_url>");
|
|
73
|
-
expect(prompt).not.toContain("<
|
|
73
|
+
expect(prompt).not.toContain("<test_directory>");
|
|
74
74
|
});
|
|
75
75
|
it("renders multiple services", () => {
|
|
76
76
|
const prompt = callWithServices([
|
|
@@ -104,7 +104,7 @@ describe("buildServiceContext (via getTestbotPrompt)", () => {
|
|
|
104
104
|
api: { baseUrl: "http://host?a=1&b=2" },
|
|
105
105
|
},
|
|
106
106
|
]);
|
|
107
|
-
expect(prompt).toContain("<
|
|
107
|
+
expect(prompt).toContain("<test_directory>tests/a&b</test_directory>");
|
|
108
108
|
expect(prompt).toContain("<base_url>http://host?a=1&b=2</base_url>");
|
|
109
109
|
});
|
|
110
110
|
it("places services block between REPOSITORY PATH and instruction line", () => {
|
|
@@ -231,3 +231,20 @@ describe("drift analysis inline embedding", () => {
|
|
|
231
231
|
expect(prompt).toContain("rules in `<drift_analysis_rules>`");
|
|
232
232
|
});
|
|
233
233
|
});
|
|
234
|
+
describe("buildWorkspaceRecoveryPrefix", () => {
|
|
235
|
+
const { buildWorkspaceRecoveryPrefix } = require("./testbot-prompts.js");
|
|
236
|
+
it("includes repositoryPath in both init_scan and init_workspace instructions", () => {
|
|
237
|
+
const prefix = buildWorkspaceRecoveryPrefix("/home/user/repo");
|
|
238
|
+
expect(prefix).toContain('skyramp_init_scan with workspacePath "/home/user/repo"');
|
|
239
|
+
expect(prefix).toContain('skyramp_init_workspace with workspacePath "/home/user/repo"');
|
|
240
|
+
});
|
|
241
|
+
it("includes force: true for both tool calls", () => {
|
|
242
|
+
const prefix = buildWorkspaceRecoveryPrefix("/repo");
|
|
243
|
+
expect(prefix).toContain("force: true, then call skyramp_init_workspace");
|
|
244
|
+
expect(prefix).toContain("force: true to regenerate");
|
|
245
|
+
});
|
|
246
|
+
it("starts with IMPORTANT", () => {
|
|
247
|
+
const prefix = buildWorkspaceRecoveryPrefix("/repo");
|
|
248
|
+
expect(prefix).toMatch(/^IMPORTANT:/);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
@@ -2,6 +2,7 @@ import { AUTH_PLACEHOLDER_TOKEN } from "../types/TestTypes.js";
|
|
|
2
2
|
import { isAuthorizationHeaderName } from "../utils/workspaceAuth.js";
|
|
3
3
|
import { inferExpectedStatus } from "../utils/httpDefaults.js";
|
|
4
4
|
import { logger } from "../utils/logger.js";
|
|
5
|
+
import { stageGeneratedPaths } from "../utils/gitStaging.js";
|
|
5
6
|
import fs from "fs";
|
|
6
7
|
import path from "path";
|
|
7
8
|
export class ScenarioGenerationService {
|
|
@@ -41,6 +42,8 @@ export class ScenarioGenerationService {
|
|
|
41
42
|
}
|
|
42
43
|
existingRequests.push(traceRequest);
|
|
43
44
|
fs.writeFileSync(filePath, JSON.stringify(existingRequests, null, 2), "utf8");
|
|
45
|
+
// Stage so testbot includes the generated files in its output commit.
|
|
46
|
+
await stageGeneratedPaths(filePath);
|
|
44
47
|
logger.info("Trace request added to file", {
|
|
45
48
|
filePath,
|
|
46
49
|
totalRequests: existingRequests.length,
|
|
@@ -8,6 +8,7 @@ import { getEntryPoint } from "../utils/telemetry.js";
|
|
|
8
8
|
import { getLanguageSteps } from "../utils/language-helper.js";
|
|
9
9
|
import { logger } from "../utils/logger.js";
|
|
10
10
|
import { normalizeLanguageParams } from "../utils/normalizeParams.js";
|
|
11
|
+
import { stageGeneratedPaths } from "../utils/gitStaging.js";
|
|
11
12
|
export class TestGenerationService {
|
|
12
13
|
client;
|
|
13
14
|
constructor() {
|
|
@@ -324,6 +325,8 @@ The generated test file remains unchanged and ready to use as-is.
|
|
|
324
325
|
throw new Error(`Test generation failed: ${result}`);
|
|
325
326
|
}
|
|
326
327
|
}
|
|
328
|
+
// Stage so testbot includes the generated files in its output commit.
|
|
329
|
+
await stageGeneratedPaths(generateOptions.outputDir);
|
|
327
330
|
return `
|
|
328
331
|
**Generated Test Details:**
|
|
329
332
|
- Test Type: ${this.getTestType()}
|
|
@@ -4,6 +4,7 @@ import { getCodeReusePrompt } from "../../prompts/code-reuse.js";
|
|
|
4
4
|
import { codeRefactoringSchema, languageSchema, } from "../../types/TestTypes.js";
|
|
5
5
|
import { SKYRAMP_UTILS_HEADER } from "../../utils/utils.js";
|
|
6
6
|
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
7
|
+
import { stageGeneratedPaths } from "../../utils/gitStaging.js";
|
|
7
8
|
const codeReuseSchema = z.object({
|
|
8
9
|
testFile: z
|
|
9
10
|
.string()
|
|
@@ -70,6 +71,8 @@ export function registerCodeReuseTool(server) {
|
|
|
70
71
|
language: params.language,
|
|
71
72
|
framework: params.framework,
|
|
72
73
|
});
|
|
74
|
+
// Stage so testbot includes the generated files in its output commit.
|
|
75
|
+
await stageGeneratedPaths(params.testFile);
|
|
73
76
|
const codeReusePrompt = getCodeReusePrompt(params.testFile, params.language, params.framework);
|
|
74
77
|
return {
|
|
75
78
|
content: [
|
|
@@ -5,6 +5,7 @@ import { getContractProviderAssertionsPrompt } from "../../prompts/enhance-asser
|
|
|
5
5
|
import { getIntegrationAssertionsPrompt } from "../../prompts/enhance-assertions/integrationAssertionsPrompt.js";
|
|
6
6
|
import { getUIAssertionsPrompt } from "../../prompts/enhance-assertions/uiAssertionsPrompt.js";
|
|
7
7
|
import { isTestbotEnabled } from "../../utils/featureFlags.js";
|
|
8
|
+
import { stageGeneratedPaths } from "../../utils/gitStaging.js";
|
|
8
9
|
const TOOL_NAME = "skyramp_enhance_assertions";
|
|
9
10
|
const TESTBOT_UI_CHECKS = `
|
|
10
11
|
### Additional Testbot-Specific Checks
|
|
@@ -34,6 +35,8 @@ export function registerEnhanceAssertionsTool(server) {
|
|
|
34
35
|
inputSchema: enhanceAssertionsSchema,
|
|
35
36
|
}, async (params) => {
|
|
36
37
|
const { testFile, testType, enhanceType } = params;
|
|
38
|
+
// Stage so testbot includes the generated files in its output commit.
|
|
39
|
+
await stageGeneratedPaths(testFile);
|
|
37
40
|
const enhanceCtx = enhanceType;
|
|
38
41
|
let instructions;
|
|
39
42
|
if (testType === TestType.UI) {
|
|
@@ -6,6 +6,7 @@ import { ModularizationService, } from "../../services/ModularizationService.js"
|
|
|
6
6
|
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
7
7
|
import { normalizeLanguageParams, resolveParamAliases, } from "../../utils/normalizeParams.js";
|
|
8
8
|
import { normalizeSkyrampImportsInFile } from "../../utils/normalizeSkyrampImports.js";
|
|
9
|
+
import { stageGeneratedPaths } from "../../utils/gitStaging.js";
|
|
9
10
|
const modularizationSchema = {
|
|
10
11
|
testFile: z
|
|
11
12
|
.string()
|
|
@@ -79,6 +80,8 @@ After modularization, if errors remain, call skyramp_fix_errors.
|
|
|
79
80
|
if (!params.isTraceBased && [TestType.UI, TestType.E2E, TestType.INTEGRATION].includes(params.testType))
|
|
80
81
|
params.isTraceBased = true;
|
|
81
82
|
normalizeSkyrampImportsInFile(params.testFile);
|
|
83
|
+
// Stage so testbot includes the generated files in its output commit.
|
|
84
|
+
await stageGeneratedPaths(params.testFile);
|
|
82
85
|
// Default prompt to test file content
|
|
83
86
|
if (!params.prompt && params.testFile) {
|
|
84
87
|
try {
|
|
@@ -46,7 +46,7 @@ const initializeWorkspaceSchema = z.object({
|
|
|
46
46
|
force: z
|
|
47
47
|
.boolean()
|
|
48
48
|
.default(false)
|
|
49
|
-
.describe("Set to true
|
|
49
|
+
.describe("Set to true to overwrite an existing workspace file. Use when the user explicitly requests it, or when recovering from a workspace validation failure (schema mismatch, unknown fields). Default is false."),
|
|
50
50
|
});
|
|
51
51
|
/**
|
|
52
52
|
* Write a YAML workspace config to disk, bypassing the library's restricted
|
|
@@ -54,7 +54,7 @@ describe("dockerImageExistsLocally", () => {
|
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
56
|
describe("pullDockerImage", () => {
|
|
57
|
-
const IMAGE = "skyramp/executor:v1.3.
|
|
57
|
+
const IMAGE = "skyramp/executor:v1.3.24";
|
|
58
58
|
beforeEach(() => jest.clearAllMocks());
|
|
59
59
|
describe("on amd64 host", () => {
|
|
60
60
|
const originalArch = process.arch;
|
|
@@ -41,11 +41,11 @@ describe("resolveServiceDetailsRef", () => {
|
|
|
41
41
|
process.env.SKYRAMP_FEATURE_TESTBOT = "1";
|
|
42
42
|
});
|
|
43
43
|
it("returns <services> block reference for testDirRef", () => {
|
|
44
|
-
expect(resolveServiceDetailsRef().testDirRef).toContain("<
|
|
44
|
+
expect(resolveServiceDetailsRef().testDirRef).toContain("<test_directory>");
|
|
45
45
|
expect(resolveServiceDetailsRef().testDirRef).toContain("<services>");
|
|
46
46
|
});
|
|
47
47
|
it("returns <services> block reference for frontendTestDirRef", () => {
|
|
48
|
-
expect(resolveServiceDetailsRef().frontendTestDirRef).toContain("<
|
|
48
|
+
expect(resolveServiceDetailsRef().frontendTestDirRef).toContain("<test_directory>");
|
|
49
49
|
expect(resolveServiceDetailsRef().frontendTestDirRef).toContain("<services>");
|
|
50
50
|
});
|
|
51
51
|
it("returns <services> block reference for authSourceRef", () => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
import { isTestbotEnabled } from "./featureFlags.js";
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
/**
|
|
7
|
+
* Stages a file path an MCP tool just wrote into the git index by
|
|
8
|
+
* running `git add -- <path>`.
|
|
9
|
+
*
|
|
10
|
+
* Gated by the SKYRAMP_FEATURE_TESTBOT=1 env var, which is set only
|
|
11
|
+
* inside a testbot CI run.
|
|
12
|
+
*/
|
|
13
|
+
export async function stageGeneratedPaths(path, cwd) {
|
|
14
|
+
if (!isTestbotEnabled())
|
|
15
|
+
return;
|
|
16
|
+
await execFileAsync("git", ["add", "--", path], { cwd });
|
|
17
|
+
logger.info("Staged generated file", { path });
|
|
18
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
jest.mock("./logger.js", () => ({
|
|
2
|
+
logger: {
|
|
3
|
+
debug: jest.fn(),
|
|
4
|
+
info: jest.fn(),
|
|
5
|
+
warning: jest.fn(),
|
|
6
|
+
error: jest.fn(),
|
|
7
|
+
},
|
|
8
|
+
}));
|
|
9
|
+
const execFileMock = jest.fn();
|
|
10
|
+
jest.mock("child_process", () => ({
|
|
11
|
+
execFile: (cmd, args, opts, cb) => {
|
|
12
|
+
const result = execFileMock(cmd, args, opts);
|
|
13
|
+
const cbErr = result && typeof result === "object" && "err" in result
|
|
14
|
+
? result.err
|
|
15
|
+
: null;
|
|
16
|
+
cb(cbErr, "", "");
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
import { stageGeneratedPaths } from "./gitStaging.js";
|
|
20
|
+
import { logger } from "./logger.js";
|
|
21
|
+
const loggerInfoMock = logger.info;
|
|
22
|
+
let originalTestbotEnv;
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
originalTestbotEnv = process.env.SKYRAMP_FEATURE_TESTBOT;
|
|
25
|
+
});
|
|
26
|
+
afterAll(() => {
|
|
27
|
+
if (originalTestbotEnv === undefined) {
|
|
28
|
+
delete process.env.SKYRAMP_FEATURE_TESTBOT;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
process.env.SKYRAMP_FEATURE_TESTBOT = originalTestbotEnv;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
execFileMock.mockReset();
|
|
36
|
+
loggerInfoMock.mockReset();
|
|
37
|
+
});
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
delete process.env.SKYRAMP_FEATURE_TESTBOT;
|
|
40
|
+
});
|
|
41
|
+
describe("stageGeneratedPaths", () => {
|
|
42
|
+
describe("gated on (SKYRAMP_FEATURE_TESTBOT=1)", () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
process.env.SKYRAMP_FEATURE_TESTBOT = "1";
|
|
45
|
+
});
|
|
46
|
+
it("runs `git add -- <path>` with the supplied path", async () => {
|
|
47
|
+
await stageGeneratedPaths("tests/a.py");
|
|
48
|
+
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
49
|
+
expect(execFileMock).toHaveBeenCalledWith("git", ["add", "--", "tests/a.py"], expect.objectContaining({}));
|
|
50
|
+
expect(loggerInfoMock).toHaveBeenCalledWith("Staged generated file", expect.objectContaining({ path: "tests/a.py" }));
|
|
51
|
+
});
|
|
52
|
+
it("passes through the cwd option when provided", async () => {
|
|
53
|
+
await stageGeneratedPaths("tests/a.py", "/repo/root");
|
|
54
|
+
expect(execFileMock).toHaveBeenCalledWith("git", ["add", "--", "tests/a.py"], expect.objectContaining({ cwd: "/repo/root" }));
|
|
55
|
+
});
|
|
56
|
+
it("propagates errors from git add", async () => {
|
|
57
|
+
const err = new Error("fatal: not a git repository");
|
|
58
|
+
execFileMock.mockImplementation(() => {
|
|
59
|
+
throw err;
|
|
60
|
+
});
|
|
61
|
+
await expect(stageGeneratedPaths("tests/a.py")).rejects.toThrow("fatal: not a git repository");
|
|
62
|
+
});
|
|
63
|
+
it("propagates async callback errors from git add", async () => {
|
|
64
|
+
const err = Object.assign(new Error("git: command not found"), {
|
|
65
|
+
code: "ENOENT",
|
|
66
|
+
});
|
|
67
|
+
execFileMock.mockReturnValueOnce({ err });
|
|
68
|
+
await expect(stageGeneratedPaths("tests/a.py", "/repo/root")).rejects.toThrow("git: command not found");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe("gated off (SKYRAMP_FEATURE_TESTBOT unset)", () => {
|
|
72
|
+
it("does not invoke git add", async () => {
|
|
73
|
+
await stageGeneratedPaths("tests/a.py");
|
|
74
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
75
|
+
expect(loggerInfoMock).not.toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe("gated off (SKYRAMP_FEATURE_TESTBOT='0')", () => {
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
process.env.SKYRAMP_FEATURE_TESTBOT = "0";
|
|
81
|
+
});
|
|
82
|
+
it("does not invoke git add", async () => {
|
|
83
|
+
await stageGeneratedPaths("tests/a.py");
|
|
84
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
package/build/utils/utils.js
CHANGED
|
@@ -144,8 +144,8 @@ export function generateSkyrampHeader(language) {
|
|
|
144
144
|
export function resolveServiceDetailsRef() {
|
|
145
145
|
if (isTestbotEnabled()) {
|
|
146
146
|
return {
|
|
147
|
-
testDirRef: "the `<
|
|
148
|
-
frontendTestDirRef: "the **frontend** service's `<
|
|
147
|
+
testDirRef: "the `<test_directory>` from the `<services>` block",
|
|
148
|
+
frontendTestDirRef: "the **frontend** service's `<test_directory>` from the `<services>` block",
|
|
149
149
|
baseUrlRef: "the `<base_url>` from the `<services>` block",
|
|
150
150
|
authSourceRef: "the `<services>` block",
|
|
151
151
|
};
|
package/build/utils/versions.js
CHANGED
|
@@ -29,9 +29,9 @@ const pressKey = (0, import_tool.defineTabTool)({
|
|
|
29
29
|
schema: {
|
|
30
30
|
name: "browser_press_key",
|
|
31
31
|
title: "Press a key",
|
|
32
|
-
description: "Press a
|
|
32
|
+
description: "Press a key on the keyboard",
|
|
33
33
|
inputSchema: import_mcpBundle.z.object({
|
|
34
|
-
key: import_mcpBundle.z.string().describe(
|
|
34
|
+
key: import_mcpBundle.z.string().describe("Name of the key to press or a character to generate, such as `ArrowLeft` or `a`")
|
|
35
35
|
}),
|
|
36
36
|
type: "input"
|
|
37
37
|
},
|
|
@@ -93,7 +93,7 @@ class TraceRecordingBackend {
|
|
|
93
93
|
await this._browserBackend.initialize(clientInfo);
|
|
94
94
|
this._initialized = true;
|
|
95
95
|
traceDebug("TraceRecordingBackend initialized");
|
|
96
|
-
this.
|
|
96
|
+
this._browserBackend.context.onBrowserContextCreated = (browserContext) => this._installPopupListener(browserContext);
|
|
97
97
|
}
|
|
98
98
|
async listTools() {
|
|
99
99
|
const browserTools = await this._browserBackend.listTools();
|
|
@@ -729,32 +729,23 @@ ${details}` }]
|
|
|
729
729
|
* When a popup opens, find the most recent click that triggered it and mark it
|
|
730
730
|
* with a popupAlias signal. Switch the current page alias to the new page.
|
|
731
731
|
*/
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
initialPageSeen = true;
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
if (this._reloading) {
|
|
746
|
-
traceDebug("Ignoring spurious popup opened during reload");
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
this._pageCount++;
|
|
750
|
-
const popupAlias = `page${this._pageCount}`;
|
|
751
|
-
this._currentPageAlias = popupAlias;
|
|
752
|
-
this._pendingPopupAlias = popupAlias;
|
|
753
|
-
traceDebug(`Popup page opened: ${popupAlias} (pending stamp)`);
|
|
754
|
-
});
|
|
755
|
-
} catch {
|
|
732
|
+
_installPopupListener(browserContext) {
|
|
733
|
+
let initialPageSeen = false;
|
|
734
|
+
browserContext.on("page", () => {
|
|
735
|
+
if (!initialPageSeen) {
|
|
736
|
+
initialPageSeen = true;
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (this._reloading) {
|
|
740
|
+
traceDebug("Ignoring spurious popup opened during reload");
|
|
741
|
+
return;
|
|
756
742
|
}
|
|
757
|
-
|
|
743
|
+
this._pageCount++;
|
|
744
|
+
const popupAlias = `page${this._pageCount}`;
|
|
745
|
+
this._currentPageAlias = popupAlias;
|
|
746
|
+
this._pendingPopupAlias = popupAlias;
|
|
747
|
+
traceDebug(`Popup page opened: ${popupAlias} (pending stamp)`);
|
|
748
|
+
});
|
|
758
749
|
}
|
|
759
750
|
_maybeTrackAction(toolName, args, result, timestamp, pageAliasBeforeAction) {
|
|
760
751
|
if (result.isError)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"main": "build/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./build/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
57
57
|
"@playwright/test": "^1.55.0",
|
|
58
|
-
"@skyramp/skyramp": "1.3.
|
|
58
|
+
"@skyramp/skyramp": "1.3.24",
|
|
59
59
|
"dockerode": "^5.0.0",
|
|
60
60
|
"fast-glob": "^3.3.3",
|
|
61
61
|
"js-yaml": "^4.1.1",
|