@xcanwin/manyoyo 8.0.1 → 8.2.0

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/web/server.js CHANGED
@@ -87,7 +87,7 @@ const WEB_CONFIG_KEEP_SECRET_PLACEHOLDER = '***HIDDEN_SECRET***';
87
87
  const DEFAULT_AGENT_STREAM_HEARTBEAT_MS = 15 * 1000;
88
88
  // token 级增量的历史落盘节流:写盘只为断线/刷新后能对账恢复,不需要逐 token 持久化
89
89
  const AGENT_STREAM_PARTIAL_PERSIST_INTERVAL_MS = 1000;
90
- const FRONTEND_DIR = path.join(__dirname, 'frontend');
90
+ const INDEX_HTML_PATH = path.join(__dirname, 'index.html');
91
91
  const SAFE_CONTAINER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
92
92
  const IMAGE_VERSION_TAG_PATTERN = /^(\d+\.\d+\.\d+)-([A-Za-z0-9][A-Za-z0-9_.-]*)$/;
93
93
  const SENSITIVE_CONFIG_KEY_PATTERN = /(pass(word)?|passwd|secret|token|api(?:_|-)?key|auth(?:_|-)?token|oauth(?:_|-)?token)$/i;
@@ -4487,24 +4487,11 @@ function buildSessionAudit(ctx, state, sessionRef) {
4487
4487
  };
4488
4488
  }
4489
4489
 
