@xcanwin/manyoyo 6.2.1 → 6.2.4

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/bin/manyoyo.js CHANGED
@@ -90,6 +90,7 @@ let SERVER_PORT = 3000;
90
90
  let SERVER_AUTH_USER = "";
91
91
  let SERVER_AUTH_PASS = "";
92
92
  let SERVER_AUTH_PASS_AUTO = false;
93
+ let SERVER_TITLE = null;
93
94
  const SAFE_CONTAINER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
94
95
 
95
96
  // Color definitions using ANSI codes
@@ -306,6 +307,7 @@ function installServeProcessDiagnostics(logger) {
306
307
  * @property {{shellPrefix?:string,shell?:string,shellSuffix?:string,env?:Object.<string,string|number|boolean>,envFile?:string[]}} [first] - 仅首次创建容器执行的一次性命令配置
307
308
  * @property {string[]} [volumes] - 挂载卷数组
308
309
  * @property {Object.<string, Object>} [plugins] - 可选插件配置映射(如 plugins.playwright)
310
+ * @property {{title?:string}} [serve] - serve 命令专属配置(如自定义网页标题,留空字符串则显示为空)
309
311
  * @property {Object.<string, Object>} [runs] - 运行配置映射(-r <name>)
310
312
  * @property {string} [yolo] - YOLO 模式
311
313
  * @property {string} [containerMode] - 容器模式
@@ -1461,6 +1463,7 @@ Notes:
1461
1463
  SERVER_AUTH_USER = resolvedRuntime.serverUser || '';
1462
1464
  SERVER_AUTH_PASS = resolvedRuntime.serverPass || '';
1463
1465
  SERVER_AUTH_PASS_AUTO = Boolean(resolvedRuntime.serverPassAuto);
1466
+ SERVER_TITLE = resolvedRuntime.serveTitle;
1464
1467
 
1465
1468
  if (isShowConfigMode) {
1466
1469
  const finalConfig = {
@@ -1489,6 +1492,9 @@ Notes:
1489
1492
  serverPort: isServerMode ? SERVER_PORT : null,
1490
1493
  serverUser: SERVER_AUTH_USER || "",
1491
1494
  serverPass: SERVER_AUTH_PASS || "",
1495
+ serve: {
1496
+ title: SERVER_TITLE
1497
+ },
1492
1498
  exec: {
1493
1499
  prefix: EXEC_COMMAND_PREFIX,
1494
1500
  shell: EXEC_COMMAND,
@@ -1590,6 +1596,7 @@ function createRuntimeContext(modeState = {}) {
1590
1596
  serverAuthUser: SERVER_AUTH_USER,
1591
1597
  serverAuthPass: SERVER_AUTH_PASS,
1592
1598
  serverAuthPassAuto: SERVER_AUTH_PASS_AUTO,
1599
+ serveTitle: SERVER_TITLE,
1593
1600
  logger: null
1594
1601
  };
1595
1602
  }
@@ -2086,6 +2093,7 @@ async function runWebServerMode(runtime) {
2086
2093
  authUser: runtime.serverAuthUser,
2087
2094
  authPass: runtime.serverAuthPass,
2088
2095
  authPassAuto: runtime.serverAuthPassAuto,
2096
+ serveTitle: runtime.serveTitle,
2089
2097
  dockerCmd: DOCKER_CMD,
2090
2098
  hostPath: runtime.hostPath,
2091
2099
  containerPath: runtime.containerPath,
@@ -148,6 +148,15 @@ function resolveRuntimeConfig(options = {}) {
148
148
  }
149
149
  }
150
150
 
151
+ let serveTitle = null;
152
+ const globalServeConfig = globalConfig.serve && typeof globalConfig.serve === 'object' ? globalConfig.serve : null;
153
+ const runServeConfig = runConfig.serve && typeof runConfig.serve === 'object' ? runConfig.serve : null;
154
+ if (runServeConfig && Object.prototype.hasOwnProperty.call(runServeConfig, 'title')) {
155
+ serveTitle = String(runServeConfig.title);
156
+ } else if (globalServeConfig && Object.prototype.hasOwnProperty.call(globalServeConfig, 'title')) {
157
+ serveTitle = String(globalServeConfig.title);
158
+ }
159
+
151
160
  if (!hostPath) {
152
161
  hostPath = defaults.hostPath;
153
162
  }
@@ -197,6 +206,7 @@ function resolveRuntimeConfig(options = {}) {
197
206
  serverUser,
198
207
  serverPass,
199
208
  serverPassAuto,
209
+ serveTitle,
200
210
  exec: {
201
211
  prefix: execPrefix,
202
212
  shell: execShell,
@@ -13,7 +13,6 @@
13
13
 
14
14
  --line: #d9c7ad;
15
15
  --line-strong: #b59263;
16
- --line-medium: rgba(181, 146, 99, 0.4);
17
16
  --tree-guide: rgba(181, 146, 99, 0.44);
18
17
  --tree-guide-soft: rgba(181, 146, 99, 0.44);
19
18
  --tree-active: #ecdfc8;
@@ -118,7 +117,7 @@ body::after {
118
117
 
119
118
  .sidebar {
120
119
  min-height: 0;
121
- padding: 16px;
120
+ padding: 16px 12px 8px;
122
121
  display: flex;
123
122
  flex-direction: column;
124
123
  gap: 12px;
@@ -151,6 +150,7 @@ body::after {
151
150
  font-size: 22px;
152
151
  line-height: 1;
153
152
  text-transform: uppercase;
153
+ white-space: nowrap;
154
154
  }
155
155
 
156
156
  .sidebar-actions {
@@ -166,6 +166,16 @@ body::after {
166
166
  min-width: 62px;
167
167
  }
168
168
 
169
+ .sidebar-create button,
170
+ .sidebar-bottom button {
171
+ width: 100%;
172
+ }
173
+
174
+ .sidebar-bottom {
175
+ display: flex;
176
+ margin-top: -4px;
177
+ }
178
+
169
179
  input:focus,
170
180
  textarea:focus {
171
181
  outline: none;
@@ -898,7 +908,7 @@ textarea:focus-visible {
898
908
 
899
909
  .main {
900
910
  min-height: 0;
901
- padding: 14px;
911
+ padding: 8px 14px;
902
912
  display: grid;
903
913
  grid-template-rows: auto minmax(0, 1fr) auto;
904
914
  gap: 0;
@@ -914,8 +924,7 @@ textarea:focus-visible {
914
924
  justify-content: space-between;
915
925
  align-items: flex-start;
916
926
  gap: 12px;
917
- padding: 6px 8px 12px;
918
- border-bottom: 1px solid var(--line-medium);
927
+ padding: 0 8px 4px;
919
928
  }
920
929
 
921
930
  .header-main {
@@ -995,7 +1004,7 @@ body.workspace-switcher-open .workspace-switcher-panel {
995
1004
  .workspace-shell {
996
1005
  min-height: 0;
997
1006
  height: 100%;
998
- padding-top: 10px;
1007
+ padding-top: 6px;
999
1008
  display: block;
1000
1009
  overflow: hidden;
1001
1010
  }
@@ -1871,8 +1880,8 @@ details.trace-card > .trace-card-summary {
1871
1880
  }
1872
1881
 
1873
1882
  .composer {
1874
- margin-top: 12px;
1875
- padding: 12px 8px 4px;
1883
+ margin-top: 4px;
1884
+ padding: 4px 8px 0;
1876
1885
  border-radius: 14px;
1877
1886
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.32) 0%, rgba(255, 249, 240, 0.78) 100%);
1878
1887
  }
@@ -2116,7 +2125,6 @@ body.composer-options-open .composer-options-panel {
2116
2125
 
2117
2126
  .header {
2118
2127
  padding: 4px 12px;
2119
- border-bottom: none;
2120
2128
  }
2121
2129
 
2122
2130
  .workbench-tabs {
@@ -18,10 +18,6 @@
18
18
  <div class="brand-wrap">
19
19
  <div class="brand">MANYOYO Web</div>
20
20
  </div>
21
- <div class="sidebar-actions">
22
- <button type="button" id="openConfigBtn" class="secondary">配置</button>
23
- <button type="button" id="openCreateBtn">新建</button>
24
- </div>
25
21
  <button
26
22
  type="button"
27
23
  id="mobileSidebarClose"
@@ -29,11 +25,17 @@
29
25
  aria-label="关闭会话列表"
30
26
  >关闭</button>
31
27
  </div>
28
+ <div class="sidebar-create">
29
+ <button type="button" id="openCreateBtn">新建容器</button>
30
+ </div>
32
31
  <div class="session-head">
33
32
  <span>工作台</span>
34
33
  <span id="sessionCount">0 个</span>
35
34
  </div>
36
35
  <div id="sessionList" role="tree" aria-label="会话树"></div>
36
+ <div class="sidebar-bottom">
37
+ <button type="button" id="openConfigBtn" class="secondary">系统设置</button>
38
+ </div>
37
39
  </aside>
38
40
  <main class="main">
39
41
  <header class="header">
@@ -2246,12 +2246,15 @@
2246
2246
  }
2247
2247
 
2248
2248
  function syncUi() {
2249
+ const useStaticTitle = typeof window.__MANYOYO_SERVE_TITLE__ === 'string';
2249
2250
  if (!state.active) {
2250
- document.title = window.ManyoyoChatBehavior.buildDocumentTitle('');
2251
+ if (!useStaticTitle) {
2252
+ document.title = window.ManyoyoChatBehavior.buildDocumentTitle('');
2253
+ }
2251
2254
  if (isComposerMode()) {
2252
2255
  commandInput.value = '';
2253
2256
  }
2254
- } else {
2257
+ } else if (!useStaticTitle) {
2255
2258
  const activeSession = getActiveSession();
2256
2259
  document.title = window.ManyoyoChatBehavior.buildDocumentTitle(
2257
2260
  activeSession && activeSession.agentName ? activeSession.agentName : state.active
@@ -4479,6 +4482,11 @@
4479
4482
  return;
4480
4483
  }
4481
4484
 
4485
+ // 窄屏(手机端):Enter 始终换行,发送需点击发送按钮
4486
+ if (isMobileLayout()) {
4487
+ return;
4488
+ }
4489
+
4482
4490
  // Shift+Enter / Option(Alt)+Enter: 换行
4483
4491
  if (event.shiftKey || event.altKey) {
4484
4492
  return;
@@ -6,14 +6,13 @@
6
6
  name="viewport"
7
7
  content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
8
8
  />
9
- <title>MANYOYO Web Login</title>
9
+ <title>MANYOYO Web</title>
10
10
  <link rel="stylesheet" href="/auth/frontend/login.css" />
11
11
  </head>
12
12
  <body>
13
13
  <form class="card" id="loginForm">
14
14
  <div class="badge">MANYOYO</div>
15
15
  <h1>Web 登录</h1>
16
- <p>登录后可访问容器会话与对话管理。</p>
17
16
  <label for="username">用户名</label>
18
17
  <input id="username" name="username" autocomplete="username" required />
19
18
  <label for="password">密码</label>
package/lib/web/server.js CHANGED
@@ -3458,6 +3458,44 @@ function loadTemplate(name) {
3458
3458
  return fs.readFileSync(filePath, 'utf-8');
3459
3459
  }
3460
3460
 
3461
+ function escapeHtmlText(value) {
3462
+ return String(value == null ? '' : value)
3463
+ .replace(/&/g, '&amp;')
3464
+ .replace(/</g, '&lt;')
3465
+ .replace(/>/g, '&gt;');
3466
+ }
3467
+
3468
+ // JSON.stringify 不转义 "</script>",且作为 String.replace 的第二个参数时 $&/$'/$` 等会被
3469
+ // 当成特殊替换模式重新解释;用替换函数 + 转义 "<" 双重防护,避免 serveTitle 里的内容
3470
+ // 提前闭合 <script> 标签或拼接进原始 HTML。
3471
+ function jsonForInlineScript(value) {
3472
+ return JSON.stringify(value).replace(/</g, '\\u003c');
3473
+ }
3474
+
3475
+ function applyServeTitle(html, ctx) {
3476
+ if (typeof ctx.serveTitle !== 'string') {
3477
+ return html;
3478
+ }
3479
+ // serveTitle 已配置(含空字符串):固定 <title>,覆盖模板默认的 MANYOYO Web
3480
+ return html.replace('<title>MANYOYO Web</title>', () => `<title>${escapeHtmlText(ctx.serveTitle)}</title>`);
3481
+ }
3482
+
3483
+ function renderIndexHtml(ctx) {
3484
+ let html = applyServeTitle(loadTemplate('app.html'), ctx);
3485
+ if (typeof ctx.serveTitle === 'string') {
3486
+ // 告知前端跳过按会话动态改写 document.title
3487
+ html = html.replace(
3488
+ '</head>',
3489
+ () => ` <script>window.__MANYOYO_SERVE_TITLE__ = ${jsonForInlineScript(ctx.serveTitle)};</script>\n</head>`
3490
+ );
3491
+ }
3492
+ return html;
3493
+ }
3494
+
3495
+ function renderLoginHtml(ctx) {
3496
+ return applyServeTitle(loadTemplate('login.html'), ctx);
3497
+ }
3498
+
3461
3499
  function toPositiveInt(value, fallback) {
3462
3500
  const parsed = Number.parseInt(value, 10);
3463
3501
  if (!Number.isFinite(parsed) || parsed <= 0) {
@@ -3656,7 +3694,7 @@ async function handleWebAuthRoutes(req, res, pathname, ctx, state) {
3656
3694
  }
3657
3695
 
3658
3696
  if (req.method === 'GET' && pathname === '/auth/login') {
3659
- sendHtml(res, 200, loadTemplate('login.html'));
3697
+ sendHtml(res, 200, renderLoginHtml(ctx));
3660
3698
  return true;
3661
3699
  }
3662
3700
 
@@ -3712,7 +3750,7 @@ async function handleWebAuthRoutes(req, res, pathname, ctx, state) {
3712
3750
  return false;
3713
3751
  }
3714
3752
 
3715
- function sendWebUnauthorized(res, pathname) {
3753
+ function sendWebUnauthorized(res, pathname, ctx) {
3716
3754
  if (pathname.startsWith('/api/') || pathname.startsWith('/auth/')) {
3717
3755
  sendJson(res, 401, { error: 'UNAUTHORIZED' });
3718
3756
  return;
@@ -3724,7 +3762,7 @@ function sendWebUnauthorized(res, pathname) {
3724
3762
  sendHtml(
3725
3763
  res,
3726
3764
  401,
3727
- loadTemplate('login.html'),
3765
+ renderLoginHtml(ctx),
3728
3766
  { 'Set-Cookie': getWebAuthClearCookie() }
3729
3767
  );
3730
3768
  }
@@ -4498,6 +4536,7 @@ async function startWebServer(options) {
4498
4536
  authUser: options.authUser,
4499
4537
  authPass: options.authPass,
4500
4538
  authPassAuto: options.authPassAuto,
4539
+ serveTitle: typeof options.serveTitle === 'string' ? options.serveTitle : null,
4501
4540
  dockerCmd: options.dockerCmd,
4502
4541
  hostPath: options.hostPath,
4503
4542
  containerPath: options.containerPath,
@@ -4574,12 +4613,12 @@ async function startWebServer(options) {
4574
4613
 
4575
4614
  const authSession = getWebAuthSession(state, req);
4576
4615
  if (!authSession) {
4577
- sendWebUnauthorized(res, pathname);
4616
+ sendWebUnauthorized(res, pathname, ctx);
4578
4617
  return;
4579
4618
  }
4580
4619
 
4581
4620
  if (req.method === 'GET' && pathname === '/') {
4582
- sendHtml(res, 200, loadTemplate('app.html'));
4621
+ sendHtml(res, 200, renderIndexHtml(ctx));
4583
4622
  return;
4584
4623
  }
4585
4624
 
@@ -44,6 +44,13 @@
44
44
  "serverUser": "admin",
45
45
  "serverPass": "change-this-password",
46
46
 
47
+ // serve 命令专属配置(覆盖型:runs.<name>.serve > 全局配置)
48
+ // 不设置 serve 字段(或不设置 title 字段)时,网页 <title> 按当前会话 Agent 名动态显示
49
+ // 一旦设置 title,则固定显示该值(不再随会话变化),留空字符串 "" 即固定显示为空
50
+ // "serve": {
51
+ // "title": "My MANYOYO"
52
+ // },
53
+
47
54
  // 可选插件(manyoyo playwright / manyoyo plugin playwright)
48
55
  "plugins": {
49
56
  "playwright": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "6.2.1",
3
+ "version": "6.2.4",
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",