oh-my-knowledge 0.47.0 → 0.49.0
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/README.md +50 -18
- package/README.zh.md +55 -23
- package/dist/analysis/coverage-analyzer.d.ts +1 -0
- package/dist/analysis/coverage-analyzer.js +125 -62
- package/dist/analysis/failure-clusterer.js +2 -1
- package/dist/analysis/gap-analyzer.d.ts +2 -2
- package/dist/analysis/gap-analyzer.js +13 -3
- package/dist/analysis/hedging-classifier.d.ts +2 -2
- package/dist/analysis/hedging-classifier.js +3 -4
- package/dist/analysis/report-diagnostics.js +9 -7
- package/dist/analysis/sample-diagnostics.js +6 -6
- package/dist/artifact-graph/doctor.js +15 -7
- package/dist/assets/agent-skills/omk/SKILL.md +27 -7
- package/dist/assets/agent-skills/omk/references/commands.md +19 -17
- package/dist/authoring/evolver.d.ts +10 -6
- package/dist/authoring/evolver.js +496 -83
- package/dist/authoring/generator.d.ts +3 -3
- package/dist/authoring/generator.js +5 -10
- package/dist/authoring/sample-fixer.d.ts +8 -6
- package/dist/authoring/sample-fixer.js +76 -5
- package/dist/cli/commands/doctor.js +31 -14
- package/dist/cli/commands/eval/index.d.ts +3 -0
- package/dist/cli/commands/eval/index.js +163 -17
- package/dist/cli/commands/evolve.d.ts +4 -4
- package/dist/cli/commands/evolve.js +27 -13
- package/dist/cli/commands/init.js +16 -3
- package/dist/cli/commands/observe/inbox.js +44 -21
- package/dist/cli/commands/observe/index.js +20 -11
- package/dist/cli/commands/observe/ingest.d.ts +3 -0
- package/dist/cli/commands/observe/ingest.js +35 -4
- package/dist/cli/commands/sample.d.ts +9 -3
- package/dist/cli/commands/sample.js +91 -74
- package/dist/cli/lib/cmd-flags.d.ts +1 -0
- package/dist/cli/lib/codex-model-hint.d.ts +9 -0
- package/dist/cli/lib/codex-model-hint.js +45 -0
- package/dist/cli/lib/generation-failure-hint.d.ts +2 -0
- package/dist/cli/lib/generation-failure-hint.js +61 -0
- package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/common.js +4 -0
- package/dist/cli/lib/i18n-dict/gen.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/gen.js +38 -6
- package/dist/cli/lib/i18n-dict/help.js +6 -6
- package/dist/cli/lib/i18n-dict/init.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/init.js +13 -9
- package/dist/cli/lib/i18n-dict/run.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/run.js +34 -2
- package/dist/cli/lib/llm-failure-classifier.d.ts +2 -0
- package/dist/cli/lib/llm-failure-classifier.js +8 -0
- package/dist/cli/lib/parse-run-config.d.ts +6 -5
- package/dist/cli/lib/parse-run-config.js +16 -9
- package/dist/cli/lib/runtime-defaults.d.ts +21 -0
- package/dist/cli/lib/runtime-defaults.js +79 -0
- package/dist/diagnosis/observe-mapper.js +14 -15
- package/dist/diagnosis/observe-producer.js +3 -1
- package/dist/diagnosis/studio-projection.js +14 -7
- package/dist/diagnosis/types.d.ts +2 -0
- package/dist/diagnosis/types.js +12 -0
- package/dist/doctor/endpoint-rule.js +2 -1
- package/dist/eval-core/artifact-file-names.js +18 -1
- package/dist/eval-core/artifact-index.d.ts +7 -11
- package/dist/eval-core/artifact-index.js +139 -80
- package/dist/eval-core/cache.d.ts +12 -3
- package/dist/eval-core/cache.js +89 -29
- package/dist/eval-core/comparability.js +10 -6
- package/dist/eval-core/evaluation-execution.d.ts +2 -1
- package/dist/eval-core/evaluation-execution.js +122 -37
- package/dist/eval-core/evaluation-job.d.ts +4 -1
- package/dist/eval-core/evaluation-job.js +4 -1
- package/dist/eval-core/evaluation-reporting.d.ts +15 -13
- package/dist/eval-core/evaluation-reporting.js +54 -52
- package/dist/eval-core/execution-strategy.d.ts +2 -0
- package/dist/eval-core/execution-strategy.js +11 -9
- package/dist/eval-core/fact-checker.js +15 -7
- package/dist/eval-core/holdout.js +3 -2
- package/dist/eval-core/judge-independence.d.ts +2 -2
- package/dist/eval-core/mock-hook.cjs +23 -6
- package/dist/eval-core/mocks-runtime.js +30 -8
- package/dist/eval-core/report-document.d.ts +12 -0
- package/dist/eval-core/report-document.js +1151 -0
- package/dist/eval-core/report-extensions.d.ts +4 -0
- package/dist/eval-core/report-extensions.js +500 -0
- package/dist/eval-core/report-file-migration.js +7 -2
- package/dist/eval-core/resume-compatibility.d.ts +31 -0
- package/dist/eval-core/resume-compatibility.js +141 -0
- package/dist/eval-core/sample-fingerprint.d.ts +12 -0
- package/dist/eval-core/sample-fingerprint.js +193 -0
- package/dist/eval-core/schema.js +86 -31
- package/dist/eval-core/verdict.d.ts +8 -4
- package/dist/eval-core/verdict.js +24 -10
- package/dist/eval-workflows/batch-evaluation-workflow.d.ts +2 -1
- package/dist/eval-workflows/batch-evaluation-workflow.js +25 -12
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.d.ts +10 -5
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +58 -21
- package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +3 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +4 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.js +4 -1
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.d.ts +6 -5
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.js +17 -10
- package/dist/eval-workflows/evaluation-pipeline.js +12 -7
- package/dist/eval-workflows/run-evaluation.d.ts +9 -7
- package/dist/eval-workflows/run-evaluation.js +79 -51
- package/dist/executors/anthropic-api.js +65 -9
- package/dist/executors/claude-cli.js +16 -79
- package/dist/executors/claude-protocol.d.ts +28 -0
- package/dist/executors/claude-protocol.js +180 -0
- package/dist/executors/claude-sdk-trace.js +56 -28
- package/dist/executors/claude-sdk.d.ts +1 -0
- package/dist/executors/claude-sdk.js +39 -93
- package/dist/executors/codex-cli-trace.js +166 -31
- package/dist/executors/codex-cli.d.ts +6 -8
- package/dist/executors/codex-cli.js +49 -151
- package/dist/executors/codex-protocol.d.ts +24 -0
- package/dist/executors/codex-protocol.js +234 -0
- package/dist/executors/codex-sdk.js +68 -120
- package/dist/executors/gemini.js +88 -13
- package/dist/executors/index.d.ts +2 -3
- package/dist/executors/index.js +5 -3
- package/dist/executors/openai-api.js +70 -9
- package/dist/executors/runtime-fingerprint.js +88 -11
- package/dist/executors/script-command.d.ts +8 -0
- package/dist/executors/script-command.js +87 -0
- package/dist/executors/script.js +202 -29
- package/dist/executors/shared.d.ts +35 -3
- package/dist/executors/shared.js +113 -15
- package/dist/grading/assertions.d.ts +1 -1
- package/dist/grading/assertions.js +19 -9
- package/dist/grading/diagnostic.d.ts +9 -2
- package/dist/grading/diagnostic.js +25 -2
- package/dist/grading/index.js +10 -4
- package/dist/grading/judge.js +19 -6
- package/dist/grading/layered-scores.d.ts +2 -3
- package/dist/grading/layered-scores.js +2 -3
- package/dist/inputs/load-samples.d.ts +1 -2
- package/dist/inputs/load-samples.js +23 -1
- package/dist/inputs/mcp-resolver.js +6 -3
- package/dist/inputs/sample-document.d.ts +11 -0
- package/dist/inputs/sample-document.js +96 -0
- package/dist/managed/evidence.d.ts +1 -0
- package/dist/managed/evidence.js +1 -1
- package/dist/managed/store.js +200 -91
- package/dist/observability/codex-trace-adapter.d.ts +5 -0
- package/dist/observability/codex-trace-adapter.js +850 -0
- package/dist/observability/experience.d.ts +32 -6
- package/dist/observability/experience.js +2695 -459
- package/dist/observability/feedback-matchers.js +16 -1
- package/dist/observability/inbox-view-model.d.ts +2 -1
- package/dist/observability/inbox-view-model.js +20 -14
- package/dist/observability/inbox.d.ts +7 -1
- package/dist/observability/inbox.js +632 -124
- package/dist/observability/problem-patterns.js +2 -0
- package/dist/observability/review-state.d.ts +6 -0
- package/dist/observability/review-state.js +235 -63
- package/dist/observability/skill-chain-advisories.js +1 -1
- package/dist/observability/skill-chain.js +17 -4
- package/dist/observability/skill-health-analyzer.d.ts +32 -7
- package/dist/observability/skill-health-analyzer.js +194 -121
- package/dist/observability/skill-health-report.d.ts +10 -0
- package/dist/observability/skill-health-report.js +620 -0
- package/dist/observability/soft-standards/constants.d.ts +0 -1
- package/dist/observability/soft-standards/constants.js +0 -1
- package/dist/observability/soft-standards/index.d.ts +1 -1
- package/dist/observability/soft-standards/index.js +1 -1
- package/dist/observability/soft-standards/llm-extractor.js +8 -10
- package/dist/observability/soft-standards/skill-standards-store.d.ts +2 -1
- package/dist/observability/soft-standards/skill-standards-store.js +59 -18
- package/dist/observability/soft-standards/types.d.ts +2 -2
- package/dist/observability/trace-adapter.d.ts +12 -7
- package/dist/observability/trace-adapter.js +11 -9
- package/dist/observability/trace-attribution.d.ts +13 -5
- package/dist/observability/trace-attribution.js +315 -21
- package/dist/observability/trace-ingestion.d.ts +9 -0
- package/dist/observability/trace-ingestion.js +80 -0
- package/dist/observability/trace-ir.d.ts +113 -0
- package/dist/observability/trace-ir.js +87 -0
- package/dist/observability/trace-segmenter.d.ts +19 -6
- package/dist/observability/trace-segmenter.js +377 -196
- package/dist/observability/trace-session-index.d.ts +19 -0
- package/dist/observability/trace-session-index.js +68 -0
- package/dist/observability/trace-source.d.ts +12 -4
- package/dist/observability/trace-source.js +939 -215
- package/dist/renderer/html-renderer.js +37 -6
- package/dist/renderer/icons.js +3 -0
- package/dist/renderer/observation-inbox-renderer.js +227 -91
- package/dist/renderer/skill-detail-renderer.js +452 -109
- package/dist/renderer/skill-health-renderer.js +69 -12
- package/dist/renderer/summary.js +28 -7
- package/dist/renderer/table.js +21 -4
- package/dist/renderer/test-view.d.ts +1 -0
- package/dist/renderer/test-view.js +44 -9
- package/dist/server/indexed-report-store.js +14 -18
- package/dist/server/job-store.js +64 -26
- package/dist/server/report-server.js +190 -78
- package/dist/server/report-store.js +57 -80
- package/dist/server/skill-index.js +143 -49
- package/dist/server/skill-insights.js +44 -5
- package/dist/shared/artifact-graph.d.ts +3 -0
- package/dist/shared/artifact-graph.js +224 -0
- package/dist/shared/assertion-types.d.ts +8 -0
- package/dist/shared/assertion-types.js +46 -0
- package/dist/shared/atomic-json.d.ts +8 -0
- package/dist/shared/atomic-json.js +33 -0
- package/dist/shared/diagnosis-schema.d.ts +9 -0
- package/dist/shared/diagnosis-schema.js +181 -0
- package/dist/shared/doctor-report.d.ts +3 -0
- package/dist/shared/doctor-report.js +103 -0
- package/dist/shared/evaluation-job.d.ts +6 -0
- package/dist/shared/evaluation-job.js +217 -0
- package/dist/shared/executor-result.d.ts +17 -0
- package/dist/shared/executor-result.js +221 -0
- package/dist/shared/file-lock.d.ts +12 -0
- package/dist/shared/file-lock.js +129 -0
- package/dist/shared/json-value.d.ts +5 -0
- package/dist/shared/json-value.js +36 -0
- package/dist/shared/keyed-mutex.d.ts +7 -0
- package/dist/shared/keyed-mutex.js +24 -0
- package/dist/shared/record-count.d.ts +8 -0
- package/dist/shared/record-count.js +43 -0
- package/dist/shared/sample-contract.d.ts +3 -0
- package/dist/shared/sample-contract.js +332 -0
- package/dist/shared/shell-quote.d.ts +2 -0
- package/dist/shared/shell-quote.js +7 -0
- package/dist/shared/timestamp.d.ts +6 -0
- package/dist/shared/timestamp.js +64 -0
- package/dist/shared/token-usage.d.ts +19 -0
- package/dist/shared/token-usage.js +50 -0
- package/dist/shared/tool-call-status.d.ts +8 -0
- package/dist/shared/tool-call-status.js +28 -0
- package/dist/shared/tool-identity.d.ts +21 -0
- package/dist/shared/tool-identity.js +84 -0
- package/dist/shared/tool-search.js +73 -16
- package/dist/shared/trace-projection.d.ts +5 -0
- package/dist/shared/trace-projection.js +20 -0
- package/dist/shared/trace-source-kind.d.ts +3 -0
- package/dist/shared/trace-source-kind.js +12 -0
- package/dist/types/diagnosis.d.ts +2 -0
- package/dist/types/eval.d.ts +4 -0
- package/dist/types/executor.d.ts +32 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/judge.d.ts +2 -0
- package/dist/types/observability.d.ts +116 -9
- package/dist/types/report.d.ts +58 -6
- package/dist/types/skill-index.d.ts +7 -0
- package/dist/types/trace.d.ts +2 -0
- package/dist/types/trace.js +1 -0
- package/package.json +9 -5
|
@@ -21,16 +21,17 @@ const BUSINESS_ACTION_CMD_RE = /<[a-z][\w.-]*-cmd\b[^>]*\bname=["']([^"']+)["'][
|
|
|
21
21
|
const COMMAND_ENVELOPE_RE = /<command-(?:name|message)>[\s\S]*?<\/command-(?:name|message)>/g;
|
|
22
22
|
// cc 内置 CLI 命令(不是 skill)。dogfood 数据中这些词频繁以 <command-name> 出现,
|
|
23
23
|
// 必须过滤掉才能得到真实 skill 分布。列表基于实测 + cc 常规命令集。
|
|
24
|
-
const
|
|
24
|
+
const CLAUDE_BUILTIN_COMMANDS = new Set([
|
|
25
25
|
'clear', 'exit', 'quit', 'help', 'fast', 'effort', 'model',
|
|
26
26
|
'plugin', 'stats', 'doctor', 'compact', 'cost', 'agents', 'init',
|
|
27
27
|
'config', 'permissions', 'resume', 'continue', 'memory',
|
|
28
28
|
]);
|
|
29
|
+
const UNSAFE_SKILL_NAMES = new Set(['__proto__', 'constructor', 'prototype']);
|
|
30
|
+
const SKILL_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/;
|
|
29
31
|
/**
|
|
30
|
-
* 归一化 skill
|
|
32
|
+
* 归一化 skill 名:去掉 plugin 前缀并校验稳定身份。
|
|
31
33
|
* - "impeccable:audit" → "audit"
|
|
32
34
|
* - "pbakaus/impeccable:audit" → "audit"
|
|
33
|
-
* - "clear" / "exit" 等 → null(表示不是 skill)
|
|
34
35
|
*/
|
|
35
36
|
export function normalizeSkillName(raw) {
|
|
36
37
|
return parseSkillRef(raw)?.skillName ?? null;
|
|
@@ -41,10 +42,12 @@ export function parseSkillRef(raw) {
|
|
|
41
42
|
return null;
|
|
42
43
|
// plugin-prefixed: pbakaus/impeccable:audit / impeccable:audit → 取最后一段
|
|
43
44
|
const colonIdx = trimmed.lastIndexOf(':');
|
|
44
|
-
const name = colonIdx >= 0 ? trimmed.slice(colonIdx + 1) : trimmed;
|
|
45
|
-
if (
|
|
45
|
+
const name = (colonIdx >= 0 ? trimmed.slice(colonIdx + 1) : trimmed).trim();
|
|
46
|
+
if (!SKILL_NAME_RE.test(name)
|
|
47
|
+
|| UNSAFE_SKILL_NAMES.has(name.toLowerCase()))
|
|
46
48
|
return null;
|
|
47
|
-
const
|
|
49
|
+
const rawPluginName = colonIdx >= 0 ? trimmed.slice(0, colonIdx).trim() : undefined;
|
|
50
|
+
const pluginName = rawPluginName && rawPluginName.length <= 256 ? rawPluginName : undefined;
|
|
48
51
|
return {
|
|
49
52
|
skillName: name,
|
|
50
53
|
rawSkillRef: trimmed,
|
|
@@ -83,7 +86,11 @@ export function extractCommandSkillRef(record) {
|
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
|
-
return raw ? parseSkillRef(raw) : null;
|
|
89
|
+
return raw && !isClaudeBuiltinCommand(raw) ? parseSkillRef(raw) : null;
|
|
90
|
+
}
|
|
91
|
+
export function isClaudeBuiltinCommand(raw) {
|
|
92
|
+
const trimmed = raw.trim();
|
|
93
|
+
return !trimmed.includes(':') && CLAUDE_BUILTIN_COMMANDS.has(trimmed.toLowerCase());
|
|
87
94
|
}
|
|
88
95
|
/**
|
|
89
96
|
* 从 OpenClaw user message 的业务动作标签里提取 skill 名字(信号 4)。
|
|
@@ -140,13 +147,207 @@ export function extractAttributionSkill(record) {
|
|
|
140
147
|
export function extractAttributionSkillRef(record) {
|
|
141
148
|
return record.attributionSkill ? parseSkillRef(record.attributionSkill) : null;
|
|
142
149
|
}
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
// Only installed runtime roots count as attribution evidence. Repository examples
|
|
151
|
+
// such as `examples/foo/skills/bar/SKILL.md` are knowledge artifacts under review,
|
|
152
|
+
// not proof that the `bar` skill was active in the session.
|
|
153
|
+
const SKILL_READ_PATH_RE = /(?:^|[\s"'`(\[{:,])([^\s"'`]*\/skills\/(?:\.system\/)?[^/\s"'`]+\/SKILL\.md)\b/g;
|
|
154
|
+
const INSTALLED_SKILL_PATH_RES = [
|
|
155
|
+
/(?:^|\/)\.(?:agents|claude|codex)\/skills\/([^/]+)\/SKILL\.md$/,
|
|
156
|
+
/(?:^|\/)\.codex\/skills\/\.system\/([^/]+)\/SKILL\.md$/,
|
|
157
|
+
/(?:^|\/)\.codex\/plugins\/cache\/.+\/skills\/([^/]+)\/SKILL\.md$/,
|
|
158
|
+
/(?:^|\/)\.openclaw\/workspace(?:-main)?\/skills\/([^/]+)\/SKILL\.md$/,
|
|
159
|
+
];
|
|
160
|
+
const SKILL_SCRIPT_PATH_RE = /(?:^|[\s"'`(\[{:,])(?:~|\.|\/)?[^\s"'`]*\/skills\/(?:\.system\/)?([^/\s"'`]+)\/scripts\/[^\s"'`]*/;
|
|
161
|
+
export function isInstalledSkillAssetPath(value, skillName) {
|
|
162
|
+
if (!value || !skillName)
|
|
163
|
+
return false;
|
|
164
|
+
const normalized = value.replaceAll('\\', '/');
|
|
165
|
+
const segments = normalized.split('/');
|
|
166
|
+
for (let index = 0; index < segments.length; index++) {
|
|
167
|
+
if (segments[index] !== 'skills')
|
|
168
|
+
continue;
|
|
169
|
+
const nameIndex = segments[index + 1] === '.system' ? index + 2 : index + 1;
|
|
170
|
+
if (segments[nameIndex] !== skillName || nameIndex + 1 >= segments.length)
|
|
171
|
+
continue;
|
|
172
|
+
const installationPrefix = segments.slice(0, index);
|
|
173
|
+
if (installationPrefix.some((segment) => segment === '.agents'
|
|
174
|
+
|| segment === '.claude'
|
|
175
|
+
|| segment === '.codex'
|
|
176
|
+
|| segment === '.openclaw'))
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
function extractInstalledSkillReadRef(text) {
|
|
182
|
+
const normalized = text.replaceAll('\\', '/');
|
|
183
|
+
for (const match of normalized.matchAll(SKILL_READ_PATH_RE)) {
|
|
184
|
+
const path = match[1];
|
|
185
|
+
if (!path)
|
|
186
|
+
continue;
|
|
187
|
+
for (const pattern of INSTALLED_SKILL_PATH_RES) {
|
|
188
|
+
const installed = path.match(pattern);
|
|
189
|
+
if (installed?.[1])
|
|
190
|
+
return parseSkillRef(installed[1]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
function skipJsString(source, start) {
|
|
196
|
+
const quote = source[start];
|
|
197
|
+
let index = start + 1;
|
|
198
|
+
while (index < source.length) {
|
|
199
|
+
if (source[index] === '\\') {
|
|
200
|
+
index += 2;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (source[index] === quote)
|
|
204
|
+
return index + 1;
|
|
205
|
+
index += 1;
|
|
206
|
+
}
|
|
207
|
+
return source.length;
|
|
208
|
+
}
|
|
209
|
+
function skipJsTrivia(source, start) {
|
|
210
|
+
let index = start;
|
|
211
|
+
while (index < source.length) {
|
|
212
|
+
if (/\s/.test(source[index])) {
|
|
213
|
+
index += 1;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (source.startsWith('//', index)) {
|
|
217
|
+
const newline = source.indexOf('\n', index + 2);
|
|
218
|
+
return newline < 0 ? source.length : skipJsTrivia(source, newline + 1);
|
|
219
|
+
}
|
|
220
|
+
if (source.startsWith('/*', index)) {
|
|
221
|
+
const end = source.indexOf('*/', index + 2);
|
|
222
|
+
return end < 0 ? source.length : skipJsTrivia(source, end + 2);
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
return index;
|
|
227
|
+
}
|
|
228
|
+
function extractExecCommandLiteral(source, callStart) {
|
|
229
|
+
let index = skipJsTrivia(source, callStart + 'tools.exec_command'.length);
|
|
230
|
+
if (source[index] !== '(')
|
|
231
|
+
return null;
|
|
232
|
+
index = skipJsTrivia(source, index + 1);
|
|
233
|
+
if (source[index] !== '{')
|
|
234
|
+
return null;
|
|
235
|
+
let depth = 1;
|
|
236
|
+
index += 1;
|
|
237
|
+
while (index < source.length && depth > 0) {
|
|
238
|
+
index = skipJsTrivia(source, index);
|
|
239
|
+
const char = source[index];
|
|
240
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
241
|
+
index = skipJsString(source, index);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (char === '{') {
|
|
245
|
+
depth += 1;
|
|
246
|
+
index += 1;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (char === '}') {
|
|
250
|
+
depth -= 1;
|
|
251
|
+
index += 1;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (depth === 1
|
|
255
|
+
&& source.startsWith('cmd', index)
|
|
256
|
+
&& !/[\w$]/.test(source[index - 1] ?? '')
|
|
257
|
+
&& !/[\w$]/.test(source[index + 3] ?? '')) {
|
|
258
|
+
let valueStart = skipJsTrivia(source, index + 3);
|
|
259
|
+
if (source[valueStart] !== ':') {
|
|
260
|
+
index += 3;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
valueStart = skipJsTrivia(source, valueStart + 1);
|
|
264
|
+
const quote = source[valueStart];
|
|
265
|
+
if (quote !== '"' && quote !== "'" && quote !== '`')
|
|
266
|
+
return null;
|
|
267
|
+
const end = skipJsString(source, valueStart);
|
|
268
|
+
return source.slice(valueStart + 1, Math.max(valueStart + 1, end - 1));
|
|
269
|
+
}
|
|
270
|
+
index += 1;
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
function extractExecCommandLiterals(source) {
|
|
275
|
+
const commands = [];
|
|
276
|
+
let index = 0;
|
|
277
|
+
while (index < source.length) {
|
|
278
|
+
index = skipJsTrivia(source, index);
|
|
279
|
+
const char = source[index];
|
|
280
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
281
|
+
index = skipJsString(source, index);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (source.startsWith('tools.exec_command', index)
|
|
285
|
+
&& !/[\w$.]/.test(source[index - 1] ?? '')
|
|
286
|
+
&& !/[\w$]/.test(source[index + 'tools.exec_command'.length] ?? '')) {
|
|
287
|
+
const command = extractExecCommandLiteral(source, index);
|
|
288
|
+
if (command)
|
|
289
|
+
commands.push(command);
|
|
290
|
+
index += 'tools.exec_command'.length;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
index += 1;
|
|
294
|
+
}
|
|
295
|
+
return commands;
|
|
296
|
+
}
|
|
297
|
+
function splitShellCommandSegments(command) {
|
|
298
|
+
const segments = [];
|
|
299
|
+
let start = 0;
|
|
300
|
+
let quote = null;
|
|
301
|
+
let index = 0;
|
|
302
|
+
while (index < command.length) {
|
|
303
|
+
const char = command[index];
|
|
304
|
+
if (char === '\\') {
|
|
305
|
+
index += 2;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (quote) {
|
|
309
|
+
if (char === quote)
|
|
310
|
+
quote = null;
|
|
311
|
+
index += 1;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (char === "'" || char === '"') {
|
|
315
|
+
quote = char;
|
|
316
|
+
index += 1;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const separatorLength = command.startsWith('&&', index) || command.startsWith('||', index)
|
|
320
|
+
? 2
|
|
321
|
+
: char === ';' || char === '|' || char === '\n'
|
|
322
|
+
? 1
|
|
323
|
+
: 0;
|
|
324
|
+
if (separatorLength > 0) {
|
|
325
|
+
segments.push(command.slice(start, index));
|
|
326
|
+
index += separatorLength;
|
|
327
|
+
start = index;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
index += 1;
|
|
331
|
+
}
|
|
332
|
+
segments.push(command.slice(start));
|
|
333
|
+
return segments;
|
|
334
|
+
}
|
|
335
|
+
const SHELL_FILE_READER_RE = /^(?:(?:env\s+)?(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S+)\s+)*)?(?:(?:command|sudo)\s+)*(?:[\w./-]+\/)?(?:cat|sed|head|tail|less|more|bat|awk|wc)\b/;
|
|
336
|
+
function extractShellSkillReadRef(command) {
|
|
337
|
+
for (const segment of splitShellCommandSegments(command)) {
|
|
338
|
+
const normalized = segment.trim();
|
|
339
|
+
if (!SHELL_FILE_READER_RE.test(normalized))
|
|
340
|
+
continue;
|
|
341
|
+
const skillRef = extractInstalledSkillReadRef(normalized);
|
|
342
|
+
if (skillRef)
|
|
343
|
+
return skillRef;
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
146
347
|
/**
|
|
147
|
-
* 从 assistant message 的 Read tool_use 里提取 skill 名字(信号 3, fallback)。
|
|
148
|
-
*
|
|
149
|
-
*
|
|
348
|
+
* 从 assistant message 的 Read / Bash tool_use 里提取 skill 名字(信号 3, fallback)。
|
|
349
|
+
* Codex 通常用 shell 读取 `.agents/skills/<name>/SKILL.md`,Claude Code 与
|
|
350
|
+
* OpenClaw 则更常用结构化 Read tool;两种形式统一识别。
|
|
150
351
|
* 返回 null 表示没命中。
|
|
151
352
|
*/
|
|
152
353
|
export function extractSkillReadFile(record) {
|
|
@@ -158,9 +359,27 @@ export function extractSkillReadFileRef(record) {
|
|
|
158
359
|
if (part.type === 'tool_use' && part.name === 'Read') {
|
|
159
360
|
const filePath = part.input?.file_path;
|
|
160
361
|
if (typeof filePath === 'string') {
|
|
161
|
-
const
|
|
162
|
-
if (
|
|
163
|
-
return
|
|
362
|
+
const skillRef = extractInstalledSkillReadRef(filePath);
|
|
363
|
+
if (skillRef)
|
|
364
|
+
return skillRef;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (part.type === 'tool_use'
|
|
368
|
+
&& (part.name === 'Bash'
|
|
369
|
+
|| part.name?.toLowerCase() === 'exec'
|
|
370
|
+
|| part.name?.toLowerCase() === 'js')) {
|
|
371
|
+
const rawCommand = part.name === 'Bash'
|
|
372
|
+
? part.input?.command
|
|
373
|
+
: part.input?.input ?? part.input?.code ?? part.input?.command;
|
|
374
|
+
const commands = typeof rawCommand !== 'string'
|
|
375
|
+
? []
|
|
376
|
+
: part.name === 'Bash'
|
|
377
|
+
? [rawCommand]
|
|
378
|
+
: extractExecCommandLiterals(rawCommand);
|
|
379
|
+
for (const command of commands) {
|
|
380
|
+
const skillRef = extractShellSkillReadRef(command);
|
|
381
|
+
if (skillRef)
|
|
382
|
+
return skillRef;
|
|
164
383
|
}
|
|
165
384
|
}
|
|
166
385
|
}
|
|
@@ -184,16 +403,91 @@ export function extractSkillScriptCommandRef(record) {
|
|
|
184
403
|
texts.push(part.text);
|
|
185
404
|
}
|
|
186
405
|
else if (part.type === 'tool_use') {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
406
|
+
const rawCommand = part.name?.toLowerCase() === 'exec'
|
|
407
|
+
? part.input?.input ?? part.input?.code ?? part.input?.command
|
|
408
|
+
: part.input?.code ?? part.input?.command;
|
|
409
|
+
if (typeof rawCommand === 'string') {
|
|
410
|
+
texts.push(...(part.name?.toLowerCase() === 'exec'
|
|
411
|
+
? extractExecCommandLiterals(rawCommand)
|
|
412
|
+
: [rawCommand]));
|
|
413
|
+
}
|
|
190
414
|
}
|
|
191
415
|
}
|
|
192
416
|
}
|
|
193
417
|
for (const text of texts) {
|
|
194
418
|
const match = SKILL_SCRIPT_PATH_RE.exec(text);
|
|
195
|
-
if (match?.[1])
|
|
196
|
-
|
|
419
|
+
if (match?.[1]) {
|
|
420
|
+
const ref = parseSkillRef(match[1]);
|
|
421
|
+
if (ref && isInstalledSkillAssetPath(match[0], ref.skillName))
|
|
422
|
+
return ref;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
export function extractCommandSkillRefFromEvent(event) {
|
|
428
|
+
const match = COMMAND_NAME_RE.exec(event.text);
|
|
429
|
+
const raw = match?.[1];
|
|
430
|
+
if (!raw)
|
|
431
|
+
return null;
|
|
432
|
+
return parseSkillRef(raw);
|
|
433
|
+
}
|
|
434
|
+
export function extractBusinessActionSkillRefFromEvent(event) {
|
|
435
|
+
const match = BUSINESS_ACTION_CMD_RE.exec(event.text);
|
|
436
|
+
const raw = match?.[1];
|
|
437
|
+
return raw && isStableSkillSlug(raw) ? parseSkillRef(raw) : null;
|
|
438
|
+
}
|
|
439
|
+
export function extractAttributionSkillRefFromEvent(event) {
|
|
440
|
+
return event.attributionSkill ? parseSkillRef(event.attributionSkill) : null;
|
|
441
|
+
}
|
|
442
|
+
export function extractSkillToolUseRefFromEvent(event) {
|
|
443
|
+
if (event.tool.name !== 'Skill')
|
|
444
|
+
return null;
|
|
445
|
+
const skill = event.input.skill;
|
|
446
|
+
return typeof skill === 'string' ? parseSkillRef(skill) : null;
|
|
447
|
+
}
|
|
448
|
+
export function extractSkillReadFileRefFromEvent(event) {
|
|
449
|
+
if (event.tool.name === 'Read') {
|
|
450
|
+
const filePath = event.input.file_path;
|
|
451
|
+
return typeof filePath === 'string' ? extractInstalledSkillReadRef(filePath) : null;
|
|
452
|
+
}
|
|
453
|
+
if (event.tool.name !== 'Bash'
|
|
454
|
+
&& event.tool.name.toLowerCase() !== 'exec'
|
|
455
|
+
&& event.tool.sourceName?.toLowerCase() !== 'exec'
|
|
456
|
+
&& event.tool.name !== 'node_repl.js'
|
|
457
|
+
&& event.tool.name.toLowerCase() !== 'js'
|
|
458
|
+
&& event.tool.sourceName?.toLowerCase() !== 'js')
|
|
459
|
+
return null;
|
|
460
|
+
const rawCommand = event.input.command ?? event.input.input ?? event.input.code;
|
|
461
|
+
if (typeof rawCommand !== 'string')
|
|
462
|
+
return null;
|
|
463
|
+
const sourceName = event.tool.sourceName?.toLowerCase();
|
|
464
|
+
const isOrchestrationWrapper = sourceName === 'exec'
|
|
465
|
+
|| sourceName === 'js'
|
|
466
|
+
|| event.tool.name === 'node_repl.js'
|
|
467
|
+
|| event.tool.name.toLowerCase() === 'js';
|
|
468
|
+
const commands = isOrchestrationWrapper
|
|
469
|
+
? extractExecCommandLiterals(rawCommand)
|
|
470
|
+
: [rawCommand];
|
|
471
|
+
for (const command of commands) {
|
|
472
|
+
const skillRef = extractShellSkillReadRef(command);
|
|
473
|
+
if (skillRef)
|
|
474
|
+
return skillRef;
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
export function extractSkillScriptCommandRefFromEvent(event) {
|
|
479
|
+
const texts = event.eventKind === 'message'
|
|
480
|
+
? [event.text]
|
|
481
|
+
: typeof (event.input.command ?? event.input.input ?? event.input.code) === 'string'
|
|
482
|
+
? [String(event.input.command ?? event.input.input ?? event.input.code)]
|
|
483
|
+
: [];
|
|
484
|
+
for (const text of texts) {
|
|
485
|
+
const match = SKILL_SCRIPT_PATH_RE.exec(text);
|
|
486
|
+
if (match?.[1]) {
|
|
487
|
+
const ref = parseSkillRef(match[1]);
|
|
488
|
+
if (ref && isInstalledSkillAssetPath(match[0], ref.skillName))
|
|
489
|
+
return ref;
|
|
490
|
+
}
|
|
197
491
|
}
|
|
198
492
|
return null;
|
|
199
493
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TraceIngestionSummary } from '../types/index.js';
|
|
2
|
+
export interface TraceIngestionNotice {
|
|
3
|
+
level: 'warning' | 'info';
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function emptyTraceIngestionSummary(fileCount?: number): TraceIngestionSummary;
|
|
7
|
+
export declare function mergeTraceIngestionSummaries(summaries: TraceIngestionSummary[]): TraceIngestionSummary;
|
|
8
|
+
export declare function parseTraceIngestionSummary(value: unknown): TraceIngestionSummary | null;
|
|
9
|
+
export declare function traceIngestionNotices(summary: TraceIngestionSummary | undefined, lang: 'zh' | 'en'): TraceIngestionNotice[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { sumRecordCounts } from '../shared/record-count.js';
|
|
2
|
+
export function emptyTraceIngestionSummary(fileCount = 0) {
|
|
3
|
+
return {
|
|
4
|
+
fileCount,
|
|
5
|
+
sourceRecordCount: 0,
|
|
6
|
+
parsedRecordCount: 0,
|
|
7
|
+
malformedRecordCount: 0,
|
|
8
|
+
ignoredValueCount: 0,
|
|
9
|
+
unknownEventCount: 0,
|
|
10
|
+
filteredSessionCount: 0,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function mergeTraceIngestionSummaries(summaries) {
|
|
14
|
+
return summaries.reduce((total, summary) => ({
|
|
15
|
+
fileCount: sumRecordCounts(total.fileCount, summary.fileCount),
|
|
16
|
+
sourceRecordCount: sumRecordCounts(total.sourceRecordCount, summary.sourceRecordCount),
|
|
17
|
+
parsedRecordCount: sumRecordCounts(total.parsedRecordCount, summary.parsedRecordCount),
|
|
18
|
+
malformedRecordCount: sumRecordCounts(total.malformedRecordCount, summary.malformedRecordCount),
|
|
19
|
+
ignoredValueCount: sumRecordCounts(total.ignoredValueCount, summary.ignoredValueCount),
|
|
20
|
+
unknownEventCount: sumRecordCounts(total.unknownEventCount, summary.unknownEventCount),
|
|
21
|
+
filteredSessionCount: sumRecordCounts(total.filteredSessionCount, summary.filteredSessionCount),
|
|
22
|
+
}), emptyTraceIngestionSummary());
|
|
23
|
+
}
|
|
24
|
+
export function parseTraceIngestionSummary(value) {
|
|
25
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
26
|
+
return null;
|
|
27
|
+
const record = value;
|
|
28
|
+
const keys = [
|
|
29
|
+
'fileCount',
|
|
30
|
+
'sourceRecordCount',
|
|
31
|
+
'parsedRecordCount',
|
|
32
|
+
'malformedRecordCount',
|
|
33
|
+
'ignoredValueCount',
|
|
34
|
+
'unknownEventCount',
|
|
35
|
+
'filteredSessionCount',
|
|
36
|
+
];
|
|
37
|
+
if (!keys.every((key) => isCount(record[key])))
|
|
38
|
+
return null;
|
|
39
|
+
const summary = record;
|
|
40
|
+
let classifiedRecordCount;
|
|
41
|
+
try {
|
|
42
|
+
classifiedRecordCount = sumRecordCounts(summary.parsedRecordCount, summary.malformedRecordCount, summary.ignoredValueCount);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (classifiedRecordCount !== summary.sourceRecordCount
|
|
48
|
+
|| (summary.fileCount === 0 && (summary.sourceRecordCount > 0
|
|
49
|
+
|| summary.unknownEventCount > 0
|
|
50
|
+
|| summary.filteredSessionCount > 0)))
|
|
51
|
+
return null;
|
|
52
|
+
return summary;
|
|
53
|
+
}
|
|
54
|
+
export function traceIngestionNotices(summary, lang) {
|
|
55
|
+
if (!summary)
|
|
56
|
+
return [];
|
|
57
|
+
const notices = [];
|
|
58
|
+
if (summary.malformedRecordCount > 0
|
|
59
|
+
|| summary.ignoredValueCount > 0
|
|
60
|
+
|| summary.unknownEventCount > 0) {
|
|
61
|
+
notices.push({
|
|
62
|
+
level: 'warning',
|
|
63
|
+
text: lang === 'zh'
|
|
64
|
+
? `注意:摄取时发现 ${summary.malformedRecordCount} 条格式损坏记录、${summary.ignoredValueCount} 个非对象值、${summary.unknownEventCount} 个未识别事件;请确认本次观测覆盖是否完整。`
|
|
65
|
+
: `Note: ingestion found ${summary.malformedRecordCount} malformed records, ${summary.ignoredValueCount} non-object values, and ${summary.unknownEventCount} unrecognized events; review whether this observation is complete.`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (summary.filteredSessionCount > 0) {
|
|
69
|
+
notices.push({
|
|
70
|
+
level: 'info',
|
|
71
|
+
text: lang === 'zh'
|
|
72
|
+
? `已过滤 ${summary.filteredSessionCount} 个运行时守护会话,不计入知识载体观测。`
|
|
73
|
+
: `Filtered ${summary.filteredSessionCount} runtime guardian session(s) from knowledge-artifact observation.`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return notices;
|
|
77
|
+
}
|
|
78
|
+
function isCount(value) {
|
|
79
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
80
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ToolCallStatus, ToolCallStatusSource, TraceIngestionSummary, TraceSourceMetadata } from '../types/index.js';
|
|
2
|
+
import type { TraceSourceKind } from '../types/trace.js';
|
|
3
|
+
import type { NormalizedToolIdentity } from '../shared/tool-identity.js';
|
|
4
|
+
export type { TraceSourceKind } from '../types/trace.js';
|
|
5
|
+
export type TraceRole = 'standalone' | 'main' | 'subagent';
|
|
6
|
+
export type TraceMessageRole = 'user' | 'assistant' | 'system';
|
|
7
|
+
export type TraceMessageOrigin = 'human' | 'runtime' | 'skill-context' | 'synthetic';
|
|
8
|
+
export type TraceToolStatus = ToolCallStatus;
|
|
9
|
+
export type TraceToolStatusSource = ToolCallStatusSource;
|
|
10
|
+
/** Source-neutral tool identity, including provider namespaces when present. */
|
|
11
|
+
export type TraceToolRef = NormalizedToolIdentity;
|
|
12
|
+
interface TraceEventBase {
|
|
13
|
+
eventId: string;
|
|
14
|
+
/** Source-native record/message id, when the source exposes one. */
|
|
15
|
+
sourceEventId?: string;
|
|
16
|
+
sourceIndex: number;
|
|
17
|
+
sourceType: string;
|
|
18
|
+
timestamp?: string;
|
|
19
|
+
turnId?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface TraceMessageEvent extends TraceEventBase {
|
|
22
|
+
eventKind: 'message';
|
|
23
|
+
role: TraceMessageRole;
|
|
24
|
+
origin: TraceMessageOrigin;
|
|
25
|
+
text: string;
|
|
26
|
+
model?: string;
|
|
27
|
+
attributionSkill?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface TraceToolCallEvent extends TraceEventBase {
|
|
30
|
+
eventKind: 'tool_call';
|
|
31
|
+
callId: string;
|
|
32
|
+
callInstanceId?: string;
|
|
33
|
+
tool: TraceToolRef;
|
|
34
|
+
input: Record<string, unknown>;
|
|
35
|
+
model?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface TraceToolResultEvent extends TraceEventBase {
|
|
38
|
+
eventKind: 'tool_result';
|
|
39
|
+
callId: string;
|
|
40
|
+
callInstanceId?: string;
|
|
41
|
+
output: string;
|
|
42
|
+
status: TraceToolStatus;
|
|
43
|
+
statusSource: TraceToolStatusSource;
|
|
44
|
+
}
|
|
45
|
+
export interface TraceUsageEvent extends TraceEventBase {
|
|
46
|
+
eventKind: 'usage';
|
|
47
|
+
model?: string;
|
|
48
|
+
inputTokens: number;
|
|
49
|
+
outputTokens: number;
|
|
50
|
+
cacheReadTokens: number;
|
|
51
|
+
cacheCreationTokens: number;
|
|
52
|
+
reasoningTokens?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface TraceLifecycleEvent extends TraceEventBase {
|
|
55
|
+
eventKind: 'lifecycle';
|
|
56
|
+
phase: 'session_started' | 'session_ended' | 'turn_started' | 'turn_completed' | 'turn_aborted' | 'turn_interrupted';
|
|
57
|
+
reason?: string;
|
|
58
|
+
durationMs?: number;
|
|
59
|
+
}
|
|
60
|
+
export interface TraceUnknownEvent extends TraceEventBase {
|
|
61
|
+
eventKind: 'unknown';
|
|
62
|
+
raw?: unknown;
|
|
63
|
+
}
|
|
64
|
+
export type TraceEvent = TraceMessageEvent | TraceToolCallEvent | TraceToolResultEvent | TraceUsageEvent | TraceLifecycleEvent | TraceUnknownEvent;
|
|
65
|
+
export interface TraceSession {
|
|
66
|
+
/** Concrete source run/thread identifier. */
|
|
67
|
+
runId: string;
|
|
68
|
+
/** Root run shared by a main trace and its descendants. */
|
|
69
|
+
rootRunId: string;
|
|
70
|
+
parentRunId?: string;
|
|
71
|
+
/** Unique evidence stream identifier. */
|
|
72
|
+
traceId: string;
|
|
73
|
+
groupPath: string;
|
|
74
|
+
role: TraceRole;
|
|
75
|
+
label: string;
|
|
76
|
+
sourcePath: string;
|
|
77
|
+
sourceKind: TraceSourceKind;
|
|
78
|
+
events: TraceEvent[];
|
|
79
|
+
cwd?: string;
|
|
80
|
+
gitBranch?: string;
|
|
81
|
+
entrypoint?: string;
|
|
82
|
+
sourceMetadata?: TraceSourceMetadata;
|
|
83
|
+
startTimestamp?: string;
|
|
84
|
+
endTimestamp?: string;
|
|
85
|
+
}
|
|
86
|
+
export interface TraceCorpus {
|
|
87
|
+
sessions: TraceSession[];
|
|
88
|
+
ingestion: TraceIngestionSummary;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build a deterministic, transport-safe identity for one physical evidence
|
|
92
|
+
* stream. Source paths remain available as provenance; the identifier itself
|
|
93
|
+
* stays opaque so it can safely cross JSON, HTML, URLs, and review-state keys.
|
|
94
|
+
*/
|
|
95
|
+
export declare function createTraceId(input: {
|
|
96
|
+
sourceKind: TraceSourceKind;
|
|
97
|
+
runId: string;
|
|
98
|
+
sourcePath: string;
|
|
99
|
+
streamId?: string;
|
|
100
|
+
}): string;
|
|
101
|
+
export declare function traceEventTimestamp(event: TraceEvent): string | undefined;
|
|
102
|
+
export declare function normalizeTraceTimestamp(value: unknown): string | undefined;
|
|
103
|
+
export declare function traceTimestampBounds(values: Iterable<unknown>): {
|
|
104
|
+
startTimestamp?: string;
|
|
105
|
+
endTimestamp?: string;
|
|
106
|
+
};
|
|
107
|
+
export declare function traceToolDisplayName(tool: TraceToolRef): string;
|
|
108
|
+
/**
|
|
109
|
+
* Correlate one concrete call occurrence with its result without treating a
|
|
110
|
+
* source-native callId as globally unique. FIFO is the only source-neutral
|
|
111
|
+
* assumption available when a runtime reuses call IDs.
|
|
112
|
+
*/
|
|
113
|
+
export declare function correlateTraceToolEvents(events: TraceEvent[]): TraceEvent[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { normalizeRfc3339Timestamp } from '../shared/timestamp.js';
|
|
5
|
+
/**
|
|
6
|
+
* Build a deterministic, transport-safe identity for one physical evidence
|
|
7
|
+
* stream. Source paths remain available as provenance; the identifier itself
|
|
8
|
+
* stays opaque so it can safely cross JSON, HTML, URLs, and review-state keys.
|
|
9
|
+
*/
|
|
10
|
+
export function createTraceId(input) {
|
|
11
|
+
const stable = [
|
|
12
|
+
input.sourceKind,
|
|
13
|
+
input.runId,
|
|
14
|
+
canonicalTraceSourcePath(input.sourcePath),
|
|
15
|
+
input.streamId ?? '',
|
|
16
|
+
].join('\u0000');
|
|
17
|
+
return `trace:${createHash('sha256').update(stable).digest('hex').slice(0, 32)}`;
|
|
18
|
+
}
|
|
19
|
+
function canonicalTraceSourcePath(sourcePath) {
|
|
20
|
+
try {
|
|
21
|
+
return realpathSync(sourcePath);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return resolve(sourcePath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function traceEventTimestamp(event) {
|
|
28
|
+
return event.timestamp;
|
|
29
|
+
}
|
|
30
|
+
export function normalizeTraceTimestamp(value) {
|
|
31
|
+
return normalizeRfc3339Timestamp(value);
|
|
32
|
+
}
|
|
33
|
+
export function traceTimestampBounds(values) {
|
|
34
|
+
let startTimestamp;
|
|
35
|
+
let endTimestamp;
|
|
36
|
+
for (const value of values) {
|
|
37
|
+
const timestamp = normalizeTraceTimestamp(value);
|
|
38
|
+
if (!timestamp)
|
|
39
|
+
continue;
|
|
40
|
+
if (!startTimestamp || timestamp < startTimestamp)
|
|
41
|
+
startTimestamp = timestamp;
|
|
42
|
+
if (!endTimestamp || timestamp > endTimestamp)
|
|
43
|
+
endTimestamp = timestamp;
|
|
44
|
+
}
|
|
45
|
+
return { startTimestamp, endTimestamp };
|
|
46
|
+
}
|
|
47
|
+
export function traceToolDisplayName(tool) {
|
|
48
|
+
return tool.displayName ?? tool.name;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Correlate one concrete call occurrence with its result without treating a
|
|
52
|
+
* source-native callId as globally unique. FIFO is the only source-neutral
|
|
53
|
+
* assumption available when a runtime reuses call IDs.
|
|
54
|
+
*/
|
|
55
|
+
export function correlateTraceToolEvents(events) {
|
|
56
|
+
const pending = new Map();
|
|
57
|
+
return events.map((event) => {
|
|
58
|
+
if (event.eventKind === 'tool_call') {
|
|
59
|
+
const callInstanceId = event.callInstanceId ?? event.eventId;
|
|
60
|
+
const correlated = { ...event, callInstanceId };
|
|
61
|
+
const queue = pending.get(event.callId) ?? [];
|
|
62
|
+
queue.push(correlated);
|
|
63
|
+
pending.set(event.callId, queue);
|
|
64
|
+
return correlated;
|
|
65
|
+
}
|
|
66
|
+
if (event.eventKind === 'tool_result') {
|
|
67
|
+
const queue = pending.get(event.callId);
|
|
68
|
+
const matchingIndex = event.callInstanceId
|
|
69
|
+
? queue?.findIndex((candidate) => candidate.callInstanceId === event.callInstanceId)
|
|
70
|
+
: undefined;
|
|
71
|
+
const call = matchingIndex !== undefined && matchingIndex >= 0
|
|
72
|
+
? queue?.splice(matchingIndex, 1)[0]
|
|
73
|
+
: event.callInstanceId
|
|
74
|
+
? undefined
|
|
75
|
+
: queue?.shift();
|
|
76
|
+
if (queue?.length === 0)
|
|
77
|
+
pending.delete(event.callId);
|
|
78
|
+
return {
|
|
79
|
+
...event,
|
|
80
|
+
callInstanceId: event.callInstanceId
|
|
81
|
+
?? call?.callInstanceId
|
|
82
|
+
?? `orphan:${event.eventId}`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return event;
|
|
86
|
+
});
|
|
87
|
+
}
|