4490
- function isSafeStaticAssetName(name) {
4491
- return /^[A-Za-z0-9._-]+$/.test(name);
4492
- }
4493
-
4494
- function resolveStaticAsset(name) {
4495
- if (!isSafeStaticAssetName(name)) {
4496
- return null;
4497
- }
4498
- const fullPath = path.join(FRONTEND_DIR, name);
4499
- return fs.existsSync(fullPath) ? fullPath : null;
4500
- }
4501
-
4502
- function loadTemplate(name) {
4503
- const filePath = resolveStaticAsset(name);
4504
- if (!filePath) {
4490
+ function loadIndexHtml() {
4491
+ if (!fs.existsSync(INDEX_HTML_PATH)) {
4505
4492
  return '<h1>Template Not Found</h1>';
4506
4493
  }
4507
- return fs.readFileSync(filePath, 'utf-8');
4494
+ return fs.readFileSync(INDEX_HTML_PATH, 'utf-8');
4508
4495
  }
4509
4496
 
4510
4497
  function escapeHtmlText(value) {
@@ -4522,13 +4509,13 @@ function applyServeTitle(html, ctx) {
4522
4509
  return html.replace(/<title>[^<]*<\/title>/, () => `<title>${escapeHtmlText(ctx.serveTitle)}</title>`);
4523
4510
  }
4524
4511
 
4525
- function renderShadcnHtml(ctx) {
4526
- // shadcn.html 是 vite singlefile 打包产物,全部依赖内联成一个 <script>,
4512
+ function renderAppHtml(ctx) {
4513
+ // index.html 是 vite singlefile 打包产物,全部依赖内联成一个 <script>,
4527
4514
  // 用 .replace('</head>', ...) 注入标记有极小概率命中某个库源码里字面量出现的
4528
4515
  // "</head>",把注入内容插进内联脚本中间导致整页解析错乱;所以改成跟
4529
4516
  // applyServeTitle 同款更窄、更安全的 <title> 定位,插一个 meta 标记
4530
4517
  // 告知前端跳过动态改写 document.title
4531
- let html = applyServeTitle(loadTemplate('shadcn.html'), ctx);
4518
+ let html = applyServeTitle(loadIndexHtml(), ctx);
4532
4519
  if (typeof ctx.serveTitle === 'string') {
4533
4520
  html = html.replace(
4534
4521
  /<\/title>/,
@@ -4787,15 +4774,9 @@ async function handleWebAuthRoutes(req, res, pathname, ctx, state) {
4787
4774
  return true;
4788
4775
  }
4789
4776
 
4790
- // 登录页:与 / 、/shadcn 复用同一份构建产物,由前端按 pathname 自行渲染
4791
- // 登录表单,登录逻辑走下面的 POST /auth/login 接口
4792
- if (req.method === 'GET' && pathname === '/shadcn/auth/login') {
4793
- sendHtml(res, 200, applyServeTitle(loadTemplate('shadcn.html'), ctx));
4794
- return true;
4795
- }
4796
-
4797
- // GET /auth/login 曾经是独立的登录页面,现在只留一个跳转,接住老书签
4798
- // 带 ?token= 时是本机启动器的一次性登录:仅回环监听启用,令牌用后即删
4777
+ // 登录页:与 / 复用同一份构建产物,由前端按 pathname 自行渲染登录表单,
4778
+ // 登录逻辑走下面的 POST /auth/login 接口
4779
+ // 带 ?token= 时是本机启动器的一次性登录:仅回环监听启用,令牌用后即删;令牌无效就落在登录页
4799
4780
  if (req.method === 'GET' && pathname === '/auth/login') {
4800
4781
  const token = new URL(req.url || '/', 'http://localhost').searchParams.get('token');
4801
4782
  if (token !== null && ctx.loginTokenDir && isLoopbackHost(ctx.serverHost) && consumeLoginToken(ctx.loginTokenDir, token)) {
@@ -4806,7 +4787,7 @@ async function handleWebAuthRoutes(req, res, pathname, ctx, state) {
4806
4787
  });
4807
4788
  return true;
4808
4789
  }
4809
- sendRedirect(res, 302, '/shadcn/auth/login', { 'Referrer-Policy': 'no-referrer' });
4790
+ sendHtml(res, 200, applyServeTitle(loadIndexHtml(), ctx), { 'Referrer-Policy': 'no-referrer' });
4810
4791
  return true;
4811
4792
  }
4812
4793
 
@@ -4869,7 +4850,7 @@ function sendWebUnauthorized(res, pathname) {
4869
4850
  }
4870
4851
  // 其余都是页面请求:一律打回登录页。登录页本身在认证网关之前就被
4871
4852
  // handleWebAuthRoutes 放行,不会在这里形成重定向循环
4872
- sendRedirect(res, 302, '/shadcn/auth/login', { 'Set-Cookie': getWebAuthClearCookie() });
4853
+ sendRedirect(res, 302, '/auth/login', { 'Set-Cookie': getWebAuthClearCookie() });
4873
4854
  }
4874
4855
 
4875
4856
  // 运行日志查看接口的支撑函数。日志目录以 serve 实际使用的 logger.path 为准,
@@ -6446,15 +6427,9 @@ async function startWebServer(options) {
6446
6427
  return;
6447
6428
  }
6448
6429
 
6449
- // shadcn/ui 版前端现在是默认主题,构建产物见 frontend-shadcn/(npm run build:web-shadcn)
6430
+ // 前端构建产物见 frontend/(npm run build:web),输出到 lib/web/index.html
6450
6431
  if (req.method === 'GET' && pathname === '/') {
6451
- sendHtml(res, 200, renderShadcnHtml(ctx));
6452
- return;
6453
- }
6454
-
6455
- // /shadcn 保留为 / 的别名,兼容此前"试验性预览"阶段留下的书签/脚本
6456
- if (req.method === 'GET' && pathname === '/shadcn') {
6457
- sendHtml(res, 200, renderShadcnHtml(ctx));
6432
+ sendHtml(res, 200, renderAppHtml(ctx));
6458
6433
  return;
6459
6434
  }
6460
6435
 
@@ -68,7 +68,7 @@
68
68
  // }
69
69
  // },
70
70
 
71
- // 可选插件(manyoyo playwright / manyoyo plugin playwright)
71
+ // 可选插件(manyoyo playwright)
72
72
  "plugins": {
73
73
  "playwright": {
74
74
  // mixed: 支持容器+宿主机;container: 仅容器;host: 仅宿主机
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "8.0.1",
4
- "imageVersion": "2.0.0-common",
3
+ "version": "8.2.0",
4
+ "imageVersion": "2.1.0-common",
5
5
  "playwrightCliVersion": "0.1.21",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",
7
7
  "keywords": [
@@ -30,17 +30,22 @@
30
30
  "my": "bin/manyoyo.js"
31
31
  },
32
32
  "scripts": {
33
- "dev:release": "node scripts/dev-release.js",
34
- "build:web-shadcn": "node scripts/build-web-shadcn.js",
35
- "dev:web-shadcn": "node scripts/dev-web-shadcn.js",
36
- "test:web-shadcn": "node scripts/test-web-shadcn.js",
37
- "prepack": "npm run build:web-shadcn",
38
- "install-link": "npm link",
39
- "test": "jest --coverage && npm run test:web-shadcn",
40
- "test:unit": "jest test/ --testPathIgnorePatterns '<rootDir>/temp/' '<rootDir>/test/integration/' && npm run test:web-shadcn",
33
+ "start": "node bin/manyoyo.js",
34
+ "serve:dev": "node bin/manyoyo.js serve 127.0.0.1:3000",
35
+ "release": "node scripts/release/index.js",
36
+ "build:release": "node scripts/web.js build-release",
37
+ "build:web": "node scripts/web.js build",
38
+ "dev:web": "node scripts/web.js dev",
39
+ "test:web": "node scripts/web.js test",
40
+ "prepack": "npm run build:web",
41
+ "test": "jest --coverage && npm run test:web",
42
+ "test:unit": "jest test/ --testPathIgnorePatterns '<rootDir>/temp/' '<rootDir>/test/integration/' && npm run test:web",
41
43
  "test:integration": "jest test/integration/",
44
+ "test:installer": "jest test/installer.test.js",
45
+ "lint:sh": "command -v shellcheck >/dev/null 2>&1 || { echo '未安装 shellcheck:macOS 用 brew install shellcheck,Debian/Ubuntu 用 sudo apt install shellcheck,或用 docker run --rm -v \"$PWD:/mnt\" -w /mnt koalaman/shellcheck:stable scripts/install.sh scripts/offline/*.sh' >&2; exit 1; } && shellcheck -s sh -x -e SC1091 scripts/install.sh scripts/offline/*.sh",
42
46
  "docs:dev": "vitepress dev docs --host 127.0.0.1 --port 5173",
43
47
  "docs:build": "vitepress build docs",
48
+ "docs:check": "npm run docs:build && node scripts/check-docs-seo.js",
44
49
  "docs:preview": "vitepress preview docs --host 127.0.0.1 --port 4173"
45
50
  },
46
51
  "homepage": "https://xcanwin.github.io/manyoyo/",
@@ -1,142 +0,0 @@
1
- const { extractAgentMessageFromCodexJsonl } = require('./codex-output');
2
-
3
- function parseReleaseVersion(version) {
4
- const match = String(version || '').trim().match(/^(\d+)\.(\d+)\.(\d+)$/);
5
- if (!match) {
6
- return null;
7
- }
8
- return {
9
- major: Number(match[1]),
10
- minor: Number(match[2]),
11
- patch: Number(match[3])
12
- };
13
- }
14
-
15
- function formatReleaseVersion(parts) {
16
- return `${parts.major}.${parts.minor}.${parts.patch}`;
17
- }
18
-
19
- function compareReleaseVersions(left, right) {
20
- const a = typeof left === 'string' ? parseReleaseVersion(left) : left;
21
- const b = typeof right === 'string' ? parseReleaseVersion(right) : right;
22
- if (!a || !b) {
23
- return 0;
24
- }
25
- if (a.major !== b.major) return a.major - b.major;
26
- if (a.minor !== b.minor) return a.minor - b.minor;
27
- return a.patch - b.patch;
28
- }
29
-
30
- function buildVersionSuggestions(version) {
31
- const parsed = parseReleaseVersion(version);
32
- if (!parsed) {
33
- return [];
34
- }
35
- return [
36
- {
37
- key: 'major',
38
- label: '第1段 +1 (major)',
39
- version: formatReleaseVersion({
40
- major: parsed.major + 1,
41
- minor: 0,
42
- patch: 0
43
- }),
44
- recommended: false
45
- },
46
- {
47
- key: 'minor',
48
- label: '第2段 +1 (minor)',
49
- version: formatReleaseVersion({
50
- major: parsed.major,
51
- minor: parsed.minor + 1,
52
- patch: 0
53
- }),
54
- recommended: false
55
- },
56
- {
57
- key: 'patch',
58
- label: '第3段 +1 (patch)',
59
- version: formatReleaseVersion({
60
- major: parsed.major,
61
- minor: parsed.minor,
62
- patch: parsed.patch + 1
63
- }),
64
- recommended: true
65
- }
66
- ];
67
- }
68
-
69
- function findRecommendedChoiceIndex(options, fallbackIndex = 0) {
70
- const list = Array.isArray(options) ? options : [];
71
- const matchedIndex = list.findIndex(option => option && option.recommended === true);
72
- return matchedIndex >= 0 ? matchedIndex : fallbackIndex;
73
- }
74
-
75
- function pickLatestVersionTag(tags) {
76
- let latest = null;
77
- for (const rawTag of (tags || [])) {
78
- const tag = String(rawTag || '').trim();
79
- if (!tag) {
80
- continue;
81
- }
82
- const normalized = tag.startsWith('v') ? tag.slice(1) : tag;
83
- const parsed = parseReleaseVersion(normalized);
84
- if (!parsed) {
85
- continue;
86
- }
87
- if (!latest || compareReleaseVersions(parsed, latest.parsed) > 0) {
88
- latest = {
89
- tag,
90
- version: normalized,
91
- parsed
92
- };
93
- }
94
- }
95
- return latest ? { tag: latest.tag, version: latest.version } : null;
96
- }
97
-
98
- function normalizeCommitMessage(text) {
99
- const lines = String(text || '').replace(/\r/g, '').split('\n');
100
- if (!lines.length) {
101
- return '';
102
- }
103
-
104
- let start = 0;
105
- let end = lines.length;
106
-
107
- while (start < end && lines[start].trim() === '') {
108
- start += 1;
109
- }
110
- while (end > start && lines[end - 1].trim() === '') {
111
- end -= 1;
112
- }
113
-
114
- if (start < end && lines[start].trim().startsWith('```')) {
115
- start += 1;
116
- while (start < end && lines[start].trim() === '') {
117
- start += 1;
118
- }
119
- if (end > start && lines[end - 1].trim() === '```') {
120
- end -= 1;
121
- }
122
- }
123
-
124
- while (start < end && lines[start].trim() === '') {
125
- start += 1;
126
- }
127
- while (end > start && lines[end - 1].trim() === '') {
128
- end -= 1;
129
- }
130
-
131
- return lines.slice(start, end).join('\n').trim();
132
- }
133
-
134
- module.exports = {
135
- parseReleaseVersion,
136
- compareReleaseVersions,
137
- buildVersionSuggestions,
138
- findRecommendedChoiceIndex,
139
- pickLatestVersionTag,
140
- normalizeCommitMessage,
141
- extractAgentMessageFromCodexJsonl
142
- };