@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/README.md +31 -177
- package/bin/manyoyo.js +149 -182
- package/lib/app-update.js +31 -10
- package/lib/podman-passthrough.js +4 -4
- package/lib/web/AGENTS.md +4 -6
- package/lib/web/{frontend/shadcn.html → index.html} +74 -74
- package/lib/web/server.js +14 -39
- package/manyoyo.example.json +1 -1
- package/package.json +15 -10
- package/lib/dev-release.js +0 -142
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
|
|
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
|
|
4491
|
-
|
|
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(
|
|
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
|
|
4526
|
-
//
|
|
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(
|
|
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
|
-
// 登录页:与 /
|
|
4791
|
-
//
|
|
4792
|
-
|
|
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
|
-
|
|
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, '/
|
|
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
|
-
//
|
|
6430
|
+
// 前端构建产物见 frontend/(npm run build:web),输出到 lib/web/index.html
|
|
6450
6431
|
if (req.method === 'GET' && pathname === '/') {
|
|
6451
|
-
sendHtml(res, 200,
|
|
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
|
|
package/manyoyo.example.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcanwin/manyoyo",
|
|
3
|
-
"version": "8.0
|
|
4
|
-
"imageVersion": "2.
|
|
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
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"test": "
|
|
40
|
-
"
|
|
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/",
|
package/lib/dev-release.js
DELETED
|
@@ -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
|
-
};
|