opencode-api-security-testing 5.4.2 → 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 -4
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -334,9 +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
|
-
//
|
|
337
|
+
// 首条消息追踪 (参考 oh-my-opencode 的 FirstMessageVariantGate 模式)
|
|
338
|
+
const pendingFirstMessages = new Set<string>();
|
|
338
339
|
const injectedSessions = new Set<string>();
|
|
339
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
|
+
|
|
340
371
|
function getConfigPath(ctx: { directory: string }): string {
|
|
341
372
|
return join(ctx.directory, SKILL_DIR, "assets", CONFIG_FILE);
|
|
342
373
|
}
|
|
@@ -1269,11 +1300,24 @@ print(json.dumps(result, ensure_ascii=False))
|
|
|
1269
1300
|
},
|
|
1270
1301
|
|
|
1271
1302
|
// 赛博监工 Hook - chat.message
|
|
1272
|
-
//
|
|
1273
|
-
// 如需使用 agents,请在需要时手动调用相关工具
|
|
1303
|
+
// 参考 oh-my-opencode 的 FirstMessageVariantGate 模式:只在首条消息注入一次
|
|
1274
1304
|
"chat.message": async (input, output) => {
|
|
1275
1305
|
const sessionID = input.sessionID;
|
|
1276
1306
|
|
|
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
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
// 标记该 session 已注入 (参考 oh-my-opencode 的 markApplied)
|
|
1318
|
+
markFirstMessageApplied(sessionID);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1277
1321
|
// 赛博监工压力注入(仅在失败时)
|
|
1278
1322
|
if (config.cyber_supervisor.enabled && config.cyber_supervisor.auto_trigger) {
|
|
1279
1323
|
const failures = getFailureCount(sessionID);
|
|
@@ -1348,10 +1392,22 @@ ${LEVEL_PROMPTS[level]}
|
|
|
1348
1392
|
return output;
|
|
1349
1393
|
},
|
|
1350
1394
|
|
|
1351
|
-
//
|
|
1395
|
+
// 会话事件处理
|
|
1352
1396
|
event: async (input) => {
|
|
1353
1397
|
const { event } = input;
|
|
1354
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
|
+
// 会话删除或压缩 - 清理状态
|
|
1355
1411
|
if (event.type === "session.deleted" || event.type === "session.compacted") {
|
|
1356
1412
|
const props = event.properties as Record<string, unknown> | undefined;
|
|
1357
1413
|
let sessionID: string | undefined;
|
|
@@ -1363,6 +1419,7 @@ ${LEVEL_PROMPTS[level]}
|
|
|
1363
1419
|
}
|
|
1364
1420
|
|
|
1365
1421
|
if (sessionID) {
|
|
1422
|
+
clearSessionState(sessionID);
|
|
1366
1423
|
resetFailureCount(sessionID);
|
|
1367
1424
|
resetModelFailures(sessionID);
|
|
1368
1425
|
}
|