newmark-agent 0.4.8 → 0.5.0

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.
@@ -4221,6 +4221,44 @@ textarea.mcp-input { min-height:76px; resize:vertical; font-family:var(--font-mo
4221
4221
  .setting-control { flex: 1; }
4222
4222
  .settings-load-error { color: var(--notice-error-text); }
4223
4223
 
4224
+ .remote-touch-copy {
4225
+ flex: 1;
4226
+ min-width: 0;
4227
+ display: flex;
4228
+ flex-direction: column;
4229
+ gap: 2px;
4230
+ }
4231
+ .remote-touch-copy-title {
4232
+ color: var(--text);
4233
+ font-size: 11px;
4234
+ font-weight: 700;
4235
+ line-height: 1.25;
4236
+ }
4237
+ .remote-touch-copy-desc {
4238
+ color: var(--text-dim);
4239
+ font-size: 10px;
4240
+ font-weight: 400;
4241
+ line-height: 1.35;
4242
+ }
4243
+ .remote-touch-action {
4244
+ flex: 0 0 120px;
4245
+ width: 120px;
4246
+ min-width: 120px;
4247
+ min-height: 28px;
4248
+ box-sizing: border-box;
4249
+ }
4250
+ .remote-touch-status {
4251
+ background: transparent !important;
4252
+ border-color: currentColor;
4253
+ box-shadow: 0 0 0 1px currentColor, 0 0 10px color-mix(in srgb, currentColor 42%, transparent);
4254
+ }
4255
+ .remote-touch-status:hover { background: transparent !important; }
4256
+ .remote-touch-status.listening { color: var(--accent2); }
4257
+ .remote-touch-status.error,
4258
+ .remote-touch-status.checking { color: #ffb454; }
4259
+ .remote-touch-status.off { color: var(--notice-error-text); }
4260
+ .remote-touch-status[aria-busy="true"] { cursor: wait; }
4261
+
4224
4262
  .segmented-control {
4225
4263
  display: grid;
4226
4264
  grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -6118,6 +6156,8 @@ var state = {
6118
6156
  agentWorkUiByConversation: {},
6119
6157
  workRunsByTarget: {},
6120
6158
  workRunsByBranch: {},
6159
+ conversationMessagesByTarget: {},
6160
+ pendingConversationActivations: {},
6121
6161
  runtimeBranchPathsByTarget: {},
6122
6162
  workRunAnchorIndexesByTarget: {},
6123
6163
  guideMessagesByTarget: {},
@@ -6302,6 +6342,9 @@ var state = {
6302
6342
  configuredAgentBackend: 'windows',
6303
6343
  agentBackendRestartRequired: false,
6304
6344
  remoteTouchEnabled: true,
6345
+ remoteTouchServer: { enabled: true, listening: false, reachable: false, state: 'checking', host: '', port: 47890, error: '', checkedAt: '', startedAt: 0 },
6346
+ remoteTouchStatusBusy: false,
6347
+ _remoteTouchStatusTimer: null,
6305
6348
  wslAvailable: false,
6306
6349
  wslDistros: [],
6307
6350
  defaultTerminalShell: 'powershell',
@@ -6484,6 +6527,10 @@ var NEWMARK_I18N = {
6484
6527
  'settings.runInWslDesc': 'Keeps the Windows UI and native desktop tools local while running the persistent Agent kernel, model calls, shell, and file tools inside WSL.',
6485
6528
  'settings.remoteTouch': 'Mobile remote-touch',
6486
6529
  'settings.remoteTouchDesc': 'Allow mobile access on the same LAN / Tailscale network',
6530
+ 'settings.remoteTouchListening': 'Listening',
6531
+ 'settings.remoteTouchError': 'Unreachable',
6532
+ 'settings.remoteTouchOff': 'Off',
6533
+ 'settings.remoteTouchChecking': 'Checking',
6487
6534
  'settings.remoteBehavior': 'Remote behavior',
6488
6535
  'settings.remoteConnect': 'Start connection',
6489
6536
  'settings.remoteConnectDesc': 'Show pairing QR',
@@ -7211,6 +7258,10 @@ var NEWMARK_I18N = {
7211
7258
  'settings.runInWslDesc': 'Windows UI 与桌面原生工具保留在本机,持续 Agent 内核、模型请求、Shell 和文件工具运行在 WSL。',
7212
7259
  'settings.remoteTouch': '移动端远程触及',
7213
7260
  'settings.remoteTouchDesc': '允许移动端通过同一内网 / Tailscale 连接',
7261
+ 'settings.remoteTouchListening': '监听中',
7262
+ 'settings.remoteTouchError': '不可触及',
7263
+ 'settings.remoteTouchOff': '已关闭',
7264
+ 'settings.remoteTouchChecking': '检测中',
7214
7265
  'settings.remoteBehavior': '远程行为',
7215
7266
  'settings.remoteConnect': '发起连接',
7216
7267
  'settings.remoteConnectDesc': '显示配对二维码',
@@ -7389,7 +7440,7 @@ var NEWMARK_I18N = {
7389
7440
  'model.unavailable': '不可用',
7390
7441
  'model.no': '否',
7391
7442
  'model.yes': '是',
7392
- 'model.notChecked': '未校验',
7443
+ 'model.notChecked': '未检测',
7393
7444
  'archive.current': '归档当前对话',
7394
7445
  'archive.archiving': '归档中...',
7395
7446
  'archive.empty': '暂无归档。',
@@ -9607,11 +9658,18 @@ function recordGuideUiMessage(input, target) {
9607
9658
  createdAt: String(receipt.createdAt || input.createdAt || (prior && prior.createdAt) || new Date().toISOString()),
9608
9659
  updatedAt: String(receipt.updatedAt || input.updatedAt || new Date().toISOString()),
9609
9660
  reason: explicitReason || (status === 'deferred' || status === 'rejected' ? String(prior && prior.reason || '') : ''),
9661
+ awaitingAck: input.awaitingAck === true || receipt.awaitingAck === true,
9610
9662
  attachments: normalizeConversationImageAttachments(input.attachments || receipt.attachments || (prior && prior.attachments) || [])
9611
9663
  };
9612
9664
  messages[clientMessageId] = record;
9613
9665
  if (isActiveConversationTarget(target)) {
9614
9666
  var element = findGuideMessageElement(clientMessageId);
9667
+ if (element) {
9668
+ if (prior && prior.awaitingAck && !record.awaitingAck && element.closest && element.closest('.work-run-pending-guides')) {
9669
+ element.remove();
9670
+ element = null;
9671
+ }
9672
+ }
9615
9673
  if (element) {
9616
9674
  applyGuideMessageMeta(element, record);
9617
9675
  if (record.runId && !element.classList.contains('work-run-guide-message')) element.remove();
@@ -9652,6 +9710,26 @@ function renderPendingGuideMessages(target, persistedGuideIds) {
9652
9710
  });
9653
9711
  pending.forEach(function(item) {
9654
9712
  var element = findGuideMessageElement(item.clientMessageId);
9713
+ if (item.awaitingAck && item.runId) {
9714
+ var runElement = findWorkRunElement({ runId: item.runId });
9715
+ var runBody = runElement && runElement.querySelector('.conversation-work-run-body');
9716
+ if (runBody) {
9717
+ var pendingStack = runBody.querySelector(':scope > .work-run-pending-guides');
9718
+ if (!pendingStack) {
9719
+ pendingStack = document.createElement('div');
9720
+ pendingStack.className = 'work-run-pending-guides';
9721
+ runBody.appendChild(pendingStack);
9722
+ }
9723
+ if (!element || !pendingStack.contains(element)) {
9724
+ var holder = document.createElement('div');
9725
+ holder.innerHTML = renderWorkRunGuideMessage(Object.assign({}, item, { status: 'accepted' }));
9726
+ element = holder.firstElementChild;
9727
+ if (element) pendingStack.appendChild(element);
9728
+ }
9729
+ if (element) applyGuideMessageMeta(element, item);
9730
+ return;
9731
+ }
9732
+ }
9655
9733
  if (element) applyGuideMessageMeta(element, item);
9656
9734
  else addMsg('user', item.content || 'Guide', 'guide', item.model || state.model, undefined, {
9657
9735
  clientMessageId: item.clientMessageId,
@@ -9915,7 +9993,7 @@ function normalizedWorkRun(run, target) {
9915
9993
  var status = String(run.status || 'running');
9916
9994
  var live = ['running', 'stopping', 'force_restarting'].indexOf(status) >= 0;
9917
9995
  var expanded = run.expanded === undefined
9918
- ? live && state.expandToolsDefault !== false
9996
+ ? (status === 'error' || (live && state.expandToolsDefault !== false))
9919
9997
  : !!run.expanded;
9920
9998
  return {
9921
9999
  runId: String(run.runId || run.id || ('run-' + Date.now() + '-' + Math.random().toString(16).slice(2))),
@@ -10583,6 +10661,7 @@ function updateConversationWorkRunElement(run, element) {
10583
10661
  '<span class="conversation-work-run-status">' + esc(String(run.status || '')) + '</span>' +
10584
10662
  '<span class="conversation-work-run-chevron" aria-hidden="true"></span></button>' +
10585
10663
  '<div class="conversation-work-run-body">' + renderWorkRunEvents(run, expanded) + '</div>';
10664
+ renderPendingGuideMessages(run.target, {});
10586
10665
  var wrapper = element.closest ? element.closest('.work-run-message') : null;
10587
10666
  if (wrapper) {
10588
10667
  wrapper.classList.toggle('flow-result-only', !!(run.flow && run.flow.activityVisibility === 'result-only'));
@@ -10729,14 +10808,14 @@ function applyAgentWorkEventToRun(event) {
10729
10808
  }
10730
10809
  run.sequence = Math.max(Number(run.sequence || 0), Number(event.sequence || 0));
10731
10810
  if (type === 'done') { run.status = 'completed'; run.endedAt = event.endedAt || event.timestampIso || new Date().toISOString(); if (run.userToggled !== true) run.expanded = false; }
10732
- else if (type === 'error') { run.status = 'error'; run.endedAt = event.endedAt || event.timestampIso || new Date().toISOString(); if (run.userToggled !== true) run.expanded = false; }
10811
+ else if (type === 'error') { run.status = 'error'; run.endedAt = event.endedAt || event.timestampIso || new Date().toISOString(); if (run.userToggled !== true) run.expanded = true; }
10733
10812
  else if (type === 'interrupted') { run.status = 'interrupted'; run.endedAt = event.endedAt || event.timestampIso || new Date().toISOString(); if (run.userToggled !== true) run.expanded = false; }
10734
10813
  else if (type === 'force_interrupted') { run.status = 'force_interrupted'; run.endedAt = event.endedAt || event.timestampIso || new Date().toISOString(); if (run.userToggled !== true) run.expanded = false; }
10735
10814
  else if (event.status && type.indexOf('guide') !== 0) {
10736
10815
  run.status = String(event.status);
10737
10816
  if (['completed', 'interrupted', 'force_interrupted', 'error'].indexOf(run.status) >= 0) {
10738
10817
  run.endedAt = event.endedAt || event.timestampIso || new Date().toISOString();
10739
- if (run.userToggled !== true) run.expanded = false;
10818
+ if (run.userToggled !== true) run.expanded = run.status === 'error';
10740
10819
  }
10741
10820
  }
10742
10821
  if (isActiveConversationTarget(target) && (!branchIds.viewed || !eventBranchId || branchIds.viewed === eventBranchId)) {
@@ -11017,14 +11096,58 @@ function renderPersistedToolMessage(message) {
11017
11096
  }, resultPrefix.test(content) || /\bcompleted\.?$/i.test(content) ? 'completed' : '');
11018
11097
  }
11019
11098
 
11020
- function renderChatMessages(messages) {
11021
- state.renderedChatMessages = Array.isArray(messages) ? messages.map(function(message) { return Object.assign({}, message); }) : [];
11099
+ function conversationMessageCache(target) {
11100
+ var scopedTarget = target || currentConversationTarget();
11101
+ var key = runtimeKeyFor(scopedTarget.workspaceId, scopedTarget.conversationId);
11102
+ if (!state.conversationMessagesByTarget) state.conversationMessagesByTarget = {};
11103
+ return Array.isArray(state.conversationMessagesByTarget[key])
11104
+ ? state.conversationMessagesByTarget[key].map(function(message) { return Object.assign({}, message); })
11105
+ : [];
11106
+ }
11107
+
11108
+ function cacheConversationMessages(messages, target) {
11109
+ var scopedTarget = target || currentConversationTarget();
11110
+ var key = runtimeKeyFor(scopedTarget.workspaceId, scopedTarget.conversationId);
11111
+ var cloned = Array.isArray(messages) ? messages.map(function(message) { return Object.assign({}, message); }) : [];
11112
+ if (!state.conversationMessagesByTarget) state.conversationMessagesByTarget = {};
11113
+ state.conversationMessagesByTarget[key] = cloned;
11114
+ return cloned.map(function(message) { return Object.assign({}, message); });
11115
+ }
11116
+
11117
+ function conversationHasReadableHistory(target) {
11118
+ return conversationMessageCache(target).length > 0 || workRunsForTarget(target).length > 0;
11119
+ }
11120
+
11121
+ function snapshotHasReadableConversationHistory(snapshot) {
11122
+ return !!(snapshot && ((Array.isArray(snapshot.chatMessages) && snapshot.chatMessages.length > 0)
11123
+ || (Array.isArray(snapshot.workRuns) && snapshot.workRuns.length > 0)));
11124
+ }
11125
+
11126
+ function renderConversationHistoryFirst(target) {
11127
+ if (!isActiveConversationTarget(target)) return false;
11128
+ var cachedMessages = conversationMessageCache(target);
11129
+ if (cachedMessages.length > 0) {
11130
+ renderChatMessages(cachedMessages, target);
11131
+ renderLoadEarlierButton();
11132
+ return true;
11133
+ }
11134
+ if (workRunsForTarget(target).length > 0) {
11135
+ renderChatMessages([], target);
11136
+ renderLoadEarlierButton();
11137
+ return true;
11138
+ }
11139
+ return false;
11140
+ }
11141
+
11142
+ function renderChatMessages(messages, target) {
11143
+ var renderTarget = target || currentConversationTarget(activeConversationId());
11144
+ var normalizedMessages = cacheConversationMessages(messages, renderTarget);
11145
+ state.renderedChatMessages = normalizedMessages;
11022
11146
  if (!els['chat-area']) return;
11023
11147
  var chatWasAtBottom = shouldAutoScroll(els['chat-area']);
11024
11148
  els['chat-area'].innerHTML = '';
11025
11149
  if (chatWasAtBottom) _chatShouldAutoScroll = true;
11026
- var renderConversationId = activeConversationId();
11027
- var renderTarget = currentConversationTarget(renderConversationId);
11150
+ var renderConversationId = renderTarget.conversationId;
11028
11151
  var guideMessages = guideMessagesForTarget(renderTarget);
11029
11152
  var persistedGuideIds = {};
11030
11153
  var persistedRuns = workRunsForTarget(renderTarget).slice();
@@ -11050,7 +11173,7 @@ function renderChatMessages(messages) {
11050
11173
  persistedRunIndexes[runId] = runIndex;
11051
11174
  }
11052
11175
  });
11053
- (Array.isArray(messages) ? messages : []).forEach(function(message, messageIndex) {
11176
+ normalizedMessages.forEach(function(message, messageIndex) {
11054
11177
  var runId = String(message && message.runId || '');
11055
11178
  var role = String(message && message.role || '');
11056
11179
  var indexedGuideId = String(message && message.clientMessageId || '');
@@ -11095,7 +11218,7 @@ function renderChatMessages(messages) {
11095
11218
  var pendingWorkReview = conversationWorkUiState(renderConversationId).pendingWorkReview;
11096
11219
  resetConversationWorkUi(renderConversationId, false);
11097
11220
  conversationWorkUiState(renderConversationId).pendingWorkReview = pendingWorkReview;
11098
- if (!messages || !messages.length) {
11221
+ if (!normalizedMessages.length) {
11099
11222
  persistedRuns.forEach(function(run) { renderOwnedWorkRun(run, true); });
11100
11223
  var pendingGuideCount = renderPendingGuideMessages(renderTarget, persistedGuideIds);
11101
11224
  if (!persistedRuns.length && !pendingGuideCount) {
@@ -11533,8 +11656,19 @@ function loadActiveConversationMessages(conversationId) {
11533
11656
  requestedConversationId === String(activeConversationId() || 'default') &&
11534
11657
  responseConversationId === requestedConversationId;
11535
11658
  if (!responseIsCurrent) return;
11536
- applyConversationSnapshot(s, requestedConversationId);
11537
- }).catch(function(){});
11659
+ applyConversationSnapshot(s, requestedConversationId, { preserveReadableHistory: true, allowEmptyHistory: true });
11660
+ }).catch(function(error) {
11661
+ var stillCurrent = loadGeneration === state.conversationLoadGeneration &&
11662
+ requestedWorkspaceKey === currentWorkspaceKey() &&
11663
+ requestedConversationId === String(activeConversationId() || 'default');
11664
+ if (!stillCurrent) return;
11665
+ showUiNotice(
11666
+ currentLang() === 'zh' ? '历史记录增强加载失败,已保留当前内容。' : 'History enhancement failed; current content was preserved.',
11667
+ 'error',
11668
+ 'conversation-history-load-' + requestedWorkspaceKey + '-' + requestedConversationId
11669
+ );
11670
+ console.error('[Conversation] history enhancement failed:', error && error.message ? error.message : String(error));
11671
+ });
11538
11672
  }
11539
11673
 
11540
11674
  function hydrateConversationBranchState(snapshot) {
@@ -11551,7 +11685,7 @@ function hydrateConversationBranchState(snapshot) {
11551
11685
  rememberRuntimeConversationBranchPath(currentConversationTarget(s.conversationId || activeConversationId()));
11552
11686
  }
11553
11687
 
11554
- function applyConversationSnapshot(s, requestedConversationId) {
11688
+ function applyConversationSnapshot(s, requestedConversationId, options) {
11555
11689
  if (!s) return;
11556
11690
  if (s.computerUse) {
11557
11691
  var computerTarget = currentConversationTarget(s.conversationId || requestedConversationId || activeConversationId());
@@ -11567,18 +11701,21 @@ function applyConversationSnapshot(s, requestedConversationId) {
11567
11701
  workspaceId: String((s && (s.workspaceId || (s.target && s.target.workspaceId))) || runtimeWorkspaceId('')),
11568
11702
  conversationId: String((s && (s.conversationId || (s.target && s.target.conversationId))) || requestedConversationId)
11569
11703
  };
11704
+ var preserveReadableHistory = !!(options && options.preserveReadableHistory
11705
+ && !snapshotHasReadableConversationHistory(s)
11706
+ && (!options.allowEmptyHistory || conversationHasReadableHistory(snapshotTarget)));
11570
11707
  if (s && s.runtimeKey) registerRuntimeKey(snapshotTarget, s.runtimeKey);
11571
11708
  hydrateConversationBranchState(s);
11572
11709
  rebindQueueToRuntimeBranch(snapshotTarget);
11573
- if (s && Array.isArray(s.workRuns)) {
11710
+ if (s && Array.isArray(s.workRuns) && !preserveReadableHistory) {
11574
11711
  var viewedBranchIdForSync = Array.isArray(s.viewedBranchNodePath) && s.viewedBranchNodePath.length
11575
11712
  ? String(s.viewedBranchNodePath[s.viewedBranchNodePath.length - 1])
11576
11713
  : '';
11577
11714
  syncWorkRunsSnapshot(s.workRuns, snapshotTarget, viewedBranchIdForSync || undefined);
11578
11715
  }
11579
11716
  applyAutoRouteRatingState(s);
11580
- if (s && s.chatMessages) {
11581
- renderChatMessages(s.chatMessages);
11717
+ if (s && Array.isArray(s.chatMessages) && !preserveReadableHistory) {
11718
+ renderChatMessages(s.chatMessages, snapshotTarget);
11582
11719
  renderLoadEarlierButton();
11583
11720
  }
11584
11721
  if (s && s.conversationId) {
@@ -13464,18 +13601,10 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
13464
13601
  attachments: optimisticAttachments,
13465
13602
  model: state.model,
13466
13603
  createdAt: envelope.createdAt,
13604
+ awaitingAck: true,
13467
13605
  allowStatusReset: retryingUnacknowledgedGuide
13468
13606
  }, lockedTarget);
13469
- applyAgentWorkEventToRun({
13470
- id: 'guide-' + clientMessageId + '-accepted',
13471
- type: 'guide_accepted',
13472
- content: displayText,
13473
- workspaceId: lockedTarget.workspaceId,
13474
- conversationId: lockedTarget.conversationId,
13475
- runId: envelope.runId,
13476
- clientMessageId: clientMessageId,
13477
- guide: optimisticGuide
13478
- });
13607
+ renderPendingGuideMessages(lockedTarget, {});
13479
13608
  var enqueue = api.enqueueGuide ? api.enqueueGuide(envelope) : api.sendMessage(requestMessage, lockedConversationId);
13480
13609
  return Promise.resolve(enqueue).then(function(receipt) {
13481
13610
  if (!receipt || typeof receipt !== 'object' || !String(receipt.status || '').trim()) {
@@ -13631,6 +13760,8 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
13631
13760
  var responseMsg = null;
13632
13761
  var sendFailure = '';
13633
13762
  try {
13763
+ var activationBeforeSend = pendingConversationActivation(lockedTarget);
13764
+ if (activationBeforeSend) await activationBeforeSend;
13634
13765
  var sendPromise = api.sendMessage(requestMessage, lockedTarget);
13635
13766
  if (requestedMode === 'goal') activateSubmittedGoal(opts.goalObjective || rawText);
13636
13767
  var r = await sendPromise;
@@ -15464,7 +15595,7 @@ function formatModelStatus(status) {
15464
15595
  if (raw === 'available' || raw === 'verified') return t('model.available');
15465
15596
  if (raw === 'degraded') return t('model.degraded');
15466
15597
  if (raw === 'unavailable' || raw === 'failed' || raw === 'error') return t('model.unavailable');
15467
- if (raw === 'not checked' || raw === 'not_checked' || raw === 'pending') return t('model.notChecked');
15598
+ if (raw === 'not checked' || raw === 'not_checked' || raw === 'unvalidated' || raw === 'pending') return t('model.notChecked');
15468
15599
  return status || t('model.notChecked');
15469
15600
  }
15470
15601
 
@@ -15476,6 +15607,7 @@ function effectiveUiModelStatus(modelEntry) {
15476
15607
  var textEvidence = capabilities.text === true || capabilities.text_input === true || capabilities.text_output === true
15477
15608
  || !!(modelEntry.evaluation && (modelEntry.evaluation.text_input || modelEntry.evaluation.text_output));
15478
15609
  if (rawValidation === 'auth_error' || rawValidation === 'invalid_config') return rawValidation;
15610
+ if (String((modelEntry.validation && modelEntry.validation.level) || '').toLowerCase() === 'discovered') return 'unvalidated';
15479
15611
  if (textEvidence && rawValidation === 'unavailable') return 'degraded';
15480
15612
  return rawValidation || rawEvaluation;
15481
15613
  }
@@ -15539,15 +15671,15 @@ function renderGeneralSettings() {
15539
15671
  '<div class="setting-desc" id="agent-wsl-status">' + esc(state.agentBackend && state.agentBackend.connected ? t('settings.wslConnected') : t('settings.wslDisconnected')) + '</div></div></div>' : '';
15540
15672
  return wslHtml + '<div class="setting-subsection">' +
15541
15673
  '<div class="setting-subsection-title">' + esc(t('settings.remoteBehavior')) + '</div>' +
15542
- '<div class="setting-row">' +
15543
- '<label class="setting-label" for="remote-touch-enabled" style="display:inline-flex;align-items:center;gap:6px;min-width:120px;">' +
15544
- '<input type="checkbox" id="remote-touch-enabled"' + (state.remoteTouchEnabled ? ' checked' : '') + ' onchange="window.setRemoteTouchEnabled(this.checked)">' +
15545
- '<span>' + esc(t('settings.remoteTouch')) + '</span></label>' +
15546
- '<span class="setting-desc" style="flex:1;min-width:140px;">' + esc(t('settings.remoteTouchDesc')) + '</span>' +
15674
+ '<div class="setting-row remote-touch-row">' +
15675
+ '<button type="button" id="remote-touch-status-button" class="sec-btn remote-touch-action remote-touch-status ' + esc(remoteTouchStatusClass()) + '" aria-pressed="' + (state.remoteTouchEnabled ? 'true' : 'false') + '" aria-busy="' + (state.remoteTouchStatusBusy ? 'true' : 'false') + '" onclick="window.setRemoteTouchEnabled(!state.remoteTouchEnabled)" title="' + esc(remoteTouchStatusTitle()) + '"' + (state.remoteTouchStatusBusy ? ' disabled' : '') + '>' + esc(remoteTouchStatusText()) + '</button>' +
15676
+ '<div class="remote-touch-copy"><div class="remote-touch-copy-title">' + esc(t('settings.remoteTouch')) + '</div>' +
15677
+ '<div class="remote-touch-copy-desc">' + esc(t('settings.remoteTouchDesc')) + '</div></div>' +
15547
15678
  '</div>' +
15548
- '<div class="setting-row" style="border-bottom:none;">' +
15549
- '<button type="button" class="sec-btn primary" onclick="window.showMobilePairing()">' + esc(t('settings.remoteConnect')) + '</button>' +
15550
- '<span class="setting-desc" style="flex:1;">' + esc(t('settings.remoteConnectDesc')) + '</span>' +
15679
+ '<div class="setting-row remote-touch-row" style="border-bottom:none;">' +
15680
+ '<button type="button" id="remote-touch-connect-button" class="sec-btn primary remote-touch-action" onclick="window.showMobilePairing()">' + esc(t('settings.remoteConnect')) + '</button>' +
15681
+ '<div class="remote-touch-copy"><div class="remote-touch-copy-title">' + esc(t('mobile.pair')) + '</div>' +
15682
+ '<div class="remote-touch-copy-desc">' + esc(t('settings.remoteConnectDesc')) + '</div></div>' +
15551
15683
  '</div>' +
15552
15684
  '</div>' +
15553
15685
  '<div class="setting-row">' +
@@ -15656,7 +15788,7 @@ function renderModelSettings() {
15656
15788
  var normalizedStatus = String(statusRaw || '').toLowerCase();
15657
15789
  var statusClass = normalizedStatus === 'available' || normalizedStatus === 'verified' || normalizedStatus === 'degraded'
15658
15790
  ? 'model-eval-ok'
15659
- : (normalizedStatus === 'not checked' ? 'model-eval-pending' : 'model-eval-bad');
15791
+ : (normalizedStatus === 'not checked' || normalizedStatus === 'unvalidated' ? 'model-eval-pending' : 'model-eval-bad');
15660
15792
  var details = '';
15661
15793
  if (typeof modelEntry !== 'string') {
15662
15794
  var tierMapCount = modelEntry.thinking_tier_map && typeof modelEntry.thinking_tier_map === 'object' ? Object.keys(modelEntry.thinking_tier_map).length : 0;
@@ -16225,13 +16357,96 @@ window.setAgentBackendMode = async function(mode) {
16225
16357
  return true;
16226
16358
  };
16227
16359
 
16360
+ function remoteTouchStatusClass() {
16361
+ if (state.remoteTouchStatusBusy) return 'checking';
16362
+ var status = state.remoteTouchServer || {};
16363
+ if (!state.remoteTouchEnabled || status.state === 'off') return 'off';
16364
+ return status.state === 'listening' && status.listening && status.reachable ? 'listening' : 'error';
16365
+ }
16366
+
16367
+ function remoteTouchStatusText() {
16368
+ var statusClass = remoteTouchStatusClass();
16369
+ if (statusClass === 'checking') return t('settings.remoteTouchChecking');
16370
+ if (statusClass === 'listening') return t('settings.remoteTouchListening');
16371
+ if (statusClass === 'error') return t('settings.remoteTouchError');
16372
+ return t('settings.remoteTouchOff');
16373
+ }
16374
+
16375
+ function remoteTouchStatusTitle() {
16376
+ var status = state.remoteTouchServer || {};
16377
+ var endpoint = status.host ? status.host + ':' + (status.port || 47890) : '';
16378
+ return [remoteTouchStatusText(), endpoint, status.error || ''].filter(Boolean).join(' · ');
16379
+ }
16380
+
16381
+ window.updateRemoteTouchStatusButton = function() {
16382
+ var button = document.getElementById('remote-touch-status-button');
16383
+ if (!button) return;
16384
+ button.className = 'sec-btn remote-touch-action remote-touch-status ' + remoteTouchStatusClass();
16385
+ button.textContent = remoteTouchStatusText();
16386
+ button.setAttribute('aria-pressed', state.remoteTouchEnabled ? 'true' : 'false');
16387
+ button.setAttribute('aria-busy', state.remoteTouchStatusBusy ? 'true' : 'false');
16388
+ button.title = remoteTouchStatusTitle();
16389
+ button.disabled = !!state.remoteTouchStatusBusy;
16390
+ };
16391
+
16392
+ window.refreshRemoteTouchServerStatus = async function() {
16393
+ if (!api.mobileServerStatus) return state.remoteTouchServer;
16394
+ try {
16395
+ var result = await api.mobileServerStatus();
16396
+ if (result && result.state) {
16397
+ state.remoteTouchServer = result;
16398
+ state.remoteTouchEnabled = !!result.enabled;
16399
+ }
16400
+ } catch (error) {
16401
+ state.remoteTouchServer = {
16402
+ enabled: state.remoteTouchEnabled,
16403
+ listening: false,
16404
+ reachable: false,
16405
+ state: state.remoteTouchEnabled ? 'error' : 'off',
16406
+ host: '',
16407
+ port: 47890,
16408
+ error: error && error.message ? error.message : String(error || ''),
16409
+ checkedAt: new Date().toISOString(),
16410
+ startedAt: 0
16411
+ };
16412
+ }
16413
+ window.updateRemoteTouchStatusButton();
16414
+ return state.remoteTouchServer;
16415
+ };
16416
+
16417
+ window.startRemoteTouchStatusPolling = function() {
16418
+ if (state._remoteTouchStatusTimer) clearInterval(state._remoteTouchStatusTimer);
16419
+ window.refreshRemoteTouchServerStatus().catch(function(){});
16420
+ state._remoteTouchStatusTimer = setInterval(function() {
16421
+ window.refreshRemoteTouchServerStatus().catch(function(){});
16422
+ }, 5000);
16423
+ };
16424
+
16228
16425
  window.setRemoteTouchEnabled = async function(value) {
16229
16426
  var next = !!value;
16230
16427
  state.remoteTouchEnabled = next;
16231
- if (api.saveSetting) {
16232
- try { await api.saveSetting('remote', 'touch_enabled', next); } catch (e) {}
16428
+ state.remoteTouchStatusBusy = true;
16429
+ window.updateRemoteTouchStatusButton();
16430
+ try {
16431
+ var result = api.setRemoteTouchEnabled
16432
+ ? await api.setRemoteTouchEnabled(next)
16433
+ : (api.saveSetting ? await api.saveSetting('remote', 'touch_enabled', next) : null);
16434
+ if (result && result.state) state.remoteTouchServer = result;
16435
+ } catch (error) {
16436
+ state.remoteTouchServer = {
16437
+ enabled: next,
16438
+ listening: false,
16439
+ reachable: false,
16440
+ state: next ? 'error' : 'off',
16441
+ host: '', port: 47890,
16442
+ error: error && error.message ? error.message : String(error || ''),
16443
+ checkedAt: new Date().toISOString(), startedAt: 0
16444
+ };
16445
+ } finally {
16446
+ state.remoteTouchStatusBusy = false;
16233
16447
  }
16234
- window.settingsTab('general');
16448
+ await window.refreshRemoteTouchServerStatus();
16449
+ window.updateRemoteTouchStatusButton();
16235
16450
  };
16236
16451
 
16237
16452
  window.setAgentWslDistro = async function(value) {
@@ -16477,6 +16692,7 @@ window.saveModelEdit = function(oldProvIdx, modelIdx) {
16477
16692
  if (typeof previous === 'string') previous = { name: previous, display: previous };
16478
16693
  var tierMap = window.parseThinkingTierMap(tierMapEl ? tierMapEl.value : '');
16479
16694
  var updated = Object.assign({}, previous, {
16695
+ _previous_name: previous.name || name,
16480
16696
  name: name,
16481
16697
  display: name,
16482
16698
  description: descEl ? descEl.value : '',
@@ -19214,6 +19430,12 @@ function holdForegroundConversation(id, ms) {
19214
19430
  state.foregroundConversationHoldUntil = Date.now() + (ms || 4500);
19215
19431
  }
19216
19432
 
19433
+ function pendingConversationActivation(target) {
19434
+ var scopedTarget = target || currentConversationTarget();
19435
+ var key = runtimeKeyFor(scopedTarget.workspaceId, scopedTarget.conversationId);
19436
+ return state.pendingConversationActivations && state.pendingConversationActivations[key];
19437
+ }
19438
+
19217
19439
  function applyBackendConversations(items, activeId, workspaceId) {
19218
19440
  if (!state.currentWorkspace || !items || !items.length) return;
19219
19441
  var key = currentWorkspaceKey(workspaceId);
@@ -19275,14 +19497,24 @@ function syncBackendConversation() {
19275
19497
  }
19276
19498
  if (conv && api.ensureConversation) {
19277
19499
  var requestedTarget = currentConversationTarget(conv.id);
19500
+ state.activeBackendConversationId = conv.id;
19501
+ var historyPromise = loadActiveConversationMessages(conv.id);
19278
19502
  var activate = api.activateConversation ? api.activateConversation(requestedTarget) : api.ensureConversation(requestedTarget);
19279
- return activate.then(function(s) {
19503
+ var activationPromise = activate.then(function(s) {
19280
19504
  var id = (s && s.conversationId) || conv.id;
19281
19505
  if (!requestIsCurrent() || String(id) !== requestedConversationId) return;
19282
19506
  state.activeBackendConversationId = id;
19283
- applyConversationSnapshot(s, id);
19284
- if (s && s.runtimeDeferred) return loadActiveConversationMessages(id);
19285
- }).catch(function(){});
19507
+ applyConversationSnapshot(s, id, { preserveReadableHistory: true });
19508
+ }).catch(function(error) {
19509
+ if (!requestIsCurrent()) return;
19510
+ showUiNotice(
19511
+ currentLang() === 'zh' ? '对话运行时激活失败,历史记录仍可查看。' : 'Conversation runtime activation failed; history remains available.',
19512
+ 'error',
19513
+ 'conversation-activation-' + requestedWorkspaceKey + '-' + requestedConversationId
19514
+ );
19515
+ console.error('[Conversation] runtime activation failed:', error && error.message ? error.message : String(error));
19516
+ });
19517
+ return Promise.allSettled([historyPromise, activationPromise]);
19286
19518
  }
19287
19519
  if (conv) {
19288
19520
  if (!requestIsCurrent()) return Promise.resolve();
@@ -19498,10 +19730,11 @@ window.newConversation = function(workspaceReference, branchCommunication) {
19498
19730
  els['chat-area'].innerHTML = '';
19499
19731
  var target = currentConversationTarget(id);
19500
19732
  var activation = api.activateConversation ? api.activateConversation(target) : (api.ensureConversation ? api.ensureConversation(target) : Promise.resolve(null));
19501
- return activation.then(function(s) {
19733
+ var activationKey = runtimeKeyFor(target.workspaceId, target.conversationId);
19734
+ var activationReady = activation.then(function(s) {
19502
19735
  if (createdWorkspaceKey !== currentWorkspaceKey() || id !== String(activeConversationId() || 'default')) return;
19503
19736
  state.activeBackendConversationId = String((s && s.conversationId) || id);
19504
- if (s) applyConversationSnapshot(s, id);
19737
+ if (s) applyConversationSnapshot(s, id, { preserveReadableHistory: true });
19505
19738
  if (branchCommunication && api.setConversationBranchCommunication) {
19506
19739
  api.setConversationBranchCommunication(target, true).then(function() {
19507
19740
  var convsNow = currentWorkspaceConversations();
@@ -19511,16 +19744,23 @@ window.newConversation = function(workspaceReference, branchCommunication) {
19511
19744
  }
19512
19745
  }).then(function() {
19513
19746
  if (createdWorkspaceKey !== currentWorkspaceKey() || id !== String(activeConversationId() || 'default')) return;
19514
- state.foregroundConversationHoldId = '';
19515
- state.foregroundConversationHoldUntil = 0;
19516
- renderChatMessages([]);
19517
- addMsg('system', '[System] ' + t('workspace.newConversation') + ': ' + summary, 'system', '');
19747
+ if (!conversationHasReadableHistory(target)) {
19748
+ renderChatMessages([], target);
19749
+ addMsg('system', '[System] ' + t('workspace.newConversation') + ': ' + summary, 'system', '');
19750
+ }
19518
19751
  }).catch(function(error) {
19519
19752
  if (createdWorkspaceKey === currentWorkspaceKey() && id === String(activeConversationId() || 'default')) {
19520
19753
  showUiNotice((currentLang() === 'zh' ? '新对话持久化失败:' : 'Failed to persist the new conversation: ') + (error && error.message ? error.message : String(error)), 'error', 'new-conversation-activation-' + id);
19521
19754
  }
19522
19755
  throw error;
19756
+ }).finally(function() {
19757
+ if (state.pendingConversationActivations && state.pendingConversationActivations[activationKey] === activationReady) {
19758
+ delete state.pendingConversationActivations[activationKey];
19759
+ }
19523
19760
  });
19761
+ if (!state.pendingConversationActivations) state.pendingConversationActivations = {};
19762
+ state.pendingConversationActivations[activationKey] = activationReady;
19763
+ return activationReady;
19524
19764
  };
19525
19765
 
19526
19766
  window.showNewConversationPage = function() {
@@ -19590,7 +19830,7 @@ window.switchConversation = function(idx) {
19590
19830
  renderConversations();
19591
19831
  var switchSeq = (state._conversationSwitchSeq || 0) + 1;
19592
19832
  state._conversationSwitchSeq = switchSeq;
19593
- els['chat-area'].innerHTML = '';
19833
+ var restoredReadableHistory = renderConversationHistoryFirst(activeBrowserTarget);
19594
19834
  // The Flow takeover bubble is conversation-local. Immediately reconcile it to
19595
19835
  // the newly active conversation: if the incoming conversation owns a Flow,
19596
19836
  // show it; otherwise hide the bubble instantly so no takeover ever leaks
@@ -19599,7 +19839,9 @@ window.switchConversation = function(idx) {
19599
19839
  if (state._conversationLoadingMsgEl && state._conversationLoadingMsgEl.parentNode) {
19600
19840
  state._conversationLoadingMsgEl.parentNode.removeChild(state._conversationLoadingMsgEl);
19601
19841
  }
19602
- state._conversationLoadingMsgEl = addMsg('workflow running', t('conversation.loadingIsolated'), 'system', state.model);
19842
+ state._conversationLoadingMsgEl = restoredReadableHistory
19843
+ ? null
19844
+ : addMsg('workflow running', t('conversation.loadingIsolated'), 'system', state.model);
19603
19845
  syncBackendConversation().then(function() {
19604
19846
  // Only the latest switch may touch the UI: an earlier switch's sync
19605
19847
  // resolution must not restore drafts into or re-render the now-current
@@ -23088,6 +23330,7 @@ function schedulePostStartupUiRendering() {
23088
23330
  }
23089
23331
 
23090
23332
  updateMarqueeFromConfig();
23333
+ window.startRemoteTouchStatusPolling();
23091
23334
 
23092
23335
  // Populate selects
23093
23336
  if (els['mode-select']) els['mode-select'].value = state.mode;
@@ -23351,6 +23594,7 @@ function schedulePostStartupUiRendering() {
23351
23594
 
23352
23595
  window.addEventListener('beforeunload', function() {
23353
23596
  if (state._postStartupUiRendering && state._postStartupUiRendering.cancel) state._postStartupUiRendering.cancel();
23597
+ if (state._remoteTouchStatusTimer) clearInterval(state._remoteTouchStatusTimer);
23354
23598
  cancelBrowserGuestIdleDestroy();
23355
23599
  flushPendingConversationDraftPersists();
23356
23600
  }, { once: true });