newmark-agent 0.3.8 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/cli-help.d.ts +2 -0
  2. package/dist/cli-help.js +22 -0
  3. package/dist/context/domain/types.d.ts +1 -1
  4. package/dist/conversation-utility-host.bundle.cjs +2060 -774
  5. package/dist/conversation-utility-host.js +4 -1
  6. package/dist/core/agent.d.ts +23 -1
  7. package/dist/core/agent.js +517 -37
  8. package/dist/core/agentKernelRunner.js +20 -3
  9. package/dist/core/browserControl.d.ts +8 -0
  10. package/dist/core/browserUsePageAdapter.d.ts +3 -0
  11. package/dist/core/browserUsePageAdapter.js +19 -2
  12. package/dist/core/compressionHistoryArchive.d.ts +28 -0
  13. package/dist/core/compressionHistoryArchive.js +131 -0
  14. package/dist/core/computerUseSession.d.ts +44 -0
  15. package/dist/core/computerUseSession.js +105 -0
  16. package/dist/core/config.js +1 -0
  17. package/dist/core/conversationKernel.d.ts +3 -1
  18. package/dist/core/conversationKernel.js +76 -7
  19. package/dist/core/electronBrowserUseHost.js +16 -0
  20. package/dist/core/electronUtilityAgentClient.js +84 -2
  21. package/dist/core/electronUtilityRuntimePool.js +6 -7
  22. package/dist/core/runtimeLifecycle.d.ts +23 -0
  23. package/dist/core/runtimeLifecycle.js +146 -0
  24. package/dist/core/types.d.ts +3 -0
  25. package/dist/core/utilityHostToolRouter.d.ts +7 -0
  26. package/dist/core/utilityHostToolRouter.js +25 -33
  27. package/dist/core/wslAgentRuntimePool.js +9 -10
  28. package/dist/launcher.js +13 -1
  29. package/dist/main.js +147 -18
  30. package/dist/preload.js +4 -2
  31. package/dist/tools/index.js +42 -52
  32. package/dist/tools/nativeTools.js +1 -1
  33. package/dist/ui/index.html +296 -40
  34. package/dist/wsl-agent-host.bundle.cjs +2063 -776
  35. package/dist/wsl-agent-host.js +4 -0
  36. package/package.json +3 -3
