@xcanwin/manyoyo 5.5.2 → 5.6.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/bin/manyoyo.js +29 -3
- package/lib/agent-resume.js +1 -1
- package/lib/web/frontend/app.css +250 -30
- package/lib/web/frontend/app.html +62 -47
- package/lib/web/frontend/app.js +353 -79
- package/lib/web/server.js +211 -24
- package/package.json +19 -8
package/lib/web/frontend/app.js
CHANGED
|
@@ -40,19 +40,24 @@
|
|
|
40
40
|
active: '',
|
|
41
41
|
messages: [],
|
|
42
42
|
messageRenderKeys: [],
|
|
43
|
+
activeTab: 'activity',
|
|
43
44
|
mode: 'agent',
|
|
44
45
|
sending: false,
|
|
45
46
|
loadingSessions: false,
|
|
46
47
|
loadingMessages: false,
|
|
48
|
+
loadingSessionDetail: false,
|
|
47
49
|
mobileSidebarOpen: false,
|
|
48
50
|
mobileActionsOpen: false,
|
|
49
|
-
modeMenuOpen: false,
|
|
50
51
|
configModalOpen: false,
|
|
51
52
|
createModalOpen: false,
|
|
52
53
|
configLoading: false,
|
|
53
54
|
configSaving: false,
|
|
54
55
|
createLoading: false,
|
|
55
56
|
createSubmitting: false,
|
|
57
|
+
configSnapshot: null,
|
|
58
|
+
sessionDetail: null,
|
|
59
|
+
sessionDetailError: '',
|
|
60
|
+
sessionDetailRequestId: 0,
|
|
56
61
|
createAgentPromptAuto: false,
|
|
57
62
|
createDefaults: null,
|
|
58
63
|
createRuns: {},
|
|
@@ -82,8 +87,10 @@
|
|
|
82
87
|
const mobileSessionToggle = document.getElementById('mobileSessionToggle');
|
|
83
88
|
const mobileActionsToggle = document.getElementById('mobileActionsToggle');
|
|
84
89
|
const headerActions = document.getElementById('headerActions');
|
|
85
|
-
const
|
|
86
|
-
const
|
|
90
|
+
const viewActivityBtn = document.getElementById('viewActivityBtn');
|
|
91
|
+
const viewTerminalBtn = document.getElementById('viewTerminalBtn');
|
|
92
|
+
const viewConfigBtn = document.getElementById('viewConfigBtn');
|
|
93
|
+
const viewCheckBtn = document.getElementById('viewCheckBtn');
|
|
87
94
|
const mobileSidebarClose = document.getElementById('mobileSidebarClose');
|
|
88
95
|
const sidebarBackdrop = document.getElementById('sidebarBackdrop');
|
|
89
96
|
const openConfigBtn = document.getElementById('openConfigBtn');
|
|
@@ -118,11 +125,15 @@
|
|
|
118
125
|
const createVolumes = document.getElementById('createVolumes');
|
|
119
126
|
const activeTitle = document.getElementById('activeTitle');
|
|
120
127
|
const activeMeta = document.getElementById('activeMeta');
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const modeTerminalBtn = document.getElementById('modeTerminalBtn');
|
|
128
|
+
const activityCommandBtn = document.getElementById('activityCommandBtn');
|
|
129
|
+
const activityAgentBtn = document.getElementById('activityAgentBtn');
|
|
124
130
|
const messagesNode = document.getElementById('messages');
|
|
125
131
|
const terminalPanel = document.getElementById('terminalPanel');
|
|
132
|
+
const configPanel = document.getElementById('configPanel');
|
|
133
|
+
const checkPanel = document.getElementById('checkPanel');
|
|
134
|
+
const configSummary = document.getElementById('configSummary');
|
|
135
|
+
const checkSummary = document.getElementById('checkSummary');
|
|
136
|
+
const contextSummary = document.getElementById('contextSummary');
|
|
126
137
|
const terminalScreen = document.getElementById('terminalScreen');
|
|
127
138
|
const composer = document.getElementById('composer');
|
|
128
139
|
const commandInput = document.getElementById('commandInput');
|
|
@@ -154,7 +165,7 @@
|
|
|
154
165
|
const AGENT_PROMPT_TEMPLATE_MAP = {
|
|
155
166
|
claude: 'claude -p {prompt}',
|
|
156
167
|
gemini: 'gemini -p {prompt}',
|
|
157
|
-
codex: 'codex exec {prompt}',
|
|
168
|
+
codex: 'codex exec --skip-git-repo-check {prompt}',
|
|
158
169
|
opencode: 'opencode run {prompt}'
|
|
159
170
|
};
|
|
160
171
|
const markdownRenderer = window.ManyoyoMarkdown
|
|
@@ -534,7 +545,7 @@
|
|
|
534
545
|
}
|
|
535
546
|
|
|
536
547
|
function isComposerMode() {
|
|
537
|
-
return state.
|
|
548
|
+
return state.activeTab === 'activity';
|
|
538
549
|
}
|
|
539
550
|
|
|
540
551
|
function isActiveAgentEnabled() {
|
|
@@ -898,18 +909,220 @@
|
|
|
898
909
|
setMobileActionsMenu(false);
|
|
899
910
|
}
|
|
900
911
|
|
|
901
|
-
const
|
|
912
|
+
const VIEW_LABELS = {
|
|
913
|
+
activity: '活动',
|
|
914
|
+
terminal: '终端',
|
|
915
|
+
config: '配置',
|
|
916
|
+
check: '检查'
|
|
917
|
+
};
|
|
902
918
|
|
|
903
|
-
function
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
919
|
+
function setActiveTab(tab) {
|
|
920
|
+
const next = String(tab || '').trim();
|
|
921
|
+
if (!VIEW_LABELS[next]) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
state.activeTab = next;
|
|
925
|
+
syncUi();
|
|
926
|
+
if (next === 'terminal' && state.active && ensureTerminalReady()) {
|
|
927
|
+
scheduleTerminalFit(false);
|
|
928
|
+
if (!state.terminal.connected && !state.terminal.connecting && !isActiveSessionHistoryOnly()) {
|
|
929
|
+
connectTerminal();
|
|
930
|
+
}
|
|
931
|
+
if (state.terminal.term) {
|
|
932
|
+
state.terminal.term.focus();
|
|
933
|
+
}
|
|
908
934
|
}
|
|
909
935
|
}
|
|
910
936
|
|
|
911
|
-
function
|
|
912
|
-
|
|
937
|
+
function escapeHtml(value) {
|
|
938
|
+
return String(value == null ? '' : value)
|
|
939
|
+
.replace(/&/g, '&')
|
|
940
|
+
.replace(/</g, '<')
|
|
941
|
+
.replace(/>/g, '>')
|
|
942
|
+
.replace(/"/g, '"')
|
|
943
|
+
.replace(/'/g, ''');
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function renderEmptyInspector(container, title, description) {
|
|
947
|
+
if (!container) return;
|
|
948
|
+
container.innerHTML = `
|
|
949
|
+
<section class="info-card empty-card">
|
|
950
|
+
<div class="card-title">${escapeHtml(title)}</div>
|
|
951
|
+
<div class="card-desc">${escapeHtml(description)}</div>
|
|
952
|
+
</section>
|
|
953
|
+
`;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function renderKeyValueCard(container, title, entries, options) {
|
|
957
|
+
if (!container) return;
|
|
958
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
959
|
+
const actionHtml = opts.actionLabel && opts.actionId
|
|
960
|
+
? `<button type="button" class="secondary inline-action" id="${escapeHtml(opts.actionId)}">${escapeHtml(opts.actionLabel)}</button>`
|
|
961
|
+
: '';
|
|
962
|
+
const rowsHtml = (entries || []).map(function (entry) {
|
|
963
|
+
const tone = entry && entry.tone ? ` tone-${entry.tone}` : '';
|
|
964
|
+
return `
|
|
965
|
+
<div class="kv-row${tone}">
|
|
966
|
+
<span class="kv-label">${escapeHtml(entry.label || '')}</span>
|
|
967
|
+
<span class="kv-value">${escapeHtml(entry.value || '—')}</span>
|
|
968
|
+
</div>
|
|
969
|
+
`;
|
|
970
|
+
}).join('');
|
|
971
|
+
container.insertAdjacentHTML('beforeend', `
|
|
972
|
+
<section class="info-card">
|
|
973
|
+
<div class="card-head">
|
|
974
|
+
<div class="card-title">${escapeHtml(title)}</div>
|
|
975
|
+
${actionHtml}
|
|
976
|
+
</div>
|
|
977
|
+
<div class="kv-list">${rowsHtml}</div>
|
|
978
|
+
</section>
|
|
979
|
+
`);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function renderCheckCard(container, title, checks) {
|
|
983
|
+
if (!container) return;
|
|
984
|
+
const itemsHtml = (checks || []).map(function (item) {
|
|
985
|
+
return `
|
|
986
|
+
<div class="check-item tone-${escapeHtml(item.tone || 'info')}">
|
|
987
|
+
<div class="check-main">
|
|
988
|
+
<span class="check-label">${escapeHtml(item.label || '')}</span>
|
|
989
|
+
<span class="check-value">${escapeHtml(item.value || '')}</span>
|
|
990
|
+
</div>
|
|
991
|
+
<div class="check-detail">${escapeHtml(item.detail || '')}</div>
|
|
992
|
+
</div>
|
|
993
|
+
`;
|
|
994
|
+
}).join('');
|
|
995
|
+
container.insertAdjacentHTML('beforeend', `
|
|
996
|
+
<section class="info-card">
|
|
997
|
+
<div class="card-title">${escapeHtml(title)}</div>
|
|
998
|
+
<div class="check-list">${itemsHtml}</div>
|
|
999
|
+
</section>
|
|
1000
|
+
`);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function renderSessionDetailPanels() {
|
|
1004
|
+
const detail = state.sessionDetail;
|
|
1005
|
+
if (!state.active) {
|
|
1006
|
+
renderEmptyInspector(contextSummary, '当前上下文', '选择左侧会话后,这里会显示容器状态、配置摘要与 Agent 信息。');
|
|
1007
|
+
renderEmptyInspector(configSummary, '配置视图', '选择会话后可查看当前容器会话的运行参数摘要。');
|
|
1008
|
+
renderEmptyInspector(checkSummary, '检查视图', '选择会话后可查看当前会话的基础健康检查。');
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
if (state.loadingSessionDetail) {
|
|
1012
|
+
renderEmptyInspector(contextSummary, '当前上下文', '正在加载会话详情...');
|
|
1013
|
+
renderEmptyInspector(configSummary, '配置视图', '正在加载会话详情...');
|
|
1014
|
+
renderEmptyInspector(checkSummary, '检查视图', '正在加载会话详情...');
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
if (!detail) {
|
|
1018
|
+
const message = state.sessionDetailError || '当前会话详情暂时不可用。';
|
|
1019
|
+
renderEmptyInspector(contextSummary, '当前上下文', message);
|
|
1020
|
+
renderEmptyInspector(configSummary, '配置视图', message);
|
|
1021
|
+
renderEmptyInspector(checkSummary, '检查视图', message);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const applied = detail.applied || {};
|
|
1026
|
+
const status = sessionStatusInfo(detail.status);
|
|
1027
|
+
const updatedText = formatDateTime(detail.updatedAt) || '暂无更新';
|
|
1028
|
+
|
|
1029
|
+
if (contextSummary) {
|
|
1030
|
+
contextSummary.innerHTML = '';
|
|
1031
|
+
renderKeyValueCard(contextSummary, '会话概览', [
|
|
1032
|
+
{ label: '会话', value: detail.name || state.active },
|
|
1033
|
+
{ label: '状态', value: status.label, tone: status.tone },
|
|
1034
|
+
{ label: '镜像', value: detail.image || applied.imageName || '—' },
|
|
1035
|
+
{ label: '最近更新', value: updatedText },
|
|
1036
|
+
{ label: '消息数', value: String(safeMessageCount(detail.messageCount)) }
|
|
1037
|
+
]);
|
|
1038
|
+
renderKeyValueCard(contextSummary, 'Agent 上下文', [
|
|
1039
|
+
{ label: '已启用', value: detail.agentEnabled ? '是' : '否', tone: detail.agentEnabled ? 'ok' : 'warn' },
|
|
1040
|
+
{ label: '程序', value: detail.agentProgram || '—' },
|
|
1041
|
+
{ label: '支持 resume', value: detail.resumeSupported ? '是' : '否', tone: detail.resumeSupported ? 'ok' : 'warn' },
|
|
1042
|
+
{ label: '最近 resume', value: detail.lastResumeAt ? formatDateTime(detail.lastResumeAt) : '暂无' },
|
|
1043
|
+
{ label: '最近结果', value: detail.lastResumeOk == null ? '暂无' : (detail.lastResumeOk ? '成功' : '失败'), tone: detail.lastResumeOk == null ? 'info' : (detail.lastResumeOk ? 'ok' : 'danger') }
|
|
1044
|
+
]);
|
|
1045
|
+
renderKeyValueCard(contextSummary, '运行参数', [
|
|
1046
|
+
{ label: 'hostPath', value: applied.hostPath || '—' },
|
|
1047
|
+
{ label: 'containerPath', value: applied.containerPath || '—' },
|
|
1048
|
+
{ label: 'imageVersion', value: applied.imageVersion || '—' },
|
|
1049
|
+
{ label: 'containerMode', value: applied.containerMode || 'default' },
|
|
1050
|
+
{ label: 'env/vol/ports', value: `${applied.envCount || 0} / ${applied.volumeCount || 0} / ${applied.portCount || 0}` }
|
|
1051
|
+
]);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
if (configSummary) {
|
|
1055
|
+
configSummary.innerHTML = '';
|
|
1056
|
+
renderKeyValueCard(configSummary, '配置摘要', [
|
|
1057
|
+
{ label: 'containerName', value: applied.containerName || detail.name || state.active },
|
|
1058
|
+
{ label: 'hostPath', value: applied.hostPath || '—' },
|
|
1059
|
+
{ label: 'containerPath', value: applied.containerPath || '—' },
|
|
1060
|
+
{ label: 'imageName', value: applied.imageName || detail.image || '—' },
|
|
1061
|
+
{ label: 'imageVersion', value: applied.imageVersion || '—' },
|
|
1062
|
+
{ label: 'containerMode', value: applied.containerMode || 'default' }
|
|
1063
|
+
], { actionLabel: '打开配置', actionId: 'configSummaryOpenBtn' });
|
|
1064
|
+
renderKeyValueCard(configSummary, '命令与 Agent', [
|
|
1065
|
+
{ label: 'shellPrefix', value: applied.shellPrefix || '—' },
|
|
1066
|
+
{ label: 'shell', value: applied.shell || '—' },
|
|
1067
|
+
{ label: 'shellSuffix', value: applied.shellSuffix || '—' },
|
|
1068
|
+
{ label: '默认命令', value: applied.defaultCommand || '—' },
|
|
1069
|
+
{ label: 'Agent 模板', value: detail.agentPromptCommand || '—' },
|
|
1070
|
+
{ label: 'yolo', value: applied.yolo || '—' }
|
|
1071
|
+
]);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (checkSummary) {
|
|
1075
|
+
checkSummary.innerHTML = '';
|
|
1076
|
+
renderCheckCard(checkSummary, '基础检查', [
|
|
1077
|
+
{
|
|
1078
|
+
label: '容器状态',
|
|
1079
|
+
value: status.label,
|
|
1080
|
+
tone: status.tone === 'running' ? 'ok' : (status.tone === 'history' ? 'warn' : 'danger'),
|
|
1081
|
+
detail: status.tone === 'running' ? '容器处于可交互状态。' : '当前不是活跃运行态,部分功能可能受限。'
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
label: 'Agent 模板',
|
|
1085
|
+
value: detail.agentEnabled ? '已配置' : '未配置',
|
|
1086
|
+
tone: detail.agentEnabled ? 'ok' : 'warn',
|
|
1087
|
+
detail: detail.agentEnabled ? '活动页可直接发送 Agent 提示词。' : '当前会话不支持 Agent 模式。'
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
label: 'Resume 能力',
|
|
1091
|
+
value: detail.resumeSupported ? '支持' : '不支持',
|
|
1092
|
+
tone: detail.resumeSupported ? 'ok' : 'warn',
|
|
1093
|
+
detail: detail.resumeSupported ? '可以尝试基于历史继续 Agent 会话。' : '当前 Agent 程序或模板不支持 resume。'
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
label: '镜像版本格式',
|
|
1097
|
+
value: applied.imageVersion || '缺失',
|
|
1098
|
+
tone: /^\d+\.\d+\.\d+-[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(String(applied.imageVersion || '')) ? 'ok' : 'danger',
|
|
1099
|
+
detail: '建议保持 x.y.z-后缀 格式,便于 manyoyo 的版本校验。'
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
label: '工作目录',
|
|
1103
|
+
value: applied.hostPath && applied.containerPath ? '完整' : '缺失',
|
|
1104
|
+
tone: applied.hostPath && applied.containerPath ? 'ok' : 'danger',
|
|
1105
|
+
detail: 'hostPath / containerPath 是容器会话最关键的上下文。'
|
|
1106
|
+
}
|
|
1107
|
+
]);
|
|
1108
|
+
if (detail.lastResumeError) {
|
|
1109
|
+
renderCheckCard(checkSummary, '最近问题', [
|
|
1110
|
+
{
|
|
1111
|
+
label: 'Resume 错误',
|
|
1112
|
+
value: '有错误输出',
|
|
1113
|
+
tone: 'danger',
|
|
1114
|
+
detail: detail.lastResumeError
|
|
1115
|
+
}
|
|
1116
|
+
]);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const configSummaryOpenBtn = document.getElementById('configSummaryOpenBtn');
|
|
1121
|
+
if (configSummaryOpenBtn) {
|
|
1122
|
+
configSummaryOpenBtn.addEventListener('click', function () {
|
|
1123
|
+
openConfigModal();
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
913
1126
|
}
|
|
914
1127
|
|
|
915
1128
|
function syncUi() {
|
|
@@ -924,34 +1137,44 @@
|
|
|
924
1137
|
activeMeta.textContent = buildActiveMeta(getActiveSession());
|
|
925
1138
|
}
|
|
926
1139
|
|
|
1140
|
+
const activityTab = state.activeTab === 'activity';
|
|
1141
|
+
const terminalTab = state.activeTab === 'terminal';
|
|
1142
|
+
const configTab = state.activeTab === 'config';
|
|
1143
|
+
const checkTab = state.activeTab === 'check';
|
|
927
1144
|
const commandMode = state.mode === 'command';
|
|
928
1145
|
const agentMode = state.mode === 'agent';
|
|
929
|
-
const terminalMode = state.mode === 'terminal';
|
|
930
|
-
const composerMode = commandMode || agentMode;
|
|
931
1146
|
const agentEnabled = isActiveAgentEnabled();
|
|
932
1147
|
|
|
933
1148
|
document.body.classList.toggle('command-mode', commandMode);
|
|
934
1149
|
document.body.classList.toggle('agent-mode', agentMode);
|
|
935
|
-
document.body.classList.toggle('terminal-mode',
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1150
|
+
document.body.classList.toggle('terminal-mode', terminalTab);
|
|
1151
|
+
document.body.classList.toggle('config-tab', configTab);
|
|
1152
|
+
document.body.classList.toggle('check-tab', checkTab);
|
|
1153
|
+
if (activityCommandBtn) {
|
|
1154
|
+
activityCommandBtn.classList.toggle('is-active', commandMode);
|
|
1155
|
+
activityCommandBtn.setAttribute('aria-pressed', commandMode ? 'true' : 'false');
|
|
1156
|
+
}
|
|
1157
|
+
if (activityAgentBtn) {
|
|
1158
|
+
activityAgentBtn.classList.toggle('is-active', agentMode);
|
|
1159
|
+
activityAgentBtn.setAttribute('aria-pressed', agentMode ? 'true' : 'false');
|
|
1160
|
+
}
|
|
1161
|
+
if (viewActivityBtn) viewActivityBtn.classList.toggle('is-active', activityTab);
|
|
1162
|
+
if (viewTerminalBtn) viewTerminalBtn.classList.toggle('is-active', terminalTab);
|
|
1163
|
+
if (viewConfigBtn) viewConfigBtn.classList.toggle('is-active', configTab);
|
|
1164
|
+
if (viewCheckBtn) viewCheckBtn.classList.toggle('is-active', checkTab);
|
|
1165
|
+
if (messagesNode) {
|
|
1166
|
+
messagesNode.hidden = !activityTab;
|
|
939
1167
|
}
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
modeAgentBtn.setAttribute('aria-pressed', agentMode ? 'true' : 'false');
|
|
1168
|
+
if (terminalPanel) {
|
|
1169
|
+
terminalPanel.hidden = !terminalTab;
|
|
943
1170
|
}
|
|
944
|
-
if (
|
|
945
|
-
|
|
946
|
-
modeTerminalBtn.setAttribute('aria-pressed', terminalMode ? 'true' : 'false');
|
|
1171
|
+
if (configPanel) {
|
|
1172
|
+
configPanel.hidden = !configTab;
|
|
947
1173
|
}
|
|
948
|
-
if (
|
|
949
|
-
|
|
1174
|
+
if (checkPanel) {
|
|
1175
|
+
checkPanel.hidden = !checkTab;
|
|
950
1176
|
}
|
|
951
|
-
if (
|
|
952
|
-
terminalPanel.hidden = !terminalMode;
|
|
953
|
-
}
|
|
954
|
-
if (terminalMode && state.terminal.terminalReady) {
|
|
1177
|
+
if (terminalTab && state.terminal.terminalReady) {
|
|
955
1178
|
scheduleTerminalFit(false);
|
|
956
1179
|
}
|
|
957
1180
|
|
|
@@ -959,8 +1182,8 @@
|
|
|
959
1182
|
refreshBtn.disabled = busy;
|
|
960
1183
|
removeBtn.disabled = !state.active || busy;
|
|
961
1184
|
removeAllBtn.disabled = !state.active || busy;
|
|
962
|
-
sendBtn.disabled = !
|
|
963
|
-
commandInput.disabled = !
|
|
1185
|
+
sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && !agentEnabled);
|
|
1186
|
+
commandInput.disabled = !activityTab || !state.active || (agentMode && !agentEnabled);
|
|
964
1187
|
if (commandInput) {
|
|
965
1188
|
commandInput.placeholder = agentMode
|
|
966
1189
|
? '输入提示词,例如:请帮我分析当前项目结构并给出重构建议'
|
|
@@ -1013,8 +1236,12 @@
|
|
|
1013
1236
|
sendState.textContent = '就绪';
|
|
1014
1237
|
}
|
|
1015
1238
|
sendState.classList.toggle('is-active', state.sending);
|
|
1239
|
+
if (composer) {
|
|
1240
|
+
composer.hidden = !activityTab;
|
|
1241
|
+
}
|
|
1016
1242
|
setMobileSessionPanel(state.mobileSidebarOpen);
|
|
1017
1243
|
setMobileActionsMenu(state.mobileActionsOpen);
|
|
1244
|
+
renderSessionDetailPanels();
|
|
1018
1245
|
}
|
|
1019
1246
|
|
|
1020
1247
|
async function api(url, options) {
|
|
@@ -1041,7 +1268,9 @@
|
|
|
1041
1268
|
}
|
|
1042
1269
|
|
|
1043
1270
|
async function fetchConfigSnapshot() {
|
|
1044
|
-
|
|
1271
|
+
const snapshot = await api('/api/config');
|
|
1272
|
+
state.configSnapshot = snapshot;
|
|
1273
|
+
return snapshot;
|
|
1045
1274
|
}
|
|
1046
1275
|
|
|
1047
1276
|
async function openConfigModal() {
|
|
@@ -1084,6 +1313,7 @@
|
|
|
1084
1313
|
method: 'PUT',
|
|
1085
1314
|
body: JSON.stringify({ raw: configEditor ? configEditor.value : '' })
|
|
1086
1315
|
});
|
|
1316
|
+
await fetchConfigSnapshot();
|
|
1087
1317
|
showConfigError('');
|
|
1088
1318
|
alert('配置已保存。后续新建会读取最新配置。');
|
|
1089
1319
|
} catch (e) {
|
|
@@ -1195,10 +1425,12 @@
|
|
|
1195
1425
|
disconnectTerminal('会话切换,终端已断开', true);
|
|
1196
1426
|
}
|
|
1197
1427
|
state.active = sessionName;
|
|
1428
|
+
state.sessionDetail = null;
|
|
1429
|
+
state.sessionDetailError = '';
|
|
1198
1430
|
if (isMobileLayout()) {
|
|
1199
1431
|
closeMobileSessionPanel();
|
|
1200
1432
|
}
|
|
1201
|
-
if (state.
|
|
1433
|
+
if (state.activeTab === 'terminal' && ensureTerminalReady()) {
|
|
1202
1434
|
scheduleTerminalFit(false);
|
|
1203
1435
|
if (!state.terminal.connected && !state.terminal.connecting) {
|
|
1204
1436
|
if (isActiveSessionHistoryOnly()) {
|
|
@@ -1210,7 +1442,10 @@
|
|
|
1210
1442
|
}
|
|
1211
1443
|
renderSessionActiveState();
|
|
1212
1444
|
syncUi();
|
|
1213
|
-
|
|
1445
|
+
Promise.all([
|
|
1446
|
+
loadMessagesForSession(sessionName),
|
|
1447
|
+
loadSessionDetailForSession(sessionName)
|
|
1448
|
+
]).catch(function (e) {
|
|
1214
1449
|
alert(e.message);
|
|
1215
1450
|
});
|
|
1216
1451
|
}
|
|
@@ -1501,6 +1736,8 @@
|
|
|
1501
1736
|
|
|
1502
1737
|
if (state.active && !state.sessions.some(function (session) { return session.name === state.active; })) {
|
|
1503
1738
|
state.active = '';
|
|
1739
|
+
state.sessionDetail = null;
|
|
1740
|
+
state.sessionDetailError = '';
|
|
1504
1741
|
}
|
|
1505
1742
|
if (!state.active && state.sessions.length) {
|
|
1506
1743
|
state.active = state.sessions[0].name;
|
|
@@ -1538,13 +1775,20 @@
|
|
|
1538
1775
|
throw requestError;
|
|
1539
1776
|
}
|
|
1540
1777
|
|
|
1541
|
-
if (state.
|
|
1778
|
+
if (state.activeTab === 'terminal' && ensureTerminalReady() && !state.terminal.connected && !state.terminal.connecting && !isActiveSessionHistoryOnly()) {
|
|
1542
1779
|
scheduleTerminalFit(false);
|
|
1543
1780
|
connectTerminal();
|
|
1544
1781
|
}
|
|
1545
1782
|
|
|
1546
1783
|
if (opts.reloadMessages) {
|
|
1547
|
-
await
|
|
1784
|
+
await Promise.all([
|
|
1785
|
+
loadMessagesForSession(state.active, { silent: false }),
|
|
1786
|
+
loadSessionDetailForSession(state.active)
|
|
1787
|
+
]);
|
|
1788
|
+
} else if (state.active) {
|
|
1789
|
+
loadSessionDetailForSession(state.active).catch(function () {
|
|
1790
|
+
// 静默失败不打断主流程
|
|
1791
|
+
});
|
|
1548
1792
|
}
|
|
1549
1793
|
}
|
|
1550
1794
|
|
|
@@ -1624,6 +1868,43 @@
|
|
|
1624
1868
|
await loadMessagesForSession(state.active, { silent: false });
|
|
1625
1869
|
}
|
|
1626
1870
|
|
|
1871
|
+
async function loadSessionDetailForSession(sessionName) {
|
|
1872
|
+
const targetSession = typeof sessionName === 'string' ? sessionName.trim() : '';
|
|
1873
|
+
const requestId = state.sessionDetailRequestId + 1;
|
|
1874
|
+
state.sessionDetailRequestId = requestId;
|
|
1875
|
+
|
|
1876
|
+
if (!targetSession) {
|
|
1877
|
+
state.sessionDetail = null;
|
|
1878
|
+
state.sessionDetailError = '';
|
|
1879
|
+
state.loadingSessionDetail = false;
|
|
1880
|
+
renderSessionDetailPanels();
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
state.loadingSessionDetail = true;
|
|
1885
|
+
state.sessionDetailError = '';
|
|
1886
|
+
renderSessionDetailPanels();
|
|
1887
|
+
try {
|
|
1888
|
+
const data = await api('/api/sessions/' + encodeURIComponent(targetSession) + '/detail');
|
|
1889
|
+
if (requestId !== state.sessionDetailRequestId) {
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
state.sessionDetail = data && data.detail ? data.detail : null;
|
|
1893
|
+
} catch (e) {
|
|
1894
|
+
if (requestId !== state.sessionDetailRequestId) {
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
state.sessionDetail = null;
|
|
1898
|
+
state.sessionDetailError = e && e.message ? e.message : '加载会话详情失败';
|
|
1899
|
+
} finally {
|
|
1900
|
+
if (requestId !== state.sessionDetailRequestId) {
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
state.loadingSessionDetail = false;
|
|
1904
|
+
renderSessionDetailPanels();
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1627
1908
|
function bumpSessionMetaAfterSend(sessionName) {
|
|
1628
1909
|
if (!sessionName) return;
|
|
1629
1910
|
const session = state.sessions.find(function (item) {
|
|
@@ -1762,6 +2043,7 @@
|
|
|
1762
2043
|
})
|
|
1763
2044
|
});
|
|
1764
2045
|
closeCreateModal();
|
|
2046
|
+
state.activeTab = 'activity';
|
|
1765
2047
|
state.mode = 'agent';
|
|
1766
2048
|
await loadSessions(data.name);
|
|
1767
2049
|
if (isMobileLayout()) {
|
|
@@ -1781,7 +2063,7 @@
|
|
|
1781
2063
|
if (!state.active) return;
|
|
1782
2064
|
if (state.sending) return;
|
|
1783
2065
|
if (state.loadingSessions || state.loadingMessages) return;
|
|
1784
|
-
if (
|
|
2066
|
+
if (state.activeTab !== 'activity') return;
|
|
1785
2067
|
const mode = state.mode === 'agent' ? 'agent' : 'command';
|
|
1786
2068
|
if (mode === 'agent' && !isActiveAgentEnabled()) {
|
|
1787
2069
|
syncUi();
|
|
@@ -1860,22 +2142,14 @@
|
|
|
1860
2142
|
|
|
1861
2143
|
// Enter / Ctrl+Enter: 发送
|
|
1862
2144
|
event.preventDefault();
|
|
1863
|
-
if (!state.active || state.sending ||
|
|
2145
|
+
if (!state.active || state.sending || state.activeTab !== 'activity') {
|
|
1864
2146
|
return;
|
|
1865
2147
|
}
|
|
1866
2148
|
composer.requestSubmit();
|
|
1867
2149
|
});
|
|
1868
2150
|
|
|
1869
|
-
if (
|
|
1870
|
-
|
|
1871
|
-
closeMobileActionsMenu();
|
|
1872
|
-
setModeMenu(!state.modeMenuOpen);
|
|
1873
|
-
});
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
if (modeCommandBtn) {
|
|
1877
|
-
modeCommandBtn.addEventListener('click', function () {
|
|
1878
|
-
closeModeMenu();
|
|
2151
|
+
if (activityCommandBtn) {
|
|
2152
|
+
activityCommandBtn.addEventListener('click', function () {
|
|
1879
2153
|
state.mode = 'command';
|
|
1880
2154
|
renderMessages(state.messages, { forceFullRender: true });
|
|
1881
2155
|
syncUi();
|
|
@@ -1883,9 +2157,8 @@
|
|
|
1883
2157
|
});
|
|
1884
2158
|
}
|
|
1885
2159
|
|
|
1886
|
-
if (
|
|
1887
|
-
|
|
1888
|
-
closeModeMenu();
|
|
2160
|
+
if (activityAgentBtn) {
|
|
2161
|
+
activityAgentBtn.addEventListener('click', function () {
|
|
1889
2162
|
state.mode = 'agent';
|
|
1890
2163
|
renderMessages(state.messages, { forceFullRender: true });
|
|
1891
2164
|
syncUi();
|
|
@@ -1893,19 +2166,28 @@
|
|
|
1893
2166
|
});
|
|
1894
2167
|
}
|
|
1895
2168
|
|
|
1896
|
-
if (
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
2169
|
+
if (viewActivityBtn) {
|
|
2170
|
+
viewActivityBtn.addEventListener('click', function () {
|
|
2171
|
+
setActiveTab('activity');
|
|
2172
|
+
commandInput.focus();
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
if (viewTerminalBtn) {
|
|
2177
|
+
viewTerminalBtn.addEventListener('click', function () {
|
|
2178
|
+
setActiveTab('terminal');
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
if (viewConfigBtn) {
|
|
2183
|
+
viewConfigBtn.addEventListener('click', function () {
|
|
2184
|
+
setActiveTab('config');
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
if (viewCheckBtn) {
|
|
2189
|
+
viewCheckBtn.addEventListener('click', function () {
|
|
2190
|
+
setActiveTab('check');
|
|
1909
2191
|
});
|
|
1910
2192
|
}
|
|
1911
2193
|
|
|
@@ -2006,21 +2288,18 @@
|
|
|
2006
2288
|
if (event.key === 'Escape' && state.mobileActionsOpen) {
|
|
2007
2289
|
closeMobileActionsMenu();
|
|
2008
2290
|
}
|
|
2009
|
-
if (event.key === 'Escape' && state.modeMenuOpen) {
|
|
2010
|
-
closeModeMenu();
|
|
2011
|
-
}
|
|
2012
2291
|
});
|
|
2013
2292
|
|
|
2014
2293
|
function onLayoutMediaChange() {
|
|
2015
2294
|
setMobileSessionPanel(state.mobileSidebarOpen);
|
|
2016
2295
|
setMobileActionsMenu(state.mobileActionsOpen);
|
|
2017
|
-
if (state.
|
|
2296
|
+
if (state.activeTab === 'terminal' && state.terminal.terminalReady) {
|
|
2018
2297
|
scheduleTerminalFit(false);
|
|
2019
2298
|
}
|
|
2020
2299
|
}
|
|
2021
2300
|
|
|
2022
2301
|
window.addEventListener('resize', function () {
|
|
2023
|
-
if (state.
|
|
2302
|
+
if (state.activeTab === 'terminal' && state.terminal.terminalReady) {
|
|
2024
2303
|
scheduleTerminalFit(false);
|
|
2025
2304
|
}
|
|
2026
2305
|
});
|
|
@@ -2044,11 +2323,6 @@
|
|
|
2044
2323
|
if (headerActions && headerActions.contains(target)) return;
|
|
2045
2324
|
closeMobileActionsMenu();
|
|
2046
2325
|
}
|
|
2047
|
-
if (state.modeMenuOpen) {
|
|
2048
|
-
if (modeToggle && modeToggle.contains(target)) return;
|
|
2049
|
-
if (modeMenu && modeMenu.contains(target)) return;
|
|
2050
|
-
closeModeMenu();
|
|
2051
|
-
}
|
|
2052
2326
|
});
|
|
2053
2327
|
|
|
2054
2328
|
removeBtn.addEventListener('click', async function () {
|