newmark-agent 0.3.11 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.example.json +6 -0
- package/dist/cli-commands.d.ts +8 -0
- package/dist/cli-commands.js +216 -16
- package/dist/cli-discovery.d.ts +15 -0
- package/dist/cli-discovery.js +182 -0
- package/dist/cli-help.d.ts +2 -0
- package/dist/cli-help.js +25 -1
- package/dist/context/domain/types.d.ts +37 -0
- package/dist/context/services/context-orchestrator.js +2 -0
- package/dist/conversation-utility-host.bundle.cjs +1503 -214
- package/dist/conversation-utility-host.js +3 -0
- package/dist/core/agent.d.ts +157 -8
- package/dist/core/agent.js +1176 -112
- package/dist/core/agentKernel/agent-loop.js +29 -3
- package/dist/core/agentKernel/types.d.ts +7 -0
- package/dist/core/agentKernelRunner.d.ts +2 -0
- package/dist/core/agentKernelRunner.js +174 -27
- package/dist/core/config.d.ts +7 -2
- package/dist/core/config.js +24 -6
- package/dist/core/conversationKernel.d.ts +5 -0
- package/dist/core/conversationKernel.js +30 -1
- package/dist/core/dshCompatibility.d.ts +198 -0
- package/dist/core/dshCompatibility.js +600 -0
- package/dist/core/electronUtilityAgentClient.d.ts +4 -0
- package/dist/core/electronUtilityAgentClient.js +4 -0
- package/dist/core/electronUtilityRuntimePool.d.ts +19 -0
- package/dist/core/electronUtilityRuntimePool.js +76 -0
- package/dist/core/flow-runner.js +1 -1
- package/dist/core/mcpManager.d.ts +1 -0
- package/dist/core/mcpManager.js +100 -10
- package/dist/core/modelValidationStore.d.ts +4 -1
- package/dist/core/modelValidationStore.js +7 -1
- package/dist/core/subagent.d.ts +6 -0
- package/dist/core/subagent.js +22 -1
- package/dist/core/toolPolicy.d.ts +6 -0
- package/dist/core/toolPolicy.js +49 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/utilityAgentProtocol.d.ts +8 -1
- package/dist/core/workspace.d.ts +15 -0
- package/dist/core/workspace.js +62 -1
- package/dist/core/wslAgentClient.d.ts +4 -0
- package/dist/core/wslAgentClient.js +4 -0
- package/dist/core/wslAgentProtocol.d.ts +8 -1
- package/dist/core/wslAgentRuntimePool.d.ts +12 -0
- package/dist/core/wslAgentRuntimePool.js +71 -0
- package/dist/launcher.js +48 -11
- package/dist/llm/provider.d.ts +9 -6
- package/dist/llm/provider.js +89 -36
- package/dist/main.js +326 -52
- package/dist/preload.js +17 -0
- package/dist/providers/chat-completions.adapter.js +42 -20
- package/dist/providers/provider-adapter.d.ts +3 -0
- 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/toolchain/registry/tool-registry.d.ts +13 -1
- package/dist/toolchain/registry/tool-registry.js +8 -0
- package/dist/toolchain/registry-seeder.js +51 -5
- package/dist/tools/index.js +11 -2
- package/dist/tools/nativeTools.js +5 -1
- 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 +2775 -284
- package/dist/ui/lucide-sprite.svg +26 -0
- package/dist/wsl-agent-host.bundle.cjs +1503 -214
- package/dist/wsl-agent-host.js +3 -0
- package/package.json +16 -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");
|
|
@@ -72,6 +73,7 @@ const startupPrewarm_1 = require("./core/startupPrewarm");
|
|
|
72
73
|
const runtimeShutdown_1 = require("./core/runtimeShutdown");
|
|
73
74
|
const runtimeLifecycle_1 = require("./core/runtimeLifecycle");
|
|
74
75
|
const compat_1 = require("./core/compat");
|
|
76
|
+
const dshCompatibility_1 = require("./core/dshCompatibility");
|
|
75
77
|
const mcpManager_1 = require("./core/mcpManager");
|
|
76
78
|
const cli_help_1 = require("./cli-help");
|
|
77
79
|
const APP_NAME = 'Newmark Agent';
|
|
@@ -122,6 +124,7 @@ let browserUseEngine = null;
|
|
|
122
124
|
// the authoritative Browser-Use/right-sidebar binding.
|
|
123
125
|
const browserGuestContentsByHost = new Map();
|
|
124
126
|
const browserGuestBindingsByRuntime = new Map();
|
|
127
|
+
const browserGuestKeyboardBridgeIds = new Set();
|
|
125
128
|
function browserGuestRuntimeKey(target) {
|
|
126
129
|
return (0, conversationTarget_1.normalizeConversationTarget)(target).runtimeKey;
|
|
127
130
|
}
|
|
@@ -426,7 +429,11 @@ function createAppIconImage(size) {
|
|
|
426
429
|
return size ? icon.resize({ width: size, height: size }) : icon;
|
|
427
430
|
}
|
|
428
431
|
function userArgs() {
|
|
429
|
-
|
|
432
|
+
const args = process.argv.slice(1);
|
|
433
|
+
// The native Windows console wrapper inserts Electron's `--` boundary before
|
|
434
|
+
// user arguments. Electron leaves that boundary in process.argv; normalize it
|
|
435
|
+
// before command discovery so the literal `Newmark.exe help` path terminates.
|
|
436
|
+
return args[0] === '--' ? args.slice(1) : args;
|
|
430
437
|
}
|
|
431
438
|
function argValue(args, key) {
|
|
432
439
|
const idx = args.indexOf(key);
|
|
@@ -486,14 +493,14 @@ function positionalAfter(args, commandName) {
|
|
|
486
493
|
return values;
|
|
487
494
|
}
|
|
488
495
|
// First-run initialization
|
|
489
|
-
function firstRunInit(root) {
|
|
496
|
+
function firstRunInit(root, options = {}) {
|
|
490
497
|
fs.mkdirSync(root, { recursive: true });
|
|
491
498
|
for (const d of ['skills', 'Work', 'Flow', 'archive', 'Memory Lab']) {
|
|
492
499
|
fs.mkdirSync(path.join(root, d), { recursive: true });
|
|
493
500
|
}
|
|
494
501
|
new memoryLab_1.MemoryLabManager(root);
|
|
495
502
|
const configModule = require('./core/config');
|
|
496
|
-
configModule.ensureRootConfig(root);
|
|
503
|
+
configModule.ensureRootConfig(root, options);
|
|
497
504
|
if (!fs.existsSync(path.join(root, 'agent.md'))) {
|
|
498
505
|
fs.writeFileSync(path.join(root, 'agent.md'), '# Newmark Agent\n\nYou are a powerful coding assistant.\n', 'utf-8');
|
|
499
506
|
}
|
|
@@ -583,18 +590,13 @@ function ensureElectronUtilityRuntimeHost() {
|
|
|
583
590
|
return host;
|
|
584
591
|
}
|
|
585
592
|
function legacyUserDataRoot() {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
catch {
|
|
590
|
-
if (process.platform === 'win32') {
|
|
591
|
-
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
592
|
-
return path.join(appData, 'Newmark Agent');
|
|
593
|
-
}
|
|
594
|
-
if (process.platform === 'darwin')
|
|
595
|
-
return path.join(os.homedir(), 'Library', 'Application Support', 'Newmark Agent');
|
|
596
|
-
return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'Newmark Agent');
|
|
593
|
+
if (process.platform === 'win32') {
|
|
594
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
595
|
+
return path.join(appData, 'Newmark Agent');
|
|
597
596
|
}
|
|
597
|
+
if (process.platform === 'darwin')
|
|
598
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Newmark Agent');
|
|
599
|
+
return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'Newmark Agent');
|
|
598
600
|
}
|
|
599
601
|
function migrateLegacyRuntimeRoot(root) {
|
|
600
602
|
const targetRoot = path.resolve(root);
|
|
@@ -677,9 +679,22 @@ function resolveRoot(args) {
|
|
|
677
679
|
return writableRuntimeRoot(explicitRoot);
|
|
678
680
|
return getRoot();
|
|
679
681
|
}
|
|
682
|
+
function resolveTuiWorkspacePath(args, root) {
|
|
683
|
+
const explicitWorkspace = pathArgValue(args, '--workspace');
|
|
684
|
+
if (explicitWorkspace)
|
|
685
|
+
return explicitWorkspace;
|
|
686
|
+
// An explicitly isolated runtime must not silently register the caller's
|
|
687
|
+
// cwd as an external workspace. Keep the opt-in --workspace escape hatch,
|
|
688
|
+
// while making the safe one-argument form fully self-contained.
|
|
689
|
+
return pathArgValue(args, '--root') ? root : process.cwd();
|
|
690
|
+
}
|
|
691
|
+
// Set immediately after argument resolution so startup failures from an
|
|
692
|
+
// explicit temporary root are recorded beside that root instead of leaking a
|
|
693
|
+
// diagnostic file into the user's canonical runtime.
|
|
694
|
+
let startupRuntimeRoot = '';
|
|
680
695
|
function startupLogPath() {
|
|
681
696
|
try {
|
|
682
|
-
const userData = userRuntimeRoot();
|
|
697
|
+
const userData = startupRuntimeRoot || userRuntimeRoot();
|
|
683
698
|
fs.mkdirSync(userData, { recursive: true });
|
|
684
699
|
return path.join(userData, 'startup.log');
|
|
685
700
|
}
|
|
@@ -832,7 +847,29 @@ function registerBrowserGuest(host, guest, requestedTarget) {
|
|
|
832
847
|
conversationId: target.conversationId,
|
|
833
848
|
});
|
|
834
849
|
browserGuestContentsByHost.set(host.id, guest.id);
|
|
850
|
+
if (!browserGuestKeyboardBridgeIds.has(guest.id)) {
|
|
851
|
+
browserGuestKeyboardBridgeIds.add(guest.id);
|
|
852
|
+
// WebView keyboard events do not bubble to the host renderer. Reserve only
|
|
853
|
+
// the two app-wide discovery surfaces and leave navigation/editing keys to
|
|
854
|
+
// the page itself.
|
|
855
|
+
guest.on('before-input-event', (event, input) => {
|
|
856
|
+
if (input.type !== 'keyDown' || input.isAutoRepeat || host.isDestroyed())
|
|
857
|
+
return;
|
|
858
|
+
const key = String(input.key || '').toLowerCase();
|
|
859
|
+
const noSecondaryModifiers = !input.alt && !input.control && !input.meta && !input.shift;
|
|
860
|
+
let commandId = '';
|
|
861
|
+
if (key === 'f1' && noSecondaryModifiers)
|
|
862
|
+
commandId = 'help.keyboardShortcuts';
|
|
863
|
+
else if (key === 'p' && (input.control || input.meta) && input.shift && !input.alt)
|
|
864
|
+
commandId = 'app.commandPalette';
|
|
865
|
+
if (!commandId)
|
|
866
|
+
return;
|
|
867
|
+
event.preventDefault();
|
|
868
|
+
host.send('keyboard:command', { id: commandId, source: 'browserGuest' });
|
|
869
|
+
});
|
|
870
|
+
}
|
|
835
871
|
guest.once('destroyed', () => {
|
|
872
|
+
browserGuestKeyboardBridgeIds.delete(guest.id);
|
|
836
873
|
if (browserGuestContentsByHost.get(host.id) === guest.id)
|
|
837
874
|
browserGuestContentsByHost.delete(host.id);
|
|
838
875
|
const binding = browserGuestBindingsByRuntime.get(target.runtimeKey);
|
|
@@ -1026,24 +1063,71 @@ const args = userArgs();
|
|
|
1026
1063
|
const command = args.find(a => a === 'flow' || a === 'edit');
|
|
1027
1064
|
const isTuiArg = args.some(arg => arg.toLowerCase() === '--tui');
|
|
1028
1065
|
const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
|
|
1066
|
+
const cliCommand = args.find(a => cli_commands_1.CLI_COMMANDS.includes(a));
|
|
1067
|
+
const isFlowArg = command === 'flow';
|
|
1068
|
+
const isEditArg = command === 'edit';
|
|
1029
1069
|
const isHelpArg = !hasCliCommand && (args.some(arg => ['--help', '-h'].includes(arg.toLowerCase())) || args[0]?.toLowerCase() === 'help');
|
|
1030
|
-
const isVersionArg = !hasCliCommand &&
|
|
1070
|
+
const isVersionArg = !hasCliCommand && (0, cli_discovery_1.isVersionArgument)(args);
|
|
1071
|
+
const isReadOnlyValidation = hasCliCommand && args.includes('validate-models') && !args.includes('--persist');
|
|
1031
1072
|
const isViewerArg = args.some(arg => arg.toLowerCase() === '--newmark-viewer');
|
|
1032
1073
|
const isCliArg = args.includes('--cli');
|
|
1033
1074
|
const isServerArg = args.includes('--server');
|
|
1034
|
-
const
|
|
1035
|
-
|
|
1075
|
+
const invalidArgument = (0, cli_discovery_1.invalidTopLevelArgument)(args);
|
|
1076
|
+
if (invalidArgument) {
|
|
1077
|
+
console.error(`Invalid Newmark argument: ${invalidArgument}`);
|
|
1078
|
+
process.exit(2);
|
|
1079
|
+
}
|
|
1080
|
+
// Keep command-specific help on the same early-exit path as top-level help.
|
|
1081
|
+
// This matters for the packaged Electron entry: a command such as
|
|
1082
|
+
// `send --help` must never create a GUI window, start prewarm work, or touch a
|
|
1083
|
+
// user-data profile merely to print its contract.
|
|
1084
|
+
if (hasCliCommand && cliCommand && (0, cli_commands_1.cliHelpRequested)(args)) {
|
|
1085
|
+
console.log((0, cli_commands_1.cliCommandHelp)(cliCommand));
|
|
1086
|
+
process.exit(0);
|
|
1087
|
+
}
|
|
1088
|
+
// Electron's Chromium profile is a separate state boundary from Newmark's
|
|
1089
|
+
// business root. Bind both before any ready event so --root cannot leave
|
|
1090
|
+
// Preferences, DIPS, DevTools ports, cookies, or session storage in the real
|
|
1091
|
+
// default AppData directory. The dedicated subdirectories keep Chromium's
|
|
1092
|
+
// files separate from the durable Newmark config/workspace files.
|
|
1093
|
+
const runtimeRoot = resolveRoot(args);
|
|
1094
|
+
startupRuntimeRoot = runtimeRoot;
|
|
1095
|
+
const explicitElectronUserDataRoot = pathArgValue(args, '--user-data-dir');
|
|
1096
|
+
const electronUserDataRoot = path.resolve(explicitElectronUserDataRoot || path.join(runtimeRoot, 'Electron'));
|
|
1097
|
+
const electronSessionDataRoot = path.join(electronUserDataRoot, 'session-data');
|
|
1098
|
+
try {
|
|
1099
|
+
fs.mkdirSync(electronSessionDataRoot, { recursive: true });
|
|
1100
|
+
electron_1.app.setPath('userData', electronUserDataRoot);
|
|
1101
|
+
electron_1.app.setPath('sessionData', electronSessionDataRoot);
|
|
1102
|
+
// app.setPath is the Electron API contract; the switch makes the same
|
|
1103
|
+
// boundary visible to Chromium children, including the console-wrapper
|
|
1104
|
+
// `--` forwarding path where an early child may otherwise retain the default
|
|
1105
|
+
// profile before the first BrowserWindow is created.
|
|
1106
|
+
electron_1.app.commandLine.removeSwitch('user-data-dir');
|
|
1107
|
+
electron_1.app.commandLine.appendSwitch('user-data-dir', electronUserDataRoot);
|
|
1108
|
+
}
|
|
1109
|
+
catch (error) {
|
|
1110
|
+
console.error(`Unable to isolate Electron user-data directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
1111
|
+
process.exit(1);
|
|
1112
|
+
}
|
|
1036
1113
|
// Help/version are terminating discovery commands. They must be handled
|
|
1037
1114
|
// before Electron's GUI/TUI/server branches can initialize runtime state or
|
|
1038
1115
|
// spawn a window, so a product-new tester can use them safely in any surface.
|
|
1039
1116
|
if (isHelpArg) {
|
|
1040
|
-
console.log((0, cli_help_1.newmarkHelpText)((0, installUpdate_1.currentAppVersion)()));
|
|
1117
|
+
console.log(isFlowArg ? (0, cli_help_1.newmarkFlowHelpText)() : isEditArg ? (0, cli_help_1.newmarkEditHelpText)() : (0, cli_help_1.newmarkHelpText)((0, installUpdate_1.currentAppVersion)()));
|
|
1041
1118
|
process.exit(0);
|
|
1042
1119
|
}
|
|
1043
1120
|
if (isVersionArg) {
|
|
1044
1121
|
console.log((0, installUpdate_1.currentAppVersion)());
|
|
1045
1122
|
process.exit(0);
|
|
1046
1123
|
}
|
|
1124
|
+
const unknownCommand = electron_1.app.isPackaged && !hasCliCommand && !isTuiArg && !isCliArg && !isServerArg && !isViewerArg
|
|
1125
|
+
? (0, cli_discovery_1.unknownTopLevelCommand)(args)
|
|
1126
|
+
: undefined;
|
|
1127
|
+
if (unknownCommand) {
|
|
1128
|
+
console.error(`Unknown Newmark command or argument: ${unknownCommand}. Run --help to see the supported entrypoints.`);
|
|
1129
|
+
process.exit(2);
|
|
1130
|
+
}
|
|
1047
1131
|
function viewerEscape(value) {
|
|
1048
1132
|
return String(value || '').replace(/[&<>"']/g, character => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[character] || character);
|
|
1049
1133
|
}
|
|
@@ -1118,7 +1202,9 @@ if (isViewerArg) {
|
|
|
1118
1202
|
}).catch(error => { console.error(`Unable to open Newmark viewer: ${error instanceof Error ? error.message : String(error)}`); electron_1.app.quit(); });
|
|
1119
1203
|
}
|
|
1120
1204
|
else if (isTuiArg) {
|
|
1121
|
-
const isConsoleLauncher = electron_1.app.isPackaged && path.basename(process.execPath).toLowerCase() === 'newmark.exe'
|
|
1205
|
+
const isConsoleLauncher = electron_1.app.isPackaged && (path.basename(process.execPath).toLowerCase() === 'newmark.exe'
|
|
1206
|
+
|| path.basename(process.execPath).toLowerCase() === 'newmark console runtime.exe'
|
|
1207
|
+
|| process.env.NEWMARK_CONSOLE_WRAPPER === '1');
|
|
1122
1208
|
if (isConsoleLauncher && process.env.NEWMARK_TUI_SIDECAR !== '1') {
|
|
1123
1209
|
const tuiProcess = (0, child_process_1.spawnSync)(process.execPath, [path.join(__dirname, 'launcher.js'), ...args], {
|
|
1124
1210
|
cwd: process.cwd(),
|
|
@@ -1126,6 +1212,11 @@ else if (isTuiArg) {
|
|
|
1126
1212
|
...process.env,
|
|
1127
1213
|
ELECTRON_RUN_AS_NODE: '1',
|
|
1128
1214
|
NEWMARK_TUI_SIDECAR: '1',
|
|
1215
|
+
// The GUI Electron host does not expose the inherited ConPTY handles
|
|
1216
|
+
// as Node TTY streams. The console wrapper has already established
|
|
1217
|
+
// that this is the terminal entrypoint, so preserve the terminal
|
|
1218
|
+
// contract for the sidecar without weakening ordinary GUI launches.
|
|
1219
|
+
NEWMARK_FORCE_TTY: isConsoleLauncher ? '1' : process.env.NEWMARK_FORCE_TTY,
|
|
1129
1220
|
},
|
|
1130
1221
|
stdio: 'inherit',
|
|
1131
1222
|
windowsHide: false,
|
|
@@ -1146,12 +1237,12 @@ else if (isTuiArg) {
|
|
|
1146
1237
|
const root = resolveRoot(args);
|
|
1147
1238
|
firstRunInit(root);
|
|
1148
1239
|
const { start } = require('./tui/src/app');
|
|
1149
|
-
start({ root, workspacePath:
|
|
1240
|
+
start({ root, workspacePath: resolveTuiWorkspacePath(args, root), desktopDist: __dirname });
|
|
1150
1241
|
}
|
|
1151
1242
|
else if (hasCliCommand) {
|
|
1152
1243
|
(async () => {
|
|
1153
1244
|
const root = resolveRoot(args);
|
|
1154
|
-
firstRunInit(root);
|
|
1245
|
+
firstRunInit(root, { readOnly: isReadOnlyValidation });
|
|
1155
1246
|
const handled = await (0, cli_commands_1.runCliCommand)(root, args);
|
|
1156
1247
|
const code = typeof process.exitCode === 'number' ? process.exitCode : 0;
|
|
1157
1248
|
exitCli(handled ? code : 1);
|
|
@@ -1312,6 +1403,51 @@ else {
|
|
|
1312
1403
|
});
|
|
1313
1404
|
electron_1.app.whenReady().then(async () => {
|
|
1314
1405
|
let root = resolveRoot(args);
|
|
1406
|
+
let workspaceRegistryWatcher = null;
|
|
1407
|
+
let workspaceRegistryWatchTimer = null;
|
|
1408
|
+
const workspaceRegistryFiles = new Set(['Local.json', 'External.json', 'State.json']);
|
|
1409
|
+
const broadcastWorkspaceChanged = (files) => {
|
|
1410
|
+
for (const win of electron_1.BrowserWindow.getAllWindows()) {
|
|
1411
|
+
if (win.isDestroyed())
|
|
1412
|
+
continue;
|
|
1413
|
+
win.webContents.send('workspace:changed', { files, root });
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
const refreshWorkspaceRegistryFromDisk = (files) => {
|
|
1417
|
+
if (!agent)
|
|
1418
|
+
return;
|
|
1419
|
+
agent.refreshWorkspaceRegistryFromStorage();
|
|
1420
|
+
const current = agent.workspace.current;
|
|
1421
|
+
if (current)
|
|
1422
|
+
workspaceSelectionCoordinator?.setCurrent(current.id || current.path || current.name);
|
|
1423
|
+
broadcastWorkspaceChanged(files);
|
|
1424
|
+
};
|
|
1425
|
+
const ensureWorkspaceRegistryWatcher = () => {
|
|
1426
|
+
if (workspaceRegistryWatcher)
|
|
1427
|
+
return;
|
|
1428
|
+
const workDir = path.join(root, 'Work');
|
|
1429
|
+
try {
|
|
1430
|
+
workspaceRegistryWatcher = fs.watch(workDir, { persistent: false }, (_eventType, filename) => {
|
|
1431
|
+
const changed = String(filename || '');
|
|
1432
|
+
if (changed && !workspaceRegistryFiles.has(path.basename(changed)))
|
|
1433
|
+
return;
|
|
1434
|
+
if (workspaceRegistryWatchTimer)
|
|
1435
|
+
clearTimeout(workspaceRegistryWatchTimer);
|
|
1436
|
+
workspaceRegistryWatchTimer = setTimeout(() => {
|
|
1437
|
+
workspaceRegistryWatchTimer = null;
|
|
1438
|
+
try {
|
|
1439
|
+
refreshWorkspaceRegistryFromDisk(changed ? [path.basename(changed)] : ['Work']);
|
|
1440
|
+
}
|
|
1441
|
+
catch (error) {
|
|
1442
|
+
logStartupFailure('workspace-registry-refresh', error);
|
|
1443
|
+
}
|
|
1444
|
+
}, 90);
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
catch (error) {
|
|
1448
|
+
logStartupFailure('workspace-registry-watch', error);
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1315
1451
|
const fileRouter = new workspaceFileRouter_1.WorkspaceFileRouter(() => path.resolve(agent?.workspace.current?.path || root));
|
|
1316
1452
|
const pdfPreviewServer = new pdfPreviewServer_1.PdfPreviewServer((token, ownerId) => fileRouter.resolvePdfCapability(token, ownerId));
|
|
1317
1453
|
await pdfPreviewServer.start();
|
|
@@ -1568,6 +1704,12 @@ else {
|
|
|
1568
1704
|
win.focus();
|
|
1569
1705
|
}, 150);
|
|
1570
1706
|
});
|
|
1707
|
+
win.webContents.on('unresponsive', () => {
|
|
1708
|
+
logStartupFailure('renderer-unresponsive', new Error(`Window renderer became unresponsive (webContents ${win.webContents.id})`));
|
|
1709
|
+
});
|
|
1710
|
+
win.webContents.on('responsive', () => {
|
|
1711
|
+
recordStartup(`renderer-responsive-${win.webContents.id}`);
|
|
1712
|
+
});
|
|
1571
1713
|
if (!automationWakeMode)
|
|
1572
1714
|
win.maximize();
|
|
1573
1715
|
if (!automationWakeMode && showWindow) {
|
|
@@ -1583,15 +1725,18 @@ else {
|
|
|
1583
1725
|
return;
|
|
1584
1726
|
if (agent) {
|
|
1585
1727
|
const closeBehavior = agent.config.getStr('general', 'close_behavior');
|
|
1728
|
+
recordStartup(`window-close-requested-${closeBehavior || 'exit'}`);
|
|
1586
1729
|
if (closeBehavior === 'minimize') {
|
|
1587
1730
|
e.preventDefault();
|
|
1588
1731
|
win.hide();
|
|
1589
1732
|
createTray();
|
|
1733
|
+
recordStartup('window-hidden-to-tray');
|
|
1590
1734
|
return;
|
|
1591
1735
|
}
|
|
1592
1736
|
if (agent.config.getBool('general', 'auto_archive_on_close')) {
|
|
1593
1737
|
agent.archiveSession();
|
|
1594
1738
|
}
|
|
1739
|
+
recordStartup('window-close-exit');
|
|
1595
1740
|
}
|
|
1596
1741
|
});
|
|
1597
1742
|
win.on('closed', () => {
|
|
@@ -1718,6 +1863,7 @@ else {
|
|
|
1718
1863
|
agent = new agent_1.Agent(root);
|
|
1719
1864
|
mcpManager = new mcpManager_1.McpManager(root);
|
|
1720
1865
|
activeAgentBackendMode = process.platform === 'win32' && agent.config.getBool('agent', 'run_in_wsl') ? 'wsl' : 'windows';
|
|
1866
|
+
ensureWorkspaceRegistryWatcher();
|
|
1721
1867
|
restoreStoredFlowSuspension();
|
|
1722
1868
|
recordStartup('agent-ready');
|
|
1723
1869
|
}
|
|
@@ -2207,8 +2353,18 @@ else {
|
|
|
2207
2353
|
(0, runtimeLifecycle_1.markRuntimeLifecycleClean)(root, 'main');
|
|
2208
2354
|
};
|
|
2209
2355
|
electron_1.app.on('will-quit', event => {
|
|
2210
|
-
|
|
2356
|
+
recordStartup('will-quit');
|
|
2357
|
+
// Window-close and tray-exit both enter this path. The runtime pools
|
|
2358
|
+
// must get a bounded graceful-shutdown window regardless of which
|
|
2359
|
+
// surface initiated the close; otherwise a stuck child can keep the
|
|
2360
|
+
// Electron parent and its renderer tree alive indefinitely.
|
|
2361
|
+
if (_forceQuit || !appExitCleanupComplete)
|
|
2211
2362
|
armForcedExitDeadline('will-quit');
|
|
2363
|
+
if (workspaceRegistryWatchTimer)
|
|
2364
|
+
clearTimeout(workspaceRegistryWatchTimer);
|
|
2365
|
+
workspaceRegistryWatchTimer = null;
|
|
2366
|
+
workspaceRegistryWatcher?.close();
|
|
2367
|
+
workspaceRegistryWatcher = null;
|
|
2212
2368
|
startupDeferredTasks?.cancel();
|
|
2213
2369
|
agent?.flushWorkspaceConversationState();
|
|
2214
2370
|
conversationKernel?.flushPersistence();
|
|
@@ -2361,6 +2517,32 @@ else {
|
|
|
2361
2517
|
activeFlowsByRuntimeKey.delete(key);
|
|
2362
2518
|
agent.clearStoredFlowSuspension(state.target.conversationId);
|
|
2363
2519
|
};
|
|
2520
|
+
// Archive is a destructive lifecycle boundary. It must cancel the Flow
|
|
2521
|
+
// owner itself before touching the runtime pool; Flow runs bypass the
|
|
2522
|
+
// ConversationKernel and therefore cannot be stopped by the pool alone.
|
|
2523
|
+
// Marking the state before aborting is important: the provider may reject
|
|
2524
|
+
// on the same turn, and its late catch/finally must not recreate a paused
|
|
2525
|
+
// conversation after archive has removed it.
|
|
2526
|
+
const interruptActiveFlowForArchive = (target) => {
|
|
2527
|
+
const key = activeFlowStateKey(target);
|
|
2528
|
+
const state = activeFlowsByRuntimeKey.get(key) || null;
|
|
2529
|
+
if (!state)
|
|
2530
|
+
return;
|
|
2531
|
+
state.archiveRequested = true;
|
|
2532
|
+
try {
|
|
2533
|
+
state.abortController?.abort(new Error(`Flow discarded because conversation was archived: ${state.name || state.workflow.name}`));
|
|
2534
|
+
}
|
|
2535
|
+
catch { }
|
|
2536
|
+
state.flowAgent?.abortActiveKernelRun();
|
|
2537
|
+
state.flowAgent?.interruptRunningConversationWorkRuns(state.target, 'force_interrupted');
|
|
2538
|
+
state.flowAgent?.clearStoredFlowSuspension(state.target.conversationId);
|
|
2539
|
+
if (agent?.workspace.current
|
|
2540
|
+
&& target.workspace
|
|
2541
|
+
&& path.resolve(agent.workspace.current.path) === path.resolve(target.workspace.path)) {
|
|
2542
|
+
agent.clearStoredFlowSuspension(target.conversationId);
|
|
2543
|
+
}
|
|
2544
|
+
activeFlowsByRuntimeKey.delete(key);
|
|
2545
|
+
};
|
|
2364
2546
|
const utilityHostToolHandler = (0, utilityHostToolRouter_1.createUtilityHostToolHandler)({
|
|
2365
2547
|
persistenceRoot: root,
|
|
2366
2548
|
isToolEnabled: toolName => !!agent && (0, nativeTools_1.isNativeToolEnabled)(toolName, agent.config.nativeToolEnabled()),
|
|
@@ -2539,6 +2721,13 @@ else {
|
|
|
2539
2721
|
else
|
|
2540
2722
|
await electronUtilityRuntimePool?.stopTarget(target);
|
|
2541
2723
|
};
|
|
2724
|
+
const forceStopTargetRuntime = async (target) => {
|
|
2725
|
+
if (wslBackendEnabled())
|
|
2726
|
+
await wslAgentRuntimePool?.forceStopTarget(target);
|
|
2727
|
+
else
|
|
2728
|
+
await electronUtilityRuntimePool?.forceStopTarget(target);
|
|
2729
|
+
};
|
|
2730
|
+
const archiveInFlight = new Map();
|
|
2542
2731
|
const isolatedConversationAgent = (target) => {
|
|
2543
2732
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
2544
2733
|
const isolated = new agent_1.Agent(root, { agentOnly: true });
|
|
@@ -2743,6 +2932,14 @@ else {
|
|
|
2743
2932
|
};
|
|
2744
2933
|
}
|
|
2745
2934
|
catch (e) {
|
|
2935
|
+
if (flowState.archiveRequested) {
|
|
2936
|
+
// Archive already force-finalized the current Build ledger. Do not
|
|
2937
|
+
// persist an interrupted Flow suspension or return Flow ownership
|
|
2938
|
+
// to the renderer after the target has been removed.
|
|
2939
|
+
flowAgent.pendingOptions = [];
|
|
2940
|
+
suspended = false;
|
|
2941
|
+
return { ok: false, archived: true, error: 'Flow discarded because the conversation was archived.' };
|
|
2942
|
+
}
|
|
2746
2943
|
if (e instanceof flow_runner_1.FlowQuestionPendingError) {
|
|
2747
2944
|
suspended = true;
|
|
2748
2945
|
flowAgent.flowPc = e.componentId;
|
|
@@ -2836,6 +3033,11 @@ else {
|
|
|
2836
3033
|
const flowKey = activeFlowStateKey(flowTarget);
|
|
2837
3034
|
const flowAgent = isolatedConversationAgent(flowTarget);
|
|
2838
3035
|
suspension.flowAgent = flowAgent;
|
|
3036
|
+
// A previous Flow may have been interrupted just before its isolated
|
|
3037
|
+
// Agent flushed the work ledger. Reconcile only this explicitly paused
|
|
3038
|
+
// target before starting the next component; the normal runner guard
|
|
3039
|
+
// remains intact for genuine concurrent Builds.
|
|
3040
|
+
flowAgent.interruptRunningConversationWorkRuns(flowTarget, 'interrupted');
|
|
2839
3041
|
const flowAbortController = new AbortController();
|
|
2840
3042
|
suspension.abortController = flowAbortController;
|
|
2841
3043
|
activeFlowsByRuntimeKey.set(flowKey, suspension);
|
|
@@ -2870,6 +3072,11 @@ else {
|
|
|
2870
3072
|
};
|
|
2871
3073
|
}
|
|
2872
3074
|
catch (error) {
|
|
3075
|
+
if (suspension.archiveRequested) {
|
|
3076
|
+
flowAgent.pendingOptions = [];
|
|
3077
|
+
suspendedAgain = false;
|
|
3078
|
+
return { ok: false, archived: true, error: 'Flow discarded because the conversation was archived.' };
|
|
3079
|
+
}
|
|
2873
3080
|
if (error instanceof flow_runner_1.FlowQuestionPendingError) {
|
|
2874
3081
|
suspendedAgain = true;
|
|
2875
3082
|
flowAgent.flowPc = error.componentId;
|
|
@@ -2891,9 +3098,9 @@ else {
|
|
|
2891
3098
|
workRuns: flowAgent.getConversationSnapshot(flowAgent.activeConversationId).workRuns,
|
|
2892
3099
|
};
|
|
2893
3100
|
}
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
3101
|
+
// A Stop/Esc during a resumed Flow is another pause, not a terminal
|
|
3102
|
+
// IPC failure. Fall through to the same interrupted-suspension path as
|
|
3103
|
+
// the initial Flow run so repeated pause/resume clicks remain valid.
|
|
2897
3104
|
suspendedAgain = true;
|
|
2898
3105
|
const resumeFailure = error;
|
|
2899
3106
|
const interruptedComponentId = typeof resumeFailure.componentId === 'number'
|
|
@@ -3046,6 +3253,12 @@ else {
|
|
|
3046
3253
|
runtimeDeferred: false,
|
|
3047
3254
|
};
|
|
3048
3255
|
});
|
|
3256
|
+
electron_1.ipcMain.handle('agent:setConversationBranchCommunication', async (_event, targetInput, enabled) => {
|
|
3257
|
+
if (!agent)
|
|
3258
|
+
return false;
|
|
3259
|
+
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
3260
|
+
return mutateTargetConversation(target, () => isolatedConversationAgent(target).setBranchCommunication(enabled !== false));
|
|
3261
|
+
});
|
|
3049
3262
|
electron_1.ipcMain.handle('agent:computerUseState', async (_event, targetInput) => {
|
|
3050
3263
|
if (!agent)
|
|
3051
3264
|
return { enabled: false, occupied: false, runtimeKey: '' };
|
|
@@ -3583,6 +3796,18 @@ else {
|
|
|
3583
3796
|
? await ensureWslConversationPool().checkpoint(target)
|
|
3584
3797
|
: await ensureElectronUtilityPool().checkpoint(target);
|
|
3585
3798
|
});
|
|
3799
|
+
electron_1.ipcMain.handle('agent:compressContext', async (_event, request) => {
|
|
3800
|
+
if (!agent)
|
|
3801
|
+
throw new Error('Agent not initialized');
|
|
3802
|
+
const target = conversationRuntimeTarget(request);
|
|
3803
|
+
const options = {
|
|
3804
|
+
keepRecent: Number.isFinite(Number(request?.keepRecent)) ? Math.floor(Number(request.keepRecent)) : undefined,
|
|
3805
|
+
force: request?.force !== false,
|
|
3806
|
+
};
|
|
3807
|
+
return wslBackendEnabled()
|
|
3808
|
+
? await ensureWslConversationPool().contextCompress(target, options)
|
|
3809
|
+
: await ensureElectronUtilityPool().contextCompress(target, options);
|
|
3810
|
+
});
|
|
3586
3811
|
electron_1.ipcMain.handle('agent:rateAutoRoute', async (_event, request) => {
|
|
3587
3812
|
if (!agent)
|
|
3588
3813
|
return { ok: false, reason: 'no_active_auto_route' };
|
|
@@ -3732,32 +3957,52 @@ else {
|
|
|
3732
3957
|
return { ok: false, error: 'Agent not initialized' };
|
|
3733
3958
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
3734
3959
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
3735
|
-
|
|
3736
|
-
|
|
3960
|
+
const existing = archiveInFlight.get(normalized.runtimeKey);
|
|
3961
|
+
if (existing)
|
|
3962
|
+
return existing;
|
|
3963
|
+
const operation = (async () => {
|
|
3964
|
+
mutatingRuntimeKeys.add(normalized.runtimeKey);
|
|
3965
|
+
try {
|
|
3966
|
+
// Archive is a destructive lifecycle command. It intentionally
|
|
3967
|
+
// bypasses the normal mutation/active-prompt guard. Cancel the
|
|
3968
|
+
// conversation-local Flow synchronously, then start the resident
|
|
3969
|
+
// runtime hard-stop in the background so a stuck child cannot hold
|
|
3970
|
+
// the archive click hostage.
|
|
3971
|
+
interruptActiveFlowForArchive(normalized);
|
|
3972
|
+
void forceStopTargetRuntime(normalized).catch(error => {
|
|
3973
|
+
console.error('[Newmark] archive runtime force-stop failed:', error instanceof Error ? error.message : String(error));
|
|
3974
|
+
});
|
|
3975
|
+
const currentWorkspacePath = path.resolve(agent.workspace.current?.path || '');
|
|
3976
|
+
const targetWorkspacePath = path.resolve(normalized.workspace?.path || '');
|
|
3977
|
+
const ownsTargetWorkspace = !!normalized.workspace
|
|
3978
|
+
&& !!agent.workspace.current
|
|
3979
|
+
&& currentWorkspacePath === targetWorkspacePath;
|
|
3980
|
+
// The host Agent owns the current workspace persistence cache. An
|
|
3981
|
+
// isolated owner is used for another workspace. The archive writer
|
|
3982
|
+
// starts payload I/O in parallel and finalizes deletion against the
|
|
3983
|
+
// latest locked state snapshot, so rapid clicks do not serialize on
|
|
3984
|
+
// large Markdown bodies or lose a sibling deletion.
|
|
3985
|
+
const archiveOwner = ownsTargetWorkspace ? agent : isolatedConversationAgent(normalized);
|
|
3986
|
+
archiveOwner.clearStoredFlowSuspension(normalized.conversationId);
|
|
3987
|
+
const archived = await archiveOwner.archiveConversationAsync(normalized.conversationId);
|
|
3988
|
+
if (!archived)
|
|
3989
|
+
return { ok: false, error: 'Conversation archive could not be written.' };
|
|
3990
|
+
return { ok: true, fileName: archived, conversationId: normalized.conversationId, workspaceId: normalized.workspaceId };
|
|
3991
|
+
}
|
|
3992
|
+
catch (error) {
|
|
3993
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
3994
|
+
}
|
|
3995
|
+
finally {
|
|
3996
|
+
mutatingRuntimeKeys.delete(normalized.runtimeKey);
|
|
3997
|
+
}
|
|
3998
|
+
})();
|
|
3999
|
+
archiveInFlight.set(normalized.runtimeKey, operation);
|
|
3737
4000
|
try {
|
|
3738
|
-
|
|
3739
|
-
if (peek.running || peek.stopping)
|
|
3740
|
-
return { ok: false, error: 'Cannot archive a conversation while its runtime is running or stopping.' };
|
|
3741
|
-
if (peek.resident)
|
|
3742
|
-
await stopTargetRuntime(normalized);
|
|
3743
|
-
const currentWorkspacePath = path.resolve(agent.workspace.current?.path || '');
|
|
3744
|
-
const targetWorkspacePath = path.resolve(normalized.workspace?.path || '');
|
|
3745
|
-
const ownsTargetWorkspace = !!normalized.workspace
|
|
3746
|
-
&& !!agent.workspace.current
|
|
3747
|
-
&& currentWorkspacePath === targetWorkspacePath;
|
|
3748
|
-
// The host Agent owns the current workspace persistence cache. Archiving
|
|
3749
|
-
// through it prevents a delayed host flush from resurrecting the target.
|
|
3750
|
-
const archiveOwner = ownsTargetWorkspace ? agent : isolatedConversationAgent(normalized);
|
|
3751
|
-
const archived = archiveOwner.archiveConversation(normalized.conversationId);
|
|
3752
|
-
if (!archived)
|
|
3753
|
-
return { ok: false, error: 'Conversation archive could not be written.' };
|
|
3754
|
-
return { ok: true, fileName: archived, conversationId: normalized.conversationId, workspaceId: normalized.workspaceId };
|
|
3755
|
-
}
|
|
3756
|
-
catch (error) {
|
|
3757
|
-
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
4001
|
+
return await operation;
|
|
3758
4002
|
}
|
|
3759
4003
|
finally {
|
|
3760
|
-
|
|
4004
|
+
if (archiveInFlight.get(normalized.runtimeKey) === operation)
|
|
4005
|
+
archiveInFlight.delete(normalized.runtimeKey);
|
|
3761
4006
|
}
|
|
3762
4007
|
});
|
|
3763
4008
|
electron_1.ipcMain.handle('agent:listArchives', async (_event, scope) => {
|
|
@@ -4083,6 +4328,20 @@ else {
|
|
|
4083
4328
|
}
|
|
4084
4329
|
return { error: 'Agent not initialized' };
|
|
4085
4330
|
});
|
|
4331
|
+
electron_1.ipcMain.handle('app:openWebUrl', async (_event, rawUrl) => {
|
|
4332
|
+
try {
|
|
4333
|
+
const target = new URL(String(rawUrl || ''));
|
|
4334
|
+
const allowedHosts = new Set(['github.com', 'www.github.com', 'npmjs.com', 'www.npmjs.com']);
|
|
4335
|
+
if (target.protocol !== 'https:' || !allowedHosts.has(target.hostname.toLowerCase()) || !!target.username || !!target.password || (!!target.port && target.port !== '443')) {
|
|
4336
|
+
return { ok: false, error: 'Only approved HTTPS documentation hosts can be opened.' };
|
|
4337
|
+
}
|
|
4338
|
+
await electron_1.shell.openExternal(target.toString());
|
|
4339
|
+
return { ok: true };
|
|
4340
|
+
}
|
|
4341
|
+
catch (error) {
|
|
4342
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
4343
|
+
}
|
|
4344
|
+
});
|
|
4086
4345
|
electron_1.ipcMain.handle('agent:selectWorkspace', async (_event, id) => {
|
|
4087
4346
|
if (agent) {
|
|
4088
4347
|
const requested = String(id || '').trim();
|
|
@@ -4380,13 +4639,25 @@ else {
|
|
|
4380
4639
|
const controller = new AbortController();
|
|
4381
4640
|
editorCompletionControllers.set(ownerId, controller);
|
|
4382
4641
|
try {
|
|
4383
|
-
|
|
4642
|
+
const requestId = String(request.requestId || '');
|
|
4643
|
+
const onTextDelta = requestId
|
|
4644
|
+
? (text) => {
|
|
4645
|
+
if (!controller.signal.aborted && !event.sender.isDestroyed())
|
|
4646
|
+
event.sender.send('agent:editorCompletionDelta', { requestId, text });
|
|
4647
|
+
}
|
|
4648
|
+
: undefined;
|
|
4649
|
+
return await (agent?.editorModelRequest({ ...request, completion: true, preferCopilot: true, onTextDelta }, controller.signal) || { ok: false, text: '', error: 'Agent not initialized' });
|
|
4384
4650
|
}
|
|
4385
4651
|
finally {
|
|
4386
4652
|
if (editorCompletionControllers.get(ownerId) === controller)
|
|
4387
4653
|
editorCompletionControllers.delete(ownerId);
|
|
4388
4654
|
}
|
|
4389
4655
|
});
|
|
4656
|
+
electron_1.ipcMain.handle('agent:editorCompleteCancel', async (event) => {
|
|
4657
|
+
const ownerId = event.sender.id;
|
|
4658
|
+
editorCompletionControllers.get(ownerId)?.abort(new Error('Editor completion cancelled'));
|
|
4659
|
+
return { ok: true };
|
|
4660
|
+
});
|
|
4390
4661
|
electron_1.ipcMain.handle('agent:editorAssist', async (_event, request) => {
|
|
4391
4662
|
return agent?.editorModelRequest({ ...request, completion: false }) || { ok: false, text: '', error: 'Agent not initialized' };
|
|
4392
4663
|
});
|
|
@@ -4483,6 +4754,7 @@ else {
|
|
|
4483
4754
|
})));
|
|
4484
4755
|
return { servers: mcpManager.list(), discovered };
|
|
4485
4756
|
});
|
|
4757
|
+
electron_1.ipcMain.handle('dsh:discover', async () => (0, dshCompatibility_1.discoverDshCompatibility)(root));
|
|
4486
4758
|
electron_1.ipcMain.handle('mcp:upsert', async (_event, input) => {
|
|
4487
4759
|
if (!mcpManager)
|
|
4488
4760
|
return { ok: false, error: 'MCP manager is unavailable.' };
|
|
@@ -4497,12 +4769,14 @@ else {
|
|
|
4497
4769
|
electron_1.ipcMain.handle('mcp:setEnabled', async (_event, id, enabled) => {
|
|
4498
4770
|
if (!mcpManager)
|
|
4499
4771
|
return { ok: false, error: 'MCP manager is unavailable.' };
|
|
4500
|
-
|
|
4772
|
+
const ok = mcpManager.setEnabled(id, enabled);
|
|
4773
|
+
return { ok, error: ok ? undefined : 'MCP server was not found.', servers: mcpManager.list() };
|
|
4501
4774
|
});
|
|
4502
4775
|
electron_1.ipcMain.handle('mcp:remove', async (_event, id) => {
|
|
4503
4776
|
if (!mcpManager)
|
|
4504
4777
|
return { ok: false, error: 'MCP manager is unavailable.' };
|
|
4505
|
-
|
|
4778
|
+
const ok = mcpManager.remove(id);
|
|
4779
|
+
return { ok, error: ok ? undefined : 'MCP server was not found.', servers: mcpManager.list() };
|
|
4506
4780
|
});
|
|
4507
4781
|
electron_1.ipcMain.handle('memoryLab:read', async (_event, selector) => {
|
|
4508
4782
|
if (!agent)
|