opencode-api-security-testing 5.4.1 → 5.4.3
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/package.json +1 -1
- package/src/index.ts +61 -9
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -334,6 +334,40 @@ const CYBER_SUPERVISOR = DEFAULT_CONFIG.cyber_supervisor;
|
|
|
334
334
|
const modelFailureCounts = new Map<string, Map<string, number>>();
|
|
335
335
|
const sessionFailures = new Map<string, number>();
|
|
336
336
|
|
|
337
|
+
// 首条消息追踪 (参考 oh-my-opencode 的 FirstMessageVariantGate 模式)
|
|
338
|
+
const pendingFirstMessages = new Set<string>();
|
|
339
|
+
const injectedSessions = new Set<string>();
|
|
340
|
+
|
|
341
|
+
// 标记新会话创建(由 event hook 调用)
|
|
342
|
+
function markSessionCreated(sessionID: string | undefined): void {
|
|
343
|
+
if (sessionID) {
|
|
344
|
+
pendingFirstMessages.add(sessionID);
|
|
345
|
+
console.log(`[api-security-testing] Session marked for first message injection: ${sessionID}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 检查是否是首条消息(由 chat.message hook 调用)
|
|
350
|
+
function isFirstMessage(sessionID: string | undefined): boolean {
|
|
351
|
+
if (!sessionID) return false;
|
|
352
|
+
return pendingFirstMessages.has(sessionID);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// 标记首条消息已处理(由 chat.message hook 调用,注入后立即调用)
|
|
356
|
+
function markFirstMessageApplied(sessionID: string | undefined): void {
|
|
357
|
+
if (sessionID) {
|
|
358
|
+
pendingFirstMessages.delete(sessionID);
|
|
359
|
+
injectedSessions.add(sessionID); // 记录已注入
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 清理会话状态(由 event hook 在 session.deleted 时调用)
|
|
364
|
+
function clearSessionState(sessionID: string | undefined): void {
|
|
365
|
+
if (sessionID) {
|
|
366
|
+
pendingFirstMessages.delete(sessionID);
|
|
367
|
+
injectedSessions.delete(sessionID);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
337
371
|
function getConfigPath(ctx: { directory: string }): string {
|
|
338
372
|
return join(ctx.directory, SKILL_DIR, "assets", CONFIG_FILE);
|
|
339
373
|
}
|
|
@@ -1266,20 +1300,25 @@ print(json.dumps(result, ensure_ascii=False))
|
|
|
1266
1300
|
},
|
|
1267
1301
|
|
|
1268
1302
|
// 赛博监工 Hook - chat.message
|
|
1303
|
+
// 参考 oh-my-opencode 的 FirstMessageVariantGate 模式:只在首条消息注入一次
|
|
1269
1304
|
"chat.message": async (input, output) => {
|
|
1270
1305
|
const sessionID = input.sessionID;
|
|
1271
1306
|
|
|
1272
|
-
//
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
textPart.text
|
|
1307
|
+
// 只在首条消息时注入 agents prompt (参考 oh-my-opencode 的 firstMessageVariantGate)
|
|
1308
|
+
if (isFirstMessage(sessionID)) {
|
|
1309
|
+
const agentsPrompt = getInjectedAgentsPrompt();
|
|
1310
|
+
if (agentsPrompt) {
|
|
1311
|
+
const parts = output.parts as Array<{ type: string; text?: string }>;
|
|
1312
|
+
const textPart = parts.find(p => p.type === "text");
|
|
1313
|
+
if (textPart && textPart.text) {
|
|
1314
|
+
textPart.text += agentsPrompt;
|
|
1315
|
+
}
|
|
1279
1316
|
}
|
|
1317
|
+
// 标记该 session 已注入 (参考 oh-my-opencode 的 markApplied)
|
|
1318
|
+
markFirstMessageApplied(sessionID);
|
|
1280
1319
|
}
|
|
1281
1320
|
|
|
1282
|
-
//
|
|
1321
|
+
// 赛博监工压力注入(仅在失败时)
|
|
1283
1322
|
if (config.cyber_supervisor.enabled && config.cyber_supervisor.auto_trigger) {
|
|
1284
1323
|
const failures = getFailureCount(sessionID);
|
|
1285
1324
|
if (failures > 0 && failures <= config.cyber_supervisor.max_retries) {
|
|
@@ -1353,10 +1392,22 @@ ${LEVEL_PROMPTS[level]}
|
|
|
1353
1392
|
return output;
|
|
1354
1393
|
},
|
|
1355
1394
|
|
|
1356
|
-
//
|
|
1395
|
+
// 会话事件处理
|
|
1357
1396
|
event: async (input) => {
|
|
1358
1397
|
const { event } = input;
|
|
1359
1398
|
|
|
1399
|
+
// 新会话创建 - 标记为首条消息待注入 (参考 oh-my-opencode)
|
|
1400
|
+
if (event.type === "session.created") {
|
|
1401
|
+
const props = event.properties as Record<string, unknown> | undefined;
|
|
1402
|
+
const sessionInfo = props?.info as { id?: string; parentID?: string } | undefined;
|
|
1403
|
+
// 只有主会话(非 fork)才注入
|
|
1404
|
+
if (sessionInfo?.id && !sessionInfo.parentID) {
|
|
1405
|
+
markSessionCreated(sessionInfo.id);
|
|
1406
|
+
console.log(`[api-security-testing] New session created: ${sessionInfo.id}`);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// 会话删除或压缩 - 清理状态
|
|
1360
1411
|
if (event.type === "session.deleted" || event.type === "session.compacted") {
|
|
1361
1412
|
const props = event.properties as Record<string, unknown> | undefined;
|
|
1362
1413
|
let sessionID: string | undefined;
|
|
@@ -1368,6 +1419,7 @@ ${LEVEL_PROMPTS[level]}
|
|
|
1368
1419
|
}
|
|
1369
1420
|
|
|
1370
1421
|
if (sessionID) {
|
|
1422
|
+
clearSessionState(sessionID);
|
|
1371
1423
|
resetFailureCount(sessionID);
|
|
1372
1424
|
resetModelFailures(sessionID);
|
|
1373
1425
|
}
|