@cotestdev/mcp_playwright 0.0.34 → 0.0.36
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/lib/common/testType.js +1 -1
- package/lib/mcp/browser/browserContextFactory.js +12 -12
- package/lib/mcp/browser/browserServerBackend.js +24 -12
- package/lib/mcp/browser/config.js +37 -7
- package/lib/mcp/browser/context.js +17 -63
- package/lib/mcp/browser/response.js +183 -251
- package/lib/mcp/browser/sessionLog.js +19 -104
- package/lib/mcp/browser/tab.js +49 -28
- package/lib/mcp/browser/tools/common.js +4 -4
- package/lib/mcp/browser/tools/console.js +20 -3
- package/lib/mcp/browser/tools/dialogs.js +0 -1
- package/lib/mcp/browser/tools/evaluate.js +6 -4
- package/lib/mcp/browser/tools/install.js +4 -1
- package/lib/mcp/browser/tools/keyboard.js +75 -8
- package/lib/mcp/browser/tools/mouse.js +59 -7
- package/lib/mcp/browser/tools/navigate.js +48 -5
- package/lib/mcp/browser/tools/network.js +21 -3
- package/lib/mcp/browser/tools/pdf.js +4 -3
- package/lib/mcp/browser/tools/runCode.js +6 -10
- package/lib/mcp/browser/tools/screenshot.js +8 -26
- package/lib/mcp/browser/tools/snapshot.js +38 -22
- package/lib/mcp/browser/tools/tabs.js +8 -8
- package/lib/mcp/browser/tools/tool.js +3 -6
- package/lib/mcp/browser/tools/tracing.js +3 -3
- package/lib/mcp/browser/tools/utils.js +2 -2
- package/lib/mcp/browser/tools/verify.js +4 -4
- package/lib/mcp/browser/tools/wait.js +1 -1
- package/lib/mcp/browser/tools.js +2 -2
- package/lib/mcp/extension/extensionContextFactory.js +2 -2
- package/lib/mcp/program.js +3 -2
- package/lib/mcp/terminal/cli.js +4 -216
- package/lib/mcp/terminal/command.js +56 -0
- package/lib/mcp/terminal/commands.js +528 -0
- package/lib/mcp/terminal/daemon.js +42 -25
- package/lib/mcp/terminal/helpGenerator.js +152 -0
- package/lib/mcp/terminal/program.js +434 -0
- package/lib/mcp/terminal/socketConnection.js +2 -4
- package/lib/mcpBundleImpl/index.js +44 -44
- package/lib/util.js +3 -6
- package/package.json +3 -2
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,290 +17,152 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
var response_exports = {};
|
|
20
30
|
__export(response_exports, {
|
|
21
|
-
RenderedResponse: () => RenderedResponse,
|
|
22
31
|
Response: () => Response,
|
|
23
32
|
parseResponse: () => parseResponse,
|
|
24
|
-
|
|
33
|
+
renderTabMarkdown: () => renderTabMarkdown,
|
|
34
|
+
renderTabsMarkdown: () => renderTabsMarkdown,
|
|
35
|
+
requestDebug: () => requestDebug,
|
|
36
|
+
serializeResponse: () => serializeResponse,
|
|
37
|
+
serializeStructuredResponse: () => serializeStructuredResponse
|
|
25
38
|
});
|
|
26
39
|
module.exports = __toCommonJS(response_exports);
|
|
40
|
+
var import_fs = __toESM(require("fs"));
|
|
41
|
+
var import_path = __toESM(require("path"));
|
|
27
42
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
28
43
|
var import_tab = require("./tab");
|
|
44
|
+
var import_utils = require("./tools/utils");
|
|
45
|
+
var import_screenshot = require("./tools/screenshot");
|
|
29
46
|
const requestDebug = (0, import_utilsBundle.debug)("pw:mcp:request");
|
|
30
47
|
class Response {
|
|
31
|
-
constructor(context, toolName, toolArgs) {
|
|
32
|
-
this.
|
|
48
|
+
constructor(ordinal, context, toolName, toolArgs) {
|
|
49
|
+
this._results = [];
|
|
50
|
+
this._errors = [];
|
|
33
51
|
this._code = [];
|
|
34
|
-
this._images = [];
|
|
35
|
-
this._files = [];
|
|
36
52
|
this._includeSnapshot = "none";
|
|
37
|
-
this._includeTabs = false;
|
|
38
|
-
this._includeMetaOnly = false;
|
|
39
53
|
this._context = context;
|
|
40
54
|
this.toolName = toolName;
|
|
41
55
|
this.toolArgs = toolArgs;
|
|
42
56
|
}
|
|
43
|
-
|
|
44
|
-
this.
|
|
57
|
+
static {
|
|
58
|
+
this._ordinal = 0;
|
|
45
59
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
static create(context, toolName, toolArgs) {
|
|
61
|
+
return new Response(++Response._ordinal, context, toolName, toolArgs);
|
|
62
|
+
}
|
|
63
|
+
addTextResult(text) {
|
|
64
|
+
this._results.push({ title: "", text });
|
|
49
65
|
}
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
async addResult(title, data, file) {
|
|
67
|
+
this._results.push({
|
|
68
|
+
text: typeof data === "string" ? data : void 0,
|
|
69
|
+
data: typeof data === "string" ? void 0 : data,
|
|
70
|
+
title,
|
|
71
|
+
file
|
|
72
|
+
});
|
|
52
73
|
}
|
|
53
|
-
|
|
54
|
-
|
|
74
|
+
addError(error) {
|
|
75
|
+
this._errors.push(error);
|
|
55
76
|
}
|
|
56
77
|
addCode(code) {
|
|
57
78
|
this._code.push(code);
|
|
58
79
|
}
|
|
59
|
-
code() {
|
|
60
|
-
return this._code.join("\n");
|
|
61
|
-
}
|
|
62
|
-
addImage(image) {
|
|
63
|
-
this._images.push(image);
|
|
64
|
-
}
|
|
65
|
-
images() {
|
|
66
|
-
return this._images;
|
|
67
|
-
}
|
|
68
|
-
async addFile(fileName, options) {
|
|
69
|
-
const resolvedFile = await this._context.outputFile(fileName, options);
|
|
70
|
-
this._files.push({ fileName: resolvedFile, title: options.reason });
|
|
71
|
-
return resolvedFile;
|
|
72
|
-
}
|
|
73
80
|
setIncludeSnapshot() {
|
|
74
81
|
this._includeSnapshot = this._context.config.snapshot.mode;
|
|
75
82
|
}
|
|
76
|
-
setIncludeFullSnapshot() {
|
|
83
|
+
setIncludeFullSnapshot(includeSnapshotFileName) {
|
|
77
84
|
this._includeSnapshot = "full";
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (this._includeSnapshot !== "none" && this._context.currentTab())
|
|
92
|
-
this._tabSnapshot = await this._context.currentTabOrDie().captureSnapshot();
|
|
93
|
-
for (const tab of this._context.tabs())
|
|
94
|
-
await tab.updateTitle();
|
|
95
|
-
}
|
|
96
|
-
tabSnapshot() {
|
|
97
|
-
return this._tabSnapshot;
|
|
98
|
-
}
|
|
99
|
-
logBegin() {
|
|
100
|
-
if (requestDebug.enabled)
|
|
101
|
-
requestDebug(this.toolName, this.toolArgs);
|
|
102
|
-
}
|
|
103
|
-
logEnd() {
|
|
104
|
-
if (requestDebug.enabled)
|
|
105
|
-
requestDebug(this.serialize());
|
|
106
|
-
}
|
|
107
|
-
render() {
|
|
108
|
-
const renderedResponse = new RenderedResponse();
|
|
109
|
-
if (this._result.length)
|
|
110
|
-
renderedResponse.results.push(...this._result);
|
|
111
|
-
if (this._code.length)
|
|
112
|
-
renderedResponse.code.push(...this._code);
|
|
113
|
-
if (this._includeSnapshot !== "none" || this._includeTabs) {
|
|
114
|
-
const tabsMarkdown = renderTabsMarkdown(this._context.tabs(), this._includeTabs);
|
|
115
|
-
if (tabsMarkdown.length)
|
|
116
|
-
renderedResponse.states.tabs = tabsMarkdown.join("\n");
|
|
85
|
+
this._includeSnapshotFileName = includeSnapshotFileName;
|
|
86
|
+
}
|
|
87
|
+
async build() {
|
|
88
|
+
const rootPath = this._context.firstRootPath();
|
|
89
|
+
const sections = [];
|
|
90
|
+
const addSection = (title) => {
|
|
91
|
+
const section = { title, content: [], isError: title === "Error" };
|
|
92
|
+
sections.push(section);
|
|
93
|
+
return section.content;
|
|
94
|
+
};
|
|
95
|
+
if (this._errors.length) {
|
|
96
|
+
const content = addSection("Error");
|
|
97
|
+
content.push({ text: this._errors.join("\n"), title: "error" });
|
|
117
98
|
}
|
|
118
|
-
if (this.
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
} else if (this._tabSnapshot) {
|
|
122
|
-
renderTabSnapshot(this._tabSnapshot, this._includeSnapshot, renderedResponse);
|
|
123
|
-
} else if (this._includeModalStates) {
|
|
124
|
-
const modalStatesMarkdown = (0, import_tab.renderModalStates)(this._includeModalStates);
|
|
125
|
-
renderedResponse.states.modal = modalStatesMarkdown.join("\n");
|
|
99
|
+
if (this._results.length) {
|
|
100
|
+
const content = addSection("Result");
|
|
101
|
+
content.push(...this._results);
|
|
126
102
|
}
|
|
127
|
-
if (this.
|
|
128
|
-
const
|
|
129
|
-
for (const
|
|
130
|
-
|
|
131
|
-
renderedResponse.updates.push({ category: "files", content: lines.join("\n") });
|
|
103
|
+
if (this._context.config.codegen !== "none" && this._code.length) {
|
|
104
|
+
const content = addSection("Ran Playwright code");
|
|
105
|
+
for (const code of this._code)
|
|
106
|
+
content.push({ text: code, title: "code" });
|
|
132
107
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const content = [
|
|
140
|
-
{
|
|
141
|
-
type: "text",
|
|
142
|
-
text: renderedResponse.asText(this._includeMetaOnly ? { categories: ["files"] } : void 0)
|
|
108
|
+
const tabSnapshot = this._context.currentTab() ? await this._context.currentTabOrDie().captureSnapshot() : void 0;
|
|
109
|
+
const tabHeaders = await Promise.all(this._context.tabs().map((tab) => tab.headerSnapshot()));
|
|
110
|
+
if (this._includeSnapshot !== "none" || tabHeaders.some((header) => header.changed)) {
|
|
111
|
+
if (tabHeaders.length !== 1) {
|
|
112
|
+
const content2 = addSection("Open tabs");
|
|
113
|
+
content2.push({ text: renderTabsMarkdown(tabHeaders).join("\n"), title: "Open tabs" });
|
|
143
114
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return { _meta, content, isError: this._isError };
|
|
147
|
-
if (this._context.config.imageResponses !== "omit") {
|
|
148
|
-
for (const image of this._images)
|
|
149
|
-
content.push({ type: "image", data: image.data.toString("base64"), mimeType: image.contentType });
|
|
115
|
+
const content = addSection("Page");
|
|
116
|
+
content.push({ text: renderTabMarkdown(tabHeaders[0]).join("\n"), title: "Page" });
|
|
150
117
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
content,
|
|
154
|
-
isError: this._isError
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
function renderTabSnapshot(tabSnapshot, includeSnapshot, response) {
|
|
159
|
-
if (tabSnapshot.consoleMessages.length) {
|
|
160
|
-
const lines2 = [];
|
|
161
|
-
for (const message of tabSnapshot.consoleMessages)
|
|
162
|
-
lines2.push(`- ${trim(message.toString(), 100)}`);
|
|
163
|
-
response.updates.push({ category: "console", content: lines2.join("\n") });
|
|
164
|
-
}
|
|
165
|
-
if (tabSnapshot.downloads.length) {
|
|
166
|
-
const lines2 = [];
|
|
167
|
-
for (const entry of tabSnapshot.downloads) {
|
|
168
|
-
if (entry.finished)
|
|
169
|
-
lines2.push(`- Downloaded file ${entry.download.suggestedFilename()} to ${entry.outputFile}`);
|
|
170
|
-
else
|
|
171
|
-
lines2.push(`- Downloading file ${entry.download.suggestedFilename()} ...`);
|
|
118
|
+
if (tabSnapshot?.modalStates.length) {
|
|
119
|
+
const content = addSection("Modal state");
|
|
120
|
+
content.push({ text: (0, import_tab.renderModalStates)(this._context.config, tabSnapshot.modalStates).join("\n"), title: "Modal state" });
|
|
172
121
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
122
|
+
if (tabSnapshot && this._includeSnapshot !== "none") {
|
|
123
|
+
const content = addSection("Snapshot");
|
|
124
|
+
const snapshot = this._includeSnapshot === "full" ? tabSnapshot.ariaSnapshot : tabSnapshot.ariaSnapshotDiff ?? tabSnapshot.ariaSnapshot;
|
|
125
|
+
content.push({ text: snapshot, title: "snapshot", file: { prefix: "page", ext: "yml", suggestedFilename: this._includeSnapshotFileName } });
|
|
126
|
+
}
|
|
127
|
+
if (tabSnapshot?.events.filter((event) => event.type !== "request").length) {
|
|
128
|
+
const content = addSection("Events");
|
|
129
|
+
const text = [];
|
|
130
|
+
for (const event of tabSnapshot.events) {
|
|
131
|
+
if (event.type === "console") {
|
|
132
|
+
if ((0, import_tab.shouldIncludeMessage)(this._context.config.console.level, event.message.type))
|
|
133
|
+
text.push(`- ${trimMiddle(event.message.toString(), 100)}`);
|
|
134
|
+
} else if (event.type === "download-start") {
|
|
135
|
+
text.push(`- Downloading file ${event.download.download.suggestedFilename()} ...`);
|
|
136
|
+
} else if (event.type === "download-finish") {
|
|
137
|
+
text.push(`- Downloaded file ${event.download.download.suggestedFilename()} to "${rootPath ? import_path.default.relative(rootPath, event.download.outputFile) : event.download.outputFile}"`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
content.push({ text: text.join("\n"), title: "events" });
|
|
141
|
+
}
|
|
142
|
+
return sections;
|
|
189
143
|
}
|
|
190
|
-
response.states.page = lines.join("\n");
|
|
191
144
|
}
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
145
|
+
function renderTabMarkdown(tab) {
|
|
146
|
+
const lines = [`- Page URL: ${tab.url}`];
|
|
147
|
+
if (tab.title)
|
|
148
|
+
lines.push(`- Page Title: ${tab.title}`);
|
|
149
|
+
return lines;
|
|
150
|
+
}
|
|
151
|
+
function renderTabsMarkdown(tabs) {
|
|
195
152
|
if (!tabs.length)
|
|
196
|
-
return [
|
|
153
|
+
return ["No open tabs. Navigate to a URL to create one."];
|
|
197
154
|
const lines = [];
|
|
198
155
|
for (let i = 0; i < tabs.length; i++) {
|
|
199
156
|
const tab = tabs[i];
|
|
200
|
-
const current = tab.
|
|
201
|
-
lines.push(`- ${i}:${current} [${tab.
|
|
157
|
+
const current = tab.current ? " (current)" : "";
|
|
158
|
+
lines.push(`- ${i}:${current} [${tab.title}](${tab.url})`);
|
|
202
159
|
}
|
|
203
160
|
return lines;
|
|
204
161
|
}
|
|
205
|
-
function
|
|
162
|
+
function trimMiddle(text, maxLength) {
|
|
206
163
|
if (text.length <= maxLength)
|
|
207
164
|
return text;
|
|
208
|
-
return text.slice(0, maxLength) + "...";
|
|
209
|
-
}
|
|
210
|
-
class RenderedResponse {
|
|
211
|
-
constructor(copy) {
|
|
212
|
-
this.states = {};
|
|
213
|
-
this.updates = [];
|
|
214
|
-
this.results = [];
|
|
215
|
-
this.code = [];
|
|
216
|
-
if (copy) {
|
|
217
|
-
this.states = copy.states;
|
|
218
|
-
this.updates = copy.updates;
|
|
219
|
-
this.results = copy.results;
|
|
220
|
-
this.code = copy.code;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
asText(filter) {
|
|
224
|
-
const text = [];
|
|
225
|
-
if (this.results.length)
|
|
226
|
-
text.push(`### Result
|
|
227
|
-
${this.results.join("\n")}
|
|
228
|
-
`);
|
|
229
|
-
if (this.code.length)
|
|
230
|
-
text.push(`### Ran Playwright code
|
|
231
|
-
${this.code.join("\n")}
|
|
232
|
-
`);
|
|
233
|
-
for (const { category, content } of this.updates) {
|
|
234
|
-
if (filter && !filter.categories.includes(category))
|
|
235
|
-
continue;
|
|
236
|
-
if (!content.trim())
|
|
237
|
-
continue;
|
|
238
|
-
switch (category) {
|
|
239
|
-
case "console":
|
|
240
|
-
text.push(`### New console messages
|
|
241
|
-
${content}
|
|
242
|
-
`);
|
|
243
|
-
break;
|
|
244
|
-
case "downloads":
|
|
245
|
-
text.push(`### Downloads
|
|
246
|
-
${content}
|
|
247
|
-
`);
|
|
248
|
-
break;
|
|
249
|
-
case "files":
|
|
250
|
-
text.push(`### Files
|
|
251
|
-
${content}
|
|
252
|
-
`);
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
for (const [category, value] of Object.entries(this.states)) {
|
|
257
|
-
if (filter && !filter.categories.includes(category))
|
|
258
|
-
continue;
|
|
259
|
-
if (!value.trim())
|
|
260
|
-
continue;
|
|
261
|
-
switch (category) {
|
|
262
|
-
case "page":
|
|
263
|
-
text.push(`### Page state
|
|
264
|
-
${value}
|
|
265
|
-
`);
|
|
266
|
-
break;
|
|
267
|
-
case "tabs":
|
|
268
|
-
text.push(`### Open tabs
|
|
269
|
-
${value}
|
|
270
|
-
`);
|
|
271
|
-
break;
|
|
272
|
-
case "modal":
|
|
273
|
-
text.push(`### Modal state
|
|
274
|
-
${value}
|
|
275
|
-
`);
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return text.join("\n");
|
|
280
|
-
}
|
|
281
|
-
asMeta() {
|
|
282
|
-
const codeUpdate = this.code.length ? { category: "code", content: this.code.join("\n") } : void 0;
|
|
283
|
-
const resultUpdate = this.results.length ? { category: "result", content: this.results.join("\n") } : void 0;
|
|
284
|
-
const updates = [resultUpdate, codeUpdate, ...this.updates].filter(Boolean);
|
|
285
|
-
return {
|
|
286
|
-
"dev.lowire/history": updates,
|
|
287
|
-
"dev.lowire/state": { ...this.states }
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
redact(secrets) {
|
|
291
|
-
const redactText = (text) => {
|
|
292
|
-
for (const [secretName, secretValue] of Object.entries(secrets))
|
|
293
|
-
text = text.replaceAll(secretValue, `<secret>${secretName}</secret>`);
|
|
294
|
-
return text;
|
|
295
|
-
};
|
|
296
|
-
const updates = this.updates.map((update) => ({ ...update, content: redactText(update.content) }));
|
|
297
|
-
const results = this.results.map((result) => redactText(result));
|
|
298
|
-
const code = this.code.map((code2) => redactText(code2));
|
|
299
|
-
const states = Object.fromEntries(Object.entries(this.states).map(([key, value]) => [key, redactText(value)]));
|
|
300
|
-
return new RenderedResponse({ states, updates, results, code });
|
|
301
|
-
}
|
|
165
|
+
return text.slice(0, Math.floor(maxLength / 2)) + "..." + text.slice(-3 - Math.floor(maxLength / 2));
|
|
302
166
|
}
|
|
303
167
|
function parseSections(text) {
|
|
304
168
|
const sections = /* @__PURE__ */ new Map();
|
|
@@ -313,40 +177,108 @@ function parseSections(text) {
|
|
|
313
177
|
}
|
|
314
178
|
return sections;
|
|
315
179
|
}
|
|
180
|
+
async function serializeResponse(context, sections, rootPath) {
|
|
181
|
+
const redactText = (text2) => {
|
|
182
|
+
for (const [secretName, secretValue] of Object.entries(context.config.secrets ?? {}))
|
|
183
|
+
text2 = text2.replaceAll(secretValue, `<secret>${secretName}</secret>`);
|
|
184
|
+
return text2;
|
|
185
|
+
};
|
|
186
|
+
const text = [];
|
|
187
|
+
for (const section of sections) {
|
|
188
|
+
text.push(`### ${section.title}`);
|
|
189
|
+
for (const result of section.content) {
|
|
190
|
+
if (!result.file) {
|
|
191
|
+
if (result.text !== void 0)
|
|
192
|
+
text.push(result.text);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (result.file.suggestedFilename || context.config.outputMode === "file" || result.data) {
|
|
196
|
+
const generatedFileName = await context.outputFile((0, import_utils.dateAsFileName)(result.file.prefix, result.file.ext), { origin: "code", title: section.title });
|
|
197
|
+
const fileName = result.file.suggestedFilename ? await context.outputFile(result.file.suggestedFilename, { origin: "llm", title: section.title }) : generatedFileName;
|
|
198
|
+
text.push(`- [${result.title}](${rootPath ? import_path.default.relative(rootPath, fileName) : fileName})`);
|
|
199
|
+
if (result.data)
|
|
200
|
+
await import_fs.default.promises.writeFile(fileName, result.data, "utf-8");
|
|
201
|
+
else
|
|
202
|
+
await import_fs.default.promises.writeFile(fileName, result.text);
|
|
203
|
+
} else {
|
|
204
|
+
if (result.file.ext === "yml")
|
|
205
|
+
text.push(`\`\`\`yaml
|
|
206
|
+
${result.text}
|
|
207
|
+
\`\`\``);
|
|
208
|
+
else
|
|
209
|
+
text.push(result.text);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const content = [
|
|
214
|
+
{
|
|
215
|
+
type: "text",
|
|
216
|
+
text: redactText(text.join("\n"))
|
|
217
|
+
}
|
|
218
|
+
];
|
|
219
|
+
if (context.config.imageResponses !== "omit") {
|
|
220
|
+
for (const result of sections.flatMap((section) => section.content).filter((result2) => result2.file?.contentType)) {
|
|
221
|
+
const scaledData = (0, import_screenshot.scaleImageToFitMessage)(result.data, result.file.contentType === "image/png" ? "png" : "jpeg");
|
|
222
|
+
content.push({ type: "image", data: scaledData.toString("base64"), mimeType: result.file.contentType });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
content,
|
|
227
|
+
...sections.some((section) => section.isError) ? { isError: true } : {}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
async function serializeStructuredResponse(sections) {
|
|
231
|
+
for (const section of sections) {
|
|
232
|
+
for (const result of section.content) {
|
|
233
|
+
if (!result.data)
|
|
234
|
+
continue;
|
|
235
|
+
result.isBase64 = true;
|
|
236
|
+
result.text = result.data.toString("base64");
|
|
237
|
+
result.data = void 0;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
content: [{ type: "text", text: "", _meta: { sections } }],
|
|
242
|
+
isError: sections.some((section) => section.isError)
|
|
243
|
+
};
|
|
244
|
+
}
|
|
316
245
|
function parseResponse(response) {
|
|
317
246
|
if (response.content?.[0].type !== "text")
|
|
318
247
|
return void 0;
|
|
319
248
|
const text = response.content[0].text;
|
|
320
249
|
const sections = parseSections(text);
|
|
250
|
+
const error = sections.get("Error");
|
|
321
251
|
const result = sections.get("Result");
|
|
322
252
|
const code = sections.get("Ran Playwright code");
|
|
323
253
|
const tabs = sections.get("Open tabs");
|
|
324
|
-
const
|
|
325
|
-
const
|
|
254
|
+
const page = sections.get("Page");
|
|
255
|
+
const snapshot = sections.get("Snapshot");
|
|
256
|
+
const events = sections.get("Events");
|
|
326
257
|
const modalState = sections.get("Modal state");
|
|
327
|
-
const downloads = sections.get("Downloads");
|
|
328
|
-
const files = sections.get("Files");
|
|
329
258
|
const codeNoFrame = code?.replace(/^```js\n/, "").replace(/\n```$/, "");
|
|
330
259
|
const isError = response.isError;
|
|
331
|
-
const attachments = response.content.slice(1);
|
|
260
|
+
const attachments = response.content.length > 1 ? response.content.slice(1) : void 0;
|
|
332
261
|
return {
|
|
333
262
|
result,
|
|
263
|
+
error,
|
|
334
264
|
code: codeNoFrame,
|
|
335
265
|
tabs,
|
|
336
|
-
|
|
337
|
-
|
|
266
|
+
page,
|
|
267
|
+
snapshot,
|
|
268
|
+
events,
|
|
338
269
|
modalState,
|
|
339
|
-
downloads,
|
|
340
|
-
files,
|
|
341
270
|
isError,
|
|
342
271
|
attachments,
|
|
343
|
-
|
|
272
|
+
text
|
|
344
273
|
};
|
|
345
274
|
}
|
|
346
275
|
// Annotate the CommonJS export names for ESM import in node:
|
|
347
276
|
0 && (module.exports = {
|
|
348
|
-
RenderedResponse,
|
|
349
277
|
Response,
|
|
350
278
|
parseResponse,
|
|
351
|
-
|
|
279
|
+
renderTabMarkdown,
|
|
280
|
+
renderTabsMarkdown,
|
|
281
|
+
requestDebug,
|
|
282
|
+
serializeResponse,
|
|
283
|
+
serializeStructuredResponse
|
|
352
284
|
});
|
|
@@ -33,124 +33,39 @@ __export(sessionLog_exports, {
|
|
|
33
33
|
module.exports = __toCommonJS(sessionLog_exports);
|
|
34
34
|
var import_fs = __toESM(require("fs"));
|
|
35
35
|
var import_path = __toESM(require("path"));
|
|
36
|
-
var import_log = require("../log");
|
|
37
36
|
var import_config = require("./config");
|
|
37
|
+
var import_response = require("./response");
|
|
38
38
|
class SessionLog {
|
|
39
39
|
constructor(sessionFolder) {
|
|
40
|
-
this._ordinal = 0;
|
|
41
|
-
this._pendingEntries = [];
|
|
42
40
|
this._sessionFileQueue = Promise.resolve();
|
|
43
41
|
this._folder = sessionFolder;
|
|
44
42
|
this._file = import_path.default.join(this._folder, "session.md");
|
|
45
43
|
}
|
|
46
44
|
static async create(config, clientInfo) {
|
|
47
|
-
const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`, { origin: "code",
|
|
45
|
+
const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`, { origin: "code", title: "Saving session" });
|
|
48
46
|
await import_fs.default.promises.mkdir(sessionFolder, { recursive: true });
|
|
49
47
|
console.error(`Session: ${sessionFolder}`);
|
|
50
48
|
return new SessionLog(sessionFolder);
|
|
51
49
|
}
|
|
52
|
-
logResponse(
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
toolName: response.toolName,
|
|
57
|
-
toolArgs: response.toolArgs,
|
|
58
|
-
result: response.result(),
|
|
59
|
-
isError: response.isError()
|
|
60
|
-
},
|
|
61
|
-
code: response.code(),
|
|
62
|
-
tabSnapshot: response.tabSnapshot()
|
|
63
|
-
};
|
|
64
|
-
this._appendEntry(entry);
|
|
65
|
-
}
|
|
66
|
-
logUserAction(action, tab, code, isUpdate) {
|
|
67
|
-
code = code.trim();
|
|
68
|
-
if (isUpdate) {
|
|
69
|
-
const lastEntry = this._pendingEntries[this._pendingEntries.length - 1];
|
|
70
|
-
if (lastEntry?.userAction?.name === action.name) {
|
|
71
|
-
lastEntry.userAction = action;
|
|
72
|
-
lastEntry.code = code;
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (action.name === "navigate") {
|
|
77
|
-
const lastEntry = this._pendingEntries[this._pendingEntries.length - 1];
|
|
78
|
-
if (lastEntry?.tabSnapshot?.url === action.url)
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const entry = {
|
|
82
|
-
timestamp: performance.now(),
|
|
83
|
-
userAction: action,
|
|
84
|
-
code,
|
|
85
|
-
tabSnapshot: {
|
|
86
|
-
url: tab.page.url(),
|
|
87
|
-
title: "",
|
|
88
|
-
ariaSnapshot: action.ariaSnapshot || "",
|
|
89
|
-
modalStates: [],
|
|
90
|
-
consoleMessages: [],
|
|
91
|
-
downloads: []
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
this._appendEntry(entry);
|
|
95
|
-
}
|
|
96
|
-
_appendEntry(entry) {
|
|
97
|
-
this._pendingEntries.push(entry);
|
|
98
|
-
if (this._flushEntriesTimeout)
|
|
99
|
-
clearTimeout(this._flushEntriesTimeout);
|
|
100
|
-
this._flushEntriesTimeout = setTimeout(() => this._flushEntries(), 1e3);
|
|
101
|
-
}
|
|
102
|
-
async _flushEntries() {
|
|
103
|
-
clearTimeout(this._flushEntriesTimeout);
|
|
104
|
-
const entries = this._pendingEntries;
|
|
105
|
-
this._pendingEntries = [];
|
|
50
|
+
logResponse(toolName, toolArgs, responseObject) {
|
|
51
|
+
const parsed = (0, import_response.parseResponse)(responseObject);
|
|
52
|
+
if (parsed)
|
|
53
|
+
delete parsed.text;
|
|
106
54
|
const lines = [""];
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
entry.toolCall.isError ? `- Error` : `- Result`,
|
|
120
|
-
"```",
|
|
121
|
-
entry.toolCall.result,
|
|
122
|
-
"```"
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (entry.userAction) {
|
|
127
|
-
const actionData = { ...entry.userAction };
|
|
128
|
-
delete actionData.ariaSnapshot;
|
|
129
|
-
delete actionData.selector;
|
|
130
|
-
delete actionData.signals;
|
|
131
|
-
lines.push(
|
|
132
|
-
`### User action: ${entry.userAction.name}`,
|
|
133
|
-
`- Args`,
|
|
134
|
-
"```json",
|
|
135
|
-
JSON.stringify(actionData, null, 2),
|
|
136
|
-
"```"
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
if (entry.code) {
|
|
140
|
-
lines.push(
|
|
141
|
-
`- Code`,
|
|
142
|
-
"```js",
|
|
143
|
-
entry.code,
|
|
144
|
-
"```"
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
if (entry.tabSnapshot) {
|
|
148
|
-
const fileName = `${ordinal}.snapshot.yml`;
|
|
149
|
-
import_fs.default.promises.writeFile(import_path.default.join(this._folder, fileName), entry.tabSnapshot.ariaSnapshot).catch(import_log.logUnhandledError);
|
|
150
|
-
lines.push(`- Snapshot: ${fileName}`);
|
|
151
|
-
}
|
|
152
|
-
lines.push("", "");
|
|
55
|
+
lines.push(
|
|
56
|
+
`### Tool call: ${toolName}`,
|
|
57
|
+
`- Args`,
|
|
58
|
+
"```json",
|
|
59
|
+
JSON.stringify(toolArgs, null, 2),
|
|
60
|
+
"```"
|
|
61
|
+
);
|
|
62
|
+
if (parsed) {
|
|
63
|
+
lines.push(`- Result`);
|
|
64
|
+
lines.push("```json");
|
|
65
|
+
lines.push(JSON.stringify(parsed, null, 2));
|
|
66
|
+
lines.push("```");
|
|
153
67
|
}
|
|
68
|
+
lines.push("");
|
|
154
69
|
this._sessionFileQueue = this._sessionFileQueue.then(() => import_fs.default.promises.appendFile(this._file, lines.join("\n")));
|
|
155
70
|
}
|
|
156
71
|
}
|