@bbradar/mcp 0.1.4 → 0.1.5
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/server.js +581 -80
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -31,17 +31,51 @@ const MAX_LOCAL_EXPORT_RESOURCES = 25;
|
|
|
31
31
|
const EXPORT_PREVIEW_LIMIT = 25;
|
|
32
32
|
const STALE_PROGRAM_ID_RESOLVE_PAGES = 5;
|
|
33
33
|
const STALE_PROGRAM_ID_RESOLVE_BUDGET = 5;
|
|
34
|
+
const MAX_RESOLVE_SEARCH_QUERIES = 4;
|
|
34
35
|
const SDK_VERSION = "1.29.0";
|
|
35
36
|
const WEB3_TAGS = ["web3", "crypto", "blockchain", "smart-contract", "smart contract", "defi", "nft", "ethereum", "solana", "solidity"];
|
|
36
37
|
const WEB3_CONTEST_SIGNALS = ["contest", "audit", "competitive", "code4rena", "sherlock", "cantina", "codehawks", "hats", "spearbit", "warden"];
|
|
37
38
|
const VDP_SIGNALS = ["vdp", "disclosure", "responsible disclosure", "vulnerability disclosure", "no bounty", "non-bounty", "no reward"];
|
|
39
|
+
const PROGRAM_RESOLUTION_STOP_WORDS = new Set([
|
|
40
|
+
"program",
|
|
41
|
+
"programs",
|
|
42
|
+
"target",
|
|
43
|
+
"targets",
|
|
44
|
+
"scope",
|
|
45
|
+
"scopes",
|
|
46
|
+
"new",
|
|
47
|
+
"latest",
|
|
48
|
+
"recent",
|
|
49
|
+
"recently",
|
|
50
|
+
"added",
|
|
51
|
+
"additions",
|
|
52
|
+
"for",
|
|
53
|
+
"from",
|
|
54
|
+
"the",
|
|
55
|
+
"a",
|
|
56
|
+
"an",
|
|
57
|
+
"of",
|
|
58
|
+
"on",
|
|
59
|
+
"in",
|
|
60
|
+
"with",
|
|
61
|
+
"to",
|
|
62
|
+
"get",
|
|
63
|
+
"find",
|
|
64
|
+
"show",
|
|
65
|
+
"list",
|
|
66
|
+
"check",
|
|
67
|
+
"does",
|
|
68
|
+
"has",
|
|
69
|
+
"have",
|
|
70
|
+
"any"
|
|
71
|
+
]);
|
|
38
72
|
const targetListModeSchema = z.enum(["identifiers", "compact", "full"]);
|
|
39
73
|
const changeListModeSchema = z.enum(["compact", "full"]);
|
|
40
74
|
const programListModeSchema = z.enum(["compact", "full"]);
|
|
41
75
|
const rewardThresholdModeSchema = z.enum(["max_at_least", "min_at_least"]);
|
|
42
76
|
const vdpModeSchema = z.enum(["include", "exclude", "only_likely", "only_known_no_bounty"]);
|
|
43
77
|
const targetMatchModeSchema = z.enum(["contains", "exact", "suffix", "wildcard"]);
|
|
44
|
-
const programNameActionSchema = z.enum(["new_targets", "scope_delta", "brief", "target_breakdown"]);
|
|
78
|
+
const programNameActionSchema = z.enum(["new_targets", "targets", "scope_delta", "brief", "target_breakdown"]);
|
|
45
79
|
const readOnlyAnnotations = {
|
|
46
80
|
readOnlyHint: true,
|
|
47
81
|
destructiveHint: false,
|
|
@@ -192,7 +226,8 @@ export function createBbradarServer(client, config) {
|
|
|
192
226
|
instructions: [
|
|
193
227
|
"Use BBRadar only for passive bug bounty intelligence; never scan or contact targets.",
|
|
194
228
|
"For BBRadar asks, use these tools first and avoid external skills unless methodology is explicitly requested.",
|
|
195
|
-
"
|
|
229
|
+
"Partial program name + action: run_program_name_action. Partial program name only: resolve_program.",
|
|
230
|
+
"Default to bounty-eligible in-scope data. Include out-of-scope, ineligible, VDP, disclosure-only, or no-bounty data only when the user explicitly asks.",
|
|
196
231
|
"Freshness: get_latest_added_targets, check_program_new_targets, check_watchlist_new_targets, find_recent_by_type, get_program_scope_delta.",
|
|
197
232
|
"Discovery: find_program_candidates, find_stack_matches, find_language_programs, find_target_type_programs, find_reward_programs, find_paid_programs, find_vdp_programs, find_web3_contests, find_low_noise_programs.",
|
|
198
233
|
"Details: get_program_brief, get_program_scope_summary, get_program_target_breakdown, find_programs_by_target, compare_programs_compact.",
|
|
@@ -208,15 +243,20 @@ export function createBbradarServer(client, config) {
|
|
|
208
243
|
platforms: stringListSchema,
|
|
209
244
|
tags: stringListSchema,
|
|
210
245
|
updated_since: isoDateTimeSchema.optional().describe("ISO datetime."),
|
|
246
|
+
vdp_mode: vdpModeSchema.default("exclude").describe("VDP/no-bounty filter mode."),
|
|
211
247
|
page: pageSchema,
|
|
212
248
|
page_size: programsPageSizeSchema
|
|
213
249
|
},
|
|
214
250
|
outputSchema: programListOutputShape,
|
|
215
251
|
annotations: readOnlyAnnotations
|
|
216
252
|
}, (args) => runTool("search_programs", config, rateLimiter, async () => {
|
|
217
|
-
const
|
|
253
|
+
const { vdp_mode, ...filters } = args;
|
|
254
|
+
const api = await client.listPrograms(toQuery(filters));
|
|
218
255
|
const data = readObject(api.data);
|
|
219
|
-
const programs = readArray(data?.programs)
|
|
256
|
+
const programs = readArray(data?.programs)
|
|
257
|
+
.map((program) => toProgramCandidate(program, config.webBaseUrl))
|
|
258
|
+
.filter((candidate) => candidateMatchesVdpMode(candidate, vdp_mode))
|
|
259
|
+
.map((candidate) => candidate.program);
|
|
220
260
|
return withApiMetadata(api, {
|
|
221
261
|
programs,
|
|
222
262
|
meta: sanitizeJson(data?.meta)
|
|
@@ -230,7 +270,8 @@ export function createBbradarServer(client, config) {
|
|
|
230
270
|
},
|
|
231
271
|
outputSchema: {
|
|
232
272
|
...apiEnvelopeOutputShape,
|
|
233
|
-
program: programOutputSchema.optional()
|
|
273
|
+
program: programOutputSchema.optional(),
|
|
274
|
+
meta: jsonRecordSchema.optional()
|
|
234
275
|
},
|
|
235
276
|
annotations: readOnlyAnnotations
|
|
236
277
|
}, (args) => runTool("get_program", config, rateLimiter, async () => {
|
|
@@ -239,11 +280,19 @@ export function createBbradarServer(client, config) {
|
|
|
239
280
|
return withApiMetadata(api, {
|
|
240
281
|
program: addProgramResourceLinks(sanitizeProgram(api.data, config.webBaseUrl))
|
|
241
282
|
});
|
|
242
|
-
})
|
|
283
|
+
}, async (indexFallback) => ({
|
|
284
|
+
request_id: randomUUID(),
|
|
285
|
+
warnings: [`Returned program metadata from the BBRadar program index because the per-program detail endpoint returned 404 for ${indexFallback.resolvedProgramId}.`],
|
|
286
|
+
program: indexFallback.program,
|
|
287
|
+
meta: {
|
|
288
|
+
detail_source: "program_index",
|
|
289
|
+
detail_endpoint_unavailable: true
|
|
290
|
+
}
|
|
291
|
+
}));
|
|
243
292
|
}));
|
|
244
293
|
server.registerTool("resolve_program", {
|
|
245
294
|
title: "Resolve Program",
|
|
246
|
-
description: "Resolve name/handle text to likely program_ids.",
|
|
295
|
+
description: "Resolve partial name/handle text to likely program_ids. Use this when the user gives a program name fragment like Nado.",
|
|
247
296
|
inputSchema: {
|
|
248
297
|
query: searchTextSchema,
|
|
249
298
|
platforms: stringListSchema,
|
|
@@ -267,7 +316,7 @@ export function createBbradarServer(client, config) {
|
|
|
267
316
|
}, (args) => runTool("resolve_program", config, rateLimiter, async () => resolveProgram(client, config, args)));
|
|
268
317
|
server.registerTool("run_program_name_action", {
|
|
269
318
|
title: "Run Program Name Action",
|
|
270
|
-
description: "Resolve a name, then run new_targets, scope_delta, brief, or target_breakdown.",
|
|
319
|
+
description: "Resolve a partial program name, then run targets, new_targets, scope_delta, brief, or target_breakdown.",
|
|
271
320
|
inputSchema: {
|
|
272
321
|
query: searchTextSchema,
|
|
273
322
|
action: programNameActionSchema.default("new_targets"),
|
|
@@ -319,26 +368,7 @@ export function createBbradarServer(client, config) {
|
|
|
319
368
|
},
|
|
320
369
|
annotations: readOnlyAnnotations
|
|
321
370
|
}, (args) => runTool("get_program_targets", config, rateLimiter, async () => {
|
|
322
|
-
return runProgramIdTool(client, config, args.program_id, async (programId) =>
|
|
323
|
-
const api = await client.getProgramTargets(programId);
|
|
324
|
-
const data = readObject(api.data);
|
|
325
|
-
const rawTargets = readArray(data?.targets);
|
|
326
|
-
const sanitizedTargets = rawTargets.map(sanitizeTarget).filter((target) => targetHasAllowedScope(target, args));
|
|
327
|
-
const targets = sanitizedTargets.slice(args.offset, args.offset + args.limit);
|
|
328
|
-
return withApiMetadata(api, {
|
|
329
|
-
program_id: programId,
|
|
330
|
-
targets: formatTargetList(targets, args.output_mode),
|
|
331
|
-
meta: {
|
|
332
|
-
offset: args.offset,
|
|
333
|
-
limit: args.limit,
|
|
334
|
-
output_mode: args.output_mode,
|
|
335
|
-
total_active_targets: rawTargets.length,
|
|
336
|
-
total_after_filters: sanitizedTargets.length,
|
|
337
|
-
returned: targets.length,
|
|
338
|
-
has_more: args.offset + args.limit < sanitizedTargets.length
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
});
|
|
371
|
+
return runProgramIdTool(client, config, args.program_id, async (programId) => getProgramTargetsPayload(client, args, programId), async (indexFallback) => getProgramTargetsExportFallbackPayload(client, args, indexFallback.resolvedProgramId));
|
|
342
372
|
}));
|
|
343
373
|
server.registerTool("get_recent_changes", {
|
|
344
374
|
title: "Get Recent Target Changes",
|
|
@@ -390,8 +420,8 @@ export function createBbradarServer(client, config) {
|
|
|
390
420
|
include_full_target_list: z.boolean().default(true),
|
|
391
421
|
full_target_list_mode: targetListModeSchema.default("identifiers"),
|
|
392
422
|
full_target_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
|
|
393
|
-
full_list_include_out_of_scope: z.boolean().default(
|
|
394
|
-
full_list_include_ineligible: z.boolean().default(
|
|
423
|
+
full_list_include_out_of_scope: z.boolean().default(false),
|
|
424
|
+
full_list_include_ineligible: z.boolean().default(false)
|
|
395
425
|
},
|
|
396
426
|
outputSchema: {
|
|
397
427
|
...apiEnvelopeOutputShape,
|
|
@@ -412,8 +442,8 @@ export function createBbradarServer(client, config) {
|
|
|
412
442
|
program_id: programIdSchema,
|
|
413
443
|
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
414
444
|
group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
|
|
415
|
-
include_out_of_scope: z.boolean().default(
|
|
416
|
-
include_ineligible: z.boolean().default(
|
|
445
|
+
include_out_of_scope: z.boolean().default(false),
|
|
446
|
+
include_ineligible: z.boolean().default(false)
|
|
417
447
|
},
|
|
418
448
|
outputSchema: {
|
|
419
449
|
...apiEnvelopeOutputShape,
|
|
@@ -423,7 +453,7 @@ export function createBbradarServer(client, config) {
|
|
|
423
453
|
meta: jsonRecordSchema.optional()
|
|
424
454
|
},
|
|
425
455
|
annotations: readOnlyAnnotations
|
|
426
|
-
}, (args) => runTool("get_program_scope_summary", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeSummary(client, config, { ...args, program_id: programId }))));
|
|
456
|
+
}, (args) => runTool("get_program_scope_summary", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeSummary(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramScopeSummaryFromIndexFallback(client, indexFallback, { ...args, program_id: indexFallback.resolvedProgramId }))));
|
|
427
457
|
server.registerTool("get_program_target_breakdown", {
|
|
428
458
|
title: "Get Program Target Breakdown",
|
|
429
459
|
description: "Target type, scope tag, language tag, and bucket counts for one program.",
|
|
@@ -431,8 +461,8 @@ export function createBbradarServer(client, config) {
|
|
|
431
461
|
program_id: programIdSchema,
|
|
432
462
|
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
433
463
|
group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(100),
|
|
434
|
-
include_out_of_scope: z.boolean().default(
|
|
435
|
-
include_ineligible: z.boolean().default(
|
|
464
|
+
include_out_of_scope: z.boolean().default(false),
|
|
465
|
+
include_ineligible: z.boolean().default(false)
|
|
436
466
|
},
|
|
437
467
|
outputSchema: {
|
|
438
468
|
...apiEnvelopeOutputShape,
|
|
@@ -442,7 +472,7 @@ export function createBbradarServer(client, config) {
|
|
|
442
472
|
meta: jsonRecordSchema.optional()
|
|
443
473
|
},
|
|
444
474
|
annotations: readOnlyAnnotations
|
|
445
|
-
}, (args) => runTool("get_program_target_breakdown", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramTargetBreakdown(client, config, { ...args, program_id: programId }))));
|
|
475
|
+
}, (args) => runTool("get_program_target_breakdown", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramTargetBreakdown(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramTargetBreakdownFromIndexFallback(client, indexFallback, { ...args, program_id: indexFallback.resolvedProgramId }))));
|
|
446
476
|
server.registerTool("get_program_scope_delta", {
|
|
447
477
|
title: "Get Program Scope Delta",
|
|
448
478
|
description: "Compact recent scope changes for one program.",
|
|
@@ -519,7 +549,7 @@ export function createBbradarServer(client, config) {
|
|
|
519
549
|
meta: jsonRecordSchema.optional()
|
|
520
550
|
},
|
|
521
551
|
annotations: readOnlyAnnotations
|
|
522
|
-
}, (args) => runTool("check_program_new_targets", config, rateLimiter, async () => checkProgramNewTargets(client, config, args)));
|
|
552
|
+
}, (args) => runTool("check_program_new_targets", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => checkProgramNewTargets(client, config, { ...args, program_id: programId }), (indexFallback) => checkProgramNewTargetsFromIndexedProgram(client, config, { ...args, program_id: indexFallback.resolvedProgramId }, indexFallback.program))));
|
|
523
553
|
server.registerTool("check_watchlist_new_targets", {
|
|
524
554
|
title: "Check Watchlist New Targets",
|
|
525
555
|
description: "New in-scope targets across many known program_ids.",
|
|
@@ -549,6 +579,7 @@ export function createBbradarServer(client, config) {
|
|
|
549
579
|
platforms: stringListSchema,
|
|
550
580
|
tags: stringListSchema,
|
|
551
581
|
updated_since: isoDateTimeSchema.optional(),
|
|
582
|
+
vdp_mode: vdpModeSchema.default("exclude").describe("VDP/no-bounty filter mode."),
|
|
552
583
|
page: pageSchema,
|
|
553
584
|
page_size: programsPageSizeSchema
|
|
554
585
|
},
|
|
@@ -558,10 +589,13 @@ export function createBbradarServer(client, config) {
|
|
|
558
589
|
},
|
|
559
590
|
annotations: readOnlyAnnotations
|
|
560
591
|
}, (args) => runTool("get_opportunities", config, rateLimiter, async () => {
|
|
561
|
-
const { level, ...filters } = args;
|
|
592
|
+
const { level, vdp_mode, ...filters } = args;
|
|
562
593
|
const api = await client.getOpportunities(level, toQuery(filters));
|
|
563
594
|
const data = readObject(api.data);
|
|
564
|
-
const programs = readArray(data?.programs)
|
|
595
|
+
const programs = readArray(data?.programs)
|
|
596
|
+
.map((program) => toProgramCandidate(program, config.webBaseUrl))
|
|
597
|
+
.filter((candidate) => candidateMatchesVdpMode(candidate, vdp_mode))
|
|
598
|
+
.map((candidate) => candidate.program);
|
|
565
599
|
return withApiMetadata(api, {
|
|
566
600
|
level,
|
|
567
601
|
programs,
|
|
@@ -592,7 +626,7 @@ export function createBbradarServer(client, config) {
|
|
|
592
626
|
exclude_non_web: z.boolean().default(false),
|
|
593
627
|
has_api_scope: z.boolean().default(false),
|
|
594
628
|
contest_like: z.boolean().default(false).describe("Require contest/audit signals."),
|
|
595
|
-
vdp_mode: vdpModeSchema.default("
|
|
629
|
+
vdp_mode: vdpModeSchema.default("exclude").describe("VDP/no-bounty filter mode."),
|
|
596
630
|
fresh_launch_days: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).optional(),
|
|
597
631
|
min_wildcards: z.number().int().min(0).max(1000).default(0),
|
|
598
632
|
min_eligible_targets: z.number().int().min(0).optional(),
|
|
@@ -800,7 +834,7 @@ export function createBbradarServer(client, config) {
|
|
|
800
834
|
min_reward: z.number().int().min(0).optional(),
|
|
801
835
|
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
802
836
|
require_bounty: z.boolean().default(false),
|
|
803
|
-
exclude_vdp: z.boolean().default(
|
|
837
|
+
exclude_vdp: z.boolean().default(true),
|
|
804
838
|
max_public_report_count: z.number().int().min(0).optional(),
|
|
805
839
|
require_known_report_count: z.boolean().default(false),
|
|
806
840
|
include_unknown_report_count: z.boolean().default(true),
|
|
@@ -830,7 +864,7 @@ export function createBbradarServer(client, config) {
|
|
|
830
864
|
require_bounty: z.boolean().default(false),
|
|
831
865
|
fresh_only: z.boolean().default(false),
|
|
832
866
|
min_added_7d: z.number().int().min(0).optional(),
|
|
833
|
-
exclude_vdp: z.boolean().default(
|
|
867
|
+
exclude_vdp: z.boolean().default(true),
|
|
834
868
|
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
835
869
|
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
836
870
|
include_target_samples: z.boolean().default(false),
|
|
@@ -899,7 +933,7 @@ export function createBbradarServer(client, config) {
|
|
|
899
933
|
web3: z.boolean().default(false),
|
|
900
934
|
contest_like: z.boolean().default(false),
|
|
901
935
|
require_bounty: z.boolean().default(false),
|
|
902
|
-
exclude_vdp: z.boolean().default(
|
|
936
|
+
exclude_vdp: z.boolean().default(true),
|
|
903
937
|
max_public_report_count: z.number().int().min(0).optional(),
|
|
904
938
|
min_eligible_targets: z.number().int().min(0).optional(),
|
|
905
939
|
fresh_only: z.boolean().default(false),
|
|
@@ -978,8 +1012,8 @@ export function createBbradarServer(client, config) {
|
|
|
978
1012
|
use_api_search: z.boolean().default(true),
|
|
979
1013
|
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(2),
|
|
980
1014
|
max_results: z.number().int().min(1).max(25).default(10),
|
|
981
|
-
include_out_of_scope: z.boolean().default(
|
|
982
|
-
include_ineligible: z.boolean().default(
|
|
1015
|
+
include_out_of_scope: z.boolean().default(false),
|
|
1016
|
+
include_ineligible: z.boolean().default(false),
|
|
983
1017
|
max_targets_per_program: z.number().int().min(1).max(50).default(10),
|
|
984
1018
|
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
985
1019
|
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
|
|
@@ -1472,7 +1506,7 @@ async function getProgramWithIdFallback(client, config, requestedProgramId) {
|
|
|
1472
1506
|
};
|
|
1473
1507
|
}
|
|
1474
1508
|
}
|
|
1475
|
-
async function runProgramIdTool(client, config, requestedProgramId, callback) {
|
|
1509
|
+
async function runProgramIdTool(client, config, requestedProgramId, callback, indexFallbackCallback) {
|
|
1476
1510
|
try {
|
|
1477
1511
|
return await callback(requestedProgramId);
|
|
1478
1512
|
}
|
|
@@ -1482,12 +1516,55 @@ async function runProgramIdTool(client, config, requestedProgramId, callback) {
|
|
|
1482
1516
|
}
|
|
1483
1517
|
const fallback = await resolveStaleProgramId(client, config, requestedProgramId, error);
|
|
1484
1518
|
if (!fallback) {
|
|
1485
|
-
|
|
1519
|
+
if (!indexFallbackCallback) {
|
|
1520
|
+
throw error;
|
|
1521
|
+
}
|
|
1522
|
+
const indexFallback = await resolveIndexedProgramId(client, config, requestedProgramId, error);
|
|
1523
|
+
if (!indexFallback) {
|
|
1524
|
+
throw error;
|
|
1525
|
+
}
|
|
1526
|
+
const payload = await indexFallbackCallback(indexFallback);
|
|
1527
|
+
return withProgramIndexFallbackMetadata(payload, indexFallback);
|
|
1486
1528
|
}
|
|
1487
1529
|
const payload = await callback(fallback.resolvedProgramId);
|
|
1488
1530
|
return withProgramIdFallbackMetadata(payload, fallback);
|
|
1489
1531
|
}
|
|
1490
1532
|
}
|
|
1533
|
+
async function resolveIndexedProgramId(client, config, requestedProgramId, staleError) {
|
|
1534
|
+
const query = programSearchText(requestedProgramId);
|
|
1535
|
+
if (query.length < 2) {
|
|
1536
|
+
return undefined;
|
|
1537
|
+
}
|
|
1538
|
+
const resolution = await resolveProgram(client, config, {
|
|
1539
|
+
query,
|
|
1540
|
+
platforms: [],
|
|
1541
|
+
tags: [],
|
|
1542
|
+
opportunity_levels: [],
|
|
1543
|
+
max_pages: STALE_PROGRAM_ID_RESOLVE_PAGES,
|
|
1544
|
+
max_results: 5,
|
|
1545
|
+
output_mode: "full",
|
|
1546
|
+
upstream_request_budget: STALE_PROGRAM_ID_RESOLVE_BUDGET
|
|
1547
|
+
});
|
|
1548
|
+
const match = selectProgramIndexFallbackMatch(requestedProgramId, resolution);
|
|
1549
|
+
if (!match) {
|
|
1550
|
+
return undefined;
|
|
1551
|
+
}
|
|
1552
|
+
const sourceRequests = readArray(resolution.source_requests).filter((request) => readObject(request) !== undefined);
|
|
1553
|
+
const warnings = readArray(resolution.warnings).filter((warning) => typeof warning === "string");
|
|
1554
|
+
const staleApiError = staleError instanceof BBRadarApiError ? staleError : undefined;
|
|
1555
|
+
const indexWarning = `program_id ${requestedProgramId} exists in the BBRadar program index, but the per-program detail endpoint returned 404.`;
|
|
1556
|
+
return {
|
|
1557
|
+
requestedProgramId,
|
|
1558
|
+
resolvedProgramId: match.resolvedProgramId,
|
|
1559
|
+
query,
|
|
1560
|
+
staleRequestId: staleApiError?.requestId,
|
|
1561
|
+
staleUpstreamRequestId: staleApiError?.upstreamRequestId,
|
|
1562
|
+
sourceRequests,
|
|
1563
|
+
warnings: uniqueStrings([...warnings, indexWarning]),
|
|
1564
|
+
resolution,
|
|
1565
|
+
program: match.program
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1491
1568
|
async function resolveStaleProgramId(client, config, requestedProgramId, staleError) {
|
|
1492
1569
|
const query = programSearchText(requestedProgramId);
|
|
1493
1570
|
if (query.length < 2) {
|
|
@@ -1556,6 +1633,44 @@ function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
|
|
|
1556
1633
|
resolvedProgramId: best.resolvedProgramId
|
|
1557
1634
|
};
|
|
1558
1635
|
}
|
|
1636
|
+
function selectProgramIndexFallbackMatch(requestedProgramId, resolution) {
|
|
1637
|
+
const requestedId = normalizeTag(requestedProgramId);
|
|
1638
|
+
const requestedHandle = normalizeTag(programHandleText(requestedProgramId));
|
|
1639
|
+
const requestedPlatform = normalizeTag(programPlatformText(requestedProgramId));
|
|
1640
|
+
const candidates = readArray(resolution.matches)
|
|
1641
|
+
.map((entry) => readObject(entry))
|
|
1642
|
+
.filter((entry) => entry !== undefined)
|
|
1643
|
+
.map((entry) => {
|
|
1644
|
+
const program = readObject(entry.program);
|
|
1645
|
+
const resolvedProgramId = stringField(entry, "program_id") ?? stringField(program, "id");
|
|
1646
|
+
const candidateHandle = normalizeTag(stringField(program, "handle") ?? (resolvedProgramId ? programHandleText(resolvedProgramId) : ""));
|
|
1647
|
+
const candidatePlatform = normalizeTag(stringField(program, "platform") ?? (resolvedProgramId ? programPlatformText(resolvedProgramId) : ""));
|
|
1648
|
+
const score = readNumber(entry.score) ?? 0;
|
|
1649
|
+
const idMatch = resolvedProgramId !== undefined && normalizeTag(resolvedProgramId) === requestedId;
|
|
1650
|
+
const platformHandleMatch = requestedPlatform.length > 0 &&
|
|
1651
|
+
candidatePlatform === requestedPlatform &&
|
|
1652
|
+
candidateHandle === requestedHandle;
|
|
1653
|
+
return {
|
|
1654
|
+
resolvedProgramId,
|
|
1655
|
+
program,
|
|
1656
|
+
score,
|
|
1657
|
+
exactMatch: idMatch || platformHandleMatch
|
|
1658
|
+
};
|
|
1659
|
+
})
|
|
1660
|
+
.filter((entry) => entry.resolvedProgramId !== undefined && entry.program !== undefined && entry.exactMatch && entry.score >= 80)
|
|
1661
|
+
.sort((left, right) => right.score - left.score || left.resolvedProgramId.localeCompare(right.resolvedProgramId));
|
|
1662
|
+
if (candidates.length === 0) {
|
|
1663
|
+
return undefined;
|
|
1664
|
+
}
|
|
1665
|
+
const [best, next] = candidates;
|
|
1666
|
+
if (!best || (next && best.score === next.score && normalizeTag(next.resolvedProgramId) !== normalizeTag(best.resolvedProgramId))) {
|
|
1667
|
+
return undefined;
|
|
1668
|
+
}
|
|
1669
|
+
return {
|
|
1670
|
+
resolvedProgramId: best.resolvedProgramId,
|
|
1671
|
+
program: addProgramResourceLinks(best.program)
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1559
1674
|
function withProgramIdFallbackMetadata(payload, fallback) {
|
|
1560
1675
|
const sourceRequests = readArray(payload.source_requests).filter((request) => readObject(request) !== undefined);
|
|
1561
1676
|
const warnings = readArray(payload.warnings).filter((warning) => typeof warning === "string");
|
|
@@ -1576,6 +1691,17 @@ function withProgramIdFallbackMetadata(payload, fallback) {
|
|
|
1576
1691
|
program_id_resolution: resolution
|
|
1577
1692
|
});
|
|
1578
1693
|
}
|
|
1694
|
+
function withProgramIndexFallbackMetadata(payload, fallback) {
|
|
1695
|
+
const enriched = withProgramIdFallbackMetadata(payload, fallback);
|
|
1696
|
+
const resolution = readObject(enriched.program_id_resolution);
|
|
1697
|
+
return {
|
|
1698
|
+
...enriched,
|
|
1699
|
+
program_id_resolution: stripUndefined({
|
|
1700
|
+
...(resolution ?? {}),
|
|
1701
|
+
reason: "program_index_detail_404"
|
|
1702
|
+
})
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1579
1705
|
async function resolveProgram(client, config, input) {
|
|
1580
1706
|
const warnings = [];
|
|
1581
1707
|
const budget = {
|
|
@@ -1583,10 +1709,11 @@ async function resolveProgram(client, config, input) {
|
|
|
1583
1709
|
remaining: input.upstream_request_budget
|
|
1584
1710
|
};
|
|
1585
1711
|
const maxPages = clampLimit("max_pages", input.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
|
|
1586
|
-
const
|
|
1712
|
+
const searchQueries = programResolutionSearchQueries(input.query);
|
|
1713
|
+
const collected = await collectProgramsForResolution(client, {
|
|
1714
|
+
searchQueries,
|
|
1587
1715
|
platforms: input.platforms,
|
|
1588
1716
|
tags: input.tags,
|
|
1589
|
-
updated_since: undefined,
|
|
1590
1717
|
opportunity_levels: input.opportunity_levels,
|
|
1591
1718
|
max_pages: maxPages,
|
|
1592
1719
|
budget,
|
|
@@ -1608,6 +1735,7 @@ async function resolveProgram(client, config, input) {
|
|
|
1608
1735
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
1609
1736
|
query: {
|
|
1610
1737
|
text: input.query,
|
|
1738
|
+
search_queries: searchQueries,
|
|
1611
1739
|
platforms: input.platforms.length > 0 ? input.platforms : undefined,
|
|
1612
1740
|
tags: input.tags.length > 0 ? input.tags : undefined
|
|
1613
1741
|
},
|
|
@@ -1715,6 +1843,31 @@ async function runResolvedProgramAction(client, config, programId, input) {
|
|
|
1715
1843
|
include_ineligible: input.include_ineligible
|
|
1716
1844
|
});
|
|
1717
1845
|
}
|
|
1846
|
+
if (input.action === "targets") {
|
|
1847
|
+
const api = await client.getProgramTargets(programId);
|
|
1848
|
+
const data = readObject(api.data);
|
|
1849
|
+
const rawTargets = readArray(data?.targets);
|
|
1850
|
+
const sanitizedTargets = rawTargets
|
|
1851
|
+
.map(sanitizeTarget)
|
|
1852
|
+
.filter((target) => targetHasAllowedScope(target, {
|
|
1853
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
1854
|
+
include_ineligible: input.include_ineligible,
|
|
1855
|
+
strict_scope_filter: true
|
|
1856
|
+
}));
|
|
1857
|
+
const targets = sanitizedTargets.slice(0, input.max_targets);
|
|
1858
|
+
return withApiMetadata(api, {
|
|
1859
|
+
program_id: programId,
|
|
1860
|
+
targets: formatTargetList(targets, input.target_list_mode),
|
|
1861
|
+
meta: {
|
|
1862
|
+
limit: input.max_targets,
|
|
1863
|
+
output_mode: input.target_list_mode,
|
|
1864
|
+
total_active_targets: rawTargets.length,
|
|
1865
|
+
total_after_filters: sanitizedTargets.length,
|
|
1866
|
+
returned: targets.length,
|
|
1867
|
+
has_more: sanitizedTargets.length > targets.length
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1718
1871
|
return checkProgramNewTargets(client, config, {
|
|
1719
1872
|
program_id: programId,
|
|
1720
1873
|
since: input.since,
|
|
@@ -2080,7 +2233,7 @@ function findLowNoisePrograms(client, config, input) {
|
|
|
2080
2233
|
exclude_mobile_only: false,
|
|
2081
2234
|
exclude_non_web: false,
|
|
2082
2235
|
has_api_scope: false,
|
|
2083
|
-
vdp_mode:
|
|
2236
|
+
vdp_mode: "exclude",
|
|
2084
2237
|
fresh_launch_days: undefined,
|
|
2085
2238
|
min_wildcards: 0,
|
|
2086
2239
|
min_eligible_targets: input.min_eligible_targets,
|
|
@@ -2616,11 +2769,132 @@ async function getLatestAddedTargets(client, config, input) {
|
|
|
2616
2769
|
})
|
|
2617
2770
|
});
|
|
2618
2771
|
}
|
|
2772
|
+
async function getProgramTargetsPayload(client, input, programId) {
|
|
2773
|
+
const api = await client.getProgramTargets(programId);
|
|
2774
|
+
const data = readObject(api.data);
|
|
2775
|
+
const rawTargets = readArray(data?.targets);
|
|
2776
|
+
const sanitizedTargets = rawTargets.map(sanitizeTarget).filter((target) => targetHasAllowedScope(target, input));
|
|
2777
|
+
const targets = sanitizedTargets.slice(input.offset, input.offset + input.limit);
|
|
2778
|
+
return withApiMetadata(api, {
|
|
2779
|
+
program_id: programId,
|
|
2780
|
+
targets: formatTargetList(targets, input.output_mode),
|
|
2781
|
+
meta: {
|
|
2782
|
+
offset: input.offset,
|
|
2783
|
+
limit: input.limit,
|
|
2784
|
+
output_mode: input.output_mode,
|
|
2785
|
+
total_active_targets: rawTargets.length,
|
|
2786
|
+
total_after_filters: sanitizedTargets.length,
|
|
2787
|
+
returned: targets.length,
|
|
2788
|
+
has_more: input.offset + input.limit < sanitizedTargets.length,
|
|
2789
|
+
target_source: "program_targets"
|
|
2790
|
+
}
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
async function getProgramTargetsExportFallbackPayload(client, input, programId) {
|
|
2794
|
+
const exportLookup = await fetchProgramTargetsFromExportFallback(client, programId);
|
|
2795
|
+
const sanitizedTargets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, input));
|
|
2796
|
+
const targets = sanitizedTargets.slice(input.offset, input.offset + input.limit);
|
|
2797
|
+
return stripUndefined({
|
|
2798
|
+
request_id: exportLookup.requestId,
|
|
2799
|
+
upstream_request_id: exportLookup.upstreamRequestId,
|
|
2800
|
+
fetched_at: exportLookup.fetchedAt,
|
|
2801
|
+
cache: exportLookup.cache,
|
|
2802
|
+
source_requests: exportLookup.sourceRequests,
|
|
2803
|
+
warnings: exportLookup.warnings.length > 0 ? uniqueStrings(exportLookup.warnings) : undefined,
|
|
2804
|
+
program_id: programId,
|
|
2805
|
+
targets: formatTargetList(targets, input.output_mode),
|
|
2806
|
+
meta: stripUndefined({
|
|
2807
|
+
offset: input.offset,
|
|
2808
|
+
limit: input.limit,
|
|
2809
|
+
output_mode: input.output_mode,
|
|
2810
|
+
total_active_targets: exportLookup.rawTargets.length,
|
|
2811
|
+
total_after_filters: sanitizedTargets.length,
|
|
2812
|
+
returned: targets.length,
|
|
2813
|
+
has_more: input.offset + input.limit < sanitizedTargets.length,
|
|
2814
|
+
target_source: exportLookup.source,
|
|
2815
|
+
target_list_unavailable: exportLookup.unavailable,
|
|
2816
|
+
fallback_error: exportLookup.error
|
|
2817
|
+
})
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
async function fetchProgramTargetsFromExportFallback(client, programId) {
|
|
2821
|
+
try {
|
|
2822
|
+
const api = await client.exportTargets({
|
|
2823
|
+
program_ids: [programId],
|
|
2824
|
+
include_out_of_scope: true,
|
|
2825
|
+
include_ineligible: true,
|
|
2826
|
+
format: "json",
|
|
2827
|
+
limit: MAX_TARGET_EXPORT
|
|
2828
|
+
});
|
|
2829
|
+
const rawTargets = readExportTargetRows(api.data).map((row) => sanitizeTarget(readObject(row)?.target ?? row));
|
|
2830
|
+
return {
|
|
2831
|
+
requestId: api.requestId,
|
|
2832
|
+
upstreamRequestId: api.upstreamRequestId,
|
|
2833
|
+
fetchedAt: api.fetchedAt,
|
|
2834
|
+
cache: readObject(stripUndefined({
|
|
2835
|
+
hit: api.cached,
|
|
2836
|
+
coalesced_live_request: api.coalesced,
|
|
2837
|
+
expires_at: api.cacheExpiresAt
|
|
2838
|
+
})),
|
|
2839
|
+
source: "target_export",
|
|
2840
|
+
rawTargets,
|
|
2841
|
+
sourceRequests: [
|
|
2842
|
+
{
|
|
2843
|
+
source: "target_export",
|
|
2844
|
+
program_id: programId,
|
|
2845
|
+
request_id: api.requestId,
|
|
2846
|
+
upstream_request_id: api.upstreamRequestId,
|
|
2847
|
+
...apiSourceMetadata(api)
|
|
2848
|
+
}
|
|
2849
|
+
],
|
|
2850
|
+
warnings: [`Used target export fallback because the per-program targets endpoint returned 404 for ${programId}.`],
|
|
2851
|
+
unavailable: false
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
catch (error) {
|
|
2855
|
+
return {
|
|
2856
|
+
requestId: randomUUID(),
|
|
2857
|
+
source: "unavailable",
|
|
2858
|
+
rawTargets: [],
|
|
2859
|
+
sourceRequests: [
|
|
2860
|
+
{
|
|
2861
|
+
source: "target_export",
|
|
2862
|
+
program_id: programId,
|
|
2863
|
+
failed: true,
|
|
2864
|
+
error: programFetchError(programId, error)
|
|
2865
|
+
}
|
|
2866
|
+
],
|
|
2867
|
+
warnings: [
|
|
2868
|
+
`The BBRadar program index contains ${programId}, but both the per-program targets endpoint and target export fallback were unavailable. Exact targets cannot be returned from the current API response.`
|
|
2869
|
+
],
|
|
2870
|
+
unavailable: true,
|
|
2871
|
+
error: programFetchError(programId, error)
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
function readExportTargetRows(data) {
|
|
2876
|
+
if (Array.isArray(data)) {
|
|
2877
|
+
return data;
|
|
2878
|
+
}
|
|
2879
|
+
const object = readObject(data);
|
|
2880
|
+
if (!object) {
|
|
2881
|
+
return [];
|
|
2882
|
+
}
|
|
2883
|
+
return [object.targets, object.results, object.data]
|
|
2884
|
+
.map(readArray)
|
|
2885
|
+
.find((rows) => rows.length > 0) ?? [];
|
|
2886
|
+
}
|
|
2619
2887
|
async function getProgramScopeSummary(client, config, input) {
|
|
2620
2888
|
const [programApi, targetsApi] = await Promise.all([client.getProgram(input.program_id), client.getProgramTargets(input.program_id)]);
|
|
2621
2889
|
const targetsData = readObject(targetsApi.data);
|
|
2622
2890
|
const rawTargets = readArray(targetsData?.targets);
|
|
2623
|
-
const targets = rawTargets
|
|
2891
|
+
const targets = rawTargets
|
|
2892
|
+
.map(sanitizeTarget)
|
|
2893
|
+
.filter((target) => targetHasAllowedScope(target, {
|
|
2894
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2895
|
+
include_ineligible: input.include_ineligible,
|
|
2896
|
+
strict_scope_filter: false
|
|
2897
|
+
}));
|
|
2624
2898
|
return {
|
|
2625
2899
|
request_id: randomUUID(),
|
|
2626
2900
|
source_requests: [
|
|
@@ -2641,11 +2915,39 @@ async function getProgramScopeSummary(client, config, input) {
|
|
|
2641
2915
|
scope: buildTargetScopeSummary(targets, input.target_list_mode, input.group_limit, input.include_out_of_scope, input.include_ineligible),
|
|
2642
2916
|
meta: {
|
|
2643
2917
|
total_active_targets: rawTargets.length,
|
|
2918
|
+
total_after_filters: targets.length,
|
|
2644
2919
|
target_list_mode: input.target_list_mode,
|
|
2645
2920
|
group_limit: input.group_limit
|
|
2646
2921
|
}
|
|
2647
2922
|
};
|
|
2648
2923
|
}
|
|
2924
|
+
async function getProgramScopeSummaryFromIndexFallback(client, indexFallback, input) {
|
|
2925
|
+
const exportLookup = await fetchProgramTargetsFromExportFallback(client, input.program_id);
|
|
2926
|
+
const targets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, {
|
|
2927
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2928
|
+
include_ineligible: input.include_ineligible,
|
|
2929
|
+
strict_scope_filter: false
|
|
2930
|
+
}));
|
|
2931
|
+
return stripUndefined({
|
|
2932
|
+
request_id: exportLookup.requestId,
|
|
2933
|
+
upstream_request_id: exportLookup.upstreamRequestId,
|
|
2934
|
+
fetched_at: exportLookup.fetchedAt,
|
|
2935
|
+
cache: exportLookup.cache,
|
|
2936
|
+
source_requests: exportLookup.sourceRequests,
|
|
2937
|
+
warnings: exportLookup.warnings.length > 0 ? uniqueStrings(exportLookup.warnings) : undefined,
|
|
2938
|
+
program: formatProgram(indexFallback.program, "compact"),
|
|
2939
|
+
scope: buildTargetScopeSummary(targets, input.target_list_mode, input.group_limit, input.include_out_of_scope, input.include_ineligible),
|
|
2940
|
+
meta: stripUndefined({
|
|
2941
|
+
total_active_targets: exportLookup.rawTargets.length,
|
|
2942
|
+
total_after_filters: targets.length,
|
|
2943
|
+
target_list_mode: input.target_list_mode,
|
|
2944
|
+
group_limit: input.group_limit,
|
|
2945
|
+
target_source: exportLookup.source,
|
|
2946
|
+
target_list_unavailable: exportLookup.unavailable,
|
|
2947
|
+
fallback_error: exportLookup.error
|
|
2948
|
+
})
|
|
2949
|
+
});
|
|
2950
|
+
}
|
|
2649
2951
|
async function getProgramTargetBreakdown(client, config, input) {
|
|
2650
2952
|
const [programApi, targetsApi] = await Promise.all([client.getProgram(input.program_id), client.getProgramTargets(input.program_id)]);
|
|
2651
2953
|
const targetsData = readObject(targetsApi.data);
|
|
@@ -2692,6 +2994,42 @@ async function getProgramTargetBreakdown(client, config, input) {
|
|
|
2692
2994
|
}
|
|
2693
2995
|
};
|
|
2694
2996
|
}
|
|
2997
|
+
async function getProgramTargetBreakdownFromIndexFallback(client, indexFallback, input) {
|
|
2998
|
+
const exportLookup = await fetchProgramTargetsFromExportFallback(client, input.program_id);
|
|
2999
|
+
const targets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, {
|
|
3000
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
3001
|
+
include_ineligible: input.include_ineligible,
|
|
3002
|
+
strict_scope_filter: false
|
|
3003
|
+
}));
|
|
3004
|
+
const scope = buildTargetScopeSummary(targets, input.target_list_mode, input.group_limit, input.include_out_of_scope, input.include_ineligible);
|
|
3005
|
+
return stripUndefined({
|
|
3006
|
+
request_id: exportLookup.requestId,
|
|
3007
|
+
upstream_request_id: exportLookup.upstreamRequestId,
|
|
3008
|
+
fetched_at: exportLookup.fetchedAt,
|
|
3009
|
+
cache: exportLookup.cache,
|
|
3010
|
+
source_requests: exportLookup.sourceRequests,
|
|
3011
|
+
warnings: exportLookup.warnings.length > 0 ? uniqueStrings(exportLookup.warnings) : undefined,
|
|
3012
|
+
program: formatProgram(indexFallback.program, "compact"),
|
|
3013
|
+
breakdown: {
|
|
3014
|
+
target_counts: readObject(scope.target_counts),
|
|
3015
|
+
by_target_type: countTargetValues(targets, (target) => stringField(target, "target_type")),
|
|
3016
|
+
by_scope_tag: countTargetValues(targets, (target) => readStringArrayField(target, "scope_tags")),
|
|
3017
|
+
by_language_tag: countTargetValues(targets, (target) => readStringArrayField(target, "language_tags")),
|
|
3018
|
+
group_counts: readObject(scope.group_counts),
|
|
3019
|
+
group_has_more: readObject(scope.group_has_more),
|
|
3020
|
+
groups: readObject(scope.groups)
|
|
3021
|
+
},
|
|
3022
|
+
meta: stripUndefined({
|
|
3023
|
+
total_active_targets: exportLookup.rawTargets.length,
|
|
3024
|
+
total_after_filters: targets.length,
|
|
3025
|
+
target_list_mode: input.target_list_mode,
|
|
3026
|
+
group_limit: input.group_limit,
|
|
3027
|
+
target_source: exportLookup.source,
|
|
3028
|
+
target_list_unavailable: exportLookup.unavailable,
|
|
3029
|
+
fallback_error: exportLookup.error
|
|
3030
|
+
})
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
2695
3033
|
async function getProgramScopeDelta(client, config, input) {
|
|
2696
3034
|
const programApi = await client.getProgram(input.program_id);
|
|
2697
3035
|
const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
|
|
@@ -2903,6 +3241,77 @@ async function checkProgramNewTargets(client, config, input) {
|
|
|
2903
3241
|
});
|
|
2904
3242
|
return programLookup.fallback ? withProgramIdFallbackMetadata(payload, programLookup.fallback) : payload;
|
|
2905
3243
|
}
|
|
3244
|
+
async function checkProgramNewTargetsFromIndexedProgram(client, config, input, indexedProgram) {
|
|
3245
|
+
const programId = input.program_id;
|
|
3246
|
+
const handle = programSearchText(programId);
|
|
3247
|
+
const api = await client.getRecentChanges({
|
|
3248
|
+
change_type: "added",
|
|
3249
|
+
include_removed: false,
|
|
3250
|
+
include_ineligible: input.include_ineligible,
|
|
3251
|
+
include_out_of_scope: false,
|
|
3252
|
+
search: handle.length >= 2 ? handle : programId,
|
|
3253
|
+
tags: input.target_type ? [input.target_type] : [],
|
|
3254
|
+
page: 1,
|
|
3255
|
+
page_size: input.page_size
|
|
3256
|
+
});
|
|
3257
|
+
const data = readObject(api.data);
|
|
3258
|
+
const upstreamMeta = readObject(sanitizeJson(data?.meta));
|
|
3259
|
+
const sinceTimestamp = input.since ? Date.parse(input.since) : undefined;
|
|
3260
|
+
const changes = readArray(data?.results).map((change) => sanitizeChange(change, config.webBaseUrl));
|
|
3261
|
+
const matchingChanges = changes
|
|
3262
|
+
.filter((change) => {
|
|
3263
|
+
const target = readObject(change.target);
|
|
3264
|
+
const changedAt = timestampField(change, "changed_at");
|
|
3265
|
+
return (stringField(change, "change_type") === "added" &&
|
|
3266
|
+
stringField(readObject(change.program), "id") === programId &&
|
|
3267
|
+
target !== undefined &&
|
|
3268
|
+
(sinceTimestamp === undefined || changedAt >= sinceTimestamp) &&
|
|
3269
|
+
(!input.target_type || targetMatchesRequestedType(target, input.target_type)) &&
|
|
3270
|
+
targetHasAllowedScope(target, {
|
|
3271
|
+
include_out_of_scope: false,
|
|
3272
|
+
include_ineligible: input.include_ineligible,
|
|
3273
|
+
strict_scope_filter: true
|
|
3274
|
+
}));
|
|
3275
|
+
})
|
|
3276
|
+
.sort((left, right) => timestampField(right, "changed_at") - timestampField(left, "changed_at"));
|
|
3277
|
+
const limitedChanges = matchingChanges.slice(0, input.max_targets);
|
|
3278
|
+
const targets = limitedChanges
|
|
3279
|
+
.map((change) => readObject(change.target))
|
|
3280
|
+
.filter((target) => target !== undefined);
|
|
3281
|
+
const latest = matchingChanges[0];
|
|
3282
|
+
return stripUndefined({
|
|
3283
|
+
request_id: randomUUID(),
|
|
3284
|
+
source_requests: [
|
|
3285
|
+
{
|
|
3286
|
+
source: "recent_changes",
|
|
3287
|
+
request_id: api.requestId,
|
|
3288
|
+
upstream_request_id: api.upstreamRequestId,
|
|
3289
|
+
...apiSourceMetadata(api)
|
|
3290
|
+
}
|
|
3291
|
+
],
|
|
3292
|
+
program_id: programId,
|
|
3293
|
+
program: latest
|
|
3294
|
+
? formatProgram(addProgramResourceLinks(readObject(latest.program) ?? {}), "compact")
|
|
3295
|
+
: formatProgram(indexedProgram, "compact"),
|
|
3296
|
+
has_new_targets: matchingChanges.length > 0,
|
|
3297
|
+
new_target_count: matchingChanges.length,
|
|
3298
|
+
latest_added_at: latest ? stringField(latest, "changed_at") : undefined,
|
|
3299
|
+
new_targets: formatTargetList(targets, input.target_list_mode),
|
|
3300
|
+
meta: stripUndefined({
|
|
3301
|
+
recent_changes_scanned: changes.length,
|
|
3302
|
+
returned: targets.length,
|
|
3303
|
+
has_more: matchingChanges.length > targets.length,
|
|
3304
|
+
since: input.since,
|
|
3305
|
+
target_type: input.target_type,
|
|
3306
|
+
in_scope_only: true,
|
|
3307
|
+
include_ineligible: input.include_ineligible,
|
|
3308
|
+
target_list_mode: input.target_list_mode,
|
|
3309
|
+
program_detail_source: "program_index",
|
|
3310
|
+
upstream_total_pages: readNumber(upstreamMeta?.total_pages),
|
|
3311
|
+
scan_may_be_incomplete: (readNumber(upstreamMeta?.total_pages) ?? 1) > 1
|
|
3312
|
+
})
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
2906
3315
|
async function checkWatchlistNewTargets(client, config, input) {
|
|
2907
3316
|
const watchlist = new Set(input.program_ids);
|
|
2908
3317
|
const api = await client.getRecentChanges({
|
|
@@ -3573,8 +3982,9 @@ function candidateLooksLikeVdp(candidate) {
|
|
|
3573
3982
|
return candidateHasKnownNoBounty(candidate) || tagIncludesAnyNormalized(candidate.normalizedSearchSignals, VDP_SIGNALS);
|
|
3574
3983
|
}
|
|
3575
3984
|
function programResolutionMatch(candidate, query) {
|
|
3576
|
-
const
|
|
3577
|
-
const
|
|
3985
|
+
const queryVariants = programResolutionQueryVariants(query);
|
|
3986
|
+
const primaryQuery = queryVariants[0] ?? normalizeTag(query);
|
|
3987
|
+
const queryTokens = meaningfulProgramQueryTokens(query);
|
|
3578
3988
|
const fields = [
|
|
3579
3989
|
{ name: "id", value: stringField(candidate.program, "id") },
|
|
3580
3990
|
{ name: "handle", value: stringField(candidate.program, "handle") },
|
|
@@ -3592,16 +4002,23 @@ function programResolutionMatch(candidate, query) {
|
|
|
3592
4002
|
}
|
|
3593
4003
|
const normalizedValue = normalizeTag(field.value);
|
|
3594
4004
|
const fieldTokens = tokenSet(normalizedValue);
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
4005
|
+
for (const queryVariant of queryVariants) {
|
|
4006
|
+
if (normalizedValue === queryVariant) {
|
|
4007
|
+
score = Math.max(score, field.name === "id" || field.name === "handle" ? 100 : 95);
|
|
4008
|
+
matchedFields.add(field.name);
|
|
4009
|
+
reasons.add(queryVariant === primaryQuery ? `${field.name} exact match` : `${field.name} exact meaningful-query match`);
|
|
4010
|
+
continue;
|
|
4011
|
+
}
|
|
4012
|
+
if (normalizedValue.includes(queryVariant)) {
|
|
4013
|
+
const containsScore = field.name === "name" || field.name === "handle"
|
|
4014
|
+
? queryVariant.includes("-") || queryVariant.includes(" ")
|
|
4015
|
+
? 90
|
|
4016
|
+
: 85
|
|
4017
|
+
: 70;
|
|
4018
|
+
matchedFields.add(field.name);
|
|
4019
|
+
score = Math.max(score, containsScore);
|
|
4020
|
+
reasons.add(queryVariant === primaryQuery ? `${field.name} contains query` : `${field.name} contains meaningful query`);
|
|
4021
|
+
}
|
|
3605
4022
|
}
|
|
3606
4023
|
const tokenOverlap = [...queryTokens].filter((token) => fieldTokens.has(token)).length;
|
|
3607
4024
|
if (tokenOverlap > 0) {
|
|
@@ -3611,10 +4028,12 @@ function programResolutionMatch(candidate, query) {
|
|
|
3611
4028
|
reasons.add(`${field.name} token match`);
|
|
3612
4029
|
}
|
|
3613
4030
|
}
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
4031
|
+
for (const queryVariant of queryVariants) {
|
|
4032
|
+
if (candidate.normalizedSearchSignals.some((signal) => signal.includes(queryVariant))) {
|
|
4033
|
+
score = Math.max(score, queryVariant === primaryQuery ? 55 : 65);
|
|
4034
|
+
matchedFields.add("search_signals");
|
|
4035
|
+
reasons.add(queryVariant === primaryQuery ? "program search signals contain query" : "program search signals contain meaningful query");
|
|
4036
|
+
}
|
|
3618
4037
|
}
|
|
3619
4038
|
return {
|
|
3620
4039
|
score,
|
|
@@ -3622,6 +4041,25 @@ function programResolutionMatch(candidate, query) {
|
|
|
3622
4041
|
reasons: [...reasons]
|
|
3623
4042
|
};
|
|
3624
4043
|
}
|
|
4044
|
+
function programResolutionSearchQueries(query) {
|
|
4045
|
+
return programResolutionQueryVariants(query).slice(0, MAX_RESOLVE_SEARCH_QUERIES);
|
|
4046
|
+
}
|
|
4047
|
+
function programResolutionQueryVariants(query) {
|
|
4048
|
+
const normalized = normalizeTag(query);
|
|
4049
|
+
const meaningfulTokens = [...meaningfulProgramQueryTokens(query)];
|
|
4050
|
+
const meaningfulPhrase = meaningfulTokens.join(" ");
|
|
4051
|
+
const values = [
|
|
4052
|
+
normalized,
|
|
4053
|
+
meaningfulPhrase,
|
|
4054
|
+
meaningfulTokens.join("-"),
|
|
4055
|
+
...meaningfulTokens
|
|
4056
|
+
];
|
|
4057
|
+
return uniqueStrings(values.filter((value) => value.length >= 2));
|
|
4058
|
+
}
|
|
4059
|
+
function meaningfulProgramQueryTokens(query) {
|
|
4060
|
+
const tokens = [...tokenSet(normalizeTag(query))].filter((token) => !PROGRAM_RESOLUTION_STOP_WORDS.has(token));
|
|
4061
|
+
return new Set(tokens.length > 0 ? tokens : [...tokenSet(normalizeTag(query))]);
|
|
4062
|
+
}
|
|
3625
4063
|
function normalizedCandidateValues(program, normalizedScopeTags) {
|
|
3626
4064
|
return [
|
|
3627
4065
|
stringField(program, "id"),
|
|
@@ -3672,9 +4110,14 @@ function programHandleText(programId) {
|
|
|
3672
4110
|
const colonIndex = programId.indexOf(":");
|
|
3673
4111
|
return colonIndex >= 0 ? programId.slice(colonIndex + 1) : programId;
|
|
3674
4112
|
}
|
|
4113
|
+
function programPlatformText(programId) {
|
|
4114
|
+
const colonIndex = programId.indexOf(":");
|
|
4115
|
+
return colonIndex >= 0 ? programId.slice(0, colonIndex) : "";
|
|
4116
|
+
}
|
|
3675
4117
|
function normalizeFindProgramsInput(input) {
|
|
3676
4118
|
const warnings = [];
|
|
3677
4119
|
const normalized = { ...input };
|
|
4120
|
+
normalized.vdp_mode ??= "exclude";
|
|
3678
4121
|
normalized.max_pages = clampLimit("max_pages", normalized.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
|
|
3679
4122
|
normalized.max_results = clampLimit("max_results", normalized.max_results, MAX_FIND_PROGRAM_RESULTS, warnings);
|
|
3680
4123
|
normalized.target_sample_programs = clampLimit("target_sample_programs", normalized.target_sample_programs, MAX_FIND_TARGET_SAMPLE_PROGRAMS, warnings);
|
|
@@ -3805,6 +4248,55 @@ async function collectPrograms(client, options) {
|
|
|
3805
4248
|
totalPagesBySource
|
|
3806
4249
|
};
|
|
3807
4250
|
}
|
|
4251
|
+
async function collectProgramsForResolution(client, options) {
|
|
4252
|
+
const collections = [];
|
|
4253
|
+
const queries = options.searchQueries.length > 0 ? options.searchQueries : [undefined];
|
|
4254
|
+
for (const search of queries) {
|
|
4255
|
+
if (options.budget.remaining <= 0) {
|
|
4256
|
+
addUniqueWarning(options.warnings, "Upstream request budget was exhausted before all program name search variants could be checked.");
|
|
4257
|
+
break;
|
|
4258
|
+
}
|
|
4259
|
+
collections.push(await collectPrograms(client, {
|
|
4260
|
+
search,
|
|
4261
|
+
platforms: options.platforms,
|
|
4262
|
+
tags: options.tags,
|
|
4263
|
+
updated_since: undefined,
|
|
4264
|
+
opportunity_levels: options.opportunity_levels,
|
|
4265
|
+
max_pages: options.max_pages,
|
|
4266
|
+
budget: options.budget,
|
|
4267
|
+
warnings: options.warnings
|
|
4268
|
+
}));
|
|
4269
|
+
}
|
|
4270
|
+
if (collections.length === 0) {
|
|
4271
|
+
return {
|
|
4272
|
+
programs: [],
|
|
4273
|
+
sourceRequests: [],
|
|
4274
|
+
requestsScanned: 0,
|
|
4275
|
+
programsScanned: 0,
|
|
4276
|
+
totalPagesBySource: {}
|
|
4277
|
+
};
|
|
4278
|
+
}
|
|
4279
|
+
return mergeProgramCollections(collections);
|
|
4280
|
+
}
|
|
4281
|
+
function mergeProgramCollections(collections) {
|
|
4282
|
+
const programs = new Map();
|
|
4283
|
+
const totalPagesBySource = {};
|
|
4284
|
+
for (const collection of collections) {
|
|
4285
|
+
for (const program of collection.programs) {
|
|
4286
|
+
programs.set(programKey(program, programs.size), program);
|
|
4287
|
+
}
|
|
4288
|
+
for (const [sourceName, totalPages] of Object.entries(collection.totalPagesBySource)) {
|
|
4289
|
+
totalPagesBySource[sourceName] = totalPages;
|
|
4290
|
+
}
|
|
4291
|
+
}
|
|
4292
|
+
return {
|
|
4293
|
+
programs: [...programs.values()],
|
|
4294
|
+
sourceRequests: collections.flatMap((collection) => collection.sourceRequests),
|
|
4295
|
+
requestsScanned: collections.reduce((total, collection) => total + collection.requestsScanned, 0),
|
|
4296
|
+
programsScanned: programs.size,
|
|
4297
|
+
totalPagesBySource
|
|
4298
|
+
};
|
|
4299
|
+
}
|
|
3808
4300
|
async function collectProgramSources(client, sources, options) {
|
|
3809
4301
|
const pages = [];
|
|
3810
4302
|
await mapWithConcurrency(sources.map((source, sourceIndex) => ({ source, sourceIndex })), PROGRAM_COLLECTION_CONCURRENCY, async ({ source, sourceIndex }) => {
|
|
@@ -3849,6 +4341,7 @@ async function fetchProgramPage(client, source, sourceIndex, page, options) {
|
|
|
3849
4341
|
const runWithSlot = options.runWithProgramPageSlot ?? runImmediately;
|
|
3850
4342
|
return runWithSlot(async () => {
|
|
3851
4343
|
const query = toQuery({
|
|
4344
|
+
search: options.search,
|
|
3852
4345
|
platforms: options.platforms,
|
|
3853
4346
|
tags: options.tags,
|
|
3854
4347
|
updated_since: options.updated_since,
|
|
@@ -3864,14 +4357,16 @@ async function fetchProgramPage(client, source, sourceIndex, page, options) {
|
|
|
3864
4357
|
const data = readObject(api.data);
|
|
3865
4358
|
const meta = readObject(data?.meta);
|
|
3866
4359
|
const sourceName = source.kind === "opportunity" ? `opportunities:${source.level}` : "programs";
|
|
4360
|
+
const labeledSourceName = options.search ? `${sourceName}:search:${normalizeTag(options.search)}` : sourceName;
|
|
3867
4361
|
return {
|
|
3868
|
-
sourceName,
|
|
4362
|
+
sourceName: labeledSourceName,
|
|
3869
4363
|
sourceIndex,
|
|
3870
4364
|
page,
|
|
3871
4365
|
programs: readArray(data?.programs),
|
|
3872
4366
|
totalPages: readNumber(meta?.total_pages),
|
|
3873
4367
|
sourceRequest: stripUndefined({
|
|
3874
|
-
source:
|
|
4368
|
+
source: labeledSourceName,
|
|
4369
|
+
search: options.search,
|
|
3875
4370
|
request_id: api.requestId,
|
|
3876
4371
|
upstream_request_id: api.upstreamRequestId,
|
|
3877
4372
|
...apiSourceMetadata(api),
|
|
@@ -3994,13 +4489,7 @@ function programCandidateMatches(candidate, filters) {
|
|
|
3994
4489
|
if (filters.contest_like === true && !candidateLooksContestLike(candidate)) {
|
|
3995
4490
|
return false;
|
|
3996
4491
|
}
|
|
3997
|
-
if (filters.vdp_mode
|
|
3998
|
-
return false;
|
|
3999
|
-
}
|
|
4000
|
-
if (filters.vdp_mode === "only_likely" && !candidateLooksLikeVdp(candidate)) {
|
|
4001
|
-
return false;
|
|
4002
|
-
}
|
|
4003
|
-
if (filters.vdp_mode === "only_known_no_bounty" && !candidateHasKnownNoBounty(candidate)) {
|
|
4492
|
+
if (!candidateMatchesVdpMode(candidate, filters.vdp_mode ?? "exclude")) {
|
|
4004
4493
|
return false;
|
|
4005
4494
|
}
|
|
4006
4495
|
if (filters.fresh_launch_days !== undefined) {
|
|
@@ -4061,6 +4550,18 @@ function compareProgramCandidates(left, right, sortBy) {
|
|
|
4061
4550
|
right.firstSeenTime - left.firstSeenTime ||
|
|
4062
4551
|
right.lastUpdatedTime - left.lastUpdatedTime);
|
|
4063
4552
|
}
|
|
4553
|
+
function candidateMatchesVdpMode(candidate, vdpMode) {
|
|
4554
|
+
if (vdpMode === "exclude") {
|
|
4555
|
+
return !candidateLooksLikeVdp(candidate);
|
|
4556
|
+
}
|
|
4557
|
+
if (vdpMode === "only_likely") {
|
|
4558
|
+
return candidateLooksLikeVdp(candidate);
|
|
4559
|
+
}
|
|
4560
|
+
if (vdpMode === "only_known_no_bounty") {
|
|
4561
|
+
return candidateHasKnownNoBounty(candidate);
|
|
4562
|
+
}
|
|
4563
|
+
return true;
|
|
4564
|
+
}
|
|
4064
4565
|
async function collectTargetSamples(client, candidates, input, budget) {
|
|
4065
4566
|
const samples = {};
|
|
4066
4567
|
const sampleCandidates = candidates.slice(0, input.target_sample_programs);
|
|
@@ -4263,7 +4764,7 @@ function registerPrompts(server) {
|
|
|
4263
4764
|
|
|
4264
4765
|
Find the best BBRadar program candidates.
|
|
4265
4766
|
- Start with find_program_candidates unless the user needs custom filters; pass opportunity_levels from this JSON array unless the user asked for a broader search: ${promptJson([args.opportunity_level ?? "elite"])}.
|
|
4266
|
-
- Use find_language_programs for language asks, find_target_type_programs for API/mobile/source-code/domain asks, find_reward_programs or find_paid_programs for reward asks, find_vdp_programs for VDP/no-bounty asks, find_web3_contests for Web3 contest/audit asks, find_web3_programs for general Web3 requests, and find_wildcard_programs for wildcard-specific requests.
|
|
4767
|
+
- Use find_language_programs for language asks, find_target_type_programs for API/mobile/source-code/domain asks, find_reward_programs or find_paid_programs for reward asks, find_vdp_programs only for explicit VDP/no-bounty asks, find_web3_contests for Web3 contest/audit asks, find_web3_programs for general Web3 requests, and find_wildcard_programs for wildcard-specific requests.
|
|
4267
4768
|
- Apply platform filters from this JSON string if provided: ${promptJson(args.platforms ?? "")}.
|
|
4268
4769
|
- Apply tag filters from this JSON string if provided: ${promptJson(args.tags ?? "")}.
|
|
4269
4770
|
- Keep the shortlist to this JSON value: ${promptJson(args.max_programs ?? "10")}.
|
|
@@ -4273,16 +4774,16 @@ Find the best BBRadar program candidates.
|
|
|
4273
4774
|
- Stay passive-only; do not recommend scanning, probing, exploit attempts, or direct contact with targets.`));
|
|
4274
4775
|
server.registerPrompt("summarize_program_scope", {
|
|
4275
4776
|
title: "Summarize Program Scope",
|
|
4276
|
-
description: "Summarize
|
|
4777
|
+
description: "Summarize BBRadar target data for one program.",
|
|
4277
4778
|
argsSchema: {
|
|
4278
4779
|
program_id: programIdSchema
|
|
4279
4780
|
}
|
|
4280
|
-
}, (args) => promptResult(`Use get_program for the program_id in this JSON string: ${promptJson(args.program_id)}. Then use get_program_targets with include_out_of_scope=
|
|
4781
|
+
}, (args) => promptResult(`Use get_program for the program_id in this JSON string: ${promptJson(args.program_id)}. Then use get_program_targets with include_out_of_scope=false, include_ineligible=false, and limit=${MAX_TARGETS_PER_PROGRAM}. Only request out-of-scope or ineligible targets if the user explicitly asks for them.
|
|
4281
4782
|
|
|
4282
4783
|
Summarize the program scope:
|
|
4283
4784
|
- Program name, platform, reward range, public report count, first seen date, last updated date, and BBRadar URL.
|
|
4284
4785
|
- In-scope bounty-eligible target categories.
|
|
4285
|
-
- Out-of-scope or ineligible target categories.
|
|
4786
|
+
- Out-of-scope or ineligible target categories only if explicitly requested.
|
|
4286
4787
|
- Wildcards, high-severity target labels, and notable language or scope tags.
|
|
4287
4788
|
- Recent target update signals if present.
|
|
4288
4789
|
- Do not contact, scan, probe, or validate any listed target.`));
|