@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.
- package/docker/manyoyo.Dockerfile +13 -70
- package/lib/plugin/playwright.js +1 -0
- package/lib/web/frontend/app.css +52 -47
- package/lib/web/frontend/app.html +22 -8
- package/lib/web/frontend/app.js +104 -9
- package/package.json +2 -2
|
@@ -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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
318
|
-
|
|
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
|
package/lib/plugin/playwright.js
CHANGED
package/lib/web/frontend/app.css
CHANGED
|
@@ -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
|
|
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
|
-
|
|
635
|
-
|
|
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-
|
|
648
|
-
display:
|
|
649
|
-
|
|
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-
|
|
662
|
-
|
|
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
|
-
|
|
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>
|
|
86
|
+
<button type="button" class="term-key-btn" data-key="left"> ◀ </button>
|
|
87
|
+
<button type="button" class="term-key-btn" data-key="up"> ▲ </button>
|
|
88
|
+
<button type="button" class="term-key-btn" data-key="down"> ▼ </button>
|
|
89
|
+
<button type="button" class="term-key-btn" data-key="right"> ▶ </button>
|
|
90
|
+
</div>
|
|
77
91
|
<div id="terminalScreen" aria-label="终端输出区域"></div>
|
|
78
92
|
|
|
79
93
|
</section>
|
package/lib/web/frontend/app.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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 (
|
|
1955
|
-
|
|
1956
|
-
|
|
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