newmark-agent 0.3.10 → 0.3.12
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/config.example.json +6 -0
- package/dist/cli-commands.d.ts +7 -0
- package/dist/cli-commands.js +206 -15
- package/dist/cli-discovery.d.ts +15 -0
- package/dist/cli-discovery.js +182 -0
- package/dist/cli-help.d.ts +4 -0
- package/dist/cli-help.js +46 -0
- package/dist/conversation-utility-host.bundle.cjs +548 -137
- package/dist/core/agent.d.ts +18 -3
- package/dist/core/agent.js +236 -34
- package/dist/core/agentKernelRunner.js +72 -10
- 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/computerUseSession.d.ts +44 -0
- package/dist/core/computerUseSession.js +105 -0
- package/dist/core/config.d.ts +7 -2
- package/dist/core/config.js +24 -6
- package/dist/core/conversationKernel.d.ts +1 -0
- package/dist/core/conversationKernel.js +39 -1
- package/dist/core/electronBrowserUseHost.js +7 -0
- package/dist/core/electronUtilityAgentClient.js +84 -2
- package/dist/core/electronUtilityRuntimePool.d.ts +11 -0
- package/dist/core/electronUtilityRuntimePool.js +62 -0
- package/dist/core/flow-runner.js +1 -1
- package/dist/core/modelValidationStore.d.ts +4 -1
- package/dist/core/modelValidationStore.js +7 -1
- package/dist/core/utilityHostToolRouter.d.ts +7 -0
- package/dist/core/utilityHostToolRouter.js +25 -33
- package/dist/core/workspace.d.ts +6 -0
- package/dist/core/workspace.js +14 -0
- package/dist/core/wslAgentRuntimePool.d.ts +4 -0
- package/dist/core/wslAgentRuntimePool.js +56 -0
- package/dist/launcher.js +51 -10
- package/dist/llm/provider.d.ts +8 -5
- package/dist/llm/provider.js +85 -33
- package/dist/main.js +297 -61
- package/dist/preload.js +10 -2
- package/dist/providers/chat-completions.adapter.js +1 -5
- package/dist/providers/provider-events.d.ts +7 -0
- package/dist/providers/provider-events.js +44 -0
- package/dist/providers/responses.adapter.js +1 -3
- package/dist/tools/index.js +36 -49
- package/dist/tui/src/adapters/core-runtime-adapter.js +40 -3
- package/dist/tui/src/app.js +47 -13
- package/dist/tui/src/render.js +23 -7
- package/dist/tui/src/state.js +61 -9
- package/dist/ui/index.html +545 -122
- package/dist/wsl-agent-host.bundle.cjs +548 -137
- package/package.json +14 -5
package/dist/main.js
CHANGED
|
@@ -51,6 +51,7 @@ const electronBrowserUseHost_1 = require("./core/electronBrowserUseHost");
|
|
|
51
51
|
const flow_1 = require("./core/flow");
|
|
52
52
|
const flow_runner_1 = require("./core/flow-runner");
|
|
53
53
|
const cli_commands_1 = require("./cli-commands");
|
|
54
|
+
const cli_discovery_1 = require("./cli-discovery");
|
|
54
55
|
const config_1 = require("./core/config");
|
|
55
56
|
const memoryLab_1 = require("./core/memoryLab");
|
|
56
57
|
const installUpdate_1 = require("./core/installUpdate");
|
|
@@ -73,6 +74,7 @@ const runtimeShutdown_1 = require("./core/runtimeShutdown");
|
|
|
73
74
|
const runtimeLifecycle_1 = require("./core/runtimeLifecycle");
|
|
74
75
|
const compat_1 = require("./core/compat");
|
|
75
76
|
const mcpManager_1 = require("./core/mcpManager");
|
|
77
|
+
const cli_help_1 = require("./cli-help");
|
|
76
78
|
const APP_NAME = 'Newmark Agent';
|
|
77
79
|
const APP_ID = 'ai.newmark.agent';
|
|
78
80
|
const CONFIG_RELOAD_RUNTIME_TIMEOUT_MS = 6_000;
|
|
@@ -116,7 +118,27 @@ let _forceQuit = false;
|
|
|
116
118
|
let forcedExitTimer = null;
|
|
117
119
|
let electronBrowserUseHost = null;
|
|
118
120
|
let browserUseEngine = null;
|
|
121
|
+
// A single renderer can host several hidden conversation-bound guests. The
|
|
122
|
+
// legacy host map is retained for focused-window discovery, while this map is
|
|
123
|
+
// the authoritative Browser-Use/right-sidebar binding.
|
|
119
124
|
const browserGuestContentsByHost = new Map();
|
|
125
|
+
const browserGuestBindingsByRuntime = new Map();
|
|
126
|
+
function browserGuestRuntimeKey(target) {
|
|
127
|
+
return (0, conversationTarget_1.normalizeConversationTarget)(target).runtimeKey;
|
|
128
|
+
}
|
|
129
|
+
function browserGuestBindingFor(runtimeKey) {
|
|
130
|
+
const binding = browserGuestBindingsByRuntime.get(String(runtimeKey || ''));
|
|
131
|
+
if (!binding)
|
|
132
|
+
return null;
|
|
133
|
+
const guest = electron_1.webContents.fromId(binding.guestId);
|
|
134
|
+
if (!guest || guest.isDestroyed() || guest.getType() !== 'webview' || guest.hostWebContents?.id !== binding.hostId) {
|
|
135
|
+
browserGuestBindingsByRuntime.delete(String(runtimeKey || ''));
|
|
136
|
+
if (browserGuestContentsByHost.get(binding.hostId) === binding.guestId)
|
|
137
|
+
browserGuestContentsByHost.delete(binding.hostId);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return binding;
|
|
141
|
+
}
|
|
120
142
|
let workspaceSwitchGeneration = 0;
|
|
121
143
|
let workspaceSelectionCoordinator = null;
|
|
122
144
|
function defaultTerminalShell() {
|
|
@@ -405,7 +427,11 @@ function createAppIconImage(size) {
|
|
|
405
427
|
return size ? icon.resize({ width: size, height: size }) : icon;
|
|
406
428
|
}
|
|
407
429
|
function userArgs() {
|
|
408
|
-
|
|
430
|
+
const args = process.argv.slice(1);
|
|
431
|
+
// The native Windows console wrapper inserts Electron's `--` boundary before
|
|
432
|
+
// user arguments. Electron leaves that boundary in process.argv; normalize it
|
|
433
|
+
// before command discovery so the literal `Newmark.exe help` path terminates.
|
|
434
|
+
return args[0] === '--' ? args.slice(1) : args;
|
|
409
435
|
}
|
|
410
436
|
function argValue(args, key) {
|
|
411
437
|
const idx = args.indexOf(key);
|
|
@@ -465,14 +491,14 @@ function positionalAfter(args, commandName) {
|
|
|
465
491
|
return values;
|
|
466
492
|
}
|
|
467
493
|
// First-run initialization
|
|
468
|
-
function firstRunInit(root) {
|
|
494
|
+
function firstRunInit(root, options = {}) {
|
|
469
495
|
fs.mkdirSync(root, { recursive: true });
|
|
470
496
|
for (const d of ['skills', 'Work', 'Flow', 'archive', 'Memory Lab']) {
|
|
471
497
|
fs.mkdirSync(path.join(root, d), { recursive: true });
|
|
472
498
|
}
|
|
473
499
|
new memoryLab_1.MemoryLabManager(root);
|
|
474
500
|
const configModule = require('./core/config');
|
|
475
|
-
configModule.ensureRootConfig(root);
|
|
501
|
+
configModule.ensureRootConfig(root, options);
|
|
476
502
|
if (!fs.existsSync(path.join(root, 'agent.md'))) {
|
|
477
503
|
fs.writeFileSync(path.join(root, 'agent.md'), '# Newmark Agent\n\nYou are a powerful coding assistant.\n', 'utf-8');
|
|
478
504
|
}
|
|
@@ -562,18 +588,13 @@ function ensureElectronUtilityRuntimeHost() {
|
|
|
562
588
|
return host;
|
|
563
589
|
}
|
|
564
590
|
function legacyUserDataRoot() {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
catch {
|
|
569
|
-
if (process.platform === 'win32') {
|
|
570
|
-
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
571
|
-
return path.join(appData, 'Newmark Agent');
|
|
572
|
-
}
|
|
573
|
-
if (process.platform === 'darwin')
|
|
574
|
-
return path.join(os.homedir(), 'Library', 'Application Support', 'Newmark Agent');
|
|
575
|
-
return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'Newmark Agent');
|
|
591
|
+
if (process.platform === 'win32') {
|
|
592
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
593
|
+
return path.join(appData, 'Newmark Agent');
|
|
576
594
|
}
|
|
595
|
+
if (process.platform === 'darwin')
|
|
596
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Newmark Agent');
|
|
597
|
+
return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'Newmark Agent');
|
|
577
598
|
}
|
|
578
599
|
function migrateLegacyRuntimeRoot(root) {
|
|
579
600
|
const targetRoot = path.resolve(root);
|
|
@@ -656,6 +677,15 @@ function resolveRoot(args) {
|
|
|
656
677
|
return writableRuntimeRoot(explicitRoot);
|
|
657
678
|
return getRoot();
|
|
658
679
|
}
|
|
680
|
+
function resolveTuiWorkspacePath(args, root) {
|
|
681
|
+
const explicitWorkspace = pathArgValue(args, '--workspace');
|
|
682
|
+
if (explicitWorkspace)
|
|
683
|
+
return explicitWorkspace;
|
|
684
|
+
// An explicitly isolated runtime must not silently register the caller's
|
|
685
|
+
// cwd as an external workspace. Keep the opt-in --workspace escape hatch,
|
|
686
|
+
// while making the safe one-argument form fully self-contained.
|
|
687
|
+
return pathArgValue(args, '--root') ? root : process.cwd();
|
|
688
|
+
}
|
|
659
689
|
function startupLogPath() {
|
|
660
690
|
try {
|
|
661
691
|
const userData = userRuntimeRoot();
|
|
@@ -737,7 +767,15 @@ async function waitForWebContentsLoad(contents, timeoutMs = 15000) {
|
|
|
737
767
|
contents.once('did-fail-load', finish);
|
|
738
768
|
});
|
|
739
769
|
}
|
|
740
|
-
function registeredBrowserGuest(hostContentsId) {
|
|
770
|
+
function registeredBrowserGuest(hostContentsId, runtimeKey) {
|
|
771
|
+
if (runtimeKey) {
|
|
772
|
+
const binding = browserGuestBindingFor(runtimeKey);
|
|
773
|
+
if (binding && (!hostContentsId || binding.hostId === hostContentsId)) {
|
|
774
|
+
const guest = electron_1.webContents.fromId(binding.guestId);
|
|
775
|
+
if (guest && !guest.isDestroyed())
|
|
776
|
+
return guest;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
741
779
|
const hostIds = hostContentsId
|
|
742
780
|
? [hostContentsId]
|
|
743
781
|
: [
|
|
@@ -752,8 +790,14 @@ function registeredBrowserGuest(hostContentsId) {
|
|
|
752
790
|
if (!guestId)
|
|
753
791
|
continue;
|
|
754
792
|
const guest = electron_1.webContents.fromId(guestId);
|
|
755
|
-
if (guest && !guest.isDestroyed() && guest.getType() === 'webview' && guest.hostWebContents?.id === hostId)
|
|
793
|
+
if (guest && !guest.isDestroyed() && guest.getType() === 'webview' && guest.hostWebContents?.id === hostId) {
|
|
794
|
+
if (runtimeKey) {
|
|
795
|
+
const bound = browserGuestBindingsByRuntime.get(runtimeKey);
|
|
796
|
+
if (!bound || bound.hostId !== hostId || bound.guestId !== guest.id)
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
756
799
|
return guest;
|
|
800
|
+
}
|
|
757
801
|
browserGuestContentsByHost.delete(hostId);
|
|
758
802
|
}
|
|
759
803
|
return null;
|
|
@@ -774,37 +818,64 @@ async function boundedOperation(operation, timeoutMs, label) {
|
|
|
774
818
|
clearTimeout(timer);
|
|
775
819
|
}
|
|
776
820
|
}
|
|
777
|
-
function registerBrowserGuest(host, guest) {
|
|
821
|
+
function registerBrowserGuest(host, guest, requestedTarget) {
|
|
778
822
|
if (host.isDestroyed() || guest.isDestroyed() || guest.getType() !== 'webview' || guest.hostWebContents?.id !== host.id)
|
|
779
823
|
return false;
|
|
824
|
+
let target;
|
|
825
|
+
try {
|
|
826
|
+
target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(requestedTarget));
|
|
827
|
+
}
|
|
828
|
+
catch {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
const prior = browserGuestBindingsByRuntime.get(target.runtimeKey);
|
|
832
|
+
if (prior && prior.guestId !== guest.id) {
|
|
833
|
+
const priorGuest = electron_1.webContents.fromId(prior.guestId);
|
|
834
|
+
if (priorGuest && !priorGuest.isDestroyed())
|
|
835
|
+
priorGuest.hostWebContents?.send('browser:guestVisibility', { runtimeKey: target.runtimeKey, visible: false });
|
|
836
|
+
}
|
|
837
|
+
browserGuestBindingsByRuntime.set(target.runtimeKey, {
|
|
838
|
+
hostId: host.id,
|
|
839
|
+
guestId: guest.id,
|
|
840
|
+
workspaceId: target.workspaceId,
|
|
841
|
+
conversationId: target.conversationId,
|
|
842
|
+
});
|
|
780
843
|
browserGuestContentsByHost.set(host.id, guest.id);
|
|
781
844
|
guest.once('destroyed', () => {
|
|
782
845
|
if (browserGuestContentsByHost.get(host.id) === guest.id)
|
|
783
846
|
browserGuestContentsByHost.delete(host.id);
|
|
847
|
+
const binding = browserGuestBindingsByRuntime.get(target.runtimeKey);
|
|
848
|
+
if (binding?.guestId === guest.id)
|
|
849
|
+
browserGuestBindingsByRuntime.delete(target.runtimeKey);
|
|
784
850
|
});
|
|
785
851
|
ensureElectronBrowserUseHost().attach(guest);
|
|
786
852
|
return true;
|
|
787
853
|
}
|
|
788
|
-
async function waitForRegisteredBrowserGuest(host, timeoutMs = 12_000) {
|
|
854
|
+
async function waitForRegisteredBrowserGuest(host, runtimeKey, timeoutMs = 12_000) {
|
|
789
855
|
const deadline = Date.now() + Math.max(250, timeoutMs);
|
|
790
856
|
while (!host.isDestroyed() && Date.now() < deadline) {
|
|
791
|
-
const guest = registeredBrowserGuest(host.id);
|
|
857
|
+
const guest = registeredBrowserGuest(host.id, runtimeKey);
|
|
792
858
|
if (guest)
|
|
793
859
|
return guest;
|
|
794
860
|
await new Promise(resolve => setTimeout(resolve, 25));
|
|
795
861
|
}
|
|
796
862
|
throw new Error('Built-in Browser guest did not become ready before the Browser-Use timeout');
|
|
797
863
|
}
|
|
798
|
-
async function ensureBrowserWebContents(boundContentsId) {
|
|
864
|
+
async function ensureBrowserWebContents(boundContentsId, runtimeKey) {
|
|
799
865
|
if (boundContentsId) {
|
|
800
866
|
const bound = electron_1.webContents.fromId(boundContentsId);
|
|
801
867
|
if (bound && !bound.isDestroyed() && bound.getType() === 'webview'
|
|
802
868
|
&& browserGuestContentsByHost.get(bound.hostWebContents?.id || 0) === bound.id) {
|
|
869
|
+
if (runtimeKey) {
|
|
870
|
+
const binding = browserGuestBindingsByRuntime.get(runtimeKey);
|
|
871
|
+
if (!binding || binding.guestId !== bound.id)
|
|
872
|
+
return await ensureBrowserWebContents(undefined, runtimeKey);
|
|
873
|
+
}
|
|
803
874
|
bound.hostWebContents?.send('browser:ensureGuest');
|
|
804
875
|
return bound;
|
|
805
876
|
}
|
|
806
877
|
}
|
|
807
|
-
const registered = registeredBrowserGuest();
|
|
878
|
+
const registered = registeredBrowserGuest(undefined, runtimeKey);
|
|
808
879
|
if (registered) {
|
|
809
880
|
registered.hostWebContents?.send('browser:ensureGuest');
|
|
810
881
|
return registered;
|
|
@@ -814,13 +885,13 @@ async function ensureBrowserWebContents(boundContentsId) {
|
|
|
814
885
|
const host = hostWindow?.webContents;
|
|
815
886
|
if (!host || host.isDestroyed())
|
|
816
887
|
throw new Error('Built-in Browser UI is unavailable');
|
|
817
|
-
host.send('browser:ensureGuest');
|
|
818
|
-
return await waitForRegisteredBrowserGuest(host);
|
|
888
|
+
host.send('browser:ensureGuest', runtimeKey ? { runtimeKey } : undefined);
|
|
889
|
+
return await waitForRegisteredBrowserGuest(host, runtimeKey, 12_000);
|
|
819
890
|
}
|
|
820
891
|
function ensureElectronBrowserUseHost() {
|
|
821
892
|
if (!electronBrowserUseHost) {
|
|
822
893
|
electronBrowserUseHost = new electronBrowserUseHost_1.ElectronBrowserUseHost({
|
|
823
|
-
resolveContents: async (
|
|
894
|
+
resolveContents: async (scope, boundContentsId) => await ensureBrowserWebContents(boundContentsId, scope.runtimeKey),
|
|
824
895
|
openExternal: async (url) => { await electron_1.shell.openExternal(url); },
|
|
825
896
|
});
|
|
826
897
|
}
|
|
@@ -833,10 +904,18 @@ function ensureBrowserUseEngine() {
|
|
|
833
904
|
}
|
|
834
905
|
return browserUseEngine;
|
|
835
906
|
}
|
|
836
|
-
function currentBrowserUseContext() {
|
|
837
|
-
const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget());
|
|
907
|
+
function currentBrowserUseContext(requestedTarget) {
|
|
908
|
+
const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(requestedTarget));
|
|
838
909
|
return { runtimeKey: target.runtimeKey, actorId: agent?.runtimeActorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID };
|
|
839
910
|
}
|
|
911
|
+
function browserControlTargetInput(input) {
|
|
912
|
+
if (!input)
|
|
913
|
+
return undefined;
|
|
914
|
+
return {
|
|
915
|
+
workspaceId: String(input.workspaceId || ''),
|
|
916
|
+
conversationId: String(input.conversationId || 'default'),
|
|
917
|
+
};
|
|
918
|
+
}
|
|
840
919
|
async function runBoundBrowserUse(input, context, signal) {
|
|
841
920
|
return await ensureBrowserUseEngine().run((0, browserUse_1.bindBrowserUseRequest)(input, context), signal);
|
|
842
921
|
}
|
|
@@ -856,8 +935,10 @@ function browserSnapshotScript(maxChars) {
|
|
|
856
935
|
}
|
|
857
936
|
async function runBrowserControl(request) {
|
|
858
937
|
const action = request.action;
|
|
938
|
+
const requestedTarget = browserControlTargetInput(request.target);
|
|
939
|
+
const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(requestedTarget));
|
|
859
940
|
if (action === 'use') {
|
|
860
|
-
const receipt = await runBoundBrowserUse(request.browserUse, currentBrowserUseContext());
|
|
941
|
+
const receipt = await runBoundBrowserUse(request.browserUse, currentBrowserUseContext(requestedTarget));
|
|
861
942
|
return {
|
|
862
943
|
ok: receipt.ok,
|
|
863
944
|
action,
|
|
@@ -868,7 +949,7 @@ async function runBrowserControl(request) {
|
|
|
868
949
|
error: receipt.error,
|
|
869
950
|
};
|
|
870
951
|
}
|
|
871
|
-
const contents = await ensureBrowserWebContents();
|
|
952
|
+
const contents = await ensureBrowserWebContents(undefined, target.runtimeKey);
|
|
872
953
|
try {
|
|
873
954
|
if (action === 'open') {
|
|
874
955
|
await contents.loadURL(request.url || 'about:blank');
|
|
@@ -953,12 +1034,55 @@ function installBrowserControlBackend() {
|
|
|
953
1034
|
const args = userArgs();
|
|
954
1035
|
const command = args.find(a => a === 'flow' || a === 'edit');
|
|
955
1036
|
const isTuiArg = args.some(arg => arg.toLowerCase() === '--tui');
|
|
1037
|
+
const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
|
|
1038
|
+
const isFlowArg = command === 'flow';
|
|
1039
|
+
const isEditArg = command === 'edit';
|
|
1040
|
+
const isHelpArg = !hasCliCommand && (args.some(arg => ['--help', '-h'].includes(arg.toLowerCase())) || args[0]?.toLowerCase() === 'help');
|
|
1041
|
+
const isVersionArg = !hasCliCommand && (0, cli_discovery_1.isVersionArgument)(args);
|
|
1042
|
+
const isReadOnlyValidation = hasCliCommand && args.includes('validate-models') && !args.includes('--persist');
|
|
956
1043
|
const isViewerArg = args.some(arg => arg.toLowerCase() === '--newmark-viewer');
|
|
957
1044
|
const isCliArg = args.includes('--cli');
|
|
958
1045
|
const isServerArg = args.includes('--server');
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
1046
|
+
const invalidArgument = (0, cli_discovery_1.invalidTopLevelArgument)(args);
|
|
1047
|
+
if (invalidArgument) {
|
|
1048
|
+
console.error(`Invalid Newmark argument: ${invalidArgument}`);
|
|
1049
|
+
process.exit(2);
|
|
1050
|
+
}
|
|
1051
|
+
// Electron's Chromium profile is a separate state boundary from Newmark's
|
|
1052
|
+
// business root. Bind both before any ready event so --root cannot leave
|
|
1053
|
+
// Preferences, DIPS, DevTools ports, cookies, or session storage in the real
|
|
1054
|
+
// default AppData directory. The dedicated subdirectories keep Chromium's
|
|
1055
|
+
// files separate from the durable Newmark config/workspace files.
|
|
1056
|
+
const runtimeRoot = resolveRoot(args);
|
|
1057
|
+
const electronUserDataRoot = path.join(runtimeRoot, 'Electron');
|
|
1058
|
+
const electronSessionDataRoot = path.join(electronUserDataRoot, 'session-data');
|
|
1059
|
+
try {
|
|
1060
|
+
fs.mkdirSync(electronSessionDataRoot, { recursive: true });
|
|
1061
|
+
electron_1.app.setPath('userData', electronUserDataRoot);
|
|
1062
|
+
electron_1.app.setPath('sessionData', electronSessionDataRoot);
|
|
1063
|
+
}
|
|
1064
|
+
catch (error) {
|
|
1065
|
+
console.error(`Unable to isolate Electron user-data directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
1066
|
+
process.exit(1);
|
|
1067
|
+
}
|
|
1068
|
+
// Help/version are terminating discovery commands. They must be handled
|
|
1069
|
+
// before Electron's GUI/TUI/server branches can initialize runtime state or
|
|
1070
|
+
// spawn a window, so a product-new tester can use them safely in any surface.
|
|
1071
|
+
if (isHelpArg) {
|
|
1072
|
+
console.log(isFlowArg ? (0, cli_help_1.newmarkFlowHelpText)() : isEditArg ? (0, cli_help_1.newmarkEditHelpText)() : (0, cli_help_1.newmarkHelpText)((0, installUpdate_1.currentAppVersion)()));
|
|
1073
|
+
process.exit(0);
|
|
1074
|
+
}
|
|
1075
|
+
if (isVersionArg) {
|
|
1076
|
+
console.log((0, installUpdate_1.currentAppVersion)());
|
|
1077
|
+
process.exit(0);
|
|
1078
|
+
}
|
|
1079
|
+
const unknownCommand = electron_1.app.isPackaged && !hasCliCommand && !isTuiArg && !isCliArg && !isServerArg && !isViewerArg
|
|
1080
|
+
? (0, cli_discovery_1.unknownTopLevelCommand)(args)
|
|
1081
|
+
: undefined;
|
|
1082
|
+
if (unknownCommand) {
|
|
1083
|
+
console.error(`Unknown Newmark command or argument: ${unknownCommand}. Run --help to see the supported entrypoints.`);
|
|
1084
|
+
process.exit(2);
|
|
1085
|
+
}
|
|
962
1086
|
function viewerEscape(value) {
|
|
963
1087
|
return String(value || '').replace(/[&<>"']/g, character => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[character] || character);
|
|
964
1088
|
}
|
|
@@ -1033,7 +1157,9 @@ if (isViewerArg) {
|
|
|
1033
1157
|
}).catch(error => { console.error(`Unable to open Newmark viewer: ${error instanceof Error ? error.message : String(error)}`); electron_1.app.quit(); });
|
|
1034
1158
|
}
|
|
1035
1159
|
else if (isTuiArg) {
|
|
1036
|
-
const isConsoleLauncher = electron_1.app.isPackaged && path.basename(process.execPath).toLowerCase() === 'newmark.exe'
|
|
1160
|
+
const isConsoleLauncher = electron_1.app.isPackaged && (path.basename(process.execPath).toLowerCase() === 'newmark.exe'
|
|
1161
|
+
|| path.basename(process.execPath).toLowerCase() === 'newmark console runtime.exe'
|
|
1162
|
+
|| process.env.NEWMARK_CONSOLE_WRAPPER === '1');
|
|
1037
1163
|
if (isConsoleLauncher && process.env.NEWMARK_TUI_SIDECAR !== '1') {
|
|
1038
1164
|
const tuiProcess = (0, child_process_1.spawnSync)(process.execPath, [path.join(__dirname, 'launcher.js'), ...args], {
|
|
1039
1165
|
cwd: process.cwd(),
|
|
@@ -1041,6 +1167,11 @@ else if (isTuiArg) {
|
|
|
1041
1167
|
...process.env,
|
|
1042
1168
|
ELECTRON_RUN_AS_NODE: '1',
|
|
1043
1169
|
NEWMARK_TUI_SIDECAR: '1',
|
|
1170
|
+
// The GUI Electron host does not expose the inherited ConPTY handles
|
|
1171
|
+
// as Node TTY streams. The console wrapper has already established
|
|
1172
|
+
// that this is the terminal entrypoint, so preserve the terminal
|
|
1173
|
+
// contract for the sidecar without weakening ordinary GUI launches.
|
|
1174
|
+
NEWMARK_FORCE_TTY: isConsoleLauncher ? '1' : process.env.NEWMARK_FORCE_TTY,
|
|
1044
1175
|
},
|
|
1045
1176
|
stdio: 'inherit',
|
|
1046
1177
|
windowsHide: false,
|
|
@@ -1061,12 +1192,12 @@ else if (isTuiArg) {
|
|
|
1061
1192
|
const root = resolveRoot(args);
|
|
1062
1193
|
firstRunInit(root);
|
|
1063
1194
|
const { start } = require('./tui/src/app');
|
|
1064
|
-
start({ root, workspacePath:
|
|
1195
|
+
start({ root, workspacePath: resolveTuiWorkspacePath(args, root), desktopDist: __dirname });
|
|
1065
1196
|
}
|
|
1066
1197
|
else if (hasCliCommand) {
|
|
1067
1198
|
(async () => {
|
|
1068
1199
|
const root = resolveRoot(args);
|
|
1069
|
-
firstRunInit(root);
|
|
1200
|
+
firstRunInit(root, { readOnly: isReadOnlyValidation });
|
|
1070
1201
|
const handled = await (0, cli_commands_1.runCliCommand)(root, args);
|
|
1071
1202
|
const code = typeof process.exitCode === 'number' ? process.exitCode : 0;
|
|
1072
1203
|
exitCli(handled ? code : 1);
|
|
@@ -1227,6 +1358,51 @@ else {
|
|
|
1227
1358
|
});
|
|
1228
1359
|
electron_1.app.whenReady().then(async () => {
|
|
1229
1360
|
let root = resolveRoot(args);
|
|
1361
|
+
let workspaceRegistryWatcher = null;
|
|
1362
|
+
let workspaceRegistryWatchTimer = null;
|
|
1363
|
+
const workspaceRegistryFiles = new Set(['Local.json', 'External.json', 'State.json']);
|
|
1364
|
+
const broadcastWorkspaceChanged = (files) => {
|
|
1365
|
+
for (const win of electron_1.BrowserWindow.getAllWindows()) {
|
|
1366
|
+
if (win.isDestroyed())
|
|
1367
|
+
continue;
|
|
1368
|
+
win.webContents.send('workspace:changed', { files, root });
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
const refreshWorkspaceRegistryFromDisk = (files) => {
|
|
1372
|
+
if (!agent)
|
|
1373
|
+
return;
|
|
1374
|
+
agent.refreshWorkspaceRegistryFromStorage();
|
|
1375
|
+
const current = agent.workspace.current;
|
|
1376
|
+
if (current)
|
|
1377
|
+
workspaceSelectionCoordinator?.setCurrent(current.id || current.path || current.name);
|
|
1378
|
+
broadcastWorkspaceChanged(files);
|
|
1379
|
+
};
|
|
1380
|
+
const ensureWorkspaceRegistryWatcher = () => {
|
|
1381
|
+
if (workspaceRegistryWatcher)
|
|
1382
|
+
return;
|
|
1383
|
+
const workDir = path.join(root, 'Work');
|
|
1384
|
+
try {
|
|
1385
|
+
workspaceRegistryWatcher = fs.watch(workDir, { persistent: false }, (_eventType, filename) => {
|
|
1386
|
+
const changed = String(filename || '');
|
|
1387
|
+
if (changed && !workspaceRegistryFiles.has(path.basename(changed)))
|
|
1388
|
+
return;
|
|
1389
|
+
if (workspaceRegistryWatchTimer)
|
|
1390
|
+
clearTimeout(workspaceRegistryWatchTimer);
|
|
1391
|
+
workspaceRegistryWatchTimer = setTimeout(() => {
|
|
1392
|
+
workspaceRegistryWatchTimer = null;
|
|
1393
|
+
try {
|
|
1394
|
+
refreshWorkspaceRegistryFromDisk(changed ? [path.basename(changed)] : ['Work']);
|
|
1395
|
+
}
|
|
1396
|
+
catch (error) {
|
|
1397
|
+
logStartupFailure('workspace-registry-refresh', error);
|
|
1398
|
+
}
|
|
1399
|
+
}, 90);
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
catch (error) {
|
|
1403
|
+
logStartupFailure('workspace-registry-watch', error);
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1230
1406
|
const fileRouter = new workspaceFileRouter_1.WorkspaceFileRouter(() => path.resolve(agent?.workspace.current?.path || root));
|
|
1231
1407
|
const pdfPreviewServer = new pdfPreviewServer_1.PdfPreviewServer((token, ownerId) => fileRouter.resolvePdfCapability(token, ownerId));
|
|
1232
1408
|
await pdfPreviewServer.start();
|
|
@@ -1513,7 +1689,12 @@ else {
|
|
|
1513
1689
|
rejectUiReadinessById(Number(fileRouterOwnerId), new Error('Startup window closed before UI readiness'));
|
|
1514
1690
|
fileRouter.revokeOwner(fileRouterOwnerId);
|
|
1515
1691
|
pdfPreviewServer.revokeOwner(fileRouterOwnerId);
|
|
1516
|
-
|
|
1692
|
+
const closedHostId = Number(fileRouterOwnerId);
|
|
1693
|
+
browserGuestContentsByHost.delete(closedHostId);
|
|
1694
|
+
for (const [runtimeKey, binding] of browserGuestBindingsByRuntime) {
|
|
1695
|
+
if (binding.hostId === closedHostId)
|
|
1696
|
+
browserGuestBindingsByRuntime.delete(runtimeKey);
|
|
1697
|
+
}
|
|
1517
1698
|
const remaining = electron_1.BrowserWindow.getAllWindows().filter(candidate => !candidate.isDestroyed() && candidate !== win);
|
|
1518
1699
|
if (mainWindow === win)
|
|
1519
1700
|
mainWindow = remaining[0] || null;
|
|
@@ -1628,6 +1809,7 @@ else {
|
|
|
1628
1809
|
agent = new agent_1.Agent(root);
|
|
1629
1810
|
mcpManager = new mcpManager_1.McpManager(root);
|
|
1630
1811
|
activeAgentBackendMode = process.platform === 'win32' && agent.config.getBool('agent', 'run_in_wsl') ? 'wsl' : 'windows';
|
|
1812
|
+
ensureWorkspaceRegistryWatcher();
|
|
1631
1813
|
restoreStoredFlowSuspension();
|
|
1632
1814
|
recordStartup('agent-ready');
|
|
1633
1815
|
}
|
|
@@ -2117,8 +2299,17 @@ else {
|
|
|
2117
2299
|
(0, runtimeLifecycle_1.markRuntimeLifecycleClean)(root, 'main');
|
|
2118
2300
|
};
|
|
2119
2301
|
electron_1.app.on('will-quit', event => {
|
|
2120
|
-
|
|
2302
|
+
// Window-close and tray-exit both enter this path. The runtime pools
|
|
2303
|
+
// must get a bounded graceful-shutdown window regardless of which
|
|
2304
|
+
// surface initiated the close; otherwise a stuck child can keep the
|
|
2305
|
+
// Electron parent and its renderer tree alive indefinitely.
|
|
2306
|
+
if (_forceQuit || !appExitCleanupComplete)
|
|
2121
2307
|
armForcedExitDeadline('will-quit');
|
|
2308
|
+
if (workspaceRegistryWatchTimer)
|
|
2309
|
+
clearTimeout(workspaceRegistryWatchTimer);
|
|
2310
|
+
workspaceRegistryWatchTimer = null;
|
|
2311
|
+
workspaceRegistryWatcher?.close();
|
|
2312
|
+
workspaceRegistryWatcher = null;
|
|
2122
2313
|
startupDeferredTasks?.cancel();
|
|
2123
2314
|
agent?.flushWorkspaceConversationState();
|
|
2124
2315
|
conversationKernel?.flushPersistence();
|
|
@@ -2179,6 +2370,7 @@ else {
|
|
|
2179
2370
|
electronBrowserUseHost = null;
|
|
2180
2371
|
browserControl_1.BrowserControl.setBackend(null);
|
|
2181
2372
|
browserGuestContentsByHost.clear();
|
|
2373
|
+
browserGuestBindingsByRuntime.clear();
|
|
2182
2374
|
if (sidecarProcess) {
|
|
2183
2375
|
sidecarProcess.kill();
|
|
2184
2376
|
sidecarProcess = null;
|
|
@@ -2448,6 +2640,13 @@ else {
|
|
|
2448
2640
|
else
|
|
2449
2641
|
await electronUtilityRuntimePool?.stopTarget(target);
|
|
2450
2642
|
};
|
|
2643
|
+
const forceStopTargetRuntime = async (target) => {
|
|
2644
|
+
if (wslBackendEnabled())
|
|
2645
|
+
await wslAgentRuntimePool?.forceStopTarget(target);
|
|
2646
|
+
else
|
|
2647
|
+
await electronUtilityRuntimePool?.forceStopTarget(target);
|
|
2648
|
+
};
|
|
2649
|
+
const archiveInFlight = new Map();
|
|
2451
2650
|
const isolatedConversationAgent = (target) => {
|
|
2452
2651
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
2453
2652
|
const isolated = new agent_1.Agent(root, { agentOnly: true });
|
|
@@ -2569,9 +2768,9 @@ else {
|
|
|
2569
2768
|
}
|
|
2570
2769
|
}
|
|
2571
2770
|
});
|
|
2572
|
-
electron_1.ipcMain.handle('browser:registerGuest', (event, guestContentsId) => {
|
|
2771
|
+
electron_1.ipcMain.handle('browser:registerGuest', (event, guestContentsId, target) => {
|
|
2573
2772
|
const guest = electron_1.webContents.fromId(Number(guestContentsId || 0));
|
|
2574
|
-
return { accepted: !!guest && registerBrowserGuest(event.sender, guest) };
|
|
2773
|
+
return { accepted: !!guest && registerBrowserGuest(event.sender, guest, target) };
|
|
2575
2774
|
});
|
|
2576
2775
|
electron_1.ipcMain.handle('browser:control', async (_event, request) => {
|
|
2577
2776
|
return await browserControl_1.BrowserControl.run(request);
|
|
@@ -2955,6 +3154,28 @@ else {
|
|
|
2955
3154
|
runtimeDeferred: false,
|
|
2956
3155
|
};
|
|
2957
3156
|
});
|
|
3157
|
+
electron_1.ipcMain.handle('agent:computerUseState', async (_event, targetInput) => {
|
|
3158
|
+
if (!agent)
|
|
3159
|
+
return { enabled: false, occupied: false, runtimeKey: '' };
|
|
3160
|
+
const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default'));
|
|
3161
|
+
return utilityHostToolHandler.computerUseState(target.runtimeKey);
|
|
3162
|
+
});
|
|
3163
|
+
electron_1.ipcMain.handle('agent:setComputerUseEnabled', async (_event, targetInput, enabled) => {
|
|
3164
|
+
if (!agent)
|
|
3165
|
+
return { ok: false, error: 'Agent not initialized' };
|
|
3166
|
+
const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default'));
|
|
3167
|
+
const result = utilityHostToolHandler.setComputerUseEnabled(target.runtimeKey, enabled !== false, `conversation:${target.conversationId}`);
|
|
3168
|
+
if (!result.ok)
|
|
3169
|
+
return result;
|
|
3170
|
+
if (enabled === false) {
|
|
3171
|
+
try {
|
|
3172
|
+
const { runComputerUse } = require('./tools/computerUse');
|
|
3173
|
+
await runComputerUse({ action: 'takeover_stop', workspacePath: target.workspace?.path || root, ownerId: target.runtimeKey, invocation: 'agent' });
|
|
3174
|
+
}
|
|
3175
|
+
catch { }
|
|
3176
|
+
}
|
|
3177
|
+
return result;
|
|
3178
|
+
});
|
|
2958
3179
|
electron_1.ipcMain.handle('agent:updateGoal', async (_event, goal, targetInput) => {
|
|
2959
3180
|
if (!agent)
|
|
2960
3181
|
return null;
|
|
@@ -3057,6 +3278,7 @@ else {
|
|
|
3057
3278
|
pendingOptions: conversationSnapshot.pendingOptions || agent.pendingOptions,
|
|
3058
3279
|
flowSuspension: flowSuspensionForTarget(target),
|
|
3059
3280
|
flowRunning: flowRunningForTarget(target),
|
|
3281
|
+
computerUse: utilityHostToolHandler.computerUseState((0, conversationTarget_1.normalizeConversationTarget)(target).runtimeKey),
|
|
3060
3282
|
draft: agent.getStoredConversationDraft(target.conversationId) || '',
|
|
3061
3283
|
proxyEnabled: agent.config.getBool('proxy', 'enabled'),
|
|
3062
3284
|
proxyUrl: agent.config.getStr('proxy', 'url'),
|
|
@@ -3618,32 +3840,46 @@ else {
|
|
|
3618
3840
|
return { ok: false, error: 'Agent not initialized' };
|
|
3619
3841
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
3620
3842
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
3621
|
-
|
|
3622
|
-
|
|
3843
|
+
const existing = archiveInFlight.get(normalized.runtimeKey);
|
|
3844
|
+
if (existing)
|
|
3845
|
+
return existing;
|
|
3846
|
+
const operation = (async () => {
|
|
3847
|
+
mutatingRuntimeKeys.add(normalized.runtimeKey);
|
|
3848
|
+
try {
|
|
3849
|
+
// Archive is a destructive lifecycle command. It intentionally
|
|
3850
|
+
// bypasses the normal mutation/active-prompt guard and hard-stops
|
|
3851
|
+
// any resident runtime before touching conversation persistence.
|
|
3852
|
+
await forceStopTargetRuntime(normalized);
|
|
3853
|
+
const currentWorkspacePath = path.resolve(agent.workspace.current?.path || '');
|
|
3854
|
+
const targetWorkspacePath = path.resolve(normalized.workspace?.path || '');
|
|
3855
|
+
const ownsTargetWorkspace = !!normalized.workspace
|
|
3856
|
+
&& !!agent.workspace.current
|
|
3857
|
+
&& currentWorkspacePath === targetWorkspacePath;
|
|
3858
|
+
// The host Agent owns the current workspace persistence cache. An
|
|
3859
|
+
// isolated owner is used for another workspace. The archive writer
|
|
3860
|
+
// starts payload I/O in parallel and finalizes deletion against the
|
|
3861
|
+
// latest locked state snapshot, so rapid clicks do not serialize on
|
|
3862
|
+
// large Markdown bodies or lose a sibling deletion.
|
|
3863
|
+
const archiveOwner = ownsTargetWorkspace ? agent : isolatedConversationAgent(normalized);
|
|
3864
|
+
const archived = await archiveOwner.archiveConversationAsync(normalized.conversationId);
|
|
3865
|
+
if (!archived)
|
|
3866
|
+
return { ok: false, error: 'Conversation archive could not be written.' };
|
|
3867
|
+
return { ok: true, fileName: archived, conversationId: normalized.conversationId, workspaceId: normalized.workspaceId };
|
|
3868
|
+
}
|
|
3869
|
+
catch (error) {
|
|
3870
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
3871
|
+
}
|
|
3872
|
+
finally {
|
|
3873
|
+
mutatingRuntimeKeys.delete(normalized.runtimeKey);
|
|
3874
|
+
}
|
|
3875
|
+
})();
|
|
3876
|
+
archiveInFlight.set(normalized.runtimeKey, operation);
|
|
3623
3877
|
try {
|
|
3624
|
-
|
|
3625
|
-
if (peek.running || peek.stopping)
|
|
3626
|
-
return { ok: false, error: 'Cannot archive a conversation while its runtime is running or stopping.' };
|
|
3627
|
-
if (peek.resident)
|
|
3628
|
-
await stopTargetRuntime(normalized);
|
|
3629
|
-
const currentWorkspacePath = path.resolve(agent.workspace.current?.path || '');
|
|
3630
|
-
const targetWorkspacePath = path.resolve(normalized.workspace?.path || '');
|
|
3631
|
-
const ownsTargetWorkspace = !!normalized.workspace
|
|
3632
|
-
&& !!agent.workspace.current
|
|
3633
|
-
&& currentWorkspacePath === targetWorkspacePath;
|
|
3634
|
-
// The host Agent owns the current workspace persistence cache. Archiving
|
|
3635
|
-
// through it prevents a delayed host flush from resurrecting the target.
|
|
3636
|
-
const archiveOwner = ownsTargetWorkspace ? agent : isolatedConversationAgent(normalized);
|
|
3637
|
-
const archived = archiveOwner.archiveConversation(normalized.conversationId);
|
|
3638
|
-
if (!archived)
|
|
3639
|
-
return { ok: false, error: 'Conversation archive could not be written.' };
|
|
3640
|
-
return { ok: true, fileName: archived, conversationId: normalized.conversationId, workspaceId: normalized.workspaceId };
|
|
3641
|
-
}
|
|
3642
|
-
catch (error) {
|
|
3643
|
-
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
3878
|
+
return await operation;
|
|
3644
3879
|
}
|
|
3645
3880
|
finally {
|
|
3646
|
-
|
|
3881
|
+
if (archiveInFlight.get(normalized.runtimeKey) === operation)
|
|
3882
|
+
archiveInFlight.delete(normalized.runtimeKey);
|
|
3647
3883
|
}
|
|
3648
3884
|
});
|
|
3649
3885
|
electron_1.ipcMain.handle('agent:listArchives', async (_event, scope) => {
|
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),
|
|
@@ -171,6 +173,12 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
171
173
|
onAgentWorkEvent: (callback) => {
|
|
172
174
|
ipcRenderer.on('agent:workEvent', callback);
|
|
173
175
|
},
|
|
176
|
+
onWorkspaceChanged: (callback) => {
|
|
177
|
+
ipcRenderer.on('workspace:changed', (_event, payload) => callback(payload));
|
|
178
|
+
},
|
|
179
|
+
removeWorkspaceChangedListener: () => {
|
|
180
|
+
ipcRenderer.removeAllListeners('workspace:changed');
|
|
181
|
+
},
|
|
174
182
|
removeAgentWorkEventListener: () => {
|
|
175
183
|
ipcRenderer.removeAllListeners('agent:workEvent');
|
|
176
184
|
},
|
|
@@ -96,11 +96,7 @@ class ChatCompletionsAdapter {
|
|
|
96
96
|
let emittedTool = false;
|
|
97
97
|
try {
|
|
98
98
|
while (true) {
|
|
99
|
-
|
|
100
|
-
throw (0, provider_events_1.providerAbortError)(signal);
|
|
101
|
-
const readPromise = reader.read();
|
|
102
|
-
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Stream read timeout')), 30000));
|
|
103
|
-
const { done, value } = await Promise.race([readPromise, timeoutPromise]);
|
|
99
|
+
const { done, value } = await (0, provider_events_1.readProviderStreamChunk)(reader, signal);
|
|
104
100
|
if (done)
|
|
105
101
|
break;
|
|
106
102
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -12,6 +12,13 @@ export declare function defaultProviderTransport(request: SerializedProviderRequ
|
|
|
12
12
|
* (`name === 'AbortError'`, preserves the abort reason when present).
|
|
13
13
|
*/
|
|
14
14
|
export declare function providerAbortError(signal?: AbortSignal): Error;
|
|
15
|
+
export declare function providerStreamTimeoutError(timeoutMs: number): Error;
|
|
16
|
+
/**
|
|
17
|
+
* Read one SSE chunk with both user cancellation and an inactivity deadline.
|
|
18
|
+
* Cancelling the reader is important: rejecting the race alone leaves the
|
|
19
|
+
* provider socket alive and lets later requests accumulate behind it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readProviderStreamChunk(reader: ReadableStreamDefaultReader<Uint8Array>, signal: AbortSignal, timeoutMs?: number): Promise<ReadableStreamReadResult<Uint8Array>>;
|
|
15
22
|
export declare function parseProviderSse(raw: string): Array<{
|
|
16
23
|
event?: string;
|
|
17
24
|
data: string;
|