@xcanwin/manyoyo 5.7.20 → 5.8.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/lib/web/frontend/app.css +100 -3
- package/lib/web/frontend/app.html +61 -2
- package/lib/web/frontend/app.js +390 -2
- package/lib/web/server.js +207 -22
- package/package.json +1 -1
package/lib/web/frontend/app.css
CHANGED
|
@@ -403,6 +403,79 @@ textarea:focus-visible {
|
|
|
403
403
|
resize: vertical;
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
.agent-template-modal {
|
|
407
|
+
width: min(760px, calc(100vw - 24px));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.agent-template-primary .text-block {
|
|
411
|
+
margin-top: 0;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.agent-template-primary .text-block + .text-block,
|
|
415
|
+
#agentTemplateOverrideGroup {
|
|
416
|
+
margin-top: 12px;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.agent-template-primary select,
|
|
420
|
+
.agent-template-primary textarea {
|
|
421
|
+
border: 1px solid var(--line);
|
|
422
|
+
border-radius: 10px;
|
|
423
|
+
padding: 9px 11px;
|
|
424
|
+
background: var(--panel-strong);
|
|
425
|
+
color: var(--text);
|
|
426
|
+
font-size: 13px;
|
|
427
|
+
font-family: var(--font-ui);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.agent-template-primary textarea {
|
|
431
|
+
min-height: 92px;
|
|
432
|
+
font-family: var(--font-mono);
|
|
433
|
+
resize: vertical;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.agent-template-section {
|
|
437
|
+
padding: 12px;
|
|
438
|
+
border: 1px solid var(--line);
|
|
439
|
+
border-radius: 12px;
|
|
440
|
+
background: rgba(255, 253, 250, 0.92);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.agent-template-section + .agent-template-section {
|
|
444
|
+
margin-top: 12px;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.agent-template-section-title {
|
|
448
|
+
margin-bottom: 8px;
|
|
449
|
+
font-size: 12px;
|
|
450
|
+
font-weight: 700;
|
|
451
|
+
color: var(--text);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.agent-template-section .text-block {
|
|
455
|
+
margin-top: 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.agent-template-section .text-block + .text-block {
|
|
459
|
+
margin-top: 10px;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.agent-template-section select,
|
|
463
|
+
.agent-template-section textarea {
|
|
464
|
+
border: 1px solid var(--line);
|
|
465
|
+
border-radius: 10px;
|
|
466
|
+
padding: 9px 11px;
|
|
467
|
+
background: var(--panel-strong);
|
|
468
|
+
color: var(--text);
|
|
469
|
+
font-size: 13px;
|
|
470
|
+
font-family: var(--font-ui);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.agent-template-section textarea {
|
|
474
|
+
min-height: 92px;
|
|
475
|
+
font-family: var(--font-mono);
|
|
476
|
+
resize: vertical;
|
|
477
|
+
}
|
|
478
|
+
|
|
406
479
|
.session-head {
|
|
407
480
|
display: flex;
|
|
408
481
|
justify-content: space-between;
|
|
@@ -1581,14 +1654,36 @@ details.trace-card > .trace-card-summary {
|
|
|
1581
1654
|
margin-bottom: 10px;
|
|
1582
1655
|
}
|
|
1583
1656
|
|
|
1657
|
+
.composer-toolbar-meta {
|
|
1658
|
+
display: inline-flex;
|
|
1659
|
+
align-items: center;
|
|
1660
|
+
gap: 10px;
|
|
1661
|
+
margin-left: auto;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1584
1664
|
.composer-mode-switch {
|
|
1585
1665
|
display: inline-flex;
|
|
1586
1666
|
gap: 8px;
|
|
1587
1667
|
}
|
|
1588
1668
|
|
|
1589
|
-
.
|
|
1669
|
+
.toolbar-mini-btn {
|
|
1670
|
+
padding: 7px 12px;
|
|
1671
|
+
font-size: 12px;
|
|
1672
|
+
white-space: nowrap;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
.toolbar-chip {
|
|
1676
|
+
display: inline-flex;
|
|
1677
|
+
align-items: center;
|
|
1678
|
+
min-height: 32px;
|
|
1679
|
+
padding: 7px 12px;
|
|
1680
|
+
border: 1px solid var(--line);
|
|
1681
|
+
border-radius: 10px;
|
|
1682
|
+
background: var(--panel-soft);
|
|
1590
1683
|
color: var(--muted);
|
|
1591
1684
|
font-size: 12px;
|
|
1685
|
+
line-height: 1;
|
|
1686
|
+
white-space: nowrap;
|
|
1592
1687
|
}
|
|
1593
1688
|
|
|
1594
1689
|
.composer-inner {
|
|
@@ -1833,8 +1928,10 @@ details.trace-card > .trace-card-summary {
|
|
|
1833
1928
|
align-items: flex-start;
|
|
1834
1929
|
}
|
|
1835
1930
|
|
|
1836
|
-
.composer-toolbar-
|
|
1837
|
-
|
|
1931
|
+
.composer-toolbar-meta {
|
|
1932
|
+
width: 100%;
|
|
1933
|
+
justify-content: space-between;
|
|
1934
|
+
margin-left: 0;
|
|
1838
1935
|
}
|
|
1839
1936
|
|
|
1840
1937
|
.composer-mode-switch {
|
|
@@ -105,7 +105,10 @@
|
|
|
105
105
|
<button type="button" id="activityAgentBtn" class="secondary is-active">Agent</button>
|
|
106
106
|
<button type="button" id="activityCommandBtn" class="secondary">命令</button>
|
|
107
107
|
</div>
|
|
108
|
-
<div class="composer-toolbar-
|
|
108
|
+
<div class="composer-toolbar-meta">
|
|
109
|
+
<button type="button" id="agentTemplateBtn" class="secondary toolbar-mini-btn">CLI · —</button>
|
|
110
|
+
<span id="activityModelChip" class="toolbar-chip" aria-live="polite">模型 · —</span>
|
|
111
|
+
</div>
|
|
109
112
|
</div>
|
|
110
113
|
<div class="composer-inner">
|
|
111
114
|
<textarea id="commandInput" placeholder="输入容器命令,例如: ls -la"></textarea>
|
|
@@ -181,7 +184,15 @@
|
|
|
181
184
|
<label>shellPrefix<input id="createShellPrefix" placeholder="例如 IS_SANDBOX=1" /></label>
|
|
182
185
|
<label>shell<input id="createShell" placeholder="例如 claude / codex" /></label>
|
|
183
186
|
<label>shellSuffix<input id="createShellSuffix" placeholder="例如 --dangerously-skip-permissions" /></label>
|
|
184
|
-
<label>
|
|
187
|
+
<label>CLI
|
|
188
|
+
<select id="createYolo">
|
|
189
|
+
<option value="">(不使用)</option>
|
|
190
|
+
<option value="claude">claude</option>
|
|
191
|
+
<option value="codex">codex</option>
|
|
192
|
+
<option value="gemini">gemini</option>
|
|
193
|
+
<option value="opencode">opencode</option>
|
|
194
|
+
</select>
|
|
195
|
+
</label>
|
|
185
196
|
<label>agentPromptCommand<input id="createAgentPromptCommand" placeholder="例如 codex exec --plain-text {prompt}" /></label>
|
|
186
197
|
</div>
|
|
187
198
|
<label class="text-block">env (KEY=VALUE,每行一项)
|
|
@@ -220,6 +231,54 @@
|
|
|
220
231
|
</div>
|
|
221
232
|
</section>
|
|
222
233
|
</div>
|
|
234
|
+
|
|
235
|
+
<div id="agentTemplateModal" class="modal-backdrop" hidden>
|
|
236
|
+
<section class="modal agent-template-modal" role="dialog" aria-modal="true" aria-labelledby="agentTemplateTitle">
|
|
237
|
+
<header class="modal-header">
|
|
238
|
+
<h2 id="agentTemplateTitle">设置 CLI / Agent 模板</h2>
|
|
239
|
+
<button type="button" id="agentTemplateCancelBtn" class="secondary">关闭</button>
|
|
240
|
+
</header>
|
|
241
|
+
<div class="modal-body">
|
|
242
|
+
<div id="agentTemplatePrimary" class="agent-template-primary">
|
|
243
|
+
<label class="text-block">CLI
|
|
244
|
+
<select id="containerCliSelect">
|
|
245
|
+
<option value="custom">自定义</option>
|
|
246
|
+
<option value="claude">claude</option>
|
|
247
|
+
<option value="codex">codex</option>
|
|
248
|
+
<option value="gemini">gemini</option>
|
|
249
|
+
<option value="opencode">opencode</option>
|
|
250
|
+
</select>
|
|
251
|
+
</label>
|
|
252
|
+
<label class="text-block">高级编辑 agentPromptCommand
|
|
253
|
+
<textarea id="containerAgentPromptEditor" placeholder="例如 codex exec --skip-git-repo-check {prompt}"></textarea>
|
|
254
|
+
</label>
|
|
255
|
+
</div>
|
|
256
|
+
<div id="agentTemplateOverrideGroup">
|
|
257
|
+
<section class="agent-template-section">
|
|
258
|
+
<div class="agent-template-section-title">当前 AGENT 覆盖</div>
|
|
259
|
+
<label class="text-block">CLI
|
|
260
|
+
<select id="agentCliSelect">
|
|
261
|
+
<option value="">继承容器默认</option>
|
|
262
|
+
<option value="custom">自定义</option>
|
|
263
|
+
<option value="claude">claude</option>
|
|
264
|
+
<option value="codex">codex</option>
|
|
265
|
+
<option value="gemini">gemini</option>
|
|
266
|
+
<option value="opencode">opencode</option>
|
|
267
|
+
</select>
|
|
268
|
+
</label>
|
|
269
|
+
<label class="text-block">高级编辑 agentPromptCommand
|
|
270
|
+
<textarea id="agentPromptOverrideEditor" placeholder="留空则继承容器默认模板"></textarea>
|
|
271
|
+
</label>
|
|
272
|
+
</section>
|
|
273
|
+
</div>
|
|
274
|
+
<div id="agentTemplateError" class="modal-error" hidden></div>
|
|
275
|
+
</div>
|
|
276
|
+
<footer class="modal-footer">
|
|
277
|
+
<button type="button" id="agentTemplateResetBtn" class="secondary">恢复当前值</button>
|
|
278
|
+
<button type="button" id="agentTemplateSaveBtn">保存</button>
|
|
279
|
+
</footer>
|
|
280
|
+
</section>
|
|
281
|
+
</div>
|
|
223
282
|
</div>
|
|
224
283
|
|
|
225
284
|
<script src="/app/vendor/xterm.js"></script>
|
package/lib/web/frontend/app.js
CHANGED
|
@@ -50,14 +50,17 @@
|
|
|
50
50
|
mobileActionsOpen: false,
|
|
51
51
|
configModalOpen: false,
|
|
52
52
|
createModalOpen: false,
|
|
53
|
+
agentTemplateModalOpen: false,
|
|
53
54
|
configLoading: false,
|
|
54
55
|
configSaving: false,
|
|
55
56
|
createLoading: false,
|
|
56
57
|
createSubmitting: false,
|
|
58
|
+
agentTemplateSaving: false,
|
|
57
59
|
configSnapshot: null,
|
|
58
60
|
sessionDetail: null,
|
|
59
61
|
sessionDetailError: '',
|
|
60
62
|
sessionDetailRequestId: 0,
|
|
63
|
+
agentTemplateError: '',
|
|
61
64
|
createAgentPromptAuto: false,
|
|
62
65
|
createContainerPathBase: '',
|
|
63
66
|
createDefaults: null,
|
|
@@ -167,6 +170,8 @@
|
|
|
167
170
|
const activeMeta = document.getElementById('activeMeta');
|
|
168
171
|
const activityCommandBtn = document.getElementById('activityCommandBtn');
|
|
169
172
|
const activityAgentBtn = document.getElementById('activityAgentBtn');
|
|
173
|
+
const agentTemplateBtn = document.getElementById('agentTemplateBtn');
|
|
174
|
+
const activityModelChip = document.getElementById('activityModelChip');
|
|
170
175
|
const messagesNode = document.getElementById('messages');
|
|
171
176
|
const terminalPanel = document.getElementById('terminalPanel');
|
|
172
177
|
const detailPanel = document.getElementById('detailPanel');
|
|
@@ -182,6 +187,17 @@
|
|
|
182
187
|
const sendState = document.getElementById('sendState');
|
|
183
188
|
const sendBtn = document.getElementById('sendBtn');
|
|
184
189
|
const stopBtn = document.getElementById('stopBtn');
|
|
190
|
+
const agentTemplateModal = document.getElementById('agentTemplateModal');
|
|
191
|
+
const agentTemplatePrimary = document.getElementById('agentTemplatePrimary');
|
|
192
|
+
const containerCliSelect = document.getElementById('containerCliSelect');
|
|
193
|
+
const containerAgentPromptEditor = document.getElementById('containerAgentPromptEditor');
|
|
194
|
+
const agentTemplateOverrideGroup = document.getElementById('agentTemplateOverrideGroup');
|
|
195
|
+
const agentCliSelect = document.getElementById('agentCliSelect');
|
|
196
|
+
const agentPromptOverrideEditor = document.getElementById('agentPromptOverrideEditor');
|
|
197
|
+
const agentTemplateError = document.getElementById('agentTemplateError');
|
|
198
|
+
const agentTemplateCancelBtn = document.getElementById('agentTemplateCancelBtn');
|
|
199
|
+
const agentTemplateResetBtn = document.getElementById('agentTemplateResetBtn');
|
|
200
|
+
const agentTemplateSaveBtn = document.getElementById('agentTemplateSaveBtn');
|
|
185
201
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
186
202
|
const removeBtn = document.getElementById('removeBtn');
|
|
187
203
|
const removeAllBtn = document.getElementById('removeAllBtn');
|
|
@@ -192,6 +208,8 @@
|
|
|
192
208
|
const TERMINAL_MIN_ROWS = 12;
|
|
193
209
|
const TERMINAL_DEFAULT_COLS = 120;
|
|
194
210
|
const TERMINAL_DEFAULT_ROWS = 36;
|
|
211
|
+
const WEB_SESSION_KEY_SEPARATOR = '~';
|
|
212
|
+
const WEB_DEFAULT_AGENT_ID = 'default';
|
|
195
213
|
const YOLO_COMMAND_MAP = {
|
|
196
214
|
claude: 'IS_SANDBOX=1 claude --dangerously-skip-permissions',
|
|
197
215
|
cc: 'IS_SANDBOX=1 claude --dangerously-skip-permissions',
|
|
@@ -214,6 +232,12 @@
|
|
|
214
232
|
const GEMINI_YOLO_FLAG = '--yolo';
|
|
215
233
|
const CODEX_DANGEROUS_FLAG = '--dangerously-bypass-approvals-and-sandbox';
|
|
216
234
|
const OPENCODE_PERMISSION_KEY = 'OPENCODE_PERMISSION=';
|
|
235
|
+
const AGENT_TEMPLATE_CLI_COMMAND_MAP = {
|
|
236
|
+
claude: YOLO_COMMAND_MAP.claude,
|
|
237
|
+
codex: YOLO_COMMAND_MAP.codex,
|
|
238
|
+
gemini: YOLO_COMMAND_MAP.gemini,
|
|
239
|
+
opencode: YOLO_COMMAND_MAP.opencode
|
|
240
|
+
};
|
|
217
241
|
const SIDEBAR_TREE_STORAGE_KEY = 'manyoyo.web.sidebarTree.v1';
|
|
218
242
|
const markdownRenderer = window.ManyoyoMarkdown
|
|
219
243
|
&& typeof window.ManyoyoMarkdown.shouldRenderMessage === 'function'
|
|
@@ -813,6 +837,26 @@
|
|
|
813
837
|
return YOLO_COMMAND_MAP[key] || '';
|
|
814
838
|
}
|
|
815
839
|
|
|
840
|
+
function normalizeCreateYoloValue(yolo) {
|
|
841
|
+
const key = String(yolo || '').trim().toLowerCase();
|
|
842
|
+
if (!key) {
|
|
843
|
+
return '';
|
|
844
|
+
}
|
|
845
|
+
if (key === 'claude' || key === 'cc' || key === 'c') {
|
|
846
|
+
return 'claude';
|
|
847
|
+
}
|
|
848
|
+
if (key === 'codex' || key === 'cx') {
|
|
849
|
+
return 'codex';
|
|
850
|
+
}
|
|
851
|
+
if (key === 'gemini' || key === 'gm' || key === 'g') {
|
|
852
|
+
return 'gemini';
|
|
853
|
+
}
|
|
854
|
+
if (key === 'opencode' || key === 'oc') {
|
|
855
|
+
return 'opencode';
|
|
856
|
+
}
|
|
857
|
+
return '';
|
|
858
|
+
}
|
|
859
|
+
|
|
816
860
|
function stripLeadingAssignments(commandText) {
|
|
817
861
|
let rest = String(commandText || '').trim();
|
|
818
862
|
const assignmentPattern = /^(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)(?:\s+|$)/;
|
|
@@ -912,7 +956,7 @@
|
|
|
912
956
|
createShellSuffix.value = value.shellSuffix || '';
|
|
913
957
|
createAgentPromptCommand.value = value.agentPromptCommand || '';
|
|
914
958
|
state.createAgentPromptAuto = false;
|
|
915
|
-
createYolo.value = value.yolo
|
|
959
|
+
createYolo.value = normalizeCreateYoloValue(value.yolo);
|
|
916
960
|
// 敏感 env 与继承数组由服务端在创建时合并,前端表单默认不回显,避免泄露或重复提交。
|
|
917
961
|
createEnv.value = '';
|
|
918
962
|
createEnvFile.value = '';
|
|
@@ -1055,6 +1099,214 @@
|
|
|
1055
1099
|
}) || null;
|
|
1056
1100
|
}
|
|
1057
1101
|
|
|
1102
|
+
function parseSessionKey(sessionName) {
|
|
1103
|
+
const raw = String(sessionName || '').trim();
|
|
1104
|
+
if (!raw) {
|
|
1105
|
+
return {
|
|
1106
|
+
containerName: '',
|
|
1107
|
+
agentId: WEB_DEFAULT_AGENT_ID
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
const separatorIndex = raw.indexOf(WEB_SESSION_KEY_SEPARATOR);
|
|
1111
|
+
if (separatorIndex === -1) {
|
|
1112
|
+
return {
|
|
1113
|
+
containerName: raw,
|
|
1114
|
+
agentId: WEB_DEFAULT_AGENT_ID
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
return {
|
|
1118
|
+
containerName: raw.slice(0, separatorIndex),
|
|
1119
|
+
agentId: raw.slice(separatorIndex + 1) || WEB_DEFAULT_AGENT_ID
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function isActiveAgentOverrideEditable() {
|
|
1124
|
+
const sessionRef = parseSessionKey(state.active);
|
|
1125
|
+
return Boolean(state.active && sessionRef.agentId && sessionRef.agentId !== WEB_DEFAULT_AGENT_ID);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function showAgentTemplateError(message) {
|
|
1129
|
+
state.agentTemplateError = String(message || '').trim();
|
|
1130
|
+
if (!agentTemplateError) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
agentTemplateError.hidden = !state.agentTemplateError;
|
|
1134
|
+
agentTemplateError.textContent = state.agentTemplateError;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function inferTemplateCliValue(templateText, options) {
|
|
1138
|
+
const text = String(templateText || '').trim();
|
|
1139
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
1140
|
+
if (!text) {
|
|
1141
|
+
return opts.allowEmpty ? '' : 'custom';
|
|
1142
|
+
}
|
|
1143
|
+
const program = resolveAgentProgram(text);
|
|
1144
|
+
return AGENT_PROMPT_TEMPLATE_MAP[program] ? program : 'custom';
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function buildTemplateFromCliValue(cliValue) {
|
|
1148
|
+
const cli = String(cliValue || '').trim().toLowerCase();
|
|
1149
|
+
if (!cli || cli === 'custom') {
|
|
1150
|
+
return '';
|
|
1151
|
+
}
|
|
1152
|
+
const baseCommand = AGENT_TEMPLATE_CLI_COMMAND_MAP[cli] || '';
|
|
1153
|
+
if (!baseCommand) {
|
|
1154
|
+
return '';
|
|
1155
|
+
}
|
|
1156
|
+
return resolveAgentPromptTemplate(baseCommand);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function syncAgentTemplateSelectFromEditor(selectNode, editorNode, options) {
|
|
1160
|
+
if (!selectNode || !editorNode) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const value = inferTemplateCliValue(editorNode.value, options);
|
|
1164
|
+
selectNode.value = value;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function getInheritedContainerTemplateText() {
|
|
1168
|
+
if (containerAgentPromptEditor) {
|
|
1169
|
+
const editorText = String(containerAgentPromptEditor.value || '').trim();
|
|
1170
|
+
if (editorText) {
|
|
1171
|
+
return editorText;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (state.sessionDetail && typeof state.sessionDetail.containerAgentPromptCommand === 'string') {
|
|
1175
|
+
const containerText = String(state.sessionDetail.containerAgentPromptCommand || '').trim();
|
|
1176
|
+
if (containerText) {
|
|
1177
|
+
return containerText;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (state.sessionDetail && typeof state.sessionDetail.agentPromptCommand === 'string') {
|
|
1181
|
+
const effectiveText = String(state.sessionDetail.agentPromptCommand || '').trim();
|
|
1182
|
+
if (effectiveText) {
|
|
1183
|
+
return effectiveText;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return '';
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function applyAgentTemplateCliSelection(selectNode, editorNode, options) {
|
|
1190
|
+
if (!selectNode || !editorNode) {
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
1194
|
+
const cliValue = String(selectNode.value || '').trim();
|
|
1195
|
+
if (!cliValue && opts.allowEmpty === true) {
|
|
1196
|
+
editorNode.value = getInheritedContainerTemplateText();
|
|
1197
|
+
showAgentTemplateError('');
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (cliValue === 'custom') {
|
|
1201
|
+
showAgentTemplateError('');
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
const nextTemplate = buildTemplateFromCliValue(cliValue);
|
|
1205
|
+
if (nextTemplate) {
|
|
1206
|
+
editorNode.value = nextTemplate;
|
|
1207
|
+
}
|
|
1208
|
+
showAgentTemplateError('');
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function fillAgentTemplateForm(detail) {
|
|
1212
|
+
const currentDetail = detail && typeof detail === 'object' ? detail : {};
|
|
1213
|
+
const overrideEditable = isActiveAgentOverrideEditable();
|
|
1214
|
+
const containerTemplateText = String(
|
|
1215
|
+
currentDetail.containerAgentPromptCommand || currentDetail.agentPromptCommand || ''
|
|
1216
|
+
).trim();
|
|
1217
|
+
if (containerAgentPromptEditor) {
|
|
1218
|
+
containerAgentPromptEditor.value = containerTemplateText;
|
|
1219
|
+
}
|
|
1220
|
+
if (agentPromptOverrideEditor) {
|
|
1221
|
+
agentPromptOverrideEditor.value = currentDetail.agentPromptCommandOverride || containerTemplateText;
|
|
1222
|
+
}
|
|
1223
|
+
syncAgentTemplateSelectFromEditor(containerCliSelect, containerAgentPromptEditor);
|
|
1224
|
+
if (agentCliSelect) {
|
|
1225
|
+
agentCliSelect.value = currentDetail.agentPromptCommandOverride ? inferTemplateCliValue(currentDetail.agentPromptCommandOverride, { allowEmpty: true }) : '';
|
|
1226
|
+
}
|
|
1227
|
+
if (agentTemplatePrimary) {
|
|
1228
|
+
agentTemplatePrimary.hidden = overrideEditable;
|
|
1229
|
+
}
|
|
1230
|
+
if (agentTemplateOverrideGroup) {
|
|
1231
|
+
agentTemplateOverrideGroup.hidden = !overrideEditable;
|
|
1232
|
+
}
|
|
1233
|
+
showAgentTemplateError('');
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
async function ensureActiveSessionDetail() {
|
|
1237
|
+
if (!state.active) {
|
|
1238
|
+
return null;
|
|
1239
|
+
}
|
|
1240
|
+
if (state.sessionDetail && state.active === (state.sessionDetail.name || state.active)) {
|
|
1241
|
+
return state.sessionDetail;
|
|
1242
|
+
}
|
|
1243
|
+
await loadSessionDetailForSession(state.active);
|
|
1244
|
+
return state.sessionDetail;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
async function openAgentTemplateModal() {
|
|
1248
|
+
if (!state.active || state.agentTemplateSaving) {
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
const detail = await ensureActiveSessionDetail();
|
|
1252
|
+
if (!detail) {
|
|
1253
|
+
alert(state.sessionDetailError || '当前会话详情暂时不可用');
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
state.agentTemplateModalOpen = true;
|
|
1257
|
+
fillAgentTemplateForm(detail);
|
|
1258
|
+
syncUi();
|
|
1259
|
+
if (isActiveAgentOverrideEditable() && agentCliSelect) {
|
|
1260
|
+
agentCliSelect.focus();
|
|
1261
|
+
} else if (containerCliSelect) {
|
|
1262
|
+
containerCliSelect.focus();
|
|
1263
|
+
} else if (containerAgentPromptEditor) {
|
|
1264
|
+
containerAgentPromptEditor.focus();
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function closeAgentTemplateModal() {
|
|
1269
|
+
state.agentTemplateModalOpen = false;
|
|
1270
|
+
showAgentTemplateError('');
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
function resetAgentTemplateModal() {
|
|
1274
|
+
fillAgentTemplateForm(state.sessionDetail || {});
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
async function saveAgentTemplateModal() {
|
|
1278
|
+
if (!state.active || state.agentTemplateSaving) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
state.agentTemplateSaving = true;
|
|
1282
|
+
showAgentTemplateError('');
|
|
1283
|
+
syncUi();
|
|
1284
|
+
try {
|
|
1285
|
+
const payload = {};
|
|
1286
|
+
if (isActiveAgentOverrideEditable()) {
|
|
1287
|
+
if (agentCliSelect && String(agentCliSelect.value || '').trim() === '') {
|
|
1288
|
+
payload.agentPromptCommandOverride = '';
|
|
1289
|
+
} else if (agentPromptOverrideEditor) {
|
|
1290
|
+
payload.agentPromptCommandOverride = String(agentPromptOverrideEditor.value || '').trim();
|
|
1291
|
+
}
|
|
1292
|
+
} else if (containerAgentPromptEditor) {
|
|
1293
|
+
payload.containerAgentPromptCommand = String(containerAgentPromptEditor.value || '').trim();
|
|
1294
|
+
}
|
|
1295
|
+
const data = await api('/api/sessions/' + encodeURIComponent(state.active) + '/agent-template', {
|
|
1296
|
+
method: 'PUT',
|
|
1297
|
+
body: JSON.stringify(payload)
|
|
1298
|
+
});
|
|
1299
|
+
state.sessionDetail = data && data.detail ? data.detail : state.sessionDetail;
|
|
1300
|
+
await refreshSessionsSilent({ preferredName: state.active });
|
|
1301
|
+
closeAgentTemplateModal();
|
|
1302
|
+
} catch (e) {
|
|
1303
|
+
showAgentTemplateError(e && e.message ? e.message : '保存失败');
|
|
1304
|
+
} finally {
|
|
1305
|
+
state.agentTemplateSaving = false;
|
|
1306
|
+
syncUi();
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1058
1310
|
function isAgentRunActiveForSession(sessionName) {
|
|
1059
1311
|
return Boolean(
|
|
1060
1312
|
state.agentRun
|
|
@@ -1078,6 +1330,24 @@
|
|
|
1078
1330
|
return Boolean(active && active.agentEnabled);
|
|
1079
1331
|
}
|
|
1080
1332
|
|
|
1333
|
+
function resolveToolbarCliLabel() {
|
|
1334
|
+
const activeSession = getActiveSession();
|
|
1335
|
+
const detail = state.sessionDetail && state.active ? state.sessionDetail : null;
|
|
1336
|
+
const agentProgram = String(
|
|
1337
|
+
(detail && detail.agentProgram)
|
|
1338
|
+
|| (activeSession && activeSession.agentProgram)
|
|
1339
|
+
|| ''
|
|
1340
|
+
).trim();
|
|
1341
|
+
return agentProgram || '未配置';
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function resolveToolbarModelLabel() {
|
|
1345
|
+
if (!state.active) {
|
|
1346
|
+
return '—';
|
|
1347
|
+
}
|
|
1348
|
+
return '自动';
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1081
1351
|
function buildActiveMeta(session) {
|
|
1082
1352
|
if (!session) {
|
|
1083
1353
|
return '会话不可用';
|
|
@@ -1566,6 +1836,13 @@
|
|
|
1566
1836
|
let resumeStatusDetail = detail.resumeSupported
|
|
1567
1837
|
? '支持 resume,但当前会话还没有最近一次执行记录。'
|
|
1568
1838
|
: '当前 Agent 程序或模板不支持 resume。';
|
|
1839
|
+
const templateSourceMap = {
|
|
1840
|
+
agent: '当前 AGENT 覆盖',
|
|
1841
|
+
container: '容器默认模板',
|
|
1842
|
+
inferred: '从启动命令推导',
|
|
1843
|
+
none: '未配置'
|
|
1844
|
+
};
|
|
1845
|
+
const templateSourceLabel = templateSourceMap[detail.agentPromptSource] || '未配置';
|
|
1569
1846
|
if (detail.lastResumeOk === true) {
|
|
1570
1847
|
resumeStatusValue = '最近成功';
|
|
1571
1848
|
resumeStatusTone = 'ok';
|
|
@@ -1596,6 +1873,7 @@
|
|
|
1596
1873
|
commandEntries.push({ label: '启动命令', value: applied.defaultCommand || '—' });
|
|
1597
1874
|
}
|
|
1598
1875
|
commandEntries.push({ label: 'Agent 模板', value: detail.agentPromptCommand || '—' });
|
|
1876
|
+
commandEntries.push({ label: '模板来源', value: templateSourceLabel });
|
|
1599
1877
|
commandEntries.push({ label: 'yolo', value: applied.yolo || '—' });
|
|
1600
1878
|
|
|
1601
1879
|
if (detailSummary) {
|
|
@@ -1611,6 +1889,7 @@
|
|
|
1611
1889
|
renderKeyValueCard(detailSummary, 'Agent 运行', [
|
|
1612
1890
|
{ label: '已启用', value: detail.agentEnabled ? '是' : '否', tone: detail.agentEnabled ? 'ok' : 'warn' },
|
|
1613
1891
|
{ label: '程序', value: detail.agentProgram || '—' },
|
|
1892
|
+
{ label: '模板来源', value: templateSourceLabel },
|
|
1614
1893
|
{ label: '支持 resume', value: detail.resumeSupported ? '是' : '否', tone: detail.resumeSupported ? 'ok' : 'warn' },
|
|
1615
1894
|
{ label: '最近 resume', value: lastResumeText },
|
|
1616
1895
|
{ label: '最近结果', value: detail.lastResumeOk == null ? '暂无' : (detail.lastResumeOk ? '成功' : '失败'), tone: detail.lastResumeOk == null ? 'info' : (detail.lastResumeOk ? 'ok' : 'danger') }
|
|
@@ -1728,6 +2007,18 @@
|
|
|
1728
2007
|
activityAgentBtn.classList.toggle('is-active', agentMode);
|
|
1729
2008
|
activityAgentBtn.setAttribute('aria-pressed', agentMode ? 'true' : 'false');
|
|
1730
2009
|
}
|
|
2010
|
+
if (agentTemplateBtn) {
|
|
2011
|
+
agentTemplateBtn.textContent = `CLI · ${resolveToolbarCliLabel()}`;
|
|
2012
|
+
agentTemplateBtn.title = state.active
|
|
2013
|
+
? '查看或修改当前会话的 CLI 模板'
|
|
2014
|
+
: '请先选择会话';
|
|
2015
|
+
}
|
|
2016
|
+
if (activityModelChip) {
|
|
2017
|
+
activityModelChip.textContent = `模型 · ${resolveToolbarModelLabel()}`;
|
|
2018
|
+
activityModelChip.title = state.active
|
|
2019
|
+
? '当前版本暂不单独配置模型,默认跟随 CLI 或容器内配置'
|
|
2020
|
+
: '请先选择会话';
|
|
2021
|
+
}
|
|
1731
2022
|
if (viewActivityBtn) viewActivityBtn.classList.toggle('is-active', activityTab);
|
|
1732
2023
|
if (viewTerminalBtn) viewTerminalBtn.classList.toggle('is-active', terminalTab);
|
|
1733
2024
|
if (viewDetailBtn) viewDetailBtn.classList.toggle('is-active', detailTab);
|
|
@@ -1764,6 +2055,9 @@
|
|
|
1764
2055
|
if (stopBtn) {
|
|
1765
2056
|
stopBtn.disabled = !activityTab || !agentMode || !activeAgentRunning || state.agentRun.stopping;
|
|
1766
2057
|
}
|
|
2058
|
+
if (agentTemplateBtn) {
|
|
2059
|
+
agentTemplateBtn.disabled = !state.active || state.agentTemplateSaving;
|
|
2060
|
+
}
|
|
1767
2061
|
commandInput.disabled = !activityTab || !state.active || (agentMode && !agentEnabled);
|
|
1768
2062
|
if (commandInput) {
|
|
1769
2063
|
commandInput.placeholder = agentMode
|
|
@@ -1823,9 +2117,33 @@
|
|
|
1823
2117
|
if (directoryPickerModal) {
|
|
1824
2118
|
directoryPickerModal.hidden = !state.directoryPicker.open;
|
|
1825
2119
|
}
|
|
2120
|
+
if (agentTemplateModal) {
|
|
2121
|
+
agentTemplateModal.hidden = !state.agentTemplateModalOpen;
|
|
2122
|
+
}
|
|
2123
|
+
if (agentTemplateSaveBtn) {
|
|
2124
|
+
agentTemplateSaveBtn.disabled = state.agentTemplateSaving || !state.active;
|
|
2125
|
+
}
|
|
2126
|
+
if (agentTemplateResetBtn) {
|
|
2127
|
+
agentTemplateResetBtn.disabled = state.agentTemplateSaving;
|
|
2128
|
+
}
|
|
2129
|
+
if (agentTemplateCancelBtn) {
|
|
2130
|
+
agentTemplateCancelBtn.disabled = state.agentTemplateSaving;
|
|
2131
|
+
}
|
|
2132
|
+
if (containerCliSelect) {
|
|
2133
|
+
containerCliSelect.disabled = state.agentTemplateSaving || isActiveAgentOverrideEditable();
|
|
2134
|
+
}
|
|
2135
|
+
if (containerAgentPromptEditor) {
|
|
2136
|
+
containerAgentPromptEditor.disabled = state.agentTemplateSaving || isActiveAgentOverrideEditable();
|
|
2137
|
+
}
|
|
2138
|
+
if (agentCliSelect) {
|
|
2139
|
+
agentCliSelect.disabled = state.agentTemplateSaving || !isActiveAgentOverrideEditable();
|
|
2140
|
+
}
|
|
2141
|
+
if (agentPromptOverrideEditor) {
|
|
2142
|
+
agentPromptOverrideEditor.disabled = state.agentTemplateSaving || !isActiveAgentOverrideEditable();
|
|
2143
|
+
}
|
|
1826
2144
|
document.body.classList.toggle(
|
|
1827
2145
|
'modal-open',
|
|
1828
|
-
state.configModalOpen || state.createModalOpen || state.directoryPicker.open
|
|
2146
|
+
state.configModalOpen || state.createModalOpen || state.directoryPicker.open || state.agentTemplateModalOpen
|
|
1829
2147
|
);
|
|
1830
2148
|
if (!state.active) {
|
|
1831
2149
|
sendState.textContent = '未选择会话';
|
|
@@ -3009,6 +3327,9 @@
|
|
|
3009
3327
|
return;
|
|
3010
3328
|
}
|
|
3011
3329
|
state.sessionDetail = data && data.detail ? data.detail : null;
|
|
3330
|
+
if (state.agentTemplateModalOpen && targetSession === state.active) {
|
|
3331
|
+
fillAgentTemplateForm(state.sessionDetail || {});
|
|
3332
|
+
}
|
|
3012
3333
|
} catch (e) {
|
|
3013
3334
|
if (requestId !== state.sessionDetailRequestId) {
|
|
3014
3335
|
return;
|
|
@@ -3296,6 +3617,14 @@
|
|
|
3296
3617
|
});
|
|
3297
3618
|
}
|
|
3298
3619
|
|
|
3620
|
+
if (agentTemplateBtn) {
|
|
3621
|
+
agentTemplateBtn.addEventListener('click', function () {
|
|
3622
|
+
openAgentTemplateModal().catch(function (e) {
|
|
3623
|
+
alert(e && e.message ? e.message : '加载 Agent 模板失败');
|
|
3624
|
+
});
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3299
3628
|
if (configCancelBtn) {
|
|
3300
3629
|
configCancelBtn.addEventListener('click', function () {
|
|
3301
3630
|
closeConfigModal();
|
|
@@ -3360,6 +3689,49 @@
|
|
|
3360
3689
|
});
|
|
3361
3690
|
}
|
|
3362
3691
|
|
|
3692
|
+
if (containerCliSelect) {
|
|
3693
|
+
containerCliSelect.addEventListener('change', function () {
|
|
3694
|
+
applyAgentTemplateCliSelection(containerCliSelect, containerAgentPromptEditor);
|
|
3695
|
+
});
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
if (agentCliSelect) {
|
|
3699
|
+
agentCliSelect.addEventListener('change', function () {
|
|
3700
|
+
applyAgentTemplateCliSelection(agentCliSelect, agentPromptOverrideEditor, { allowEmpty: true });
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
if (containerAgentPromptEditor) {
|
|
3705
|
+
containerAgentPromptEditor.addEventListener('input', function () {
|
|
3706
|
+
syncAgentTemplateSelectFromEditor(containerCliSelect, containerAgentPromptEditor);
|
|
3707
|
+
});
|
|
3708
|
+
}
|
|
3709
|
+
|
|
3710
|
+
if (agentPromptOverrideEditor) {
|
|
3711
|
+
agentPromptOverrideEditor.addEventListener('input', function () {
|
|
3712
|
+
syncAgentTemplateSelectFromEditor(agentCliSelect, agentPromptOverrideEditor, { allowEmpty: true });
|
|
3713
|
+
});
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
if (agentTemplateCancelBtn) {
|
|
3717
|
+
agentTemplateCancelBtn.addEventListener('click', function () {
|
|
3718
|
+
closeAgentTemplateModal();
|
|
3719
|
+
syncUi();
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
if (agentTemplateResetBtn) {
|
|
3724
|
+
agentTemplateResetBtn.addEventListener('click', function () {
|
|
3725
|
+
resetAgentTemplateModal();
|
|
3726
|
+
});
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
if (agentTemplateSaveBtn) {
|
|
3730
|
+
agentTemplateSaveBtn.addEventListener('click', function () {
|
|
3731
|
+
saveAgentTemplateModal();
|
|
3732
|
+
});
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3363
3735
|
if (createRun) {
|
|
3364
3736
|
createRun.addEventListener('change', function () {
|
|
3365
3737
|
applyCurrentRunDefaults();
|
|
@@ -3372,6 +3744,9 @@
|
|
|
3372
3744
|
inputNode.addEventListener('input', function () {
|
|
3373
3745
|
updateCreateAgentPromptCommandFromCommand();
|
|
3374
3746
|
});
|
|
3747
|
+
inputNode.addEventListener('change', function () {
|
|
3748
|
+
updateCreateAgentPromptCommandFromCommand();
|
|
3749
|
+
});
|
|
3375
3750
|
});
|
|
3376
3751
|
|
|
3377
3752
|
if (createAgentPromptCommand) {
|
|
@@ -3686,6 +4061,15 @@
|
|
|
3686
4061
|
});
|
|
3687
4062
|
}
|
|
3688
4063
|
|
|
4064
|
+
if (agentTemplateModal) {
|
|
4065
|
+
agentTemplateModal.addEventListener('click', function (event) {
|
|
4066
|
+
if (event.target === agentTemplateModal && !state.agentTemplateSaving) {
|
|
4067
|
+
closeAgentTemplateModal();
|
|
4068
|
+
syncUi();
|
|
4069
|
+
}
|
|
4070
|
+
});
|
|
4071
|
+
}
|
|
4072
|
+
|
|
3689
4073
|
window.addEventListener('keydown', function (event) {
|
|
3690
4074
|
if (event.key === 'Escape' && state.configModalOpen) {
|
|
3691
4075
|
closeConfigModal();
|
|
@@ -3698,6 +4082,10 @@
|
|
|
3698
4082
|
if (event.key === 'Escape' && state.directoryPicker.open) {
|
|
3699
4083
|
closeDirectoryPicker();
|
|
3700
4084
|
}
|
|
4085
|
+
if (event.key === 'Escape' && state.agentTemplateModalOpen) {
|
|
4086
|
+
closeAgentTemplateModal();
|
|
4087
|
+
syncUi();
|
|
4088
|
+
}
|
|
3701
4089
|
if (event.key === 'Escape' && state.mobileSidebarOpen) {
|
|
3702
4090
|
closeMobileSessionPanel();
|
|
3703
4091
|
}
|
package/lib/web/server.js
CHANGED
|
@@ -137,6 +137,7 @@ function createEmptyWebAgentSession(agentId, agentName) {
|
|
|
137
137
|
return {
|
|
138
138
|
agentId,
|
|
139
139
|
agentName: normalizeWebAgentName(agentId, agentName),
|
|
140
|
+
agentPromptCommand: '',
|
|
140
141
|
updatedAt: null,
|
|
141
142
|
messages: [],
|
|
142
143
|
lastResumeAt: null,
|
|
@@ -150,6 +151,9 @@ function normalizeWebAgentSessionRecord(agentId, rawAgent) {
|
|
|
150
151
|
return {
|
|
151
152
|
agentId,
|
|
152
153
|
agentName: normalizeWebAgentName(agentId, source.agentName),
|
|
154
|
+
agentPromptCommand: typeof source.agentPromptCommand === 'string'
|
|
155
|
+
? normalizeAgentPromptCommandTemplate(source.agentPromptCommand, `agents.${agentId}.agentPromptCommand`)
|
|
156
|
+
: '',
|
|
153
157
|
updatedAt: typeof source.updatedAt === 'string' ? source.updatedAt : null,
|
|
154
158
|
messages: Array.isArray(source.messages) ? source.messages : [],
|
|
155
159
|
lastResumeAt: typeof source.lastResumeAt === 'string' ? source.lastResumeAt : null,
|
|
@@ -253,7 +257,7 @@ function saveWebSessionHistory(webHistoryDir, containerName, history) {
|
|
|
253
257
|
ensureWebHistoryDir(webHistoryDir);
|
|
254
258
|
const filePath = getWebHistoryFile(webHistoryDir, containerName);
|
|
255
259
|
const normalized = normalizeWebHistoryRecord(containerName, history);
|
|
256
|
-
const runtimeMeta = getAgentRuntimeMeta(normalized);
|
|
260
|
+
const runtimeMeta = getAgentRuntimeMeta(normalized.agentPromptCommand || '');
|
|
257
261
|
const defaultAgent = getWebAgentSession(normalized, WEB_DEFAULT_AGENT_ID) || createEmptyWebAgentSession(WEB_DEFAULT_AGENT_ID);
|
|
258
262
|
const legacyCompatible = {
|
|
259
263
|
...normalized,
|
|
@@ -383,6 +387,81 @@ function setWebSessionAgentPromptCommand(webHistoryDir, containerName, agentProm
|
|
|
383
387
|
saveWebSessionHistory(webHistoryDir, containerName, history);
|
|
384
388
|
}
|
|
385
389
|
|
|
390
|
+
function setWebAgentSessionPromptCommand(webHistoryDir, sessionRefOrContainerName, agentPromptCommand) {
|
|
391
|
+
const sessionRef = typeof sessionRefOrContainerName === 'string'
|
|
392
|
+
? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
|
|
393
|
+
: sessionRefOrContainerName;
|
|
394
|
+
if (sessionRef.agentId === WEB_DEFAULT_AGENT_ID) {
|
|
395
|
+
throw new Error('默认 AGENT 请直接修改容器级 agentPromptCommand');
|
|
396
|
+
}
|
|
397
|
+
const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
|
|
398
|
+
const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
|
|
399
|
+
agentSession.agentPromptCommand = normalizeAgentPromptCommandTemplate(
|
|
400
|
+
agentPromptCommand,
|
|
401
|
+
`agents.${sessionRef.agentId}.agentPromptCommand`
|
|
402
|
+
);
|
|
403
|
+
saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function deriveAgentPromptCommandFromDefaultCommand(defaultCommand) {
|
|
407
|
+
const normalizedCommand = String(defaultCommand || '').trim();
|
|
408
|
+
if (!normalizedCommand) {
|
|
409
|
+
return '';
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
return normalizeAgentPromptCommandTemplate(
|
|
413
|
+
resolveAgentPromptCommandTemplate(normalizedCommand),
|
|
414
|
+
'agentPromptCommand'
|
|
415
|
+
);
|
|
416
|
+
} catch (e) {
|
|
417
|
+
return '';
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function resolveEffectiveSessionAgentPromptCommand(history, defaultCommand) {
|
|
422
|
+
return resolveEffectiveAgentPromptCommandForSession(history, WEB_DEFAULT_AGENT_ID, defaultCommand);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function resolveEffectiveAgentPromptCommandForSession(history, agentId, defaultCommand) {
|
|
426
|
+
const sessionHistory = history && typeof history === 'object' ? history : {};
|
|
427
|
+
const requestedAgentId = String(agentId || WEB_DEFAULT_AGENT_ID).trim() || WEB_DEFAULT_AGENT_ID;
|
|
428
|
+
const agentSession = getWebAgentSession(sessionHistory, requestedAgentId);
|
|
429
|
+
const agentTemplate = agentSession && typeof agentSession.agentPromptCommand === 'string'
|
|
430
|
+
? normalizeAgentPromptCommandTemplate(agentSession.agentPromptCommand, `agents.${requestedAgentId}.agentPromptCommand`)
|
|
431
|
+
: '';
|
|
432
|
+
if (isAgentPromptCommandEnabled(agentTemplate)) {
|
|
433
|
+
return agentTemplate;
|
|
434
|
+
}
|
|
435
|
+
const historyTemplate = typeof sessionHistory.agentPromptCommand === 'string'
|
|
436
|
+
? normalizeAgentPromptCommandTemplate(sessionHistory.agentPromptCommand, 'agentPromptCommand')
|
|
437
|
+
: '';
|
|
438
|
+
if (isAgentPromptCommandEnabled(historyTemplate)) {
|
|
439
|
+
return historyTemplate;
|
|
440
|
+
}
|
|
441
|
+
return deriveAgentPromptCommandFromDefaultCommand(defaultCommand);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function getEffectiveAgentPromptCommandSource(history, agentId, defaultCommand) {
|
|
445
|
+
const sessionHistory = history && typeof history === 'object' ? history : {};
|
|
446
|
+
const requestedAgentId = String(agentId || WEB_DEFAULT_AGENT_ID).trim() || WEB_DEFAULT_AGENT_ID;
|
|
447
|
+
const agentSession = getWebAgentSession(sessionHistory, requestedAgentId);
|
|
448
|
+
const agentTemplate = agentSession && typeof agentSession.agentPromptCommand === 'string'
|
|
449
|
+
? normalizeAgentPromptCommandTemplate(agentSession.agentPromptCommand, `agents.${requestedAgentId}.agentPromptCommand`)
|
|
450
|
+
: '';
|
|
451
|
+
if (isAgentPromptCommandEnabled(agentTemplate)) {
|
|
452
|
+
return 'agent';
|
|
453
|
+
}
|
|
454
|
+
const historyTemplate = typeof sessionHistory.agentPromptCommand === 'string'
|
|
455
|
+
? normalizeAgentPromptCommandTemplate(sessionHistory.agentPromptCommand, 'agentPromptCommand')
|
|
456
|
+
: '';
|
|
457
|
+
if (isAgentPromptCommandEnabled(historyTemplate)) {
|
|
458
|
+
return 'container';
|
|
459
|
+
}
|
|
460
|
+
return isAgentPromptCommandEnabled(deriveAgentPromptCommandFromDefaultCommand(defaultCommand))
|
|
461
|
+
? 'inferred'
|
|
462
|
+
: 'none';
|
|
463
|
+
}
|
|
464
|
+
|
|
386
465
|
function patchWebSessionHistory(webHistoryDir, containerName, patch) {
|
|
387
466
|
const history = loadWebSessionHistory(webHistoryDir, containerName);
|
|
388
467
|
if (!patch || typeof patch !== 'object') {
|
|
@@ -694,10 +773,9 @@ function extractAgentMessageFromStructuredOutput(agentProgram, text) {
|
|
|
694
773
|
return '';
|
|
695
774
|
}
|
|
696
775
|
|
|
697
|
-
function getAgentRuntimeMeta(
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
const agentProgram = resolveAgentProgram(template);
|
|
776
|
+
function getAgentRuntimeMeta(template) {
|
|
777
|
+
const normalizedTemplate = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
778
|
+
const agentProgram = resolveAgentProgram(normalizedTemplate);
|
|
701
779
|
const resumeCommand = buildAgentResumeCommand(agentProgram);
|
|
702
780
|
return {
|
|
703
781
|
agentProgram: agentProgram || '',
|
|
@@ -1362,17 +1440,34 @@ function prepareCodexTraceEvent(payload) {
|
|
|
1362
1440
|
async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
1363
1441
|
const history = loadWebSessionHistory(state.webHistoryDir, sessionRef.containerName);
|
|
1364
1442
|
const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
|
|
1365
|
-
const
|
|
1366
|
-
|
|
1367
|
-
|
|
1443
|
+
const containerMap = listWebManyoyoContainers(ctx);
|
|
1444
|
+
const containerInfo = containerMap[sessionRef.containerName] || {};
|
|
1445
|
+
const normalizedContainerTemplate = normalizeAgentPromptCommandTemplate(history.agentPromptCommand, 'agentPromptCommand');
|
|
1446
|
+
if (normalizedContainerTemplate !== history.agentPromptCommand) {
|
|
1447
|
+
history.agentPromptCommand = normalizedContainerTemplate;
|
|
1368
1448
|
saveWebSessionHistory(state.webHistoryDir, sessionRef.containerName, history);
|
|
1369
1449
|
}
|
|
1370
|
-
if (
|
|
1450
|
+
if (agentSession && typeof agentSession.agentPromptCommand === 'string') {
|
|
1451
|
+
const normalizedAgentTemplate = normalizeAgentPromptCommandTemplate(
|
|
1452
|
+
agentSession.agentPromptCommand,
|
|
1453
|
+
`agents.${sessionRef.agentId}.agentPromptCommand`
|
|
1454
|
+
);
|
|
1455
|
+
if (normalizedAgentTemplate !== agentSession.agentPromptCommand) {
|
|
1456
|
+
agentSession.agentPromptCommand = normalizedAgentTemplate;
|
|
1457
|
+
saveWebSessionHistory(state.webHistoryDir, sessionRef.containerName, history);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
const effectiveTemplate = resolveEffectiveAgentPromptCommandForSession(
|
|
1461
|
+
history,
|
|
1462
|
+
sessionRef.agentId,
|
|
1463
|
+
containerInfo.defaultCommand
|
|
1464
|
+
);
|
|
1465
|
+
if (!isAgentPromptCommandEnabled(effectiveTemplate)) {
|
|
1371
1466
|
throw new Error('当前会话未配置 agentPromptCommand');
|
|
1372
1467
|
}
|
|
1373
1468
|
|
|
1374
1469
|
await ensureWebContainer(ctx, state, sessionRef.containerName, sessionRef);
|
|
1375
|
-
const agentMeta = getAgentRuntimeMeta(
|
|
1470
|
+
const agentMeta = getAgentRuntimeMeta(effectiveTemplate);
|
|
1376
1471
|
const hasPriorConversation = hasAgentConversationHistory(agentSession);
|
|
1377
1472
|
let resumeAttempted = false;
|
|
1378
1473
|
let resumeSucceeded = false;
|
|
@@ -1391,7 +1486,7 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
|
1391
1486
|
const effectivePrompt = resumeSucceeded
|
|
1392
1487
|
? prompt
|
|
1393
1488
|
: buildAgentPromptWithHistory(agentSession, prompt);
|
|
1394
|
-
const command = buildWebAgentExecCommand(
|
|
1489
|
+
const command = buildWebAgentExecCommand(effectiveTemplate, effectivePrompt, agentMeta.agentProgram);
|
|
1395
1490
|
const contextMode = resumeSucceeded ? 'resume' : (hasPriorConversation ? 'history-injected' : 'first-turn');
|
|
1396
1491
|
|
|
1397
1492
|
return {
|
|
@@ -2204,11 +2299,23 @@ function listWebManyoyoContainers(ctx) {
|
|
|
2204
2299
|
if (!imageName.includes('manyoyo') && !name.startsWith('manyoyo-') && !name.startsWith('my-')) {
|
|
2205
2300
|
return;
|
|
2206
2301
|
}
|
|
2302
|
+
let defaultCommand = '';
|
|
2303
|
+
try {
|
|
2304
|
+
defaultCommand = String(
|
|
2305
|
+
ctx.dockerExecArgs(
|
|
2306
|
+
['inspect', '-f', '{{index .Config.Labels "manyoyo.default_cmd"}}', name],
|
|
2307
|
+
{ ignoreError: true }
|
|
2308
|
+
) || ''
|
|
2309
|
+
).trim();
|
|
2310
|
+
} catch (e) {
|
|
2311
|
+
defaultCommand = '';
|
|
2312
|
+
}
|
|
2207
2313
|
map[name] = {
|
|
2208
2314
|
name,
|
|
2209
2315
|
status: status || 'unknown',
|
|
2210
2316
|
image: imageName,
|
|
2211
|
-
createdAt: estimateStartTimeFromStatus(status)
|
|
2317
|
+
createdAt: estimateStartTimeFromStatus(status),
|
|
2318
|
+
defaultCommand
|
|
2212
2319
|
};
|
|
2213
2320
|
});
|
|
2214
2321
|
|
|
@@ -2588,7 +2695,6 @@ function buildSessionSummary(ctx, state, containerMap, sessionRef) {
|
|
|
2588
2695
|
const containerName = sessionRef && sessionRef.containerName ? sessionRef.containerName : '';
|
|
2589
2696
|
const agentId = sessionRef && sessionRef.agentId ? sessionRef.agentId : WEB_DEFAULT_AGENT_ID;
|
|
2590
2697
|
const history = loadWebSessionHistory(state.webHistoryDir, containerName);
|
|
2591
|
-
const agentMeta = getAgentRuntimeMeta(history);
|
|
2592
2698
|
const agentSession = getWebAgentSession(history, agentId)
|
|
2593
2699
|
|| (agentId === WEB_DEFAULT_AGENT_ID ? createEmptyWebAgentSession(WEB_DEFAULT_AGENT_ID) : null);
|
|
2594
2700
|
if (!agentSession) {
|
|
@@ -2596,10 +2702,15 @@ function buildSessionSummary(ctx, state, containerMap, sessionRef) {
|
|
|
2596
2702
|
}
|
|
2597
2703
|
const latestMessage = agentSession.messages.length ? agentSession.messages[agentSession.messages.length - 1] : null;
|
|
2598
2704
|
const containerInfo = containerMap[containerName] || {};
|
|
2705
|
+
const effectiveAgentPromptCommand = resolveEffectiveAgentPromptCommandForSession(history, agentId, containerInfo.defaultCommand);
|
|
2706
|
+
const agentMeta = getAgentRuntimeMeta(effectiveAgentPromptCommand);
|
|
2707
|
+
const effectiveAgentProgram = agentMeta.agentProgram || resolveAgentProgram(effectiveAgentPromptCommand);
|
|
2708
|
+
const effectiveResumeSupported = agentMeta.resumeSupported || Boolean(buildAgentResumeCommand(effectiveAgentProgram));
|
|
2599
2709
|
const applied = history.applied && typeof history.applied === 'object' && !Array.isArray(history.applied)
|
|
2600
2710
|
? history.applied
|
|
2601
2711
|
: buildSessionFallbackApplied(ctx, state, containerName, history, {
|
|
2602
|
-
status: containerInfo.status || 'history'
|
|
2712
|
+
status: containerInfo.status || 'history',
|
|
2713
|
+
defaultCommand: containerInfo.defaultCommand || ''
|
|
2603
2714
|
});
|
|
2604
2715
|
const updatedAt = agentSession.updatedAt || history.updatedAt || (latestMessage && latestMessage.timestamp) || containerInfo.createdAt || null;
|
|
2605
2716
|
return {
|
|
@@ -2611,9 +2722,9 @@ function buildSessionSummary(ctx, state, containerMap, sessionRef) {
|
|
|
2611
2722
|
image: containerInfo.image || '',
|
|
2612
2723
|
updatedAt,
|
|
2613
2724
|
messageCount: agentSession.messages.length,
|
|
2614
|
-
agentEnabled: isAgentPromptCommandEnabled(
|
|
2615
|
-
agentProgram:
|
|
2616
|
-
resumeSupported:
|
|
2725
|
+
agentEnabled: isAgentPromptCommandEnabled(effectiveAgentPromptCommand),
|
|
2726
|
+
agentProgram: effectiveAgentProgram || '',
|
|
2727
|
+
resumeSupported: effectiveResumeSupported,
|
|
2617
2728
|
hostPath: applied.hostPath || '',
|
|
2618
2729
|
containerPath: applied.containerPath || ''
|
|
2619
2730
|
};
|
|
@@ -2622,14 +2733,18 @@ function buildSessionSummary(ctx, state, containerMap, sessionRef) {
|
|
|
2622
2733
|
function buildSessionFallbackApplied(ctx, state, name, history, summary) {
|
|
2623
2734
|
const snapshot = readWebConfigSnapshot(state.webConfigPath);
|
|
2624
2735
|
const defaults = buildConfigDefaults(ctx, snapshot.parseError ? {} : snapshot.parsed);
|
|
2625
|
-
const
|
|
2626
|
-
const effectiveAgentProgram = resolveAgentProgram(effectiveAgentPromptCommand) || '';
|
|
2627
|
-
const effectiveResumeSupported = Boolean(buildAgentResumeCommand(effectiveAgentProgram));
|
|
2628
|
-
const defaultCommand = buildDefaultCommand(
|
|
2736
|
+
const configuredDefaultCommand = buildDefaultCommand(
|
|
2629
2737
|
defaults.shellPrefix,
|
|
2630
2738
|
defaults.shell,
|
|
2631
2739
|
defaults.shellSuffix
|
|
2632
2740
|
) || buildStaticContainerRuntime(ctx, name).defaultCommand;
|
|
2741
|
+
const defaultCommand = pickFirstString(
|
|
2742
|
+
summary && summary.defaultCommand,
|
|
2743
|
+
configuredDefaultCommand
|
|
2744
|
+
);
|
|
2745
|
+
const effectiveAgentPromptCommand = resolveEffectiveSessionAgentPromptCommand(history, defaultCommand);
|
|
2746
|
+
const effectiveAgentProgram = resolveAgentProgram(effectiveAgentPromptCommand) || '';
|
|
2747
|
+
const effectiveResumeSupported = Boolean(buildAgentResumeCommand(effectiveAgentProgram));
|
|
2633
2748
|
|
|
2634
2749
|
return {
|
|
2635
2750
|
containerName: name,
|
|
@@ -2656,7 +2771,12 @@ function buildSessionFallbackApplied(ctx, state, name, history, summary) {
|
|
|
2656
2771
|
function buildSessionDetail(ctx, state, containerMap, name) {
|
|
2657
2772
|
const sessionRef = typeof name === 'string' ? parseWebSessionKey(name) : name;
|
|
2658
2773
|
const history = loadWebSessionHistory(state.webHistoryDir, sessionRef.containerName);
|
|
2659
|
-
const
|
|
2774
|
+
const containerInfo = containerMap[sessionRef.containerName] || {};
|
|
2775
|
+
const normalizedTemplate = resolveEffectiveAgentPromptCommandForSession(
|
|
2776
|
+
history,
|
|
2777
|
+
sessionRef.agentId,
|
|
2778
|
+
containerInfo.defaultCommand
|
|
2779
|
+
);
|
|
2660
2780
|
const summary = buildSessionSummary(ctx, state, containerMap, sessionRef);
|
|
2661
2781
|
const agentSession = getWebAgentSession(history, sessionRef.agentId)
|
|
2662
2782
|
|| (sessionRef.agentId === WEB_DEFAULT_AGENT_ID ? createEmptyWebAgentSession(WEB_DEFAULT_AGENT_ID) : null);
|
|
@@ -2677,6 +2797,14 @@ function buildSessionDetail(ctx, state, containerMap, name) {
|
|
|
2677
2797
|
latestRole: latestMessage && latestMessage.role ? String(latestMessage.role) : '',
|
|
2678
2798
|
latestTimestamp: latestMessage && latestMessage.timestamp ? latestMessage.timestamp : summary.updatedAt,
|
|
2679
2799
|
agentPromptCommand: normalizedTemplate || '',
|
|
2800
|
+
containerAgentPromptCommand: typeof history.agentPromptCommand === 'string'
|
|
2801
|
+
? normalizeAgentPromptCommandTemplate(history.agentPromptCommand, 'agentPromptCommand')
|
|
2802
|
+
: '',
|
|
2803
|
+
agentPromptCommandOverride: agentSession && typeof agentSession.agentPromptCommand === 'string'
|
|
2804
|
+
? normalizeAgentPromptCommandTemplate(agentSession.agentPromptCommand, `agents.${sessionRef.agentId}.agentPromptCommand`)
|
|
2805
|
+
: '',
|
|
2806
|
+
inferredAgentPromptCommand: deriveAgentPromptCommandFromDefaultCommand(containerInfo.defaultCommand),
|
|
2807
|
+
agentPromptSource: getEffectiveAgentPromptCommandSource(history, sessionRef.agentId, containerInfo.defaultCommand),
|
|
2680
2808
|
agentProgram: summary.agentProgram || '',
|
|
2681
2809
|
resumeSupported: summary.resumeSupported === true,
|
|
2682
2810
|
lastResumeAt: agentSession.lastResumeAt || null,
|
|
@@ -3216,6 +3344,63 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
3216
3344
|
sendJson(res, 200, { name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId), detail });
|
|
3217
3345
|
}
|
|
3218
3346
|
},
|
|
3347
|
+
{
|
|
3348
|
+
method: 'PUT',
|
|
3349
|
+
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/agent-template$/),
|
|
3350
|
+
handler: async match => {
|
|
3351
|
+
const sessionRef = getValidSessionRef(ctx, res, match[1]);
|
|
3352
|
+
if (!sessionRef) {
|
|
3353
|
+
return;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
let payload = null;
|
|
3357
|
+
try {
|
|
3358
|
+
payload = await readJsonBody(req);
|
|
3359
|
+
} catch (e) {
|
|
3360
|
+
sendJson(res, 400, { error: e.message || '请求参数错误' });
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
const normalizedPayload = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {};
|
|
3364
|
+
const hasContainerTemplate = hasOwn(normalizedPayload, 'containerAgentPromptCommand');
|
|
3365
|
+
const hasAgentOverride = hasOwn(normalizedPayload, 'agentPromptCommandOverride');
|
|
3366
|
+
if (!hasContainerTemplate && !hasAgentOverride) {
|
|
3367
|
+
sendJson(res, 400, { error: '至少提供一个模板字段' });
|
|
3368
|
+
return;
|
|
3369
|
+
}
|
|
3370
|
+
if (hasAgentOverride && sessionRef.agentId === WEB_DEFAULT_AGENT_ID) {
|
|
3371
|
+
sendJson(res, 400, { error: '默认 AGENT 不支持单独覆盖模板,请直接修改容器模板' });
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
try {
|
|
3376
|
+
if (hasContainerTemplate) {
|
|
3377
|
+
setWebSessionAgentPromptCommand(
|
|
3378
|
+
state.webHistoryDir,
|
|
3379
|
+
sessionRef.containerName,
|
|
3380
|
+
normalizedPayload.containerAgentPromptCommand
|
|
3381
|
+
);
|
|
3382
|
+
}
|
|
3383
|
+
if (hasAgentOverride) {
|
|
3384
|
+
setWebAgentSessionPromptCommand(
|
|
3385
|
+
state.webHistoryDir,
|
|
3386
|
+
sessionRef,
|
|
3387
|
+
normalizedPayload.agentPromptCommandOverride
|
|
3388
|
+
);
|
|
3389
|
+
}
|
|
3390
|
+
} catch (e) {
|
|
3391
|
+
sendJson(res, 400, { error: e.message || '保存 Agent 模板失败' });
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
const containerMap = listWebManyoyoContainers(ctx);
|
|
3396
|
+
const detail = buildSessionDetail(ctx, state, containerMap, sessionRef);
|
|
3397
|
+
sendJson(res, 200, {
|
|
3398
|
+
saved: true,
|
|
3399
|
+
name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId),
|
|
3400
|
+
detail
|
|
3401
|
+
});
|
|
3402
|
+
}
|
|
3403
|
+
},
|
|
3219
3404
|
{
|
|
3220
3405
|
method: 'POST',
|
|
3221
3406
|
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/run$/),
|