newmark-agent 0.5.12 → 0.5.13
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/conversation-utility-host.bundle.cjs +75276 -26574
- package/dist/core/agent.d.ts +42 -17
- package/dist/core/agent.js +297 -64
- package/dist/core/browserUse.d.ts +7 -0
- package/dist/core/browserUse.js +24 -7
- package/dist/core/displayImages.d.ts +10 -0
- package/dist/core/displayImages.js +31 -8
- package/dist/core/electronBrowserUseHost.d.ts +4 -0
- package/dist/core/electronBrowserUseHost.js +32 -6
- package/dist/core/electronUtilityAgentClient.d.ts +1 -0
- package/dist/core/electronUtilityAgentClient.js +50 -3
- package/dist/core/runtimeDiagnostics.d.ts +18 -0
- package/dist/core/runtimeDiagnostics.js +89 -0
- package/dist/core/runtimeLifecycle.d.ts +7 -1
- package/dist/core/runtimeLifecycle.js +29 -0
- package/dist/core/searchMcpPool.d.ts +80 -0
- package/dist/core/searchMcpPool.js +419 -0
- package/dist/main.js +72 -1
- package/dist/server.js +35 -0
- package/dist/tools/index.d.ts +17 -1
- package/dist/tools/index.js +203 -64
- package/dist/tui/src/data.js +2 -2
- package/dist/ui/index.html +80 -17
- package/dist/wsl-agent-host.bundle.cjs +75283 -26581
- package/package.json +11 -2
package/dist/core/browserUse.js
CHANGED
|
@@ -46,7 +46,17 @@ function hasControlCharacter(text) {
|
|
|
46
46
|
return false;
|
|
47
47
|
}
|
|
48
48
|
function scopeKey(scope) {
|
|
49
|
-
return `${scope.runtimeKey}\u0000${scope.owner}`;
|
|
49
|
+
return `${scope.runtimeKey}\u0000${scope.owner}\u0000${browserUseVisible(scope.visible) ? 'visible' : 'background'}`;
|
|
50
|
+
}
|
|
51
|
+
function browserUseVisible(value) {
|
|
52
|
+
return typeof value === 'boolean' ? value : true;
|
|
53
|
+
}
|
|
54
|
+
function bindBrowserUseVisible(value) {
|
|
55
|
+
if (value === undefined)
|
|
56
|
+
return true;
|
|
57
|
+
if (typeof value !== 'boolean')
|
|
58
|
+
throw new TypeError('Browser-Use visible must be a boolean when provided.');
|
|
59
|
+
return value;
|
|
50
60
|
}
|
|
51
61
|
function abortReason(signal) {
|
|
52
62
|
return signal.reason instanceof Error
|
|
@@ -154,6 +164,7 @@ function bindBrowserUseRequest(input, context) {
|
|
|
154
164
|
const request = {
|
|
155
165
|
owner: runtimeKey && actorId ? `browser-use:${runtimeKey}:actor:${actorId}` : '',
|
|
156
166
|
runtimeKey,
|
|
167
|
+
visible: bindBrowserUseVisible(raw.visible),
|
|
157
168
|
action: String(raw.action || '').trim().toLowerCase(),
|
|
158
169
|
};
|
|
159
170
|
if (raw.actionId !== undefined || raw.action_id !== undefined)
|
|
@@ -263,10 +274,11 @@ class BrowserUseEngine {
|
|
|
263
274
|
const rawAction = String(input?.action || '').trim().toLowerCase();
|
|
264
275
|
const action = (ACTIONS.has(rawAction) ? rawAction : 'observe');
|
|
265
276
|
const actionId = cleanScopePart(input?.actionId) || `browser-use-${this.id()}`;
|
|
266
|
-
const
|
|
277
|
+
const visible = browserUseVisible(input?.visible);
|
|
278
|
+
const normalized = { ...input, owner, runtimeKey, visible, action, actionId };
|
|
267
279
|
if (!owner || !runtimeKey || !ACTIONS.has(rawAction))
|
|
268
280
|
return await this.runNow(normalized, signal);
|
|
269
|
-
const session = this.ensureSession({ owner, runtimeKey });
|
|
281
|
+
const session = this.ensureSession({ owner, runtimeKey, visible });
|
|
270
282
|
const cached = session.receipts.get(actionId);
|
|
271
283
|
if (cached)
|
|
272
284
|
return cached;
|
|
@@ -293,17 +305,18 @@ class BrowserUseEngine {
|
|
|
293
305
|
throwIfBrowserUseAborted(signal);
|
|
294
306
|
const owner = cleanScopePart(input?.owner);
|
|
295
307
|
const runtimeKey = cleanScopePart(input?.runtimeKey);
|
|
308
|
+
const visible = browserUseVisible(input?.visible);
|
|
296
309
|
const rawAction = String(input?.action || '').trim().toLowerCase();
|
|
297
310
|
const action = (ACTIONS.has(rawAction) ? rawAction : 'observe');
|
|
298
311
|
const actionId = cleanScopePart(input?.actionId) || `browser-use-${this.id()}`;
|
|
299
312
|
const startedAt = this.now();
|
|
300
313
|
if (!owner || !runtimeKey) {
|
|
301
|
-
return this.standaloneFailure({ owner, runtimeKey, action, actionId, startedAt }, 'invalid_scope', 'Browser-Use requires a non-empty owner and runtimeKey.');
|
|
314
|
+
return this.standaloneFailure({ owner, runtimeKey, visible, action, actionId, startedAt }, 'invalid_scope', 'Browser-Use requires a non-empty owner and runtimeKey.');
|
|
302
315
|
}
|
|
303
316
|
if (!ACTIONS.has(rawAction)) {
|
|
304
|
-
return this.standaloneFailure({ owner, runtimeKey, action, actionId, startedAt }, 'invalid_request', `Unsupported Browser-Use action: ${rawAction || '(missing)'}`);
|
|
317
|
+
return this.standaloneFailure({ owner, runtimeKey, visible, action, actionId, startedAt }, 'invalid_request', `Unsupported Browser-Use action: ${rawAction || '(missing)'}`);
|
|
305
318
|
}
|
|
306
|
-
const scope = { owner, runtimeKey };
|
|
319
|
+
const scope = { owner, runtimeKey, visible };
|
|
307
320
|
const session = this.ensureSession(scope);
|
|
308
321
|
const cached = session.receipts.get(actionId);
|
|
309
322
|
if (cached)
|
|
@@ -572,7 +585,10 @@ class BrowserUse {
|
|
|
572
585
|
throwIfBrowserUseAborted(signal);
|
|
573
586
|
if (bridged.ok && bridged.data && typeof bridged.data === 'object') {
|
|
574
587
|
const receipt = bridged.data;
|
|
575
|
-
if (receipt.action && receipt.actionId
|
|
588
|
+
if (receipt.action && receipt.actionId
|
|
589
|
+
&& receipt.owner === request.owner
|
|
590
|
+
&& receipt.runtimeKey === request.runtimeKey
|
|
591
|
+
&& browserUseVisible(receipt.visible) === browserUseVisible(request.visible))
|
|
576
592
|
return receipt;
|
|
577
593
|
}
|
|
578
594
|
return this.unavailable(request, bridged.error ? 'backend_error' : 'backend_unavailable', bridged.error || 'Browser-Use backend is not connected. Start Newmark Desktop to use the built-in browser.');
|
|
@@ -585,6 +601,7 @@ class BrowserUse {
|
|
|
585
601
|
actionId: cleanScopePart(request?.actionId) || `browser-use-${(0, crypto_1.randomUUID)()}`,
|
|
586
602
|
owner: cleanScopePart(request?.owner),
|
|
587
603
|
runtimeKey: cleanScopePart(request?.runtimeKey),
|
|
604
|
+
visible: browserUseVisible(request?.visible),
|
|
588
605
|
sequence: 0,
|
|
589
606
|
pageGeneration: Number.isInteger(request?.pageGeneration) ? Number(request.pageGeneration) : 0,
|
|
590
607
|
startedAt: now,
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { DisplayImageAttachment } from './types';
|
|
2
|
+
export interface WorkspaceImageObservation {
|
|
3
|
+
path: string;
|
|
4
|
+
name: string;
|
|
5
|
+
byteLength: number;
|
|
6
|
+
dataUrl: string;
|
|
7
|
+
mimeType: DisplayImageAttachment['mimeType'];
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function readWorkspaceImageForVision(workspacePath: string, requestedPath: string): WorkspaceImageObservation;
|
|
2
12
|
export declare function persistWorkspaceDisplayImage(rootPath: string, workspacePath: string, requestedPath: string, caption?: string, createdAt?: string): DisplayImageAttachment;
|
|
3
13
|
export declare function hydrateDisplayImage(rootPath: string, input: unknown): DisplayImageAttachment | undefined;
|
|
4
14
|
export declare function durableDisplayImage(input: DisplayImageAttachment | undefined): DisplayImageAttachment | undefined;
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readWorkspaceImageForVision = readWorkspaceImageForVision;
|
|
36
37
|
exports.persistWorkspaceDisplayImage = persistWorkspaceDisplayImage;
|
|
37
38
|
exports.hydrateDisplayImage = hydrateDisplayImage;
|
|
38
39
|
exports.durableDisplayImage = durableDisplayImage;
|
|
@@ -78,6 +79,27 @@ function decodeFile(filePath) {
|
|
|
78
79
|
throw new Error('Display image extension does not match its decoded content.');
|
|
79
80
|
return { bytes, dataUrl, mimeType, width: decoded.width, height: decoded.height };
|
|
80
81
|
}
|
|
82
|
+
function readWorkspaceImageForVision(workspacePath, requestedPath) {
|
|
83
|
+
const workspace = fs.realpathSync(path.resolve(workspacePath));
|
|
84
|
+
const candidate = path.resolve(workspace, String(requestedPath || '').trim());
|
|
85
|
+
if (!inside(workspace, candidate))
|
|
86
|
+
throw new Error('Workspace images must stay inside the active workspace.');
|
|
87
|
+
if (!fs.existsSync(candidate) || !fs.statSync(candidate).isFile())
|
|
88
|
+
throw new Error(`Workspace image not found: ${requestedPath}`);
|
|
89
|
+
const realCandidate = fs.realpathSync(candidate);
|
|
90
|
+
if (!inside(workspace, realCandidate))
|
|
91
|
+
throw new Error('Workspace images must stay inside the active workspace.');
|
|
92
|
+
const decoded = decodeFile(realCandidate);
|
|
93
|
+
return {
|
|
94
|
+
path: path.relative(workspace, realCandidate).split(path.sep).join('/'),
|
|
95
|
+
name: path.basename(realCandidate),
|
|
96
|
+
byteLength: decoded.bytes.length,
|
|
97
|
+
dataUrl: decoded.dataUrl,
|
|
98
|
+
mimeType: decoded.mimeType,
|
|
99
|
+
width: decoded.width,
|
|
100
|
+
height: decoded.height,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
81
103
|
function writeAsset(filePath, bytes, sha256) {
|
|
82
104
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
83
105
|
if (fs.existsSync(filePath)) {
|
|
@@ -105,14 +127,15 @@ function writeAsset(filePath, bytes, sha256) {
|
|
|
105
127
|
}
|
|
106
128
|
}
|
|
107
129
|
function persistWorkspaceDisplayImage(rootPath, workspacePath, requestedPath, caption = '', createdAt = new Date().toISOString()) {
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
const source = readWorkspaceImageForVision(workspacePath, requestedPath);
|
|
131
|
+
const realCandidate = path.resolve(fs.realpathSync(path.resolve(workspacePath)), ...source.path.split('/'));
|
|
132
|
+
const decoded = {
|
|
133
|
+
bytes: Buffer.from(source.dataUrl.slice(source.dataUrl.indexOf(',') + 1), 'base64'),
|
|
134
|
+
dataUrl: source.dataUrl,
|
|
135
|
+
mimeType: source.mimeType,
|
|
136
|
+
width: source.width,
|
|
137
|
+
height: source.height,
|
|
138
|
+
};
|
|
116
139
|
const sha256 = crypto.createHash('sha256').update(decoded.bytes).digest('hex');
|
|
117
140
|
writeAsset(absoluteAssetPath(rootPath, sha256, decoded.mimeType), decoded.bytes, sha256);
|
|
118
141
|
return {
|
|
@@ -5,6 +5,7 @@ export interface ElectronBrowserUseHostOptions {
|
|
|
5
5
|
resolveContents(scope: BrowserUseScope, boundContentsId?: number): Promise<WebContents>;
|
|
6
6
|
openExternal?(url: string): void | Promise<void>;
|
|
7
7
|
guardSettleMs?: number;
|
|
8
|
+
releaseContents?(scope: BrowserUseScope, contents: WebContents): void;
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
11
|
* Electron-owned Browser-Use page host. All model-independent DOM programs live in
|
|
@@ -25,6 +26,9 @@ export declare class ElectronBrowserUseHost {
|
|
|
25
26
|
private page;
|
|
26
27
|
private activeEffects;
|
|
27
28
|
private isRuntimeBound;
|
|
29
|
+
private bindingKey;
|
|
30
|
+
private bindingMatchesScope;
|
|
31
|
+
private releaseBinding;
|
|
28
32
|
private installDownloadGuard;
|
|
29
33
|
}
|
|
30
34
|
//# sourceMappingURL=electronBrowserUseHost.d.ts.map
|
|
@@ -59,22 +59,31 @@ class ElectronBrowserUseHost {
|
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
async resolve(scope) {
|
|
62
|
-
const
|
|
62
|
+
const bindingKey = this.bindingKey(scope);
|
|
63
|
+
const boundId = this.runtimeBindings.get(bindingKey);
|
|
63
64
|
const contents = await this.options.resolveContents(scope, boundId);
|
|
64
65
|
if (contents.isDestroyed())
|
|
65
66
|
throw new Error('Built-in browser page is unavailable.');
|
|
66
67
|
this.attach(contents);
|
|
67
|
-
this.runtimeBindings.set(
|
|
68
|
+
this.runtimeBindings.set(bindingKey, contents.id);
|
|
68
69
|
return this.page(contents);
|
|
69
70
|
}
|
|
70
71
|
clear(scope) {
|
|
71
|
-
if (scope)
|
|
72
|
-
this.runtimeBindings
|
|
73
|
-
|
|
72
|
+
if (!scope) {
|
|
73
|
+
for (const [bindingKey, contentsId] of this.runtimeBindings)
|
|
74
|
+
this.releaseBinding(bindingKey, contentsId);
|
|
74
75
|
this.runtimeBindings.clear();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const [bindingKey, contentsId] of this.runtimeBindings) {
|
|
79
|
+
if (!this.bindingMatchesScope(bindingKey, scope))
|
|
80
|
+
continue;
|
|
81
|
+
this.releaseBinding(bindingKey, contentsId, scope);
|
|
82
|
+
this.runtimeBindings.delete(bindingKey);
|
|
83
|
+
}
|
|
75
84
|
}
|
|
76
85
|
dispose() {
|
|
77
|
-
this.
|
|
86
|
+
this.clear();
|
|
78
87
|
for (const [browserSession, handler] of this.downloadHandlers) {
|
|
79
88
|
browserSession.removeListener('will-download', handler);
|
|
80
89
|
}
|
|
@@ -193,6 +202,23 @@ class ElectronBrowserUseHost {
|
|
|
193
202
|
}
|
|
194
203
|
return false;
|
|
195
204
|
}
|
|
205
|
+
bindingKey(scope) {
|
|
206
|
+
return `${scope.runtimeKey}\u0000${scope.visible === false ? 'background' : 'visible'}`;
|
|
207
|
+
}
|
|
208
|
+
bindingMatchesScope(bindingKey, scope) {
|
|
209
|
+
const prefix = `${scope.runtimeKey}\u0000`;
|
|
210
|
+
return bindingKey.startsWith(prefix)
|
|
211
|
+
&& (scope.visible === undefined || bindingKey === this.bindingKey({ ...scope, visible: scope.visible !== false }));
|
|
212
|
+
}
|
|
213
|
+
releaseBinding(bindingKey, contentsId, scope) {
|
|
214
|
+
const contents = this.pages.get(contentsId)?.contents;
|
|
215
|
+
if (!contents || contents.isDestroyed())
|
|
216
|
+
return;
|
|
217
|
+
const separator = bindingKey.lastIndexOf('\u0000');
|
|
218
|
+
const runtimeKey = separator >= 0 ? bindingKey.slice(0, separator) : bindingKey;
|
|
219
|
+
const visible = separator < 0 || bindingKey.slice(separator + 1) !== 'background';
|
|
220
|
+
this.options.releaseContents?.({ owner: scope?.owner || '', runtimeKey, visible }, contents);
|
|
221
|
+
}
|
|
196
222
|
installDownloadGuard(browserSession) {
|
|
197
223
|
if (this.downloadHandlers.has(browserSession))
|
|
198
224
|
return;
|
|
@@ -73,6 +73,7 @@ export declare class ElectronUtilityAgentClient {
|
|
|
73
73
|
private childRootIdentity;
|
|
74
74
|
private sequence;
|
|
75
75
|
private lastError;
|
|
76
|
+
private expectedExit;
|
|
76
77
|
private restartQuarantine;
|
|
77
78
|
constructor(root: string, hostScript: string, target: NormalizedConversationTarget, options?: ElectronUtilityAgentClientOptions);
|
|
78
79
|
subscribe(listener: (event: AgentWorkEvent) => void): () => void;
|
|
@@ -49,6 +49,8 @@ const fs = __importStar(require("fs"));
|
|
|
49
49
|
const path = __importStar(require("path"));
|
|
50
50
|
const electron_1 = require("electron");
|
|
51
51
|
const child_process_1 = require("child_process");
|
|
52
|
+
const runtimeDiagnostics_1 = require("./runtimeDiagnostics");
|
|
53
|
+
const runtimeLifecycle_1 = require("./runtimeLifecycle");
|
|
52
54
|
const conversationTarget_1 = require("./conversationTarget");
|
|
53
55
|
// PowerShell startup can be slow on a cold or busy Windows host even though
|
|
54
56
|
// the precompiled Toolhelp helper itself is healthy.
|
|
@@ -882,6 +884,7 @@ class ElectronUtilityAgentClient {
|
|
|
882
884
|
childRootIdentity = null;
|
|
883
885
|
sequence = 0;
|
|
884
886
|
lastError = '';
|
|
887
|
+
expectedExit = false;
|
|
885
888
|
// A failed force-stop means an old descendant may still own target-scoped
|
|
886
889
|
// resources. This is intentionally sticky for the lifetime of this client:
|
|
887
890
|
// only rebuilding the Electron main-process runtime pool may clear it.
|
|
@@ -955,6 +958,11 @@ class ElectronUtilityAgentClient {
|
|
|
955
958
|
const generation = ++this.childGeneration;
|
|
956
959
|
this.readyGeneration = 0;
|
|
957
960
|
this.lastError = '';
|
|
961
|
+
this.expectedExit = false;
|
|
962
|
+
(0, runtimeDiagnostics_1.appendRuntimeDiagnostic)(this.root, {
|
|
963
|
+
event: 'utility_runtime_started', runtimeKey: this.target.runtimeKey,
|
|
964
|
+
generation, pid: Number(child.pid || 0),
|
|
965
|
+
});
|
|
958
966
|
child.on('message', message => this.handleMessage(child, generation, message));
|
|
959
967
|
child.on('error', (_type, _location, report) => {
|
|
960
968
|
if (this.child === child)
|
|
@@ -1078,6 +1086,7 @@ class ElectronUtilityAgentClient {
|
|
|
1078
1086
|
const child = this.child;
|
|
1079
1087
|
if (!child)
|
|
1080
1088
|
return;
|
|
1089
|
+
this.expectedExit = true;
|
|
1081
1090
|
if (!this.restartQuarantine && this.readyGeneration === this.childGeneration) {
|
|
1082
1091
|
try {
|
|
1083
1092
|
await this.request('shutdown', undefined, 2_000);
|
|
@@ -1337,7 +1346,13 @@ class ElectronUtilityAgentClient {
|
|
|
1337
1346
|
return;
|
|
1338
1347
|
let result;
|
|
1339
1348
|
const controller = new AbortController();
|
|
1340
|
-
|
|
1349
|
+
const run = { generation, controller, tool: String(request.tool), startedAt: Date.now(), stage: 'received' };
|
|
1350
|
+
this.hostToolRuns.set(request.requestId, run);
|
|
1351
|
+
(0, runtimeDiagnostics_1.appendRuntimeDiagnostic)(this.root, {
|
|
1352
|
+
event: 'utility_host_rpc_started', runtimeKey: this.target.runtimeKey,
|
|
1353
|
+
generation, pid: Number(child.pid || 0), requestId: request.requestId,
|
|
1354
|
+
tool: request.tool, stage: run.stage,
|
|
1355
|
+
});
|
|
1341
1356
|
const allowed = new Set(['browser_control', 'browser_use', 'screen_capture', 'computer_use', 'automation', 'terminal_takeover']);
|
|
1342
1357
|
if (!allowed.has(request.tool)) {
|
|
1343
1358
|
result = { requestId: request.requestId, ok: false, error: `Electron host tool is not allowed: ${String(request.tool)}` };
|
|
@@ -1350,13 +1365,26 @@ class ElectronUtilityAgentClient {
|
|
|
1350
1365
|
}
|
|
1351
1366
|
else {
|
|
1352
1367
|
try {
|
|
1368
|
+
const active = this.hostToolRuns.get(request.requestId);
|
|
1369
|
+
if (active)
|
|
1370
|
+
active.stage = 'running';
|
|
1353
1371
|
result = { requestId: request.requestId, ok: true, result: await this.hostToolHandler(request, controller.signal) };
|
|
1354
1372
|
}
|
|
1355
1373
|
catch (error) {
|
|
1356
1374
|
result = { requestId: request.requestId, ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
1357
1375
|
}
|
|
1358
1376
|
}
|
|
1377
|
+
const active = this.hostToolRuns.get(request.requestId);
|
|
1378
|
+
if (active)
|
|
1379
|
+
active.stage = 'responding';
|
|
1359
1380
|
this.hostToolRuns.delete(request.requestId);
|
|
1381
|
+
(0, runtimeDiagnostics_1.appendRuntimeDiagnostic)(this.root, {
|
|
1382
|
+
event: result.ok ? 'utility_host_rpc_completed' : 'utility_host_rpc_failed',
|
|
1383
|
+
level: result.ok ? 'info' : 'warn', runtimeKey: this.target.runtimeKey,
|
|
1384
|
+
generation, pid: Number(child.pid || 0), requestId: request.requestId,
|
|
1385
|
+
tool: request.tool, stage: active?.stage || 'responding',
|
|
1386
|
+
durationMs: Date.now() - run.startedAt, error: result.ok ? '' : result.error,
|
|
1387
|
+
});
|
|
1360
1388
|
if (controller.signal.aborted
|
|
1361
1389
|
|| this.restartQuarantine
|
|
1362
1390
|
|| this.invalidGenerations.has(generation)
|
|
@@ -1377,12 +1405,30 @@ class ElectronUtilityAgentClient {
|
|
|
1377
1405
|
handleExit(child, code) {
|
|
1378
1406
|
if (this.child !== child)
|
|
1379
1407
|
return;
|
|
1380
|
-
const
|
|
1408
|
+
const expected = this.expectedExit || !!this.restartQuarantine;
|
|
1409
|
+
const lastHostRun = [...this.hostToolRuns.entries()]
|
|
1410
|
+
.filter(([, run]) => run.generation === this.childGeneration)
|
|
1411
|
+
.sort((left, right) => right[1].startedAt - left[1].startedAt)[0];
|
|
1412
|
+
const hostSuffix = lastHostRun
|
|
1413
|
+
? `; last host RPC ${lastHostRun[1].tool}/${lastHostRun[1].stage} (${Date.now() - lastHostRun[1].startedAt} ms)`
|
|
1414
|
+
: '';
|
|
1415
|
+
const error = new Error(`Electron utility runtime exited (${code}): ${this.lastError || 'no stderr'}${hostSuffix}`);
|
|
1416
|
+
(0, runtimeDiagnostics_1.appendRuntimeDiagnostic)(this.root, {
|
|
1417
|
+
event: expected ? 'utility_runtime_stopped' : 'utility_runtime_unexpected_exit',
|
|
1418
|
+
level: expected ? 'info' : 'error', runtimeKey: this.target.runtimeKey,
|
|
1419
|
+
generation: this.childGeneration, pid: Number(child.pid || 0), exitCode: code,
|
|
1420
|
+
expected, requestId: lastHostRun?.[0], tool: lastHostRun?.[1].tool,
|
|
1421
|
+
stage: lastHostRun?.[1].stage, durationMs: lastHostRun ? Date.now() - lastHostRun[1].startedAt : 0,
|
|
1422
|
+
error: this.lastError || (expected ? '' : 'no stderr'),
|
|
1423
|
+
});
|
|
1424
|
+
(0, runtimeLifecycle_1.markRuntimeLifecycleExitedByPid)(this.root, 'utility', Number(child.pid || 0), {
|
|
1425
|
+
unexpected: !expected, exitCode: code, error: this.lastError || '',
|
|
1426
|
+
});
|
|
1381
1427
|
// Surface unexpected worker death as a target-scoped terminal error. A
|
|
1382
1428
|
// silent child exit used to leave the renderer with a generic interrupted
|
|
1383
1429
|
// state and no explanation of whether the provider, runtime, or process
|
|
1384
1430
|
// supervisor was responsible.
|
|
1385
|
-
if (
|
|
1431
|
+
if (!expected && !this.restartQuarantine) {
|
|
1386
1432
|
const event = {
|
|
1387
1433
|
id: `utility-runtime-exit-${process.pid}-${Date.now()}`,
|
|
1388
1434
|
conversationId: this.target.conversationId,
|
|
@@ -1400,6 +1446,7 @@ class ElectronUtilityAgentClient {
|
|
|
1400
1446
|
listener(event);
|
|
1401
1447
|
}
|
|
1402
1448
|
this.detachChild(child, error);
|
|
1449
|
+
this.expectedExit = false;
|
|
1403
1450
|
}
|
|
1404
1451
|
detachChild(child, error) {
|
|
1405
1452
|
if (this.child !== child)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type RuntimeDiagnosticLevel = 'info' | 'warn' | 'error';
|
|
2
|
+
export interface RuntimeDiagnosticEvent {
|
|
3
|
+
event: string;
|
|
4
|
+
level?: RuntimeDiagnosticLevel;
|
|
5
|
+
runtimeKey?: string;
|
|
6
|
+
generation?: number;
|
|
7
|
+
pid?: number;
|
|
8
|
+
requestId?: string;
|
|
9
|
+
tool?: string;
|
|
10
|
+
stage?: string;
|
|
11
|
+
durationMs?: number;
|
|
12
|
+
exitCode?: number | null;
|
|
13
|
+
expected?: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Append one bounded, path-free JSON event. Diagnostics must never break runtime work. */
|
|
17
|
+
export declare function appendRuntimeDiagnostic(root: string, input: RuntimeDiagnosticEvent): void;
|
|
18
|
+
//# sourceMappingURL=runtimeDiagnostics.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.appendRuntimeDiagnostic = appendRuntimeDiagnostic;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const crypto_1 = require("crypto");
|
|
40
|
+
const MAX_LOG_BYTES = 2 * 1024 * 1024;
|
|
41
|
+
function boundedText(value, limit = 800) {
|
|
42
|
+
return String(value || '')
|
|
43
|
+
.replace(/(?:[A-Za-z]:[\\/]|\\\\)[^\s"']+/g, '[local-path]')
|
|
44
|
+
.replace(/\b(?:sk|ghp|github_pat)-?[A-Za-z0-9_.-]{8,}\b/g, '[redacted]')
|
|
45
|
+
.replace(/(Authorization\s*:\s*Bearer\s+)[^\s,;]+/ig, '$1[redacted]')
|
|
46
|
+
.slice(-limit);
|
|
47
|
+
}
|
|
48
|
+
function runtimeCorrelation(runtimeKey) {
|
|
49
|
+
return runtimeKey
|
|
50
|
+
? (0, crypto_1.createHash)('sha256').update(runtimeKey).digest('hex').slice(0, 16)
|
|
51
|
+
: '';
|
|
52
|
+
}
|
|
53
|
+
/** Append one bounded, path-free JSON event. Diagnostics must never break runtime work. */
|
|
54
|
+
function appendRuntimeDiagnostic(root, input) {
|
|
55
|
+
try {
|
|
56
|
+
const directory = path.join(root, '.newmark-runtime');
|
|
57
|
+
const file = path.join(directory, 'runtime-events.jsonl');
|
|
58
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
59
|
+
try {
|
|
60
|
+
if (fs.statSync(file).size > MAX_LOG_BYTES) {
|
|
61
|
+
const previous = `${file}.1`;
|
|
62
|
+
try {
|
|
63
|
+
fs.unlinkSync(previous);
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
fs.renameSync(file, previous);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch { }
|
|
70
|
+
const record = {
|
|
71
|
+
at: new Date().toISOString(),
|
|
72
|
+
event: boundedText(input.event, 100),
|
|
73
|
+
level: input.level || 'info',
|
|
74
|
+
runtime: runtimeCorrelation(String(input.runtimeKey || '')),
|
|
75
|
+
generation: Math.max(0, Math.floor(Number(input.generation) || 0)),
|
|
76
|
+
pid: Math.max(0, Math.floor(Number(input.pid) || 0)),
|
|
77
|
+
requestId: boundedText(input.requestId, 160),
|
|
78
|
+
tool: boundedText(input.tool, 80),
|
|
79
|
+
stage: boundedText(input.stage, 80),
|
|
80
|
+
durationMs: Math.max(0, Math.floor(Number(input.durationMs) || 0)),
|
|
81
|
+
exitCode: input.exitCode === null ? null : Number.isFinite(Number(input.exitCode)) ? Number(input.exitCode) : undefined,
|
|
82
|
+
expected: input.expected,
|
|
83
|
+
error: boundedText(input.error),
|
|
84
|
+
};
|
|
85
|
+
fs.appendFileSync(file, `${JSON.stringify(record)}\n`, 'utf-8');
|
|
86
|
+
}
|
|
87
|
+
catch { }
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=runtimeDiagnostics.js.map
|
|
@@ -6,7 +6,7 @@ export interface RuntimeLifecycleState {
|
|
|
6
6
|
startedAt: string;
|
|
7
7
|
previousOwnerAlive: boolean;
|
|
8
8
|
unexpectedExit: boolean;
|
|
9
|
-
active:
|
|
9
|
+
active: boolean;
|
|
10
10
|
}
|
|
11
11
|
export declare function isRuntimeProcessAlive(pid: number): boolean;
|
|
12
12
|
/** Prepare crash-recovery markers asynchronously after the startup shell is visible. */
|
|
@@ -21,5 +21,11 @@ export declare function prepareRuntimeLifecycle(root: string, role?: RuntimeLife
|
|
|
21
21
|
export declare function beginRuntimeLifecycle(root: string, role?: RuntimeLifecycleRole): RuntimeLifecycleState;
|
|
22
22
|
/** Mark only this owner clean; a hard kill leaves the active marker intact. */
|
|
23
23
|
export declare function markRuntimeLifecycleClean(root: string, role?: RuntimeLifecycleRole): void;
|
|
24
|
+
/** Parent-side fallback for a worker that died before it could clean its own marker. */
|
|
25
|
+
export declare function markRuntimeLifecycleExitedByPid(root: string, role: RuntimeLifecycleRole, pid: number, input?: {
|
|
26
|
+
unexpected: boolean;
|
|
27
|
+
exitCode?: number | null;
|
|
28
|
+
error?: string;
|
|
29
|
+
}): void;
|
|
24
30
|
export declare function runtimeLifecycleState(root: string, role?: RuntimeLifecycleRole): RuntimeLifecycleState | null;
|
|
25
31
|
//# sourceMappingURL=runtimeLifecycle.d.ts.map
|
|
@@ -37,6 +37,7 @@ exports.isRuntimeProcessAlive = isRuntimeProcessAlive;
|
|
|
37
37
|
exports.prepareRuntimeLifecycle = prepareRuntimeLifecycle;
|
|
38
38
|
exports.beginRuntimeLifecycle = beginRuntimeLifecycle;
|
|
39
39
|
exports.markRuntimeLifecycleClean = markRuntimeLifecycleClean;
|
|
40
|
+
exports.markRuntimeLifecycleExitedByPid = markRuntimeLifecycleExitedByPid;
|
|
40
41
|
exports.runtimeLifecycleState = runtimeLifecycleState;
|
|
41
42
|
const fs = __importStar(require("fs"));
|
|
42
43
|
const path = __importStar(require("path"));
|
|
@@ -189,6 +190,34 @@ function markRuntimeLifecycleClean(root, role = 'main') {
|
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
192
|
}
|
|
193
|
+
/** Parent-side fallback for a worker that died before it could clean its own marker. */
|
|
194
|
+
function markRuntimeLifecycleExitedByPid(root, role, pid, input = { unexpected: true }) {
|
|
195
|
+
const directory = path.join(root, '.newmark-runtime');
|
|
196
|
+
let files = [];
|
|
197
|
+
try {
|
|
198
|
+
files = fs.readdirSync(directory).filter(file => file.startsWith(`lifecycle-${role}-`) && file.endsWith('.json'));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
for (const name of files) {
|
|
204
|
+
const file = path.join(directory, name);
|
|
205
|
+
const state = readState(file);
|
|
206
|
+
if (!state || Number(state.pid) !== Math.floor(Number(pid) || 0) || state.active !== true)
|
|
207
|
+
continue;
|
|
208
|
+
try {
|
|
209
|
+
writeState(root, role, {
|
|
210
|
+
...state,
|
|
211
|
+
active: false,
|
|
212
|
+
unexpectedExit: input.unexpected,
|
|
213
|
+
exitedAt: new Date().toISOString(),
|
|
214
|
+
exitCode: input.exitCode,
|
|
215
|
+
error: String(input.error || '').slice(-800),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch { }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
192
221
|
function runtimeLifecycleState(root, role = 'main') {
|
|
193
222
|
return processStates.get(stateKey(root, role)) || null;
|
|
194
223
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export type SearchMcpTransport = 'stdio' | 'streamable_http' | 'sse' | 'template';
|
|
2
|
+
export interface SearchMcpEndpoint {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
priority: number;
|
|
7
|
+
transport: SearchMcpTransport;
|
|
8
|
+
command?: string;
|
|
9
|
+
args?: string[];
|
|
10
|
+
cwd?: string;
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
url?: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
tool?: string;
|
|
15
|
+
argument?: string;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
notes?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SearchMcpPoolConfig {
|
|
20
|
+
version: 1;
|
|
21
|
+
endpoints: SearchMcpEndpoint[];
|
|
22
|
+
}
|
|
23
|
+
export interface PublicSearchMcpEndpoint extends Omit<SearchMcpEndpoint, 'env' | 'headers' | 'command' | 'args' | 'cwd'> {
|
|
24
|
+
envKeys: string[];
|
|
25
|
+
headerKeys: string[];
|
|
26
|
+
url?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SearchMcpAttempt {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
status: 'success' | 'empty' | 'error' | 'unconfigured';
|
|
32
|
+
durationMs: number;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SearchMcpPoolResult {
|
|
36
|
+
invocationId: string;
|
|
37
|
+
checkedAt: string;
|
|
38
|
+
ok: boolean;
|
|
39
|
+
provider?: string;
|
|
40
|
+
text?: string;
|
|
41
|
+
attempts: SearchMcpAttempt[];
|
|
42
|
+
}
|
|
43
|
+
export interface SearchMcpPoolDependencies {
|
|
44
|
+
callEndpoint?: (endpoint: SearchMcpEndpoint, query: string, signal?: AbortSignal) => Promise<string>;
|
|
45
|
+
now?: () => number;
|
|
46
|
+
}
|
|
47
|
+
export declare const MAX_SEARCH_MCP_RESULT_CHARS = 60000;
|
|
48
|
+
/**
|
|
49
|
+
* Public protocol catalog. Only endpoints with evidence-backed launch metadata are runnable by default.
|
|
50
|
+
* The remaining requested implementations are exposed as disabled templates until a distributor/user
|
|
51
|
+
* supplies a concrete stdio command or Streamable HTTP/SSE URL in search-mcp.json.
|
|
52
|
+
*/
|
|
53
|
+
export declare const DEFAULT_SEARCH_MCP_MANIFEST: SearchMcpEndpoint[];
|
|
54
|
+
export declare function loadSearchMcpManifest(root: string): SearchMcpEndpoint[];
|
|
55
|
+
export declare function publicSearchMcpManifest(root: string): PublicSearchMcpEndpoint[];
|
|
56
|
+
export declare function redactSearchMcpLocalPaths(value: string, additionalRoots?: string[]): string;
|
|
57
|
+
export declare function chooseSearchTool(tools: Array<{
|
|
58
|
+
name: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
inputSchema?: {
|
|
61
|
+
type?: string;
|
|
62
|
+
properties?: Record<string, {
|
|
63
|
+
type?: string;
|
|
64
|
+
}>;
|
|
65
|
+
required?: string[];
|
|
66
|
+
additionalProperties?: boolean;
|
|
67
|
+
};
|
|
68
|
+
}>, preferred?: string): {
|
|
69
|
+
name: string;
|
|
70
|
+
argument: string;
|
|
71
|
+
} | undefined;
|
|
72
|
+
export declare class SearchMcpPool {
|
|
73
|
+
private readonly root;
|
|
74
|
+
private readonly callEndpoint;
|
|
75
|
+
private readonly now;
|
|
76
|
+
constructor(root: string, dependencies?: SearchMcpPoolDependencies);
|
|
77
|
+
manifest(): SearchMcpEndpoint[];
|
|
78
|
+
search(query: string, signal?: AbortSignal): Promise<SearchMcpPoolResult>;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=searchMcpPool.d.ts.map
|