@@ -5461,6 +5461,7 @@ html.newmark-memory-overview-viewer body {
5461
5461
  <button class="shell-btn browser-action-btn" onclick="window.browserReload()" title="Reload"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#refresh-cw"></use></svg><span class="icon-only-label">Reload</span></button>
5462
5462
  <input class="browser-url-input" id="browser-url" placeholder="Enter URL..." onkeydown="if(event.key==='Enter')window.navigateBrowser(this.value)">
5463
5463
  <button class="shell-btn" onclick="window.navigateBrowser(document.getElementById('browser-url').value)" title="Go"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#send"></use></svg><span class="icon-only-label">Go</span></button>
5464
+ <button class="shell-btn" id="computer-use-toggle" onclick="window.toggleComputerUse()" title="ComputerUse: enabled for this conversation" aria-pressed="true">CU</button>
5464
5465
  </div>
5465
5466
  <div class="browser-frame" id="browser-frame">
5466
5467
  <div class="browser-guest-placeholder" id="browser-guest-placeholder">Browser loads on first use</div>
@@ -5681,6 +5682,7 @@ var state = {
5681
5682
  terminalInterruptTimeoutMs: 0,
5682
5683
  nativeTools: [],
5683
5684
  nativeToolEnabled: {},
5685
+ computerUseByTarget: {},
5684
5686
  sshConnections: [],
5685
5687
  _layoutSaveTimer: null,
5686
5688
  _sendInFlight: false
@@ -9594,17 +9596,22 @@ function renderConversationWorkRuns(runs, target) {
9594
9596
  return normalized;
9595
9597
  }
9596
9598
 
9597
- var pendingLargeWorkRunRenders = {};
9598
- var scheduledLargeWorkRunRenders = {};
9599
- function scheduleLargeConversationWorkRunRender(run) {
9599
+ // Work events can arrive much faster than the browser can paint them (text
9600
+ // deltas are especially noisy). Keep the authoritative run in memory for
9601
+ // every event, but coalesce its DOM replacement to one animation frame. This
9602
+ // is important for long real-model runs: replacing the whole work block for
9603
+ // every delta can starve Chromium's renderer/CDP event loop.
9604
+ var pendingConversationWorkRunRenders = {};
9605
+ var scheduledConversationWorkRunRenders = {};
9606
+ function scheduleConversationWorkRunRender(run) {
9600
9607
  var key = runtimeKeyFor(run.target && run.target.workspaceId, run.target && run.target.conversationId) + '::' + String(run.runId || '');
9601
- pendingLargeWorkRunRenders[key] = run;
9602
- if (scheduledLargeWorkRunRenders[key]) return;
9603
- scheduledLargeWorkRunRenders[key] = true;
9608
+ pendingConversationWorkRunRenders[key] = run;
9609
+ if (scheduledConversationWorkRunRenders[key]) return;
9610
+ scheduledConversationWorkRunRenders[key] = true;
9604
9611
  requestAnimationFrame(function() {
9605
- var latest = pendingLargeWorkRunRenders[key];
9606
- delete pendingLargeWorkRunRenders[key];
9607
- delete scheduledLargeWorkRunRenders[key];
9612
+ var latest = pendingConversationWorkRunRenders[key];
9613
+ delete pendingConversationWorkRunRenders[key];
9614
+ delete scheduledConversationWorkRunRenders[key];
9608
9615
  if (latest && isActiveConversationTarget(latest.target)) renderConversationWorkRun(latest);
9609
9616
  });
9610
9617
  }
@@ -9640,7 +9647,9 @@ function applyAgentWorkEventToRun(event) {
9640
9647
  var priorGuideEvent = run.events[guideEventIndex];
9641
9648
  if (guideWorkEventKey(priorGuideEvent) === incomingGuideKey) {
9642
9649
  run.events[guideEventIndex] = mergeGuideWorkEvent(priorGuideEvent, event);
9643
- updateConversationWorkRunElement(run);
9650
+ if (isActiveConversationTarget(target) && (!branchIds.viewed || !eventBranchId || branchIds.viewed === eventBranchId)) {
9651
+ scheduleConversationWorkRunRender(run);
9652
+ }
9644
9653
  return run;
9645
9654
  }
9646
9655
  }
@@ -9667,8 +9676,8 @@ function applyAgentWorkEventToRun(event) {
9667
9676
  if (isActiveConversationTarget(target) && (!branchIds.viewed || !eventBranchId || branchIds.viewed === eventBranchId)) {
9668
9677
  state.workRunsByTarget[runtimeKeyFor(target.workspaceId, target.conversationId)] = runs;
9669
9678
  var existingElement = findWorkRunElement(run);
9670
- if (existingElement && run.events.length >= 12 && (type === 'tool_call' || type === 'tool_result')) scheduleLargeConversationWorkRunRender(run);
9671
- else renderConversationWorkRun(run);
9679
+ if (!existingElement || type === 'start') renderConversationWorkRun(run);
9680
+ else scheduleConversationWorkRunRender(run);
9672
9681
  }
9673
9682
  return run;
9674
9683
  }
@@ -10459,7 +10468,14 @@ function hydrateConversationBranchState(snapshot) {
10459
10468
  }
10460
10469
 
10461
10470
  function applyConversationSnapshot(s, requestedConversationId) {
10462
- if (!s) return;
10471
+ if (!s) return;
10472
+ if (s.computerUse) {
10473
+ var computerTarget = currentConversationTarget(s.conversationId || requestedConversationId || activeConversationId());
10474
+ var computerKey = computerUseTargetKey(computerTarget);
10475
+ if (!state.computerUseByTarget) state.computerUseByTarget = {};
10476
+ state.computerUseByTarget[computerKey] = s.computerUse;
10477
+ if (isActiveConversationTarget(computerTarget)) renderComputerUseToggle(s.computerUse, computerTarget);
10478
+ }
10463
10479
  state.goalText = s.goal && s.goal.objective ? String(s.goal.objective) : '';
10464
10480
  state.goalVisible = !!state.goalText;
10465
10481
  state.goalPaused = !!(s.goal && s.goal.paused);
@@ -10813,6 +10829,44 @@ function isCurrentConversationRunning() {
10813
10829
  return !!(id && runningConversationRecord(id));
10814
10830
  }
10815
10831
 
10832
+ // A terminal final_response event can reach the renderer before the awaited
10833
+ // send promise performs its final local cleanup. If a user submits in that
10834
+ // small window, trusting the stale local running/active-send flags silently
10835
+ // drops the new message. Reconcile only when a send is already considered
10836
+ // active; a backend-confirmed running snapshot still keeps the guard intact.
10837
+ async function reconcileStaleLocalRuntimeBeforeSend(target) {
10838
+ if (!target || !api.getState) return false;
10839
+ var snapshot;
10840
+ try {
10841
+ snapshot = await api.getState(target);
10842
+ } catch (_) {
10843
+ return false;
10844
+ }
10845
+ var runtimeSnapshot = snapshot && snapshot.runtime || null;
10846
+ var snapshotStatus = String(snapshot && snapshot.status || '').toLowerCase();
10847
+ var backendStillRunning = !!(runtimeSnapshot && (runtimeSnapshot.running || runtimeSnapshot.stopRequested))
10848
+ || ['running', 'stopping', 'force_restarting'].indexOf(snapshotStatus) >= 0;
10849
+ if (backendStillRunning) return false;
10850
+ if (['idle', 'completed', 'interrupted', 'force_interrupted', 'error'].indexOf(snapshotStatus) < 0) return false;
10851
+ var runId = String((runtimeSnapshot && runtimeSnapshot.runId) || snapshot.runId || '');
10852
+ var generation = Number((runtimeSnapshot && runtimeSnapshot.generation) || snapshot.generation || 0);
10853
+ setConversationRuntimeState(target, snapshotStatus, runId, { provisional: false, generation: generation });
10854
+ var targetKey = runtimeKeyFor(target.workspaceId, target.conversationId);
10855
+ if (state.activeSendCallsByTarget) {
10856
+ delete state.activeSendCallsByTarget[targetKey];
10857
+ delete state.activeSendCallsByTarget[runtimeKeyFor('', target.conversationId)];
10858
+ }
10859
+ if (state.nextQueueSchedulesByTarget) {
10860
+ delete state.nextQueueSchedulesByTarget[targetKey];
10861
+ delete state.nextQueueSchedulesByTarget[runtimeKeyFor('', target.conversationId)];
10862
+ }
10863
+ state._sendInFlight = Object.keys(state.runningConversations || {}).length > 0;
10864
+ setWorking(state._sendInFlight);
10865
+ updateSubmitButtonState();
10866
+ if (window.renderInputStack) window.renderInputStack();
10867
+ return true;
10868
+ }
10869
+
10816
10870
  function setSubmitButtonVisual(icon, label, running, stopMode) {
10817
10871
  var btn = els['submit-btn'] || document.getElementById('submit-btn');
10818
10872
  if (!btn) return;
@@ -11837,6 +11891,13 @@ window.endQueueDrag = function(event) {
11837
11891
  if (event.currentTarget) event.currentTarget.classList.remove('dragging');
11838
11892
  };
11839
11893
 
11894
+ function recoverableGuideRejection(receipt) {
11895
+ if (!receipt || String(receipt.status || '').toLowerCase() !== 'rejected') return false;
11896
+ var reason = String(receipt.reason || receipt.error || '').trim();
11897
+ return reason === 'Target conversation is not running'
11898
+ || reason === 'Guide runId does not match the active run';
11899
+ }
11900
+
11840
11901
  window.sendMessage = async function(modeOverride, queuedText, opts) {
11841
11902
  if (!window.requireWorkspace('send')) return;
11842
11903
  opts = opts || {};
@@ -11960,6 +12021,28 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
11960
12021
  var conversationRunning = !!activeRuntime;
11961
12022
  markConversationTracked(lockedConversationId, state.conversationTrackMs || 300000, lockedTarget.workspaceId);
11962
12023
 
12024
+ var activeSendCall = !!(state.activeSendCallsByTarget && state.activeSendCallsByTarget[lockedRuntimeKey]);
12025
+ if ((conversationRunning || activeSendCall) && api.getState) {
12026
+ var reconciledLocalRuntime = await reconcileStaleLocalRuntimeBeforeSend(lockedTarget);
12027
+ if (reconciledLocalRuntime) {
12028
+ activeRuntime = runningConversationRecord(lockedConversationId, lockedTarget.workspaceId);
12029
+ conversationRunning = !!activeRuntime;
12030
+ if (!conversationRunning && effectiveInputMode === 'guide') effectiveInputMode = 'next';
12031
+ }
12032
+ }
12033
+
12034
+ if (opts.retryAfterGuideRejection && conversationRunning && api.getState) {
12035
+ try {
12036
+ var freshRuntimeState = await api.getState(lockedTarget);
12037
+ var freshStatus = String(freshRuntimeState && freshRuntimeState.status || '').toLowerCase();
12038
+ if (['idle', 'completed', 'interrupted', 'force_interrupted', 'error'].indexOf(freshStatus) >= 0) {
12039
+ setConversationRuntimeState(lockedTarget, 'idle', '');
12040
+ activeRuntime = null;
12041
+ conversationRunning = false;
12042
+ }
12043
+ } catch (_) {}
12044
+ }
12045
+
11963
12046
  if (effectiveInputMode === 'next' && !opts.fromQueue && conversationRunning) {
11964
12047
  if (!state.nextQueue) state.nextQueue = [];
11965
12048
  if (!state.nextQueueRequests) state.nextQueueRequests = [];
@@ -12041,6 +12124,57 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
12041
12124
  throw new Error(currentLang() === 'zh' ? '未收到有效的 Guide 回执' : 'A valid Guide acknowledgement was not received');
12042
12125
  }
12043
12126
  var status = normalizeGuideUiStatus(receipt.status);
12127
+ if (recoverableGuideRejection(receipt)) {
12128
+ // Renderer runtime state can lag the backend's finalization event by
12129
+ // one IPC turn. The Guide was explicitly rejected, so replaying the
12130
+ // exact payload as a fresh Build is safe and prevents a normal user
12131
+ // send from disappearing behind a stale Guide route.
12132
+ var fallbackRequest = {
12133
+ text: typeof requestMessage === 'string' ? requestMessage : String(requestMessage.text || ''),
12134
+ images: typeof requestMessage === 'object' && Array.isArray(requestMessage.images)
12135
+ ? requestMessage.images.map(function(image) { return Object.assign({}, image); })
12136
+ : []
12137
+ };
12138
+ recordGuideUiMessage({
12139
+ clientMessageId: clientMessageId,
12140
+ target: receipt.target || lockedTarget,
12141
+ runId: receipt.runId || envelope.runId,
12142
+ status: 'deferred',
12143
+ content: displayText,
12144
+ attachments: optimisticAttachments,
12145
+ createdAt: receipt.createdAt || envelope.createdAt,
12146
+ updatedAt: receipt.updatedAt,
12147
+ reason: currentLang() === 'zh'
12148
+ ? '运行已结束,输入已作为新的 Build 继续。'
12149
+ : 'The run ended before Guide delivery; the input continued as a new Build.'
12150
+ }, lockedTarget);
12151
+ applyAgentWorkEventToRun({
12152
+ id: 'guide-' + clientMessageId + '-deferred',
12153
+ type: 'guide_deferred',
12154
+ content: displayText,
12155
+ workspaceId: lockedTarget.workspaceId,
12156
+ conversationId: lockedTarget.conversationId,
12157
+ runId: receipt.runId || envelope.runId,
12158
+ sequence: receipt.sequence || 0,
12159
+ guideStatus: 'deferred',
12160
+ clientMessageId: clientMessageId,
12161
+ guide: guideMessagesForTarget(lockedTarget)[clientMessageId]
12162
+ });
12163
+ return Promise.resolve(window.sendMessage('next', undefined, {
12164
+ requestedMode: requestedMode,
12165
+ goalObjective: opts.goalObjective,
12166
+ forceBuild: true,
12167
+ retryAfterGuideRejection: true,
12168
+ queuedRequest: fallbackRequest
12169
+ })).then(function(fallbackResult) {
12170
+ return {
12171
+ ok: !(fallbackResult && fallbackResult.ok === false),
12172
+ guideReceipt: receipt,
12173
+ recoveredAsBuild: true,
12174
+ fallback: fallbackResult
12175
+ };
12176
+ });
12177
+ }
12044
12178
  var guideRecord = recordGuideUiMessage({
12045
12179
  clientMessageId: clientMessageId,
12046
12180
  target: receipt.target || lockedTarget,
@@ -12683,6 +12817,7 @@ window.switchRightTab = function(tab) {
12683
12817
  if (tab === 'subagent') window.renderSubagentList();
12684
12818
  if (tab === 'browser') {
12685
12819
  cancelBrowserGuestIdleDestroy();
12820
+ window.refreshComputerUseState(currentConversationTarget());
12686
12821
  window.ensureBrowserPanel({ activate: true }).catch(function(error) {
12687
12822
  console.error('[Browser] guest initialization failed:', error && error.message ? error.message : String(error));
12688
12823
  });
@@ -13682,7 +13817,6 @@ function activateSubmittedGoal(goalObjective) {
13682
13817
  if (!objective) return '';
13683
13818
  state.goalText = objective;
13684
13819
  state.goalVisible = true;
13685
- state.goalPaused = false;
13686
13820
  window.renderInputStack();
13687
13821
  return objective;
13688
13822
  }
@@ -16850,11 +16984,63 @@ var NEWMARK_BROWSER_PARTITION = 'persist:newmark-browser';
16850
16984
  var NEWMARK_BROWSER_MIN_CREATE_DELAY_MS = 5000;
16851
16985
  var NEWMARK_BROWSER_IDLE_DESTROY_MS = 60000;
16852
16986
  var browserGuestCreatePromise = null;
16987
+ var browserGuestCreatePromises = {};
16853
16988
  var browserGuestIdleTimer = null;
16854
16989
  var browserRetainedUrl = 'about:blank';
16990
+ var browserRetainedUrls = {};
16855
16991
  var browserGuestCreateCount = 0;
16856
16992
  var browserGuestFirstCreatedAtMs = null;
16857
16993
 
16994
+ function computerUseTargetKey(target) {
16995
+ target = target || currentConversationTarget();
16996
+ return runtimeKeyFor(target.workspaceId, target.conversationId);
16997
+ }
16998
+
16999
+ function renderComputerUseToggle(value, target) {
17000
+ var button = document.getElementById('computer-use-toggle');
17001
+ if (!button) return;
17002
+ var enabled = value && value.enabled !== false;
17003
+ var occupied = !!(value && value.occupied);
17004
+ var owner = value && value.ownerLabel ? ' · ' + String(value.ownerLabel) : '';
17005
+ button.classList.toggle('active', enabled);
17006
+ button.setAttribute('aria-pressed', enabled ? 'true' : 'false');
17007
+ button.title = (enabled ? 'ComputerUse enabled for this conversation' : 'ComputerUse disabled for this conversation') + owner;
17008
+ button.textContent = enabled ? (occupied ? 'CU●' : 'CU') : 'CU×';
17009
+ button.dataset.computerUseRuntimeKey = computerUseTargetKey(target);
17010
+ }
17011
+
17012
+ window.refreshComputerUseState = function(target) {
17013
+ target = target || currentConversationTarget();
17014
+ var key = computerUseTargetKey(target);
17015
+ var cached = state.computerUseByTarget && state.computerUseByTarget[key];
17016
+ if (cached) renderComputerUseToggle(cached, target);
17017
+ if (!api.computerUseState) return Promise.resolve(cached || { enabled: true, occupied: false, runtimeKey: key });
17018
+ return api.computerUseState(target).then(function(value) {
17019
+ if (!state.computerUseByTarget) state.computerUseByTarget = {};
17020
+ state.computerUseByTarget[key] = value || { enabled: true, occupied: false, runtimeKey: key };
17021
+ if (isActiveConversationTarget(target)) renderComputerUseToggle(state.computerUseByTarget[key], target);
17022
+ return state.computerUseByTarget[key];
17023
+ }).catch(function() { return cached || { enabled: true, occupied: false, runtimeKey: key }; });
17024
+ };
17025
+
17026
+ window.toggleComputerUse = function() {
17027
+ var target = currentConversationTarget();
17028
+ var key = computerUseTargetKey(target);
17029
+ var current = state.computerUseByTarget && state.computerUseByTarget[key];
17030
+ var enabled = !(current && current.enabled !== false);
17031
+ if (!api.setComputerUseEnabled) return Promise.resolve(null);
17032
+ return api.setComputerUseEnabled(target, enabled).then(function(result) {
17033
+ if (result && result.ok === false) {
17034
+ showUiNotice(String(result.error || 'ComputerUse occupied'), 'error', 'computer-use-toggle-' + key);
17035
+ return result;
17036
+ }
17037
+ if (!state.computerUseByTarget) state.computerUseByTarget = {};
17038
+ state.computerUseByTarget[key] = result && result.state ? result.state : { enabled: enabled, occupied: false, runtimeKey: key };
17039
+ renderComputerUseToggle(state.computerUseByTarget[key], target);
17040
+ return result;
17041
+ });
17042
+ };
17043
+
16858
17044
  window.browserGuestLifecycleSnapshot = function() {
16859
17045
  return {
16860
17046
  createCount: browserGuestCreateCount,
@@ -16871,15 +17057,60 @@ function browserUrlsEquivalent(left, right) {
16871
17057
  return !!a && a === b;
16872
17058
  }
16873
17059
 
16874
- function currentBrowserGuest() {
16875
- var view = document.getElementById('browser-webview');
16876
- return view && view.isConnected ? view : null;
17060
+ function browserTargetKey(target) {
17061
+ target = target || currentConversationTarget();
17062
+ return runtimeKeyFor(target.workspaceId, target.conversationId);
16877
17063
  }
16878
17064
 
16879
- function rememberBrowserUrl(url, force) {
17065
+ function browserPartitionForTarget(target) {
17066
+ var key = browserTargetKey(target).replace(/[^A-Za-z0-9_.-]/g, '_').slice(0, 150) || 'default';
17067
+ return NEWMARK_BROWSER_PARTITION + '-' + key;
17068
+ }
17069
+
17070
+ function browserGuestForTarget(target) {
17071
+ var key = browserTargetKey(target);
17072
+ var frame = els['browser-frame'] || document.getElementById('browser-frame');
17073
+ if (!frame) return null;
17074
+ var views = frame.querySelectorAll('webview[data-newmark-browser-runtime-key]');
17075
+ for (var i = 0; i < views.length; i++) {
17076
+ if (String(views[i].getAttribute('data-newmark-browser-runtime-key') || '') === key && views[i].isConnected) return views[i];
17077
+ }
17078
+ return null;
17079
+ }
17080
+
17081
+ function setActiveBrowserGuest(view, target) {
17082
+ var key = browserTargetKey(target);
17083
+ var frame = els['browser-frame'] || document.getElementById('browser-frame');
17084
+ if (frame) {
17085
+ var views = frame.querySelectorAll('webview[data-newmark-browser-runtime-key]');
17086
+ for (var i = 0; i < views.length; i++) {
17087
+ var same = views[i] === view || String(views[i].getAttribute('data-newmark-browser-runtime-key') || '') === key;
17088
+ views[i].style.display = same ? '' : 'none';
17089
+ if (same) views[i].id = 'browser-webview';
17090
+ else if (views[i].id === 'browser-webview') views[i].id = 'browser-webview-' + String(views[i].getAttribute('data-newmark-browser-runtime-key') || 'guest').replace(/[^A-Za-z0-9_.-]/g, '_').slice(-80);
17091
+ }
17092
+ }
17093
+ els['browser-webview'] = view || null;
17094
+ if (view) view._newmarkBrowserTargetKey = key;
17095
+ }
17096
+
17097
+ function currentBrowserGuest(target) {
17098
+ var view = browserGuestForTarget(target || currentConversationTarget());
17099
+ if (view) return view;
17100
+ var active = document.getElementById('browser-webview');
17101
+ return active && active.isConnected ? active : null;
17102
+ }
17103
+
17104
+ function rememberBrowserUrl(url, force, target) {
17105
+ target = target || currentConversationTarget();
17106
+ var targetKey = browserTargetKey(target);
16880
17107
  url = String(url || '').trim();
16881
- if (!force && url === 'about:blank' && browserRetainedUrl !== 'about:blank') return;
16882
- if (url) browserRetainedUrl = url;
17108
+ var prior = browserRetainedUrls[targetKey] || 'about:blank';
17109
+ if (!force && url === 'about:blank' && prior !== 'about:blank') return;
17110
+ if (url) {
17111
+ browserRetainedUrls[targetKey] = url;
17112
+ if (isActiveConversationTarget(target)) browserRetainedUrl = url;
17113
+ }
16883
17114
  }
16884
17115
 
16885
17116
  function waitForBrowserCreationFloor() {
@@ -16888,8 +17119,9 @@ function waitForBrowserCreationFloor() {
16888
17119
  return new Promise(function(resolve) { setTimeout(resolve, remaining); });
16889
17120
  }
16890
17121
 
16891
- function createBrowserGuestElement() {
16892
- var existing = currentBrowserGuest();
17122
+ function createBrowserGuestElement(target) {
17123
+ target = target || currentConversationTarget();
17124
+ var existing = browserGuestForTarget(target);
16893
17125
  if (existing) return existing;
16894
17126
  var frame = els['browser-frame'] || document.getElementById('browser-frame');
16895
17127
  if (!frame) throw new Error('Built-in Browser frame is unavailable');
@@ -16899,8 +17131,14 @@ function createBrowserGuestElement() {
16899
17131
  browserGuestCreateCount += 1;
16900
17132
  if (browserGuestFirstCreatedAtMs === null) browserGuestFirstCreatedAtMs = Date.now() - newmarkRendererStartedAt;
16901
17133
  view.id = 'browser-webview';
16902
- view.setAttribute('partition', NEWMARK_BROWSER_PARTITION);
17134
+ view.setAttribute('data-newmark-browser-runtime-key', browserTargetKey(target));
17135
+ view.setAttribute('partition', browserPartitionForTarget(target));
16903
17136
  frame.appendChild(view);
17137
+ if (isActiveConversationTarget(target)) setActiveBrowserGuest(view, target);
17138
+ else {
17139
+ view.style.display = 'none';
17140
+ view.id = 'browser-webview-' + browserTargetKey(target).replace(/[^A-Za-z0-9_.-]/g, '_').slice(-80);
17141
+ }
16904
17142
  els['browser-webview'] = view;
16905
17143
  return view;
16906
17144
  }
@@ -16923,11 +17161,14 @@ function cancelBrowserGuestIdleDestroy() {
16923
17161
  function destroyIdleBrowserGuest() {
16924
17162
  browserGuestIdleTimer = null;
16925
17163
  if (state.rightTab === 'browser') return;
16926
- var view = currentBrowserGuest();
17164
+ var view = currentBrowserGuest(currentConversationTarget());
16927
17165
  if (!view) return;
16928
- try { rememberBrowserUrl(view.getURL ? view.getURL() : ''); } catch {}
17166
+ var target = { workspaceId: state.currentWorkspaceId || state.currentWorkspacePath || state.currentWorkspace || 'none', conversationId: activeConversationId() || 'default' };
17167
+ try { rememberBrowserUrl(view.getURL ? view.getURL() : '', false, target); } catch {}
16929
17168
  if (view.parentNode) view.parentNode.removeChild(view);
16930
17169
  if (els['browser-webview'] === view) els['browser-webview'] = null;
17170
+ var key = browserTargetKey(target);
17171
+ delete browserGuestCreatePromises[key];
16931
17172
  browserGuestCreatePromise = null;
16932
17173
  restoreBrowserPlaceholder();
16933
17174
  }
@@ -16959,16 +17200,17 @@ function navigateBrowserView(view, url) {
16959
17200
  }
16960
17201
 
16961
17202
  window.navigateBrowser = function(url) {
17203
+ var target = currentConversationTarget();
16962
17204
  url = String(url || '').trim();
16963
17205
  if (!/^(?:https?:\/\/|about:blank$|newmark-preview:\/\/)/i.test(url)) {
16964
17206
  url = 'https://' + url;
16965
17207
  }
16966
17208
  if (state.browserPreviewToken && url !== state.browserPreviewUrl) window.revokeBrowserPreviewCapability();
16967
- rememberBrowserUrl(url, true);
17209
+ rememberBrowserUrl(url, true, target);
16968
17210
  var input = els['browser-url'];
16969
17211
  if (input) input.value = url;
16970
17212
  cancelBrowserGuestIdleDestroy();
16971
- return window.ensureBrowserPanel({ activate: false }).then(function(view) {
17213
+ return window.ensureBrowserPanel({ activate: false, target: target }).then(function(view) {
16972
17214
  navigateBrowserView(view, url);
16973
17215
  if (state.rightTab !== 'browser') scheduleBrowserGuestIdleDestroy();
16974
17216
  return view;
@@ -16997,6 +17239,8 @@ window.browserReload = function() {
16997
17239
  };
16998
17240
 
16999
17241
  function initializeBrowserGuest(view) {
17242
+ var target = arguments.length > 1 && arguments[1] ? arguments[1] : currentConversationTarget();
17243
+ var targetKey = browserTargetKey(target);
17000
17244
  if (!view._newmarkReadyPromise) {
17001
17245
  view._newmarkDomReady = false;
17002
17246
  view._newmarkReadySettled = false;
@@ -17012,12 +17256,12 @@ function initializeBrowserGuest(view) {
17012
17256
  currentUrl = view.getURL ? view.getURL() : '';
17013
17257
  if (input && currentUrl && currentUrl !== 'about:blank') input.value = currentUrl;
17014
17258
  } catch {}
17015
- rememberBrowserUrl(currentUrl);
17259
+ rememberBrowserUrl(currentUrl, false, target);
17016
17260
  if (view._newmarkReadySettled) return;
17017
17261
  var registration = Promise.resolve({ accepted: true });
17018
17262
  try {
17019
17263
  if (!api.browserRegisterGuest || !view.getWebContentsId) throw new Error('Built-in Browser registration API is unavailable');
17020
- registration = api.browserRegisterGuest(view.getWebContentsId());
17264
+ registration = api.browserRegisterGuest(view.getWebContentsId(), target);
17021
17265
  } catch (registrationError) {
17022
17266
  registration = Promise.reject(registrationError);
17023
17267
  }
@@ -17057,13 +17301,13 @@ function initializeBrowserGuest(view) {
17057
17301
  view.addEventListener('did-navigate', function(e) {
17058
17302
  var input = els['browser-url'];
17059
17303
  if (input && e.url && e.url !== 'about:blank') input.value = e.url;
17060
- rememberBrowserUrl(e.url);
17304
+ rememberBrowserUrl(e.url, false, target);
17061
17305
  if (browserUrlsEquivalent(e.url, view._newmarkPendingUrl)) view._newmarkPendingUrl = '';
17062
17306
  });
17063
17307
  view.addEventListener('did-navigate-in-page', function(e) {
17064
17308
  var input = els['browser-url'];
17065
17309
  if (input && e.url && e.url !== 'about:blank') input.value = e.url;
17066
- rememberBrowserUrl(e.url);
17310
+ rememberBrowserUrl(e.url, false, target);
17067
17311
  if (browserUrlsEquivalent(e.url, view._newmarkPendingUrl)) view._newmarkPendingUrl = '';
17068
17312
  });
17069
17313
  view.addEventListener('did-fail-load', function(e) {
@@ -17077,27 +17321,31 @@ function initializeBrowserGuest(view) {
17077
17321
  });
17078
17322
  var current = '';
17079
17323
  try { current = view.getURL ? view.getURL() : ''; } catch { current = ''; }
17080
- if (!current && !view.getAttribute('src')) view.setAttribute('src', 'about:blank');
17324
+ if (!current && !view.getAttribute('src')) view.setAttribute('src', browserRetainedUrls[targetKey] || 'about:blank');
17081
17325
  }
17082
17326
  return view._newmarkReadyPromise;
17083
17327
  }
17084
17328
 
17085
17329
  window.ensureBrowserPanel = function(options) {
17086
17330
  options = options || {};
17331
+ var target = options.target || currentConversationTarget();
17332
+ var targetKey = browserTargetKey(target);
17087
17333
  cancelBrowserGuestIdleDestroy();
17088
- if (!browserGuestCreatePromise) {
17089
- browserGuestCreatePromise = waitForBrowserCreationFloor().then(function() {
17090
- return initializeBrowserGuest(createBrowserGuestElement());
17334
+ if (!browserGuestCreatePromises[targetKey]) {
17335
+ browserGuestCreatePromises[targetKey] = waitForBrowserCreationFloor().then(function() {
17336
+ return initializeBrowserGuest(createBrowserGuestElement(target), target);
17091
17337
  }).catch(function(error) {
17092
- browserGuestCreatePromise = null;
17338
+ delete browserGuestCreatePromises[targetKey];
17093
17339
  throw error;
17094
17340
  });
17095
17341
  }
17096
- return browserGuestCreatePromise.then(function(view) {
17342
+ browserGuestCreatePromise = browserGuestCreatePromises[targetKey];
17343
+ return browserGuestCreatePromises[targetKey].then(function(view) {
17344
+ if (isActiveConversationTarget(target)) setActiveBrowserGuest(view, target);
17097
17345
  if (options.activate === true) {
17098
17346
  var current = '';
17099
17347
  try { current = view.getURL ? view.getURL() : ''; } catch { current = ''; }
17100
- var targetUrl = browserRetainedUrl || 'about:blank';
17348
+ var targetUrl = browserRetainedUrls[targetKey] || 'about:blank';
17101
17349
  if (targetUrl !== 'about:blank' && (!current || current === 'about:blank') && !view._newmarkPendingUrl) {
17102
17350
  navigateBrowserView(view, targetUrl);
17103
17351
  }
@@ -17480,6 +17728,13 @@ window.switchConversation = function(idx) {
17480
17728
  if (previousId) markConversationTracked(previousId, state.conversationTrackMs || 300000);
17481
17729
  setActiveWorkspaceConversation(idx);
17482
17730
  var activeId = activeConversationId();
17731
+ var activeBrowserTarget = currentConversationTarget(activeId);
17732
+ var activeBrowserView = browserGuestForTarget(activeBrowserTarget);
17733
+ if (activeBrowserView) setActiveBrowserGuest(activeBrowserView, activeBrowserTarget);
17734
+ else if (state.rightTab === 'browser') window.ensureBrowserPanel({ target: activeBrowserTarget, activate: true }).catch(function(error) {
17735
+ console.error('[Browser] conversation-bound guest initialization failed:', error && error.message ? error.message : String(error));
17736
+ });
17737
+ window.refreshComputerUseState(activeBrowserTarget);
17483
17738
  state.backendQueue = backendQueueForTarget(currentConversationTarget(activeId));
17484
17739
  holdForegroundConversation(activeId, 4500);
17485
17740
  markConversationTracked(activeId, state.conversationTrackMs || 300000);
@@ -19681,9 +19936,10 @@ function schedulePostStartupUiRendering() {
19681
19936
  cacheEls();
19682
19937
  setupAgentWorkEvents();
19683
19938
  if (api.onBrowserEnsureGuest) {
19684
- api.onBrowserEnsureGuest(function() {
19939
+ api.onBrowserEnsureGuest(function(target) {
19685
19940
  cancelBrowserGuestIdleDestroy();
19686
- window.ensureBrowserPanel({ activate: false }).catch(function(error) {
19941
+ var requestedTarget = target || currentConversationTarget();
19942
+ window.ensureBrowserPanel({ target: requestedTarget, activate: isActiveConversationTarget(requestedTarget) }).catch(function(error) {
19687
19943
  console.error('[Browser] guest initialization failed:', error && error.message ? error.message : String(error));
19688
19944
  });
19689
19945
  });