@xcanwin/manyoyo 6.2.10 → 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.
@@ -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 = {
@@ -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;
@@ -747,6 +751,15 @@ textarea:focus-visible {
747
751
  }
748
752
  }
749
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
+
750
763
  .tree-node-menu-trigger {
751
764
  width: 26px;
752
765
  height: 26px;
@@ -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">
@@ -179,6 +179,12 @@
179
179
  const createEnv = document.getElementById('createEnv');
180
180
  const createEnvFile = document.getElementById('createEnvFile');
181
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');
182
188
  const directoryPickerModal = document.getElementById('directoryPickerModal');
183
189
  const directoryPickerTitle = document.getElementById('directoryPickerTitle');
184
190
  const directoryPickerTip = document.getElementById('directoryPickerTip');
@@ -3167,11 +3173,47 @@
3167
3173
  return wrap;
3168
3174
  }
3169
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
+
3170
3208
  async function removeContainerByName(containerName) {
3171
3209
  const target = String(containerName || '').trim();
3172
3210
  if (!target) return;
3173
- const yes = confirm('确认删除容器 ' + target + ' ?');
3174
- if (!yes) return;
3211
+ const choice = await confirmRemoveChoice({
3212
+ title: '删除容器',
3213
+ message: `确认删除容器 ${target}?可以选择是否同时删除该容器的全部历史记录(消息与事件日志)。`
3214
+ });
3215
+ if (!choice) return;
3216
+ const removeHistory = choice === 'with-history';
3175
3217
  try {
3176
3218
  const wasActiveContainer = parseSessionKey(state.active).containerName === target;
3177
3219
  if (parseSessionKey(state.terminal.sessionName).containerName === target
@@ -3179,7 +3221,8 @@
3179
3221
  disconnectTerminal('容器删除,终端已断开', true);
3180
3222
  }
3181
3223
  await api('/api/sessions/' + encodeURIComponent(target) + '/remove', {
3182
- method: 'POST'
3224
+ method: 'POST',
3225
+ body: JSON.stringify({ removeHistory })
3183
3226
  });
3184
3227
  await refreshSessions({
3185
3228
  preferredName: wasActiveContainer ? target : (state.active || ''),
@@ -3194,8 +3237,12 @@
3194
3237
  async function removeAgentSessionByName(sessionName, agentLabel) {
3195
3238
  const target = String(sessionName || '').trim();
3196
3239
  if (!target) return;
3197
- const yes = confirm('确认删除 AGENT ' + (agentLabel || target) + ' ?');
3198
- if (!yes) return;
3240
+ const choice = await confirmRemoveChoice({
3241
+ title: '删除 AGENT',
3242
+ message: `确认删除 AGENT ${agentLabel || target}?可以选择是否同时删除它的历史记录(消息与事件日志)。`
3243
+ });
3244
+ if (!choice) return;
3245
+ const removeHistory = choice === 'with-history';
3199
3246
  const isActive = target === state.active;
3200
3247
  try {
3201
3248
  const containerName = parseSessionKey(target).containerName;
@@ -3203,7 +3250,8 @@
3203
3250
  ? findPreferredSessionNameAfterRemoval(state.sessions, target)
3204
3251
  : '';
3205
3252
  await api('/api/sessions/' + encodeURIComponent(target) + '/remove-with-history', {
3206
- method: 'POST'
3253
+ method: 'POST',
3254
+ body: JSON.stringify({ removeHistory })
3207
3255
  });
3208
3256
  await refreshSessions({
3209
3257
  preferredName: isActive ? (fallbackSessionName || '') : (state.active || ''),
@@ -3319,11 +3367,50 @@
3319
3367
  sep.textContent = '›';
3320
3368
  sessionBreadcrumb.appendChild(sep);
3321
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
+
3322
3376
  const containerCrumb = document.createElement('span');
3323
3377
  containerCrumb.className = 'crumb-item is-current';
3324
- containerCrumb.textContent = state.navContainer;
3325
- containerCrumb.title = state.navContainer;
3378
+ containerCrumb.textContent = containerRemark || state.navContainer;
3379
+ containerCrumb.title = containerRemark ? `${containerRemark}(${state.navContainer})` : state.navContainer;
3326
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
+ ]));
3327
3414
  }
3328
3415
 
3329
3416
  const activeSession = getActiveSession();
@@ -3442,7 +3529,8 @@
3442
3529
 
3443
3530
  function renderAgentLevel(containerGroup) {
3444
3531
  sessionList.innerHTML = '';
3445
- const sessions = containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions : [];
3532
+ const allSessions = containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions : [];
3533
+ const sessions = allSessions.filter(function (session) { return !session.synthetic; });
3446
3534
  if (!sessions.length) {
3447
3535
  const empty = document.createElement('div');
3448
3536
  empty.className = 'empty';
@@ -3514,9 +3602,10 @@
3514
3602
  const containerCount = new Set(state.sessions.map(function (session) {
3515
3603
  return session && session.containerName ? session.containerName : '';
3516
3604
  }).filter(Boolean)).size;
3605
+ const agentCount = state.sessions.filter(function (session) { return !session.synthetic; }).length;
3517
3606
  sessionCount.textContent = state.loadingSessions
3518
3607
  ? '加载中...'
3519
- : `${containerCount} 个容器 / ${state.sessions.length} 个 AGENT`;
3608
+ : `${containerCount} 个容器 / ${agentCount} 个 AGENT`;
3520
3609
  renderBreadcrumb();
3521
3610
 
3522
3611
  if (state.loadingSessions) {
@@ -4876,6 +4965,14 @@
4876
4965
  });
4877
4966
  }
4878
4967
 
4968
+ if (removeConfirmModal) {
4969
+ removeConfirmModal.addEventListener('click', function (event) {
4970
+ if (event.target === removeConfirmModal) {
4971
+ removeConfirmCancelBtn.click();
4972
+ }
4973
+ });
4974
+ }
4975
+
4879
4976
  if (directoryPickerModal) {
4880
4977
  directoryPickerModal.addEventListener('click', function (event) {
4881
4978
  if (event.target === directoryPickerModal && !state.directoryPicker.loading) {
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: [],
@@ -221,6 +222,7 @@ function normalizeWebAgentSessionRecord(agentId, rawAgent) {
221
222
  ? normalizeAgentPromptCommandTemplate(source.agentPromptCommand, `agents.${agentId}.agentPromptCommand`)
222
223
  : '',
223
224
  remark: normalizeRemarkText(source.remark),
225
+ archived: source.archived === true,
224
226
  createdAt: typeof source.createdAt === 'string' ? source.createdAt : null,
225
227
  updatedAt: typeof source.updatedAt === 'string' ? source.updatedAt : null,
226
228
  messages: Array.isArray(source.messages) ? source.messages : [],
@@ -312,7 +314,7 @@ function normalizeWebHistoryRecord(containerName, rawData) {
312
314
  });
313
315
  }
314
316
 
315
- 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) {
316
318
  history.agents[WEB_DEFAULT_AGENT_ID] = normalizeWebAgentSessionRecord(WEB_DEFAULT_AGENT_ID, {
317
319
  agentName: data.agentName || WEB_DEFAULT_AGENT_NAME,
318
320
  updatedAt: typeof data.updatedAt === 'string' ? data.updatedAt : null,
@@ -345,16 +347,18 @@ function saveWebSessionHistory(webHistoryDir, containerName, history) {
345
347
  const filePath = getWebHistoryFile(webHistoryDir, containerName);
346
348
  const normalized = normalizeWebHistoryRecord(containerName, history);
347
349
  const runtimeMeta = getAgentRuntimeMeta(normalized.agentPromptCommand || '');
348
- const defaultAgent = getWebAgentSession(normalized, WEB_DEFAULT_AGENT_ID) || createEmptyWebAgentSession(WEB_DEFAULT_AGENT_ID);
350
+ const defaultAgent = getWebAgentSession(normalized, WEB_DEFAULT_AGENT_ID);
349
351
  const legacyCompatible = {
350
352
  ...normalized,
351
- messages: Array.isArray(defaultAgent.messages) ? defaultAgent.messages : [],
352
353
  agentProgram: runtimeMeta.agentProgram || '',
353
- resumeSupported: runtimeMeta.resumeSupported === true,
354
- lastResumeAt: defaultAgent.lastResumeAt || null,
355
- lastResumeOk: typeof defaultAgent.lastResumeOk === 'boolean' ? defaultAgent.lastResumeOk : null,
356
- lastResumeError: defaultAgent.lastResumeError || ''
354
+ resumeSupported: runtimeMeta.resumeSupported === true
357
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
+ }
358
362
  fs.writeFileSync(filePath, JSON.stringify(legacyCompatible, null, 4));
359
363
  }
360
364
 
@@ -366,6 +370,28 @@ function removeWebSessionHistory(webHistoryDir, containerName) {
366
370
  }
367
371
  }
368
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
+
369
395
  function listWebHistorySessionNames(webHistoryDir, isValidContainerName) {
370
396
  ensureWebHistoryDir(webHistoryDir);
371
397
  return fs.readdirSync(webHistoryDir)
@@ -405,14 +431,19 @@ function parseWebSessionKey(sessionKey) {
405
431
  };
406
432
  }
407
433
 
434
+ function isVisibleWebAgentSession(agentSession) {
435
+ return Boolean(agentSession) && agentSession.archived !== true;
436
+ }
437
+
408
438
  function getWebAgentSession(history, agentId, options = {}) {
409
439
  const sessionHistory = history && typeof history === 'object' ? history : { agents: {} };
410
440
  if (!sessionHistory.agents || typeof sessionHistory.agents !== 'object' || Array.isArray(sessionHistory.agents)) {
411
441
  sessionHistory.agents = {};
412
442
  }
413
443
  const requestedAgentId = String(agentId || WEB_DEFAULT_AGENT_ID).trim() || WEB_DEFAULT_AGENT_ID;
414
- if (sessionHistory.agents[requestedAgentId]) {
415
- return sessionHistory.agents[requestedAgentId];
444
+ const existing = sessionHistory.agents[requestedAgentId];
445
+ if (isVisibleWebAgentSession(existing)) {
446
+ return existing;
416
447
  }
417
448
  if (options.create === true) {
418
449
  const agentSession = createEmptyWebAgentSession(requestedAgentId);
@@ -427,21 +458,20 @@ function listWebAgentSessions(history, options = {}) {
427
458
  const agents = sessionHistory.agents && typeof sessionHistory.agents === 'object' && !Array.isArray(sessionHistory.agents)
428
459
  ? sessionHistory.agents
429
460
  : {};
430
- const agentIds = Object.keys(agents);
431
- if (!agentIds.length && options.includeSyntheticDefault === true) {
432
- return [createEmptyWebAgentSession(WEB_DEFAULT_AGENT_ID)];
433
- }
434
- return agentIds
461
+ const visibleSessions = Object.keys(agents)
435
462
  .map(agentId => agents[agentId])
436
- .filter(Boolean)
437
- .sort((a, b) => {
438
- const orderA = a.agentId === WEB_DEFAULT_AGENT_ID ? 0 : 1;
439
- const orderB = b.agentId === WEB_DEFAULT_AGENT_ID ? 0 : 1;
440
- if (orderA !== orderB) {
441
- return orderA - orderB;
442
- }
443
- return String(a.agentName || '').localeCompare(String(b.agentName || ''), 'zh-CN');
444
- });
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
+ });
445
475
  }
446
476
 
447
477
  function getWebAgentCreationRank(agentId) {
@@ -3019,19 +3049,6 @@ async function createClonedContainer(ctx, state, sourceContainerName, requestedN
3019
3049
  };
3020
3050
  }
3021
3051
 
3022
- function resolveContainerNamesByHostPath(ctx, state, hostPath) {
3023
- const target = String(hostPath || '').trim();
3024
- const containerMap = listWebManyoyoContainers(ctx);
3025
- const names = new Set([
3026
- ...Object.keys(containerMap),
3027
- ...listWebHistorySessionNames(state.webHistoryDir, ctx.isValidContainerName)
3028
- ]);
3029
- return Array.from(names).filter(name => {
3030
- const summary = buildSessionSummary(ctx, state, containerMap, { containerName: name, agentId: WEB_DEFAULT_AGENT_ID });
3031
- return summary && String(summary.hostPath || '').trim() === target;
3032
- });
3033
- }
3034
-
3035
3052
  // Estimate container start time from "Up X hours/minutes/seconds" status string.
3036
3053
  // Uses relative time to avoid Podman Machine VM clock drift issues.
3037
3054
  function estimateStartTimeFromStatus(status) {
@@ -3781,11 +3798,13 @@ function buildSessionSummary(ctx, state, containerMap, sessionRef) {
3781
3798
  const containerName = sessionRef && sessionRef.containerName ? sessionRef.containerName : '';
3782
3799
  const agentId = sessionRef && sessionRef.agentId ? sessionRef.agentId : WEB_DEFAULT_AGENT_ID;
3783
3800
  const history = loadWebSessionHistory(state.webHistoryDir, containerName);
3784
- const agentSession = getWebAgentSession(history, agentId)
3801
+ const realAgentSession = getWebAgentSession(history, agentId);
3802
+ const agentSession = realAgentSession
3785
3803
  || (agentId === WEB_DEFAULT_AGENT_ID ? createEmptyWebAgentSession(WEB_DEFAULT_AGENT_ID) : null);
3786
3804
  if (!agentSession) {
3787
3805
  return null;
3788
3806
  }
3807
+ const synthetic = !realAgentSession;
3789
3808
  const latestMessage = agentSession.messages.length ? agentSession.messages[agentSession.messages.length - 1] : null;
3790
3809
  const containerInfo = containerMap[containerName] || {};
3791
3810
  const effectiveAgentPromptCommand = resolveEffectiveAgentPromptCommandForSession(history, agentId, containerInfo.defaultCommand);
@@ -3822,7 +3841,8 @@ function buildSessionSummary(ctx, state, containerMap, sessionRef) {
3822
3841
  resumeSupported: effectiveResumeSupported,
3823
3842
  model: agentSession.model || '',
3824
3843
  hostPath: applied.hostPath || '',
3825
- containerPath: applied.containerPath || ''
3844
+ containerPath: applied.containerPath || '',
3845
+ ...(synthetic ? { synthetic: true } : {})
3826
3846
  };
3827
3847
  }
3828
3848
 
@@ -4569,37 +4589,6 @@ async function handleWebApi(req, res, pathname, ctx, state) {
4569
4589
  sendJson(res, 200, { name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId), remark });
4570
4590
  }
4571
4591
  },
4572
- {
4573
- method: 'POST',
4574
- match: currentPath => currentPath === '/api/sessions/by-directory/remove' ? [] : null,
4575
- handler: async () => {
4576
- const payload = await readJsonBody(req);
4577
- const hostPath = typeof payload.hostPath === 'string' ? payload.hostPath : '';
4578
- if (!hostPath.trim()) {
4579
- sendJson(res, 400, { error: 'hostPath 不能为空' });
4580
- return;
4581
- }
4582
- const targets = resolveContainerNamesByHostPath(ctx, state, hostPath);
4583
- if (!targets.length) {
4584
- sendJson(res, 404, { error: '未找到该工作目录下的容器' });
4585
- return;
4586
- }
4587
- const removed = [];
4588
- const errors = [];
4589
- targets.forEach(name => {
4590
- try {
4591
- if (ctx.containerExists(name)) {
4592
- ctx.removeContainer(name);
4593
- }
4594
- removeWebSessionHistory(state.webHistoryDir, name);
4595
- removed.push(name);
4596
- } catch (e) {
4597
- errors.push({ name, error: e.message || String(e) });
4598
- }
4599
- });
4600
- sendJson(res, 200, { hostPath, removed, errors });
4601
- }
4602
- },
4603
4592
  {
4604
4593
  method: 'GET',
4605
4594
  match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/messages$/),
@@ -5235,12 +5224,23 @@ async function handleWebApi(req, res, pathname, ctx, state) {
5235
5224
  return;
5236
5225
  }
5237
5226
 
5238
- if (ctx.containerExists(sessionRef.containerName)) {
5239
- ctx.removeContainer(sessionRef.containerName);
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) {
5240
5236
  appendWebSessionMessage(state.webHistoryDir, sessionRef, 'system', `容器 ${sessionRef.containerName} 已删除。`);
5241
5237
  }
5242
5238
 
5243
- sendJson(res, 200, { removed: true, name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId) });
5239
+ sendJson(res, 200, {
5240
+ removed: true,
5241
+ removedHistory: removeHistory,
5242
+ name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId)
5243
+ });
5244
5244
  }
5245
5245
  },
5246
5246
  {
@@ -5252,12 +5252,17 @@ async function handleWebApi(req, res, pathname, ctx, state) {
5252
5252
  return;
5253
5253
  }
5254
5254
 
5255
+ const payload = await readJsonBody(req);
5256
+ const removeHistory = payload.removeHistory === true;
5257
+
5255
5258
  const history = loadWebSessionHistory(state.webHistoryDir, sessionRef.containerName);
5256
5259
  if (history.agents && typeof history.agents === 'object') {
5257
- if (sessionRef.agentId === WEB_DEFAULT_AGENT_ID) {
5258
- delete history.agents[WEB_DEFAULT_AGENT_ID];
5259
- } else {
5260
+ if (removeHistory) {
5260
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();
5261
5266
  }
5262
5267
  }
5263
5268
  if (!Object.keys(history.agents || {}).length && !ctx.containerExists(sessionRef.containerName)) {
@@ -5266,7 +5271,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
5266
5271
  saveWebSessionHistory(state.webHistoryDir, sessionRef.containerName, history);
5267
5272
  }
5268
5273
  sendJson(res, 200, {
5269
- removedHistory: true,
5274
+ removedHistory: removeHistory,
5270
5275
  name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId)
5271
5276
  });
5272
5277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "6.2.10",
3
+ "version": "6.2.11",
4
4
  "imageVersion": "1.9.1-common",
5
5
  "playwrightCliVersion": "0.1.18",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",