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.
@@ -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 normalized = { ...input, owner, runtimeKey, action, actionId };
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 && receipt.owner === request.owner && receipt.runtimeKey === request.runtimeKey)
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 workspace = fs.realpathSync(path.resolve(workspacePath));
109
- const candidate = path.resolve(workspace, String(requestedPath || '').trim());
110
- if (!fs.existsSync(candidate) || !fs.statSync(candidate).isFile())
111
- throw new Error(`Display image not found: ${requestedPath}`);
112
- const realCandidate = fs.realpathSync(candidate);
113
- if (!inside(workspace, realCandidate))
114
- throw new Error('Display images must stay inside the active workspace.');
115
- const decoded = decodeFile(realCandidate);
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 boundId = this.runtimeBindings.get(scope.runtimeKey);
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(scope.runtimeKey, contents.id);
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.delete(scope.runtimeKey);
73
- else
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.runtimeBindings.clear();
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
- this.hostToolRuns.set(request.requestId, { generation, controller });
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 error = new Error(`Electron utility runtime exited (${code}): ${this.lastError || 'no stderr'}`);
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 (code !== 0 && !this.restartQuarantine) {
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: true;
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