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/tools/index.js
CHANGED
|
@@ -60,6 +60,7 @@ const toolArgumentValidator_1 = require("../core/toolArgumentValidator");
|
|
|
60
60
|
const localOcr_1 = require("../core/localOcr");
|
|
61
61
|
const visualTextFallback_1 = require("../core/visualTextFallback");
|
|
62
62
|
const computerUseSession_1 = require("../core/computerUseSession");
|
|
63
|
+
const searchMcpPool_1 = require("../core/searchMcpPool");
|
|
63
64
|
function normalizeComputerUseAction(action) {
|
|
64
65
|
return String(action || '').trim().toLowerCase();
|
|
65
66
|
}
|
|
@@ -149,7 +150,7 @@ function decodePdfLiteral(input) {
|
|
|
149
150
|
.replace(/\\t/g, '\t')
|
|
150
151
|
.replace(/\\([0-7]{1,3})/g, (_match, octal) => String.fromCharCode(parseInt(octal, 8)));
|
|
151
152
|
}
|
|
152
|
-
function
|
|
153
|
+
function extractPdfTextLayerLegacy(buffer) {
|
|
153
154
|
const binary = buffer.toString('latin1');
|
|
154
155
|
const chunks = [];
|
|
155
156
|
for (const match of binary.matchAll(/\((?:\\.|[^\\)]){1,4000}\)\s*Tj/g)) {
|
|
@@ -171,6 +172,61 @@ function extractPdfTextLayer(buffer) {
|
|
|
171
172
|
}
|
|
172
173
|
return chunks.join(' ').replace(/\s+/g, ' ').trim().slice(0, 100_000);
|
|
173
174
|
}
|
|
175
|
+
async function extractPdfTextLayer(buffer, maxChars, signal) {
|
|
176
|
+
if (signal?.aborted)
|
|
177
|
+
throw abortReason(signal);
|
|
178
|
+
let loadingTask = null;
|
|
179
|
+
try {
|
|
180
|
+
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
|
181
|
+
loadingTask = pdfjs.getDocument({
|
|
182
|
+
data: new Uint8Array(buffer),
|
|
183
|
+
isEvalSupported: false,
|
|
184
|
+
useSystemFonts: true,
|
|
185
|
+
stopAtErrors: false,
|
|
186
|
+
});
|
|
187
|
+
const abortPromise = signal ? new Promise((_resolve, reject) => {
|
|
188
|
+
signal.addEventListener('abort', () => reject(abortReason(signal)), { once: true });
|
|
189
|
+
}) : null;
|
|
190
|
+
const document = await (abortPromise ? Promise.race([loadingTask.promise, abortPromise]) : loadingTask.promise);
|
|
191
|
+
const chunks = [];
|
|
192
|
+
let length = 0;
|
|
193
|
+
for (let pageNumber = 1; pageNumber <= document.numPages && length < maxChars; pageNumber += 1) {
|
|
194
|
+
if (signal?.aborted)
|
|
195
|
+
throw abortReason(signal);
|
|
196
|
+
const page = await document.getPage(pageNumber);
|
|
197
|
+
const content = await page.getTextContent({ disableNormalization: false });
|
|
198
|
+
const pageText = (Array.isArray(content.items) ? content.items : [])
|
|
199
|
+
.map((item) => `${String(item.str || '')}${item.hasEOL ? '\n' : ' '}`)
|
|
200
|
+
.join('')
|
|
201
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
202
|
+
.replace(/[ \t]{2,}/g, ' ')
|
|
203
|
+
.trim();
|
|
204
|
+
if (pageText) {
|
|
205
|
+
const bounded = pageText.slice(0, Math.max(0, maxChars - length));
|
|
206
|
+
chunks.push(bounded);
|
|
207
|
+
length += bounded.length + 1;
|
|
208
|
+
}
|
|
209
|
+
page.cleanup();
|
|
210
|
+
}
|
|
211
|
+
return chunks.join('\n').trim().slice(0, maxChars);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (signal?.aborted)
|
|
215
|
+
throw abortReason(signal);
|
|
216
|
+
// Retain the bounded legacy decoder for minimal/malformed fixture PDFs and
|
|
217
|
+
// old documents whose streams pdf.js rejects. It never opens the browser.
|
|
218
|
+
const fallback = extractPdfTextLayerLegacy(buffer).slice(0, maxChars);
|
|
219
|
+
if (fallback)
|
|
220
|
+
return fallback;
|
|
221
|
+
return '';
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
try {
|
|
225
|
+
await loadingTask?.destroy();
|
|
226
|
+
}
|
|
227
|
+
catch { }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
174
230
|
async function abortableToolDelay(durationMs, signal) {
|
|
175
231
|
if (signal?.aborted)
|
|
176
232
|
throw abortReason(signal);
|
|
@@ -219,6 +275,7 @@ class ToolExecutor {
|
|
|
219
275
|
root;
|
|
220
276
|
localOcr;
|
|
221
277
|
argumentValidators = new toolArgumentValidator_1.ToolArgumentValidatorRegistry();
|
|
278
|
+
searchMcpPool;
|
|
222
279
|
hostProfile = {
|
|
223
280
|
kind: 'desktop',
|
|
224
281
|
platform: process.platform,
|
|
@@ -231,14 +288,38 @@ class ToolExecutor {
|
|
|
231
288
|
this.workspace = workspace;
|
|
232
289
|
this.root = root;
|
|
233
290
|
this.localOcr = new localOcr_1.LocalOcrEngine(root);
|
|
291
|
+
this.searchMcpPool = new searchMcpPool_1.SearchMcpPool(root);
|
|
234
292
|
}
|
|
235
|
-
async webSearch(query) {
|
|
236
|
-
return this.
|
|
293
|
+
async webSearch(query, signal) {
|
|
294
|
+
return (await this.webSearchDetailed(query, signal)).text;
|
|
295
|
+
}
|
|
296
|
+
async webSearchDetailed(query, signal) {
|
|
297
|
+
return this.wsearchDetailed(query, signal);
|
|
298
|
+
}
|
|
299
|
+
/** Search the configured MCP pool without entering any HTTP fallback. */
|
|
300
|
+
async webSearchMcpOnly(query, signal) {
|
|
301
|
+
const result = await this.searchMcpPool.search(query, signal);
|
|
302
|
+
return {
|
|
303
|
+
invocationId: result.invocationId,
|
|
304
|
+
checkedAt: result.checkedAt,
|
|
305
|
+
ok: result.ok,
|
|
306
|
+
provider: result.provider || '',
|
|
307
|
+
text: result.text || '',
|
|
308
|
+
attempts: result.attempts,
|
|
309
|
+
};
|
|
237
310
|
}
|
|
238
311
|
/** OCR entry point for the runtime's final visual fallback. */
|
|
239
312
|
async finalVisualFallbackOcr(dataUrl, signal) {
|
|
240
313
|
return await this.localOcr.recognizeDataUrl(dataUrl, signal, 'sparse-ui');
|
|
241
314
|
}
|
|
315
|
+
async readPdfFile(pdfPath, signal) {
|
|
316
|
+
if (signal?.aborted)
|
|
317
|
+
throw abortReason(signal);
|
|
318
|
+
return await fs.promises.readFile(pdfPath, signal ? { signal } : undefined);
|
|
319
|
+
}
|
|
320
|
+
async extractPdfText(buffer, maxChars, signal) {
|
|
321
|
+
return await extractPdfTextLayer(buffer, maxChars, signal);
|
|
322
|
+
}
|
|
242
323
|
setHostProfile(profile) {
|
|
243
324
|
this.hostProfile = { ...profile };
|
|
244
325
|
}
|
|
@@ -282,8 +363,9 @@ class ToolExecutor {
|
|
|
282
363
|
t('browser_forward', 'Navigate the controlled browser forward.', {}, []),
|
|
283
364
|
t('browser_reload', 'Reload the controlled browser.', {}, []),
|
|
284
365
|
t('browser_cdp', 'Run a raw Chrome DevTools Protocol command against the controlled browser. Advanced use only.', { method: { type: 'string' }, params: { type: 'object' } }, ['method']),
|
|
285
|
-
t('browser_use', 'Native observe-then-act control
|
|
366
|
+
t('browser_use', 'Native observe-then-act browser control. visible defaults to true and uses the right-sidebar built-in browser. Set visible=false to run on an independent host-owned background page that never connects to, displays, or renders the right-sidebar webview. Keep the same visible value throughout one observe/action sequence. Receipts are owner/runtime/surface scoped; stale observations are rejected.', {
|
|
286
367
|
action: { type: 'string', enum: browserUseActions },
|
|
368
|
+
visible: { type: 'boolean', description: 'Whether to bind to the visible right-sidebar browser. Defaults to true. false uses an independent background execution surface and does not create or touch the sidebar webview.' },
|
|
287
369
|
action_id: { type: 'string', description: 'Unique idempotency id for this action. Reusing it returns the original receipt without repeating the action.' },
|
|
288
370
|
page_generation: { type: 'number', description: 'Generation returned by the latest observe receipt.' },
|
|
289
371
|
observation_id: { type: 'string', description: 'Opaque observation capability returned by the latest observe receipt.' },
|
|
@@ -346,8 +428,9 @@ class ToolExecutor {
|
|
|
346
428
|
},
|
|
347
429
|
},
|
|
348
430
|
}, ['action']),
|
|
349
|
-
t('image_inspect', 'Inspect a durable user-submitted image by stable attachment_id
|
|
350
|
-
action: { type: 'string', enum: ['source_info', 'crop'] },
|
|
431
|
+
t('image_inspect', 'Inspect a durable user-submitted image by stable attachment_id or latest-message image_index, or send one active-workspace PNG/JPEG to the current validated vision model with action=inspect. Use source_info first when submitted-image dimensions are unknown, then crop with pixel coordinates. Workspace observations and derived crops are current-turn only; image bytes never enter durable tool history.', {
|
|
432
|
+
action: { type: 'string', enum: ['source_info', 'crop', 'inspect'] },
|
|
433
|
+
path: { type: 'string', description: 'For action=inspect, a workspace-relative PNG/JPEG path. Absolute paths are accepted only when they remain inside the active workspace.' },
|
|
351
434
|
attachment_id: { type: 'string', description: 'Stable user-image attachment id from the visible conversation. Prefer this when revisiting an older submitted image.' },
|
|
352
435
|
image_index: { type: 'number', description: '1-based image index in the latest user message containing submitted images. Defaults to 1.' },
|
|
353
436
|
x: { type: 'number', description: 'Crop left edge in source-image pixels.' },
|
|
@@ -366,10 +449,11 @@ class ToolExecutor {
|
|
|
366
449
|
path: { type: 'string', description: 'For source=image, a workspace PNG/JPEG/BMP path.' },
|
|
367
450
|
fallback_reason: { type: 'string', enum: ['vision_unavailable', 'vision_failed'] },
|
|
368
451
|
}, ['source', 'fallback_reason']),
|
|
369
|
-
t('pdf_read', 'Read a PDF with enforced fallback order: embedded text layer first; if unreadable, render the requested page in Newmark Browser and send a screenshot to a validated vision model; use bundled Chinese/English OCR only when vision is unavailable, or later through ocr_read after vision failed. Designed for scanned PDFs, not layout/table reconstruction.', {
|
|
452
|
+
t('pdf_read', 'Read a PDF with enforced fallback order: embedded text layer first; if unreadable, render the requested page in Newmark Browser and send a screenshot to a validated vision model; use bundled Chinese/English OCR only when vision is unavailable, or later through ocr_read after vision failed. One cumulative timeout covers the entire PDF read, including file I/O, pdf.js parsing, and rendered-page observation. Designed for scanned PDFs, not layout/table reconstruction.', {
|
|
370
453
|
path: { type: 'string', description: 'Workspace PDF path.' },
|
|
371
454
|
page: { type: 'number', minimum: 1, maximum: 100, description: 'Page to render when no usable text layer exists. Defaults to 1.' },
|
|
372
455
|
max_chars: { type: 'number', minimum: 500, maximum: 100000 },
|
|
456
|
+
timeout_ms: { type: 'number', minimum: 1000, maximum: 120000, description: 'Bounded entire PDF read timeout. Defaults to 30000 ms. A timeout returns a recoverable tool result and does not abort the Agent run.' },
|
|
373
457
|
}, ['path']),
|
|
374
458
|
t('terminal_takeover', 'Take over a persistent owner-scoped PTY session that is independent from the one-shot bash tool. Actions: start creates/reuses a named PTY, write sends a command to the same session, read returns its output buffer, resize updates PTY geometry, detach releases the UI attachment without stopping the shell, stop interrupts it, list shows sessions. Use this when the user wants continuous terminal state such as cd/env/process context or interactive TTY programs.', {
|
|
375
459
|
action: { type: 'string', enum: ['start', 'write', 'read', 'resize', 'detach', 'stop', 'list'] },
|
|
@@ -517,6 +601,7 @@ class ToolExecutor {
|
|
|
517
601
|
copy.function.description = 'Plan read-only browser: observe, navigate, wait, extract only.';
|
|
518
602
|
copy.function.parameters.properties = {
|
|
519
603
|
action: { type: 'string', enum: [...toolPolicy_1.PLAN_BROWSER_USE_ACTIONS] },
|
|
604
|
+
visible: copy.function.parameters.properties.visible,
|
|
520
605
|
action_id: copy.function.parameters.properties.action_id,
|
|
521
606
|
page_generation: copy.function.parameters.properties.page_generation,
|
|
522
607
|
observation_id: copy.function.parameters.properties.observation_id,
|
|
@@ -688,6 +773,7 @@ class ToolExecutor {
|
|
|
688
773
|
const scope = browserUseScope(context, wsPath);
|
|
689
774
|
const request = {
|
|
690
775
|
...scope,
|
|
776
|
+
visible: typeof args.visible === 'boolean' ? args.visible : true,
|
|
691
777
|
action: String(args.action || '').trim().toLowerCase(),
|
|
692
778
|
...(g('action_id') ? { actionId: g('action_id') } : {}),
|
|
693
779
|
...(args.page_generation !== undefined ? { pageGeneration: Number(args.page_generation) } : {}),
|
|
@@ -760,47 +846,79 @@ class ToolExecutor {
|
|
|
760
846
|
const pdfPath = resolve(g('path'));
|
|
761
847
|
if (path.extname(pdfPath).toLowerCase() !== '.pdf')
|
|
762
848
|
return '[pdf_read error] path must end in .pdf.';
|
|
763
|
-
const stat = fs.statSync(pdfPath);
|
|
764
|
-
if (!stat.isFile() || stat.size <= 0 || stat.size > 250 * 1024 * 1024) {
|
|
765
|
-
return '[pdf_read error] PDF must be a regular file no larger than 250 MB.';
|
|
766
|
-
}
|
|
767
849
|
const maxChars = Math.max(500, Math.min(100_000, Number(args.max_chars || 50_000)));
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
|
|
850
|
+
const page = Math.max(1, Math.min(100, Math.floor(Number(args.page || 1))));
|
|
851
|
+
const timeoutMs = Math.max(1000, Math.min(120_000, Number(args.timeout_ms || 30_000)));
|
|
852
|
+
const guard = abortGuard(context.signal, timeoutMs);
|
|
853
|
+
let stage = 'file_stat';
|
|
854
|
+
try {
|
|
855
|
+
const guardedContext = { ...context, signal: guard.signal };
|
|
856
|
+
const stat = await fs.promises.stat(pdfPath);
|
|
857
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > 250 * 1024 * 1024) {
|
|
858
|
+
return '[pdf_read error] PDF must be a regular file no larger than 250 MB.';
|
|
859
|
+
}
|
|
860
|
+
stage = 'file_read';
|
|
861
|
+
const bytes = await this.readPdfFile(pdfPath, guard.signal);
|
|
862
|
+
stage = 'text_parse';
|
|
863
|
+
const textLayer = await this.extractPdfText(bytes, maxChars, guard.signal);
|
|
864
|
+
const readableCount = (textLayer.match(/[A-Za-z0-9\u3400-\u9fff]/g) || []).length;
|
|
865
|
+
if (readableCount >= 20) {
|
|
866
|
+
return JSON.stringify({
|
|
867
|
+
ok: true,
|
|
868
|
+
source: 'pdf_text_layer',
|
|
869
|
+
recognition_order: 'text>vision>local_ocr',
|
|
870
|
+
text: textLayer,
|
|
871
|
+
truncated: textLayer.length >= maxChars,
|
|
872
|
+
}, null, 2);
|
|
873
|
+
}
|
|
874
|
+
stage = 'rendered_page_observation';
|
|
875
|
+
const url = `${(0, url_1.pathToFileURL)(pdfPath).toString()}#page=${page}&zoom=page-fit`;
|
|
876
|
+
const opened = await this.browserRun({ action: 'open', url }, guard.signal, guardedContext, wsPath);
|
|
877
|
+
if (!opened.includes('[browser:open] OK')) {
|
|
878
|
+
return JSON.stringify({ ok: false, source: 'pdf_render', code: 'pdf_open_failed', error: opened || 'Unable to open PDF.' }, null, 2);
|
|
879
|
+
}
|
|
880
|
+
await abortableToolDelay(900, guard.signal);
|
|
881
|
+
const observed = await this.execute('browser_use', JSON.stringify({
|
|
882
|
+
action: 'observe',
|
|
883
|
+
action_id: `pdf-read-${crypto.randomUUID()}`,
|
|
884
|
+
max_chars: maxChars,
|
|
885
|
+
max_refs: 80,
|
|
886
|
+
}), wsPath, guardedContext);
|
|
887
|
+
if (guard.signal.aborted)
|
|
888
|
+
throw abortReason(guard.signal);
|
|
889
|
+
let parsed = observed;
|
|
890
|
+
try {
|
|
891
|
+
parsed = JSON.parse(observed);
|
|
892
|
+
}
|
|
893
|
+
catch { }
|
|
771
894
|
return JSON.stringify({
|
|
772
895
|
ok: true,
|
|
773
|
-
source: '
|
|
896
|
+
source: 'pdf_rendered_page',
|
|
897
|
+
page,
|
|
774
898
|
recognition_order: 'text>vision>local_ocr',
|
|
775
|
-
|
|
776
|
-
truncated: textLayer.length >= maxChars,
|
|
899
|
+
result: parsed,
|
|
777
900
|
}, null, 2);
|
|
778
901
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
902
|
+
catch (error) {
|
|
903
|
+
if (context.signal?.aborted)
|
|
904
|
+
throw abortReason(context.signal);
|
|
905
|
+
if (guard.signal.aborted) {
|
|
906
|
+
return JSON.stringify({
|
|
907
|
+
ok: false,
|
|
908
|
+
source: stage === 'rendered_page_observation' ? 'pdf_rendered_page' : 'pdf_read',
|
|
909
|
+
code: 'pdf_read_timeout',
|
|
910
|
+
stage,
|
|
911
|
+
page,
|
|
912
|
+
timeout_ms: timeoutMs,
|
|
913
|
+
recoverable: true,
|
|
914
|
+
error: `PDF read timed out during ${stage} after ${timeoutMs} ms. The Agent run can continue or retry with a larger timeout_ms.`,
|
|
915
|
+
}, null, 2);
|
|
916
|
+
}
|
|
917
|
+
throw error;
|
|
784
918
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
action: 'observe',
|
|
788
|
-
action_id: `pdf-read-${crypto.randomUUID()}`,
|
|
789
|
-
max_chars: maxChars,
|
|
790
|
-
max_refs: 80,
|
|
791
|
-
}), wsPath, context);
|
|
792
|
-
let parsed = observed;
|
|
793
|
-
try {
|
|
794
|
-
parsed = JSON.parse(observed);
|
|
919
|
+
finally {
|
|
920
|
+
guard.dispose();
|
|
795
921
|
}
|
|
796
|
-
catch { }
|
|
797
|
-
return JSON.stringify({
|
|
798
|
-
ok: true,
|
|
799
|
-
source: 'pdf_rendered_page',
|
|
800
|
-
page,
|
|
801
|
-
recognition_order: 'text>vision>local_ocr',
|
|
802
|
-
result: parsed,
|
|
803
|
-
}, null, 2);
|
|
804
922
|
}
|
|
805
923
|
case 'screen_capture': {
|
|
806
924
|
const target = g('target').toLowerCase() === 'application' ? 'application' : 'desktop';
|
|
@@ -1357,6 +1475,9 @@ class ToolExecutor {
|
|
|
1357
1475
|
}
|
|
1358
1476
|
}
|
|
1359
1477
|
async wsearch(query, signal) {
|
|
1478
|
+
return (await this.wsearchDetailed(query, signal)).text;
|
|
1479
|
+
}
|
|
1480
|
+
async wsearchDetailed(query, signal) {
|
|
1360
1481
|
const clean = (s) => s
|
|
1361
1482
|
.replace(/<[^>]+>/g, ' ')
|
|
1362
1483
|
.replace(/&/g, '&')
|
|
@@ -1367,34 +1488,23 @@ class ToolExecutor {
|
|
|
1367
1488
|
.replace(/\s+/g, ' ')
|
|
1368
1489
|
.trim();
|
|
1369
1490
|
const errors = [];
|
|
1491
|
+
let mcp = { invocationId: '', checkedAt: '', ok: false, attempts: [] };
|
|
1370
1492
|
try {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
try {
|
|
1375
|
-
const resp = await this.proxyFetch(url, {
|
|
1376
|
-
headers: { 'User-Agent': 'NewmarkAgent/1.0' },
|
|
1377
|
-
signal: guard.signal,
|
|
1378
|
-
});
|
|
1379
|
-
html = await resp.text();
|
|
1493
|
+
mcp = await this.webSearchMcpOnly(query, signal);
|
|
1494
|
+
if (mcp.ok && mcp.text) {
|
|
1495
|
+
return { invocationId: mcp.invocationId, checkedAt: mcp.checkedAt, ok: true, provider: mcp.provider || 'MCP search', text: mcp.text, attempts: mcp.attempts };
|
|
1380
1496
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
let m;
|
|
1387
|
-
while ((m = re.exec(html)) !== null && results.length < 8) {
|
|
1388
|
-
results.push(`${clean(m[2])}\n${clean(m[1])}\n${clean(m[3])}`);
|
|
1497
|
+
for (const attempt of mcp.attempts) {
|
|
1498
|
+
if (attempt.status === 'error')
|
|
1499
|
+
errors.push(`${attempt.name}: ${attempt.error || 'failed'}`);
|
|
1500
|
+
else if (attempt.status === 'empty')
|
|
1501
|
+
errors.push(`${attempt.name}: no results`);
|
|
1389
1502
|
}
|
|
1390
|
-
if (results.length > 0)
|
|
1391
|
-
return results.join('\n\n');
|
|
1392
|
-
errors.push('DuckDuckGo returned no parseable results');
|
|
1393
1503
|
}
|
|
1394
|
-
catch (
|
|
1504
|
+
catch (error) {
|
|
1395
1505
|
if (signal?.aborted)
|
|
1396
1506
|
throw abortReason(signal);
|
|
1397
|
-
errors.push(`
|
|
1507
|
+
errors.push(`Search MCP pool: ${error instanceof Error ? error.message : String(error)}`);
|
|
1398
1508
|
}
|
|
1399
1509
|
try {
|
|
1400
1510
|
const url = `https://www.bing.com/search?q=${encodeURIComponent(query)}`;
|
|
@@ -1422,7 +1532,7 @@ class ToolExecutor {
|
|
|
1422
1532
|
results.push(`${clean(title[2])}\n${clean(title[1])}\n${snippet ? clean(snippet[1]) : ''}`.trim());
|
|
1423
1533
|
}
|
|
1424
1534
|
if (results.length > 0)
|
|
1425
|
-
return results.join('\n\n');
|
|
1535
|
+
return { invocationId: mcp.invocationId, checkedAt: mcp.checkedAt, ok: true, provider: 'Bing HTTP', text: results.join('\n\n'), attempts: mcp.attempts };
|
|
1426
1536
|
errors.push('Bing returned no parseable results');
|
|
1427
1537
|
}
|
|
1428
1538
|
catch (e) {
|
|
@@ -1430,7 +1540,36 @@ class ToolExecutor {
|
|
|
1430
1540
|
throw abortReason(signal);
|
|
1431
1541
|
errors.push(`Bing: ${e instanceof Error ? e.message : String(e)}`);
|
|
1432
1542
|
}
|
|
1433
|
-
|
|
1543
|
+
try {
|
|
1544
|
+
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
1545
|
+
const guard = abortGuard(signal, 15000);
|
|
1546
|
+
let html = '';
|
|
1547
|
+
try {
|
|
1548
|
+
const resp = await this.proxyFetch(url, {
|
|
1549
|
+
headers: { 'User-Agent': 'NewmarkAgent/1.0' },
|
|
1550
|
+
signal: guard.signal,
|
|
1551
|
+
});
|
|
1552
|
+
html = await resp.text();
|
|
1553
|
+
}
|
|
1554
|
+
finally {
|
|
1555
|
+
guard.dispose();
|
|
1556
|
+
}
|
|
1557
|
+
const re = /<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>(.*?)<\/a>[\s\S]*?class="result__snippet">(.*?)<\/a>/g;
|
|
1558
|
+
const results = [];
|
|
1559
|
+
let match;
|
|
1560
|
+
while ((match = re.exec(html)) !== null && results.length < 8) {
|
|
1561
|
+
results.push(`${clean(match[2])}\n${clean(match[1])}\n${clean(match[3])}`);
|
|
1562
|
+
}
|
|
1563
|
+
if (results.length > 0)
|
|
1564
|
+
return { invocationId: mcp.invocationId, checkedAt: mcp.checkedAt, ok: true, provider: 'DuckDuckGo HTTP', text: results.join('\n\n'), attempts: mcp.attempts };
|
|
1565
|
+
errors.push('DuckDuckGo returned no parseable results');
|
|
1566
|
+
}
|
|
1567
|
+
catch (e) {
|
|
1568
|
+
if (signal?.aborted)
|
|
1569
|
+
throw abortReason(signal);
|
|
1570
|
+
errors.push(`DuckDuckGo: ${e instanceof Error ? e.message : String(e)}`);
|
|
1571
|
+
}
|
|
1572
|
+
return { invocationId: mcp.invocationId, checkedAt: mcp.checkedAt, ok: false, provider: '', text: `[web_search] No results. ${errors.join('; ')}`, attempts: mcp.attempts };
|
|
1434
1573
|
}
|
|
1435
1574
|
async wfetch(url, signal) {
|
|
1436
1575
|
try {
|
package/dist/tui/src/data.js
CHANGED
|
@@ -20,7 +20,7 @@ const navigation = [
|
|
|
20
20
|
const workspace = {
|
|
21
21
|
id: "workspace-newmark-agent-demo",
|
|
22
22
|
name: "Newmark Agent",
|
|
23
|
-
path: "C:\\Users\\
|
|
23
|
+
path: "C:\\Users\\DemoUser\\Projects\\Newmark Agent",
|
|
24
24
|
isInternal: false,
|
|
25
25
|
hostBinding: "demo-host",
|
|
26
26
|
icon: "[W]",
|
|
@@ -40,7 +40,7 @@ const workspaces = [
|
|
|
40
40
|
{
|
|
41
41
|
id: "workspace-condensed-lab-demo",
|
|
42
42
|
name: "Condensed Lab",
|
|
43
|
-
path: "C:\\Users\\
|
|
43
|
+
path: "C:\\Users\\DemoUser\\Projects\\Condensed Lab",
|
|
44
44
|
isInternal: false,
|
|
45
45
|
hostBinding: "demo-host",
|
|
46
46
|
icon: "[W]",
|
package/dist/ui/index.html
CHANGED
|
@@ -5894,6 +5894,14 @@ button.diff-block-header:active:not(:disabled),
|
|
|
5894
5894
|
button.work-review-head:active:not(:disabled) {
|
|
5895
5895
|
scale: 1;
|
|
5896
5896
|
filter: none;
|
|
5897
|
+
}
|
|
5898
|
+
button.conversation-work-run-head:active:not(:disabled) {
|
|
5899
|
+
background: transparent !important;
|
|
5900
|
+
background-image: none !important;
|
|
5901
|
+
}
|
|
5902
|
+
button.shell-block-header:active:not(:disabled),
|
|
5903
|
+
button.diff-block-header:active:not(:disabled),
|
|
5904
|
+
button.work-review-head:active:not(:disabled) {
|
|
5897
5905
|
background-color: revert !important;
|
|
5898
5906
|
}
|
|
5899
5907
|
|
|
@@ -10034,7 +10042,9 @@ var STREAMING_LARGE_TEXT_INTERVAL_MS = 100;
|
|
|
10034
10042
|
|
|
10035
10043
|
function updateMsg(div, text, role, mode, model, options) {
|
|
10036
10044
|
if (!div) return;
|
|
10045
|
+
var preserveRunFinalResponse = div.classList && div.classList.contains('run-final-response');
|
|
10037
10046
|
if (role) div.className = 'chat-msg ' + role;
|
|
10047
|
+
if (preserveRunFinalResponse) div.classList.add('run-final-response');
|
|
10038
10048
|
text = redactSensitiveText(text);
|
|
10039
10049
|
div._newmarkMessageText = String(text || '');
|
|
10040
10050
|
var body = div.querySelector('.msg-body');
|
|
@@ -11232,6 +11242,7 @@ function presentedWorkRunEvents(run, includeGuides) {
|
|
|
11232
11242
|
var rawEvents = (run.events || []).filter(publicWorkEvent).filter(function(event) {
|
|
11233
11243
|
return includeGuides !== false || !(String(event && event.type || '').toLowerCase().indexOf('guide') === 0 || !!(event && event.guide));
|
|
11234
11244
|
}).sort(compareConversationWorkEvents);
|
|
11245
|
+
var terminalAssistantResponse = terminalAssistantResponseForRun(run);
|
|
11235
11246
|
var terminalInterrupted = ['interrupted', 'force_interrupted'].indexOf(String(run && run.status || '')) >= 0;
|
|
11236
11247
|
var events = [];
|
|
11237
11248
|
var publicText = '';
|
|
@@ -11258,6 +11269,10 @@ function presentedWorkRunEvents(run, includeGuides) {
|
|
|
11258
11269
|
// response boundary, while retaining every distinct provider reply.
|
|
11259
11270
|
publicText = '';
|
|
11260
11271
|
while (events.length && ['public_text', 'partial_text'].indexOf(String(events[events.length - 1].type || '').toLowerCase()) >= 0) events.pop();
|
|
11272
|
+
// If an interrupted run never produced final_response, its last public
|
|
11273
|
+
// response becomes the standalone Agent boundary below the Build. Do
|
|
11274
|
+
// not keep the same text inside the expanded Build as a duplicate.
|
|
11275
|
+
if (terminalAssistantResponse && terminalAssistantResponse.interrupted && terminalAssistantResponse.event === rawEvent) continue;
|
|
11261
11276
|
events.push(rawEvent);
|
|
11262
11277
|
continue;
|
|
11263
11278
|
}
|
|
@@ -12011,6 +12026,47 @@ function renderConversationHistoryFirst(target) {
|
|
|
12011
12026
|
}
|
|
12012
12027
|
|
|
12013
12028
|
function renderChatMessages(messages, target) {
|
|
12029
|
+
var liveArea = els['chat-area'];
|
|
12030
|
+
if (!liveArea) return;
|
|
12031
|
+
var previousRendered = Array.isArray(state.renderedChatMessages) ? state.renderedChatMessages.slice() : [];
|
|
12032
|
+
var previousHtml = liveArea.innerHTML;
|
|
12033
|
+
var previousScrollTop = liveArea.scrollTop;
|
|
12034
|
+
var wasAtBottom = shouldAutoScroll(liveArea);
|
|
12035
|
+
var stagingArea = liveArea.cloneNode(false);
|
|
12036
|
+
els['chat-area'] = stagingArea;
|
|
12037
|
+
try {
|
|
12038
|
+
renderChatMessagesUnsafe(messages, target);
|
|
12039
|
+
liveArea.replaceChildren.apply(liveArea, Array.prototype.slice.call(stagingArea.childNodes));
|
|
12040
|
+
if (wasAtBottom) _chatShouldAutoScroll = true;
|
|
12041
|
+
else liveArea.scrollTop = previousScrollTop;
|
|
12042
|
+
} catch (error) {
|
|
12043
|
+
state.renderedChatMessages = previousRendered;
|
|
12044
|
+
liveArea.innerHTML = previousHtml;
|
|
12045
|
+
liveArea.scrollTop = previousScrollTop;
|
|
12046
|
+
console.error('[ui_chat_render_failed]', error);
|
|
12047
|
+
showUiNotice(currentLang() === 'zh' ? '对话渲染出现异常,已保留原内容。' : 'Chat rendering failed; the previous content was preserved.', 'error', 'chat-render-failed');
|
|
12048
|
+
} finally {
|
|
12049
|
+
els['chat-area'] = liveArea;
|
|
12050
|
+
}
|
|
12051
|
+
}
|
|
12052
|
+
|
|
12053
|
+
function terminalAssistantResponseForRun(run) {
|
|
12054
|
+
var events = Array.isArray(run && run.events) ? run.events : [];
|
|
12055
|
+
for (var finalIndex = events.length - 1; finalIndex >= 0; finalIndex--) {
|
|
12056
|
+
if (String(events[finalIndex] && events[finalIndex].type || '').toLowerCase() !== 'final_response') continue;
|
|
12057
|
+
var finalContent = String(events[finalIndex] && events[finalIndex].content || '').trim();
|
|
12058
|
+
if (finalContent) return { content: finalContent, timestamp: events[finalIndex].timestamp || run.endedAt || '', interrupted: false, event: events[finalIndex] };
|
|
12059
|
+
}
|
|
12060
|
+
if (['interrupted', 'force_interrupted'].indexOf(String(run && run.status || '').toLowerCase()) < 0) return null;
|
|
12061
|
+
for (var responseIndex = events.length - 1; responseIndex >= 0; responseIndex--) {
|
|
12062
|
+
if (String(events[responseIndex] && events[responseIndex].type || '').toLowerCase() !== 'response') continue;
|
|
12063
|
+
var responseContent = String(events[responseIndex] && events[responseIndex].content || '').trim();
|
|
12064
|
+
if (responseContent) return { content: responseContent, timestamp: events[responseIndex].timestamp || run.endedAt || '', interrupted: true, event: events[responseIndex] };
|
|
12065
|
+
}
|
|
12066
|
+
return null;
|
|
12067
|
+
}
|
|
12068
|
+
|
|
12069
|
+
function renderChatMessagesUnsafe(messages, target) {
|
|
12014
12070
|
var renderTarget = target || currentConversationTarget(activeConversationId());
|
|
12015
12071
|
var normalizedMessages = cacheConversationMessages(messages, renderTarget);
|
|
12016
12072
|
state.renderedChatMessages = normalizedMessages;
|
|
@@ -12035,6 +12091,7 @@ function renderChatMessages(messages, target) {
|
|
|
12035
12091
|
var persistedRunsById = {};
|
|
12036
12092
|
var persistedRunIndexes = {};
|
|
12037
12093
|
var renderedRunIds = {};
|
|
12094
|
+
var recoveredAssistantRunIds = {};
|
|
12038
12095
|
var messageRunRoles = {};
|
|
12039
12096
|
state.guideMessageIndexByClientId = {};
|
|
12040
12097
|
persistedRuns.forEach(function(run, runIndex) {
|
|
@@ -12053,26 +12110,20 @@ function renderChatMessages(messages, target) {
|
|
|
12053
12110
|
if (!messageRunRoles[runId]) messageRunRoles[runId] = {};
|
|
12054
12111
|
if (role === 'user' || role === 'assistant') messageRunRoles[runId][role] = true;
|
|
12055
12112
|
});
|
|
12056
|
-
function finalResponseForRun(run) {
|
|
12057
|
-
var events = Array.isArray(run && run.events) ? run.events : [];
|
|
12058
|
-
for (var eventIndex = events.length - 1; eventIndex >= 0; eventIndex--) {
|
|
12059
|
-
if (String(events[eventIndex] && events[eventIndex].type || '').toLowerCase() !== 'final_response') continue;
|
|
12060
|
-
var content = String(events[eventIndex] && events[eventIndex].content || '').trim();
|
|
12061
|
-
if (content) return { content: content, timestamp: events[eventIndex].timestamp || run.endedAt || '' };
|
|
12062
|
-
}
|
|
12063
|
-
return null;
|
|
12064
|
-
}
|
|
12065
12113
|
function renderOwnedWorkRun(run, includeRecoveredFinal) {
|
|
12066
12114
|
var runId = String(run && run.runId || '');
|
|
12067
|
-
if (!runId
|
|
12115
|
+
if (!runId) return;
|
|
12068
12116
|
var roles = messageRunRoles[runId] || {};
|
|
12069
|
-
if (!
|
|
12070
|
-
|
|
12117
|
+
if (!renderedRunIds[runId]) {
|
|
12118
|
+
if (!roles.user && String(run.primaryPrompt || '').trim()) {
|
|
12119
|
+
addMsg('user', run.primaryPrompt, '', '', -1, { runId: runId, target: renderTarget, timestamp: run.startedAt || '', recovered: true });
|
|
12120
|
+
}
|
|
12121
|
+
renderConversationWorkRun(run, workRunBeforeElement(run));
|
|
12122
|
+
renderedRunIds[runId] = true;
|
|
12071
12123
|
}
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
var recoveredFinal = finalResponseForRun(run);
|
|
12124
|
+
if (includeRecoveredFinal && !roles.assistant && !recoveredAssistantRunIds[runId]) {
|
|
12125
|
+
recoveredAssistantRunIds[runId] = true;
|
|
12126
|
+
var recoveredFinal = terminalAssistantResponseForRun(run);
|
|
12076
12127
|
if (recoveredFinal) addMsg('assistant', recoveredFinal.content, '', '', -1, { runId: runId, target: renderTarget, timestamp: recoveredFinal.timestamp, recovered: true });
|
|
12077
12128
|
}
|
|
12078
12129
|
}
|
|
@@ -12354,6 +12405,18 @@ function renderAgentWorkEvent(event) {
|
|
|
12354
12405
|
var finalMsg = addMsg('assistant', content, event.mode || state.mode, event.model || state.model, undefined, { runId: event.runId || '', target: workRun.target || currentConversationTarget(eventConversationId) });
|
|
12355
12406
|
markFinalResponseMsg(eventConversationId, content, finalMsg);
|
|
12356
12407
|
}
|
|
12408
|
+
} else if (workRun && (['interrupted', 'force_interrupted'].indexOf(String(type).toLowerCase()) >= 0
|
|
12409
|
+
|| (type === 'status' && ['interrupted', 'force_interrupted'].indexOf(String(event.status || workRun.status || '').toLowerCase()) >= 0))) {
|
|
12410
|
+
ui.activeWorkflowText = '';
|
|
12411
|
+
ui.activeWorkflowMsg = null;
|
|
12412
|
+
ui._streamFlushPending = false;
|
|
12413
|
+
ui._lastLargeStreamRenderAt = 0;
|
|
12414
|
+
var interruptedResponse = terminalAssistantResponseForRun(workRun);
|
|
12415
|
+
if (interruptedResponse && interruptedResponse.content) {
|
|
12416
|
+
var interruptedMsg = addMsg('assistant', interruptedResponse.content, event.mode || state.mode, event.model || state.model, undefined, { runId: event.runId || workRun.runId || '', target: workRun.target || currentConversationTarget(eventConversationId) });
|
|
12417
|
+
markFinalResponseMsg(eventConversationId, interruptedResponse.content, interruptedMsg);
|
|
12418
|
+
}
|
|
12419
|
+
finishToolBatch(eventConversationId);
|
|
12357
12420
|
} else if (type === 'tool_call' && !workRun) {
|
|
12358
12421
|
upsertToolEvent(event, '');
|
|
12359
12422
|
} else if (type === 'tool_result' && !workRun) {
|
|
@@ -16176,7 +16239,7 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
16176
16239
|
applyAutoRouteRatingState(s);
|
|
16177
16240
|
hydrateConversationBranchState(s);
|
|
16178
16241
|
syncWorkRunsSnapshot(s.workRuns, lockedTarget);
|
|
16179
|
-
renderChatMessages(s.chatMessages
|
|
16242
|
+
if (Array.isArray(s.chatMessages)) renderChatMessages(s.chatMessages, lockedTarget);
|
|
16180
16243
|
}
|
|
16181
16244
|
if (s && s.queued) {
|
|
16182
16245
|
var refreshedQueue = setBackendQueueForTarget(s.queued, lockedTarget);
|