@kaitranntt/ccs 7.79.1-dev.17 → 7.79.1-dev.19
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.
|
@@ -639,7 +639,7 @@ function getTools() {
|
|
|
639
639
|
},
|
|
640
640
|
},
|
|
641
641
|
priority: { type: 'integer' },
|
|
642
|
-
action: { type: 'string', enum:
|
|
642
|
+
action: { type: 'string', enum: getInterceptActionEnum() },
|
|
643
643
|
statusCode: { type: 'integer', minimum: 100, maximum: 599 },
|
|
644
644
|
responseHeaders: {
|
|
645
645
|
type: 'array',
|
|
@@ -1209,7 +1209,18 @@ function getTools() {
|
|
|
1209
1209
|
},
|
|
1210
1210
|
event: {
|
|
1211
1211
|
type: 'object',
|
|
1212
|
-
description:
|
|
1212
|
+
description:
|
|
1213
|
+
'Required event selector. request events require urlIncludes; download events require urlIncludes or suggestedFilenameIncludes. URLs in returned details are redacted.',
|
|
1214
|
+
properties: {
|
|
1215
|
+
kind: { type: 'string', enum: ['dialog', 'navigation', 'request', 'download'] },
|
|
1216
|
+
dialogType: { type: 'string' },
|
|
1217
|
+
messageIncludes: { type: 'string' },
|
|
1218
|
+
urlIncludes: { type: 'string' },
|
|
1219
|
+
method: { type: 'string' },
|
|
1220
|
+
suggestedFilenameIncludes: { type: 'string' },
|
|
1221
|
+
},
|
|
1222
|
+
required: ['kind'],
|
|
1223
|
+
additionalProperties: false,
|
|
1213
1224
|
},
|
|
1214
1225
|
},
|
|
1215
1226
|
required: ['event'],
|
|
@@ -1292,9 +1303,30 @@ function parseOptionalPageId(toolArgs) {
|
|
|
1292
1303
|
: '';
|
|
1293
1304
|
}
|
|
1294
1305
|
|
|
1306
|
+
function getBrowserInterceptFulfillMode() {
|
|
1307
|
+
return String(process.env.CCS_BROWSER_INTERCEPT_FULFILL_MODE || 'disabled').trim() === 'enabled'
|
|
1308
|
+
? 'enabled'
|
|
1309
|
+
: 'disabled';
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
function isBrowserInterceptFulfillEnabled() {
|
|
1313
|
+
return getBrowserInterceptFulfillMode() === 'enabled';
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function getInterceptActionEnum() {
|
|
1317
|
+
return isBrowserInterceptFulfillEnabled()
|
|
1318
|
+
? ['continue', 'fail', 'fulfill']
|
|
1319
|
+
: ['continue', 'fail'];
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1295
1322
|
function parseInterceptAction(value) {
|
|
1323
|
+
if (value === 'fulfill' && !isBrowserInterceptFulfillEnabled()) {
|
|
1324
|
+
throw new Error(
|
|
1325
|
+
'action fulfill is disabled by CCS_BROWSER_INTERCEPT_FULFILL_MODE=disabled; set it to enabled only for trusted local testing'
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1296
1328
|
if (value !== 'continue' && value !== 'fail' && value !== 'fulfill') {
|
|
1297
|
-
throw new Error(
|
|
1329
|
+
throw new Error(`action must be one of: ${getInterceptActionEnum().join(', ')}`);
|
|
1298
1330
|
}
|
|
1299
1331
|
return value;
|
|
1300
1332
|
}
|
|
@@ -3327,24 +3359,80 @@ function parseEventCondition(value) {
|
|
|
3327
3359
|
};
|
|
3328
3360
|
}
|
|
3329
3361
|
if (value.kind === 'request') {
|
|
3362
|
+
const urlIncludes = value.urlIncludes ? String(value.urlIncludes) : undefined;
|
|
3363
|
+
if (!urlIncludes) {
|
|
3364
|
+
throw new Error('request events require urlIncludes to limit network metadata exposure');
|
|
3365
|
+
}
|
|
3330
3366
|
return {
|
|
3331
3367
|
kind: 'request',
|
|
3332
|
-
urlIncludes
|
|
3368
|
+
urlIncludes,
|
|
3333
3369
|
method: value.method ? String(value.method) : undefined,
|
|
3334
3370
|
};
|
|
3335
3371
|
}
|
|
3336
3372
|
if (value.kind === 'download') {
|
|
3373
|
+
const urlIncludes = value.urlIncludes ? String(value.urlIncludes) : undefined;
|
|
3374
|
+
const suggestedFilenameIncludes = value.suggestedFilenameIncludes
|
|
3375
|
+
? String(value.suggestedFilenameIncludes)
|
|
3376
|
+
: undefined;
|
|
3377
|
+
if (!urlIncludes && !suggestedFilenameIncludes) {
|
|
3378
|
+
throw new Error(
|
|
3379
|
+
'download events require urlIncludes or suggestedFilenameIncludes to limit metadata exposure'
|
|
3380
|
+
);
|
|
3381
|
+
}
|
|
3337
3382
|
return {
|
|
3338
3383
|
kind: 'download',
|
|
3339
|
-
urlIncludes
|
|
3340
|
-
suggestedFilenameIncludes
|
|
3341
|
-
? String(value.suggestedFilenameIncludes)
|
|
3342
|
-
: undefined,
|
|
3384
|
+
urlIncludes,
|
|
3385
|
+
suggestedFilenameIncludes,
|
|
3343
3386
|
};
|
|
3344
3387
|
}
|
|
3345
3388
|
throw new Error(`unknown event kind: ${String(value.kind || '')}`);
|
|
3346
3389
|
}
|
|
3347
3390
|
|
|
3391
|
+
function redactUrlForModel(value, options = {}) {
|
|
3392
|
+
const rawUrl = String(value || '');
|
|
3393
|
+
if (!rawUrl) {
|
|
3394
|
+
return '';
|
|
3395
|
+
}
|
|
3396
|
+
try {
|
|
3397
|
+
const url = new URL(rawUrl);
|
|
3398
|
+
if (options.originOnly) {
|
|
3399
|
+
return url.origin;
|
|
3400
|
+
}
|
|
3401
|
+
url.username = '';
|
|
3402
|
+
url.password = '';
|
|
3403
|
+
url.search = '';
|
|
3404
|
+
url.hash = '';
|
|
3405
|
+
return url.toString();
|
|
3406
|
+
} catch {
|
|
3407
|
+
return '[redacted-url]';
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
function redactObservedEvent(event, observed) {
|
|
3412
|
+
if (!observed || typeof observed !== 'object') {
|
|
3413
|
+
return observed;
|
|
3414
|
+
}
|
|
3415
|
+
if (event.kind === 'request') {
|
|
3416
|
+
return {
|
|
3417
|
+
url: redactUrlForModel(observed.url, { originOnly: true }),
|
|
3418
|
+
method: observed.method || '',
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
if (event.kind === 'download') {
|
|
3422
|
+
return {
|
|
3423
|
+
url: redactUrlForModel(observed.url, { originOnly: true }),
|
|
3424
|
+
suggestedFilename: observed.suggestedFilename || '',
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
if (event.kind === 'navigation' && Object.prototype.hasOwnProperty.call(observed, 'url')) {
|
|
3428
|
+
return {
|
|
3429
|
+
...observed,
|
|
3430
|
+
url: redactUrlForModel(observed.url, { originOnly: true }),
|
|
3431
|
+
};
|
|
3432
|
+
}
|
|
3433
|
+
return observed;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3348
3436
|
function matchesObservedEvent(event, observed) {
|
|
3349
3437
|
if (event.kind === 'dialog') {
|
|
3350
3438
|
return (
|
|
@@ -4798,7 +4886,8 @@ async function handleWaitForEvent(toolArgs) {
|
|
|
4798
4886
|
);
|
|
4799
4887
|
const event = parseEventCondition(toolArgs.event);
|
|
4800
4888
|
const observed = await waitForMatchingEvent({ page, pageIndex, timeoutMs, event });
|
|
4801
|
-
|
|
4889
|
+
const safeObserved = redactObservedEvent(event, observed);
|
|
4890
|
+
return `pageIndex: ${pageIndex}\nevent: ${event.kind}\nstatus: observed\ndetail: ${JSON.stringify(safeObserved)}`;
|
|
4802
4891
|
}
|
|
4803
4892
|
|
|
4804
4893
|
function findInterceptRuleIndex(ruleId) {
|