newmark-agent 0.3.10 → 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.
package/dist/main.js CHANGED
@@ -73,6 +73,7 @@ const runtimeShutdown_1 = require("./core/runtimeShutdown");
73
73
  const runtimeLifecycle_1 = require("./core/runtimeLifecycle");
74
74
  const compat_1 = require("./core/compat");
75
75
  const mcpManager_1 = require("./core/mcpManager");
76
+ const cli_help_1 = require("./cli-help");
76
77
  const APP_NAME = 'Newmark Agent';
77
78
  const APP_ID = 'ai.newmark.agent';
78
79
  const CONFIG_RELOAD_RUNTIME_TIMEOUT_MS = 6_000;
@@ -116,7 +117,27 @@ let _forceQuit = false;
116
117
  let forcedExitTimer = null;
117
118
  let electronBrowserUseHost = null;
118
119
  let browserUseEngine = null;
120
+ // A single renderer can host several hidden conversation-bound guests. The
121
+ // legacy host map is retained for focused-window discovery, while this map is
122
+ // the authoritative Browser-Use/right-sidebar binding.
119
123
  const browserGuestContentsByHost = new Map();
124
+ const browserGuestBindingsByRuntime = new Map();
125
+ function browserGuestRuntimeKey(target) {
126
+ return (0, conversationTarget_1.normalizeConversationTarget)(target).runtimeKey;
127
+ }
128
+ function browserGuestBindingFor(runtimeKey) {
129
+ const binding = browserGuestBindingsByRuntime.get(String(runtimeKey || ''));
130
+ if (!binding)
131
+ return null;
132
+ const guest = electron_1.webContents.fromId(binding.guestId);
133
+ if (!guest || guest.isDestroyed() || guest.getType() !== 'webview' || guest.hostWebContents?.id !== binding.hostId) {
134
+ browserGuestBindingsByRuntime.delete(String(runtimeKey || ''));
135
+ if (browserGuestContentsByHost.get(binding.hostId) === binding.guestId)
136
+ browserGuestContentsByHost.delete(binding.hostId);
137
+ return null;
138
+ }
139
+ return binding;
140
+ }
120
141
  let workspaceSwitchGeneration = 0;
121
142
  let workspaceSelectionCoordinator = null;
122
143
  function defaultTerminalShell() {
@@ -737,7 +758,15 @@ async function waitForWebContentsLoad(contents, timeoutMs = 15000) {
737
758
  contents.once('did-fail-load', finish);
738
759
  });
739
760
  }
