@xcanwin/manyoyo 5.2.18 → 5.3.1

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.
@@ -131,6 +131,7 @@ EOX
131
131
 
132
132
  # 从 cache-stage 复制 Node.js(缓存或下载)
133
133
  COPY --from=cache-stage /opt/node /usr/local
134
+ COPY ./docker/res/ /tmp/docker-res/
134
135
  ARG GIT_SSL_NO_VERIFY=false
135
136
 
136
137
  RUN <<EOX
@@ -145,17 +146,10 @@ RUN <<EOX
145
146
  # 安装 Claude CLI
146
147
  npm install -g @anthropic-ai/claude-code
147
148
  mkdir -p ~/.claude/plugins/marketplaces/
148
- cat > ~/.claude.json <<EOF
149
- {
150
- "bypassPermissionsModeAccepted": true,
151
- "hasCompletedOnboarding": true,
152
- "env": {
153
- "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
154
- "CLAUDE_CODE_HIDE_ACCOUNT_INFO": "1",
155
- "DISABLE_AUTOUPDATER": "1"
156
- }
157
- }
158
- EOF
149
+ cp /tmp/docker-res/claude/claude.json ~/.claude.json
150
+ cp /tmp/docker-res/claude/settings.json ~/.claude/settings.json
151
+ cp /tmp/docker-res/claude/statusline.sh ~/.claude/statusline.sh
152
+ chmod +x ~/.claude/statusline.sh
159
153
  claude plugin marketplace add https://github.com/anthropics/claude-plugins-official
160
154
  claude plugin install ralph-loop@claude-plugins-official
161
155
  claude plugin install typescript-lsp@claude-plugins-official
@@ -172,12 +166,7 @@ EOF
172
166
  # 安装 Codex CLI
173
167
  npm install -g @openai/codex
174
168
  mkdir -p ~/.codex
175
- cat > ~/.codex/config.toml <<EOF
176
- check_for_update_on_startup = false
177
-
178
- [analytics]
179
- enabled = false
180
- EOF
169
+ cp /tmp/docker-res/codex/config.toml ~/.codex/config.toml
181
170
  mkdir -p "$HOME/.codex/skills"
182
171
  git clone --depth 1 https://github.com/openai/skills.git /tmp/openai-skills
183
172
  cp -a /tmp/openai-skills/skills/.system "$HOME/.codex/skills/.system"
@@ -204,59 +193,14 @@ EOF
204
193
  npm install -g @google/gemini-cli
205
194
  mkdir -p ~/.gemini/ ~/.gemini/tmp/bin
206
195
  ln -s $(which rg) ~/.gemini/tmp/bin/rg
207
- cat > ~/.gemini/settings.json <<EOF
208
- {
209
- "privacy": {
210
- "usageStatisticsEnabled": false
211
- },
212
- "general": {
213
- "previewFeatures": true,
214
- "enableAutoUpdate": false,
215
- "enableAutoUpdateNotification": false
216
- },
217
- "ui": {
218
- "showLineNumbers": false
219
- },
220
- "security": {
221
- "auth": {
222
- "selectedType": "oauth-personal"
223
- }
224
- },
225
- "model": {
226
- "name": "gemini-3-pro-preview"
227
- }
228
- }
229
- EOF
196
+ cp /tmp/docker-res/gemini/settings.json ~/.gemini/settings.json
230
197
  ;; esac
231
198
 
232
199
  # 安装 OpenCode CLI
233
200
  case ",$TOOL," in *,full,*|*,opencode,*)
234
201
  npm install -g opencode-ai
235
202
  mkdir -p ~/.config/opencode/
