@xcanwin/manyoyo 6.2.9 → 6.2.11
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/core/event-store.js +11 -0
- package/lib/web/frontend/app.css +19 -1
- package/lib/web/frontend/app.html +42 -1
- package/lib/web/frontend/app.js +299 -13
- package/lib/web/server.js +313 -86
- package/package.json +1 -1
package/lib/core/event-store.js
CHANGED
|
@@ -99,6 +99,17 @@ class FileEventStore {
|
|
|
99
99
|
fs.writeFileSync(temporaryPath, `${JSON.stringify(projection)}\n`);
|
|
100
100
|
fs.renameSync(temporaryPath, targetPath);
|
|
101
101
|
}
|
|
102
|
+
|
|
103
|
+
remove(aggregateId) {
|
|
104
|
+
const eventFilePath = this.getEventFilePath(aggregateId);
|
|
105
|
+
if (fs.existsSync(eventFilePath)) {
|
|
106
|
+
fs.unlinkSync(eventFilePath);
|
|
107
|
+
}
|
|
108
|
+
const projectionFilePath = this.getProjectionFilePath(aggregateId);
|
|
109
|
+
if (fs.existsSync(projectionFilePath)) {
|
|
110
|
+
fs.unlinkSync(projectionFilePath);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
module.exports = {
|
package/lib/web/frontend/app.css
CHANGED
|
@@ -284,6 +284,10 @@ textarea:focus-visible {
|
|
|
284
284
|
overflow: hidden;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
.modal.modal-sm {
|
|
288
|
+
width: min(420px, calc(100vw - 24px));
|
|
289
|
+
}
|
|
290
|
+
|
|
287
291
|
.modal-header {
|
|
288
292
|
display: flex;
|
|
289
293
|
align-items: center;
|
|
@@ -391,10 +395,15 @@ textarea:focus-visible {
|
|
|
391
395
|
color: var(--muted);
|
|
392
396
|
}
|
|
393
397
|
|
|
398
|
+
.text-block[hidden] {
|
|
399
|
+
display: none;
|
|
400
|
+
}
|
|
401
|
+
|
|
394
402
|
.form-grid input,
|
|
395
403
|
.form-grid select,
|
|
396
404
|
.text-block textarea,
|
|
397
|
-
.text-block input
|
|
405
|
+
.text-block input,
|
|
406
|
+
.text-block select {
|
|
398
407
|
border: 1px solid var(--line);
|
|
399
408
|
border-radius: 10px;
|
|
400
409
|
padding: 9px 11px;
|
|
@@ -742,6 +751,15 @@ textarea:focus-visible {
|
|
|
742
751
|
}
|
|
743
752
|
}
|
|
744
753
|
|
|
754
|
+
.crumb-bar .tree-node-menu {
|
|
755
|
+
position: relative;
|
|
756
|
+
top: auto;
|
|
757
|
+
right: auto;
|
|
758
|
+
transform: none;
|
|
759
|
+
opacity: 1;
|
|
760
|
+
pointer-events: auto;
|
|
761
|
+
}
|
|
762
|
+
|
|
745
763
|
.tree-node-menu-trigger {
|
|
746
764
|
width: 26px;
|
|
747
765
|
height: 26px;
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
<button type="button" id="activityAgentBtn" class="secondary is-active">Agent对话</button>
|
|
122
122
|
<button type="button" id="activityCommandBtn" class="secondary">系统命令</button>
|
|
123
123
|
<button type="button" id="agentTemplateBtn" class="secondary">CLI · —</button>
|
|
124
|
-
<
|
|
124
|
+
<button type="button" id="activityModelChip" class="secondary">模型 · —</button>
|
|
125
125
|
</div>
|
|
126
126
|
</div>
|
|
127
127
|
<button type="submit" id="sendBtn">发送</button>
|
|
@@ -215,6 +215,22 @@
|
|
|
215
215
|
</section>
|
|
216
216
|
</div>
|
|
217
217
|
|
|
218
|
+
<div id="removeConfirmModal" class="modal-backdrop" hidden>
|
|
219
|
+
<section class="modal modal-sm" role="dialog" aria-modal="true" aria-labelledby="removeConfirmTitle">
|
|
220
|
+
<header class="modal-header">
|
|
221
|
+
<h2 id="removeConfirmTitle">确认删除</h2>
|
|
222
|
+
<button type="button" id="removeConfirmCancelBtn" class="secondary">关闭</button>
|
|
223
|
+
</header>
|
|
224
|
+
<div class="modal-body">
|
|
225
|
+
<p id="removeConfirmMessage"></p>
|
|
226
|
+
</div>
|
|
227
|
+
<footer class="modal-footer">
|
|
228
|
+
<button type="button" id="removeConfirmKeepHistoryBtn" class="secondary">否,保留历史</button>
|
|
229
|
+
<button type="button" id="removeConfirmWithHistoryBtn" class="danger">是,连同历史一起删除</button>
|
|
230
|
+
</footer>
|
|
231
|
+
</section>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
218
234
|
<div id="directoryPickerModal" class="modal-backdrop" hidden>
|
|
219
235
|
<section class="modal dir-picker-modal" role="dialog" aria-modal="true" aria-labelledby="directoryPickerTitle">
|
|
220
236
|
<header class="modal-header">
|
|
@@ -288,6 +304,31 @@
|
|
|
288
304
|
</section>
|
|
289
305
|
</div>
|
|
290
306
|
|
|
307
|
+
<div id="modelModal" class="modal-backdrop" hidden>
|
|
308
|
+
<section class="modal" role="dialog" aria-modal="true" aria-labelledby="modelModalTitle">
|
|
309
|
+
<header class="modal-header">
|
|
310
|
+
<h2 id="modelModalTitle">选择模型</h2>
|
|
311
|
+
<button type="button" id="modelModalCancelBtn" class="secondary">关闭</button>
|
|
312
|
+
</header>
|
|
313
|
+
<div class="modal-body">
|
|
314
|
+
<div id="modelModalLoading" class="modal-tip" hidden>正在获取模型列表…</div>
|
|
315
|
+
<label class="text-block">模型
|
|
316
|
+
<select id="modelSelect">
|
|
317
|
+
<option value="">跟随默认(不传 --model)</option>
|
|
318
|
+
<option value="__custom__">自定义…</option>
|
|
319
|
+
</select>
|
|
320
|
+
</label>
|
|
321
|
+
<label class="text-block" id="modelCustomGroup" hidden>自定义模型名称
|
|
322
|
+
<input type="text" id="modelCustomInput" placeholder="例如 gemini-2.5-pro">
|
|
323
|
+
</label>
|
|
324
|
+
<div id="modelModalError" class="modal-error" hidden></div>
|
|
325
|
+
</div>
|
|
326
|
+
<footer class="modal-footer">
|
|
327
|
+
<button type="button" id="modelModalSaveBtn">保存</button>
|
|
328
|
+
</footer>
|
|
329
|
+
</section>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
291
332
|
<div id="externalLinkModal" class="modal-backdrop" hidden>
|
|
292
333
|
<section class="modal" role="dialog" aria-modal="true" aria-labelledby="externalLinkTitle">
|
|
293
334
|
<header class="modal-header">
|
package/lib/web/frontend/app.js
CHANGED
|
@@ -53,6 +53,10 @@
|
|
|
53
53
|
createModalOpen: false,
|
|
54
54
|
agentTemplateModalOpen: false,
|
|
55
55
|
externalLinkModalOpen: false,
|
|
56
|
+
modelModalOpen: false,
|
|
57
|
+
modelModalSaving: false,
|
|
58
|
+
modelModalLoading: false,
|
|
59
|
+
modelModalCatalog: [],
|
|
56
60
|
configLoading: false,
|
|
57
61
|
configSaving: false,
|
|
58
62
|
configSaveMessage: '',
|
|
@@ -175,6 +179,12 @@
|
|
|
175
179
|
const createEnv = document.getElementById('createEnv');
|
|
176
180
|
const createEnvFile = document.getElementById('createEnvFile');
|
|
177
181
|
const createVolumes = document.getElementById('createVolumes');
|
|
182
|
+
const removeConfirmModal = document.getElementById('removeConfirmModal');
|
|
183
|
+
const removeConfirmTitle = document.getElementById('removeConfirmTitle');
|
|
184
|
+
const removeConfirmMessage = document.getElementById('removeConfirmMessage');
|
|
185
|
+
const removeConfirmCancelBtn = document.getElementById('removeConfirmCancelBtn');
|
|
186
|
+
const removeConfirmKeepHistoryBtn = document.getElementById('removeConfirmKeepHistoryBtn');
|
|
187
|
+
const removeConfirmWithHistoryBtn = document.getElementById('removeConfirmWithHistoryBtn');
|
|
178
188
|
const directoryPickerModal = document.getElementById('directoryPickerModal');
|
|
179
189
|
const directoryPickerTitle = document.getElementById('directoryPickerTitle');
|
|
180
190
|
const directoryPickerTip = document.getElementById('directoryPickerTip');
|
|
@@ -216,6 +226,14 @@
|
|
|
216
226
|
const agentTemplateCancelBtn = document.getElementById('agentTemplateCancelBtn');
|
|
217
227
|
const agentTemplateResetBtn = document.getElementById('agentTemplateResetBtn');
|
|
218
228
|
const agentTemplateSaveBtn = document.getElementById('agentTemplateSaveBtn');
|
|
229
|
+
const modelModal = document.getElementById('modelModal');
|
|
230
|
+
const modelModalLoading = document.getElementById('modelModalLoading');
|
|
231
|
+
const modelSelect = document.getElementById('modelSelect');
|
|
232
|
+
const modelCustomGroup = document.getElementById('modelCustomGroup');
|
|
233
|
+
const modelCustomInput = document.getElementById('modelCustomInput');
|
|
234
|
+
const modelModalError = document.getElementById('modelModalError');
|
|
235
|
+
const modelModalCancelBtn = document.getElementById('modelModalCancelBtn');
|
|
236
|
+
const modelModalSaveBtn = document.getElementById('modelModalSaveBtn');
|
|
219
237
|
const externalLinkModal = document.getElementById('externalLinkModal');
|
|
220
238
|
const externalLinkUrl = document.getElementById('externalLinkUrl');
|
|
221
239
|
const externalLinkCancelBtn = document.getElementById('externalLinkCancelBtn');
|
|
@@ -1524,7 +1542,120 @@
|
|
|
1524
1542
|
if (!state.active) {
|
|
1525
1543
|
return '—';
|
|
1526
1544
|
}
|
|
1527
|
-
|
|
1545
|
+
const activeSession = getActiveSession();
|
|
1546
|
+
const detail = state.sessionDetail && state.active === (state.sessionDetail.name || state.active) ? state.sessionDetail : null;
|
|
1547
|
+
const model = String(
|
|
1548
|
+
(detail && detail.model)
|
|
1549
|
+
|| (activeSession && activeSession.model)
|
|
1550
|
+
|| ''
|
|
1551
|
+
).trim();
|
|
1552
|
+
return model || '自动';
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
const MODEL_CUSTOM_OPTION_VALUE = '__custom__';
|
|
1556
|
+
|
|
1557
|
+
function populateModelSelect(models, selectedModel) {
|
|
1558
|
+
if (!modelSelect) {
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
modelSelect.innerHTML = '';
|
|
1562
|
+
const followOption = document.createElement('option');
|
|
1563
|
+
followOption.value = '';
|
|
1564
|
+
followOption.textContent = '跟随默认(不传 --model)';
|
|
1565
|
+
modelSelect.appendChild(followOption);
|
|
1566
|
+
|
|
1567
|
+
const knownValues = new Set();
|
|
1568
|
+
(Array.isArray(models) ? models : []).forEach(function (model) {
|
|
1569
|
+
if (!model || !model.value) {
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
knownValues.add(model.value);
|
|
1573
|
+
const option = document.createElement('option');
|
|
1574
|
+
option.value = model.value;
|
|
1575
|
+
option.textContent = model.description ? `${model.label} · ${model.description}` : model.label;
|
|
1576
|
+
modelSelect.appendChild(option);
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
const customOption = document.createElement('option');
|
|
1580
|
+
customOption.value = MODEL_CUSTOM_OPTION_VALUE;
|
|
1581
|
+
customOption.textContent = '自定义…';
|
|
1582
|
+
modelSelect.appendChild(customOption);
|
|
1583
|
+
|
|
1584
|
+
if (selectedModel && knownValues.has(selectedModel)) {
|
|
1585
|
+
modelSelect.value = selectedModel;
|
|
1586
|
+
if (modelCustomGroup) modelCustomGroup.hidden = true;
|
|
1587
|
+
} else if (selectedModel) {
|
|
1588
|
+
modelSelect.value = MODEL_CUSTOM_OPTION_VALUE;
|
|
1589
|
+
if (modelCustomInput) modelCustomInput.value = selectedModel;
|
|
1590
|
+
if (modelCustomGroup) modelCustomGroup.hidden = false;
|
|
1591
|
+
} else {
|
|
1592
|
+
modelSelect.value = '';
|
|
1593
|
+
if (modelCustomGroup) modelCustomGroup.hidden = true;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
function showModelModalError(message) {
|
|
1598
|
+
if (!modelModalError) return;
|
|
1599
|
+
modelModalError.textContent = message || '';
|
|
1600
|
+
modelModalError.hidden = !message;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
async function openModelModal() {
|
|
1604
|
+
if (!state.active || state.modelModalSaving) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
const detail = await ensureActiveSessionDetail();
|
|
1608
|
+
if (!detail) {
|
|
1609
|
+
alert(state.sessionDetailError || '当前会话详情暂时不可用');
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
state.modelModalOpen = true;
|
|
1613
|
+
state.modelModalLoading = true;
|
|
1614
|
+
state.modelModalCatalog = [];
|
|
1615
|
+
showModelModalError('');
|
|
1616
|
+
populateModelSelect([], detail.model || '');
|
|
1617
|
+
syncUi();
|
|
1618
|
+
try {
|
|
1619
|
+
const data = await api('/api/sessions/' + encodeURIComponent(state.active) + '/models');
|
|
1620
|
+
state.modelModalCatalog = Array.isArray(data && data.models) ? data.models : [];
|
|
1621
|
+
} catch (e) {
|
|
1622
|
+
state.modelModalCatalog = [];
|
|
1623
|
+
} finally {
|
|
1624
|
+
state.modelModalLoading = false;
|
|
1625
|
+
populateModelSelect(state.modelModalCatalog, detail.model || '');
|
|
1626
|
+
syncUi();
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
function closeModelModal() {
|
|
1631
|
+
state.modelModalOpen = false;
|
|
1632
|
+
showModelModalError('');
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
async function saveModelModal() {
|
|
1636
|
+
if (!state.active || state.modelModalSaving) {
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
const rawValue = modelSelect && modelSelect.value === MODEL_CUSTOM_OPTION_VALUE
|
|
1640
|
+
? (modelCustomInput ? modelCustomInput.value : '')
|
|
1641
|
+
: (modelSelect ? modelSelect.value : '');
|
|
1642
|
+
state.modelModalSaving = true;
|
|
1643
|
+
showModelModalError('');
|
|
1644
|
+
syncUi();
|
|
1645
|
+
try {
|
|
1646
|
+
await api('/api/sessions/' + encodeURIComponent(state.active) + '/model', {
|
|
1647
|
+
method: 'POST',
|
|
1648
|
+
body: JSON.stringify({ model: String(rawValue || '').trim() })
|
|
1649
|
+
});
|
|
1650
|
+
await refreshSessionsSilent({ preferredName: state.active });
|
|
1651
|
+
await loadSessionDetailForSession(state.active);
|
|
1652
|
+
closeModelModal();
|
|
1653
|
+
} catch (e) {
|
|
1654
|
+
showModelModalError(e && e.message ? e.message : '保存失败');
|
|
1655
|
+
} finally {
|
|
1656
|
+
state.modelModalSaving = false;
|
|
1657
|
+
syncUi();
|
|
1658
|
+
}
|
|
1528
1659
|
}
|
|
1529
1660
|
|
|
1530
1661
|
function buildMessageMetaLines(message) {
|
|
@@ -2217,7 +2348,7 @@
|
|
|
2217
2348
|
if (activityModelChip) {
|
|
2218
2349
|
activityModelChip.textContent = `模型 · ${resolveToolbarModelLabel()}`;
|
|
2219
2350
|
activityModelChip.title = state.active
|
|
2220
|
-
? '
|
|
2351
|
+
? '点击选择本会话使用的模型'
|
|
2221
2352
|
: '请先选择会话';
|
|
2222
2353
|
}
|
|
2223
2354
|
if (viewActivityBtn) viewActivityBtn.classList.toggle('is-active', activityTab);
|
|
@@ -2322,6 +2453,27 @@
|
|
|
2322
2453
|
if (externalLinkModal) {
|
|
2323
2454
|
externalLinkModal.hidden = !state.externalLinkModalOpen;
|
|
2324
2455
|
}
|
|
2456
|
+
if (modelModal) {
|
|
2457
|
+
modelModal.hidden = !state.modelModalOpen;
|
|
2458
|
+
}
|
|
2459
|
+
if (modelModalLoading) {
|
|
2460
|
+
modelModalLoading.hidden = !state.modelModalLoading;
|
|
2461
|
+
}
|
|
2462
|
+
if (modelSelect) {
|
|
2463
|
+
modelSelect.disabled = state.modelModalSaving || state.modelModalLoading;
|
|
2464
|
+
}
|
|
2465
|
+
if (modelCustomInput) {
|
|
2466
|
+
modelCustomInput.disabled = state.modelModalSaving || state.modelModalLoading;
|
|
2467
|
+
}
|
|
2468
|
+
if (modelModalSaveBtn) {
|
|
2469
|
+
modelModalSaveBtn.disabled = state.modelModalSaving || state.modelModalLoading;
|
|
2470
|
+
}
|
|
2471
|
+
if (modelModalCancelBtn) {
|
|
2472
|
+
modelModalCancelBtn.disabled = state.modelModalSaving;
|
|
2473
|
+
}
|
|
2474
|
+
if (activityModelChip) {
|
|
2475
|
+
activityModelChip.disabled = !state.active;
|
|
2476
|
+
}
|
|
2325
2477
|
if (agentTemplateSaveBtn) {
|
|
2326
2478
|
agentTemplateSaveBtn.disabled = state.agentTemplateSaving || !state.active;
|
|
2327
2479
|
}
|
|
@@ -2345,7 +2497,7 @@
|
|
|
2345
2497
|
}
|
|
2346
2498
|
document.body.classList.toggle(
|
|
2347
2499
|
'modal-open',
|
|
2348
|
-
state.configModalOpen || state.createModalOpen || state.directoryPicker.open || state.agentTemplateModalOpen || state.externalLinkModalOpen || state.cloneModal.open
|
|
2500
|
+
state.configModalOpen || state.createModalOpen || state.directoryPicker.open || state.agentTemplateModalOpen || state.externalLinkModalOpen || state.cloneModal.open || state.modelModalOpen
|
|
2349
2501
|
);
|
|
2350
2502
|
if (composer) {
|
|
2351
2503
|
composer.hidden = !activityTab;
|
|
@@ -3021,11 +3173,47 @@
|
|
|
3021
3173
|
return wrap;
|
|
3022
3174
|
}
|
|
3023
3175
|
|
|
3176
|
+
function confirmRemoveChoice(options) {
|
|
3177
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
3178
|
+
if (!removeConfirmModal) {
|
|
3179
|
+
return Promise.resolve(confirm(opts.message || '确认删除?') ? 'with-history' : null);
|
|
3180
|
+
}
|
|
3181
|
+
return new Promise(function (resolve) {
|
|
3182
|
+
if (removeConfirmTitle) {
|
|
3183
|
+
removeConfirmTitle.textContent = opts.title || '确认删除';
|
|
3184
|
+
}
|
|
3185
|
+
if (removeConfirmMessage) {
|
|
3186
|
+
removeConfirmMessage.textContent = opts.message || '';
|
|
3187
|
+
}
|
|
3188
|
+
let settled = false;
|
|
3189
|
+
function finish(value) {
|
|
3190
|
+
if (settled) return;
|
|
3191
|
+
settled = true;
|
|
3192
|
+
setModalVisible(removeConfirmModal, false);
|
|
3193
|
+
removeConfirmKeepHistoryBtn.removeEventListener('click', onKeepHistory);
|
|
3194
|
+
removeConfirmWithHistoryBtn.removeEventListener('click', onWithHistory);
|
|
3195
|
+
removeConfirmCancelBtn.removeEventListener('click', onCancel);
|
|
3196
|
+
resolve(value);
|
|
3197
|
+
}
|
|
3198
|
+
function onKeepHistory() { finish('keep-history'); }
|
|
3199
|
+
function onWithHistory() { finish('with-history'); }
|
|
3200
|
+
function onCancel() { finish(null); }
|
|
3201
|
+
removeConfirmKeepHistoryBtn.addEventListener('click', onKeepHistory);
|
|
3202
|
+
removeConfirmWithHistoryBtn.addEventListener('click', onWithHistory);
|
|
3203
|
+
removeConfirmCancelBtn.addEventListener('click', onCancel);
|
|
3204
|
+
setModalVisible(removeConfirmModal, true);
|
|
3205
|
+
});
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3024
3208
|
async function removeContainerByName(containerName) {
|
|
3025
3209
|
const target = String(containerName || '').trim();
|
|
3026
3210
|
if (!target) return;
|
|
3027
|
-
const
|
|
3028
|
-
|
|
3211
|
+
const choice = await confirmRemoveChoice({
|
|
3212
|
+
title: '删除容器',
|
|
3213
|
+
message: `确认删除容器 ${target}?可以选择是否同时删除该容器的全部历史记录(消息与事件日志)。`
|
|
3214
|
+
});
|
|
3215
|
+
if (!choice) return;
|
|
3216
|
+
const removeHistory = choice === 'with-history';
|
|
3029
3217
|
try {
|
|
3030
3218
|
const wasActiveContainer = parseSessionKey(state.active).containerName === target;
|
|
3031
3219
|
if (parseSessionKey(state.terminal.sessionName).containerName === target
|
|
@@ -3033,7 +3221,8 @@
|
|
|
3033
3221
|
disconnectTerminal('容器删除,终端已断开', true);
|
|
3034
3222
|
}
|
|
3035
3223
|
await api('/api/sessions/' + encodeURIComponent(target) + '/remove', {
|
|
3036
|
-
method: 'POST'
|
|
3224
|
+
method: 'POST',
|
|
3225
|
+
body: JSON.stringify({ removeHistory })
|
|
3037
3226
|
});
|
|
3038
3227
|
await refreshSessions({
|
|
3039
3228
|
preferredName: wasActiveContainer ? target : (state.active || ''),
|
|
@@ -3048,8 +3237,12 @@
|
|
|
3048
3237
|
async function removeAgentSessionByName(sessionName, agentLabel) {
|
|
3049
3238
|
const target = String(sessionName || '').trim();
|
|
3050
3239
|
if (!target) return;
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3240
|
+
const choice = await confirmRemoveChoice({
|
|
3241
|
+
title: '删除 AGENT',
|
|
3242
|
+
message: `确认删除 AGENT ${agentLabel || target}?可以选择是否同时删除它的历史记录(消息与事件日志)。`
|
|
3243
|
+
});
|
|
3244
|
+
if (!choice) return;
|
|
3245
|
+
const removeHistory = choice === 'with-history';
|
|
3053
3246
|
const isActive = target === state.active;
|
|
3054
3247
|
try {
|
|
3055
3248
|
const containerName = parseSessionKey(target).containerName;
|
|
@@ -3057,7 +3250,8 @@
|
|
|
3057
3250
|
? findPreferredSessionNameAfterRemoval(state.sessions, target)
|
|
3058
3251
|
: '';
|
|
3059
3252
|
await api('/api/sessions/' + encodeURIComponent(target) + '/remove-with-history', {
|
|
3060
|
-
method: 'POST'
|
|
3253
|
+
method: 'POST',
|
|
3254
|
+
body: JSON.stringify({ removeHistory })
|
|
3061
3255
|
});
|
|
3062
3256
|
await refreshSessions({
|
|
3063
3257
|
preferredName: isActive ? (fallbackSessionName || '') : (state.active || ''),
|
|
@@ -3173,11 +3367,50 @@
|
|
|
3173
3367
|
sep.textContent = '›';
|
|
3174
3368
|
sessionBreadcrumb.appendChild(sep);
|
|
3175
3369
|
|
|
3370
|
+
const sessionForCrumb = state.sessions.find(function (session) {
|
|
3371
|
+
return getSessionContainerName(session) === state.navContainer;
|
|
3372
|
+
});
|
|
3373
|
+
const containerRemark = sessionForCrumb && sessionForCrumb.containerRemark ? sessionForCrumb.containerRemark : '';
|
|
3374
|
+
const containerGroupForMenu = { containerName: state.navContainer, containerRemark: containerRemark };
|
|
3375
|
+
|
|
3176
3376
|
const containerCrumb = document.createElement('span');
|
|
3177
3377
|
containerCrumb.className = 'crumb-item is-current';
|
|
3178
|
-
containerCrumb.textContent = state.navContainer;
|
|
3179
|
-
containerCrumb.title = state.navContainer;
|
|
3378
|
+
containerCrumb.textContent = containerRemark || state.navContainer;
|
|
3379
|
+
containerCrumb.title = containerRemark ? `${containerRemark}(${state.navContainer})` : state.navContainer;
|
|
3180
3380
|
sessionBreadcrumb.appendChild(containerCrumb);
|
|
3381
|
+
sessionBreadcrumb.appendChild(createTreeNodeMenu([
|
|
3382
|
+
{
|
|
3383
|
+
label: '新建 AGENT',
|
|
3384
|
+
onClick: function () {
|
|
3385
|
+
createAgentSession(containerGroupForMenu.containerName);
|
|
3386
|
+
}
|
|
3387
|
+
},
|
|
3388
|
+
{
|
|
3389
|
+
label: '编辑备注',
|
|
3390
|
+
onClick: function () {
|
|
3391
|
+
editContainerRemark(containerGroupForMenu);
|
|
3392
|
+
}
|
|
3393
|
+
},
|
|
3394
|
+
{
|
|
3395
|
+
label: '创建相同配置容器',
|
|
3396
|
+
onClick: function () {
|
|
3397
|
+
openCloneModal('clone-config', containerGroupForMenu.containerName);
|
|
3398
|
+
}
|
|
3399
|
+
},
|
|
3400
|
+
{
|
|
3401
|
+
label: '复制容器',
|
|
3402
|
+
onClick: function () {
|
|
3403
|
+
openCloneModal('duplicate', containerGroupForMenu.containerName);
|
|
3404
|
+
}
|
|
3405
|
+
},
|
|
3406
|
+
{
|
|
3407
|
+
label: '删除容器',
|
|
3408
|
+
danger: true,
|
|
3409
|
+
onClick: function () {
|
|
3410
|
+
removeContainerByName(containerGroupForMenu.containerName);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
]));
|
|
3181
3414
|
}
|
|
3182
3415
|
|
|
3183
3416
|
const activeSession = getActiveSession();
|
|
@@ -3296,7 +3529,8 @@
|
|
|
3296
3529
|
|
|
3297
3530
|
function renderAgentLevel(containerGroup) {
|
|
3298
3531
|
sessionList.innerHTML = '';
|
|
3299
|
-
const
|
|
3532
|
+
const allSessions = containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions : [];
|
|
3533
|
+
const sessions = allSessions.filter(function (session) { return !session.synthetic; });
|
|
3300
3534
|
if (!sessions.length) {
|
|
3301
3535
|
const empty = document.createElement('div');
|
|
3302
3536
|
empty.className = 'empty';
|
|
@@ -3368,9 +3602,10 @@
|
|
|
3368
3602
|
const containerCount = new Set(state.sessions.map(function (session) {
|
|
3369
3603
|
return session && session.containerName ? session.containerName : '';
|
|
3370
3604
|
}).filter(Boolean)).size;
|
|
3605
|
+
const agentCount = state.sessions.filter(function (session) { return !session.synthetic; }).length;
|
|
3371
3606
|
sessionCount.textContent = state.loadingSessions
|
|
3372
3607
|
? '加载中...'
|
|
3373
|
-
: `${containerCount} 个容器 / ${
|
|
3608
|
+
: `${containerCount} 个容器 / ${agentCount} 个 AGENT`;
|
|
3374
3609
|
renderBreadcrumb();
|
|
3375
3610
|
|
|
3376
3611
|
if (state.loadingSessions) {
|
|
@@ -4238,6 +4473,45 @@
|
|
|
4238
4473
|
});
|
|
4239
4474
|
}
|
|
4240
4475
|
|
|
4476
|
+
if (activityModelChip) {
|
|
4477
|
+
activityModelChip.addEventListener('click', function () {
|
|
4478
|
+
closeComposerOptionsMenu();
|
|
4479
|
+
openModelModal().catch(function (e) {
|
|
4480
|
+
alert(e && e.message ? e.message : '加载模型信息失败');
|
|
4481
|
+
});
|
|
4482
|
+
});
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4485
|
+
if (modelModalCancelBtn) {
|
|
4486
|
+
modelModalCancelBtn.addEventListener('click', function () {
|
|
4487
|
+
closeModelModal();
|
|
4488
|
+
syncUi();
|
|
4489
|
+
});
|
|
4490
|
+
}
|
|
4491
|
+
|
|
4492
|
+
if (modelModalSaveBtn) {
|
|
4493
|
+
modelModalSaveBtn.addEventListener('click', function () {
|
|
4494
|
+
saveModelModal();
|
|
4495
|
+
});
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
if (modelSelect) {
|
|
4499
|
+
modelSelect.addEventListener('change', function () {
|
|
4500
|
+
if (modelCustomGroup) {
|
|
4501
|
+
modelCustomGroup.hidden = modelSelect.value !== MODEL_CUSTOM_OPTION_VALUE;
|
|
4502
|
+
}
|
|
4503
|
+
});
|
|
4504
|
+
}
|
|
4505
|
+
|
|
4506
|
+
if (modelModal) {
|
|
4507
|
+
modelModal.addEventListener('click', function (event) {
|
|
4508
|
+
if (event.target === modelModal && !state.modelModalSaving) {
|
|
4509
|
+
closeModelModal();
|
|
4510
|
+
syncUi();
|
|
4511
|
+
}
|
|
4512
|
+
});
|
|
4513
|
+
}
|
|
4514
|
+
|
|
4241
4515
|
if (configCancelBtn) {
|
|
4242
4516
|
configCancelBtn.addEventListener('click', function () {
|
|
4243
4517
|
closeConfigModal();
|
|
@@ -4691,6 +4965,14 @@
|
|
|
4691
4965
|
});
|
|
4692
4966
|
}
|
|
4693
4967
|
|
|
4968
|
+
if (removeConfirmModal) {
|
|
4969
|
+
removeConfirmModal.addEventListener('click', function (event) {
|
|
4970
|
+
if (event.target === removeConfirmModal) {
|
|
4971
|
+
removeConfirmCancelBtn.click();
|
|
4972
|
+
}
|
|
4973
|
+
});
|
|
4974
|
+
}
|
|
4975
|
+
|
|
4694
4976
|
if (directoryPickerModal) {
|
|
4695
4977
|
directoryPickerModal.addEventListener('click', function (event) {
|
|
4696
4978
|
if (event.target === directoryPickerModal && !state.directoryPicker.loading) {
|
|
@@ -4764,6 +5046,10 @@
|
|
|
4764
5046
|
closeAgentTemplateModal();
|
|
4765
5047
|
syncUi();
|
|
4766
5048
|
}
|
|
5049
|
+
if (event.key === 'Escape' && state.modelModalOpen && !state.modelModalSaving) {
|
|
5050
|
+
closeModelModal();
|
|
5051
|
+
syncUi();
|
|
5052
|
+
}
|
|
4767
5053
|
if (event.key === 'Escape' && state.externalLinkModalOpen) {
|
|
4768
5054
|
closeExternalLinkModalView();
|
|
4769
5055
|
syncUi();
|
package/lib/web/server.js
CHANGED
|
@@ -182,6 +182,7 @@ function createEmptyWebAgentSession(agentId, agentName) {
|
|
|
182
182
|
agentName: normalizeWebAgentName(agentId, agentName),
|
|
183
183
|
agentPromptCommand: '',
|
|
184
184
|
remark: '',
|
|
185
|
+
archived: false,
|
|
185
186
|
createdAt: null,
|
|
186
187
|
updatedAt: null,
|
|
187
188
|
messages: [],
|
|
@@ -190,7 +191,8 @@ function createEmptyWebAgentSession(agentId, agentName) {
|
|
|
190
191
|
lastResumeOk: null,
|
|
191
192
|
lastResumeError: '',
|
|
192
193
|
engineSessionId: '',
|
|
193
|
-
usageTotal: null
|
|
194
|
+
usageTotal: null,
|
|
195
|
+
model: ''
|
|
194
196
|
};
|
|
195
197
|
}
|
|
196
198
|
|
|
@@ -220,6 +222,7 @@ function normalizeWebAgentSessionRecord(agentId, rawAgent) {
|
|
|
220
222
|
? normalizeAgentPromptCommandTemplate(source.agentPromptCommand, `agents.${agentId}.agentPromptCommand`)
|
|
221
223
|
: '',
|
|
222
224
|
remark: normalizeRemarkText(source.remark),
|
|
225
|
+
archived: source.archived === true,
|
|
223
226
|
createdAt: typeof source.createdAt === 'string' ? source.createdAt : null,
|
|
224
227
|
updatedAt: typeof source.updatedAt === 'string' ? source.updatedAt : null,
|
|
225
228
|
messages: Array.isArray(source.messages) ? source.messages : [],
|
|
@@ -237,7 +240,8 @@ function normalizeWebAgentSessionRecord(agentId, rawAgent) {
|
|
|
237
240
|
lastResumeOk: typeof source.lastResumeOk === 'boolean' ? source.lastResumeOk : null,
|
|
238
241
|
lastResumeError: typeof source.lastResumeError === 'string' ? source.lastResumeError : '',
|
|
239
242
|
engineSessionId: typeof source.engineSessionId === 'string' ? source.engineSessionId : '',
|
|
240
|
-
usageTotal: normalizeWebAgentUsageTotal(source.usageTotal)
|
|
243
|
+
usageTotal: normalizeWebAgentUsageTotal(source.usageTotal),
|
|
244
|
+
model: normalizeStoredAgentModelValue(source.model)
|
|
241
245
|
};
|
|
242
246
|
}
|
|
243
247
|
|
|
@@ -310,7 +314,7 @@ function normalizeWebHistoryRecord(containerName, rawData) {
|
|
|
310
314
|
});
|
|
311
315
|
}
|
|
312
316
|
|
|
313
|
-
if (!Object.keys(history.agents).length && Array.isArray(data.messages)) {
|
|
317
|
+
if (!Object.keys(history.agents).length && Array.isArray(data.messages) && data.messages.length > 0) {
|
|
314
318
|
history.agents[WEB_DEFAULT_AGENT_ID] = normalizeWebAgentSessionRecord(WEB_DEFAULT_AGENT_ID, {
|
|
315
319
|
agentName: data.agentName || WEB_DEFAULT_AGENT_NAME,
|
|
316
320
|
updatedAt: typeof data.updatedAt === 'string' ? data.updatedAt : null,
|
|
@@ -343,16 +347,18 @@ function saveWebSessionHistory(webHistoryDir, containerName, history) {
|
|
|
343
347
|
const filePath = getWebHistoryFile(webHistoryDir, containerName);
|
|
344
348
|
const normalized = normalizeWebHistoryRecord(containerName, history);
|
|
345
349
|
const runtimeMeta = getAgentRuntimeMeta(normalized.agentPromptCommand || '');
|
|
346
|
-
const defaultAgent = getWebAgentSession(normalized, WEB_DEFAULT_AGENT_ID)
|
|
350
|
+
const defaultAgent = getWebAgentSession(normalized, WEB_DEFAULT_AGENT_ID);
|
|
347
351
|
const legacyCompatible = {
|
|
348
352
|
...normalized,
|
|
349
|
-
messages: Array.isArray(defaultAgent.messages) ? defaultAgent.messages : [],
|
|
350
353
|
agentProgram: runtimeMeta.agentProgram || '',
|
|
351
|
-
resumeSupported: runtimeMeta.resumeSupported === true
|
|
352
|
-
lastResumeAt: defaultAgent.lastResumeAt || null,
|
|
353
|
-
lastResumeOk: typeof defaultAgent.lastResumeOk === 'boolean' ? defaultAgent.lastResumeOk : null,
|
|
354
|
-
lastResumeError: defaultAgent.lastResumeError || ''
|
|
354
|
+
resumeSupported: runtimeMeta.resumeSupported === true
|
|
355
355
|
};
|
|
356
|
+
if (defaultAgent) {
|
|
357
|
+
legacyCompatible.messages = Array.isArray(defaultAgent.messages) ? defaultAgent.messages : [];
|
|
358
|
+
legacyCompatible.lastResumeAt = defaultAgent.lastResumeAt || null;
|
|
359
|
+
legacyCompatible.lastResumeOk = typeof defaultAgent.lastResumeOk === 'boolean' ? defaultAgent.lastResumeOk : null;
|
|
360
|
+
legacyCompatible.lastResumeError = defaultAgent.lastResumeError || '';
|
|
361
|
+
}
|
|
356
362
|
fs.writeFileSync(filePath, JSON.stringify(legacyCompatible, null, 4));
|
|
357
363
|
}
|
|
358
364
|
|
|
@@ -364,6 +370,28 @@ function removeWebSessionHistory(webHistoryDir, containerName) {
|
|
|
364
370
|
}
|
|
365
371
|
}
|
|
366
372
|
|
|
373
|
+
// 容器可能已经被删过一次(例如并发请求、或用户对着一条"仅历史"幽灵条目重复点删除),
|
|
374
|
+
// containerExists 和真正执行删除之间存在竞态,因此仍需 try/catch 兜底,
|
|
375
|
+
// 吞掉"已经不存在"这一类错误,让操作保持幂等。
|
|
376
|
+
function removeContainerIdempotent(ctx, containerName) {
|
|
377
|
+
if (!ctx.containerExists(containerName)) {
|
|
378
|
+
return { removedContainer: false };
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
ctx.removeContainer(containerName);
|
|
382
|
+
return { removedContainer: true };
|
|
383
|
+
} catch (e) {
|
|
384
|
+
return { removedContainer: false, error: e };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function removeAllAgentHistoryArtifacts(webHistoryDir, containerName, agentIds) {
|
|
389
|
+
const eventStore = new FileEventStore(webHistoryDir);
|
|
390
|
+
(Array.isArray(agentIds) ? agentIds : []).forEach(agentId => {
|
|
391
|
+
eventStore.remove(buildWebSessionKey(containerName, agentId));
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
367
395
|
function listWebHistorySessionNames(webHistoryDir, isValidContainerName) {
|
|
368
396
|
ensureWebHistoryDir(webHistoryDir);
|
|
369
397
|
return fs.readdirSync(webHistoryDir)
|
|
@@ -403,14 +431,19 @@ function parseWebSessionKey(sessionKey) {
|
|
|
403
431
|
};
|
|
404
432
|
}
|
|
405
433
|
|
|
434
|
+
function isVisibleWebAgentSession(agentSession) {
|
|
435
|
+
return Boolean(agentSession) && agentSession.archived !== true;
|
|
436
|
+
}
|
|
437
|
+
|
|
406
438
|
function getWebAgentSession(history, agentId, options = {}) {
|
|
407
439
|
const sessionHistory = history && typeof history === 'object' ? history : { agents: {} };
|
|
408
440
|
if (!sessionHistory.agents || typeof sessionHistory.agents !== 'object' || Array.isArray(sessionHistory.agents)) {
|
|
409
441
|
sessionHistory.agents = {};
|
|
410
442
|
}
|
|
411
443
|
const requestedAgentId = String(agentId || WEB_DEFAULT_AGENT_ID).trim() || WEB_DEFAULT_AGENT_ID;
|
|
412
|
-
|
|
413
|
-
|
|
444
|
+
const existing = sessionHistory.agents[requestedAgentId];
|
|
445
|
+
if (isVisibleWebAgentSession(existing)) {
|
|
446
|
+
return existing;
|
|
414
447
|
}
|
|
415
448
|
if (options.create === true) {
|
|
416
449
|
const agentSession = createEmptyWebAgentSession(requestedAgentId);
|
|
@@ -425,21 +458,20 @@ function listWebAgentSessions(history, options = {}) {
|
|
|
425
458
|
const agents = sessionHistory.agents && typeof sessionHistory.agents === 'object' && !Array.isArray(sessionHistory.agents)
|
|
426
459
|
? sessionHistory.agents
|
|
427
460
|
: {};
|
|
428
|
-
const
|
|
429
|
-
if (!agentIds.length && options.includeSyntheticDefault === true) {
|
|
430
|
-
return [createEmptyWebAgentSession(WEB_DEFAULT_AGENT_ID)];
|
|
431
|
-
}
|
|
432
|
-
return agentIds
|
|
461
|
+
const visibleSessions = Object.keys(agents)
|
|
433
462
|
.map(agentId => agents[agentId])
|
|
434
|
-
.filter(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
463
|
+
.filter(isVisibleWebAgentSession);
|
|
464
|
+
if (!visibleSessions.length && options.includeSyntheticDefault === true) {
|
|
465
|
+
return [{ ...createEmptyWebAgentSession(WEB_DEFAULT_AGENT_ID), synthetic: true }];
|
|
466
|
+
}
|
|
467
|
+
return visibleSessions.sort((a, b) => {
|
|
468
|
+
const orderA = a.agentId === WEB_DEFAULT_AGENT_ID ? 0 : 1;
|
|
469
|
+
const orderB = b.agentId === WEB_DEFAULT_AGENT_ID ? 0 : 1;
|
|
470
|
+
if (orderA !== orderB) {
|
|
471
|
+
return orderA - orderB;
|
|
472
|
+
}
|
|
473
|
+
return String(a.agentName || '').localeCompare(String(b.agentName || ''), 'zh-CN');
|
|
474
|
+
});
|
|
443
475
|
}
|
|
444
476
|
|
|
445
477
|
function getWebAgentCreationRank(agentId) {
|
|
@@ -775,6 +807,7 @@ function renderAgentPromptCommand(template, prompt) {
|
|
|
775
807
|
function buildCodexAgentExecCommand(template, prompt, options = {}) {
|
|
776
808
|
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
777
809
|
const sessionId = options && typeof options.sessionId === 'string' ? options.sessionId.trim() : '';
|
|
810
|
+
const model = options && typeof options.model === 'string' ? options.model.trim() : '';
|
|
778
811
|
const execMatch = templateText.match(
|
|
779
812
|
/^((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)codex\s+exec\b/
|
|
780
813
|
);
|
|
@@ -784,12 +817,14 @@ function buildCodexAgentExecCommand(template, prompt, options = {}) {
|
|
|
784
817
|
const suffix = templateText.slice(execMatch[0].length);
|
|
785
818
|
const hasJson = /(?:^|\s)--json(?:\s|$)/.test(suffix);
|
|
786
819
|
const injectedFlags = hasJson ? '' : ' --json';
|
|
820
|
+
const hasModel = /(?:^|\s)(?:-m|--model)(?:\s|$)/.test(suffix);
|
|
821
|
+
const modelFlag = model && !hasModel ? ` --model ${model}` : '';
|
|
787
822
|
if (sessionId) {
|
|
788
823
|
const promptIndex = suffix.indexOf('{prompt}');
|
|
789
824
|
const resumeSuffix = `${suffix.slice(0, promptIndex)}${quoteBashSingleValue(sessionId)} ${suffix.slice(promptIndex)}`;
|
|
790
|
-
codexTemplate = `${prefix}codex exec resume${injectedFlags}${resumeSuffix}`;
|
|
825
|
+
codexTemplate = `${prefix}codex exec resume${injectedFlags}${modelFlag}${resumeSuffix}`;
|
|
791
826
|
} else {
|
|
792
|
-
codexTemplate = `${prefix}codex exec${injectedFlags}${suffix}`;
|
|
827
|
+
codexTemplate = `${prefix}codex exec${injectedFlags}${modelFlag}${suffix}`;
|
|
793
828
|
}
|
|
794
829
|
}
|
|
795
830
|
return codexTemplate === templateText
|
|
@@ -824,6 +859,10 @@ function buildClaudeAgentExecCommand(template, prompt, options = {}) {
|
|
|
824
859
|
if (sessionId) {
|
|
825
860
|
flagSpecs.push({ flag: `-r ${sessionId}`, pattern: /(?:^|\s)-r(?:\s|$)/ });
|
|
826
861
|
}
|
|
862
|
+
const model = options && typeof options.model === 'string' ? options.model.trim() : '';
|
|
863
|
+
if (model) {
|
|
864
|
+
flagSpecs.push({ flag: `--model ${model}`, pattern: /(?:^|\s)--model(?:\s|$)/ });
|
|
865
|
+
}
|
|
827
866
|
const claudeTemplate = prependAgentFlags(
|
|
828
867
|
templateText,
|
|
829
868
|
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)claude\b)(.*)$/,
|
|
@@ -834,14 +873,19 @@ function buildClaudeAgentExecCommand(template, prompt, options = {}) {
|
|
|
834
873
|
: renderAgentPromptCommand(claudeTemplate, prompt);
|
|
835
874
|
}
|
|
836
875
|
|
|
837
|
-
function buildGeminiAgentExecCommand(template, prompt) {
|
|
876
|
+
function buildGeminiAgentExecCommand(template, prompt, options = {}) {
|
|
838
877
|
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
878
|
+
const flagSpecs = [
|
|
879
|
+
{ flag: '--output-format stream-json', pattern: /(?:^|\s)--output-format(?:\s|$)/ }
|
|
880
|
+
];
|
|
881
|
+
const model = options && typeof options.model === 'string' ? options.model.trim() : '';
|
|
882
|
+
if (model) {
|
|
883
|
+
flagSpecs.push({ flag: `--model ${model}`, pattern: /(?:^|\s)(?:-m|--model)(?:\s|$)/ });
|
|
884
|
+
}
|
|
839
885
|
const geminiTemplate = prependAgentFlags(
|
|
840
886
|
templateText,
|
|
841
887
|
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)gemini\b)(.*)$/,
|
|
842
|
-
|
|
843
|
-
{ flag: '--output-format stream-json', pattern: /(?:^|\s)--output-format(?:\s|$)/ }
|
|
844
|
-
]
|
|
888
|
+
flagSpecs
|
|
845
889
|
);
|
|
846
890
|
return geminiTemplate === templateText
|
|
847
891
|
? renderAgentPromptCommand(templateText, prompt)
|
|
@@ -860,6 +904,10 @@ function buildOpenCodeAgentExecCommand(template, prompt, options = {}) {
|
|
|
860
904
|
pattern: /(?:^|\s)(?:--session|-s)(?:\s|$)/
|
|
861
905
|
});
|
|
862
906
|
}
|
|
907
|
+
const model = options && typeof options.model === 'string' ? options.model.trim() : '';
|
|
908
|
+
if (model) {
|
|
909
|
+
flagSpecs.push({ flag: `--model ${model}`, pattern: /(?:^|\s)(?:-m|--model)(?:\s|$)/ });
|
|
910
|
+
}
|
|
863
911
|
const opencodeTemplate = prependAgentFlags(
|
|
864
912
|
templateText,
|
|
865
913
|
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)opencode\b.*?\s+run\b)(.*)$/,
|
|
@@ -875,7 +923,7 @@ function buildWebAgentExecCommand(template, prompt, agentProgram, options = {})
|
|
|
875
923
|
case 'claude':
|
|
876
924
|
return buildClaudeAgentExecCommand(template, prompt, options);
|
|
877
925
|
case 'gemini':
|
|
878
|
-
return buildGeminiAgentExecCommand(template, prompt);
|
|
926
|
+
return buildGeminiAgentExecCommand(template, prompt, options);
|
|
879
927
|
case 'codex':
|
|
880
928
|
return buildCodexAgentExecCommand(template, prompt, options);
|
|
881
929
|
case 'opencode':
|
|
@@ -1138,6 +1186,162 @@ function accumulateUsageTotal(baseline, turnUsage) {
|
|
|
1138
1186
|
};
|
|
1139
1187
|
}
|
|
1140
1188
|
|
|
1189
|
+
// 起一个短命的容器内子进程,通过对应协议问一次元信息(初始化握手 / model 列表),拿到结果就杀掉进程——
|
|
1190
|
+
// 不是长驻会话,不会产生真实对话轮次或费用。
|
|
1191
|
+
function runAgentProtocolProcess(ctx, containerName, command, options = {}) {
|
|
1192
|
+
const stdinLines = Array.isArray(options.stdinLines) ? options.stdinLines : [];
|
|
1193
|
+
const onLine = typeof options.onLine === 'function' ? options.onLine : null;
|
|
1194
|
+
const timeoutMs = typeof options.timeoutMs === 'number' ? options.timeoutMs : 8000;
|
|
1195
|
+
return new Promise(resolve => {
|
|
1196
|
+
let settled = false;
|
|
1197
|
+
let pending = '';
|
|
1198
|
+
let stdoutAll = '';
|
|
1199
|
+
let child;
|
|
1200
|
+
try {
|
|
1201
|
+
child = spawn(ctx.dockerCmd, ['exec', '-i', containerName, '/bin/bash', '-lc', command], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
1202
|
+
} catch (e) {
|
|
1203
|
+
resolve({ result: null, stdout: '' });
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
1207
|
+
function finish(result) {
|
|
1208
|
+
if (settled) return;
|
|
1209
|
+
settled = true;
|
|
1210
|
+
clearTimeout(timer);
|
|
1211
|
+
try { child.kill('SIGKILL'); } catch (e) { /* 进程可能已退出 */ }
|
|
1212
|
+
resolve({ result, stdout: stdoutAll });
|
|
1213
|
+
}
|
|
1214
|
+
child.on('error', () => finish(null));
|
|
1215
|
+
child.on('close', () => finish(null));
|
|
1216
|
+
child.stdout.on('data', chunk => {
|
|
1217
|
+
const text = chunk.toString('utf-8');
|
|
1218
|
+
stdoutAll += text;
|
|
1219
|
+
if (!onLine) {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
pending += text;
|
|
1223
|
+
let newlineIndex = pending.indexOf('\n');
|
|
1224
|
+
while (newlineIndex !== -1) {
|
|
1225
|
+
const line = pending.slice(0, newlineIndex);
|
|
1226
|
+
pending = pending.slice(newlineIndex + 1);
|
|
1227
|
+
const trimmed = line.trim();
|
|
1228
|
+
if (trimmed) {
|
|
1229
|
+
const payload = parseJsonObjectLine(trimmed);
|
|
1230
|
+
if (payload) {
|
|
1231
|
+
const matched = onLine(payload);
|
|
1232
|
+
if (matched !== undefined) {
|
|
1233
|
+
finish(matched);
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
newlineIndex = pending.indexOf('\n');
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
try {
|
|
1242
|
+
stdinLines.forEach(line => child.stdin.write(`${line}\n`));
|
|
1243
|
+
if (!stdinLines.length) {
|
|
1244
|
+
child.stdin.end();
|
|
1245
|
+
}
|
|
1246
|
+
} catch (e) { /* 进程可能已提前退出 */ }
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function normalizeAgentModelCatalogEntry(item) {
|
|
1251
|
+
const value = pickFirstString(item && item.value, item && item.id, item && item.model);
|
|
1252
|
+
if (!value) {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
return {
|
|
1256
|
+
value,
|
|
1257
|
+
label: pickFirstString(item && item.displayName, value),
|
|
1258
|
+
description: pickFirstString(item && item.description)
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function extractClaudeModelsFromControlResponse(payload) {
|
|
1263
|
+
if (!payload || payload.type !== 'control_response') {
|
|
1264
|
+
return undefined;
|
|
1265
|
+
}
|
|
1266
|
+
const response = toPlainObject(payload.response);
|
|
1267
|
+
if (response.request_id !== 'manyoyo-model-catalog') {
|
|
1268
|
+
return undefined;
|
|
1269
|
+
}
|
|
1270
|
+
const inner = toPlainObject(response.response);
|
|
1271
|
+
const models = Array.isArray(inner.models) ? inner.models : [];
|
|
1272
|
+
return models.map(normalizeAgentModelCatalogEntry).filter(Boolean);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function extractCodexModelsFromListResponse(payload) {
|
|
1276
|
+
if (!payload || payload.id !== 'manyoyo-model-list' || !payload.result) {
|
|
1277
|
+
return undefined;
|
|
1278
|
+
}
|
|
1279
|
+
const data = Array.isArray(payload.result.data) ? payload.result.data : [];
|
|
1280
|
+
return data.map(normalizeAgentModelCatalogEntry).filter(Boolean);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
function extractOpenCodeModelsFromOutput(text) {
|
|
1284
|
+
return String(text || '')
|
|
1285
|
+
.split('\n')
|
|
1286
|
+
.map(line => line.trim())
|
|
1287
|
+
.filter(Boolean)
|
|
1288
|
+
.map(value => ({ value, label: value, description: '' }));
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
async function fetchAgentModelCatalog(ctx, containerName, agentProgram) {
|
|
1292
|
+
if (agentProgram === 'claude') {
|
|
1293
|
+
const requestLine = JSON.stringify({
|
|
1294
|
+
type: 'control_request',
|
|
1295
|
+
request_id: 'manyoyo-model-catalog',
|
|
1296
|
+
request: { subtype: 'initialize' }
|
|
1297
|
+
});
|
|
1298
|
+
const { result } = await runAgentProtocolProcess(
|
|
1299
|
+
ctx,
|
|
1300
|
+
containerName,
|
|
1301
|
+
'claude -p --input-format stream-json --output-format stream-json --verbose',
|
|
1302
|
+
{ stdinLines: [requestLine], onLine: extractClaudeModelsFromControlResponse }
|
|
1303
|
+
);
|
|
1304
|
+
return Array.isArray(result) ? result : [];
|
|
1305
|
+
}
|
|
1306
|
+
if (agentProgram === 'codex') {
|
|
1307
|
+
const stdinLines = [
|
|
1308
|
+
JSON.stringify({ jsonrpc: '2.0', id: 'manyoyo-init', method: 'initialize', params: { clientInfo: { name: 'manyoyo', version: '1.0.0' } } }),
|
|
1309
|
+
JSON.stringify({ jsonrpc: '2.0', id: 'manyoyo-model-list', method: 'model/list', params: {} })
|
|
1310
|
+
];
|
|
1311
|
+
const { result } = await runAgentProtocolProcess(
|
|
1312
|
+
ctx,
|
|
1313
|
+
containerName,
|
|
1314
|
+
'codex app-server',
|
|
1315
|
+
{ stdinLines, onLine: extractCodexModelsFromListResponse }
|
|
1316
|
+
);
|
|
1317
|
+
return Array.isArray(result) ? result : [];
|
|
1318
|
+
}
|
|
1319
|
+
if (agentProgram === 'opencode') {
|
|
1320
|
+
const { stdout } = await runAgentProtocolProcess(ctx, containerName, 'opencode models', {});
|
|
1321
|
+
return extractOpenCodeModelsFromOutput(stdout);
|
|
1322
|
+
}
|
|
1323
|
+
// Gemini 的 --acp 模式在认证前就会挂起重试,没有可用的“认证前”探测窗口,暂不支持目录抓取。
|
|
1324
|
+
return [];
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
const SAFE_AGENT_MODEL_VALUE_PATTERN = /^[A-Za-z0-9._\-[\]/]+$/;
|
|
1328
|
+
|
|
1329
|
+
function normalizeAgentModelValueInput(value) {
|
|
1330
|
+
const trimmed = String(value || '').trim();
|
|
1331
|
+
if (!trimmed) {
|
|
1332
|
+
return '';
|
|
1333
|
+
}
|
|
1334
|
+
if (!SAFE_AGENT_MODEL_VALUE_PATTERN.test(trimmed)) {
|
|
1335
|
+
throw new Error(`模型名称包含非法字符: ${trimmed}`);
|
|
1336
|
+
}
|
|
1337
|
+
return trimmed;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function normalizeStoredAgentModelValue(value) {
|
|
1341
|
+
const trimmed = typeof value === 'string' ? value.trim() : '';
|
|
1342
|
+
return SAFE_AGENT_MODEL_VALUE_PATTERN.test(trimmed) ? trimmed : '';
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1141
1345
|
function getAgentRuntimeMeta(template) {
|
|
1142
1346
|
const normalizedTemplate = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
1143
1347
|
const agentProgram = resolveAgentProgram(normalizedTemplate);
|
|
@@ -1843,7 +2047,8 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
|
1843
2047
|
? prompt
|
|
1844
2048
|
: buildAgentPromptWithHistory(agentSession, prompt);
|
|
1845
2049
|
const command = buildWebAgentExecCommand(effectiveTemplate, effectivePrompt, agentMeta.agentProgram, {
|
|
1846
|
-
sessionId: sessionIdForRun
|
|
2050
|
+
sessionId: sessionIdForRun,
|
|
2051
|
+
model: agentSession.model || ''
|
|
1847
2052
|
});
|
|
1848
2053
|
const contextMode = resumeSucceeded ? 'resume' : (hasPriorConversation ? 'history-injected' : 'first-turn');
|
|
1849
2054
|
|
|
@@ -2844,19 +3049,6 @@ async function createClonedContainer(ctx, state, sourceContainerName, requestedN
|
|
|
2844
3049
|
};
|
|
2845
3050
|
}
|
|
2846
3051
|
|
|
2847
|
-
function resolveContainerNamesByHostPath(ctx, state, hostPath) {
|
|
2848
|
-
const target = String(hostPath || '').trim();
|
|
2849
|
-
const containerMap = listWebManyoyoContainers(ctx);
|
|
2850
|
-
const names = new Set([
|
|
2851
|
-
...Object.keys(containerMap),
|
|
2852
|
-
...listWebHistorySessionNames(state.webHistoryDir, ctx.isValidContainerName)
|
|
2853
|
-
]);
|
|
2854
|
-
return Array.from(names).filter(name => {
|
|
2855
|
-
const summary = buildSessionSummary(ctx, state, containerMap, { containerName: name, agentId: WEB_DEFAULT_AGENT_ID });
|
|
2856
|
-
return summary && String(summary.hostPath || '').trim() === target;
|
|
2857
|
-
});
|
|
2858
|
-
}
|
|
2859
|
-
|
|
2860
3052
|
// Estimate container start time from "Up X hours/minutes/seconds" status string.
|
|
2861
3053
|
// Uses relative time to avoid Podman Machine VM clock drift issues.
|
|
2862
3054
|
function estimateStartTimeFromStatus(status) {
|
|
@@ -3606,11 +3798,13 @@ function buildSessionSummary(ctx, state, containerMap, sessionRef) {
|
|
|
3606
3798
|
const containerName = sessionRef && sessionRef.containerName ? sessionRef.containerName : '';
|
|
3607
3799
|
const agentId = sessionRef && sessionRef.agentId ? sessionRef.agentId : WEB_DEFAULT_AGENT_ID;
|
|
3608
3800
|
const history = loadWebSessionHistory(state.webHistoryDir, containerName);
|
|
3609
|
-
const
|
|
3801
|
+
const realAgentSession = getWebAgentSession(history, agentId);
|
|
3802
|
+
const agentSession = realAgentSession
|
|
3610
3803
|
|| (agentId === WEB_DEFAULT_AGENT_ID ? createEmptyWebAgentSession(WEB_DEFAULT_AGENT_ID) : null);
|
|
3611
3804
|
if (!agentSession) {
|
|
3612
3805
|
return null;
|
|
3613
3806
|
}
|
|
3807
|
+
const synthetic = !realAgentSession;
|
|
3614
3808
|
const latestMessage = agentSession.messages.length ? agentSession.messages[agentSession.messages.length - 1] : null;
|
|
3615
3809
|
const containerInfo = containerMap[containerName] || {};
|
|
3616
3810
|
const effectiveAgentPromptCommand = resolveEffectiveAgentPromptCommandForSession(history, agentId, containerInfo.defaultCommand);
|
|
@@ -3645,8 +3839,10 @@ function buildSessionSummary(ctx, state, containerMap, sessionRef) {
|
|
|
3645
3839
|
agentEnabled: isAgentPromptCommandEnabled(effectiveAgentPromptCommand),
|
|
3646
3840
|
agentProgram: effectiveAgentProgram || '',
|
|
3647
3841
|
resumeSupported: effectiveResumeSupported,
|
|
3842
|
+
model: agentSession.model || '',
|
|
3648
3843
|
hostPath: applied.hostPath || '',
|
|
3649
|
-
containerPath: applied.containerPath || ''
|
|
3844
|
+
containerPath: applied.containerPath || '',
|
|
3845
|
+
...(synthetic ? { synthetic: true } : {})
|
|
3650
3846
|
};
|
|
3651
3847
|
}
|
|
3652
3848
|
|
|
@@ -3731,6 +3927,7 @@ function buildSessionDetail(ctx, state, containerMap, name) {
|
|
|
3731
3927
|
lastResumeOk: typeof agentSession.lastResumeOk === 'boolean' ? agentSession.lastResumeOk : null,
|
|
3732
3928
|
lastResumeError: agentSession.lastResumeError || '',
|
|
3733
3929
|
usageTotal: agentSession.usageTotal || null,
|
|
3930
|
+
model: agentSession.model || '',
|
|
3734
3931
|
applied
|
|
3735
3932
|
};
|
|
3736
3933
|
}
|
|
@@ -4392,37 +4589,6 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
4392
4589
|
sendJson(res, 200, { name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId), remark });
|
|
4393
4590
|
}
|
|
4394
4591
|
},
|
|
4395
|
-
{
|
|
4396
|
-
method: 'POST',
|
|
4397
|
-
match: currentPath => currentPath === '/api/sessions/by-directory/remove' ? [] : null,
|
|
4398
|
-
handler: async () => {
|
|
4399
|
-
const payload = await readJsonBody(req);
|
|
4400
|
-
const hostPath = typeof payload.hostPath === 'string' ? payload.hostPath : '';
|
|
4401
|
-
if (!hostPath.trim()) {
|
|
4402
|
-
sendJson(res, 400, { error: 'hostPath 不能为空' });
|
|
4403
|
-
return;
|
|
4404
|
-
}
|
|
4405
|
-
const targets = resolveContainerNamesByHostPath(ctx, state, hostPath);
|
|
4406
|
-
if (!targets.length) {
|
|
4407
|
-
sendJson(res, 404, { error: '未找到该工作目录下的容器' });
|
|
4408
|
-
return;
|
|
4409
|
-
}
|
|
4410
|
-
const removed = [];
|
|
4411
|
-
const errors = [];
|
|
4412
|
-
targets.forEach(name => {
|
|
4413
|
-
try {
|
|
4414
|
-
if (ctx.containerExists(name)) {
|
|
4415
|
-
ctx.removeContainer(name);
|
|
4416
|
-
}
|
|
4417
|
-
removeWebSessionHistory(state.webHistoryDir, name);
|
|
4418
|
-
removed.push(name);
|
|
4419
|
-
} catch (e) {
|
|
4420
|
-
errors.push({ name, error: e.message || String(e) });
|
|
4421
|
-
}
|
|
4422
|
-
});
|
|
4423
|
-
sendJson(res, 200, { hostPath, removed, errors });
|
|
4424
|
-
}
|
|
4425
|
-
},
|
|
4426
4592
|
{
|
|
4427
4593
|
method: 'GET',
|
|
4428
4594
|
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/messages$/),
|
|
@@ -4648,6 +4814,51 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
4648
4814
|
sendJson(res, 200, { name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId), detail });
|
|
4649
4815
|
}
|
|
4650
4816
|
},
|
|
4817
|
+
{
|
|
4818
|
+
method: 'GET',
|
|
4819
|
+
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/models$/),
|
|
4820
|
+
handler: async match => {
|
|
4821
|
+
const sessionRef = getValidSessionRef(ctx, res, match[1]);
|
|
4822
|
+
if (!sessionRef) {
|
|
4823
|
+
return;
|
|
4824
|
+
}
|
|
4825
|
+
const containerMap = listWebManyoyoContainers(ctx);
|
|
4826
|
+
const containerInfo = containerMap[sessionRef.containerName] || {};
|
|
4827
|
+
const history = loadWebSessionHistory(state.webHistoryDir, sessionRef.containerName);
|
|
4828
|
+
const effectiveTemplate = resolveEffectiveAgentPromptCommandForSession(
|
|
4829
|
+
history,
|
|
4830
|
+
sessionRef.agentId,
|
|
4831
|
+
containerInfo.defaultCommand
|
|
4832
|
+
);
|
|
4833
|
+
const agentProgram = resolveAgentProgram(effectiveTemplate);
|
|
4834
|
+
const models = await fetchAgentModelCatalog(ctx, sessionRef.containerName, agentProgram);
|
|
4835
|
+
sendJson(res, 200, {
|
|
4836
|
+
name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId),
|
|
4837
|
+
agentProgram,
|
|
4838
|
+
models
|
|
4839
|
+
});
|
|
4840
|
+
}
|
|
4841
|
+
},
|
|
4842
|
+
{
|
|
4843
|
+
method: 'POST',
|
|
4844
|
+
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/model$/),
|
|
4845
|
+
handler: async match => {
|
|
4846
|
+
const sessionRef = getValidSessionRef(ctx, res, match[1]);
|
|
4847
|
+
if (!sessionRef) {
|
|
4848
|
+
return;
|
|
4849
|
+
}
|
|
4850
|
+
const payload = await readJsonBody(req);
|
|
4851
|
+
let model;
|
|
4852
|
+
try {
|
|
4853
|
+
model = normalizeAgentModelValueInput(payload.model);
|
|
4854
|
+
} catch (e) {
|
|
4855
|
+
sendJson(res, 400, { error: e.message || String(e) });
|
|
4856
|
+
return;
|
|
4857
|
+
}
|
|
4858
|
+
patchWebAgentSessionState(state.webHistoryDir, sessionRef, { model });
|
|
4859
|
+
sendJson(res, 200, { name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId), model });
|
|
4860
|
+
}
|
|
4861
|
+
},
|
|
4651
4862
|
{
|
|
4652
4863
|
method: 'GET',
|
|
4653
4864
|
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/audit$/),
|
|
@@ -5013,12 +5224,23 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
5013
5224
|
return;
|
|
5014
5225
|
}
|
|
5015
5226
|
|
|
5016
|
-
|
|
5017
|
-
|
|
5227
|
+
const payload = await readJsonBody(req);
|
|
5228
|
+
const removeHistory = payload.removeHistory === true;
|
|
5229
|
+
const { removedContainer } = removeContainerIdempotent(ctx, sessionRef.containerName);
|
|
5230
|
+
|
|
5231
|
+
if (removeHistory) {
|
|
5232
|
+
const history = loadWebSessionHistory(state.webHistoryDir, sessionRef.containerName);
|
|
5233
|
+
removeAllAgentHistoryArtifacts(state.webHistoryDir, sessionRef.containerName, Object.keys(history.agents || {}));
|
|
5234
|
+
removeWebSessionHistory(state.webHistoryDir, sessionRef.containerName);
|
|
5235
|
+
} else if (removedContainer) {
|
|
5018
5236
|
appendWebSessionMessage(state.webHistoryDir, sessionRef, 'system', `容器 ${sessionRef.containerName} 已删除。`);
|
|
5019
5237
|
}
|
|
5020
5238
|
|
|
5021
|
-
sendJson(res, 200, {
|
|
5239
|
+
sendJson(res, 200, {
|
|
5240
|
+
removed: true,
|
|
5241
|
+
removedHistory: removeHistory,
|
|
5242
|
+
name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId)
|
|
5243
|
+
});
|
|
5022
5244
|
}
|
|
5023
5245
|
},
|
|
5024
5246
|
{
|
|
@@ -5030,12 +5252,17 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
5030
5252
|
return;
|
|
5031
5253
|
}
|
|
5032
5254
|
|
|
5255
|
+
const payload = await readJsonBody(req);
|
|
5256
|
+
const removeHistory = payload.removeHistory === true;
|
|
5257
|
+
|
|
5033
5258
|
const history = loadWebSessionHistory(state.webHistoryDir, sessionRef.containerName);
|
|
5034
5259
|
if (history.agents && typeof history.agents === 'object') {
|
|
5035
|
-
if (
|
|
5036
|
-
delete history.agents[WEB_DEFAULT_AGENT_ID];
|
|
5037
|
-
} else {
|
|
5260
|
+
if (removeHistory) {
|
|
5038
5261
|
delete history.agents[sessionRef.agentId];
|
|
5262
|
+
removeAllAgentHistoryArtifacts(state.webHistoryDir, sessionRef.containerName, [sessionRef.agentId]);
|
|
5263
|
+
} else if (history.agents[sessionRef.agentId]) {
|
|
5264
|
+
history.agents[sessionRef.agentId].archived = true;
|
|
5265
|
+
history.agents[sessionRef.agentId].updatedAt = new Date().toISOString();
|
|
5039
5266
|
}
|
|
5040
5267
|
}
|
|
5041
5268
|
if (!Object.keys(history.agents || {}).length && !ctx.containerExists(sessionRef.containerName)) {
|
|
@@ -5044,7 +5271,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
5044
5271
|
saveWebSessionHistory(state.webHistoryDir, sessionRef.containerName, history);
|
|
5045
5272
|
}
|
|
5046
5273
|
sendJson(res, 200, {
|
|
5047
|
-
removedHistory:
|
|
5274
|
+
removedHistory: removeHistory,
|
|
5048
5275
|
name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId)
|
|
5049
5276
|
});
|
|
5050
5277
|
}
|