newmark-agent 0.4.0 → 0.4.2

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.
@@ -197,9 +197,14 @@ function createState(options = {}) {
197
197
  inputCursor: 0,
198
198
  conversationScroll: 0,
199
199
  conversationMaxScroll: 0,
200
+ contentScroll: 0,
201
+ contentFocusLine: -1,
200
202
  conversationHistoryFocus: false,
201
203
  historySelectedIndex: -1,
202
204
  historySelectedImageIndex: -1,
205
+ historyEventFocus: false,
206
+ historyEventIndex: -1,
207
+ collapsedBuildEvents: new Set(),
203
208
  historyVisibleRunIds: [],
204
209
  historyCursorDirection: 0,
205
210
  expandedBuildRuns: new Set(
@@ -549,6 +554,8 @@ function switchView(state, id) {
549
554
  state.input = "";
550
555
  state.inputCursor = 0;
551
556
  if (id === "chat") state.conversationScroll = 0;
557
+ state.contentScroll = 0;
558
+ state.contentFocusLine = -1;
552
559
  }
553
560
 
554
561
  function normalizedAutomation(item) {
@@ -1330,6 +1337,76 @@ function filteredCommands(state) {
1330
1337
  return data.commands.filter((command) => command.label.toLowerCase().includes(query));
1331
1338
  }
1332
1339
 
1340
+ function focusableEventsForRun(run) {
1341
+ return (run?.events || [])
1342
+ .filter((event) => {
1343
+ const type = String(event?.type || "").toLowerCase();
1344
+ return type && type !== "final_response" && !type.startsWith("guide");
1345
+ })
1346
+ .sort((left, right) => {
1347
+ const leftSeq = Number(left?.sequence);
1348
+ const rightSeq = Number(right?.sequence);
1349
+ if (Number.isFinite(leftSeq) && Number.isFinite(rightSeq) && leftSeq !== rightSeq) return leftSeq - rightSeq;
1350
+ const leftTime = new Date(left?.timestamp || left?.createdAt || "").getTime();
1351
+ const rightTime = new Date(right?.timestamp || right?.createdAt || "").getTime();
1352
+ return (Number.isFinite(leftTime) ? leftTime : 0) - (Number.isFinite(rightTime) ? rightTime : 0);
1353
+ });
1354
+ }
1355
+
1356
+ function buildEventKey(event, index) {
1357
+ return String(event?.id || event?.toolCallId || `${event?.type || "event"}:${event?.toolName || ""}:${index}`);
1358
+ }
1359
+
1360
+ function selectedHistoryRun(state) {
1361
+ const runs = [...(state.snapshot.workRuns || [])].sort((left, right) => Number(left.sequence || 0) - Number(right.sequence || 0));
1362
+ return runs[Number(state.historySelectedIndex) || 0];
1363
+ }
1364
+
1365
+ function enterHistoryEventFocus(state) {
1366
+ const run = selectedHistoryRun(state);
1367
+ const events = focusableEventsForRun(run);
1368
+ if (!run || !state.expandedBuildRuns?.has(run.runId) || !events.length) return false;
1369
+ state.historyEventFocus = true;
1370
+ state.historyEventIndex = 0;
1371
+ state.historySelectedImageIndex = -1;
1372
+ state.notice = `History focus · ${events.length} item(s) · Enter expand · ← back`;
1373
+ return true;
1374
+ }
1375
+
1376
+ function exitHistoryEventFocus(state) {
1377
+ state.historyEventFocus = false;
1378
+ state.historyEventIndex = -1;
1379
+ state.notice = "History focus · Build Block · Enter expands";
1380
+ return true;
1381
+ }
1382
+
1383
+ function moveHistoryEventCursor(state, direction) {
1384
+ const run = selectedHistoryRun(state);
1385
+ const events = focusableEventsForRun(run);
1386
+ if (!events.length) return false;
1387
+ const count = events.length;
1388
+ state.historyEventIndex = ((Number(state.historyEventIndex) || 0) + direction + count) % count;
1389
+ state.notice = `History focus · item ${state.historyEventIndex + 1}/${count} · Enter expand`;
1390
+ return true;
1391
+ }
1392
+
1393
+ function toggleSelectedBuildEvent(state) {
1394
+ const run = selectedHistoryRun(state);
1395
+ const events = focusableEventsForRun(run);
1396
+ const index = Number(state.historyEventIndex) || 0;
1397
+ const event = events[index];
1398
+ if (!event) return false;
1399
+ const key = buildEventKey(event, index);
1400
+ if (state.collapsedBuildEvents.has(key)) {
1401
+ state.collapsedBuildEvents.delete(key);
1402
+ state.notice = "Event expanded";
1403
+ } else {
1404
+ state.collapsedBuildEvents.add(key);
1405
+ state.notice = "Event collapsed";
1406
+ }
1407
+ return true;
1408
+ }
1409
+
1333
1410
  module.exports = {
1334
1411
  activeConversationModelLabel,
1335
1412
  INTELLIGENCE_TIERS,
@@ -1341,6 +1418,7 @@ module.exports = {
1341
1418
  applyConversationResult,
1342
1419
  beginAutomationCreate,
1343
1420
  beginWorkflowCreate,
1421
+ buildEventKey,
1344
1422
  conversationModelOptions,
1345
1423
  createAutomationFromDraft,
1346
1424
  createState,
@@ -1349,7 +1427,10 @@ module.exports = {
1349
1427
  cycleMemoryComponent,
1350
1428
  cycleSettingsTab,
1351
1429
  enterConversation,
1430
+ enterHistoryEventFocus,
1431
+ exitHistoryEventFocus,
1352
1432
  filteredCommands,
1433
+ focusableEventsForRun,
1353
1434
  itemCount,
1354
1435
  memoryColumnItems,
1355
1436
  memoryTagOptions,
@@ -1358,6 +1439,7 @@ module.exports = {
1358
1439
  moveMenuSelection,
1359
1440
  moveFocusHorizontal,
1360
1441
  moveConversationHistoryCursor,
1442
+ moveHistoryEventCursor,
1361
1443
  moveInputCursorVertical,
1362
1444
  moveSettingChoiceSelection,
1363
1445
  moveSelection,
@@ -1378,6 +1460,7 @@ module.exports = {
1378
1460
  toggleSelected,
1379
1461
  toggleConversationPinned,
1380
1462
  toggleSelectedBuildBlock,
1463
+ toggleSelectedBuildEvent,
1381
1464
  validateSelectedModel,
1382
1465
  workspaceMenuChildren
1383
1466
  };
@@ -10,6 +10,9 @@ try {
10
10
  if (startupQuery.get('startupPrewarm') === '1' && Number(startupQuery.get('startupAttempt') || 0) > 0) {
11
11
  document.documentElement.classList.add('startup-prewarm');
12
12
  }
13
+ if (sessionStorage.getItem('newmark-config-reloading') === '1') {
14
+ document.documentElement.classList.add('config-reloading');
15
+ }
13
16
  } catch {}
14
17
  </script>
15
18
  <style>
@@ -279,6 +282,7 @@ html, body {
279
282
  }
280
283
 
281
284
  .startup-prewarm #startup-cover { display: flex; }
285
+ .config-reloading #startup-cover { display: flex; }
282
286
 
283
287
  .startup-cover-shell {
284
288
  width: min(520px, calc(100vw - 48px));
@@ -1059,6 +1063,19 @@ button.left-ws-item {
1059
1063
  .conv-archive-btn:hover,
1060
1064
  .conv-rename-btn:hover { background: rgba(255,255,255,0.1); color: var(--text-bright); }
1061
1065
 
1066
+ .conv-archive-btn.archiving { cursor: default; border-color: var(--accent); }
1067
+ .conv-archive-spinner {
1068
+ width: 11px;
1069
+ height: 11px;
1070
+ border: 2px solid var(--border);
1071
+ border-top-color: var(--accent);
1072
+ border-radius: 50%;
1073
+ animation: conv-archive-spin 0.7s linear infinite;
1074
+ }
1075
+ @keyframes conv-archive-spin {
1076
+ to { transform: rotate(360deg); }
1077
+ }
1078
+
1062
1079
  .conv-item.dragging { opacity: 0.58; }
1063
1080
  .conv-item.drag-over { border-color: var(--accent); background: rgba(91,120,255,0.14); }
1064
1081
  .conv-rename-input {
@@ -2432,9 +2449,9 @@ button.left-ws-item {
2432
2449
  .conversation-work-event.error { color: #ff8888; }
2433
2450
  .conversation-work-event-content { min-width: 0; white-space: pre-wrap; word-break: break-word; }
2434
2451
  .conversation-work-event.narrative { display: block; color: var(--text); font-size: 13px; line-height: 1.65; padding: 3px 1px 7px; }
2435
- .conversation-work-event.activity-summary { font-size: 12px; font-weight: 600; padding: 2px 0; }
2452
+ .conversation-work-event.activity-summary { font-size: 11px; font-weight: 400; color: var(--text-dim); padding: 2px 0; }
2436
2453
  .conversation-work-activity { margin: 0; padding: 0; }
2437
- .conversation-work-activity > summary { display: grid; grid-template-columns: 17px minmax(0,1fr) 10px; gap: 8px; align-items: center; min-height: 28px; cursor: pointer; list-style: none; color: var(--text); font-size: 12px; font-weight: 600; }
2454
+ .conversation-work-activity > summary { display: grid; grid-template-columns: 17px minmax(0,1fr) 10px; gap: 8px; align-items: center; min-height: 28px; cursor: pointer; list-style: none; color: var(--text-dim); font-size: 11px; font-weight: 400; }
2438
2455
  .conversation-work-activity > summary::-webkit-details-marker { display: none; }
2439
2456
  .conversation-work-activity > summary .nm-icon { color: var(--text-dim); }
2440
2457
  .conversation-work-activity-chevron { width: 7px; height: 7px; border-right: 1px solid var(--text-dim); border-bottom: 1px solid var(--text-dim); transform: rotate(-45deg); transition: transform 140ms ease; }
@@ -3137,15 +3154,19 @@ button.todo-item { background: transparent; }
3137
3154
  font: 9px var(--font-mono);
3138
3155
  }
3139
3156
 
3140
- .conv-branch-comm-badge {
3141
- flex: 0 0 auto;
3142
- padding: 2px 5px;
3143
- border-radius: 999px;
3144
- background: color-mix(in srgb, var(--accent) 16%, transparent);
3157
+ .conv-branch-comm-icon {
3158
+ width: 20px;
3159
+ height: 20px;
3160
+ border-radius: var(--radius-sm);
3161
+ border: 1px solid var(--border);
3162
+ background: transparent;
3145
3163
  color: var(--accent);
3146
- font: 9px var(--font-mono);
3147
- white-space: nowrap;
3164
+ display: flex;
3165
+ align-items: center;
3166
+ justify-content: center;
3167
+ flex-shrink: 0;
3148
3168
  }
3169
+ .conv-branch-comm-icon .nm-icon { width: 11px; height: 11px; }
3149
3170
 
3150
3171
  .conv-runtime-badge.running { color: var(--accent2); }
3151
3172
  .conv-runtime-badge.stopping,
@@ -5009,6 +5030,32 @@ html.newmark-memory-overview-viewer body {
5009
5030
  .auto-input-group select:focus,
5010
5031
  .auto-input-group textarea:focus { border-color: var(--accent); }
5011
5032
 
5033
+ .auto-input-group.checkbox-row {
5034
+ display: flex;
5035
+ padding: 9px 11px;
5036
+ border: 1px solid var(--border);
5037
+ border-radius: var(--radius-md);
5038
+ background: var(--glass-bg-1);
5039
+ }
5040
+ .auto-input-group.checkbox-row label {
5041
+ display: flex;
5042
+ align-items: center;
5043
+ gap: 8px;
5044
+ margin: 0;
5045
+ cursor: pointer;
5046
+ font-size: 12px;
5047
+ color: var(--text);
5048
+ }
5049
+ .auto-input-group.checkbox-row input[type="checkbox"] {
5050
+ width: 15px;
5051
+ height: 15px;
5052
+ flex-shrink: 0;
5053
+ accent-color: var(--accent);
5054
+ cursor: pointer;
5055
+ margin: 0;
5056
+ padding: 0;
5057
+ }
5058
+
5012
5059
  .auto-conditions {
5013
5060
  display: flex;
5014
5061
  gap: 8px;
@@ -5328,6 +5375,11 @@ html.newmark-memory-overview-viewer body {
5328
5375
  <symbol id="folder" viewBox="0 0 24 24">
5329
5376
  <path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
5330
5377
  </symbol>
5378
+ <symbol id="git-branch" viewBox="0 0 24 24">
5379
+ <path d="M15 6a9 9 0 0 0-9 9V3" />
5380
+ <circle cx="18" cy="6" r="3" />
5381
+ <circle cx="6" cy="18" r="3" />
5382
+ </symbol>
5331
5383
  <symbol id="globe" viewBox="0 0 24 24">
5332
5384
  <circle cx="12" cy="12" r="10" />
5333
5385
  <path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
@@ -6489,6 +6541,7 @@ var NEWMARK_I18N = {
6489
6541
  'model.yes': 'yes',
6490
6542
  'model.notChecked': 'not checked',
6491
6543
  'archive.current': 'Archive current chat',
6544
+ 'archive.archiving': 'Archiving...',
6492
6545
  'archive.empty': 'No archives yet.',
6493
6546
  'archive.loading': 'Loading workspace archives...',
6494
6547
  'archive.unavailable': 'Archive list unavailable',
@@ -7190,6 +7243,7 @@ var NEWMARK_I18N = {
7190
7243
  'model.yes': '是',
7191
7244
  'model.notChecked': '未校验',
7192
7245
  'archive.current': '归档当前对话',
7246
+ 'archive.archiving': '归档中...',
7193
7247
  'archive.empty': '暂无归档。',
7194
7248
  'archive.loading': '正在加载工作区归档...',
7195
7249
  'archive.unavailable': '归档列表不可用',
@@ -15545,8 +15599,8 @@ window.refreshGlobalConfigFile = function() {
15545
15599
  if (!api.reloadGlobalConfig) return;
15546
15600
  api.reloadGlobalConfig().then(function(result) {
15547
15601
  if (result && result.error) throw new Error(result.error);
15548
- showUiNotice(t('settings.configReloaded'), 'success', 'global-config-refresh');
15549
- setTimeout(function() { window.location.reload(); }, 180);
15602
+ try { sessionStorage.setItem('newmark-config-reloading', '1'); } catch (_) {}
15603
+ window.location.reload();
15550
15604
  }).catch(function(error) {
15551
15605
  showUiNotice(error && error.message ? error.message : String(error), 'error', 'global-config-refresh');
15552
15606
  });
@@ -16223,7 +16277,13 @@ window.validateAllModels = function() {
16223
16277
  return state.modelValidationResults;
16224
16278
  }).catch(function(err) {
16225
16279
  document.body.classList.remove('model-evaluating');
16226
- window.openSubWin(t('model.validationTitle'), '<div style="color:#ff7777;font-size:12px;">' + esc(t('model.validationFailed')) + ': ' + esc(err.message) + '</div>');
16280
+ var failedHtml = '<div style="color:#ff7777;font-size:12px;">' + esc(t('model.validationFailed')) + ': ' + esc(err.message) + '</div>';
16281
+ var body = document.getElementById('sub-win-body');
16282
+ if (body && document.getElementById('model-validation-progress') && els['sub-win-overlay'].classList.contains('open')) {
16283
+ body.innerHTML = failedHtml;
16284
+ } else {
16285
+ addMsg('assistant', redactSensitiveText('[Error] ' + t('model.validationFailed') + ': ' + err.message), 'error', '');
16286
+ }
16227
16287
  return [];
16228
16288
  }).finally(function() {
16229
16289
  window.stopModelValidationProgressPolling();
@@ -16239,16 +16299,45 @@ window.renderModelValidationProgress = function(progress) {
16239
16299
  var completedModels = Math.max(0, Number(progress.completedModels || 0));
16240
16300
  var totalModels = Math.max(0, Number(progress.totalModels || 0));
16241
16301
  var current = [String(progress.currentModel || ''), String(progress.currentCheck || '')].filter(Boolean).join(' · ');
16242
- return '<div class="provider-card marquee-border">' +
16243
- '<div style="display:flex;justify-content:space-between;gap:10px;"><span>' + esc(t('model.validating')) + '</span><strong>' + esc(String(percent)) + '%</strong></div>' +
16244
- '<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + esc(String(percent)) + '" style="height:7px;margin-top:10px;border-radius:999px;background:var(--surface-strong);overflow:hidden;"><div style="height:100%;width:' + esc(String(percent)) + '%;background:var(--accent);transition:width .18s ease;"></div></div>' +
16245
- '<div style="margin-top:8px;color:var(--text-dim);font-size:11px;display:flex;justify-content:space-between;gap:8px;"><span>' + esc(String(completedChecks)) + '/' + esc(String(totalChecks)) + ' ' + esc(t('model.validationChecks')) + '</span><span>' + esc(String(completedModels)) + '/' + esc(String(totalModels)) + ' ' + esc(t('model.validationModels')) + '</span></div>' +
16246
- (current ? '<div style="margin-top:6px;color:var(--text-dim);font-size:11px;overflow-wrap:anywhere;">' + esc(t('model.validationCurrent')) + ': ' + esc(current) + '</div>' : '') +
16302
+ return '<div class="provider-card marquee-border" id="model-validation-progress">' +
16303
+ '<div style="display:flex;justify-content:space-between;gap:10px;"><span>' + esc(t('model.validating')) + '</span><strong id="mv-percent">' + esc(String(percent)) + '%</strong></div>' +
16304
+ '<div id="mv-progressbar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + esc(String(percent)) + '" style="height:7px;margin-top:10px;border-radius:999px;background:var(--surface-strong);overflow:hidden;"><div id="mv-bar" style="height:100%;width:' + esc(String(percent)) + '%;background:var(--accent);transition:width .18s ease;"></div></div>' +
16305
+ '<div style="margin-top:8px;color:var(--text-dim);font-size:11px;display:flex;justify-content:space-between;gap:8px;"><span id="mv-checks">' + esc(String(completedChecks)) + '/' + esc(String(totalChecks)) + ' ' + esc(t('model.validationChecks')) + '</span><span id="mv-models">' + esc(String(completedModels)) + '/' + esc(String(totalModels)) + ' ' + esc(t('model.validationModels')) + '</span></div>' +
16306
+ '<div id="mv-current" style="margin-top:6px;color:var(--text-dim);font-size:11px;overflow-wrap:anywhere;' + (current ? '' : 'display:none;') + '">' + esc(t('model.validationCurrent')) + ': ' + esc(current) + '</div>' +
16247
16307
  '</div><div style="margin-top:6px;color:var(--text-dim);font-size:11px;">' + esc(t('model.validationBackgroundNote')) + '</div>';
16248
16308
  };
16249
16309
 
16310
+ window.updateModelValidationProgress = function(progress) {
16311
+ progress = progress || {};
16312
+ var body = document.getElementById('sub-win-body');
16313
+ if (!body) return;
16314
+ var percent = Math.max(0, Math.min(100, Number(progress.percent || 0)));
16315
+ var completedChecks = Math.max(0, Number(progress.completedChecks || 0));
16316
+ var totalChecks = Math.max(0, Number(progress.totalChecks || 0));
16317
+ var completedModels = Math.max(0, Number(progress.completedModels || 0));
16318
+ var totalModels = Math.max(0, Number(progress.totalModels || 0));
16319
+ var current = [String(progress.currentModel || ''), String(progress.currentCheck || '')].filter(Boolean).join(' · ');
16320
+ var percentEl = document.getElementById('mv-percent');
16321
+ if (!percentEl) return;
16322
+ percentEl.textContent = String(percent) + '%';
16323
+ var barEl = document.getElementById('mv-bar');
16324
+ if (barEl) barEl.style.width = String(percent) + '%';
16325
+ var progressbarEl = document.getElementById('mv-progressbar');
16326
+ if (progressbarEl) progressbarEl.setAttribute('aria-valuenow', String(percent));
16327
+ var checksEl = document.getElementById('mv-checks');
16328
+ if (checksEl) checksEl.textContent = String(completedChecks) + '/' + String(totalChecks) + ' ' + t('model.validationChecks');
16329
+ var modelsEl = document.getElementById('mv-models');
16330
+ if (modelsEl) modelsEl.textContent = String(completedModels) + '/' + String(totalModels) + ' ' + t('model.validationModels');
16331
+ var currentEl = document.getElementById('mv-current');
16332
+ if (currentEl) {
16333
+ currentEl.textContent = t('model.validationCurrent') + ': ' + current;
16334
+ currentEl.style.display = current ? '' : 'none';
16335
+ }
16336
+ };
16337
+
16250
16338
  window.showModelValidationProgress = function(progress) {
16251
16339
  document.body.classList.add('model-evaluating');
16340
+ if (document.getElementById('model-validation-progress')) return;
16252
16341
  window.openSubWin(t('model.validationTitle'), window.renderModelValidationProgress(progress));
16253
16342
  };
16254
16343
 
@@ -16264,7 +16353,7 @@ window.startModelValidationProgressPolling = function() {
16264
16353
  Promise.resolve(api.modelValidationStatus()).then(function(progress) {
16265
16354
  if (!state.modelValidationPromise) return;
16266
16355
  var body = document.getElementById('sub-win-body');
16267
- if (body && els['sub-win-overlay'].classList.contains('open')) body.innerHTML = window.renderModelValidationProgress(progress);
16356
+ if (body && els['sub-win-overlay'].classList.contains('open')) window.updateModelValidationProgress(progress);
16268
16357
  state.modelValidationProgressTimer = setTimeout(poll, 150);
16269
16358
  }).catch(function() {
16270
16359
  if (state.modelValidationPromise) state.modelValidationProgressTimer = setTimeout(poll, 300);
@@ -16291,7 +16380,21 @@ window.showModelEvaluationResults = function(results) {
16291
16380
  }
16292
16381
  }
16293
16382
  html += '</div>';
16294
- window.openSubWin(t('model.validationTitle'), html);
16383
+ var body = document.getElementById('sub-win-body');
16384
+ if (body && document.getElementById('model-validation-progress') && els['sub-win-overlay'].classList.contains('open')) {
16385
+ // The progress window is still visible: replace it in place so closing
16386
+ // the results restores whatever window was open before validation.
16387
+ body.innerHTML = html;
16388
+ return;
16389
+ }
16390
+ // The user closed the progress window or opened another window while
16391
+ // validation was running. Do not hijack the current window; summarize in chat.
16392
+ var okCount = 0;
16393
+ for (var j = 0; j < (results || []).length; j++) {
16394
+ var status = String((results[j] && results[j].status) || '');
16395
+ if (status === 'verified' || status === 'degraded' || status === 'available') okCount++;
16396
+ }
16397
+ addMsg('assistant', '[System] ' + t('model.validationTitle') + ': ' + okCount + '/' + (results || []).length + ' ' + t('model.validationModels'), okCount === (results || []).length ? 'success' : 'warning', '');
16295
16398
  };
16296
16399
  renderArchiveSettings = function() {
16297
16400
  if (api.listArchives && !state._allArchiveSettingsLoading) {
@@ -18895,11 +18998,15 @@ function renderConversations() {
18895
18998
  if (runtimeState && ['running', 'stopping', 'force_restarting'].indexOf(String(runtimeState.status || '')) >= 0) div.classList.add('marquee-border');
18896
18999
  var runtimeBadge = runtimeState && runtimeState.status && runtimeState.status !== 'idle'
18897
19000
  ? '<span class="conv-runtime-badge ' + escAttr(String(runtimeState.status)) + '">' + esc(String(runtimeState.status)) + '</span>' : '';
18898
- var branchCommBadge = conv.branchCommunication
18899
- ? '<span class="conv-branch-comm-badge" title="' + escAttr(t('conversation.branchCommunicationBadge')) + '">' + esc(t('conversation.branchCommunicationBadge')) + '</span>' : '';
18900
- div.innerHTML = '<span class="conv-summary" title="' + escAttr(String(conv.id || '')) + '">' + esc(displaySummary) + (conv.messageCount ? ' (' + esc(String(conv.messageCount)) + ')' : '') + '</span>' + branchCommBadge + runtimeBadge +
19001
+ var branchCommIcon = conv.branchCommunication
19002
+ ? '<span class="conv-branch-comm-icon" title="' + escAttr(t('conversation.branchCommunicationBadge')) + '" aria-label="' + escAttr(t('conversation.branchCommunicationBadge')) + '">' + iconSvg('git-branch', t('conversation.branchCommunicationBadge')) + '</span>' : '';
19003
+ var archivePendingKey = currentWorkspaceKey() + '::' + String(conv.id);
19004
+ var archiveBtn = state.conversationArchivePending[archivePendingKey]
19005
+ ? '<button class="conv-archive-btn archiving" title="' + escAttr(t('archive.archiving')) + '" disabled><span class="conv-archive-spinner"></span></button>'
19006
+ : '<button class="conv-archive-btn" onclick="event.stopPropagation();window.archiveConv(this.closest(&quot;.conv-item&quot;).getAttribute(&quot;data-conversation-id&quot;))" title="' + escAttr(t('conversation.archive')) + '">' + iconOnly('archive', t('conversation.archive')) + '</button>';
19007
+ div.innerHTML = '<span class="conv-summary" title="' + escAttr(String(conv.id || '')) + '">' + esc(displaySummary) + (conv.messageCount ? ' (' + esc(String(conv.messageCount)) + ')' : '') + '</span>' + branchCommIcon + runtimeBadge +
18901
19008
  '<button class="conv-rename-btn" onclick="event.stopPropagation();window.editConversationName(' + i + ')" title="' + escAttr(t('conversation.rename')) + '">' + iconOnly('pencil', t('conversation.rename')) + '</button>' +
18902
- '<button class="conv-archive-btn" onclick="event.stopPropagation();window.archiveConv(this.closest(&quot;.conv-item&quot;).getAttribute(&quot;data-conversation-id&quot;))" title="' + escAttr(t('conversation.archive')) + '">' + iconOnly('archive', t('conversation.archive')) + '</button>' +
19009
+ archiveBtn +
18903
19010
  '<button class="conv-pin-btn' + (conv.pinned ? ' active' : '') + '" onclick="event.stopPropagation();window.toggleConversationPinned(' + i + ')" title="' + escAttr(conv.pinned ? t('conversation.unpin') : t('conversation.pin')) + '">' + iconOnly('pin', conv.pinned ? t('conversation.unpin') : t('conversation.pin')) + '</button>';
18904
19011
  div.onclick = function(idx) { return function() { window.switchConversation(idx); }; }(i);
18905
19012
  div.addEventListener('keydown', window.handleConversationKey);
@@ -19052,7 +19159,7 @@ window.showNewConversationPage = function() {
19052
19159
  var html = '<div style="display:flex;flex-direction:column;gap:12px;padding:4px 2px;">' +
19053
19160
  '<div style="font-size:12px;color:var(--text-dim);line-height:1.5;">' + esc(t('workspace.newConversationDesc')) + '</div>' +
19054
19161
  '<div class="auto-input-group"><label>' + esc(t('status.workspace')) + '</label><select id="new-conv-ws">' + options + '</select></div>' +
19055
- '<div class="auto-input-group" style="flex-direction:row;align-items:center;gap:8px;"><label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;"><input type="checkbox" id="new-conv-branch-comm" style="accent-color:var(--accent);">' + esc(t('conversation.branchCommunication')) + '</label></div>' +
19162
+ '<div class="auto-input-group checkbox-row"><label><input type="checkbox" id="new-conv-branch-comm"><span>' + esc(t('conversation.branchCommunication')) + '</span></label></div>' +
19056
19163
  '<div style="display:flex;gap:8px;">' +
19057
19164
  '<button class="sec-btn primary" style="flex:1;" onclick="window.doNewConversationFromPage()">' + esc(t('workspace.createConversation')) + '</button>' +
19058
19165
  '<button class="sec-btn" style="flex:1;" onclick="window.showNewWorkspaceDialog()">' + esc(t('workspace.createAction')) + '</button>' +
@@ -19196,45 +19303,45 @@ window.archiveConv = function(conversationId) {
19196
19303
  orderIds: rollbackOrder,
19197
19304
  chatHtml: priorActiveId === targetId && els['chat-area'] ? els['chat-area'].innerHTML : ''
19198
19305
  };
19199
- convs.splice(idx, 1);
19200
- if (!convs.length) convs.push({ id: 'default', summary: t('workspace.defaultConversation'), archived: false, active: true });
19201
- var nextActiveId = priorActiveId && priorActiveId !== targetId ? priorActiveId : String(convs[Math.min(idx, convs.length - 1)].id || 'default');
19202
- var nextActiveIndex = Math.max(0, convs.findIndex(function(item) { return String(item && item.id || '') === nextActiveId; }));
19203
- for (var i = 0; i < convs.length; i++) convs[i].active = i === nextActiveIndex;
19204
- state.workspaceActiveConversation[workspaceKey] = nextActiveIndex;
19205
- state.activeConversation = nextActiveIndex;
19206
- state.conversations = convs;
19207
- if (priorActiveId === targetId) {
19208
- state.activeBackendConversationId = nextActiveId;
19209
- if (els['chat-area']) els['chat-area'].innerHTML = '';
19210
- }
19211
- // Archiving is a destructive handoff: remove the target's Flow takeover
19212
- // locally at click time as well as removing its conversation row. The
19213
- // backend cancellation/manifest write is asynchronous; a late Flow
19214
- // promise must not leave a visible running bubble over the replacement
19215
- // conversation while the archive is already in flight.
19216
- if (state.flowTakeovers) {
19217
- var archivedFlowKey = runtimeKeyFor(targetRuntime.workspaceId, targetRuntime.conversationId);
19218
- var archivedFlowRecord = state.flowTakeovers[archivedFlowKey];
19219
- if (archivedFlowRecord) {
19220
- archivedFlowRecord.running = false;
19221
- archivedFlowRecord.paused = false;
19222
- archivedFlowRecord.runtimeLease = null;
19223
- archivedFlowRecord.queueLease = null;
19224
- delete state.flowTakeovers[archivedFlowKey];
19225
- }
19226
- if (priorActiveId === targetId && window.renderFlowTakeover) {
19227
- window.renderFlowTakeover(false, '', { target: targetRuntime });
19228
- }
19229
- }
19230
- setConversationRuntimeState(targetRuntime, 'idle', '');
19231
- setWorking(!!runningConversationRecord(activeConversationId()));
19306
+ // 归档中:不立即剔除行,按钮转圈等待;后端完成归档后再剔除前端。
19232
19307
  renderConversations();
19233
- if (priorActiveId === targetId) scheduleConversationArchiveActiveSync(workspaceKey);
19234
19308
  var archivePromise = api.archive ? api.archive(targetRuntime) : Promise.reject(new Error('Archive API unavailable'));
19235
19309
  archivePromise.then(function(receipt) {
19236
19310
  if (!receipt || receipt.ok !== true) throw new Error((receipt && receipt.error) || 'Archive failed');
19237
19311
  delete state.conversationArchivePending[pendingKey];
19312
+ // 后端完成归档:现在才剔除前端行 + 切换 active + 清理 Flow takeover。
19313
+ var currentConvs = currentWorkspaceConversations();
19314
+ var currentIdx = currentConvs.findIndex(function(item) { return String(item && item.id || 'default') === targetId; });
19315
+ if (currentIdx >= 0) currentConvs.splice(currentIdx, 1);
19316
+ if (!currentConvs.length) currentConvs.push({ id: 'default', summary: t('workspace.defaultConversation'), archived: false, active: true });
19317
+ var nextActiveId = priorActiveId && priorActiveId !== targetId ? priorActiveId : String(currentConvs[Math.min(Math.max(currentIdx, 0), currentConvs.length - 1)].id || 'default');
19318
+ var nextActiveIndex = Math.max(0, currentConvs.findIndex(function(item) { return String(item && item.id || '') === nextActiveId; }));
19319
+ for (var i = 0; i < currentConvs.length; i++) currentConvs[i].active = i === nextActiveIndex;
19320
+ state.workspaceActiveConversation[workspaceKey] = nextActiveIndex;
19321
+ state.activeConversation = nextActiveIndex;
19322
+ state.conversations = currentConvs;
19323
+ if (priorActiveId === targetId) {
19324
+ state.activeBackendConversationId = nextActiveId;
19325
+ if (els['chat-area']) els['chat-area'].innerHTML = '';
19326
+ }
19327
+ if (state.flowTakeovers) {
19328
+ var archivedFlowKey = runtimeKeyFor(targetRuntime.workspaceId, targetRuntime.conversationId);
19329
+ var archivedFlowRecord = state.flowTakeovers[archivedFlowKey];
19330
+ if (archivedFlowRecord) {
19331
+ archivedFlowRecord.running = false;
19332
+ archivedFlowRecord.paused = false;
19333
+ archivedFlowRecord.runtimeLease = null;
19334
+ archivedFlowRecord.queueLease = null;
19335
+ delete state.flowTakeovers[archivedFlowKey];
19336
+ }
19337
+ if (priorActiveId === targetId && window.renderFlowTakeover) {
19338
+ window.renderFlowTakeover(false, '', { target: targetRuntime });
19339
+ }
19340
+ }
19341
+ setConversationRuntimeState(targetRuntime, 'idle', '');
19342
+ setWorking(!!runningConversationRecord(activeConversationId()));
19343
+ renderConversations();
19344
+ if (priorActiveId === targetId) scheduleConversationArchiveActiveSync(workspaceKey);
19238
19345
  var optimisticArchive = {
19239
19346
  id: receipt.fileName,
19240
19347
  name: receipt.fileName,
@@ -19262,10 +19369,8 @@ window.archiveConv = function(conversationId) {
19262
19369
  scheduleConversationArchiveRefresh(workspaceKey);
19263
19370
  }).catch(function(err) {
19264
19371
  delete state.conversationArchivePending[pendingKey];
19265
- // Keep the optimistic removal authoritative for this renderer session.
19266
- // A failed IPC receipt is surfaced, but never resurrects the row under
19267
- // the user's pointer; the next explicit workspace refresh is the only
19268
- // path allowed to reconcile a failed destructive operation.
19372
+ // 归档失败:恢复按钮,保留行(不再乐观剔除)。
19373
+ renderConversations();
19269
19374
  showUiNotice('[Archive] ' + t('workspace.saveFailed') + ': ' + (err.message || String(err)), 'error', 'archive-failed-' + targetId);
19270
19375
  });
19271
19376
  }
@@ -22511,6 +22616,11 @@ function schedulePostStartupUiRendering() {
22511
22616
  // navigation surfaces render after promotion on cancellable browser tasks.
22512
22617
  updateWorkspaceGate();
22513
22618
 
22619
+ if (document.documentElement.classList.contains('config-reloading')) {
22620
+ document.documentElement.classList.remove('config-reloading');
22621
+ try { sessionStorage.removeItem('newmark-config-reloading'); } catch (_) {}
22622
+ }
22623
+
22514
22624
  // === Event Listeners ===
22515
22625
  function escapeBelongsToFocusedControl(event) {
22516
22626
  var target = event && event.target;
@@ -112,6 +112,11 @@
112
112
  <symbol id="folder" viewBox="0 0 24 24">
113
113
  <path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
114
114
  </symbol>
115
+ <symbol id="git-branch" viewBox="0 0 24 24">
116
+ <path d="M15 6a9 9 0 0 0-9 9V3" />
117
+ <circle cx="18" cy="6" r="3" />
118
+ <circle cx="6" cy="18" r="3" />
119
+ </symbol>
115
120
  <symbol id="globe" viewBox="0 0 24 24">
116
121
  <circle cx="12" cy="12" r="10" />
117
122
  <path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />