newmark-agent 0.4.0 → 0.4.3

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.
@@ -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,
@@ -3502,8 +3523,6 @@ button.todo-item { background: transparent; }
3502
3523
  .context-inspector-head { display:flex; align-items:flex-start; gap:8px; margin-bottom:10px; }
3503
3524
  .context-inspector-title { flex:1; min-width:0; color:var(--text-bright); font-size:12px; font-weight:700; }
3504
3525
  .context-inspector-subtitle { margin-top:2px; color:var(--text-dim); font-size:9px; }
3505
- .context-inspector-close { width:22px; height:22px; padding:0; border:0; border-radius:var(--radius-sm); background:transparent; color:var(--text-dim); cursor:pointer; }
3506
- .context-inspector-close:hover, .context-inspector-close:focus-visible { color:var(--text-bright); background:var(--control-hover-bg); outline:none; }
3507
3526
  .context-inspector-meter { height:6px; margin:8px 0 10px; overflow:hidden; border-radius:var(--radius-full); background:rgba(168,168,168,.18); }
3508
3527
  .context-inspector-meter-fill { height:100%; border-radius:inherit; background:var(--accent); transition:width var(--duration-normal) var(--ease-out-expo), background var(--duration-fast) var(--ease-out-expo); }
3509
3528
  .context-inspector-grid { display:grid; grid-template-columns:1fr 1fr; gap:7px; }
@@ -5009,6 +5028,32 @@ html.newmark-memory-overview-viewer body {
5009
5028
  .auto-input-group select:focus,
5010
5029
  .auto-input-group textarea:focus { border-color: var(--accent); }
5011
5030
 
5031
+ .auto-input-group.checkbox-row {
5032
+ display: flex;
5033
+ padding: 9px 11px;
5034
+ border: 1px solid var(--border);
5035
+ border-radius: var(--radius-md);
5036
+ background: var(--glass-bg-1);
5037
+ }
5038
+ .auto-input-group.checkbox-row label {
5039
+ display: flex;
5040
+ align-items: center;
5041
+ gap: 8px;
5042
+ margin: 0;
5043
+ cursor: pointer;
5044
+ font-size: 12px;
5045
+ color: var(--text);
5046
+ }
5047
+ .auto-input-group.checkbox-row input[type="checkbox"] {
5048
+ width: 15px;
5049
+ height: 15px;
5050
+ flex-shrink: 0;
5051
+ accent-color: var(--accent);
5052
+ cursor: pointer;
5053
+ margin: 0;
5054
+ padding: 0;
5055
+ }
5056
+
5012
5057
  .auto-conditions {
5013
5058
  display: flex;
5014
5059
  gap: 8px;
@@ -5328,6 +5373,11 @@ html.newmark-memory-overview-viewer body {
5328
5373
  <symbol id="folder" viewBox="0 0 24 24">
5329
5374
  <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
5375
  </symbol>
5376
+ <symbol id="git-branch" viewBox="0 0 24 24">
5377
+ <path d="M15 6a9 9 0 0 0-9 9V3" />
5378
+ <circle cx="18" cy="6" r="3" />
5379
+ <circle cx="6" cy="18" r="3" />
5380
+ </symbol>
5331
5381
  <symbol id="globe" viewBox="0 0 24 24">
5332
5382
  <circle cx="12" cy="12" r="10" />
5333
5383
  <path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
@@ -6433,6 +6483,8 @@ var NEWMARK_I18N = {
6433
6483
  'model.contextSize': 'Context size',
6434
6484
  'model.vision': 'Vision',
6435
6485
  'model.thinking': 'Thinking',
6486
+ 'model.thinkingTierMap': 'Thinking tier map',
6487
+ 'model.thinkingTierMapHelp': 'Map native model effort tiers to Newmark tiers. One "native=Newmark" per line (e.g. minimal=low, balanced=medium, deep=high). Leave empty to keep the default mapping (Newmark tier names are sent as-is).',
6436
6488
  'model.description': 'Description',
6437
6489
  'model.optionalDescription': 'Optional description',
6438
6490
  'model.fuzzyInput': 'API key / provider details',
@@ -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',
@@ -6794,6 +6847,8 @@ var NEWMARK_I18N = {
6794
6847
  'status.contextCompressed': 'Context compressed',
6795
6848
  'status.contextCompression': 'Context compression',
6796
6849
  'status.contextTokens': 'Context tokens',
6850
+ 'status.conversationTokens': 'Conversation tokens',
6851
+ 'status.cacheHitRate': 'Cache hit rate',
6797
6852
  'status.contextNearLimit': 'Context near limit',
6798
6853
  'status.contextOverLimit': 'Context over limit',
6799
6854
  'status.contextModeFallback': 'fallback',
@@ -6801,7 +6856,7 @@ var NEWMARK_I18N = {
6801
6856
  'status.messages': 'messages',
6802
6857
  'status.noCompression': 'No compression event in this conversation',
6803
6858
  'status.contextInspector': 'Context management',
6804
- 'status.contextInspectorHint': 'DSH-inspired live budget; visible chat history stays unchanged.',
6859
+ 'status.contextInspectorHint': 'Live context budget; visible chat history stays unchanged.',
6805
6860
  'status.activeBuild': 'Active Build',
6806
6861
  'status.longHistory': 'Long history',
6807
6862
  'status.trigger': 'Trigger',
@@ -7134,6 +7189,8 @@ var NEWMARK_I18N = {
7134
7189
  'model.contextSize': '上下文规模',
7135
7190
  'model.vision': '视觉',
7136
7191
  'model.thinking': '思考',
7192
+ 'model.thinkingTierMap': '思考强度档位映射',
7193
+ 'model.thinkingTierMapHelp': '将模型原生思考强度档位映射到 Newmark 档位。每行一个「原生档位=Newmark档位」(如 minimal=low、balanced=medium、deep=high)。留空表示不变动映射(按 Newmark 档位名原样发送)。',
7137
7194
  'model.description': '描述',
7138
7195
  'model.optionalDescription': '可选描述',
7139
7196
  'model.fuzzyInput': 'API key / 供应商信息',
@@ -7190,6 +7247,7 @@ var NEWMARK_I18N = {
7190
7247
  'model.yes': '是',
7191
7248
  'model.notChecked': '未校验',
7192
7249
  'archive.current': '归档当前对话',
7250
+ 'archive.archiving': '归档中...',
7193
7251
  'archive.empty': '暂无归档。',
7194
7252
  'archive.loading': '正在加载工作区归档...',
7195
7253
  'archive.unavailable': '归档列表不可用',
@@ -7495,6 +7553,8 @@ var NEWMARK_I18N = {
7495
7553
  'status.contextCompressed': '上下文已压缩',
7496
7554
  'status.contextCompression': '上下文压缩',
7497
7555
  'status.contextTokens': '上下文 token',
7556
+ 'status.conversationTokens': '全对话 token',
7557
+ 'status.cacheHitRate': '缓存命中率',
7498
7558
  'status.contextNearLimit': '上下文接近上限',
7499
7559
  'status.contextOverLimit': '上下文已超限',
7500
7560
  'status.contextModeFallback': 'fallback',
@@ -7502,7 +7562,7 @@ var NEWMARK_I18N = {
7502
7562
  'status.messages': '条消息',
7503
7563
  'status.noCompression': '当前对话暂无压缩事件',
7504
7564
  'status.contextInspector': '上下文管理',
7505
- 'status.contextInspectorHint': '借鉴 DSH 的动态预算;可见对话历史不会被改写。',
7565
+ 'status.contextInspectorHint': '实时上下文预算;可见对话历史不会被改写。',
7506
7566
  'status.activeBuild': '当前 Build',
7507
7567
  'status.longHistory': '长期历史',
7508
7568
  'status.trigger': '触发线',
@@ -10120,7 +10180,7 @@ function renderWorkToolGroup(event, eventIndex) {
10120
10180
  var edited = workToolEditedFile(item);
10121
10181
  if (edited) {
10122
10182
  var editKey = 'edit:' + String(item.id || item.toolCallId || itemIndex) + ':' + edited.path;
10123
- return '<details class="conversation-work-file conversation-work-file-inline" open data-work-detail-key="' + escAttr(editKey) + '"><summary>' +
10183
+ return '<details class="conversation-work-file conversation-work-file-inline" data-work-detail-key="' + escAttr(editKey) + '"><summary>' +
10124
10184
  iconSvg('pencil', 'edited', 'tiny') +
10125
10185
  '<span class="conversation-work-file-name" title="' + escAttr(edited.path) + '">' + esc(currentLang() === 'zh' ? '已编辑 ' : 'Edited ') + esc(edited.name) + '</span>' +
10126
10186
  '<span class="conversation-work-file-stats"><span class="conversation-work-file-add">+' + edited.added + '</span><span class="conversation-work-file-del">-' + edited.deleted + '</span></span>' +
@@ -12021,6 +12081,12 @@ window.renderContextInspector = function() {
12021
12081
  var historyTrigger = contextInspectorValue(c.longHistoryTriggerTokens);
12022
12082
  var activeRetention = contextInspectorValue(c.buildBlockRetentionTokens);
12023
12083
  var historyRetention = contextInspectorValue(c.longHistoryRetentionTokens);
12084
+ var providerTotalTokens = contextInspectorValue(c.providerTotalTokens);
12085
+ var providerInputTokens = contextInspectorValue(c.providerInputTokens);
12086
+ var providerOutputTokens = contextInspectorValue(c.providerOutputTokens);
12087
+ var providerCacheReadTokens = contextInspectorValue(c.providerCacheReadTokens);
12088
+ var providerCacheReadRatio = Math.max(0, Math.min(1, Number(c.providerCacheReadRatio) || 0));
12089
+ var cacheHitPercent = Math.round(providerCacheReadRatio * 1000) / 10;
12024
12090
  var percent = Math.min(100, Math.round((used / max) * 100));
12025
12091
  var fillColor = c.warning === 'over_limit' ? 'var(--nm-state-danger)' : (c.warning === 'near_limit' ? 'var(--nm-state-warning)' : 'var(--accent)');
12026
12092
  var compression = state.contextCompression || null;
@@ -12029,12 +12095,13 @@ window.renderContextInspector = function() {
12029
12095
  : t('status.noCompressionShort');
12030
12096
  var busy = !!state.contextMutationPending;
12031
12097
  var running = typeof isCurrentConversationRunning === 'function' && isCurrentConversationRunning();
12032
- panel.innerHTML = '<div class="context-inspector-head"><div><div class="context-inspector-title">' + esc(t('status.contextInspector')) + '</div><div class="context-inspector-subtitle">' + esc(t('status.contextInspectorHint')) + '</div></div>' +
12033
- '<button type="button" class="context-inspector-close" onclick="window.closeContextInspector()" aria-label="' + esc(t('common.close') || 'Close') + '">×</button></div>' +
12098
+ panel.innerHTML = '<div class="context-inspector-head"><div><div class="context-inspector-title">' + esc(t('status.contextInspector')) + '</div><div class="context-inspector-subtitle">' + esc(t('status.contextInspectorHint')) + '</div></div></div>' +
12034
12099
  '<div class="context-inspector-meter"><div class="context-inspector-meter-fill" style="width:' + percent + '%;background:' + fillColor + '"></div></div>' +
12035
12100
  '<div class="context-inspector-grid">' +
12036
12101
  contextInspectorCell(t('status.contextTokens'), used + ' / ' + max) +
12102
+ contextInspectorCell(t('status.conversationTokens'), providerTotalTokens + ' · ' + providerInputTokens + ' in / ' + providerOutputTokens + ' out') +
12037
12103
  contextInspectorCell(t('status.activeBuild'), active + ' / ' + activeTrigger) +
12104
+ contextInspectorCell(t('status.cacheHitRate'), cacheHitPercent + '% · ' + providerCacheReadTokens + ' cached') +
12038
12105
  contextInspectorCell(t('status.longHistory'), history + ' / ' + historyTrigger) +
12039
12106
  contextInspectorCell(t('status.retention'), activeRetention + ' + ' + historyRetention) +
12040
12107
  contextInspectorCell(t('status.hotCache'), contextInspectorValue(c.cacheEntries) + ' entries') +
@@ -12050,6 +12117,17 @@ window.closeContextInspector = function() {
12050
12117
  window.renderContextInspector();
12051
12118
  };
12052
12119
 
12120
+ (function() {
12121
+ document.addEventListener('click', function(event) {
12122
+ if (!state.contextInspectorOpen) return;
12123
+ var ring = document.getElementById('context-token-ring');
12124
+ var panel = document.getElementById('context-inspector');
12125
+ if (!ring || !panel) return;
12126
+ if (panel.contains(event.target) || ring.contains(event.target)) return;
12127
+ window.closeContextInspector();
12128
+ });
12129
+ })();
12130
+
12053
12131
  window.toggleContextInspector = function() {
12054
12132
  state.contextInspectorOpen = !state.contextInspectorOpen;
12055
12133
  window.hideContextWindowTooltip();
@@ -15275,9 +15353,11 @@ function renderModelSettings() {
15275
15353
  : (normalizedStatus === 'not checked' ? 'model-eval-pending' : 'model-eval-bad');
15276
15354
  var details = '';
15277
15355
  if (typeof modelEntry !== 'string') {
15356
+ var tierMapCount = modelEntry.thinking_tier_map && typeof modelEntry.thinking_tier_map === 'object' ? Object.keys(modelEntry.thinking_tier_map).length : 0;
15278
15357
  details = '<span class="model-eval-line">ctx ' + esc(modelEntry.max_tokens || '') +
15279
15358
  ' | ' + esc(t('model.vision')) + ' ' + (modelEntry.vision ? t('common.on') : t('common.off')) +
15280
15359
  ' | ' + esc(t('model.thinking')) + ' ' + (modelEntry.thinking ? t('common.on') : t('common.off')) +
15360
+ (tierMapCount ? ' | ' + esc(t('model.thinkingTierMap')) + ' ' + tierMapCount : '') +
15281
15361
  ' | ' + esc(t('model.cost')) + ' ' + esc(evaluation ? evaluation.cost_rating : '-') +
15282
15362
  ' | ' + esc(t('model.performanceShort')) + ' ' + esc(modelEntry.capability_rating || '-') +
15283
15363
  ' | ' + esc(t('model.speed')) + ' ' + esc(modelEntry.speed_rating || '-') + '</span>';
@@ -15545,8 +15625,8 @@ window.refreshGlobalConfigFile = function() {
15545
15625
  if (!api.reloadGlobalConfig) return;
15546
15626
  api.reloadGlobalConfig().then(function(result) {
15547
15627
  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);
15628
+ try { sessionStorage.setItem('newmark-config-reloading', '1'); } catch (_) {}
15629
+ window.location.reload();
15550
15630
  }).catch(function(error) {
15551
15631
  showUiNotice(error && error.message ? error.message : String(error), 'error', 'global-config-refresh');
15552
15632
  });
@@ -15986,9 +16066,31 @@ window.addModel = function() {
15986
16066
  '<div style="display:flex;gap:10px;margin-bottom:10px;"><label style="font-size:11px;color:var(--text-dim);"><input type="checkbox" id="new-model-vision"> ' + esc(t('model.vision')) + '</label>' +
15987
16067
  '<label style="font-size:11px;color:var(--text-dim);"><input type="checkbox" id="new-model-thinking"> ' + esc(t('model.thinking')) + '</label></div>' +
15988
16068
  '<div class="auto-input-group"><label>' + esc(t('model.description')) + '</label><textarea id="new-model-desc" rows="2" placeholder="' + escAttr(t('model.optionalDescription')) + '"></textarea></div>' +
16069
+ '<div class="auto-input-group"><label>' + esc(t('model.thinkingTierMap')) + '</label><textarea id="new-model-tier-map" rows="3" placeholder="minimal=low&#10;balanced=medium&#10;deep=high"></textarea>' +
16070
+ '<div style="margin-top:10px;font-size:11px;color:var(--text-dim);">' + esc(t('model.thinkingTierMapHelp')) + '</div></div>' +
15989
16071
  '<div style="margin-top:10px;"><button class="sec-btn primary" onclick="window.saveNewModel()">' + esc(t('common.save')) + '</button></div>');
15990
16072
  };
15991
16073
 
16074
+ window.parseThinkingTierMap = function(text) {
16075
+ var map = {};
16076
+ if (!text) return map;
16077
+ String(text).split('\n').forEach(function(line) {
16078
+ var trimmed = line.trim();
16079
+ if (!trimmed) return;
16080
+ var eq = trimmed.indexOf('=');
16081
+ if (eq <= 0) return;
16082
+ var native = trimmed.slice(0, eq).trim();
16083
+ var newmark = trimmed.slice(eq + 1).trim();
16084
+ if (native && newmark) map[native] = newmark;
16085
+ });
16086
+ return map;
16087
+ };
16088
+
16089
+ window.serializeThinkingTierMap = function(map) {
16090
+ if (!map || typeof map !== 'object') return '';
16091
+ return Object.keys(map).map(function(k) { return k + '=' + map[k]; }).join('\n');
16092
+ };
16093
+
15992
16094
  window.saveNewModel = function() {
15993
16095
  var name = document.getElementById('new-model-name').value;
15994
16096
  var provIdx = parseInt(document.getElementById('new-model-provider').value);
@@ -15996,9 +16098,11 @@ window.saveNewModel = function() {
15996
16098
  var visionEl = document.getElementById('new-model-vision');
15997
16099
  var thinkingEl = document.getElementById('new-model-thinking');
15998
16100
  var descEl = document.getElementById('new-model-desc');
16101
+ var tierMapEl = document.getElementById('new-model-tier-map');
15999
16102
  if (name && !isNaN(provIdx) && state.providers[provIdx]) {
16000
16103
  if (!state.providers[provIdx].models) state.providers[provIdx].models = [];
16001
- state.providers[provIdx].models.push({
16104
+ var tierMap = window.parseThinkingTierMap(tierMapEl ? tierMapEl.value : '');
16105
+ var entry = {
16002
16106
  name: name,
16003
16107
  display: name,
16004
16108
  description: descEl ? descEl.value : '',
@@ -16015,7 +16119,9 @@ window.saveNewModel = function() {
16015
16119
  xhigh: { description: 'xhigh' },
16016
16120
  max: { description: 'max' }
16017
16121
  }
16018
- });
16122
+ };
16123
+ if (Object.keys(tierMap).length) entry.thinking_tier_map = tierMap;
16124
+ state.providers[provIdx].models.push(entry);
16019
16125
  api.saveConfig({providers: state.providers});
16020
16126
  window.refreshModelSelect();
16021
16127
  }
@@ -16035,6 +16141,8 @@ window.editModel = function(provIdx, modelIdx) {
16035
16141
  '<div style="display:flex;gap:10px;margin-bottom:10px;"><label style="font-size:11px;color:var(--text-dim);"><input type="checkbox" id="edit-model-vision"' + (model.vision ? ' checked' : '') + '> ' + esc(t('model.vision')) + '</label>' +
16036
16142
  '<label style="font-size:11px;color:var(--text-dim);"><input type="checkbox" id="edit-model-thinking"' + (model.thinking ? ' checked' : '') + '> ' + esc(t('model.thinking')) + '</label></div>' +
16037
16143
  '<div class="auto-input-group"><label>' + esc(t('model.description')) + '</label><textarea id="edit-model-desc" rows="2">' + esc(model.description || '') + '</textarea></div>' +
16144
+ '<div class="auto-input-group"><label>' + esc(t('model.thinkingTierMap')) + '</label><textarea id="edit-model-tier-map" rows="3">' + esc(window.serializeThinkingTierMap(model.thinking_tier_map)) + '</textarea>' +
16145
+ '<div style="margin-top:10px;font-size:11px;color:var(--text-dim);">' + esc(t('model.thinkingTierMapHelp')) + '</div></div>' +
16038
16146
  '<div style="margin-top:10px;"><button class="sec-btn primary" onclick="window.saveModelEdit(' + provIdx + ',' + modelIdx + ')">' + esc(t('common.save')) + '</button></div>');
16039
16147
  };
16040
16148
 
@@ -16047,11 +16155,13 @@ window.saveModelEdit = function(oldProvIdx, modelIdx) {
16047
16155
  var visionEl = document.getElementById('edit-model-vision');
16048
16156
  var thinkingEl = document.getElementById('edit-model-thinking');
16049
16157
  var descEl = document.getElementById('edit-model-desc');
16158
+ var tierMapEl = document.getElementById('edit-model-tier-map');
16050
16159
  var name = nameEl ? nameEl.value.trim() : '';
16051
16160
  var newProvIdx = provEl ? parseInt(provEl.value) : oldProvIdx;
16052
16161
  if (!name || isNaN(newProvIdx) || !state.providers[newProvIdx]) return;
16053
16162
  var previous = oldProvider.models[modelIdx];
16054
16163
  if (typeof previous === 'string') previous = { name: previous, display: previous };
16164
+ var tierMap = window.parseThinkingTierMap(tierMapEl ? tierMapEl.value : '');
16055
16165
  var updated = Object.assign({}, previous, {
16056
16166
  name: name,
16057
16167
  display: name,
@@ -16060,6 +16170,8 @@ window.saveModelEdit = function(oldProvIdx, modelIdx) {
16060
16170
  vision: !!(visionEl && visionEl.checked),
16061
16171
  thinking: !!(thinkingEl && thinkingEl.checked)
16062
16172
  });
16173
+ if (Object.keys(tierMap).length) updated.thinking_tier_map = tierMap;
16174
+ else delete updated.thinking_tier_map;
16063
16175
  oldProvider.models.splice(modelIdx, 1);
16064
16176
  if (!state.providers[newProvIdx].models) state.providers[newProvIdx].models = [];
16065
16177
  state.providers[newProvIdx].models.push(updated);
@@ -16223,7 +16335,13 @@ window.validateAllModels = function() {
16223
16335
  return state.modelValidationResults;
16224
16336
  }).catch(function(err) {
16225
16337
  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>');
16338
+ var failedHtml = '<div style="color:#ff7777;font-size:12px;">' + esc(t('model.validationFailed')) + ': ' + esc(err.message) + '</div>';
16339
+ var body = document.getElementById('sub-win-body');
16340
+ if (body && document.getElementById('model-validation-progress') && els['sub-win-overlay'].classList.contains('open')) {
16341
+ body.innerHTML = failedHtml;
16342
+ } else {
16343
+ addMsg('assistant', redactSensitiveText('[Error] ' + t('model.validationFailed') + ': ' + err.message), 'error', '');
16344
+ }
16227
16345
  return [];
16228
16346
  }).finally(function() {
16229
16347
  window.stopModelValidationProgressPolling();
@@ -16239,16 +16357,45 @@ window.renderModelValidationProgress = function(progress) {
16239
16357
  var completedModels = Math.max(0, Number(progress.completedModels || 0));
16240
16358
  var totalModels = Math.max(0, Number(progress.totalModels || 0));
16241
16359
  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>' : '') +
16360
+ return '<div class="provider-card marquee-border" id="model-validation-progress">' +
16361
+ '<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>' +
16362
+ '<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>' +
16363
+ '<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>' +
16364
+ '<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
16365
  '</div><div style="margin-top:6px;color:var(--text-dim);font-size:11px;">' + esc(t('model.validationBackgroundNote')) + '</div>';
16248
16366
  };
16249
16367
 
16368
+ window.updateModelValidationProgress = function(progress) {
16369
+ progress = progress || {};
16370
+ var body = document.getElementById('sub-win-body');
16371
+ if (!body) return;
16372
+ var percent = Math.max(0, Math.min(100, Number(progress.percent || 0)));
16373
+ var completedChecks = Math.max(0, Number(progress.completedChecks || 0));
16374
+ var totalChecks = Math.max(0, Number(progress.totalChecks || 0));
16375
+ var completedModels = Math.max(0, Number(progress.completedModels || 0));
16376
+ var totalModels = Math.max(0, Number(progress.totalModels || 0));
16377
+ var current = [String(progress.currentModel || ''), String(progress.currentCheck || '')].filter(Boolean).join(' · ');
16378
+ var percentEl = document.getElementById('mv-percent');
16379
+ if (!percentEl) return;
16380
+ percentEl.textContent = String(percent) + '%';
16381
+ var barEl = document.getElementById('mv-bar');
16382
+ if (barEl) barEl.style.width = String(percent) + '%';
16383
+ var progressbarEl = document.getElementById('mv-progressbar');
16384
+ if (progressbarEl) progressbarEl.setAttribute('aria-valuenow', String(percent));
16385
+ var checksEl = document.getElementById('mv-checks');
16386
+ if (checksEl) checksEl.textContent = String(completedChecks) + '/' + String(totalChecks) + ' ' + t('model.validationChecks');
16387
+ var modelsEl = document.getElementById('mv-models');
16388
+ if (modelsEl) modelsEl.textContent = String(completedModels) + '/' + String(totalModels) + ' ' + t('model.validationModels');
16389
+ var currentEl = document.getElementById('mv-current');
16390
+ if (currentEl) {
16391
+ currentEl.textContent = t('model.validationCurrent') + ': ' + current;
16392
+ currentEl.style.display = current ? '' : 'none';
16393
+ }
16394
+ };
16395
+
16250
16396
  window.showModelValidationProgress = function(progress) {
16251
16397
  document.body.classList.add('model-evaluating');
16398
+ if (document.getElementById('model-validation-progress')) return;
16252
16399
  window.openSubWin(t('model.validationTitle'), window.renderModelValidationProgress(progress));
16253
16400
  };
16254
16401
 
@@ -16264,7 +16411,7 @@ window.startModelValidationProgressPolling = function() {
16264
16411
  Promise.resolve(api.modelValidationStatus()).then(function(progress) {
16265
16412
  if (!state.modelValidationPromise) return;
16266
16413
  var body = document.getElementById('sub-win-body');
16267
- if (body && els['sub-win-overlay'].classList.contains('open')) body.innerHTML = window.renderModelValidationProgress(progress);
16414
+ if (body && els['sub-win-overlay'].classList.contains('open')) window.updateModelValidationProgress(progress);
16268
16415
  state.modelValidationProgressTimer = setTimeout(poll, 150);
16269
16416
  }).catch(function() {
16270
16417
  if (state.modelValidationPromise) state.modelValidationProgressTimer = setTimeout(poll, 300);
@@ -16291,7 +16438,21 @@ window.showModelEvaluationResults = function(results) {
16291
16438
  }
16292
16439
  }
16293
16440
  html += '</div>';
16294
- window.openSubWin(t('model.validationTitle'), html);
16441
+ var body = document.getElementById('sub-win-body');
16442
+ if (body && document.getElementById('model-validation-progress') && els['sub-win-overlay'].classList.contains('open')) {
16443
+ // The progress window is still visible: replace it in place so closing
16444
+ // the results restores whatever window was open before validation.
16445
+ body.innerHTML = html;
16446
+ return;
16447
+ }
16448
+ // The user closed the progress window or opened another window while
16449
+ // validation was running. Do not hijack the current window; summarize in chat.
16450
+ var okCount = 0;
16451
+ for (var j = 0; j < (results || []).length; j++) {
16452
+ var status = String((results[j] && results[j].status) || '');
16453
+ if (status === 'verified' || status === 'degraded' || status === 'available') okCount++;
16454
+ }
16455
+ addMsg('assistant', '[System] ' + t('model.validationTitle') + ': ' + okCount + '/' + (results || []).length + ' ' + t('model.validationModels'), okCount === (results || []).length ? 'success' : 'warning', '');
16295
16456
  };
16296
16457
  renderArchiveSettings = function() {
16297
16458
  if (api.listArchives && !state._allArchiveSettingsLoading) {
@@ -18895,11 +19056,15 @@ function renderConversations() {
18895
19056
  if (runtimeState && ['running', 'stopping', 'force_restarting'].indexOf(String(runtimeState.status || '')) >= 0) div.classList.add('marquee-border');
18896
19057
  var runtimeBadge = runtimeState && runtimeState.status && runtimeState.status !== 'idle'
18897
19058
  ? '<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 +
19059
+ var branchCommIcon = conv.branchCommunication
19060
+ ? '<span class="conv-branch-comm-icon" title="' + escAttr(t('conversation.branchCommunicationBadge')) + '" aria-label="' + escAttr(t('conversation.branchCommunicationBadge')) + '">' + iconSvg('git-branch', t('conversation.branchCommunicationBadge')) + '</span>' : '';
19061
+ var archivePendingKey = currentWorkspaceKey() + '::' + String(conv.id);
19062
+ var archiveBtn = state.conversationArchivePending[archivePendingKey]
19063
+ ? '<button class="conv-archive-btn archiving" title="' + escAttr(t('archive.archiving')) + '" disabled><span class="conv-archive-spinner"></span></button>'
19064
+ : '<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>';
19065
+ div.innerHTML = '<span class="conv-summary" title="' + escAttr(String(conv.id || '')) + '">' + esc(displaySummary) + (conv.messageCount ? ' (' + esc(String(conv.messageCount)) + ')' : '') + '</span>' + branchCommIcon + runtimeBadge +
18901
19066
  '<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>' +
19067
+ archiveBtn +
18903
19068
  '<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
19069
  div.onclick = function(idx) { return function() { window.switchConversation(idx); }; }(i);
18905
19070
  div.addEventListener('keydown', window.handleConversationKey);
@@ -19052,7 +19217,7 @@ window.showNewConversationPage = function() {
19052
19217
  var html = '<div style="display:flex;flex-direction:column;gap:12px;padding:4px 2px;">' +
19053
19218
  '<div style="font-size:12px;color:var(--text-dim);line-height:1.5;">' + esc(t('workspace.newConversationDesc')) + '</div>' +
19054
19219
  '<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>' +
19220
+ '<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
19221
  '<div style="display:flex;gap:8px;">' +
19057
19222
  '<button class="sec-btn primary" style="flex:1;" onclick="window.doNewConversationFromPage()">' + esc(t('workspace.createConversation')) + '</button>' +
19058
19223
  '<button class="sec-btn" style="flex:1;" onclick="window.showNewWorkspaceDialog()">' + esc(t('workspace.createAction')) + '</button>' +
@@ -19196,45 +19361,45 @@ window.archiveConv = function(conversationId) {
19196
19361
  orderIds: rollbackOrder,
19197
19362
  chatHtml: priorActiveId === targetId && els['chat-area'] ? els['chat-area'].innerHTML : ''
19198
19363
  };
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()));
19364
+ // 归档中:不立即剔除行,按钮转圈等待;后端完成归档后再剔除前端。
19232
19365
  renderConversations();
19233
- if (priorActiveId === targetId) scheduleConversationArchiveActiveSync(workspaceKey);
19234
19366
  var archivePromise = api.archive ? api.archive(targetRuntime) : Promise.reject(new Error('Archive API unavailable'));
19235
19367
  archivePromise.then(function(receipt) {
19236
19368
  if (!receipt || receipt.ok !== true) throw new Error((receipt && receipt.error) || 'Archive failed');
19237
19369
  delete state.conversationArchivePending[pendingKey];
19370
+ // 后端完成归档:现在才剔除前端行 + 切换 active + 清理 Flow takeover。
19371
+ var currentConvs = currentWorkspaceConversations();
19372
+ var currentIdx = currentConvs.findIndex(function(item) { return String(item && item.id || 'default') === targetId; });
19373
+ if (currentIdx >= 0) currentConvs.splice(currentIdx, 1);
19374
+ if (!currentConvs.length) currentConvs.push({ id: 'default', summary: t('workspace.defaultConversation'), archived: false, active: true });
19375
+ var nextActiveId = priorActiveId && priorActiveId !== targetId ? priorActiveId : String(currentConvs[Math.min(Math.max(currentIdx, 0), currentConvs.length - 1)].id || 'default');
19376
+ var nextActiveIndex = Math.max(0, currentConvs.findIndex(function(item) { return String(item && item.id || '') === nextActiveId; }));
19377
+ for (var i = 0; i < currentConvs.length; i++) currentConvs[i].active = i === nextActiveIndex;
19378
+ state.workspaceActiveConversation[workspaceKey] = nextActiveIndex;
19379
+ state.activeConversation = nextActiveIndex;
19380
+ state.conversations = currentConvs;
19381
+ if (priorActiveId === targetId) {
19382
+ state.activeBackendConversationId = nextActiveId;
19383
+ if (els['chat-area']) els['chat-area'].innerHTML = '';
19384
+ }
19385
+ if (state.flowTakeovers) {
19386
+ var archivedFlowKey = runtimeKeyFor(targetRuntime.workspaceId, targetRuntime.conversationId);
19387
+ var archivedFlowRecord = state.flowTakeovers[archivedFlowKey];
19388
+ if (archivedFlowRecord) {
19389
+ archivedFlowRecord.running = false;
19390
+ archivedFlowRecord.paused = false;
19391
+ archivedFlowRecord.runtimeLease = null;
19392
+ archivedFlowRecord.queueLease = null;
19393
+ delete state.flowTakeovers[archivedFlowKey];
19394
+ }
19395
+ if (priorActiveId === targetId && window.renderFlowTakeover) {
19396
+ window.renderFlowTakeover(false, '', { target: targetRuntime });
19397
+ }
19398
+ }
19399
+ setConversationRuntimeState(targetRuntime, 'idle', '');
19400
+ setWorking(!!runningConversationRecord(activeConversationId()));
19401
+ renderConversations();
19402
+ if (priorActiveId === targetId) scheduleConversationArchiveActiveSync(workspaceKey);
19238
19403
  var optimisticArchive = {
19239
19404
  id: receipt.fileName,
19240
19405
  name: receipt.fileName,
@@ -19262,10 +19427,8 @@ window.archiveConv = function(conversationId) {
19262
19427
  scheduleConversationArchiveRefresh(workspaceKey);
19263
19428
  }).catch(function(err) {
19264
19429
  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.
19430
+ // 归档失败:恢复按钮,保留行(不再乐观剔除)。
19431
+ renderConversations();
19269
19432
  showUiNotice('[Archive] ' + t('workspace.saveFailed') + ': ' + (err.message || String(err)), 'error', 'archive-failed-' + targetId);
19270
19433
  });
19271
19434
  }
@@ -22511,6 +22674,11 @@ function schedulePostStartupUiRendering() {
22511
22674
  // navigation surfaces render after promotion on cancellable browser tasks.
22512
22675
  updateWorkspaceGate();
22513
22676
 
22677
+ if (document.documentElement.classList.contains('config-reloading')) {
22678
+ document.documentElement.classList.remove('config-reloading');
22679
+ try { sessionStorage.removeItem('newmark-config-reloading'); } catch (_) {}
22680
+ }
22681
+
22514
22682
  // === Event Listeners ===
22515
22683
  function escapeBelongsToFocusedControl(event) {
22516
22684
  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" />