ronds_ai 0.1.31 → 0.1.33
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/lib/ai_usage_events.js +45 -1
- package/lib/analyze_claude.js +8 -0
- package/lib/check_record.js +5 -0
- package/lib/git.js +5 -0
- package/package.json +1 -1
package/lib/ai_usage_events.js
CHANGED
|
@@ -66,6 +66,14 @@ const COMMAND_NAME_PATTERN = /<command-name>\/([a-zA-Z0-9_:-]+)\s*<\/command-nam
|
|
|
66
66
|
/** 原始输入以 slash 命令开头的形式,例如 "/review 检查这个提交"。 */
|
|
67
67
|
const LEADING_COMMAND_PATTERN = /^[\s\uFEFF]*\/([a-zA-Z0-9_:-]+)(?:\s|$)/;
|
|
68
68
|
|
|
69
|
+
// 宿主注入的消息与真人输入共用 UserPromptSubmit 通道,Hook 载荷里没有 mode/origin
|
|
70
|
+
// 之类的判别字段,只能按信封标记识别。标签名后必须跟 ">" 或空白属性:
|
|
71
|
+
// 子 Agent hand-back 的真实写法是 <agent-message from="…">,写成 ^<agent-message> 会漏掉。
|
|
72
|
+
const HARNESS_INJECTED_PATTERN = /^\s*<(task-notification|agent-message|system-reminder)(?:\s[^>]*)?>/i;
|
|
73
|
+
// IDE 上下文是附件序列化出来的前缀(<ide_opened_file>/<ide_selection>),后面紧跟真人原话;
|
|
74
|
+
// 它不产生独立输入项,所以要剥不要丢。
|
|
75
|
+
const IDE_CONTEXT_ENVELOPE_PATTERN = /^\s*<ide_(opened_file|selection)(?:\s[^>]*)?>[\s\S]*?<\/ide_\1>\s*/i;
|
|
76
|
+
|
|
69
77
|
// ---------------------------------------------------------------------------
|
|
70
78
|
// 目录解析(允许测试注入隔离目录)
|
|
71
79
|
// ---------------------------------------------------------------------------
|
|
@@ -153,6 +161,31 @@ function extractPromptSkillNames(prompt) {
|
|
|
153
161
|
return names;
|
|
154
162
|
}
|
|
155
163
|
|
|
164
|
+
/**
|
|
165
|
+
* 判断本轮输入是否为宿主注入的消息(后台任务完成通知、子 Agent hand-back、系统提醒)。
|
|
166
|
+
*
|
|
167
|
+
* 这类内容不是用户提交的 Prompt,必须整条丢弃:它既会污染逐轮正文,也会把
|
|
168
|
+
* 会话轮数抬高(Claude Code 的 UserPromptSubmit 对它们同样会触发)。
|
|
169
|
+
*/
|
|
170
|
+
function isHarnessInjectedPrompt(text) {
|
|
171
|
+
return HARNESS_INJECTED_PATTERN.test(String(text || ''));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 剥掉 IDE 上下文信封,保留其后的真人原话;可处理连续多个信封。
|
|
176
|
+
*
|
|
177
|
+
* 返回空串表示剥完没有剩余正文,调用方应按「无有效 Prompt」处理。
|
|
178
|
+
*/
|
|
179
|
+
function stripHarnessEnvelope(text) {
|
|
180
|
+
let result = String(text ?? '');
|
|
181
|
+
let previous;
|
|
182
|
+
do {
|
|
183
|
+
previous = result;
|
|
184
|
+
result = result.replace(IDE_CONTEXT_ENVELOPE_PATTERN, '');
|
|
185
|
+
} while (result !== previous);
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
156
189
|
// ---------------------------------------------------------------------------
|
|
157
190
|
// 会话上下文缓存
|
|
158
191
|
// ---------------------------------------------------------------------------
|
|
@@ -377,6 +410,10 @@ function buildCommonFields({ source, sessionId, occurredAt, context }) {
|
|
|
377
410
|
* 由 UserPromptSubmit 载荷构造 Prompt 事件,以及用户直敲 slash 触发的 Skill 事件。
|
|
378
411
|
* 返回事件数组;载荷缺少会话或 Prompt 时返回空数组。
|
|
379
412
|
*
|
|
413
|
+
* 过滤规则(Claude Code 的 UserPromptSubmit 对宿主注入同样会触发):
|
|
414
|
+
* - 宿主注入消息整条丢弃,不占轮次;
|
|
415
|
+
* - IDE 上下文信封只剥前缀,保留其后的真人原话。
|
|
416
|
+
*
|
|
380
417
|
* event_id 由「会话 + 轮次 + 完整内容」推导:同一会话中两次内容相同的提问会落到不同轮次,
|
|
381
418
|
* 因此分别计数;而同一轮的重放(见 advancePromptTurn)沿用同一轮次,仍然幂等。
|
|
382
419
|
*/
|
|
@@ -385,9 +422,14 @@ function buildPromptEvents(payload, config, options = {}) {
|
|
|
385
422
|
const sessionId = resolveSessionId(payload);
|
|
386
423
|
const rawPrompt = typeof payload?.prompt === 'string' ? payload.prompt : '';
|
|
387
424
|
if (!sessionId || !rawPrompt.trim()) return [];
|
|
425
|
+
// 入口(analyze_claude)已丢弃宿主注入,这里再挡一次,
|
|
426
|
+
// 保证任何直接调用方都不会把非人类输入落成一轮 Prompt。
|
|
427
|
+
if (isHarnessInjectedPrompt(rawPrompt)) return [];
|
|
388
428
|
|
|
389
429
|
const projectDir = resolveProjectDir(payload);
|
|
390
|
-
|
|
430
|
+
// IDE 上下文只是前缀,剥掉后内容为空说明这一轮没有有效 Prompt。
|
|
431
|
+
const content = stripHarnessEnvelope(rawPrompt);
|
|
432
|
+
if (!content.trim()) return [];
|
|
391
433
|
const contentHash = buildEventId(source, sessionId, 'prompt_content', content);
|
|
392
434
|
const now = Number(options.now || Date.now());
|
|
393
435
|
const context = resolveSessionContext({ options, source, sessionId, projectDir, config });
|
|
@@ -776,6 +818,7 @@ module.exports = {
|
|
|
776
818
|
extractPromptSkillNames,
|
|
777
819
|
extractSkillCommandNames,
|
|
778
820
|
flushEventQueue,
|
|
821
|
+
isHarnessInjectedPrompt,
|
|
779
822
|
postEvents,
|
|
780
823
|
readPendingEventFiles,
|
|
781
824
|
resolveEventsDir,
|
|
@@ -784,4 +827,5 @@ module.exports = {
|
|
|
784
827
|
resolveSessionId,
|
|
785
828
|
runFlushWithLock,
|
|
786
829
|
spawnEventFlusher,
|
|
830
|
+
stripHarnessEnvelope,
|
|
787
831
|
};
|
package/lib/analyze_claude.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 路由规则:
|
|
7
7
|
* - UserPromptSubmit:采集本轮用户 Prompt,并识别 slash 触发的 Skill;
|
|
8
|
+
* 宿主注入的消息(后台任务通知、子 Agent hand-back、系统提醒)整条丢弃,
|
|
9
|
+
* IDE 上下文信封(ide_opened_file / ide_selection)只剥前缀、保留真人原话;
|
|
8
10
|
* - PostToolUse(Skill):采集模型主动调用的 Skill;
|
|
9
11
|
* - Stop / SessionEnd:历史版本(schema v5 及以前)部署的会话快照事件,静默忽略,不再扫描 transcript。
|
|
10
12
|
*/
|
|
@@ -14,6 +16,7 @@ const {
|
|
|
14
16
|
buildPromptEvents,
|
|
15
17
|
buildSkillToolEvent,
|
|
16
18
|
enqueueEvents,
|
|
19
|
+
isHarnessInjectedPrompt,
|
|
17
20
|
resolveEventsDir,
|
|
18
21
|
spawnEventFlusher,
|
|
19
22
|
} = require('./ai_usage_events');
|
|
@@ -76,6 +79,11 @@ function extractHookContextDetailed(payload) {
|
|
|
76
79
|
if (typeof payload.prompt !== 'string' || !payload.prompt.trim()) {
|
|
77
80
|
return { context: null, reasonCode: 'PAYLOAD_MISSING_PROMPT' };
|
|
78
81
|
}
|
|
82
|
+
// Claude Code 的 UserPromptSubmit 对宿主注入的消息同样会触发,载荷里没有
|
|
83
|
+
// mode/origin 字段可判别,只能按信封丢弃后台任务通知、子 Agent hand-back 与系统提醒。
|
|
84
|
+
if (isHarnessInjectedPrompt(payload.prompt)) {
|
|
85
|
+
return { context: null, reasonCode: 'HARNESS_INJECTED_PROMPT' };
|
|
86
|
+
}
|
|
79
87
|
return { context: { event, kind: 'prompt', sessionId, projectDir: resolveProjectDir(payload) }, reasonCode: '' };
|
|
80
88
|
}
|
|
81
89
|
|
package/lib/check_record.js
CHANGED
|
@@ -51,6 +51,9 @@ function readWorkerIdFromWindowsUserEnv() {
|
|
|
51
51
|
return execFileSync('powershell', ['-NoProfile', '-Command', `[Environment]::GetEnvironmentVariable('${ENVIRONMENT_VARIABLE_NAME}', 'User')`], {
|
|
52
52
|
encoding: 'utf8',
|
|
53
53
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
54
|
+
// 与 runGit 同理:powershell 是控制台程序,无控制台父进程下会被
|
|
55
|
+
// Windows 新建控制台并唤起 Windows Terminal 弹 tab,这里显式隐藏。
|
|
56
|
+
windowsHide: true,
|
|
54
57
|
}).trim();
|
|
55
58
|
} catch {
|
|
56
59
|
return '';
|
|
@@ -133,6 +136,8 @@ function upsertProfileWorkerId(value) {
|
|
|
133
136
|
function setWindowsUserWorkerId(value) {
|
|
134
137
|
execFileSync('setx', [ENVIRONMENT_VARIABLE_NAME, String(value)], {
|
|
135
138
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
139
|
+
// setx 是控制台程序,与 runGit / powershell 同理显式隐藏控制台窗口。
|
|
140
|
+
windowsHide: true,
|
|
136
141
|
});
|
|
137
142
|
|
|
138
143
|
return {
|
package/lib/git.js
CHANGED
|
@@ -6,6 +6,11 @@ function runGit(repoRoot, args, required = true) {
|
|
|
6
6
|
cwd: repoRoot,
|
|
7
7
|
encoding: 'utf8',
|
|
8
8
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
9
|
+
// git 是控制台程序:detached 后台子进程(events flush / hooks auto-sync)
|
|
10
|
+
// 没有可继承的控制台,Windows 会为它新建控制台并被默认终端
|
|
11
|
+
// (Windows Terminal)接管,弹出 D:\Git\cmd\git.exe 新 tab 后立即关闭。
|
|
12
|
+
// windowsHide: true 让 Windows 以隐藏窗口方式分配控制台,不再弹终端。
|
|
13
|
+
windowsHide: true,
|
|
9
14
|
}).trim();
|
|
10
15
|
} catch (error) {
|
|
11
16
|
if (!required) {
|