236
- cat > ~/.config/opencode/opencode.json <<EOF
237
- {
238
- "\$schema": "https://opencode.ai/config.json",
239
- "autoupdate": false,
240
- "model": "Custom_Provider/{env:OPENAI_MODEL}",
241
- "provider": {
242
- "Custom_Provider": {
243
- "npm": "@ai-sdk/openai-compatible",
244
- "options": {
245
- "baseURL": "{env:OPENAI_BASE_URL}",
246
- "apiKey": "{env:OPENAI_API_KEY}",
247
- "headers": {
248
- "User-Agent": "opencode"
249
- }
250
- },
251
- "models": {
252
- "{env:OPENAI_MODEL}": {},
253
- "claude-sonnet-4-5-20250929": {},
254
- "gpt-5.2": {}
255
- }
256
- }
257
- }
258
- }
259
- EOF
203
+ cp /tmp/docker-res/opencode/opencode.json ~/.config/opencode/opencode.json
260
204
  ;; esac
261
205
 
262
206
  # 清理
@@ -313,13 +257,12 @@ RUN <<EOX
313
257
  rm -rf /tmp/gopls-cache
314
258
  EOX
315
259
 
260
+ # 配置 supervisor
261
+ COPY ./docker/res/supervisor/s.conf /etc/supervisor/conf.d/s.conf
262
+
316
263
  RUN <<EOX
