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.
- package/dist/cli-help.d.ts +2 -0
- package/dist/cli-help.js +22 -0
- package/dist/context/domain/types.d.ts +1 -1
- package/dist/conversation-utility-host.bundle.cjs +2060 -774
- package/dist/conversation-utility-host.js +4 -1
- package/dist/core/agent.d.ts +23 -1
- package/dist/core/agent.js +517 -37
- package/dist/core/agentKernelRunner.js +20 -3
- package/dist/core/browserControl.d.ts +8 -0
- package/dist/core/browserUsePageAdapter.d.ts +3 -0
- package/dist/core/browserUsePageAdapter.js +19 -2
- package/dist/core/compressionHistoryArchive.d.ts +28 -0
- package/dist/core/compressionHistoryArchive.js +131 -0
- package/dist/core/computerUseSession.d.ts +44 -0
- package/dist/core/computerUseSession.js +105 -0
- package/dist/core/config.js +1 -0
- package/dist/core/conversationKernel.d.ts +3 -1
- package/dist/core/conversationKernel.js +76 -7
- package/dist/core/electronBrowserUseHost.js +16 -0
- package/dist/core/electronUtilityAgentClient.js +84 -2
- package/dist/core/electronUtilityRuntimePool.js +6 -7
- package/dist/core/runtimeLifecycle.d.ts +23 -0
- package/dist/core/runtimeLifecycle.js +146 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/core/utilityHostToolRouter.d.ts +7 -0
- package/dist/core/utilityHostToolRouter.js +25 -33
- package/dist/core/wslAgentRuntimePool.js +9 -10
- package/dist/launcher.js +13 -1
- package/dist/main.js +147 -18
- package/dist/preload.js +4 -2
- package/dist/tools/index.js +42 -52
- package/dist/tools/nativeTools.js +1 -1
- package/dist/ui/index.html +296 -40
- package/dist/wsl-agent-host.bundle.cjs +2063 -776
- package/dist/wsl-agent-host.js +4 -0
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -70,8 +70,10 @@ const wslAgentRuntimePool_1 = require("./core/wslAgentRuntimePool");
|
|
|
70
70
|
const utilityHostToolRouter_1 = require("./core/utilityHostToolRouter");
|
|
71
71
|
const startupPrewarm_1 = require("./core/startupPrewarm");
|
|
72
72
|
const runtimeShutdown_1 = require("./core/runtimeShutdown");
|
|
73
|
+
const runtimeLifecycle_1 = require("./core/runtimeLifecycle");
|
|
73
74
|
const compat_1 = require("./core/compat");
|
|
74
75
|
const mcpManager_1 = require("./core/mcpManager");
|
|
76
|
+
const cli_help_1 = require("./cli-help");
|
|
75
77
|
const APP_NAME = 'Newmark Agent';
|
|
76
78
|
const APP_ID = 'ai.newmark.agent';
|
|
77
79
|
const CONFIG_RELOAD_RUNTIME_TIMEOUT_MS = 6_000;
|
|
@@ -115,7 +117,27 @@ let _forceQuit = false;
|
|
|
115
117
|
let forcedExitTimer = null;
|
|
116
118
|
let electronBrowserUseHost = null;
|
|
117
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.
|
|
118
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
|
+
}
|
|
119
141
|
let workspaceSwitchGeneration = 0;
|
|
120
142
|
let workspaceSelectionCoordinator = null;
|
|
121
143
|
function defaultTerminalShell() {
|
|
@@ -736,7 +758,15 @@ async function waitForWebContentsLoad(contents, timeoutMs = 15000) {
|
|
|
736
758
|
contents.once('did-fail-load', finish);
|
|
737
759
|
});
|
|
738
760
|
}
|
|
739
|
-
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
|
+
}
|
|
740
770
|
const hostIds = hostContentsId
|
|
741
771
|
? [hostContentsId]
|
|
742
772
|
: [
|
|
@@ -751,8 +781,14 @@ function registeredBrowserGuest(hostContentsId) {
|
|
|
751
781
|
if (!guestId)
|
|
752
782
|
continue;
|
|
753
783
|
const guest = electron_1.webContents.fromId(guestId);
|
|
754
|
-
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
|
+
}
|
|
755
790
|
return guest;
|
|
791
|
+
}
|
|
756
792
|
browserGuestContentsByHost.delete(hostId);
|
|
757
793
|
}
|
|
758
794
|
return null;
|
|
@@ -773,37 +809,64 @@ async function boundedOperation(operation, timeoutMs, label) {
|
|
|
773
809
|
clearTimeout(timer);
|
|
774
810
|
}
|
|
775
811
|
}
|
|
776
|
-
function registerBrowserGuest(host, guest) {
|
|
812
|
+
function registerBrowserGuest(host, guest, requestedTarget) {
|
|
777
813
|
if (host.isDestroyed() || guest.isDestroyed() || guest.getType() !== 'webview' || guest.hostWebContents?.id !== host.id)
|
|
778
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
|
+
});
|
|
779
834
|
browserGuestContentsByHost.set(host.id, guest.id);
|
|
780
835
|
guest.once('destroyed', () => {
|
|
781
836
|
if (browserGuestContentsByHost.get(host.id) === guest.id)
|
|
782
837
|
browserGuestContentsByHost.delete(host.id);
|
|
838
|
+
const binding = browserGuestBindingsByRuntime.get(target.runtimeKey);
|
|
839
|
+
if (binding?.guestId === guest.id)
|
|
840
|
+
browserGuestBindingsByRuntime.delete(target.runtimeKey);
|
|
783
841
|
});
|
|
784
842
|
ensureElectronBrowserUseHost().attach(guest);
|
|
785
843
|
return true;
|
|
786
844
|
}
|
|
787
|
-
async function waitForRegisteredBrowserGuest(host, timeoutMs = 12_000) {
|
|
845
|
+
async function waitForRegisteredBrowserGuest(host, runtimeKey, timeoutMs = 12_000) {
|
|
788
846
|
const deadline = Date.now() + Math.max(250, timeoutMs);
|
|
789
847
|
while (!host.isDestroyed() && Date.now() < deadline) {
|
|
790
|
-
const guest = registeredBrowserGuest(host.id);
|
|
848
|
+
const guest = registeredBrowserGuest(host.id, runtimeKey);
|
|
791
849
|
if (guest)
|
|
792
850
|
return guest;
|
|
793
851
|
await new Promise(resolve => setTimeout(resolve, 25));
|
|
794
852
|
}
|
|
795
853
|
throw new Error('Built-in Browser guest did not become ready before the Browser-Use timeout');
|
|
796
854
|
}
|
|
797
|
-
async function ensureBrowserWebContents(boundContentsId) {
|
|
855
|
+
async function ensureBrowserWebContents(boundContentsId, runtimeKey) {
|
|
798
856
|
if (boundContentsId) {
|
|
799
857
|
const bound = electron_1.webContents.fromId(boundContentsId);
|
|
800
858
|
if (bound && !bound.isDestroyed() && bound.getType() === 'webview'
|
|
801
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
|
+
}
|
|
802
865
|
bound.hostWebContents?.send('browser:ensureGuest');
|
|
803
866
|
return bound;
|
|
804
867
|
}
|
|
805
868
|
}
|
|
806
|
-
const registered = registeredBrowserGuest();
|
|
869
|
+
const registered = registeredBrowserGuest(undefined, runtimeKey);
|
|
807
870
|
if (registered) {
|
|
808
871
|
registered.hostWebContents?.send('browser:ensureGuest');
|
|
809
872
|
return registered;
|
|
@@ -813,13 +876,13 @@ async function ensureBrowserWebContents(boundContentsId) {
|
|
|
813
876
|
const host = hostWindow?.webContents;
|
|
814
877
|
if (!host || host.isDestroyed())
|
|
815
878
|
throw new Error('Built-in Browser UI is unavailable');
|
|
816
|
-
host.send('browser:ensureGuest');
|
|
817
|
-
return await waitForRegisteredBrowserGuest(host);
|
|
879
|
+
host.send('browser:ensureGuest', runtimeKey ? { runtimeKey } : undefined);
|
|
880
|
+
return await waitForRegisteredBrowserGuest(host, runtimeKey, 12_000);
|
|
818
881
|
}
|
|
819
882
|
function ensureElectronBrowserUseHost() {
|
|
820
883
|
if (!electronBrowserUseHost) {
|
|
821
884
|
electronBrowserUseHost = new electronBrowserUseHost_1.ElectronBrowserUseHost({
|
|
822
|
-
resolveContents: async (
|
|
885
|
+
resolveContents: async (scope, boundContentsId) => await ensureBrowserWebContents(boundContentsId, scope.runtimeKey),
|
|
823
886
|
openExternal: async (url) => { await electron_1.shell.openExternal(url); },
|
|
824
887
|
});
|
|
825
888
|
}
|
|
@@ -832,10 +895,18 @@ function ensureBrowserUseEngine() {
|
|
|
832
895
|
}
|
|
833
896
|
return browserUseEngine;
|
|
834
897
|
}
|
|
835
|
-
function currentBrowserUseContext() {
|
|
836
|
-
const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget());
|
|
898
|
+
function currentBrowserUseContext(requestedTarget) {
|
|
899
|
+
const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(requestedTarget));
|
|
837
900
|
return { runtimeKey: target.runtimeKey, actorId: agent?.runtimeActorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID };
|
|
838
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
|
+
}
|
|
839
910
|
async function runBoundBrowserUse(input, context, signal) {
|
|
840
911
|
return await ensureBrowserUseEngine().run((0, browserUse_1.bindBrowserUseRequest)(input, context), signal);
|
|
841
912
|
}
|
|
@@ -855,8 +926,10 @@ function browserSnapshotScript(maxChars) {
|
|
|
855
926
|
}
|
|
856
927
|
async function runBrowserControl(request) {
|
|
857
928
|
const action = request.action;
|
|
929
|
+
const requestedTarget = browserControlTargetInput(request.target);
|
|
930
|
+
const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(requestedTarget));
|
|
858
931
|
if (action === 'use') {
|
|
859
|
-
const receipt = await runBoundBrowserUse(request.browserUse, currentBrowserUseContext());
|
|
932
|
+
const receipt = await runBoundBrowserUse(request.browserUse, currentBrowserUseContext(requestedTarget));
|
|
860
933
|
return {
|
|
861
934
|
ok: receipt.ok,
|
|
862
935
|
action,
|
|
@@ -867,7 +940,7 @@ async function runBrowserControl(request) {
|
|
|
867
940
|
error: receipt.error,
|
|
868
941
|
};
|
|
869
942
|
}
|
|
870
|
-
const contents = await ensureBrowserWebContents();
|
|
943
|
+
const contents = await ensureBrowserWebContents(undefined, target.runtimeKey);
|
|
871
944
|
try {
|
|
872
945
|
if (action === 'open') {
|
|
873
946
|
await contents.loadURL(request.url || 'about:blank');
|
|
@@ -952,12 +1025,25 @@ function installBrowserControlBackend() {
|
|
|
952
1025
|
const args = userArgs();
|
|
953
1026
|
const command = args.find(a => a === 'flow' || a === 'edit');
|
|
954
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()));
|
|
955
1031
|
const isViewerArg = args.some(arg => arg.toLowerCase() === '--newmark-viewer');
|
|
956
1032
|
const isCliArg = args.includes('--cli');
|
|
957
1033
|
const isServerArg = args.includes('--server');
|
|
958
1034
|
const isFlowArg = command === 'flow';
|
|
959
1035
|
const isEditArg = command === 'edit';
|
|
960
|
-
|
|
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
|
+
}
|
|
961
1047
|
function viewerEscape(value) {
|
|
962
1048
|
return String(value || '').replace(/[&<>"']/g, character => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[character] || character);
|
|
963
1049
|
}
|
|
@@ -1512,7 +1598,12 @@ else {
|
|
|
1512
1598
|
rejectUiReadinessById(Number(fileRouterOwnerId), new Error('Startup window closed before UI readiness'));
|
|
1513
1599
|
fileRouter.revokeOwner(fileRouterOwnerId);
|
|
1514
1600
|
pdfPreviewServer.revokeOwner(fileRouterOwnerId);
|
|
1515
|
-
|
|
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
|
+
}
|
|
1516
1607
|
const remaining = electron_1.BrowserWindow.getAllWindows().filter(candidate => !candidate.isDestroyed() && candidate !== win);
|
|
1517
1608
|
if (mainWindow === win)
|
|
1518
1609
|
mainWindow = remaining[0] || null;
|
|
@@ -2106,6 +2197,15 @@ else {
|
|
|
2106
2197
|
}
|
|
2107
2198
|
electron_1.app.quit();
|
|
2108
2199
|
};
|
|
2200
|
+
const hasUnsettledGoalOrFlow = () => {
|
|
2201
|
+
const goalRunning = !!agent?.goal && !agent.goal.paused;
|
|
2202
|
+
const flowRunning = Array.from(activeFlowsByRuntimeKey.values()).some(state => !!state.abortController);
|
|
2203
|
+
return goalRunning || flowRunning;
|
|
2204
|
+
};
|
|
2205
|
+
const markMainLifecycleCleanIfSafe = () => {
|
|
2206
|
+
if (!hasUnsettledGoalOrFlow())
|
|
2207
|
+
(0, runtimeLifecycle_1.markRuntimeLifecycleClean)(root, 'main');
|
|
2208
|
+
};
|
|
2109
2209
|
electron_1.app.on('will-quit', event => {
|
|
2110
2210
|
if (_forceQuit)
|
|
2111
2211
|
armForcedExitDeadline('will-quit');
|
|
@@ -2131,6 +2231,7 @@ else {
|
|
|
2131
2231
|
throw error;
|
|
2132
2232
|
})
|
|
2133
2233
|
: undefined;
|
|
2234
|
+
let shutdownFailed = false;
|
|
2134
2235
|
void (0, runtimeShutdown_1.runRuntimeShutdownBarrier)({
|
|
2135
2236
|
operations: [
|
|
2136
2237
|
legacyStop,
|
|
@@ -2139,9 +2240,12 @@ else {
|
|
|
2139
2240
|
],
|
|
2140
2241
|
shutdownHelpers: async () => await (0, electronUtilityAgentClient_1.shutdownWindowsProcessHelpers)(2_000),
|
|
2141
2242
|
}).catch(error => {
|
|
2243
|
+
shutdownFailed = true;
|
|
2142
2244
|
console.error('[Newmark] Runtime shutdown cleanup failed:', error instanceof Error ? error.message : String(error));
|
|
2143
2245
|
}).finally(() => {
|
|
2144
2246
|
appExitCleanupComplete = true;
|
|
2247
|
+
if (!shutdownFailed)
|
|
2248
|
+
markMainLifecycleCleanIfSafe();
|
|
2145
2249
|
electron_1.app.quit();
|
|
2146
2250
|
});
|
|
2147
2251
|
}
|
|
@@ -2165,11 +2269,13 @@ else {
|
|
|
2165
2269
|
electronBrowserUseHost = null;
|
|
2166
2270
|
browserControl_1.BrowserControl.setBackend(null);
|
|
2167
2271
|
browserGuestContentsByHost.clear();
|
|
2272
|
+
browserGuestBindingsByRuntime.clear();
|
|
2168
2273
|
if (sidecarProcess) {
|
|
2169
2274
|
sidecarProcess.kill();
|
|
2170
2275
|
sidecarProcess = null;
|
|
2171
2276
|
}
|
|
2172
2277
|
(0, terminalTakeover_1.shutdownTerminalTakeoverSessions)('app-exit');
|
|
2278
|
+
markMainLifecycleCleanIfSafe();
|
|
2173
2279
|
if (forcedExitTimer) {
|
|
2174
2280
|
clearTimeout(forcedExitTimer);
|
|
2175
2281
|
forcedExitTimer = null;
|
|
@@ -2554,9 +2660,9 @@ else {
|
|
|
2554
2660
|
}
|
|
2555
2661
|
}
|
|
2556
2662
|
});
|
|
2557
|
-
electron_1.ipcMain.handle('browser:registerGuest', (event, guestContentsId) => {
|
|
2663
|
+
electron_1.ipcMain.handle('browser:registerGuest', (event, guestContentsId, target) => {
|
|
2558
2664
|
const guest = electron_1.webContents.fromId(Number(guestContentsId || 0));
|
|
2559
|
-
return { accepted: !!guest && registerBrowserGuest(event.sender, guest) };
|
|
2665
|
+
return { accepted: !!guest && registerBrowserGuest(event.sender, guest, target) };
|
|
2560
2666
|
});
|
|
2561
2667
|
electron_1.ipcMain.handle('browser:control', async (_event, request) => {
|
|
2562
2668
|
return await browserControl_1.BrowserControl.run(request);
|
|
@@ -2940,6 +3046,28 @@ else {
|
|
|
2940
3046
|
runtimeDeferred: false,
|
|
2941
3047
|
};
|
|
2942
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
|
+
});
|
|
2943
3071
|
electron_1.ipcMain.handle('agent:updateGoal', async (_event, goal, targetInput) => {
|
|
2944
3072
|
if (!agent)
|
|
2945
3073
|
return null;
|
|
@@ -3042,6 +3170,7 @@ else {
|
|
|
3042
3170
|
pendingOptions: conversationSnapshot.pendingOptions || agent.pendingOptions,
|
|
3043
3171
|
flowSuspension: flowSuspensionForTarget(target),
|
|
3044
3172
|
flowRunning: flowRunningForTarget(target),
|
|
3173
|
+
computerUse: utilityHostToolHandler.computerUseState((0, conversationTarget_1.normalizeConversationTarget)(target).runtimeKey),
|
|
3045
3174
|
draft: agent.getStoredConversationDraft(target.conversationId) || '',
|
|
3046
3175
|
proxyEnabled: agent.config.getBool('proxy', 'enabled'),
|
|
3047
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),
|
package/dist/tools/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
186
|
-
|
|
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
|
|
195
|
-
|
|
196
|
-
|
|
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;
|
|
@@ -374,13 +351,16 @@ class ToolExecutor {
|
|
|
374
351
|
t('linked_plan', 'Read or update the current conversation linked Markdown plan. Update requires the current expected_revision.', { action: { type: 'string', enum: ['get', 'update'] }, markdown: { type: 'string' }, expected_revision: { type: 'number' } }, ['action']),
|
|
375
352
|
t('build_history_query', 'Read the concrete public work details of one historical Build Block. The prompt already exposes user input, final summary, and completion status; call this read-only tool only when the user asks what specifically happened. Select by newest-to-oldest history_index, or by run_id returned from an earlier query.', { history_index: { type: 'number', minimum: 1, description: '1-based historical Build Block index from the request ledger; 1 is the newest previous task.' }, run_id: { type: 'string', description: 'Exact run id returned by an earlier build_history_query result.' }, max_events: { type: 'number', minimum: 1, maximum: 200, description: 'Maximum trailing public work events; defaults to 80.' } }, []),
|
|
376
353
|
t('context_compress', 'Actively compress the LLM context history for this conversation. This collapses older history entries into a concise summary while preserving the recent tail, which reduces context tokens and cost. IMPORTANT: it affects only the LLM context (what the model sees); the displayed conversation history shown to the user is never altered. Call this when the conversation is long, token pressure is high, or you judge that older turns are no longer needed in full. Idempotent and safe: repeated calls produce incremental summaries.', { keep_recent: { type: 'number', minimum: 2, maximum: 60, description: 'Recent message count to keep uncompressed at the tail. Defaults to the configured keep_recent_messages.' }, force: { type: 'boolean', description: 'Compress even if the context is not yet over the automatic threshold. Defaults to false.' } }, []),
|
|
377
|
-
t('context_history_manage', 'Manage the LLM context history for this conversation without affecting the displayed conversation history. This is the active context-management surface.
|
|
378
|
-
action: { type: 'string', enum: ['list', 'remove', 'summarize', 'restore', 'search', 'status'], description: 'list
|
|
354
|
+
t('context_history_manage', 'Manage the LLM context history for this conversation without affecting the displayed conversation history. This is the active context-management surface. The hot cache stays bounded; evicted folded segments remain in a conversation-isolated append-only cold archive and are loaded only by explicit search/read/restore calls. Actions: list returns a bounded index of current context entries; remove deletes one current entry; summarize folds a contiguous current range; restore reinserts a folded segment when its summary marker is still present; search finds matching hot or archived segments; read returns one bounded segment without injecting the whole archive; status reports budgets, hot cache, cold archive, and the protected recent zone. The recent context tail and last user message are protected from remove/summarize unless dangerous is true.', {
|
|
355
|
+
action: { type: 'string', enum: ['list', 'remove', 'summarize', 'restore', 'search', 'read', 'status'], description: 'list current entries; remove one; summarize a range; restore by restore_id; search hot/cold folded segments; read one bounded folded segment; status report context budgets and storage.' },
|
|
379
356
|
position: { type: 'number', minimum: 0, description: '0-based context entry index for remove, or the start of the range for summarize.' },
|
|
380
357
|
to: { type: 'number', minimum: 0, description: '0-based inclusive end of the range for summarize. Defaults to position.' },
|
|
381
|
-
limit: { type: 'number', minimum:
|
|
358
|
+
limit: { type: 'number', minimum: 1, maximum: 400, description: 'Maximum context entries/messages/matches to return. Current-history list still keeps a minimum page of 5.' },
|
|
382
359
|
restore_id: { type: 'string', description: 'Cache id of a folded segment (from search or status) to restore into context.' },
|
|
383
360
|
query: { type: 'string', description: 'Case-insensitive text to search for across cached folded segments and their summaries.' },
|
|
361
|
+
offset: { type: 'number', minimum: 0, description: 'Message offset for read pagination.' },
|
|
362
|
+
content_offset: { type: 'number', minimum: 0, description: 'Character offset within the first read message, used with nextContentOffset when one message exceeds max_chars.' },
|
|
363
|
+
max_chars: { type: 'number', minimum: 1000, maximum: 60000, description: 'Maximum message-content characters returned by read (default 12000).' },
|
|
384
364
|
dangerous: { type: 'boolean', description: 'Set true to override the protected recent-message zone and allow removing/summarizing entries that include the recent context tail or the last user message.' },
|
|
385
365
|
}, ['action']),
|
|
386
366
|
t('question', 'Ask user a multiple-choice question', { questions: { type: 'array' } }, ['questions']),
|
|
@@ -751,8 +731,8 @@ class ToolExecutor {
|
|
|
751
731
|
const action = normalizeComputerUseAction(g('action'));
|
|
752
732
|
const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || 'root')}`;
|
|
753
733
|
const lockGuard = action === 'takeover_stop'
|
|
754
|
-
? assertComputerUseLockOwner(action, owner)
|
|
755
|
-
: acquireComputerUseLock(action, owner, wsPath);
|
|
734
|
+
? assertComputerUseLockOwner(action, owner, context, wsPath)
|
|
735
|
+
: acquireComputerUseLock(action, owner, wsPath, context, args.dry_run === true);
|
|
756
736
|
if (lockGuard)
|
|
757
737
|
return lockGuard;
|
|
758
738
|
if (process.env.NEWMARK_WSL_DISTRO) {
|
|
@@ -769,7 +749,7 @@ class ToolExecutor {
|
|
|
769
749
|
}
|
|
770
750
|
finally {
|
|
771
751
|
if (action === 'takeover_stop')
|
|
772
|
-
releaseComputerUseLock(action, owner);
|
|
752
|
+
releaseComputerUseLock(action, owner, context, wsPath);
|
|
773
753
|
}
|
|
774
754
|
}
|
|
775
755
|
if (process.env.NEWMARK_ISOLATED_RUNTIME === '1') {
|
|
@@ -791,7 +771,7 @@ class ToolExecutor {
|
|
|
791
771
|
}
|
|
792
772
|
finally {
|
|
793
773
|
if (action === 'takeover_stop')
|
|
794
|
-
releaseComputerUseLock(action, owner);
|
|
774
|
+
releaseComputerUseLock(action, owner, context, wsPath);
|
|
795
775
|
}
|
|
796
776
|
}
|
|
797
777
|
const output = await (0, computerUse_1.runComputerUse)({
|
|
@@ -835,7 +815,7 @@ class ToolExecutor {
|
|
|
835
815
|
: undefined,
|
|
836
816
|
});
|
|
837
817
|
if (action === 'takeover_stop')
|
|
838
|
-
releaseComputerUseLock(action, owner);
|
|
818
|
+
releaseComputerUseLock(action, owner, context, wsPath);
|
|
839
819
|
return output;
|
|
840
820
|
}
|
|
841
821
|
case 'terminal_takeover': {
|
|
@@ -1349,9 +1329,17 @@ class ToolExecutor {
|
|
|
1349
1329
|
}
|
|
1350
1330
|
}
|
|
1351
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
|
+
};
|
|
1352
1341
|
if (process.env.NEWMARK_WSL_DISTRO) {
|
|
1353
|
-
const
|
|
1354
|
-
const result = await (0, wslHostToolBridge_1.requestWindowsHostTool)('browser_control', request, {
|
|
1342
|
+
const result = await (0, wslHostToolBridge_1.requestWindowsHostTool)('browser_control', scopedRequest, {
|
|
1355
1343
|
conversationId: context.conversationId || process.env.NEWMARK_CONVERSATION_ID || 'default',
|
|
1356
1344
|
workspaceId: process.env.NEWMARK_WORKSPACE_ID || context.workspaceId || (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(workspacePath),
|
|
1357
1345
|
actorId: context.actorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID,
|
|
@@ -1361,10 +1349,12 @@ class ToolExecutor {
|
|
|
1361
1349
|
return this.formatBrowserResult(result);
|
|
1362
1350
|
}
|
|
1363
1351
|
if (process.env.NEWMARK_ISOLATED_RUNTIME === '1') {
|
|
1364
|
-
const result = await (0, utilityHostToolBridge_1.requestUtilityHostTool)('browser_control',
|
|
1352
|
+
const result = await (0, utilityHostToolBridge_1.requestUtilityHostTool)('browser_control', scopedRequest, undefined, 30_000, signal);
|
|
1365
1353
|
return this.formatBrowserResult(result);
|
|
1366
1354
|
}
|
|
1367
|
-
|
|
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);
|
|
1368
1358
|
return this.formatBrowserResult(result);
|
|
1369
1359
|
}
|
|
1370
1360
|
formatBrowserResult(result) {
|
|
@@ -41,7 +41,7 @@ exports.NATIVE_TOOL_CATALOG = [
|
|
|
41
41
|
{ name: 'linked_plan', label: 'Linked plan', description: 'Read or conservatively update the conversation-linked Markdown plan.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
42
42
|
{ name: 'build_history_query', label: 'Build history query', description: 'Read concrete public work details for one historical Build Block.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
43
43
|
{ name: 'context_compress', label: 'Context compress', description: 'Actively compress the LLM context history, leaving the displayed conversation history unchanged.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
44
|
-
{ name: 'context_history_manage', label: 'Context history manage', description: '
|
|
44
|
+
{ name: 'context_history_manage', label: 'Context history manage', description: 'Inspect, search, restore, or fold LLM context history without touching the displayed conversation history.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
45
45
|
{ name: 'question', label: 'Ask question', description: 'Ask the user for structured option feedback.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
46
46
|
{ name: 'skill_download', label: 'Skill download', description: 'Download and install a skill.', category: 'agent', defaultEnabled: true },
|
|
47
47
|
{ name: 'skill', label: 'Skill', description: 'Search enabled skill metadata or load one skill body on demand.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|