@xcanwin/manyoyo 5.8.5 → 5.8.9
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/README.md +1 -0
- package/bin/manyoyo.js +265 -174
- package/lib/global-config.js +1 -198
- package/lib/image-build.js +20 -4
- package/lib/init-config.js +22 -10
- package/lib/json5-text-edit.js +238 -0
- package/lib/plugin/playwright-bootstrap.js +116 -0
- package/lib/plugin/playwright-command-output.js +95 -0
- package/lib/plugin/playwright-container-runtime.js +94 -0
- package/lib/plugin/playwright-extension-manager.js +265 -0
- package/lib/plugin/playwright-extension-paths.js +98 -0
- package/lib/plugin/playwright-host-runtime.js +114 -0
- package/lib/plugin/playwright-scene-config.js +137 -0
- package/lib/plugin/playwright-scene-drivers.js +285 -0
- package/lib/plugin/playwright-scene-state.js +80 -0
- package/lib/plugin/playwright.js +169 -1049
- package/lib/runtime-normalizers.js +65 -0
- package/lib/runtime-resolver.js +195 -0
- package/lib/web/agent-command.js +153 -0
- package/lib/web/api-route-helpers.js +88 -0
- package/lib/web/container-exec.js +215 -0
- package/lib/web/http-handlers.js +163 -0
- package/lib/web/runtime-state.js +50 -0
- package/lib/web/server-context.js +71 -0
- package/lib/web/server-lifecycle.js +129 -0
- package/lib/web/server.js +293 -2496
- package/lib/web/session-api-routes.js +390 -0
- package/lib/web/structured-output.js +149 -0
- package/lib/web/structured-trace.js +603 -0
- package/lib/web/system-api-routes.js +114 -0
- package/lib/web/terminal-session.js +205 -0
- package/lib/web/upgrade-handler.js +94 -0
- package/package.json +1 -1
package/lib/global-config.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const JSON5 = require('json5');
|
|
7
|
+
const { findTopLevelPropertyValueRange } = require('./json5-text-edit');
|
|
7
8
|
|
|
8
9
|
function getManyoyoConfigPath(homeDir = os.homedir()) {
|
|
9
10
|
return path.join(homeDir, '.manyoyo', 'manyoyo.json');
|
|
@@ -36,204 +37,6 @@ function readManyoyoConfig(homeDir = os.homedir()) {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
function readQuotedString(text, startIndex) {
|
|
40
|
-
const quote = text[startIndex];
|
|
41
|
-
let value = '';
|
|
42
|
-
|
|
43
|
-
for (let i = startIndex + 1; i < text.length; i += 1) {
|
|
44
|
-
const ch = text[i];
|
|
45
|
-
if (ch === '\\') {
|
|
46
|
-
value += ch;
|
|
47
|
-
if (i + 1 < text.length) {
|
|
48
|
-
value += text[i + 1];
|
|
49
|
-
i += 1;
|
|
50
|
-
}
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if (ch === quote) {
|
|
54
|
-
return {
|
|
55
|
-
value,
|
|
56
|
-
end: i + 1
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
value += ch;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function isIdentifierStart(ch) {
|
|
66
|
-
return /[A-Za-z_$]/.test(ch);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function isIdentifierPart(ch) {
|
|
70
|
-
return /[A-Za-z0-9_$]/.test(ch);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function skipWhitespace(text, index) {
|
|
74
|
-
let i = index;
|
|
75
|
-
while (i < text.length && /\s/.test(text[i])) {
|
|
76
|
-
i += 1;
|
|
77
|
-
}
|
|
78
|
-
return i;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function findTopLevelPropertyValueRange(text, propertyName) {
|
|
82
|
-
let depth = 0;
|
|
83
|
-
let inString = '';
|
|
84
|
-
let inLineComment = false;
|
|
85
|
-
let inBlockComment = false;
|
|
86
|
-
|
|
87
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
88
|
-
const ch = text[i];
|
|
89
|
-
const next = text[i + 1];
|
|
90
|
-
|
|
91
|
-
if (inLineComment) {
|
|
92
|
-
if (ch === '\n') inLineComment = false;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (inBlockComment) {
|
|
96
|
-
if (ch === '*' && next === '/') {
|
|
97
|
-
inBlockComment = false;
|
|
98
|
-
i += 1;
|
|
99
|
-
}
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (inString) {
|
|
103
|
-
if (ch === '\\') {
|
|
104
|
-
i += 1;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
if (ch === inString) inString = '';
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (ch === '/' && next === '/') {
|
|
112
|
-
inLineComment = true;
|
|
113
|
-
i += 1;
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
if (ch === '/' && next === '*') {
|
|
117
|
-
inBlockComment = true;
|
|
118
|
-
i += 1;
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if (depth === 1 && !/\s|,/.test(ch)) {
|
|
122
|
-
let property = '';
|
|
123
|
-
let cursor = i;
|
|
124
|
-
if (ch === '"' || ch === '\'') {
|
|
125
|
-
const token = readQuotedString(text, i);
|
|
126
|
-
if (!token) {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
property = token.value;
|
|
130
|
-
cursor = token.end;
|
|
131
|
-
} else if (isIdentifierStart(ch)) {
|
|
132
|
-
cursor = i + 1;
|
|
133
|
-
while (cursor < text.length && isIdentifierPart(text[cursor])) {
|
|
134
|
-
cursor += 1;
|
|
135
|
-
}
|
|
136
|
-
property = text.slice(i, cursor);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (property) {
|
|
140
|
-
const colonIndex = skipWhitespace(text, cursor);
|
|
141
|
-
if (text[colonIndex] === ':') {
|
|
142
|
-
if (property !== propertyName) {
|
|
143
|
-
i = colonIndex;
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
let valueStart = skipWhitespace(text, colonIndex + 1);
|
|
148
|
-
let valueEnd = valueStart;
|
|
149
|
-
let valueString = '';
|
|
150
|
-
let valueLineComment = false;
|
|
151
|
-
let valueBlockComment = false;
|
|
152
|
-
let valueDepth = 0;
|
|
153
|
-
|
|
154
|
-
for (; valueEnd < text.length; valueEnd += 1) {
|
|
155
|
-
const valueCh = text[valueEnd];
|
|
156
|
-
const valueNext = text[valueEnd + 1];
|
|
157
|
-
|
|
158
|
-
if (valueLineComment) {
|
|
159
|
-
if (valueCh === '\n') valueLineComment = false;
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
if (valueBlockComment) {
|
|
163
|
-
if (valueCh === '*' && valueNext === '/') {
|
|
164
|
-
valueBlockComment = false;
|
|
165
|
-
valueEnd += 1;
|
|
166
|
-
}
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
if (valueString) {
|
|
170
|
-
if (valueCh === '\\') {
|
|
171
|
-
valueEnd += 1;
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
if (valueCh === valueString) valueString = '';
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (valueCh === '/' && valueNext === '/') {
|
|
179
|
-
valueLineComment = true;
|
|
180
|
-
valueEnd += 1;
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
if (valueCh === '/' && valueNext === '*') {
|
|
184
|
-
valueBlockComment = true;
|
|
185
|
-
valueEnd += 1;
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (valueCh === '"' || valueCh === '\'') {
|
|
189
|
-
valueString = valueCh;
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
if (valueCh === '{' || valueCh === '[' || valueCh === '(') {
|
|
193
|
-
valueDepth += 1;
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (valueCh === '}' || valueCh === ']' || valueCh === ')') {
|
|
197
|
-
if (valueDepth === 0) {
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
valueDepth -= 1;
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (valueDepth === 0 && valueCh === ',') {
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
while (valueEnd > valueStart && /\s/.test(text[valueEnd - 1])) {
|
|
209
|
-
valueEnd -= 1;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
start: valueStart,
|
|
214
|
-
end: valueEnd
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (ch === '"' || ch === '\'') {
|
|
221
|
-
inString = ch;
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
if (ch === '{' || ch === '[') {
|
|
225
|
-
depth += 1;
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (ch === '}' || ch === ']') {
|
|
229
|
-
depth -= 1;
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
40
|
function insertTopLevelImageVersion(text, imageVersion) {
|
|
238
41
|
const openBraceIndex = text.indexOf('{');
|
|
239
42
|
if (openBraceIndex === -1) {
|
package/lib/image-build.js
CHANGED
|
@@ -230,15 +230,31 @@ function prepareGoplsBuildCache(ctx, cache, imageTool, arch) {
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
function createBuildCacheArtifacts(ctx, cache, imageTool, archInfo) {
|
|
234
|
+
return [
|
|
235
|
+
{
|
|
236
|
+
name: 'node',
|
|
237
|
+
prepare: () => prepareNodeBuildCache(ctx, cache, archInfo.archNode)
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'jdtls',
|
|
241
|
+
prepare: () => prepareJdtlsBuildCache(ctx, cache, imageTool)
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: 'gopls',
|
|
245
|
+
prepare: () => prepareGoplsBuildCache(ctx, cache, imageTool, archInfo.arch)
|
|
246
|
+
}
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
|
|
233
250
|
async function prepareBuildCache(ctx, imageTool) {
|
|
234
251
|
const { CYAN, GREEN, NC } = ctx.colors;
|
|
235
252
|
const cache = createBuildCacheContext(ctx);
|
|
236
|
-
const
|
|
253
|
+
const archInfo = resolveBuildCacheArch();
|
|
254
|
+
const artifacts = createBuildCacheArtifacts(ctx, cache, imageTool, archInfo);
|
|
237
255
|
|
|
238
256
|
ctx.log(`\n${CYAN}准备构建缓存...${NC}`);
|
|
239
|
-
|
|
240
|
-
prepareJdtlsBuildCache(ctx, cache, imageTool);
|
|
241
|
-
prepareGoplsBuildCache(ctx, cache, imageTool, arch);
|
|
257
|
+
artifacts.forEach(artifact => artifact.prepare());
|
|
242
258
|
saveBuildCacheTimestamps(cache.timestampFile, cache.timestamps);
|
|
243
259
|
ctx.log(`${GREEN}✅ 构建缓存准备完成${NC}\n`);
|
|
244
260
|
}
|
package/lib/init-config.js
CHANGED
|
@@ -251,6 +251,25 @@ function collectOpenCodeInitData(homeDir, ctx) {
|
|
|
251
251
|
return { keys, values, notes, volumes: dedupeList(volumes) };
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
const AGENT_INIT_SPECS = {
|
|
255
|
+
claude: {
|
|
256
|
+
yolo: 'c',
|
|
257
|
+
collect: (homeDir, ctx) => collectClaudeInitData(homeDir, ctx)
|
|
258
|
+
},
|
|
259
|
+
codex: {
|
|
260
|
+
yolo: 'cx',
|
|
261
|
+
collect: (homeDir, ctx) => collectCodexInitData(homeDir, ctx)
|
|
262
|
+
},
|
|
263
|
+
gemini: {
|
|
264
|
+
yolo: 'gm',
|
|
265
|
+
collect: (homeDir) => collectGeminiInitData(homeDir)
|
|
266
|
+
},
|
|
267
|
+
opencode: {
|
|
268
|
+
yolo: 'oc',
|
|
269
|
+
collect: (homeDir, ctx) => collectOpenCodeInitData(homeDir, ctx)
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
254
273
|
function buildInitRunEnv(keys, values) {
|
|
255
274
|
const envMap = {};
|
|
256
275
|
const missingKeys = [];
|
|
@@ -338,24 +357,17 @@ async function initAgentConfigs(rawAgents, options = {}) {
|
|
|
338
357
|
runsMap = { ...manyoyoConfig.runs };
|
|
339
358
|
}
|
|
340
359
|
|
|
341
|
-
const extractors = {
|
|
342
|
-
claude: homeDir => collectClaudeInitData(homeDir, ctx),
|
|
343
|
-
codex: homeDir => collectCodexInitData(homeDir, ctx),
|
|
344
|
-
gemini: collectGeminiInitData,
|
|
345
|
-
opencode: homeDir => collectOpenCodeInitData(homeDir, ctx)
|
|
346
|
-
};
|
|
347
|
-
const yoloMap = { claude: 'c', codex: 'cx', gemini: 'gm', opencode: 'oc' };
|
|
348
|
-
|
|
349
360
|
let hasConfigChanged = false;
|
|
350
361
|
ctx.log(`${CYAN}🧭 正在初始化 MANYOYO 配置: ${agents.join(', ')}${NC}`);
|
|
351
362
|
|
|
352
363
|
for (const agent of agents) {
|
|
353
|
-
const
|
|
364
|
+
const spec = AGENT_INIT_SPECS[agent];
|
|
365
|
+
const data = spec.collect(ctx.homeDir, ctx);
|
|
354
366
|
const shouldWriteRun = await shouldOverwriteInitRunEntry(agent, Object.prototype.hasOwnProperty.call(runsMap, agent), ctx);
|
|
355
367
|
|
|
356
368
|
let writeResult = { missingKeys: [], unsafeKeys: [] };
|
|
357
369
|
if (shouldWriteRun) {
|
|
358
|
-
const buildResult = buildInitRunProfile(agent,
|
|
370
|
+
const buildResult = buildInitRunProfile(agent, spec.yolo, data.volumes, data.keys, data.values);
|
|
359
371
|
runsMap[agent] = buildResult.runProfile;
|
|
360
372
|
writeResult = { missingKeys: buildResult.missingKeys, unsafeKeys: buildResult.unsafeKeys };
|
|
361
373
|
hasConfigChanged = true;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function readQuotedString(text, startIndex) {
|
|
4
|
+
const quote = text[startIndex];
|
|
5
|
+
let value = '';
|
|
6
|
+
|
|
7
|
+
for (let i = startIndex + 1; i < text.length; i += 1) {
|
|
8
|
+
const ch = text[i];
|
|
9
|
+
if (ch === '\\') {
|
|
10
|
+
value += ch;
|
|
11
|
+
if (i + 1 < text.length) {
|
|
12
|
+
value += text[i + 1];
|
|
13
|
+
i += 1;
|
|
14
|
+
}
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (ch === quote) {
|
|
18
|
+
return {
|
|
19
|
+
value,
|
|
20
|
+
end: i + 1
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
value += ch;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isIdentifierStart(ch) {
|
|
30
|
+
return /[A-Za-z_$]/.test(ch);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isIdentifierPart(ch) {
|
|
34
|
+
return /[A-Za-z0-9_$]/.test(ch);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function skipTrivia(text, index) {
|
|
38
|
+
let cursor = index;
|
|
39
|
+
while (cursor < text.length) {
|
|
40
|
+
const ch = text[cursor];
|
|
41
|
+
const next = text[cursor + 1];
|
|
42
|
+
if (/\s/.test(ch)) {
|
|
43
|
+
cursor += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (ch === '/' && next === '/') {
|
|
47
|
+
cursor += 2;
|
|
48
|
+
while (cursor < text.length && text[cursor] !== '\n') {
|
|
49
|
+
cursor += 1;
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (ch === '/' && next === '*') {
|
|
54
|
+
cursor += 2;
|
|
55
|
+
while (cursor + 1 < text.length && !(text[cursor] === '*' && text[cursor + 1] === '/')) {
|
|
56
|
+
cursor += 1;
|
|
57
|
+
}
|
|
58
|
+
cursor = cursor + 1 < text.length ? cursor + 2 : text.length;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
return cursor;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function scanValueEnd(text, startIndex) {
|
|
67
|
+
let cursor = startIndex;
|
|
68
|
+
let stringQuote = '';
|
|
69
|
+
let lineComment = false;
|
|
70
|
+
let blockComment = false;
|
|
71
|
+
let depth = 0;
|
|
72
|
+
|
|
73
|
+
for (; cursor < text.length; cursor += 1) {
|
|
74
|
+
const ch = text[cursor];
|
|
75
|
+
const next = text[cursor + 1];
|
|
76
|
+
|
|
77
|
+
if (lineComment) {
|
|
78
|
+
if (ch === '\n') {
|
|
79
|
+
lineComment = false;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (blockComment) {
|
|
84
|
+
if (ch === '*' && next === '/') {
|
|
85
|
+
blockComment = false;
|
|
86
|
+
cursor += 1;
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (stringQuote) {
|
|
91
|
+
if (ch === '\\') {
|
|
92
|
+
cursor += 1;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (ch === stringQuote) {
|
|
96
|
+
stringQuote = '';
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (ch === '/' && next === '/') {
|
|
102
|
+
lineComment = true;
|
|
103
|
+
cursor += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (ch === '/' && next === '*') {
|
|
107
|
+
blockComment = true;
|
|
108
|
+
cursor += 1;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (ch === '"' || ch === '\'') {
|
|
112
|
+
stringQuote = ch;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (ch === '{' || ch === '[' || ch === '(') {
|
|
116
|
+
depth += 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (ch === '}' || ch === ']' || ch === ')') {
|
|
120
|
+
if (depth === 0) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
depth -= 1;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (depth === 0 && ch === ',') {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let end = cursor;
|
|
132
|
+
while (end > startIndex && /\s/.test(text[end - 1])) {
|
|
133
|
+
end -= 1;
|
|
134
|
+
}
|
|
135
|
+
return end;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function findRootObjectStart(text) {
|
|
139
|
+
const source = String(text || '');
|
|
140
|
+
const start = skipTrivia(source, 0);
|
|
141
|
+
return source[start] === '{' ? start : -1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function readPropertyToken(text, startIndex) {
|
|
145
|
+
const ch = text[startIndex];
|
|
146
|
+
if (ch === '"' || ch === '\'') {
|
|
147
|
+
return readQuotedString(text, startIndex);
|
|
148
|
+
}
|
|
149
|
+
if (!isIdentifierStart(ch)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let end = startIndex + 1;
|
|
154
|
+
while (end < text.length && isIdentifierPart(text[end])) {
|
|
155
|
+
end += 1;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
value: text.slice(startIndex, end),
|
|
159
|
+
end
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function findObjectPropertyValueRange(text, objectStartIndex, propertyName) {
|
|
164
|
+
let cursor = skipTrivia(text, objectStartIndex + 1);
|
|
165
|
+
while (cursor < text.length) {
|
|
166
|
+
cursor = skipTrivia(text, cursor);
|
|
167
|
+
if (text[cursor] === '}') {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const token = readPropertyToken(text, cursor);
|
|
171
|
+
if (!token) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
cursor = skipTrivia(text, token.end);
|
|
175
|
+
if (text[cursor] !== ':') {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const valueStart = skipTrivia(text, cursor + 1);
|
|
179
|
+
const valueEnd = scanValueEnd(text, valueStart);
|
|
180
|
+
if (token.value === propertyName) {
|
|
181
|
+
return { start: valueStart, end: valueEnd };
|
|
182
|
+
}
|
|
183
|
+
cursor = skipTrivia(text, valueEnd);
|
|
184
|
+
if (text[cursor] === ',') {
|
|
185
|
+
cursor += 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (text[cursor] === '}') {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function findValueRangeByPath(text, pathParts) {
|
|
196
|
+
if (!Array.isArray(pathParts) || pathParts.length === 0) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let objectStart = findRootObjectStart(text);
|
|
201
|
+
if (objectStart === -1) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let range = null;
|
|
206
|
+
for (let i = 0; i < pathParts.length; i += 1) {
|
|
207
|
+
range = findObjectPropertyValueRange(text, objectStart, pathParts[i]);
|
|
208
|
+
if (!range) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
if (i === pathParts.length - 1) {
|
|
212
|
+
return range;
|
|
213
|
+
}
|
|
214
|
+
const nextObjectStart = skipTrivia(text, range.start);
|
|
215
|
+
if (text[nextObjectStart] !== '{') {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
objectStart = nextObjectStart;
|
|
219
|
+
}
|
|
220
|
+
return range;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function findTopLevelPropertyValueRange(text, propertyName) {
|
|
224
|
+
return findValueRangeByPath(text, [propertyName]);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function applyTextReplacements(text, replacements) {
|
|
228
|
+
return replacements
|
|
229
|
+
.slice()
|
|
230
|
+
.sort((a, b) => b.start - a.start)
|
|
231
|
+
.reduce((result, item) => `${result.slice(0, item.start)}${item.text}${result.slice(item.end)}`, text);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = {
|
|
235
|
+
findTopLevelPropertyValueRange,
|
|
236
|
+
findValueRangeByPath,
|
|
237
|
+
applyTextReplacements
|
|
238
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function createPlaywrightBootstrapManager(options = {}) {
|
|
7
|
+
const plugin = options.plugin;
|
|
8
|
+
const isCliScene = options.isCliScene || (() => false);
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
buildInitScriptContent() {
|
|
12
|
+
const lines = [
|
|
13
|
+
"'use strict';",
|
|
14
|
+
'(function () {',
|
|
15
|
+
` const platformValue = ${JSON.stringify(plugin.config.navigatorPlatform)};`,
|
|
16
|
+
' try {',
|
|
17
|
+
' const navProto = Object.getPrototypeOf(navigator);',
|
|
18
|
+
" Object.defineProperty(navProto, 'platform', {",
|
|
19
|
+
' configurable: true,',
|
|
20
|
+
' get: () => platformValue',
|
|
21
|
+
' });',
|
|
22
|
+
' } catch (_) {}'
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
if (plugin.config.disableWebRTC) {
|
|
26
|
+
lines.push(
|
|
27
|
+
' try {',
|
|
28
|
+
' const scope = globalThis;',
|
|
29
|
+
" const blocked = ['RTCPeerConnection', 'webkitRTCPeerConnection', 'RTCIceCandidate', 'RTCRtpSender', 'RTCRtpReceiver', 'RTCRtpTransceiver', 'RTCDataChannel'];",
|
|
30
|
+
' for (const name of blocked) {',
|
|
31
|
+
" Object.defineProperty(scope, name, { configurable: true, writable: true, value: undefined });",
|
|
32
|
+
' }',
|
|
33
|
+
' if (navigator.mediaDevices) {',
|
|
34
|
+
' const errorFactory = () => {',
|
|
35
|
+
' try {',
|
|
36
|
+
" return new DOMException('WebRTC is disabled', 'NotAllowedError');",
|
|
37
|
+
' } catch (_) {',
|
|
38
|
+
" const error = new Error('WebRTC is disabled');",
|
|
39
|
+
" error.name = 'NotAllowedError';",
|
|
40
|
+
' return error;',
|
|
41
|
+
' }',
|
|
42
|
+
' };',
|
|
43
|
+
" Object.defineProperty(navigator.mediaDevices, 'getUserMedia', {",
|
|
44
|
+
' configurable: true,',
|
|
45
|
+
' writable: true,',
|
|
46
|
+
' value: async () => { throw errorFactory(); }',
|
|
47
|
+
' });',
|
|
48
|
+
' }',
|
|
49
|
+
' } catch (_) {}'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
lines.push('})();', '');
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
},
|
|
56
|
+
ensureSceneInitScript(sceneName) {
|
|
57
|
+
const filePath = plugin.sceneInitScriptPath(sceneName);
|
|
58
|
+
const content = this.buildInitScriptContent();
|
|
59
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
60
|
+
return filePath;
|
|
61
|
+
},
|
|
62
|
+
defaultBrowserName(sceneName) {
|
|
63
|
+
if (isCliScene(sceneName)) {
|
|
64
|
+
return 'chromium';
|
|
65
|
+
}
|
|
66
|
+
const cfg = plugin.buildSceneConfig(sceneName);
|
|
67
|
+
const browserName = cfg && cfg.browser && cfg.browser.browserName;
|
|
68
|
+
return String(browserName || 'chromium');
|
|
69
|
+
},
|
|
70
|
+
ensureContainerScenePrerequisites(sceneName) {
|
|
71
|
+
if (!plugin.sceneConfigMissing(sceneName)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const tag = String(plugin.config.dockerTag || 'latest').trim() || 'latest';
|
|
75
|
+
const image = `mcr.microsoft.com/playwright/mcp:${tag}`;
|
|
76
|
+
plugin.runCmd([plugin.config.containerRuntime, 'pull', image], { check: true });
|
|
77
|
+
},
|
|
78
|
+
ensureHostScenePrerequisites(sceneName) {
|
|
79
|
+
if (!isCliScene(sceneName) && !plugin.sceneConfigMissing(sceneName)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
plugin.runCmd([plugin.playwrightBinPath(sceneName), 'install', '--with-deps', plugin.defaultBrowserName(sceneName)], { check: true });
|
|
83
|
+
},
|
|
84
|
+
localBinPath(binName) {
|
|
85
|
+
const filename = process.platform === 'win32' ? `${binName}.cmd` : binName;
|
|
86
|
+
const binPath = path.join(plugin.projectRoot, 'node_modules', '.bin', filename);
|
|
87
|
+
if (!fs.existsSync(binPath)) {
|
|
88
|
+
throw new Error(`local binary not found: ${binPath}. Run npm install first.`);
|
|
89
|
+
}
|
|
90
|
+
return binPath;
|
|
91
|
+
},
|
|
92
|
+
playwrightBinPath(sceneName) {
|
|
93
|
+
if (!isCliScene(sceneName)) {
|
|
94
|
+
return plugin.localBinPath('playwright');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const filename = process.platform === 'win32' ? 'playwright.cmd' : 'playwright';
|
|
98
|
+
const candidates = [
|
|
99
|
+
path.join(plugin.projectRoot, 'node_modules', '@playwright', 'mcp', 'node_modules', '.bin', filename),
|
|
100
|
+
path.join(plugin.projectRoot, 'node_modules', '.bin', filename)
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
for (const candidate of candidates) {
|
|
104
|
+
if (fs.existsSync(candidate)) {
|
|
105
|
+
return candidate;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
throw new Error(`local binary not found for ${sceneName}. Run npm install first.`);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
createPlaywrightBootstrapManager
|
|
116
|
+
};
|