@skyramp/mcp 0.4.4 → 0.5.0-rc.10
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/entitlements/entitlementGate.d.ts +8 -0
- package/build/entitlements/entitlementGate.js +57 -0
- package/build/entitlements/runCodes.d.ts +18 -0
- package/build/entitlements/runCodes.js +16 -0
- package/build/index.js +32 -6
- package/build/playwright/PlaywrightTraceService.d.ts +17 -0
- package/build/playwright/PlaywrightTraceService.js +29 -1
- package/build/playwright/dataSafetyConfig.d.ts +74 -0
- package/build/playwright/dataSafetyConfig.js +109 -0
- package/build/playwright/essentialTools.d.ts +1 -0
- package/build/playwright/essentialTools.js +44 -0
- package/build/playwright/registerPlaywrightTools.d.ts +36 -1
- package/build/playwright/registerPlaywrightTools.js +87 -43
- package/build/playwright/safeResolveDataSafety.d.ts +24 -0
- package/build/playwright/safeResolveDataSafety.js +33 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +46 -177
- package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
- package/build/prompts/modularization/ui-test-modularization.js +9 -2
- package/build/prompts/test-maintenance/driftAnalysisShared.js +1 -1
- package/build/services/FleetExecutionService.d.ts +87 -16
- package/build/services/FleetExecutionService.js +487 -48
- package/build/services/FleetRunRegistry.d.ts +49 -0
- package/build/services/FleetRunRegistry.js +178 -0
- package/build/services/containerEnv.js +8 -2
- package/build/services/fleetLimitConfirmation.d.ts +19 -0
- package/build/services/fleetLimitConfirmation.js +126 -0
- package/build/tool-phases.d.ts +14 -8
- package/build/tool-phases.js +88 -43
- package/build/tools/code-refactor/assertion-state.js +3 -1
- package/build/tools/code-refactor/codeReuseTool.js +2 -1
- package/build/tools/code-refactor/utils-verify-gates.js +1 -1
- package/build/tools/executeFleetTool.d.ts +6 -4
- package/build/tools/executeFleetTool.js +67 -23
- package/build/tools/fixErrorTool.js +2 -1
- package/build/tools/fleetLifecycleTools.d.ts +20 -0
- package/build/tools/fleetLifecycleTools.js +124 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +38 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.js +7 -1
- package/build/tools/generate-tests/generateUILoadRestTool.d.ts +19 -1
- package/build/tools/generate-tests/generateUILoadRestTool.js +316 -50
- package/build/tools/submitReportTool.js +4 -122
- package/build/tools/test-management/registerTestPlanTool.js +22 -6
- package/build/tools/workspace/initScanWorkspaceTool.d.ts +11 -1
- package/build/tools/workspace/initScanWorkspaceTool.js +111 -9
- package/build/tools/workspace/initializeWorkspaceTool.js +40 -4
- package/build/types/AssertionOutcome.d.ts +3 -0
- package/build/types/TestbotReport.d.ts +0 -37
- package/build/types/TestbotReport.js +0 -22
- package/build/types/index.d.ts +1 -1
- package/build/types/index.js +1 -1
- package/build/utils/assertion-verify/created-add-guard.d.ts +12 -0
- package/build/utils/assertion-verify/created-add-guard.js +173 -0
- package/build/utils/assertion-verify/index.d.ts +1 -0
- package/build/utils/assertion-verify/index.js +1 -0
- package/build/utils/assertion-verify/verify.d.ts +7 -3
- package/build/utils/assertion-verify/verify.js +9 -3
- package/build/utils/featureFlags.js +6 -3
- package/build/utils/repositorySlug.d.ts +14 -0
- package/build/utils/repositorySlug.js +46 -1
- package/build/utils/telemetry.d.ts +2 -0
- package/build/utils/telemetry.js +12 -0
- package/build/utils/utils-verify/action-key.d.ts +59 -2
- package/build/utils/utils-verify/action-key.js +162 -6
- package/build/utils/utils-verify/action-sites.d.ts +7 -0
- package/build/utils/utils-verify/action-sites.js +277 -35
- package/build/utils/utils-verify/call-sites.d.ts +8 -2
- package/build/utils/utils-verify/language-spec.js +1 -1
- package/build/utils/utils-verify/parse.d.ts +4 -0
- package/build/utils/utils-verify/parse.js +2 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +63 -121
- package/build/workspace/workspace.js +45 -48
- package/build/workspace/workspaceVerifier.d.ts +22 -0
- package/build/workspace/workspaceVerifier.js +65 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +7 -6
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +5 -4
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +12 -0
- package/node_modules/playwright/lib/mcp/skyramp/dataSafetyGuard.js +822 -0
- package/node_modules/playwright/lib/mcp/skyramp/dataSafetySession.js +372 -0
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +101 -8
- package/node_modules/playwright/lib/mcp/skyramp/finishTestSetupTool.js +45 -0
- package/node_modules/playwright/lib/mcp/skyramp/index.js +16 -0
- package/node_modules/playwright/lib/mcp/skyramp/recordingPhase.js +65 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +1 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +598 -127
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +85 -31
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +132 -5
- package/node_modules/playwright/node_modules/playwright-core/lib/cli/program.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/client/browserContext.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/protocol/validator.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/browserContext.js +17 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harRecorder.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harTracer.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/clickExpandedState.js +75 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +85 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bou8kKzE.js → codeMirrorModule-BuhAdckO.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-COAHN430.js +809 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.0MJW0uG7.js → index.7AU8h0PZ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.GLqsx5cI.js → uiMode.Dl7smBJn.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/cli/program.ts +5 -0
- package/node_modules/playwright/node_modules/playwright-core/src/client/browserContext.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/client/types.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/protocol/validator.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/browserContext.ts +25 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/har/harRecorder.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/har/harTracer.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/clickExpandedState.ts +82 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +137 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +4 -4
- package/plugin/prompts/plan-tests.md +2 -2
- package/plugin/prompts/testbot-task1.md +1 -1
- package/plugin/skills/enhance-assertions/reference/integration.md +1 -0
- package/plugin/skills/setup-testbot-target-sut/SKILL.md +60 -0
- package/plugin/skills/setup-testbot-target-sut/reference/ci-workflow.md +26 -0
- package/plugin/skills/setup-testbot-target-sut/reference/deployed-environment.md +105 -0
- package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +14 -0
- package/plugin/skills/setup-testbot-target-sut/reference/troubleshooting.md +20 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +0 -2
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +0 -178
- package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +0 -2
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +0 -311
- package/build/prompts/sut-setup/shared.d.ts +0 -62
- package/build/prompts/sut-setup/shared.js +0 -196
- package/build/resources/sutSetupResource.d.ts +0 -2
- package/build/resources/sutSetupResource.js +0 -63
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DTp2-8Si.js +0 -809
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var dataSafetySession_exports = {};
|
|
20
|
+
__export(dataSafetySession_exports, {
|
|
21
|
+
DataSafetySession: () => DataSafetySession,
|
|
22
|
+
assertNoNetworkPolicyCollision: () => assertNoNetworkPolicyCollision
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(dataSafetySession_exports);
|
|
25
|
+
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
26
|
+
var import_dataSafetyGuard = require("./dataSafetyGuard");
|
|
27
|
+
const traceDebug = (0, import_utilsBundle.debug)("pw:mcp:trace");
|
|
28
|
+
function originalRequestHeaders(req) {
|
|
29
|
+
let first = req;
|
|
30
|
+
for (let prev = first.redirectedFrom(); prev; prev = prev.redirectedFrom())
|
|
31
|
+
first = prev;
|
|
32
|
+
return first.headers();
|
|
33
|
+
}
|
|
34
|
+
function assertNoNetworkPolicyCollision(network, guardActive) {
|
|
35
|
+
if (guardActive && (network?.allowedOrigins?.length || network?.blockedOrigins?.length))
|
|
36
|
+
throw new Error("data safety guard cannot compose with network.allowedOrigins/blockedOrigins yet \u2014 the create-capture path cannot fall back");
|
|
37
|
+
}
|
|
38
|
+
class DataSafetySession {
|
|
39
|
+
constructor(_guard, _requestProvenance) {
|
|
40
|
+
this._guard = _guard;
|
|
41
|
+
this._requestProvenance = _requestProvenance;
|
|
42
|
+
// response.text() reads in flight from the 'response' listener below —
|
|
43
|
+
// export/teardown/ledgerJson must settle these first or a POST that just
|
|
44
|
+
// barely raced past export can be missed entirely (dropped promise). Keyed
|
|
45
|
+
// by the promise, valued by the request URL — settleBodyReads needs to
|
|
46
|
+
// name WHICH create(s) are still in flight when it times out, not just
|
|
47
|
+
// how many.
|
|
48
|
+
this._pendingBodyReads = /* @__PURE__ */ new Map();
|
|
49
|
+
// Requests whose create-response body was captured synchronously in the
|
|
50
|
+
// route handler (route.fetch -> fulfill) — the 'response' listener must not
|
|
51
|
+
// record them a second time. WeakSet: entries die with their requests.
|
|
52
|
+
this._routeCapturedRequests = /* @__PURE__ */ new WeakSet();
|
|
53
|
+
// Each redirect hop is a separate Request. Preserve the original POST's
|
|
54
|
+
// snapshot even when later hops or response bodies arrive in another phase.
|
|
55
|
+
// Request-start snapshot of action provenance + the request's own
|
|
56
|
+
// resourceType, keyed by Request so every later observation of the same
|
|
57
|
+
// request (route capture, deferred read, redirect hop) reads one value.
|
|
58
|
+
this._contextByRequest = /* @__PURE__ */ new WeakMap();
|
|
59
|
+
}
|
|
60
|
+
static {
|
|
61
|
+
/**
|
|
62
|
+
* Bounds _captureCreateAtRoute's route.fetch — overridable in tests (same
|
|
63
|
+
* pattern as settleTimeoutMs) so a synthetic never-responding POST route
|
|
64
|
+
* doesn't have to eat the full production timeout.
|
|
65
|
+
*/
|
|
66
|
+
this.routeFetchTimeoutMs = 3e4;
|
|
67
|
+
}
|
|
68
|
+
get guard() {
|
|
69
|
+
return this._guard;
|
|
70
|
+
}
|
|
71
|
+
/** Whether the guard would actually do anything — mode 'strict' or 'readonly', or offLimits configured even in mode 'off'. */
|
|
72
|
+
get active() {
|
|
73
|
+
return this._guard.config.mode !== "off" || this._guard.config.offLimits.length > 0;
|
|
74
|
+
}
|
|
75
|
+
get hasLiveContext() {
|
|
76
|
+
return !!this._activeBrowserContext;
|
|
77
|
+
}
|
|
78
|
+
/** The live BrowserContext, when there is one — teardown's own request context, and a hook for tests that drive the page directly. */
|
|
79
|
+
get browserContext() {
|
|
80
|
+
return this._activeBrowserContext;
|
|
81
|
+
}
|
|
82
|
+
/** Track a newly-created BrowserContext as "live" until it closes. Called by install(); also usable on its own. */
|
|
83
|
+
noteContext(browserContext) {
|
|
84
|
+
this._activeBrowserContext = browserContext;
|
|
85
|
+
browserContext.on("close", () => {
|
|
86
|
+
if (this._activeBrowserContext === browserContext)
|
|
87
|
+
this._activeBrowserContext = void 0;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Wire the guard into a live BrowserContext: route interception (classify
|
|
92
|
+
* + route-time create-body capture), the response listener (redirect
|
|
93
|
+
* detection + deferred create-body reads), and the WebSocket blocker.
|
|
94
|
+
* Always tracks the context (noteContext) even when the guard is inert —
|
|
95
|
+
* teardown/prepareExport still need to know whether a context is live.
|
|
96
|
+
*/
|
|
97
|
+
async install(browserContext) {
|
|
98
|
+
this.noteContext(browserContext);
|
|
99
|
+
if (!this.active)
|
|
100
|
+
return;
|
|
101
|
+
this._guard.flush();
|
|
102
|
+
browserContext.on("request", (req) => {
|
|
103
|
+
if (req.method().toUpperCase() === "POST" && this._guard.config.mode === "strict")
|
|
104
|
+
this._contextForRequest(req);
|
|
105
|
+
});
|
|
106
|
+
await browserContext.route("**", async (route) => {
|
|
107
|
+
const req = route.request();
|
|
108
|
+
const verdict = this._guard.classify(req.method(), req.url(), req.postData());
|
|
109
|
+
if (verdict === "ALLOW") {
|
|
110
|
+
if (this._guard.shouldCaptureCreateBody(req.method(), req.url())) {
|
|
111
|
+
const capture = this._captureCreateAtRoute(route);
|
|
112
|
+
this._pendingBodyReads.set(capture, req.url());
|
|
113
|
+
capture.finally(() => this._pendingBodyReads.delete(capture));
|
|
114
|
+
return capture;
|
|
115
|
+
}
|
|
116
|
+
return route.fallback();
|
|
117
|
+
}
|
|
118
|
+
const rule = (0, import_dataSafetyGuard.blockReason)(verdict, this._guard.config.mode);
|
|
119
|
+
this._guard.recordBlock({ url: req.url(), method: req.method(), verdict, rule });
|
|
120
|
+
traceDebug(`data safety blocked ${req.method()} ${req.url()} (${verdict})`);
|
|
121
|
+
return route.abort("blockedbyclient");
|
|
122
|
+
});
|
|
123
|
+
browserContext.on("response", (response) => {
|
|
124
|
+
const req = response.request();
|
|
125
|
+
const status = response.status();
|
|
126
|
+
if (req.method().toUpperCase() !== "POST")
|
|
127
|
+
this._guard.noteEntityResponse(req.method(), req.url(), status);
|
|
128
|
+
if (status >= 300 && status < 400) {
|
|
129
|
+
this._guard.noteRedirect(req.method(), req.url(), status, response.headers()["location"] ?? null);
|
|
130
|
+
if (req.method().toUpperCase() === "POST" && this._guard.config.mode === "strict" && !this._routeCapturedRequests.has(req))
|
|
131
|
+
this._guard.recordResponse(req.method(), req.url(), status, response.headers()["location"] ?? null, null, response.headers()["content-type"] ?? null, this._contextForRequest(req), originalRequestHeaders(req));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (req.method().toUpperCase() !== "POST" || this._guard.config.mode !== "strict")
|
|
135
|
+
return;
|
|
136
|
+
if (this._routeCapturedRequests.has(req))
|
|
137
|
+
return;
|
|
138
|
+
const context = this._contextForRequest(req);
|
|
139
|
+
const requestHeaders = originalRequestHeaders(req);
|
|
140
|
+
const bodyRead = response.text().then((body) => {
|
|
141
|
+
this._guard.recordResponse(req.method(), req.url(), status, response.headers()["location"] ?? null, body, response.headers()["content-type"] ?? null, context, requestHeaders);
|
|
142
|
+
}).catch(() => {
|
|
143
|
+
this._guard.recordResponse(req.method(), req.url(), status, response.headers()["location"] ?? null, null, response.headers()["content-type"] ?? null, context, requestHeaders);
|
|
144
|
+
});
|
|
145
|
+
this._pendingBodyReads.set(bodyRead, req.url());
|
|
146
|
+
bodyRead.finally(() => this._pendingBodyReads.delete(bodyRead));
|
|
147
|
+
});
|
|
148
|
+
const mode = this._guard.config.mode;
|
|
149
|
+
if (mode !== "off") {
|
|
150
|
+
await browserContext.routeWebSocket("**", (ws) => {
|
|
151
|
+
this._guard.recordBlock({ url: ws.url(), method: "WEBSOCKET", verdict: "BLOCK_FOREIGN_MUTATION", rule: `websocket blocked (mode=${mode})` });
|
|
152
|
+
traceDebug(`data safety blocked websocket ${ws.url()}`);
|
|
153
|
+
void ws.close({ code: 1008, reason: `blocked by data safety (mode=${mode})` });
|
|
154
|
+
});
|
|
155
|
+
} else {
|
|
156
|
+
const offLimits = (0, import_dataSafetyGuard.compileOffLimits)(this._guard.config.offLimits);
|
|
157
|
+
await browserContext.routeWebSocket((url) => offLimits(url.toString()), (ws) => {
|
|
158
|
+
this._guard.recordBlock({ url: ws.url(), method: "WEBSOCKET", verdict: "BLOCK_DENYLISTED", rule: (0, import_dataSafetyGuard.blockReason)("BLOCK_DENYLISTED", mode) });
|
|
159
|
+
traceDebug(`data safety blocked off-limits websocket ${ws.url()}`);
|
|
160
|
+
void ws.close({ code: 1008, reason: "blocked by data safety (off-limits)" });
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The create-request context for `req`, fixed on first sight. A redirect
|
|
166
|
+
* hop is a distinct Request object in Playwright, but it is the SAME
|
|
167
|
+
* create from the page's point of view — so a hop inherits the original
|
|
168
|
+
* request's whole context (action provenance AND resourceType) rather
|
|
169
|
+
* than re-snapshotting either.
|
|
170
|
+
*/
|
|
171
|
+
_contextForRequest(req) {
|
|
172
|
+
let context = this._contextByRequest.get(req);
|
|
173
|
+
if (!context) {
|
|
174
|
+
const redirectedFrom = req.redirectedFrom();
|
|
175
|
+
context = redirectedFrom ? this._contextForRequest(redirectedFrom) : { ...this._requestProvenance(), resourceType: req.resourceType() };
|
|
176
|
+
this._contextByRequest.set(req, context);
|
|
177
|
+
}
|
|
178
|
+
return context;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Fetch a strict-mode ledgerable create ourselves, record its body, then
|
|
182
|
+
* hand the buffered response to the page (route.fetch → fulfill). Never
|
|
183
|
+
* rejects — the route always gets an answer.
|
|
184
|
+
*
|
|
185
|
+
* Failure handling is asymmetric on purpose: once route.fetch has been
|
|
186
|
+
* issued, the server may have processed the create even when the fetch
|
|
187
|
+
* errors (e.g. a timeout mid-response), so falling back to
|
|
188
|
+
* route.continue() could SEND THE CREATE TWICE. Abort instead (the app
|
|
189
|
+
* sees an ordinary network failure) and record the attempt in the
|
|
190
|
+
* unparsed-create audit (status 0 = never completed) so the export still
|
|
191
|
+
* surfaces it.
|
|
192
|
+
*/
|
|
193
|
+
async _captureCreateAtRoute(route) {
|
|
194
|
+
const req = route.request();
|
|
195
|
+
const context = this._contextForRequest(req);
|
|
196
|
+
let fetched;
|
|
197
|
+
const TIMED_OUT = Symbol("data-safety-route-fetch-timeout");
|
|
198
|
+
let timer;
|
|
199
|
+
try {
|
|
200
|
+
const timeout = new Promise((resolve) => {
|
|
201
|
+
timer = setTimeout(() => resolve(TIMED_OUT), DataSafetySession.routeFetchTimeoutMs);
|
|
202
|
+
timer.unref?.();
|
|
203
|
+
});
|
|
204
|
+
const result = await Promise.race([route.fetch({ maxRedirects: 0 }), timeout]);
|
|
205
|
+
if (result === TIMED_OUT) {
|
|
206
|
+
this._guard.recordUnparsedCreate(req.url(), 0, void 0, "capture-timeout");
|
|
207
|
+
traceDebug(`data safety: route.fetch timed out for ${req.url()} after ${DataSafetySession.routeFetchTimeoutMs}ms \u2014 aborting (strict mode cannot record SSE/long-poll-over-POST endpoints)`);
|
|
208
|
+
await route.abort("timedout").catch((e) => traceDebug(`data safety: route.abort failed for ${req.url()}: ${e}`));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
fetched = result;
|
|
212
|
+
} catch (e) {
|
|
213
|
+
this._guard.recordUnparsedCreate(req.url(), 0, void 0, "capture-failed");
|
|
214
|
+
traceDebug(`data safety: route.fetch failed for ${req.url()} \u2014 aborting to avoid a double send: ${e}`);
|
|
215
|
+
await route.abort("failed").catch((abortError) => traceDebug(`data safety: route.abort failed for ${req.url()}: ${abortError}`));
|
|
216
|
+
return;
|
|
217
|
+
} finally {
|
|
218
|
+
if (timer)
|
|
219
|
+
clearTimeout(timer);
|
|
220
|
+
}
|
|
221
|
+
let body = null;
|
|
222
|
+
try {
|
|
223
|
+
body = await fetched.text();
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
this._guard.recordResponse("POST", req.url(), fetched.status(), fetched.headers()["location"] ?? null, body, fetched.headers()["content-type"] ?? null, context, originalRequestHeaders(req));
|
|
227
|
+
this._routeCapturedRequests.add(req);
|
|
228
|
+
await route.fulfill({ response: fetched }).catch((e) => traceDebug(`data safety: route.fulfill failed for ${req.url()}: ${e}`));
|
|
229
|
+
}
|
|
230
|
+
static {
|
|
231
|
+
/**
|
|
232
|
+
* Overridable in tests (module/class-level, not a constructor param —
|
|
233
|
+
* every call site shares one session-lifetime timeout) so a synthetic
|
|
234
|
+
* "never responds" capture doesn't have to eat the full production
|
|
235
|
+
* timeout in every test that needs to exercise the timeout path.
|
|
236
|
+
*/
|
|
237
|
+
this.settleTimeoutMs = 5e3;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Wait out every in-flight response.text() read the 'response' listener
|
|
241
|
+
* (and route-time capture) kicked off, so a POST create that's still
|
|
242
|
+
* arriving isn't dropped from the ledger by a teardown/export that started
|
|
243
|
+
* a beat too soon. Bounded (unref'd, so it can never hold the process
|
|
244
|
+
* open) — a read that never settles must not hang export forever.
|
|
245
|
+
*
|
|
246
|
+
* Reports whether it gave up on a timeout rather than everything actually
|
|
247
|
+
* settling, and how many captures were still pending at that point — a
|
|
248
|
+
* caller that silently treated a timeout the same as "all settled" could
|
|
249
|
+
* report a clean export while a create was still in flight (may not even
|
|
250
|
+
* have reached the server yet, or just landed with nothing recording it
|
|
251
|
+
* anywhere). Each still-pending URL is also recorded into the guard's own
|
|
252
|
+
* unparsed-create audit (rule 'capture-pending') so the record exists even
|
|
253
|
+
* if this call's caller does nothing further with the return value.
|
|
254
|
+
*/
|
|
255
|
+
async settleBodyReads() {
|
|
256
|
+
if (!this._pendingBodyReads.size)
|
|
257
|
+
return { timedOut: false, unsettled: 0 };
|
|
258
|
+
let timer;
|
|
259
|
+
let timedOut = false;
|
|
260
|
+
const timeout = new Promise((resolve) => {
|
|
261
|
+
timer = setTimeout(() => {
|
|
262
|
+
timedOut = true;
|
|
263
|
+
resolve();
|
|
264
|
+
}, DataSafetySession.settleTimeoutMs);
|
|
265
|
+
timer.unref?.();
|
|
266
|
+
});
|
|
267
|
+
await Promise.race([Promise.allSettled([...this._pendingBodyReads.keys()]).then(() => {
|
|
268
|
+
}), timeout]);
|
|
269
|
+
if (timer)
|
|
270
|
+
clearTimeout(timer);
|
|
271
|
+
if (!timedOut)
|
|
272
|
+
return { timedOut: false, unsettled: 0 };
|
|
273
|
+
const stillPending = [...this._pendingBodyReads.values()];
|
|
274
|
+
for (const url of stillPending)
|
|
275
|
+
this._guard.recordUnparsedCreate(url, 0, void 0, "capture-pending");
|
|
276
|
+
return { timedOut: true, unsettled: stillPending.length };
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Settle in-flight creates, tear down what is pending, and repeat until
|
|
280
|
+
* both the ledger and the in-flight reads are quiet. A single teardown()
|
|
281
|
+
* call snapshots the entries before its first await, so a create that
|
|
282
|
+
* starts during a DELETE (a page action, an autosave, or the delete
|
|
283
|
+
* handler itself) is only visible to a later pass, and only after its
|
|
284
|
+
* response is settled. Bounded so a target that creates a new entity on
|
|
285
|
+
* every DELETE cannot hang export: whatever is still pending after the
|
|
286
|
+
* last pass is reported as a leftover by the caller. The context is read
|
|
287
|
+
* again each pass because it can close during a DELETE.
|
|
288
|
+
*
|
|
289
|
+
* A pass in which every DELETE timed out is not retried: against a hung
|
|
290
|
+
* target a retry only spends the caller's time bound on the same entries.
|
|
291
|
+
*
|
|
292
|
+
* stopOnTimeout: export stops at a settle timeout (the context stays
|
|
293
|
+
* alive, so a retry can still tear down); the abnormal-end path keeps
|
|
294
|
+
* tearing down what is ledgered, because nothing runs after it.
|
|
295
|
+
*/
|
|
296
|
+
async _settleAndTeardown(stopOnTimeout) {
|
|
297
|
+
const MAX_TEARDOWN_PASSES = 3;
|
|
298
|
+
let result = { deleted: [], leftovers: [] };
|
|
299
|
+
let timedOut = false;
|
|
300
|
+
let onlyDeleteTimeouts = false;
|
|
301
|
+
for (let pass = 0; ; pass++) {
|
|
302
|
+
if (!timedOut)
|
|
303
|
+
timedOut = (await this.settleBodyReads()).timedOut;
|
|
304
|
+
if (timedOut && stopOnTimeout || !this._guard.pendingTeardownCount)
|
|
305
|
+
return { timedOut, contextClosed: false, result };
|
|
306
|
+
const context = this._activeBrowserContext;
|
|
307
|
+
if (!context)
|
|
308
|
+
return { timedOut, contextClosed: true, result };
|
|
309
|
+
if (pass === MAX_TEARDOWN_PASSES || onlyDeleteTimeouts)
|
|
310
|
+
return { timedOut, contextClosed: false, result };
|
|
311
|
+
result = await this._guard.teardown(context.request);
|
|
312
|
+
onlyDeleteTimeouts = !result.deleted.length && result.leftovers.length > 0 && result.leftovers.every((l) => l.timedOut);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* The skyramp_export_zip gate: settle in-flight reads and run strict
|
|
317
|
+
* teardown while the browser context (and its session auth) is still
|
|
318
|
+
* alive, then decide whether export can proceed. The ledger is only ever
|
|
319
|
+
* non-empty in strict mode (recordResponse gates on it), so the pending
|
|
320
|
+
* checks below already imply strict.
|
|
321
|
+
*/
|
|
322
|
+
async prepareExport() {
|
|
323
|
+
const { timedOut, contextClosed, result } = await this._settleAndTeardown(true);
|
|
324
|
+
if (timedOut) {
|
|
325
|
+
const urls = [...this._pendingBodyReads.values()];
|
|
326
|
+
const manifest2 = (0, import_dataSafetyGuard.renderCappedList)(
|
|
327
|
+
urls.map((u) => `- ${u}`),
|
|
328
|
+
"in-flight create(s) still being captured when the settle timeout elapsed \u2014 see dataSafetyLedger.json."
|
|
329
|
+
);
|
|
330
|
+
return { kind: "capture-pending", urls, manifest: manifest2 };
|
|
331
|
+
}
|
|
332
|
+
const pending = this._guard.ledger.filter((e) => !e.tornDown);
|
|
333
|
+
if (!pending.length)
|
|
334
|
+
return { kind: "clean" };
|
|
335
|
+
if (contextClosed) {
|
|
336
|
+
const manifest2 = (0, import_dataSafetyGuard.renderCappedList)(
|
|
337
|
+
pending.map((e) => `- ${e.entityUrl} (id ${e.id})`),
|
|
338
|
+
"unverified entities \u2014 see dataSafetyLedger.json."
|
|
339
|
+
);
|
|
340
|
+
return { kind: "context-closed", pending, manifest: manifest2 };
|
|
341
|
+
}
|
|
342
|
+
const attempted = new Map(result.leftovers.map((l) => [l.entry, l]));
|
|
343
|
+
const leftovers = pending.map((entry) => attempted.get(entry) ?? { entry, status: "error", error: "teardown pass limit reached \u2014 entity ledgered too late to be attempted" });
|
|
344
|
+
const manifest = (0, import_dataSafetyGuard.renderCappedList)(
|
|
345
|
+
leftovers.map((l) => `- ${l.entry.entityUrl} (id ${l.entry.id}) \u2014 DELETE ${l.entry.deleteEndpoint} \u2192 ${l.status}${l.error ? ` (${l.error})` : ""}`),
|
|
346
|
+
"left-behind entities \u2014 see dataSafetyLedger.json."
|
|
347
|
+
);
|
|
348
|
+
return { kind: "leftovers", leftovers, manifest };
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* The abnormal-end (client disconnected without calling skyramp_export_zip)
|
|
352
|
+
* best-effort variant: settle reads, then attempt teardown if there's
|
|
353
|
+
* anything to tear down, but never throw and never block on a context
|
|
354
|
+
* that's already gone (best-effort — the caller preserves the ledger
|
|
355
|
+
* regardless of the outcome here).
|
|
356
|
+
*/
|
|
357
|
+
async settleAndTeardownBestEffort() {
|
|
358
|
+
try {
|
|
359
|
+
const { timedOut } = await this._settleAndTeardown(false);
|
|
360
|
+
return { teardownClean: !timedOut && this._guard.pendingTeardownCount === 0 };
|
|
361
|
+
} catch (e) {
|
|
362
|
+
process.stderr.write(`[skyramp] Data safety: session-end teardown failed: ${String(e)}
|
|
363
|
+
`);
|
|
364
|
+
return { teardownClean: false };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
369
|
+
0 && (module.exports = {
|
|
370
|
+
DataSafetySession,
|
|
371
|
+
assertNoNetworkPolicyCollision
|
|
372
|
+
});
|
|
@@ -30,7 +30,9 @@ var exportTool_exports = {};
|
|
|
30
30
|
__export(exportTool_exports, {
|
|
31
31
|
createExportZipHandler: () => createExportZipHandler,
|
|
32
32
|
exportZipMcpTool: () => exportZipMcpTool,
|
|
33
|
-
exportZipSchema: () => exportZipSchema
|
|
33
|
+
exportZipSchema: () => exportZipSchema,
|
|
34
|
+
resolveSourceActions: () => resolveSourceActions,
|
|
35
|
+
validateResourceProvenance: () => validateResourceProvenance
|
|
34
36
|
});
|
|
35
37
|
module.exports = __toCommonJS(exportTool_exports);
|
|
36
38
|
var import_path = __toESM(require("path"));
|
|
@@ -38,6 +40,7 @@ var import_fs = __toESM(require("fs"));
|
|
|
38
40
|
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
39
41
|
var import_skyRampExport = require("../test/skyRampExport");
|
|
40
42
|
var import_tool = require("../sdk/tool");
|
|
43
|
+
var import_dataSafetyGuard = require("./dataSafetyGuard");
|
|
41
44
|
const exportZipSchema = {
|
|
42
45
|
name: "skyramp_export_zip",
|
|
43
46
|
title: "Export Skyramp zip",
|
|
@@ -47,18 +50,91 @@ const exportZipSchema = {
|
|
|
47
50
|
"BEFORE calling this tool, output a <thinking> block that: (1) lists every user-requested step and confirms it was completed, (2) confirms no step was skipped or hallucinated.",
|
|
48
51
|
"Only call this tool when all interactions are fully complete. Do NOT ask the user for confirmation \u2014 call it automatically.",
|
|
49
52
|
"Only the last complete attempt is exported \u2014 retries from the start URL are deduplicated automatically.",
|
|
50
|
-
"Do NOT reuse zip files from previous sessions \u2014 always record fresh."
|
|
53
|
+
"Do NOT reuse zip files from previous sessions \u2014 always record fresh.",
|
|
54
|
+
"If the recording created records, call skyramp_finish_test_setup between the setup steps and the scenario steps; export refuses without it.",
|
|
55
|
+
"If export refuses because a create request may have reached the server but could not be recorded, tell the user which requests and that they may have left records behind; only then retry with acknowledgeUnverifiedCreates: true."
|
|
51
56
|
].join(" "),
|
|
52
57
|
inputSchema: import_mcpBundle.z.object({
|
|
53
|
-
outputPath: import_mcpBundle.z.string().describe("Absolute path where the zip should be written, e.g. /path/to/box_notes.zip. Use the same directory and base name as the test file, replacing .spec.ts with .zip.")
|
|
58
|
+
outputPath: import_mcpBundle.z.string().describe("Absolute path where the zip should be written, e.g. /path/to/box_notes.zip. Use the same directory and base name as the test file, replacing .spec.ts with .zip."),
|
|
59
|
+
acknowledgeUnverifiedCreates: import_mcpBundle.z.boolean().optional().describe("Set to true only after a refused export named create requests that could not be recorded, and you told the user they may have left records behind.")
|
|
54
60
|
}),
|
|
55
61
|
type: "readOnly"
|
|
56
62
|
};
|
|
63
|
+
function serializedActions(actions, entries, jsonl) {
|
|
64
|
+
return (jsonl ?? (0, import_skyRampExport.buildJsonlContent)([...actions], "chromium", "", void 0, void 0, entries).jsonl).trim().split("\n").slice(1).map((line) => JSON.parse(line));
|
|
65
|
+
}
|
|
66
|
+
function actionNumber(actionId) {
|
|
67
|
+
const match = /^action-(\d+)$/.exec(actionId);
|
|
68
|
+
return match ? Number(match[1]) : NaN;
|
|
69
|
+
}
|
|
70
|
+
function resolveSourceActions(actions, entries, passiveActionIds, jsonl) {
|
|
71
|
+
const changes = /* @__PURE__ */ new Map();
|
|
72
|
+
if (!entries.some((entry) => entry.sourceActionId))
|
|
73
|
+
return changes;
|
|
74
|
+
const serialized = serializedActions(actions, entries, jsonl);
|
|
75
|
+
const phases = new Map(serialized.map((action) => [action.actionId, action.phase]));
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const source = entry.sourceActionId;
|
|
78
|
+
if (!source || phases.has(source) && (entry.phase !== "scenario" || phases.get(source) === "scenario"))
|
|
79
|
+
continue;
|
|
80
|
+
if (entry.phase === "scenario" && !passiveActionIds.has(source))
|
|
81
|
+
continue;
|
|
82
|
+
const before = actionNumber(source);
|
|
83
|
+
let nearest;
|
|
84
|
+
for (const action of serialized) {
|
|
85
|
+
if (action.actionId && action.phase === entry.phase && actionNumber(action.actionId) < before && (!nearest || actionNumber(action.actionId) > actionNumber(nearest)))
|
|
86
|
+
nearest = action.actionId;
|
|
87
|
+
}
|
|
88
|
+
if (entry.phase === "scenario" && !nearest)
|
|
89
|
+
continue;
|
|
90
|
+
changes.set(entry, nearest);
|
|
91
|
+
}
|
|
92
|
+
return changes;
|
|
93
|
+
}
|
|
94
|
+
function validateResourceProvenance(actions, entries, setupCompleted, jsonl) {
|
|
95
|
+
if (!entries.length)
|
|
96
|
+
return { ok: true };
|
|
97
|
+
if (!setupCompleted)
|
|
98
|
+
return { ok: false, message: "Created resources were recorded before skyramp_finish_test_setup marked the scenario boundary." };
|
|
99
|
+
const invalidEntry = entries.find((entry) => entry.phase !== "setup" && entry.phase !== "scenario");
|
|
100
|
+
if (invalidEntry)
|
|
101
|
+
return { ok: false, message: `Created resource ${invalidEntry.entityUrl} has no valid recording phase.` };
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
const missing = [
|
|
104
|
+
entry.idSource !== "responseBody" && entry.idSource !== "locationHeader" ? "idSource" : null,
|
|
105
|
+
typeof entry.resourceType !== "string" || !entry.resourceType ? "resourceType" : null,
|
|
106
|
+
typeof entry.responseStatus !== "number" ? "responseStatus" : null
|
|
107
|
+
].filter((field) => field !== null);
|
|
108
|
+
if (missing.length)
|
|
109
|
+
return { ok: false, message: `Created resource ${entry.entityUrl} has no valid id-source record (${missing.join(", ")}). Re-record with the current @skyramp/playwright recorder \u2014 test generation needs these fields to recover the id at runtime.` };
|
|
110
|
+
}
|
|
111
|
+
const invalidAction = actions.find((action) => !action.actionId || action.phase !== "setup" && action.phase !== "scenario");
|
|
112
|
+
if (invalidAction)
|
|
113
|
+
return { ok: false, message: `Recorded action ${invalidAction.toolName} has no valid provenance.` };
|
|
114
|
+
const serialized = serializedActions(actions, entries, jsonl);
|
|
115
|
+
const phases = new Map(serialized.map((action) => [action.actionId, action.phase]));
|
|
116
|
+
const unresolved = entries.find((entry) => entry.phase === "scenario" ? !entry.sourceActionId || phases.get(entry.sourceActionId) !== "scenario" : entry.sourceActionId && !phases.has(entry.sourceActionId));
|
|
117
|
+
if (unresolved)
|
|
118
|
+
return { ok: false, message: `Created resource ${unresolved.entityUrl} has no valid source action in serialized JSONL: ${unresolved.sourceActionId ?? "(none)"}. The record was created by a step the recording cannot replay; re-record the scenario so the record is created by a recorded action.` };
|
|
119
|
+
return { ok: true };
|
|
120
|
+
}
|
|
57
121
|
function summarizeDropped(dropped) {
|
|
58
122
|
const counts = /* @__PURE__ */ new Map();
|
|
59
123
|
for (const d of dropped)
|
|
60
124
|
counts.set(d.toolName, (counts.get(d.toolName) ?? 0) + 1);
|
|
61
|
-
|
|
125
|
+
const lines = [...counts.entries()].map(([tool, n]) => n > 1 ? `${tool} \xD7${n}` : tool);
|
|
126
|
+
return (0, import_dataSafetyGuard.renderCappedList)(lines, "dropped action type(s)");
|
|
127
|
+
}
|
|
128
|
+
function summarizeUnparsedCreates(records) {
|
|
129
|
+
const lines = records.map((r) => {
|
|
130
|
+
const countSuffix = r.count && r.count > 1 ? ` (\xD7${r.count})` : "";
|
|
131
|
+
const ruleSuffix = r.rule ? ` [${r.rule}]` : "";
|
|
132
|
+
return `- POST ${r.url} \u2192 ${r.status}${countSuffix}${ruleSuffix}`;
|
|
133
|
+
});
|
|
134
|
+
return (0, import_dataSafetyGuard.renderCappedList)(lines, "unparsed create(s)");
|
|
135
|
+
}
|
|
136
|
+
function totalUnparsedCount(records) {
|
|
137
|
+
return records.reduce((sum, r) => sum + (r.count ?? 1), 0);
|
|
62
138
|
}
|
|
63
139
|
function createExportZipHandler(ctx) {
|
|
64
140
|
return async (params) => {
|
|
@@ -88,8 +164,19 @@ Requested path: ${outputZip}` }],
|
|
|
88
164
|
counter++;
|
|
89
165
|
outputZip = `${base}_${counter}${ext}`;
|
|
90
166
|
}
|
|
91
|
-
const
|
|
92
|
-
|
|
167
|
+
const ledger = ctx.dataSafety ? JSON.parse(ctx.dataSafety.ledgerJson) : void 0;
|
|
168
|
+
const { jsonl: jsonlContent, actionCount, skipped, simplified } = (0, import_skyRampExport.buildJsonlContent)(ctx.trackedActions, "chromium", ctx.harPath, void 0, void 0, ledger?.entries);
|
|
169
|
+
const provenance = validateResourceProvenance(ctx.trackedActions, ledger?.entries ?? [], ledger?.setupCompleted === true, jsonlContent);
|
|
170
|
+
if (!provenance.ok)
|
|
171
|
+
return { content: [{ type: "text", text: `### Error
|
|
172
|
+
${provenance.message}
|
|
173
|
+
The recording was NOT exported.` }], isError: true };
|
|
174
|
+
await (0, import_skyRampExport.writeSkyrampZip)(
|
|
175
|
+
outputZip,
|
|
176
|
+
jsonlContent,
|
|
177
|
+
ctx.harPath,
|
|
178
|
+
ctx.dataSafety ? [{ name: "dataSafetyLedger.json", content: ctx.dataSafety.ledgerJson }] : void 0
|
|
179
|
+
);
|
|
93
180
|
const skippedNote = skipped.length ? `
|
|
94
181
|
Skipped (not exportable): ${[...new Set(skipped)].join(", ")}` : "";
|
|
95
182
|
const simplifiedNote = simplified.length ? `
|
|
@@ -97,6 +184,10 @@ Exported with a simplified locator (a part the trace format cannot carry was dro
|
|
|
97
184
|
const dropped = ctx.droppedActions ?? [];
|
|
98
185
|
const droppedNote = dropped.length ? `
|
|
99
186
|
Failed & dropped (not recorded): ${summarizeDropped(dropped)}` : "";
|
|
187
|
+
const unparsed = ctx.dataSafety?.unparsedCreates ?? [];
|
|
188
|
+
const unparsedNote = unparsed.length ? `
|
|
189
|
+
Data safety: ${totalUnparsedCount(unparsed)} response(s) returned 2xx/3xx to a POST but no entity id could be identified \u2014 entities may have been created that teardown could not delete. See dataSafetyLedger.json (unparsedCreates).
|
|
190
|
+
${summarizeUnparsedCreates(unparsed)}` : "";
|
|
100
191
|
return {
|
|
101
192
|
content: [{
|
|
102
193
|
type: "text",
|
|
@@ -104,7 +195,7 @@ Failed & dropped (not recorded): ${summarizeDropped(dropped)}` : "";
|
|
|
104
195
|
Skyramp zip written to ${outputZip}
|
|
105
196
|
|
|
106
197
|
Contains:
|
|
107
|
-
- skyramp_playwright.txt (${actionCount} actions)${skippedNote}${simplifiedNote}${droppedNote}`
|
|
198
|
+
- skyramp_playwright.txt (${actionCount} actions)${skippedNote}${simplifiedNote}${droppedNote}${unparsedNote}`
|
|
108
199
|
}]
|
|
109
200
|
};
|
|
110
201
|
};
|
|
@@ -116,5 +207,7 @@ function exportZipMcpTool() {
|
|
|
116
207
|
0 && (module.exports = {
|
|
117
208
|
createExportZipHandler,
|
|
118
209
|
exportZipMcpTool,
|
|
119
|
-
exportZipSchema
|
|
210
|
+
exportZipSchema,
|
|
211
|
+
resolveSourceActions,
|
|
212
|
+
validateResourceProvenance
|
|
120
213
|
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var finishTestSetupTool_exports = {};
|
|
20
|
+
__export(finishTestSetupTool_exports, {
|
|
21
|
+
finishTestSetupMcpTool: () => finishTestSetupMcpTool,
|
|
22
|
+
finishTestSetupSchema: () => finishTestSetupSchema
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(finishTestSetupTool_exports);
|
|
25
|
+
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
26
|
+
var import_tool = require("../sdk/tool");
|
|
27
|
+
const finishTestSetupSchema = {
|
|
28
|
+
name: "skyramp_finish_test_setup",
|
|
29
|
+
title: "Finish test setup",
|
|
30
|
+
description: [
|
|
31
|
+
"Marks all subsequent browser actions and created resources as part of the test scenario.",
|
|
32
|
+
"Call it once, after the setup steps (sign-in, creating the records the test needs) and before the first scenario step.",
|
|
33
|
+
"When data safety is strict and the recording created any record, skyramp_export_zip refuses to export until this tool was called."
|
|
34
|
+
].join(" "),
|
|
35
|
+
inputSchema: import_mcpBundle.z.object({}),
|
|
36
|
+
type: "readOnly"
|
|
37
|
+
};
|
|
38
|
+
function finishTestSetupMcpTool() {
|
|
39
|
+
return (0, import_tool.toMcpTool)(finishTestSetupSchema);
|
|
40
|
+
}
|
|
41
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
42
|
+
0 && (module.exports = {
|
|
43
|
+
finishTestSetupMcpTool,
|
|
44
|
+
finishTestSetupSchema
|
|
45
|
+
});
|
|
@@ -19,28 +19,44 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var skyramp_exports = {};
|
|
20
20
|
__export(skyramp_exports, {
|
|
21
21
|
ARGS_ONLY_TOOLS: () => import_types.ARGS_ONLY_TOOLS,
|
|
22
|
+
DataSafetyGuard: () => import_dataSafetyGuard.DataSafetyGuard,
|
|
22
23
|
TraceRecordingBackend: () => import_traceRecordingBackend.TraceRecordingBackend,
|
|
23
24
|
actionsFromPath: () => import_skyRampImport.actionsFromPath,
|
|
24
25
|
buildJsonlContent: () => import_skyRampExport.buildJsonlContent,
|
|
26
|
+
compileOffLimits: () => import_dataSafetyGuard.compileOffLimits,
|
|
27
|
+
finishTestSetupMcpTool: () => import_finishTestSetupTool.finishTestSetupMcpTool,
|
|
28
|
+
finishTestSetupSchema: () => import_finishTestSetupTool.finishTestSetupSchema,
|
|
25
29
|
loadTraceMcpTool: () => import_loadTraceTool.loadTraceMcpTool,
|
|
26
30
|
loadTraceSchema: () => import_loadTraceTool.loadTraceSchema,
|
|
31
|
+
matchesOffLimits: () => import_dataSafetyGuard.matchesOffLimits,
|
|
32
|
+
parseDataSafetyConfig: () => import_dataSafetyGuard.parseDataSafetyConfig,
|
|
27
33
|
parseJsonl: () => import_skyRampImport.parseJsonl,
|
|
34
|
+
renderCappedList: () => import_dataSafetyGuard.renderCappedList,
|
|
28
35
|
writeSkyrampZip: () => import_skyRampExport.writeSkyrampZip
|
|
29
36
|
});
|
|
30
37
|
module.exports = __toCommonJS(skyramp_exports);
|
|
31
38
|
var import_traceRecordingBackend = require("./traceRecordingBackend");
|
|
39
|
+
var import_dataSafetyGuard = require("./dataSafetyGuard");
|
|
32
40
|
var import_types = require("./types");
|
|
33
41
|
var import_skyRampExport = require("../test/skyRampExport");
|
|
34
42
|
var import_loadTraceTool = require("./loadTraceTool");
|
|
35
43
|
var import_skyRampImport = require("./skyRampImport");
|
|
44
|
+
var import_finishTestSetupTool = require("./finishTestSetupTool");
|
|
36
45
|
// Annotate the CommonJS export names for ESM import in node:
|
|
37
46
|
0 && (module.exports = {
|
|
38
47
|
ARGS_ONLY_TOOLS,
|
|
48
|
+
DataSafetyGuard,
|
|
39
49
|
TraceRecordingBackend,
|
|
40
50
|
actionsFromPath,
|
|
41
51
|
buildJsonlContent,
|
|
52
|
+
compileOffLimits,
|
|
53
|
+
finishTestSetupMcpTool,
|
|
54
|
+
finishTestSetupSchema,
|
|
42
55
|
loadTraceMcpTool,
|
|
43
56
|
loadTraceSchema,
|
|
57
|
+
matchesOffLimits,
|
|
58
|
+
parseDataSafetyConfig,
|
|
44
59
|
parseJsonl,
|
|
60
|
+
renderCappedList,
|
|
45
61
|
writeSkyrampZip
|
|
46
62
|
});
|