740
- function registeredBrowserGuest(hostContentsId) {
761
+ function registeredBrowserGuest(hostContentsId, runtimeKey) {
762
+ if (runtimeKey) {
763
+ const binding = browserGuestBindingFor(runtimeKey);
764
+ if (binding && (!hostContentsId || binding.hostId === hostContentsId)) {
765
+ const guest = electron_1.webContents.fromId(binding.guestId);
766
+ if (guest && !guest.isDestroyed())
767
+ return guest;
768
+ }
769
+ }
741
770
  const hostIds = hostContentsId
742
771
  ? [hostContentsId]
743
772
  : [
@@ -752,8 +781,14 @@ function registeredBrowserGuest(hostContentsId) {
752
781
  if (!guestId)
753
782
  continue;
754
783
  const guest = electron_1.webContents.fromId(guestId);
755
- if (guest && !guest.isDestroyed() && guest.getType() === 'webview' && guest.hostWebContents?.id === hostId)
784
+ if (guest && !guest.isDestroyed() && guest.getType() === 'webview' && guest.hostWebContents?.id === hostId) {
785
+ if (runtimeKey) {
786
+ const bound = browserGuestBindingsByRuntime.get(runtimeKey);
787
+ if (!bound || bound.hostId !== hostId || bound.guestId !== guest.id)
788
+ continue;
789
+ }
756
790
  return guest;
791
+ }
757
792
  browserGuestContentsByHost.delete(hostId);
758
793
  }
759
794
  return null;
@@ -774,37 +809,64 @@ async function boundedOperation(operation, timeoutMs, label) {
774
809
  clearTimeout(timer);
775
810
  }
776
811
  }
777
- function registerBrowserGuest(host, guest) {
812
+ function registerBrowserGuest(host, guest, requestedTarget) {
778
813
  if (host.isDestroyed() || guest.isDestroyed() || guest.getType() !== 'webview' || guest.hostWebContents?.id !== host.id)
779
814
  return false;
815
+ let target;
816
+ try {
817
+ target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(requestedTarget));
818
+ }
819
+ catch {
820
+ return false;
821
+ }
822
+ const prior = browserGuestBindingsByRuntime.get(target.runtimeKey);
823
+ if (prior && prior.guestId !== guest.id) {
824
+ const priorGuest = electron_1.webContents.fromId(prior.guestId);
825
+ if (priorGuest && !priorGuest.isDestroyed())
826
+ priorGuest.hostWebContents?.send('browser:guestVisibility', { runtimeKey: target.runtimeKey, visible: false });
827
+ }
828
+ browserGuestBindingsByRuntime.set(target.runtimeKey, {
829
+ hostId: host.id,
830
+ guestId: guest.id,
831
+ workspaceId: target.workspaceId,
832
+ conversationId: target.conversationId,
833
+ });
780
834
  browserGuestContentsByHost.set(host.id, guest.id);
781
835
  guest.once('destroyed', () => {
782
836
  if (browserGuestContentsByHost.get(host.id) === guest.id)
783
837
  browserGuestContentsByHost.delete(host.id);
838
+ const binding = browserGuestBindingsByRuntime.get(target.runtimeKey);
839
+ if (binding?.guestId === guest.id)
840
+ browserGuestBindingsByRuntime.delete(target.runtimeKey);
784
841
  });
785
842
  ensureElectronBrowserUseHost().attach(guest);
786
843
  return true;
787
844
  }
788
- async function waitForRegisteredBrowserGuest(host, timeoutMs = 12_000) {
845
+ async function waitForRegisteredBrowserGuest(host, runtimeKey, timeoutMs = 12_000) {
789
846
  const deadline = Date.now() + Math.max(250, timeoutMs);
790
847
  while (!host.isDestroyed() && Date.now() < deadline) {
791
- const guest = registeredBrowserGuest(host.id);
848
+ const guest = registeredBrowserGuest(host.id, runtimeKey);
792
849
  if (guest)
793
850
  return guest;
794
851
  await new Promise(resolve => setTimeout(resolve, 25));
795
852
  }
796
853
  throw new Error('Built-in Browser guest did not become ready before the Browser-Use timeout');
797
854
  }
798
- async function ensureBrowserWebContents(boundContentsId) {
855
+ async function ensureBrowserWebContents(boundContentsId, runtimeKey) {
799
856
  if (boundContentsId) {
800
857
  const bound = electron_1.webContents.fromId(boundContentsId);
801
858
  if (bound && !bound.isDestroyed() && bound.getType() === 'webview'
802
859
  && browserGuestContentsByHost.get(bound.hostWebContents?.id || 0) === bound.id) {
860
+ if (runtimeKey) {
861
+ const binding = browserGuestBindingsByRuntime.get(runtimeKey);
862
+ if (!binding || binding.guestId !== bound.id)
863
+ return await ensureBrowserWebContents(undefined, runtimeKey);
864
+ }
803
865
  bound.hostWebContents?.send('browser:ensureGuest');
804
866
  return bound;
805
867
  }
806
868
  }
807
- const registered = registeredBrowserGuest();
869
+ const registered = registeredBrowserGuest(undefined, runtimeKey);
808
870
  if (registered) {
809
871
  registered.hostWebContents?.send('browser:ensureGuest');
810
872
  return registered;
@@ -814,13 +876,13 @@ async function ensureBrowserWebContents(boundContentsId) {
814
876
  const host = hostWindow?.webContents;
815
877
  if (!host || host.isDestroyed())
816
878
  throw new Error('Built-in Browser UI is unavailable');
817
- host.send('browser:ensureGuest');
818
- return await waitForRegisteredBrowserGuest(host);
879
+ host.send('browser:ensureGuest', runtimeKey ? { runtimeKey } : undefined);
880
+ return await waitForRegisteredBrowserGuest(host, runtimeKey, 12_000);
819
881
  }
820
882
  function ensureElectronBrowserUseHost() {
821
883
  if (!electronBrowserUseHost) {
822
884
  electronBrowserUseHost = new electronBrowserUseHost_1.ElectronBrowserUseHost({
823
- resolveContents: async (_scope, boundContentsId) => await ensureBrowserWebContents(boundContentsId),
885
+ resolveContents: async (scope, boundContentsId) => await ensureBrowserWebContents(boundContentsId, scope.runtimeKey),
824
886
  openExternal: async (url) => { await electron_1.shell.openExternal(url); },
825
887
  });
826
888
  }
@@ -833,10 +895,18 @@ function ensureBrowserUseEngine() {
833
895
  }
834
896
  return browserUseEngine;
835
897
  }
836
- function currentBrowserUseContext() {
837
- const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget());
898
+ function currentBrowserUseContext(requestedTarget) {
899
+ const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(requestedTarget));
838
900
  return { runtimeKey: target.runtimeKey, actorId: agent?.runtimeActorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID };
839
901
  }
902
+ function browserControlTargetInput(input) {
903
+ if (!input)
904
+ return undefined;
905
+ return {
906
+ workspaceId: String(input.workspaceId || ''),
907
+ conversationId: String(input.conversationId || 'default'),
908
+ };
909
+ }
840
910
  async function runBoundBrowserUse(input, context, signal) {
841
911
  return await ensureBrowserUseEngine().run((0, browserUse_1.bindBrowserUseRequest)(input, context), signal);
842
912
  }
@@ -856,8 +926,10 @@ function browserSnapshotScript(maxChars) {
856
926
  }
857
927
  async function runBrowserControl(request) {
858
928
  const action = request.action;
929
+ const requestedTarget = browserControlTargetInput(request.target);
930
+ const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(requestedTarget));
859
931
  if (action === 'use') {
860
- const receipt = await runBoundBrowserUse(request.browserUse, currentBrowserUseContext());
932
+ const receipt = await runBoundBrowserUse(request.browserUse, currentBrowserUseContext(requestedTarget));
861
933
  return {
862
934
  ok: receipt.ok,
863
935
  action,
@@ -868,7 +940,7 @@ async function runBrowserControl(request) {
868
940
  error: receipt.error,
869
941
  };
870
942
  }
871
- const contents = await ensureBrowserWebContents();
943
+ const contents = await ensureBrowserWebContents(undefined, target.runtimeKey);
872
944
  try {
873
945
  if (action === 'open') {
874
946
  await contents.loadURL(request.url || 'about:blank');
@@ -953,12 +1025,25 @@ function installBrowserControlBackend() {
953
1025
  const args = userArgs();
954
1026
  const command = args.find(a => a === 'flow' || a === 'edit');
955
1027
  const isTuiArg = args.some(arg => arg.toLowerCase() === '--tui');
1028
+ const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
1029
+ const isHelpArg = !hasCliCommand && (args.some(arg => ['--help', '-h'].includes(arg.toLowerCase())) || args[0]?.toLowerCase() === 'help');
1030
+ const isVersionArg = !hasCliCommand && args.some(arg => ['--version', '-v'].includes(arg.toLowerCase()));
956
1031
  const isViewerArg = args.some(arg => arg.toLowerCase() === '--newmark-viewer');
957
1032
  const isCliArg = args.includes('--cli');
958
1033
  const isServerArg = args.includes('--server');
959
1034
  const isFlowArg = command === 'flow';
960
1035
  const isEditArg = command === 'edit';
961
- const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
1036
+ // Help/version are terminating discovery commands. They must be handled
1037
+ // before Electron's GUI/TUI/server branches can initialize runtime state or
1038
+ // spawn a window, so a product-new tester can use them safely in any surface.
1039
+ if (isHelpArg) {
1040
+ console.log((0, cli_help_1.newmarkHelpText)((0, installUpdate_1.currentAppVersion)()));
1041
+ process.exit(0);
1042
+ }
1043
+ if (isVersionArg) {
1044
+ console.log((0, installUpdate_1.currentAppVersion)());
1045
+ process.exit(0);
1046
+ }
962
1047
  function viewerEscape(value) {
963
1048
  return String(value || '').replace(/[&<>"']/g, character => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[character] || character);
964
1049
  }
@@ -1513,7 +1598,12 @@ else {
1513
1598
  rejectUiReadinessById(Number(fileRouterOwnerId), new Error('Startup window closed before UI readiness'));
1514
1599
  fileRouter.revokeOwner(fileRouterOwnerId);
1515
1600
  pdfPreviewServer.revokeOwner(fileRouterOwnerId);
1516
- browserGuestContentsByHost.delete(Number(fileRouterOwnerId));
1601
+ const closedHostId = Number(fileRouterOwnerId);
1602
+ browserGuestContentsByHost.delete(closedHostId);
1603
+ for (const [runtimeKey, binding] of browserGuestBindingsByRuntime) {
1604
+ if (binding.hostId === closedHostId)
1605
+ browserGuestBindingsByRuntime.delete(runtimeKey);
1606
+ }
1517
1607
  const remaining = electron_1.BrowserWindow.getAllWindows().filter(candidate => !candidate.isDestroyed() && candidate !== win);
1518
1608
  if (mainWindow === win)
1519
1609
  mainWindow = remaining[0] || null;
@@ -2179,6 +2269,7 @@ else {
2179
2269
  electronBrowserUseHost = null;
2180
2270
  browserControl_1.BrowserControl.setBackend(null);
2181
2271
  browserGuestContentsByHost.clear();
2272
+ browserGuestBindingsByRuntime.clear();
2182
2273
  if (sidecarProcess) {
2183
2274
  sidecarProcess.kill();
2184
2275
  sidecarProcess = null;
@@ -2569,9 +2660,9 @@ else {
2569
2660
  }
2570
2661
  }
2571
2662
  });
2572
- electron_1.ipcMain.handle('browser:registerGuest', (event, guestContentsId) => {
2663
+ electron_1.ipcMain.handle('browser:registerGuest', (event, guestContentsId, target) => {
2573
2664
  const guest = electron_1.webContents.fromId(Number(guestContentsId || 0));
2574
- return { accepted: !!guest && registerBrowserGuest(event.sender, guest) };
2665
+ return { accepted: !!guest && registerBrowserGuest(event.sender, guest, target) };
2575
2666
  });
2576
2667
  electron_1.ipcMain.handle('browser:control', async (_event, request) => {
2577
2668
  return await browserControl_1.BrowserControl.run(request);
@@ -2955,6 +3046,28 @@ else {
2955
3046
  runtimeDeferred: false,
2956
3047
  };
2957
3048
  });
3049
+ electron_1.ipcMain.handle('agent:computerUseState', async (_event, targetInput) => {
3050
+ if (!agent)
3051
+ return { enabled: false, occupied: false, runtimeKey: '' };
3052
+ const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default'));
3053
+ return utilityHostToolHandler.computerUseState(target.runtimeKey);
3054
+ });
3055
+ electron_1.ipcMain.handle('agent:setComputerUseEnabled', async (_event, targetInput, enabled) => {
3056
+ if (!agent)
3057
+ return { ok: false, error: 'Agent not initialized' };
3058
+ const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default'));
3059
+ const result = utilityHostToolHandler.setComputerUseEnabled(target.runtimeKey, enabled !== false, `conversation:${target.conversationId}`);
3060
+ if (!result.ok)
3061
+ return result;
3062
+ if (enabled === false) {
3063
+ try {
3064
+ const { runComputerUse } = require('./tools/computerUse');
3065
+ await runComputerUse({ action: 'takeover_stop', workspacePath: target.workspace?.path || root, ownerId: target.runtimeKey, invocation: 'agent' });
3066
+ }
3067
+ catch { }
3068
+ }
3069
+ return result;
3070
+ });
2958
3071
  electron_1.ipcMain.handle('agent:updateGoal', async (_event, goal, targetInput) => {
2959
3072
  if (!agent)
2960
3073
  return null;
@@ -3057,6 +3170,7 @@ else {
3057
3170
  pendingOptions: conversationSnapshot.pendingOptions || agent.pendingOptions,
3058
3171
  flowSuspension: flowSuspensionForTarget(target),
3059
3172
  flowRunning: flowRunningForTarget(target),
3173
+ computerUse: utilityHostToolHandler.computerUseState((0, conversationTarget_1.normalizeConversationTarget)(target).runtimeKey),
3060
3174
  draft: agent.getStoredConversationDraft(target.conversationId) || '',
3061
3175
  proxyEnabled: agent.config.getBool('proxy', 'enabled'),
3062
3176
  proxyUrl: agent.config.getStr('proxy', 'url'),
package/dist/preload.js CHANGED
@@ -34,11 +34,13 @@ contextBridge.exposeInMainWorld('api', {
34
34
  setConversationPinned: (id, pinned) => ipcRenderer.invoke('agent:setConversationPinned', id, pinned),
35
35
  renameConversation: (id, title) => ipcRenderer.invoke('agent:renameConversation', id, title),
36
36
  reorderConversations: (ids) => ipcRenderer.invoke('agent:reorderConversations', ids),
37
- browserRegisterGuest: (guestContentsId) => ipcRenderer.invoke('browser:registerGuest', guestContentsId),
37
+ browserRegisterGuest: (guestContentsId, target) => ipcRenderer.invoke('browser:registerGuest', guestContentsId, target),
38
38
  onBrowserEnsureGuest: (callback) => {
39
- ipcRenderer.on('browser:ensureGuest', () => callback());
39
+ ipcRenderer.on('browser:ensureGuest', (_event, target) => callback(target));
40
40
  },
41
41
  browserControl: (request) => ipcRenderer.invoke('browser:control', request),
42
+ computerUseState: (target) => ipcRenderer.invoke('agent:computerUseState', target),
43
+ setComputerUseEnabled: (target, enabled) => ipcRenderer.invoke('agent:setComputerUseEnabled', target, enabled),
42
44
  runFlow: (name, input, start) => ipcRenderer.invoke('flow:run', name, input, start),
43
45
  resumeFlow: (response, target) => ipcRenderer.invoke('flow:resume', response, target),
44
46
  guideFlow: (message, target) => ipcRenderer.invoke('flow:guide', message, target),
@@ -57,8 +57,7 @@ const nativeBash_1 = require("../core/nativeBash");
57
57
  const toolArgumentValidator_1 = require("../core/toolArgumentValidator");
58
58
  const localOcr_1 = require("../core/localOcr");
59
59
  const visualTextFallback_1 = require("../core/visualTextFallback");
60
- let computerUseLock = null;
61
- const COMPUTER_USE_LOCK_TTL_MS = 10 * 60 * 1000;
60
+ const computerUseSession_1 = require("../core/computerUseSession");
62
61
  function normalizeComputerUseAction(action) {
63
62
  return String(action || '').trim().toLowerCase();
64
63
  }
@@ -154,50 +153,28 @@ async function abortableToolDelay(durationMs, signal) {
154
153
  abort();
155
154
  });
156
155
  }
157
- function clearStaleComputerUseLock(now = Date.now()) {
158
- if (computerUseLock && now - computerUseLock.updatedAt > COMPUTER_USE_LOCK_TTL_MS) {
159
- computerUseLock = null;
160
- }
161
- }
162
- function computerUseLockError(action, owner) {
163
- return JSON.stringify({
164
- ok: false,
165
- action,
166
- error: `ComputerUse is already active in ${computerUseLock?.owner || 'another conversation'}. Stop it with computer_use takeover_stop or wait before using ComputerUse from another conversation.`,
167
- lock_owner: computerUseLock?.owner || '',
168
- requested_owner: owner,
169
- }, null, 2);
170
- }
171
- function acquireComputerUseLock(action, owner, wsPath) {
172
- const now = Date.now();
173
- clearStaleComputerUseLock(now);
174
- if (computerUseLock && computerUseLock.owner !== owner) {
175
- return computerUseLockError(action, owner);
176
- }
177
- computerUseLock = {
178
- owner,
156
+ function computerUseSessionScope(context, wsPath, owner) {
157
+ return {
158
+ runtimeKey: browserUseScope(context, wsPath).runtimeKey,
159
+ ownerLabel: owner,
179
160
  workspacePath: path.resolve(wsPath || process.cwd()),
180
- acquiredAt: computerUseLock?.owner === owner ? computerUseLock.acquiredAt : now,
181
- updatedAt: now,
182
161
  };
183
- return null;
184
162
  }
185
- function releaseComputerUseLock(action, owner) {
186
- clearStaleComputerUseLock();
187
- if (computerUseLock && computerUseLock.owner !== owner) {
188
- return computerUseLockError(action, owner);
189
- }
190
- if (computerUseLock?.owner === owner)
191
- computerUseLock = null;
192
- return null;
163
+ function acquireComputerUseLock(action, owner, wsPath, context = {}, dryRun = false) {
164
+ return computerUseSession_1.defaultComputerUseSessionRegistry.authorize(action, computerUseSessionScope(context, wsPath, owner), dryRun);
193
165
  }
194
- function assertComputerUseLockOwner(action, owner) {
195
- clearStaleComputerUseLock();
196
- if (computerUseLock && computerUseLock.owner !== owner) {
197
- return computerUseLockError(action, owner);
198
- }
166
+ function releaseComputerUseLock(action, owner, context = {}, wsPath = context.workspacePath || '') {
167
+ const scope = computerUseSessionScope(context, wsPath, owner);
168
+ computerUseSession_1.defaultComputerUseSessionRegistry.complete(action, scope);
199
169
  return null;
200
170
  }
171
+ function assertComputerUseLockOwner(action, owner, context = {}, wsPath = context.workspacePath || '') {
172
+ return computerUseSession_1.defaultComputerUseSessionRegistry.authorize(action, computerUseSessionScope(context, wsPath, owner));
173
+ }
174
+ // A competing conversation receives the stable user-facing marker
175
+ // "ComputerUse is already active" / "computerUse occupied".
176
+ // The two-argument releaseComputerUseLock(action, owner) form remains valid
177
+ // for direct callers; routed Build calls additionally provide their target.
201
178
  class ToolExecutor {
202
179
  config;
203
180
  ssh;
@@ -754,8 +731,8 @@ class ToolExecutor {
754
731
  const action = normalizeComputerUseAction(g('action'));
755
732
  const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || 'root')}`;
756
733
  const lockGuard = action === 'takeover_stop'
757
- ? assertComputerUseLockOwner(action, owner)
758
- : acquireComputerUseLock(action, owner, wsPath);
734
+ ? assertComputerUseLockOwner(action, owner, context, wsPath)
735
+ : acquireComputerUseLock(action, owner, wsPath, context, args.dry_run === true);
759
736
  if (lockGuard)
760
737
  return lockGuard;
761
738
  if (process.env.NEWMARK_WSL_DISTRO) {
@@ -772,7 +749,7 @@ class ToolExecutor {
772
749
  }
773
750
  finally {
774
751
  if (action === 'takeover_stop')
775
- releaseComputerUseLock(action, owner);
752
+ releaseComputerUseLock(action, owner, context, wsPath);
776
753
  }
777
754
  }
778
755
  if (process.env.NEWMARK_ISOLATED_RUNTIME === '1') {
@@ -794,7 +771,7 @@ class ToolExecutor {
794
771
  }
795
772
  finally {
796
773
  if (action === 'takeover_stop')
797
- releaseComputerUseLock(action, owner);
774
+ releaseComputerUseLock(action, owner, context, wsPath);
798
775
  }
799
776
  }
800
777
  const output = await (0, computerUse_1.runComputerUse)({
@@ -838,7 +815,7 @@ class ToolExecutor {
838
815
  : undefined,
839
816
  });
840
817
  if (action === 'takeover_stop')
841
- releaseComputerUseLock(action, owner);
818
+ releaseComputerUseLock(action, owner, context, wsPath);
842
819
  return output;
843
820
  }
844
821
  case 'terminal_takeover': {
@@ -1352,9 +1329,17 @@ class ToolExecutor {
1352
1329
  }
1353
1330
  }
1354
1331
  async browserRun(request, signal, context = {}, workspacePath = this.root) {
1332
+ const scope = browserUseScope(context, workspacePath);
1333
+ const scopedRequest = {
1334
+ ...request,
1335
+ target: {
1336
+ workspaceId: context.workspaceId || (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(workspacePath),
1337
+ conversationId: context.conversationId || 'default',
1338
+ runtimeKey: scope.runtimeKey,
1339
+ },
1340
+ };
1355
1341
  if (process.env.NEWMARK_WSL_DISTRO) {
1356
- const scope = browserUseScope(context, workspacePath);
1357
- const result = await (0, wslHostToolBridge_1.requestWindowsHostTool)('browser_control', request, {
1342
+ const result = await (0, wslHostToolBridge_1.requestWindowsHostTool)('browser_control', scopedRequest, {
1358
1343
  conversationId: context.conversationId || process.env.NEWMARK_CONVERSATION_ID || 'default',
1359
1344
  workspaceId: process.env.NEWMARK_WORKSPACE_ID || context.workspaceId || (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(workspacePath),
1360
1345
  actorId: context.actorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID,
@@ -1364,10 +1349,12 @@ class ToolExecutor {
1364
1349
  return this.formatBrowserResult(result);
1365
1350
  }
1366
1351
  if (process.env.NEWMARK_ISOLATED_RUNTIME === '1') {
1367
- const result = await (0, utilityHostToolBridge_1.requestUtilityHostTool)('browser_control', request, undefined, 30_000, signal);
1352
+ const result = await (0, utilityHostToolBridge_1.requestUtilityHostTool)('browser_control', scopedRequest, undefined, 30_000, signal);
1368
1353
  return this.formatBrowserResult(result);
1369
1354
  }
1370
- const result = await browserControl_1.BrowserControl.run(request, signal);
1355
+ // Preserve the cancellation contract of BrowserControl.run(request, signal)
1356
+ // while routing the concrete request through the target-bound copy above.
1357
+ const result = await browserControl_1.BrowserControl.run(scopedRequest, signal);
1371
1358
  return this.formatBrowserResult(result);
1372
1359
  }
1373
1360
  formatBrowserResult(result) {