testronaut 1.3.0 → 1.3.2
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/README.md +2 -0
- package/bin/cli.js +124 -16
- package/bin/init.js +2 -0
- package/bin/initHelpers.js +8 -0
- package/core/agent.js +21 -13
- package/core/config.js +121 -0
- package/core/missionDiscovery.js +55 -0
- package/core/turnLoop.js +169 -8
- package/package.json +3 -2
- package/runner/testronaut.js +71 -2
- package/tests/binTests/cli.helpers.test.js +45 -0
- package/tests/binTests/init.test.js +6 -0
- package/tests/binTests/initHelpersTests/defaultConfig.test.js +3 -0
- package/tests/coreTests/config.test.js +137 -0
- package/tests/coreTests/missionDiscovery.test.js +81 -0
- package/tests/coreTests/turnLoop.test.js +100 -3
- package/tests/runnerTests/testronaut.test.js +76 -6
- package/tests/toolsTests/domControl.test.js +35 -0
- package/tests/toolsTests/generateHtmlReport.test.js +90 -0
- package/tests/toolsTests/toolSchema.test.js +24 -0
- package/tools/chromeBrowser.js +211 -4
- package/tools/domControl.js +10 -3
- package/tools/generateHtmlReport.js +38 -4
- package/tools/toolSchema.js +43 -1
package/tools/chromeBrowser.js
CHANGED
|
@@ -41,7 +41,7 @@ function appendFileEvent(eventObj = {}) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export class ChromeBrowser {
|
|
44
|
-
constructor() {
|
|
44
|
+
constructor(options = {}) {
|
|
45
45
|
this.browser = null;
|
|
46
46
|
this.context = null;
|
|
47
47
|
this.page = null;
|
|
@@ -49,6 +49,32 @@ export class ChromeBrowser {
|
|
|
49
49
|
// simple registry
|
|
50
50
|
this.pages = []; // [{ id, page }]
|
|
51
51
|
this.currentId = ''; // page id we currently control
|
|
52
|
+
|
|
53
|
+
this.domListLimit = options.domListLimit;
|
|
54
|
+
this.debugEnabled = !!options.debug;
|
|
55
|
+
this._domLimitLogged = false;
|
|
56
|
+
|
|
57
|
+
const defaultResourceGuard = {
|
|
58
|
+
enabled: true,
|
|
59
|
+
hrefIncludes: ['/document/', '/file/', '/download', '/attachment/'],
|
|
60
|
+
dataTypes: ['document', 'file', 'item', 'row'],
|
|
61
|
+
};
|
|
62
|
+
const guardCfg = options.resourceGuard || defaultResourceGuard;
|
|
63
|
+
this.resourceGuard = {
|
|
64
|
+
enabled: guardCfg.enabled !== false,
|
|
65
|
+
hrefIncludes: Array.isArray(guardCfg.hrefIncludes)
|
|
66
|
+
? guardCfg.hrefIncludes.map(s => s.toLowerCase())
|
|
67
|
+
: defaultResourceGuard.hrefIncludes,
|
|
68
|
+
dataTypes: Array.isArray(guardCfg.dataTypes)
|
|
69
|
+
? guardCfg.dataTypes.map(s => s.toLowerCase())
|
|
70
|
+
: defaultResourceGuard.dataTypes,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_debugLog(...args) {
|
|
75
|
+
if (this.debugEnabled) {
|
|
76
|
+
console.log('[debug]', ...args);
|
|
77
|
+
}
|
|
52
78
|
}
|
|
53
79
|
|
|
54
80
|
_makePageId(page) {
|
|
@@ -791,6 +817,57 @@ export class ChromeBrowser {
|
|
|
791
817
|
{ limit = 100000, exclude = true, focus = [], loadState = 'networkidle', timeout = 10000 } = {},
|
|
792
818
|
agentMemory = {}
|
|
793
819
|
) {
|
|
820
|
+
const matchesHrefPattern = (href = '') => {
|
|
821
|
+
const lower = (href || '').toLowerCase();
|
|
822
|
+
return this.resourceGuard.hrefIncludes.some(p => lower.includes(p));
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
const extractInitialDocumentList = (rawHtml) => {
|
|
826
|
+
const key = '"initialDocumentList":';
|
|
827
|
+
const idx = rawHtml.indexOf(key);
|
|
828
|
+
if (idx < 0) return [];
|
|
829
|
+
const start = rawHtml.indexOf('[', idx);
|
|
830
|
+
if (start < 0) return [];
|
|
831
|
+
let depth = 0;
|
|
832
|
+
let end = -1;
|
|
833
|
+
for (let i = start; i < rawHtml.length; i++) {
|
|
834
|
+
const ch = rawHtml[i];
|
|
835
|
+
if (ch === '[') depth++;
|
|
836
|
+
else if (ch === ']') depth--;
|
|
837
|
+
if (depth === 0) {
|
|
838
|
+
end = i;
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (end === -1) return [];
|
|
843
|
+
const jsonSlice = rawHtml.slice(start, end + 1);
|
|
844
|
+
try {
|
|
845
|
+
const arr = JSON.parse(jsonSlice);
|
|
846
|
+
return Array.isArray(arr) ? arr : [];
|
|
847
|
+
} catch {
|
|
848
|
+
return [];
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
const normalizeListLimit = () => {
|
|
853
|
+
if (this.domListLimit === Infinity) return Infinity;
|
|
854
|
+
const asNum = Number(this.domListLimit);
|
|
855
|
+
if (Number.isFinite(asNum)) {
|
|
856
|
+
return Math.min(100, Math.max(0, asNum));
|
|
857
|
+
}
|
|
858
|
+
return 3; // current default behavior (keep first 3 matching items)
|
|
859
|
+
};
|
|
860
|
+
const listLimit = normalizeListLimit();
|
|
861
|
+
if (this.debugEnabled && !this._domLimitLogged) {
|
|
862
|
+
const pretty = listLimit === Infinity ? 'all' : listLimit;
|
|
863
|
+
this._debugLog(`DOM list limit resolved`, { raw: this.domListLimit, applied: pretty });
|
|
864
|
+
this._domLimitLogged = true;
|
|
865
|
+
}
|
|
866
|
+
if (this.debugEnabled) {
|
|
867
|
+
const pretty = listLimit === Infinity ? 'all' : listLimit;
|
|
868
|
+
this._debugLog(`get_dom invoked`, { limit, exclude, focus, listLimit: pretty });
|
|
869
|
+
}
|
|
870
|
+
|
|
794
871
|
// --- 1) Ensure we have a live page ---
|
|
795
872
|
const hasContext = !!this.context;
|
|
796
873
|
|
|
@@ -865,7 +942,7 @@ export class ChromeBrowser {
|
|
|
865
942
|
|
|
866
943
|
if (exclude) {
|
|
867
944
|
$ = await removeNonContentTags($);
|
|
868
|
-
$ = await reduceRepeatedDivs(
|
|
945
|
+
$ = await reduceRepeatedDivs($, listLimit);
|
|
869
946
|
// If you later re-enable:
|
|
870
947
|
// $ = await removeObfuscatedClassNames($);
|
|
871
948
|
// $ = await removeAutoGeneratedClasses($);
|
|
@@ -874,6 +951,100 @@ export class ChromeBrowser {
|
|
|
874
951
|
// Minify whitespace *after* structural cleanup
|
|
875
952
|
$ = minifyDomWhitespace($);
|
|
876
953
|
|
|
954
|
+
if (this.debugEnabled) {
|
|
955
|
+
const liCount = $('li').length;
|
|
956
|
+
const ulCount = $('ul').length;
|
|
957
|
+
const ulSizes = [];
|
|
958
|
+
$('ul').each((_, el) => {
|
|
959
|
+
ulSizes.push($(el).children('li').length);
|
|
960
|
+
});
|
|
961
|
+
const maxUlSize = ulSizes.length ? Math.max(...ulSizes) : 0;
|
|
962
|
+
const anchorDocs = $('a[href]').filter((_, el) => {
|
|
963
|
+
if (!this.resourceGuard.enabled) return false;
|
|
964
|
+
const href = $(el).attr('href');
|
|
965
|
+
return matchesHrefPattern(href);
|
|
966
|
+
}).map((_, el) => {
|
|
967
|
+
const $el = $(el);
|
|
968
|
+
return {
|
|
969
|
+
id: $el.closest('[data-id]').attr('data-id') || $el.attr('id') || '',
|
|
970
|
+
title: ($el.attr('data-title') || $el.text() || '').trim().slice(0, 160),
|
|
971
|
+
href: $el.attr('href') || '',
|
|
972
|
+
};
|
|
973
|
+
}).get();
|
|
974
|
+
const docCards = $('.document-list-view-documents').map((_, el) => {
|
|
975
|
+
const $el = $(el);
|
|
976
|
+
const anchor = $el.find('a[href*="/document/"]').first();
|
|
977
|
+
return {
|
|
978
|
+
id: $el.attr('data-id') || $el.attr('id') || '',
|
|
979
|
+
title: ($el.attr('data-title') || anchor.text() || '').trim().slice(0, 160),
|
|
980
|
+
href: anchor.attr('href') || '',
|
|
981
|
+
};
|
|
982
|
+
}).get();
|
|
983
|
+
const dataDocs = $('[data-type]').filter((_, el) => {
|
|
984
|
+
if (!this.resourceGuard.enabled) return false;
|
|
985
|
+
const val = ($(el).attr('data-type') || '').toLowerCase();
|
|
986
|
+
return this.resourceGuard.dataTypes.includes(val);
|
|
987
|
+
}).map((_, el) => {
|
|
988
|
+
const $el = $(el);
|
|
989
|
+
const href = $el.find('a[href*="/document/"]').attr('href') || '';
|
|
990
|
+
return {
|
|
991
|
+
id: $el.attr('data-id') || $el.attr('id') || '',
|
|
992
|
+
title: ($el.attr('data-title') || '').trim().slice(0, 160),
|
|
993
|
+
href,
|
|
994
|
+
};
|
|
995
|
+
}).get();
|
|
996
|
+
const mergedDocs = [...anchorDocs, ...dataDocs, ...docCards]
|
|
997
|
+
.filter(d => d.href || d.title)
|
|
998
|
+
.map(d => ({
|
|
999
|
+
id: d.id || '',
|
|
1000
|
+
title: d.title || '',
|
|
1001
|
+
href: d.href || '',
|
|
1002
|
+
}));
|
|
1003
|
+
// dedupe by href+title to avoid spam
|
|
1004
|
+
const seen = new Set();
|
|
1005
|
+
const docItems = [];
|
|
1006
|
+
for (const d of mergedDocs) {
|
|
1007
|
+
const key = `${d.href}|${d.title}`;
|
|
1008
|
+
if (seen.has(key)) continue;
|
|
1009
|
+
seen.add(key);
|
|
1010
|
+
docItems.push(d);
|
|
1011
|
+
}
|
|
1012
|
+
const sampleLi = $('li').slice(0, 5).map((_, el) => $(el).text().trim().slice(0, 80)).get();
|
|
1013
|
+
// Fallback: parse the page's embedded initialDocumentList JSON if present
|
|
1014
|
+
const scriptDocsRaw = extractInitialDocumentList(html);
|
|
1015
|
+
const scriptDocs = scriptDocsRaw.map(d => ({
|
|
1016
|
+
id: d.Id || d.id || '',
|
|
1017
|
+
title: d.Title || d.title || '',
|
|
1018
|
+
href: d.ContentUrl || d.ContentURL || '',
|
|
1019
|
+
}));
|
|
1020
|
+
for (const d of scriptDocs) {
|
|
1021
|
+
const key = `${d.href}|${d.title}`;
|
|
1022
|
+
if (seen.has(key)) continue;
|
|
1023
|
+
seen.add(key);
|
|
1024
|
+
docItems.push(d);
|
|
1025
|
+
}
|
|
1026
|
+
const docLinks = docItems.filter(d => d.href).length;
|
|
1027
|
+
this._debugLog('DOM list stats', {
|
|
1028
|
+
liCount,
|
|
1029
|
+
ulCount,
|
|
1030
|
+
maxUlSize,
|
|
1031
|
+
docLinks,
|
|
1032
|
+
docItems: docItems.length,
|
|
1033
|
+
scriptDocs: scriptDocs.length,
|
|
1034
|
+
anchorDocs: anchorDocs.length,
|
|
1035
|
+
sampleLi,
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
// Provide the agent an explicit, structured list of resources to avoid stopping early.
|
|
1039
|
+
if (this.resourceGuard.enabled && docItems.length) {
|
|
1040
|
+
const summary = [
|
|
1041
|
+
`Testronaut document list (${docItems.length} items):`,
|
|
1042
|
+
...docItems.map(d => `- [${d.id || 'n/a'}] ${d.title || ''} ${d.href || ''}`.trim()),
|
|
1043
|
+
].join('\n');
|
|
1044
|
+
$('body').prepend(`<pre data-testronaut-doc-list="true">${summary}</pre>`);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
877
1048
|
// (Optional: If you'd like to use `focus`, you could filter here by selectors/keywords.)
|
|
878
1049
|
|
|
879
1050
|
// --- 5) Return trimmed HTML ---
|
|
@@ -1060,7 +1231,6 @@ export class ChromeBrowser {
|
|
|
1060
1231
|
return JSON.stringify(payload);
|
|
1061
1232
|
}
|
|
1062
1233
|
|
|
1063
|
-
|
|
1064
1234
|
async download_file({ selector, url, fileName, timeoutMs = 30000 }) {
|
|
1065
1235
|
ensureDir(FILES_DIR);
|
|
1066
1236
|
|
|
@@ -1139,6 +1309,30 @@ export class ChromeBrowser {
|
|
|
1139
1309
|
return JSON.stringify(payload);
|
|
1140
1310
|
}
|
|
1141
1311
|
|
|
1312
|
+
async list_local_files({ dir = 'missions/files', extensions = [] } = {}) {
|
|
1313
|
+
const resolvedDir = path.resolve(process.cwd(), dir);
|
|
1314
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
1315
|
+
throw new Error(`list_local_files: directory does not exist: ${resolvedDir}`);
|
|
1316
|
+
}
|
|
1317
|
+
const exts = Array.isArray(extensions)
|
|
1318
|
+
? extensions.map(e => e.toLowerCase())
|
|
1319
|
+
: [];
|
|
1320
|
+
const entries = fs.readdirSync(resolvedDir, { withFileTypes: true });
|
|
1321
|
+
const files = entries
|
|
1322
|
+
.filter(d => d.isFile())
|
|
1323
|
+
.map(d => d.name)
|
|
1324
|
+
.filter(name => {
|
|
1325
|
+
if (!exts.length) return true;
|
|
1326
|
+
const lower = name.toLowerCase();
|
|
1327
|
+
return exts.some(ext => lower.endsWith(ext));
|
|
1328
|
+
});
|
|
1329
|
+
return {
|
|
1330
|
+
dir: resolvedDir,
|
|
1331
|
+
count: files.length,
|
|
1332
|
+
files,
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1142
1336
|
|
|
1143
1337
|
async close() {
|
|
1144
1338
|
await this.browser?.close();
|
|
@@ -1166,4 +1360,17 @@ export const CHROME_TOOL_MAP = {
|
|
|
1166
1360
|
close_current_page: (b, args) => b.close_current_page(args),
|
|
1167
1361
|
upload_file: (b, args) => b.upload_file(args),
|
|
1168
1362
|
download_file: (b, args) => b.download_file(args),
|
|
1169
|
-
|
|
1363
|
+
list_local_files: (b, args) => b.list_local_files(args),
|
|
1364
|
+
resource_progress: (b, args, agentMemory) => {
|
|
1365
|
+
const prog = agentMemory?.docProgress;
|
|
1366
|
+
if (!prog) return JSON.stringify({ enabled: false, total: 0, downloaded: 0, remaining: [] });
|
|
1367
|
+
const remaining = prog.items.filter(i => !prog.downloaded.has(i.id));
|
|
1368
|
+
return JSON.stringify({
|
|
1369
|
+
enabled: true,
|
|
1370
|
+
total: prog.items.length || prog.lastScriptCount || 0,
|
|
1371
|
+
downloaded: prog.downloaded.size || 0,
|
|
1372
|
+
remaining: remaining.map(r => ({ id: r.id, title: r.title, href: r.href })).slice(0, 20),
|
|
1373
|
+
patterns: prog.patterns,
|
|
1374
|
+
});
|
|
1375
|
+
},
|
|
1376
|
+
};
|
package/tools/domControl.js
CHANGED
|
@@ -37,7 +37,9 @@ export const removeNonContentTags = async ($) => {
|
|
|
37
37
|
const $el = $(el);
|
|
38
38
|
const allowedAttrs = [
|
|
39
39
|
'href', 'src', 'type', 'alt', 'title',
|
|
40
|
-
'placeholder', 'name', 'id', 'class', 'aria-label', 'value'
|
|
40
|
+
'placeholder', 'name', 'id', 'class', 'aria-label', 'value',
|
|
41
|
+
// keep key data-* attributes that help identify rows/items without bloating too much
|
|
42
|
+
'data-type', 'data-id', 'data-title', 'data-parentid', 'data-order'
|
|
41
43
|
];
|
|
42
44
|
|
|
43
45
|
const attrs = el.attribs;
|
|
@@ -155,6 +157,11 @@ export const chunkBySection = async ($, focus) => {
|
|
|
155
157
|
* @returns {$} same Cheerio instance for chaining
|
|
156
158
|
*/
|
|
157
159
|
export function reduceRepeatedDivs($, maxAllowed = 3) {
|
|
160
|
+
let limit = maxAllowed === Infinity ? Infinity : Number(maxAllowed);
|
|
161
|
+
if (limit !== Infinity) {
|
|
162
|
+
limit = Number.isFinite(limit) ? Math.max(0, limit) : 3;
|
|
163
|
+
}
|
|
164
|
+
|
|
158
165
|
$('body, div').each((_, parentEl) => {
|
|
159
166
|
const $parent = $(parentEl);
|
|
160
167
|
const children = $parent.children('div');
|
|
@@ -175,9 +182,9 @@ export function reduceRepeatedDivs($, maxAllowed = 3) {
|
|
|
175
182
|
// Reduce each group to maxAllowed
|
|
176
183
|
for (const className in classGroups) {
|
|
177
184
|
const group = classGroups[className];
|
|
178
|
-
if (group.length >
|
|
185
|
+
if (limit !== Infinity && group.length > limit) {
|
|
179
186
|
// Remove excess nodes from the DOM
|
|
180
|
-
group.slice(
|
|
187
|
+
group.slice(limit).forEach(el => $(el).remove());
|
|
181
188
|
$parent.append(`<div data-collapsed="true" data-class="${className}">[...truncated]</div>`);
|
|
182
189
|
}
|
|
183
190
|
}
|
|
@@ -1,7 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* generateHtmlReport.js
|
|
3
|
+
* ---------------------
|
|
4
|
+
* Purpose:
|
|
5
|
+
* Render a Testronaut run summary into a single, self-contained HTML file.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Escape and format mission/step data into a readable report.
|
|
9
|
+
* - Group submissions by mission and surface per-step metadata (tokens, retries).
|
|
10
|
+
* - Write the HTML to disk at the provided output path (or a default location).
|
|
11
|
+
*
|
|
12
|
+
* Related tests:
|
|
13
|
+
* tests/toolsTests/generateHtmlReport.test.js
|
|
14
|
+
*
|
|
15
|
+
* Used by:
|
|
16
|
+
* - CLI report generation and upload flows.
|
|
17
|
+
*/
|
|
2
18
|
import fs from 'fs';
|
|
3
19
|
import path from 'path';
|
|
4
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Render and write a Testronaut run report to disk.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} report - normalized run JSON (runId, missions, summary, llm, etc.)
|
|
25
|
+
* @param {string} [outputPath] - optional absolute/relative path for the HTML file
|
|
26
|
+
* @returns {string} absolute path to the written HTML file
|
|
27
|
+
*/
|
|
5
28
|
export function generateHtmlReport(report, outputPath) {
|
|
6
29
|
const { runId, startTime, endTime, missions = [], summary = {}, llm = {} } = report;
|
|
7
30
|
const durationSec =
|
|
@@ -9,7 +32,7 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
9
32
|
? ((new Date(endTime) - new Date(startTime)) / 1000).toFixed(2)
|
|
10
33
|
: '—';
|
|
11
34
|
|
|
12
|
-
// helpers
|
|
35
|
+
// Escaping helpers for text and HTML attributes
|
|
13
36
|
const esc = (s) => String(s ?? '')
|
|
14
37
|
.replace(/&/g, '&').replace(/</g, '<')
|
|
15
38
|
.replace(/>/g, '>');
|
|
@@ -28,6 +51,17 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
28
51
|
const stepItems = steps.map((step, idx) => {
|
|
29
52
|
const events = Array.isArray(step.events) ? step.events : [];
|
|
30
53
|
const ok = /✅|Passed|Mission Success/i.test(step.result || '');
|
|
54
|
+
const resultRaw = step.result || '—';
|
|
55
|
+
const resultTooltip = resultRaw.includes('⚠️ Turn Issues')
|
|
56
|
+
? 'Turn had tool/action issues (e.g., selector/timeouts). It may not indicate a product bug.'
|
|
57
|
+
: '';
|
|
58
|
+
const retryAttempt = step.retryAttempt || 1; // includes initial
|
|
59
|
+
const retryLimit = step.retryLimit; // number of retries allowed (excludes initial)
|
|
60
|
+
const retryNumber = retryAttempt - 1;
|
|
61
|
+
const retryTotal = Number.isFinite(retryLimit) ? retryLimit : Math.max(retryNumber, 0);
|
|
62
|
+
const attemptLabel = retryNumber > 0
|
|
63
|
+
? ` (re-attempt ${retryNumber}/${retryTotal || retryNumber})`
|
|
64
|
+
: '';
|
|
31
65
|
const imgTag = step.screenshotPath
|
|
32
66
|
? `<img src="${esc(step.screenshotPath)}" alt="screenshot turn ${esc(step.turn ?? idx)}">`
|
|
33
67
|
: '';
|
|
@@ -42,9 +76,9 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
42
76
|
return `
|
|
43
77
|
<details class="step" ${ok ? '' : 'open'}>
|
|
44
78
|
<summary>
|
|
45
|
-
<span class="turn">Turn ${esc((step.turn ?? idx) + 1)}</span>
|
|
79
|
+
<span class="turn">Turn ${esc((step.turn ?? idx) + 1)}${esc(attemptLabel)}</span>
|
|
46
80
|
${planSpan}
|
|
47
|
-
<span class="step-result ${ok ? 'ok' : 'bad'}"
|
|
81
|
+
<span class="step-result ${ok ? 'ok' : 'bad'}" ${resultTooltip ? `title="${escAttr(resultTooltip)}"` : ''}>${esc(resultRaw)}</span>
|
|
48
82
|
<span class="tokens">tokens: ${esc(step.tokensUsed ?? '—')} / total: ${esc(step.totalTokensUsed ?? '—')}</span>
|
|
49
83
|
</summary>
|
|
50
84
|
<pre class="events">${esc(events.join('\n')) || '(no events)'}</pre>
|
package/tools/toolSchema.js
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toolsSchema.js
|
|
3
|
+
* --------------
|
|
4
|
+
* Canonical list of tool/function definitions exposed to the LLM.
|
|
5
|
+
* Keep descriptions concise but clear; they are surfaced directly in the
|
|
6
|
+
* function/tool-calling interface for the model.
|
|
7
|
+
*
|
|
8
|
+
* Notes:
|
|
9
|
+
* - When adding a new tool, include a short description and minimally
|
|
10
|
+
* strict parameters schema to avoid over-constraining the model.
|
|
11
|
+
* - Resource helper `resource_progress` is a lightweight introspection tool
|
|
12
|
+
* used to report remaining detected resources (documents/files/rows).
|
|
13
|
+
*/
|
|
1
14
|
const toolsSchema = [
|
|
2
15
|
{
|
|
3
16
|
type: 'function',
|
|
@@ -286,6 +299,35 @@ const toolsSchema = [
|
|
|
286
299
|
}
|
|
287
300
|
}
|
|
288
301
|
},
|
|
302
|
+
{
|
|
303
|
+
type: 'function',
|
|
304
|
+
function: {
|
|
305
|
+
name: 'list_local_files',
|
|
306
|
+
description: 'List files from a local directory (defaults to missions/files). Use to discover downloaded PDFs before uploading.',
|
|
307
|
+
parameters: {
|
|
308
|
+
type: 'object',
|
|
309
|
+
properties: {
|
|
310
|
+
dir: { type: 'string', description: 'Directory relative to project root (default: missions/files)' },
|
|
311
|
+
extensions: {
|
|
312
|
+
type: 'array',
|
|
313
|
+
items: { type: 'string' },
|
|
314
|
+
description: 'Optional list of extensions to include (e.g., [".pdf"])'
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
type: 'function',
|
|
322
|
+
function: {
|
|
323
|
+
name: 'resource_progress',
|
|
324
|
+
description: 'Report remaining items from the detected resource list (documents/files/rows) to ensure full coverage.',
|
|
325
|
+
parameters: {
|
|
326
|
+
type: 'object',
|
|
327
|
+
properties: {},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
},
|
|
289
331
|
{
|
|
290
332
|
type: 'function',
|
|
291
333
|
function: {
|
|
@@ -389,4 +431,4 @@ const toolsSchema = [
|
|
|
389
431
|
}
|
|
390
432
|
];
|
|
391
433
|
|
|
392
|
-
export default toolsSchema;
|
|
434
|
+
export default toolsSchema;
|