@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.
@@ -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) {
@@ -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 { arch, archNode } = resolveBuildCacheArch();
253
+ const archInfo = resolveBuildCacheArch();
254
+ const artifacts = createBuildCacheArtifacts(ctx, cache, imageTool, archInfo);
237
255
 
238
256
  ctx.log(`\n${CYAN}准备构建缓存...${NC}`);
239
- prepareNodeBuildCache(ctx, cache, archNode);
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
  }
@@ -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 data = extractors[agent](ctx.homeDir);
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, yoloMap[agent], data.volumes, data.keys, data.values);
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
+ };