317
- # 配置 supervisor
318
- cat > /etc/supervisor/conf.d/s.conf << EOF
319
- [supervisord]
320
- user=root
321
- nodaemon=true
322
- EOF
264
+ # 清理
265
+ rm -rf /tmp/* /var/tmp/* /var/log/apt /var/log/*.log /var/lib/apt/lists/* ~/.cache ~/.npm ~/go/pkg/mod/cache
323
266
  EOX
324
267
 
325
268
  WORKDIR /tmp
@@ -676,6 +676,7 @@ class PlaywrightPlugin {
676
676
  }
677
677
 
678
678
  return {
679
+ outputDir: '/tmp/playwright-mcp',
679
680
  server: {
680
681
  host: def.listenHost,
681
682
  port,
@@ -558,7 +558,7 @@ textarea:focus-visible {
558
558
  min-height: 0;
559
559
  padding: 14px;
560
560
  display: grid;
561
- grid-template-rows: auto auto minmax(0, 1fr) auto;
561
+ grid-template-rows: auto minmax(0, 1fr) auto;
562
562
  gap: 0;
563
563
  background:
564
564
  linear-gradient(165deg, rgba(255, 251, 243, 0.95) 0%, rgba(247, 237, 223, 0.95) 100%);
@@ -631,42 +631,31 @@ body.mobile-actions-open .header-actions {
631
631
  display: grid;
632
632
  }
633
633
 
634
- .mode-switch {
635
- display: flex;
636
- justify-content: space-between;
637
- align-items: center;
638
- gap: 10px;
639
- margin: 10px 8px 4px;
640
- min-height: 36px;
641
- padding: 8px;
642
- border: 1px solid var(--line);
643
- border-radius: 12px;
644
- background: rgba(255, 251, 244, 0.9);
634
+ #modeToggle {
635
+ position: relative;
645
636
  }
646
637
 
647
- .mode-switch-left {
648
- display: inline-flex;
649
- align-items: center;
638
+ .mode-menu {
639
+ display: none;
640
+ position: absolute;
641
+ top: calc(100% + 6px);
642
+ right: 8px;
643
+ z-index: 8;
644
+ width: min(200px, calc(100vw - 36px));
645
+ padding: 8px;
646
+ border: 1px solid var(--line);
647
+ border-radius: 10px;
648
+ background: #fffaf2;
649
+ box-shadow: var(--shadow-strong);
650
+ grid-template-columns: 1fr;
650
651
  gap: 8px;
651
- min-width: 0;
652
- }
653
-
654
- .mode-switch button {
655
- min-width: 102px;
656
- color: var(--text);
657
- background: #f7eee1;
658
- border-color: #e0c6a5;
659
652
  }
660
653
 
661
- .mode-switch button.is-active {
662
- color: #ffffff;
663
- background: var(--accent);
664
- border-color: var(--accent-strong);
654
+ body.mode-menu-open .mode-menu {
655
+ display: grid;
665
656
  }
666
657
 
667
- body.command-mode #modeCommandBtn,
668
- body.agent-mode #modeAgentBtn,
669
- body.terminal-mode #modeTerminalBtn {
658
+ .mode-menu button.is-active {
670
659
  color: #ffffff;
671
660
  background: var(--accent);
672
661
  border-color: var(--accent-strong);
@@ -694,6 +683,39 @@ body.terminal-mode #modeTerminalBtn {
694
683
  margin: 4px 0 0;
695
684
  }
696
685
 
686
+ .term-keybar {
687
+ display: flex;
688
+ gap: 4px;
689
+ padding: 4px 6px;
690
+ background: #1a1a1a;
691
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
692
+ flex-shrink: 0;
693
+ }
694
+
695
+ .term-key-btn {
696
+ background: rgba(255, 255, 255, 0.07);
697
+ color: #bbb;
698
+ border: 1px solid rgba(255, 255, 255, 0.13);
699
+ border-radius: 4px;
700
+ padding: 2px 9px;
701
+ font-size: 12px;
702
+ font-family: monospace;
703
+ cursor: pointer;
704
+ user-select: none;
705
+ line-height: 1.6;
706
+ }
707
+
708
+ .term-key-btn:hover {
709
+ background: rgba(255, 255, 255, 0.14);
710
+ color: #eee;
711
+ }
712
+
713
+ .term-key-btn.is-active {
714
+ background: var(--accent);
715
+ color: #fff;
716
+ border-color: var(--accent-strong);
717
+ }
718
+
697
719
  #terminalScreen {
698
720
  flex: 1;
699
721
  min-height: 0;
@@ -1066,23 +1088,6 @@ body.command-mode .msg.origin-agent .bubble {
1066
1088
  display: none;
1067
1089
  }
1068
1090
 
1069
- .mode-switch {
1070
- margin: 8px 8px 2px;
1071
- flex-wrap: wrap;
1072
- padding: 8px;
1073
- gap: 8px;
1074
- }
1075
-
1076
- .mode-switch-left {
1077
- flex: 1 1 auto;
1078
- min-width: 0;
1079
- }
1080
-
1081
- .mode-switch button {
1082
- flex: 0 0 auto;
1083
- min-width: 84px;
1084
- }
1085
-
1086
1091
  #commandInput {
1087
1092
  min-height: 68px;
1088
1093
  max-height: 160px;
@@ -48,6 +48,13 @@
48
48
  aria-controls="sessionList"
49
49
  >会话</button>
50
50
  <h1 id="activeTitle">未选择会话</h1>
51
+ <button
52
+ type="button"
53
+ id="modeToggle"
54
+ class="secondary"
55
+ aria-expanded="false"
56
+ aria-controls="modeMenu"
57
+ >AGENT 模式</button>
51
58
  <button
52
59
  type="button"
53
60
  id="mobileActionsToggle"
@@ -58,22 +65,29 @@
58
65
  </div>
59
66
  <div id="activeMeta">请选择左侧会话</div>
60
67
  </div>
68
+ <div class="mode-menu" id="modeMenu">
69
+ <button type="button" id="modeAgentBtn" class="secondary is-active" aria-pressed="true">AGENT 模式</button>
70
+ <button type="button" id="modeCommandBtn" class="secondary" aria-pressed="false">命令模式</button>
71
+ <button type="button" id="modeTerminalBtn" class="secondary" aria-pressed="false">终端模式</button>
72
+ </div>
61
73
  <div class="header-actions" id="headerActions">
62
74
  <button type="button" id="refreshBtn" class="secondary">刷新</button>
63
75
  <button type="button" id="removeBtn" class="danger-outline">删除容器</button>
64
76
  <button type="button" id="removeAllBtn" class="danger">删除对话</button>
65
77
  </div>
66
78
  </header>
67
- <section class="mode-switch" id="modeSwitch">
68
- <div class="mode-switch-left">
69
- <button type="button" id="modeAgentBtn" class="secondary is-active">AGENT 模式</button>
70
- <button type="button" id="modeCommandBtn" class="secondary">命令模式</button>
71
- <button type="button" id="modeTerminalBtn" class="secondary">交互终端</button>
72
- </div>
73
-
74
- </section>
75
79
  <section id="messages"></section>
76
80
  <section id="terminalPanel" hidden>
81
+ <div id="terminalKeybar" class="term-keybar" aria-label="终端快捷键">
82
+ <button type="button" class="term-key-btn" data-key="esc">esc</button>
83
+ <button type="button" class="term-key-btn" data-key="tab">tab</button>
84
+ <button type="button" class="term-key-btn term-ctrl-btn" data-key="ctrl">ctrl</button>
85
+ <button type="button" class="term-key-btn term-alt-btn" data-key="alt">alt</button>&nbsp;&nbsp;
86
+ <button type="button" class="term-key-btn" data-key="left">&nbsp;◀&nbsp;</button>
87
+ <button type="button" class="term-key-btn" data-key="up">&nbsp;▲&nbsp;</button>
88
+ <button type="button" class="term-key-btn" data-key="down">&nbsp;▼&nbsp;</button>
89
+ <button type="button" class="term-key-btn" data-key="right">&nbsp;▶&nbsp;</button>
90
+ </div>
77
91
  <div id="terminalScreen" aria-label="终端输出区域"></div>
78
92
 
79
93
  </section>
@@ -46,6 +46,7 @@
46
46
  loadingMessages: false,
47
47
  mobileSidebarOpen: false,
48
48
  mobileActionsOpen: false,
49
+ modeMenuOpen: false,
49
50
  configModalOpen: false,
50
51
  createModalOpen: false,
51
52
  configLoading: false,
@@ -69,7 +70,9 @@
69
70
  terminalReady: false,
70
71
  fitTimer: null,
71
72
  lastSentCols: 0,
72
- lastSentRows: 0
73
+ lastSentRows: 0,
74
+ ctrlMode: false,
75
+ altMode: false
73
76
  }
74
77
  };
75
78
 
@@ -79,6 +82,8 @@
79
82
  const mobileSessionToggle = document.getElementById('mobileSessionToggle');
80
83
  const mobileActionsToggle = document.getElementById('mobileActionsToggle');
81
84
  const headerActions = document.getElementById('headerActions');
85
+ const modeToggle = document.getElementById('modeToggle');
86
+ const modeMenu = document.getElementById('modeMenu');
82
87
  const mobileSidebarClose = document.getElementById('mobileSidebarClose');
83
88
  const sidebarBackdrop = document.getElementById('sidebarBackdrop');
84
89
  const openConfigBtn = document.getElementById('openConfigBtn');
@@ -523,6 +528,11 @@
523
528
  }) || null;
524
529
  }
525
530
 
531
+ function isActiveSessionHistoryOnly() {
532
+ const session = getActiveSession();
533
+ return sessionStatusInfo(session && session.status).tone === 'history';
534
+ }
535
+
526
536
  function isComposerMode() {
527
537
  return state.mode === 'command' || state.mode === 'agent';
528
538
  }
@@ -686,9 +696,18 @@
686
696
  if (!data || !state.terminal.socket || state.terminal.socket.readyState !== window.WebSocket.OPEN) {
687
697
  return;
688
698
  }
699
+ let send = data;
700
+ if (state.terminal.ctrlMode && data.length === 1) {
701
+ const code = data.charCodeAt(0);
702
+ if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122)) {
703
+ send = String.fromCharCode(code & 0x1f);
704
+ }
705
+ } else if (state.terminal.altMode && data.length === 1) {
706
+ send = '\x1b' + data;
707
+ }
689
708
  state.terminal.socket.send(JSON.stringify({
690
709
  type: 'input',
691
- data: data
710
+ data: send
692
711
  }));
693
712
  });
694
713
  state.terminal.term.onResize(function (size) {
@@ -768,6 +787,7 @@
768
787
  state.terminal.sessionName = sessionName;
769
788
  state.terminal.lastSentCols = 0;
770
789
  state.terminal.lastSentRows = 0;
790
+ state.terminal.term.reset();
771
791
  writeTerminalLine('[system] 正在连接终端...');
772
792
  syncUi();
773
793
 
@@ -878,6 +898,20 @@
878
898
  setMobileActionsMenu(false);
879
899
  }
880
900
 
901
+ const MODE_LABELS = { agent: 'AGENT 模式', command: '命令模式', terminal: '终端模式' };
902
+
903
+ function setModeMenu(open) {
904
+ state.modeMenuOpen = Boolean(open);
905
+ document.body.classList.toggle('mode-menu-open', state.modeMenuOpen);
906
+ if (modeToggle) {
907
+ modeToggle.setAttribute('aria-expanded', state.modeMenuOpen ? 'true' : 'false');
908
+ }
909
+ }
910
+
911
+ function closeModeMenu() {
912
+ setModeMenu(false);
913
+ }
914
+
881
915
  function syncUi() {
882
916
  if (!state.active) {
883
917
  activeTitle.textContent = '未选择会话';
@@ -911,6 +945,9 @@
911
945
  modeTerminalBtn.classList.toggle('is-active', terminalMode);
912
946
  modeTerminalBtn.setAttribute('aria-pressed', terminalMode ? 'true' : 'false');
913
947
  }
948
+ if (modeToggle) {
949
+ modeToggle.textContent = MODE_LABELS[state.mode] || '模式';
950
+ }
914
951
  if (terminalPanel) {
915
952
  terminalPanel.hidden = !terminalMode;
916
953
  }
@@ -1164,7 +1201,11 @@
1164
1201
  if (state.mode === 'terminal' && ensureTerminalReady()) {
1165
1202
  scheduleTerminalFit(false);
1166
1203
  if (!state.terminal.connected && !state.terminal.connecting) {
1167
- connectTerminal();
1204
+ if (isActiveSessionHistoryOnly()) {
1205
+ state.terminal.term.reset();
1206
+ } else {
1207
+ connectTerminal();
1208
+ }
1168
1209
  }
1169
1210
  }
1170
1211
  renderSessionActiveState();
@@ -1497,7 +1538,7 @@
1497
1538
  throw requestError;
1498
1539
  }
1499
1540
 
1500
- if (state.mode === 'terminal' && ensureTerminalReady() && !state.terminal.connected && !state.terminal.connecting) {
1541
+ if (state.mode === 'terminal' && ensureTerminalReady() && !state.terminal.connected && !state.terminal.connecting && !isActiveSessionHistoryOnly()) {
1501
1542
  scheduleTerminalFit(false);
1502
1543
  connectTerminal();
1503
1544
  }
@@ -1825,8 +1866,16 @@
1825
1866
  composer.requestSubmit();
1826
1867
  });
1827
1868
 
1869
+ if (modeToggle) {
1870
+ modeToggle.addEventListener('click', function () {
1871
+ closeMobileActionsMenu();
1872
+ setModeMenu(!state.modeMenuOpen);
1873
+ });
1874
+ }
1875
+
1828
1876
  if (modeCommandBtn) {
1829
1877
  modeCommandBtn.addEventListener('click', function () {
1878
+ closeModeMenu();
1830
1879
  state.mode = 'command';
1831
1880
  renderMessages(state.messages, { forceFullRender: true });
1832
1881
  syncUi();
@@ -1836,6 +1885,7 @@
1836
1885
 
1837
1886
  if (modeAgentBtn) {
1838
1887
  modeAgentBtn.addEventListener('click', function () {
1888
+ closeModeMenu();
1839
1889
  state.mode = 'agent';
1840
1890
  renderMessages(state.messages, { forceFullRender: true });
1841
1891
  syncUi();
@@ -1845,13 +1895,14 @@
1845
1895
 
1846
1896
  if (modeTerminalBtn) {
1847
1897
  modeTerminalBtn.addEventListener('click', function () {
1898
+ closeModeMenu();
1848
1899
  state.mode = 'terminal';
1849
1900
  renderMessages(state.messages, { forceFullRender: true });
1850
1901
  syncUi();
1851
1902
  if (ensureTerminalReady()) {
1852
1903
  scheduleTerminalFit(false);
1853
1904
  state.terminal.term.focus();
1854
- if (!state.terminal.connected && !state.terminal.connecting) {
1905
+ if (!state.terminal.connected && !state.terminal.connecting && !isActiveSessionHistoryOnly()) {
1855
1906
  connectTerminal();
1856
1907
  }
1857
1908
  }
@@ -1863,6 +1914,41 @@
1863
1914
  loadSessions(state.active).catch(function (e) { alert(e.message); });
1864
1915
  });
1865
1916
 
1917
+ const TERM_KEY_SEQUENCES = {
1918
+ esc: '\x1b',
1919
+ tab: '\x09',
1920
+ up: '\x1b[A',
1921
+ down: '\x1b[B',
1922
+ left: '\x1b[D',
1923
+ right: '\x1b[C'
1924
+ };
1925
+ const termKeybar = document.getElementById('terminalKeybar');
1926
+ if (termKeybar) {
1927
+ termKeybar.addEventListener('click', function (e) {
1928
+ const btn = e.target.closest('[data-key]');
1929
+ if (!btn) return;
1930
+ const key = btn.dataset.key;
1931
+ if (key === 'ctrl') {
1932
+ state.terminal.ctrlMode = !state.terminal.ctrlMode;
1933
+ btn.classList.toggle('is-active', state.terminal.ctrlMode);
1934
+ if (state.terminal.term) state.terminal.term.focus();
1935
+ return;
1936
+ }
1937
+ if (key === 'alt') {
1938
+ state.terminal.altMode = !state.terminal.altMode;
1939
+ btn.classList.toggle('is-active', state.terminal.altMode);
1940
+ if (state.terminal.term) state.terminal.term.focus();
1941
+ return;
1942
+ }
1943
+ const seq = TERM_KEY_SEQUENCES[key];
1944
+ if (!seq) return;
1945
+ if (state.terminal.socket && state.terminal.socket.readyState === window.WebSocket.OPEN) {
1946
+ state.terminal.socket.send(JSON.stringify({ type: 'input', data: seq }));
1947
+ }
1948
+ if (state.terminal.term) state.terminal.term.focus();
1949
+ });
1950
+ }
1951
+
1866
1952
  if (mobileSessionToggle) {
1867
1953
  mobileSessionToggle.addEventListener('click', function () {
1868
1954
  setMobileSessionPanel(!state.mobileSidebarOpen);
@@ -1920,6 +2006,9 @@
1920
2006
  if (event.key === 'Escape' && state.mobileActionsOpen) {
1921
2007
  closeMobileActionsMenu();
1922
2008
  }
2009
+ if (event.key === 'Escape' && state.modeMenuOpen) {
2010
+ closeModeMenu();
2011
+ }
1923
2012
  });
1924
2013
 
1925
2014
  function onLayoutMediaChange() {
@@ -1949,11 +2038,17 @@
1949
2038
  }
1950
2039
 
1951
2040
  document.addEventListener('click', function (event) {
1952
- if (!state.mobileActionsOpen) return;
1953
2041
  const target = event.target;
1954
- if (mobileActionsToggle && mobileActionsToggle.contains(target)) return;
1955
- if (headerActions && headerActions.contains(target)) return;
1956
- closeMobileActionsMenu();
2042
+ if (state.mobileActionsOpen) {
2043
+ if (mobileActionsToggle && mobileActionsToggle.contains(target)) return;
2044
+ if (headerActions && headerActions.contains(target)) return;
2045
+ closeMobileActionsMenu();
2046
+ }
2047
+ if (state.modeMenuOpen) {
2048
+ if (modeToggle && modeToggle.contains(target)) return;
2049
+ if (modeMenu && modeMenu.contains(target)) return;
2050
+ closeModeMenu();
2051
+ }
1957
2052
  });
1958
2053
 
1959
2054
  removeBtn.addEventListener('click', async function () {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.2.18",
4
- "imageVersion": "1.8.1-common",
3
+ "version": "5.3.1",
4
+ "imageVersion": "1.8.4-common",
5
5
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",
6
6
  "keywords": [
7
7
  "manyoyo",