@xcanwin/manyoyo 7.0.26 → 7.1.2
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/AGENTS.md +6 -8
- package/lib/web/frontend/shadcn.html +91 -88
- package/lib/web/server.js +16 -171
- package/package.json +2 -21
- package/lib/web/frontend/app.css +0 -2269
- package/lib/web/frontend/app.html +0 -394
- package/lib/web/frontend/app.js +0 -5352
- package/lib/web/frontend/chat-behavior.js +0 -107
- package/lib/web/frontend/codemirror-entry.js +0 -111
- package/lib/web/frontend/codemirror.bundle.js +0 -31
- package/lib/web/frontend/file-browser.js +0 -800
- package/lib/web/frontend/login.css +0 -208
- package/lib/web/frontend/login.html +0 -25
- package/lib/web/frontend/login.js +0 -74
- package/lib/web/frontend/markdown-renderer.js +0 -316
- package/lib/web/frontend/markdown.css +0 -84
package/lib/web/server.js
CHANGED
|
@@ -102,36 +102,6 @@ const DEFAULT_WEB_CONFIG_TEMPLATE = `{
|
|
|
102
102
|
}
|
|
103
103
|
`;
|
|
104
104
|
|
|
105
|
-
let XTERM_JS_FILE = null;
|
|
106
|
-
let XTERM_CSS_FILE = null;
|
|
107
|
-
let XTERM_ADDON_FIT_JS_FILE = null;
|
|
108
|
-
let MARKED_MIN_JS_FILE = null;
|
|
109
|
-
try {
|
|
110
|
-
const xtermPackageDir = path.dirname(require.resolve('@xterm/xterm/package.json'));
|
|
111
|
-
XTERM_JS_FILE = path.join(xtermPackageDir, 'lib', 'xterm.js');
|
|
112
|
-
XTERM_CSS_FILE = path.join(xtermPackageDir, 'css', 'xterm.css');
|
|
113
|
-
} catch (e) {
|
|
114
|
-
XTERM_JS_FILE = null;
|
|
115
|
-
XTERM_CSS_FILE = null;
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
const xtermAddonFitPackageDir = path.dirname(require.resolve('@xterm/addon-fit/package.json'));
|
|
119
|
-
XTERM_ADDON_FIT_JS_FILE = path.join(xtermAddonFitPackageDir, 'lib', 'addon-fit.js');
|
|
120
|
-
} catch (e) {
|
|
121
|
-
XTERM_ADDON_FIT_JS_FILE = null;
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
const markedPackageDir = path.dirname(require.resolve('marked/package.json'));
|
|
125
|
-
MARKED_MIN_JS_FILE = path.join(markedPackageDir, 'lib', 'marked.umd.js');
|
|
126
|
-
} catch (e) {
|
|
127
|
-
MARKED_MIN_JS_FILE = null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const MIME_TYPES = {
|
|
131
|
-
'.css': 'text/css; charset=utf-8',
|
|
132
|
-
'.js': 'application/javascript; charset=utf-8',
|
|
133
|
-
'.html': 'text/html; charset=utf-8'
|
|
134
|
-
};
|
|
135
105
|
const FILE_LANGUAGE_MAP = {
|
|
136
106
|
'.cjs': 'javascript',
|
|
137
107
|
'.css': 'css',
|
|
@@ -4189,49 +4159,6 @@ function resolveStaticAsset(name) {
|
|
|
4189
4159
|
return fs.existsSync(fullPath) ? fullPath : null;
|
|
4190
4160
|
}
|
|
4191
4161
|
|
|
4192
|
-
function resolveVendorAsset(name) {
|
|
4193
|
-
if (!isSafeStaticAssetName(name)) {
|
|
4194
|
-
return null;
|
|
4195
|
-
}
|
|
4196
|
-
if (name === 'xterm.js') {
|
|
4197
|
-
return XTERM_JS_FILE && fs.existsSync(XTERM_JS_FILE) ? XTERM_JS_FILE : null;
|
|
4198
|
-
}
|
|
4199
|
-
if (name === 'xterm.css') {
|
|
4200
|
-
return XTERM_CSS_FILE && fs.existsSync(XTERM_CSS_FILE) ? XTERM_CSS_FILE : null;
|
|
4201
|
-
}
|
|
4202
|
-
if (name === 'xterm-addon-fit.js') {
|
|
4203
|
-
return XTERM_ADDON_FIT_JS_FILE && fs.existsSync(XTERM_ADDON_FIT_JS_FILE) ? XTERM_ADDON_FIT_JS_FILE : null;
|
|
4204
|
-
}
|
|
4205
|
-
if (name === 'marked.min.js') {
|
|
4206
|
-
return MARKED_MIN_JS_FILE && fs.existsSync(MARKED_MIN_JS_FILE) ? MARKED_MIN_JS_FILE : null;
|
|
4207
|
-
}
|
|
4208
|
-
return null;
|
|
4209
|
-
}
|
|
4210
|
-
|
|
4211
|
-
function sendFileAsset(res, filePath) {
|
|
4212
|
-
if (!filePath || !fs.existsSync(filePath)) {
|
|
4213
|
-
sendHtml(res, 404, '<h1>404 Not Found</h1>');
|
|
4214
|
-
return;
|
|
4215
|
-
}
|
|
4216
|
-
|
|
4217
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
4218
|
-
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
4219
|
-
const content = fs.readFileSync(filePath);
|
|
4220
|
-
res.writeHead(200, {
|
|
4221
|
-
'Content-Type': contentType,
|
|
4222
|
-
'Cache-Control': 'no-store'
|
|
4223
|
-
});
|
|
4224
|
-
res.end(content);
|
|
4225
|
-
}
|
|
4226
|
-
|
|
4227
|
-
function sendStaticAsset(res, assetName) {
|
|
4228
|
-
sendFileAsset(res, resolveStaticAsset(assetName));
|
|
4229
|
-
}
|
|
4230
|
-
|
|
4231
|
-
function sendVendorAsset(res, assetName) {
|
|
4232
|
-
sendFileAsset(res, resolveVendorAsset(assetName));
|
|
4233
|
-
}
|
|
4234
|
-
|
|
4235
4162
|
function loadTemplate(name) {
|
|
4236
4163
|
const filePath = resolveStaticAsset(name);
|
|
4237
4164
|
if (!filePath) {
|
|
@@ -4247,42 +4174,20 @@ function escapeHtmlText(value) {
|
|
|
4247
4174
|
.replace(/>/g, '>');
|
|
4248
4175
|
}
|
|
4249
4176
|
|
|
4250
|
-
// JSON.stringify 不转义 "</script>",且作为 String.replace 的第二个参数时 $&/$'/$` 等会被
|
|
4251
|
-
// 当成特殊替换模式重新解释;用替换函数 + 转义 "<" 双重防护,避免 serveTitle 里的内容
|
|
4252
|
-
// 提前闭合 <script> 标签或拼接进原始 HTML。
|
|
4253
|
-
function jsonForInlineScript(value) {
|
|
4254
|
-
return JSON.stringify(value).replace(/</g, '\\u003c');
|
|
4255
|
-
}
|
|
4256
|
-
|
|
4257
4177
|
function applyServeTitle(html, ctx) {
|
|
4258
4178
|
if (typeof ctx.serveTitle !== 'string') {
|
|
4259
4179
|
return html;
|
|
4260
4180
|
}
|
|
4261
|
-
// 正则匹配任意 <title> 内容而非硬编码 "MANYOYO Web"
|
|
4181
|
+
// 正则匹配任意 <title> 内容而非硬编码 "MANYOYO Web",前端换了默认标题也不用改这里
|
|
4262
4182
|
return html.replace(/<title>[^<]*<\/title>/, () => `<title>${escapeHtmlText(ctx.serveTitle)}</title>`);
|
|
4263
4183
|
}
|
|
4264
4184
|
|
|
4265
|
-
function injectServeTitleFlag(html, ctx) {
|
|
4266
|
-
if (typeof ctx.serveTitle !== 'string') {
|
|
4267
|
-
return html;
|
|
4268
|
-
}
|
|
4269
|
-
// 告知 app.js 跳过按会话动态改写 document.title
|
|
4270
|
-
return html.replace(
|
|
4271
|
-
'</head>',
|
|
4272
|
-
() => ` <script>window.__MANYOYO_SERVE_TITLE__ = ${jsonForInlineScript(ctx.serveTitle)};</script>\n</head>`
|
|
4273
|
-
);
|
|
4274
|
-
}
|
|
4275
|
-
|
|
4276
|
-
function renderIndexHtml(ctx) {
|
|
4277
|
-
return injectServeTitleFlag(applyServeTitle(loadTemplate('app.html'), ctx), ctx);
|
|
4278
|
-
}
|
|
4279
|
-
|
|
4280
4185
|
function renderShadcnHtml(ctx) {
|
|
4281
4186
|
// shadcn.html 是 vite singlefile 打包产物,全部依赖内联成一个 <script>,
|
|
4282
|
-
// 用 .replace('</head>', ...)
|
|
4283
|
-
// "</head>"
|
|
4284
|
-
//
|
|
4285
|
-
//
|
|
4187
|
+
// 用 .replace('</head>', ...) 注入标记有极小概率命中某个库源码里字面量出现的
|
|
4188
|
+
// "</head>",把注入内容插进内联脚本中间导致整页解析错乱;所以改成跟
|
|
4189
|
+
// applyServeTitle 同款更窄、更安全的 <title> 定位,插一个 meta 标记
|
|
4190
|
+
// 告知前端跳过动态改写 document.title
|
|
4286
4191
|
let html = applyServeTitle(loadTemplate('shadcn.html'), ctx);
|
|
4287
4192
|
if (typeof ctx.serveTitle === 'string') {
|
|
4288
4193
|
html = html.replace(
|
|
@@ -4293,10 +4198,6 @@ function renderShadcnHtml(ctx) {
|
|
|
4293
4198
|
return html;
|
|
4294
4199
|
}
|
|
4295
4200
|
|
|
4296
|
-
function renderLoginHtml(ctx) {
|
|
4297
|
-
return applyServeTitle(loadTemplate('login.html'), ctx);
|
|
4298
|
-
}
|
|
4299
|
-
|
|
4300
4201
|
function toPositiveInt(value, fallback) {
|
|
4301
4202
|
const parsed = Number.parseInt(value, 10);
|
|
4302
4203
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
@@ -4521,27 +4422,16 @@ async function handleWebAuthRoutes(req, res, pathname, ctx, state) {
|
|
|
4521
4422
|
return true;
|
|
4522
4423
|
}
|
|
4523
4424
|
|
|
4524
|
-
//
|
|
4525
|
-
|
|
4526
|
-
sendHtml(res, 200, renderLoginHtml(ctx));
|
|
4527
|
-
return true;
|
|
4528
|
-
}
|
|
4529
|
-
|
|
4530
|
-
// 默认主题(shadcn/ui 版)的登录页:与 / 、/shadcn 复用同一份构建产物,
|
|
4531
|
-
// 由前端按 pathname 自行渲染登录表单,登录逻辑仍复用 /auth/login 接口
|
|
4425
|
+
// 登录页:与 / 、/shadcn 复用同一份构建产物,由前端按 pathname 自行渲染
|
|
4426
|
+
// 登录表单,登录逻辑走下面的 POST /auth/login 接口
|
|
4532
4427
|
if (req.method === 'GET' && pathname === '/shadcn/auth/login') {
|
|
4533
4428
|
sendHtml(res, 200, applyServeTitle(loadTemplate('shadcn.html'), ctx));
|
|
4534
4429
|
return true;
|
|
4535
4430
|
}
|
|
4536
4431
|
|
|
4537
|
-
|
|
4538
|
-
if (req.method === 'GET' &&
|
|
4539
|
-
|
|
4540
|
-
if (!(assetName === 'login.css' || assetName === 'login.js')) {
|
|
4541
|
-
sendHtml(res, 404, '<h1>404 Not Found</h1>');
|
|
4542
|
-
return true;
|
|
4543
|
-
}
|
|
4544
|
-
sendStaticAsset(res, assetName);
|
|
4432
|
+
// GET /auth/login 曾经是独立的登录页面,现在只留一个跳转,接住老书签
|
|
4433
|
+
if (req.method === 'GET' && pathname === '/auth/login') {
|
|
4434
|
+
sendRedirect(res, 302, '/shadcn/auth/login');
|
|
4545
4435
|
return true;
|
|
4546
4436
|
}
|
|
4547
4437
|
|
|
@@ -4586,25 +4476,14 @@ async function handleWebAuthRoutes(req, res, pathname, ctx, state) {
|
|
|
4586
4476
|
return false;
|
|
4587
4477
|
}
|
|
4588
4478
|
|
|
4589
|
-
function sendWebUnauthorized(res, pathname
|
|
4479
|
+
function sendWebUnauthorized(res, pathname) {
|
|
4590
4480
|
if (pathname.startsWith('/api/') || pathname.startsWith('/auth/')) {
|
|
4591
4481
|
sendJson(res, 401, { error: 'UNAUTHORIZED' });
|
|
4592
4482
|
return;
|
|
4593
4483
|
}
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
}
|
|
4598
|
-
if (pathname === '/legacy') {
|
|
4599
|
-
sendRedirect(res, 302, '/auth/login', { 'Set-Cookie': getWebAuthClearCookie() });
|
|
4600
|
-
return;
|
|
4601
|
-
}
|
|
4602
|
-
sendHtml(
|
|
4603
|
-
res,
|
|
4604
|
-
401,
|
|
4605
|
-
renderLoginHtml(ctx),
|
|
4606
|
-
{ 'Set-Cookie': getWebAuthClearCookie() }
|
|
4607
|
-
);
|
|
4484
|
+
// 其余都是页面请求:一律打回登录页。登录页本身在认证网关之前就被
|
|
4485
|
+
// handleWebAuthRoutes 放行,不会在这里形成重定向循环
|
|
4486
|
+
sendRedirect(res, 302, '/shadcn/auth/login', { 'Set-Cookie': getWebAuthClearCookie() });
|
|
4608
4487
|
}
|
|
4609
4488
|
|
|
4610
4489
|
// 运行日志查看接口的支撑函数。日志目录以 serve 实际使用的 logger.path 为准,
|
|
@@ -5945,7 +5824,7 @@ async function startWebServer(options) {
|
|
|
5945
5824
|
|
|
5946
5825
|
const authSession = getWebAuthSession(state, req);
|
|
5947
5826
|
if (!authSession) {
|
|
5948
|
-
sendWebUnauthorized(res, pathname
|
|
5827
|
+
sendWebUnauthorized(res, pathname);
|
|
5949
5828
|
return;
|
|
5950
5829
|
}
|
|
5951
5830
|
|
|
@@ -5961,40 +5840,6 @@ async function startWebServer(options) {
|
|
|
5961
5840
|
return;
|
|
5962
5841
|
}
|
|
5963
5842
|
|
|
5964
|
-
// 旧版默认主题:不再是默认路由,移到 /legacy 继续保留一段时间作为兜底
|
|
5965
|
-
if (req.method === 'GET' && pathname === '/legacy') {
|
|
5966
|
-
sendHtml(res, 200, renderIndexHtml(ctx));
|
|
5967
|
-
return;
|
|
5968
|
-
}
|
|
5969
|
-
|
|
5970
|
-
const appFrontendMatch = pathname.match(/^\/app\/frontend\/([A-Za-z0-9._-]+)$/);
|
|
5971
|
-
if (req.method === 'GET' && appFrontendMatch) {
|
|
5972
|
-
const assetName = appFrontendMatch[1];
|
|
5973
|
-
if (!(assetName === 'app.css'
|
|
5974
|
-
|| assetName === 'app.js'
|
|
5975
|
-
|| assetName === 'markdown.css'
|
|
5976
|
-
|| assetName === 'markdown-renderer.js'
|
|
5977
|
-
|| assetName === 'chat-behavior.js'
|
|
5978
|
-
|| assetName === 'file-browser.js'
|
|
5979
|
-
|| assetName === 'codemirror.bundle.js')) {
|
|
5980
|
-
sendHtml(res, 404, '<h1>404 Not Found</h1>');
|
|
5981
|
-
return;
|
|
5982
|
-
}
|
|
5983
|
-
sendStaticAsset(res, assetName);
|
|
5984
|
-
return;
|
|
5985
|
-
}
|
|
5986
|
-
|
|
5987
|
-
const appVendorMatch = pathname.match(/^\/app\/vendor\/([A-Za-z0-9._-]+)$/);
|
|
5988
|
-
if (req.method === 'GET' && appVendorMatch) {
|
|
5989
|
-
const assetName = appVendorMatch[1];
|
|
5990
|
-
if (!(assetName === 'xterm.css' || assetName === 'xterm.js' || assetName === 'xterm-addon-fit.js' || assetName === 'marked.min.js')) {
|
|
5991
|
-
sendHtml(res, 404, '<h1>404 Not Found</h1>');
|
|
5992
|
-
return;
|
|
5993
|
-
}
|
|
5994
|
-
sendVendorAsset(res, assetName);
|
|
5995
|
-
return;
|
|
5996
|
-
}
|
|
5997
|
-
|
|
5998
5843
|
if (pathname === '/healthz') {
|
|
5999
5844
|
sendJson(res, 200, { ok: true });
|
|
6000
5845
|
return;
|
|
@@ -6130,7 +5975,7 @@ async function startWebServer(options) {
|
|
|
6130
5975
|
const { GREEN, CYAN, YELLOW, NC } = ctx.colors;
|
|
6131
5976
|
const listenHost = formatUrlHost(ctx.serverHost);
|
|
6132
5977
|
console.log(`${GREEN}✅ MANYOYO Web 服务已启动: http://${listenHost}:${listenPort}${NC}`);
|
|
6133
|
-
console.log(`${CYAN}提示: 左侧是 manyoyo
|
|
5978
|
+
console.log(`${CYAN}提示: 左侧是 manyoyo 容器与 AGENT 列表,右侧是聊天/终端/文件/详情/配置/检查工作台。${NC}`);
|
|
6134
5979
|
if (ctx.serverHost === '0.0.0.0') {
|
|
6135
5980
|
console.log(`${CYAN}提示: 当前监听全部网卡,请用本机局域网 IP 访问。${NC}`);
|
|
6136
5981
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcanwin/manyoyo",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.2",
|
|
4
4
|
"imageVersion": "1.9.1-common",
|
|
5
5
|
"playwrightCliVersion": "0.1.18",
|
|
6
6
|
"description": "AI Agent CLI Security Sandbox for Docker and Podman",
|
|
@@ -31,12 +31,10 @@
|
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"dev:release": "node scripts/dev-release.js",
|
|
34
|
-
"build:web-editor": "node scripts/build-web-code-editor.js",
|
|
35
34
|
"build:web-shadcn": "node scripts/build-web-shadcn.js",
|
|
36
35
|
"dev:web-shadcn": "node scripts/dev-web-shadcn.js",
|
|
37
36
|
"test:web-shadcn": "node scripts/test-web-shadcn.js",
|
|
38
|
-
"prepack": "npm run build:web-
|
|
39
|
-
"prepare": "npm run build:web-editor",
|
|
37
|
+
"prepack": "npm run build:web-shadcn",
|
|
40
38
|
"install-link": "npm link",
|
|
41
39
|
"test": "jest --coverage && npm run test:web-shadcn",
|
|
42
40
|
"test:unit": "jest test/ && npm run test:web-shadcn",
|
|
@@ -63,29 +61,12 @@
|
|
|
63
61
|
],
|
|
64
62
|
"dependencies": {
|
|
65
63
|
"@playwright/mcp": "0.0.79",
|
|
66
|
-
"@xterm/addon-fit": "^0.11.0",
|
|
67
|
-
"@xterm/xterm": "^6.0.0",
|
|
68
64
|
"commander": "^15.0.0",
|
|
69
65
|
"json5": "^2.2.3",
|
|
70
|
-
"marked": "^18.0.9",
|
|
71
66
|
"playwright": "1.62.1",
|
|
72
67
|
"ws": "^8.21.3"
|
|
73
68
|
},
|
|
74
69
|
"devDependencies": {
|
|
75
|
-
"@codemirror/commands": "^6.10.4",
|
|
76
|
-
"@codemirror/lang-css": "^6.3.1",
|
|
77
|
-
"@codemirror/lang-html": "^6.4.12",
|
|
78
|
-
"@codemirror/lang-javascript": "^6.2.5",
|
|
79
|
-
"@codemirror/lang-json": "^6.0.2",
|
|
80
|
-
"@codemirror/lang-markdown": "^6.5.2",
|
|
81
|
-
"@codemirror/lang-python": "^6.2.1",
|
|
82
|
-
"@codemirror/lang-yaml": "^6.1.3",
|
|
83
|
-
"@codemirror/language": "^6.12.4",
|
|
84
|
-
"@codemirror/search": "^6.7.1",
|
|
85
|
-
"@codemirror/state": "^6.7.1",
|
|
86
|
-
"@codemirror/view": "^6.43.8",
|
|
87
|
-
"codemirror": "^6.0.2",
|
|
88
|
-
"esbuild": "^0.28.2",
|
|
89
70
|
"jest": "^30.4.2",
|
|
90
71
|
"vitepress": "^1.6.4"
|
|
91
72
|
},
|