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.
Files changed (246) hide show
  1. package/README.md +50 -18
  2. package/README.zh.md +55 -23
  3. package/dist/analysis/coverage-analyzer.d.ts +1 -0
  4. package/dist/analysis/coverage-analyzer.js +125 -62
  5. package/dist/analysis/failure-clusterer.js +2 -1
  6. package/dist/analysis/gap-analyzer.d.ts +2 -2
  7. package/dist/analysis/gap-analyzer.js +13 -3
  8. package/dist/analysis/hedging-classifier.d.ts +2 -2
  9. package/dist/analysis/hedging-classifier.js +3 -4
  10. package/dist/analysis/report-diagnostics.js +9 -7
  11. package/dist/analysis/sample-diagnostics.js +6 -6
  12. package/dist/artifact-graph/doctor.js +15 -7
  13. package/dist/assets/agent-skills/omk/SKILL.md +27 -7
  14. package/dist/assets/agent-skills/omk/references/commands.md +19 -17
  15. package/dist/authoring/evolver.d.ts +10 -6
  16. package/dist/authoring/evolver.js +496 -83
  17. package/dist/authoring/generator.d.ts +3 -3
  18. package/dist/authoring/generator.js +5 -10
  19. package/dist/authoring/sample-fixer.d.ts +8 -6
  20. package/dist/authoring/sample-fixer.js +76 -5
  21. package/dist/cli/commands/doctor.js +31 -14
  22. package/dist/cli/commands/eval/index.d.ts +3 -0
  23. package/dist/cli/commands/eval/index.js +163 -17
  24. package/dist/cli/commands/evolve.d.ts +4 -4
  25. package/dist/cli/commands/evolve.js +27 -13
  26. package/dist/cli/commands/init.js +16 -3
  27. package/dist/cli/commands/observe/inbox.js +44 -21
  28. package/dist/cli/commands/observe/index.js +20 -11
  29. package/dist/cli/commands/observe/ingest.d.ts +3 -0
  30. package/dist/cli/commands/observe/ingest.js +35 -4
  31. package/dist/cli/commands/sample.d.ts +9 -3
  32. package/dist/cli/commands/sample.js +91 -74
  33. package/dist/cli/lib/cmd-flags.d.ts +1 -0
  34. package/dist/cli/lib/codex-model-hint.d.ts +9 -0
  35. package/dist/cli/lib/codex-model-hint.js +45 -0
  36. package/dist/cli/lib/generation-failure-hint.d.ts +2 -0
  37. package/dist/cli/lib/generation-failure-hint.js +61 -0
  38. package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
  39. package/dist/cli/lib/i18n-dict/common.js +4 -0
  40. package/dist/cli/lib/i18n-dict/gen.d.ts +1 -1
  41. package/dist/cli/lib/i18n-dict/gen.js +38 -6
  42. package/dist/cli/lib/i18n-dict/help.js +6 -6
  43. package/dist/cli/lib/i18n-dict/init.d.ts +1 -1
  44. package/dist/cli/lib/i18n-dict/init.js +13 -9
  45. package/dist/cli/lib/i18n-dict/run.d.ts +1 -1
  46. package/dist/cli/lib/i18n-dict/run.js +34 -2
  47. package/dist/cli/lib/llm-failure-classifier.d.ts +2 -0
  48. package/dist/cli/lib/llm-failure-classifier.js +8 -0
  49. package/dist/cli/lib/parse-run-config.d.ts +6 -5
  50. package/dist/cli/lib/parse-run-config.js +16 -9
  51. package/dist/cli/lib/runtime-defaults.d.ts +21 -0
  52. package/dist/cli/lib/runtime-defaults.js +79 -0
  53. package/dist/diagnosis/observe-mapper.js +14 -15
  54. package/dist/diagnosis/observe-producer.js +3 -1
  55. package/dist/diagnosis/studio-projection.js +14 -7
  56. package/dist/diagnosis/types.d.ts +2 -0
  57. package/dist/diagnosis/types.js +12 -0
  58. package/dist/doctor/endpoint-rule.js +2 -1
  59. package/dist/eval-core/artifact-file-names.js +18 -1
  60. package/dist/eval-core/artifact-index.d.ts +7 -11
  61. package/dist/eval-core/artifact-index.js +139 -80
  62. package/dist/eval-core/cache.d.ts +12 -3
  63. package/dist/eval-core/cache.js +89 -29
  64. package/dist/eval-core/comparability.js +10 -6
  65. package/dist/eval-core/evaluation-execution.d.ts +2 -1
  66. package/dist/eval-core/evaluation-execution.js +122 -37
  67. package/dist/eval-core/evaluation-job.d.ts +4 -1
  68. package/dist/eval-core/evaluation-job.js +4 -1
  69. package/dist/eval-core/evaluation-reporting.d.ts +15 -13
  70. package/dist/eval-core/evaluation-reporting.js +54 -52
  71. package/dist/eval-core/execution-strategy.d.ts +2 -0
  72. package/dist/eval-core/execution-strategy.js +11 -9
  73. package/dist/eval-core/fact-checker.js +15 -7
  74. package/dist/eval-core/holdout.js +3 -2
  75. package/dist/eval-core/judge-independence.d.ts +2 -2
  76. package/dist/eval-core/mock-hook.cjs +23 -6
  77. package/dist/eval-core/mocks-runtime.js +30 -8
  78. package/dist/eval-core/report-document.d.ts +12 -0
  79. package/dist/eval-core/report-document.js +1151 -0
  80. package/dist/eval-core/report-extensions.d.ts +4 -0
  81. package/dist/eval-core/report-extensions.js +500 -0
  82. package/dist/eval-core/report-file-migration.js +7 -2
  83. package/dist/eval-core/resume-compatibility.d.ts +31 -0
  84. package/dist/eval-core/resume-compatibility.js +141 -0
  85. package/dist/eval-core/sample-fingerprint.d.ts +12 -0
  86. package/dist/eval-core/sample-fingerprint.js +193 -0
  87. package/dist/eval-core/schema.js +86 -31
  88. package/dist/eval-core/verdict.d.ts +8 -4
  89. package/dist/eval-core/verdict.js +24 -10
  90. package/dist/eval-workflows/batch-evaluation-workflow.d.ts +2 -1
  91. package/dist/eval-workflows/batch-evaluation-workflow.js +25 -12
  92. package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.d.ts +10 -5
  93. package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +58 -21
  94. package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +3 -1
  95. package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +4 -1
  96. package/dist/eval-workflows/evaluation-pipeline/run-state.js +4 -1
  97. package/dist/eval-workflows/evaluation-pipeline/test-set-hash.d.ts +6 -5
  98. package/dist/eval-workflows/evaluation-pipeline/test-set-hash.js +17 -10
  99. package/dist/eval-workflows/evaluation-pipeline.js +12 -7
  100. package/dist/eval-workflows/run-evaluation.d.ts +9 -7
  101. package/dist/eval-workflows/run-evaluation.js +79 -51
  102. package/dist/executors/anthropic-api.js +65 -9
  103. package/dist/executors/claude-cli.js +16 -79
  104. package/dist/executors/claude-protocol.d.ts +28 -0
  105. package/dist/executors/claude-protocol.js +180 -0
  106. package/dist/executors/claude-sdk-trace.js +56 -28
  107. package/dist/executors/claude-sdk.d.ts +1 -0
  108. package/dist/executors/claude-sdk.js +39 -93
  109. package/dist/executors/codex-cli-trace.js +166 -31
  110. package/dist/executors/codex-cli.d.ts +6 -8
  111. package/dist/executors/codex-cli.js +49 -151
  112. package/dist/executors/codex-protocol.d.ts +24 -0
  113. package/dist/executors/codex-protocol.js +234 -0
  114. package/dist/executors/codex-sdk.js +68 -120
  115. package/dist/executors/gemini.js +88 -13
  116. package/dist/executors/index.d.ts +2 -3
  117. package/dist/executors/index.js +5 -3
  118. package/dist/executors/openai-api.js +70 -9
  119. package/dist/executors/runtime-fingerprint.js +88 -11
  120. package/dist/executors/script-command.d.ts +8 -0
  121. package/dist/executors/script-command.js +87 -0
  122. package/dist/executors/script.js +202 -29
  123. package/dist/executors/shared.d.ts +35 -3
  124. package/dist/executors/shared.js +113 -15
  125. package/dist/grading/assertions.d.ts +1 -1
  126. package/dist/grading/assertions.js +19 -9
  127. package/dist/grading/diagnostic.d.ts +9 -2
  128. package/dist/grading/diagnostic.js +25 -2
  129. package/dist/grading/index.js +10 -4
  130. package/dist/grading/judge.js +19 -6
  131. package/dist/grading/layered-scores.d.ts +2 -3
  132. package/dist/grading/layered-scores.js +2 -3
  133. package/dist/inputs/load-samples.d.ts +1 -2
  134. package/dist/inputs/load-samples.js +23 -1
  135. package/dist/inputs/mcp-resolver.js +6 -3
  136. package/dist/inputs/sample-document.d.ts +11 -0
  137. package/dist/inputs/sample-document.js +96 -0
  138. package/dist/managed/evidence.d.ts +1 -0
  139. package/dist/managed/evidence.js +1 -1
  140. package/dist/managed/store.js +200 -91
  141. package/dist/observability/codex-trace-adapter.d.ts +5 -0
  142. package/dist/observability/codex-trace-adapter.js +850 -0
  143. package/dist/observability/experience.d.ts +32 -6
  144. package/dist/observability/experience.js +2695 -459
  145. package/dist/observability/feedback-matchers.js +16 -1
  146. package/dist/observability/inbox-view-model.d.ts +2 -1
  147. package/dist/observability/inbox-view-model.js +20 -14
  148. package/dist/observability/inbox.d.ts +7 -1
  149. package/dist/observability/inbox.js +632 -124
  150. package/dist/observability/problem-patterns.js +2 -0
  151. package/dist/observability/review-state.d.ts +6 -0
  152. package/dist/observability/review-state.js +235 -63
  153. package/dist/observability/skill-chain-advisories.js +1 -1
  154. package/dist/observability/skill-chain.js +17 -4
  155. package/dist/observability/skill-health-analyzer.d.ts +32 -7
  156. package/dist/observability/skill-health-analyzer.js +194 -121
  157. package/dist/observability/skill-health-report.d.ts +10 -0
  158. package/dist/observability/skill-health-report.js +620 -0
  159. package/dist/observability/soft-standards/constants.d.ts +0 -1
  160. package/dist/observability/soft-standards/constants.js +0 -1
  161. package/dist/observability/soft-standards/index.d.ts +1 -1
  162. package/dist/observability/soft-standards/index.js +1 -1
  163. package/dist/observability/soft-standards/llm-extractor.js +8 -10
  164. package/dist/observability/soft-standards/skill-standards-store.d.ts +2 -1
  165. package/dist/observability/soft-standards/skill-standards-store.js +59 -18
  166. package/dist/observability/soft-standards/types.d.ts +2 -2
  167. package/dist/observability/trace-adapter.d.ts +12 -7
  168. package/dist/observability/trace-adapter.js +11 -9
  169. package/dist/observability/trace-attribution.d.ts +13 -5
  170. package/dist/observability/trace-attribution.js +315 -21
  171. package/dist/observability/trace-ingestion.d.ts +9 -0
  172. package/dist/observability/trace-ingestion.js +80 -0
  173. package/dist/observability/trace-ir.d.ts +113 -0
  174. package/dist/observability/trace-ir.js +87 -0
  175. package/dist/observability/trace-segmenter.d.ts +19 -6
  176. package/dist/observability/trace-segmenter.js +377 -196
  177. package/dist/observability/trace-session-index.d.ts +19 -0
  178. package/dist/observability/trace-session-index.js +68 -0
  179. package/dist/observability/trace-source.d.ts +12 -4
  180. package/dist/observability/trace-source.js +939 -215
  181. package/dist/renderer/html-renderer.js +37 -6
  182. package/dist/renderer/icons.js +3 -0
  183. package/dist/renderer/observation-inbox-renderer.js +227 -91
  184. package/dist/renderer/skill-detail-renderer.js +452 -109
  185. package/dist/renderer/skill-health-renderer.js +69 -12
  186. package/dist/renderer/summary.js +28 -7
  187. package/dist/renderer/table.js +21 -4
  188. package/dist/renderer/test-view.d.ts +1 -0
  189. package/dist/renderer/test-view.js +44 -9
  190. package/dist/server/indexed-report-store.js +14 -18
  191. package/dist/server/job-store.js +64 -26
  192. package/dist/server/report-server.js +190 -78
  193. package/dist/server/report-store.js +57 -80
  194. package/dist/server/skill-index.js +143 -49
  195. package/dist/server/skill-insights.js +44 -5
  196. package/dist/shared/artifact-graph.d.ts +3 -0
  197. package/dist/shared/artifact-graph.js +224 -0
  198. package/dist/shared/assertion-types.d.ts +8 -0
  199. package/dist/shared/assertion-types.js +46 -0
  200. package/dist/shared/atomic-json.d.ts +8 -0
  201. package/dist/shared/atomic-json.js +33 -0
  202. package/dist/shared/diagnosis-schema.d.ts +9 -0
  203. package/dist/shared/diagnosis-schema.js +181 -0
  204. package/dist/shared/doctor-report.d.ts +3 -0
  205. package/dist/shared/doctor-report.js +103 -0
  206. package/dist/shared/evaluation-job.d.ts +6 -0
  207. package/dist/shared/evaluation-job.js +217 -0
  208. package/dist/shared/executor-result.d.ts +17 -0
  209. package/dist/shared/executor-result.js +221 -0
  210. package/dist/shared/file-lock.d.ts +12 -0
  211. package/dist/shared/file-lock.js +129 -0
  212. package/dist/shared/json-value.d.ts +5 -0
  213. package/dist/shared/json-value.js +36 -0
  214. package/dist/shared/keyed-mutex.d.ts +7 -0
  215. package/dist/shared/keyed-mutex.js +24 -0
  216. package/dist/shared/record-count.d.ts +8 -0
  217. package/dist/shared/record-count.js +43 -0
  218. package/dist/shared/sample-contract.d.ts +3 -0
  219. package/dist/shared/sample-contract.js +332 -0
  220. package/dist/shared/shell-quote.d.ts +2 -0
  221. package/dist/shared/shell-quote.js +7 -0
  222. package/dist/shared/timestamp.d.ts +6 -0
  223. package/dist/shared/timestamp.js +64 -0
  224. package/dist/shared/token-usage.d.ts +19 -0
  225. package/dist/shared/token-usage.js +50 -0
  226. package/dist/shared/tool-call-status.d.ts +8 -0
  227. package/dist/shared/tool-call-status.js +28 -0
  228. package/dist/shared/tool-identity.d.ts +21 -0
  229. package/dist/shared/tool-identity.js +84 -0
  230. package/dist/shared/tool-search.js +73 -16
  231. package/dist/shared/trace-projection.d.ts +5 -0
  232. package/dist/shared/trace-projection.js +20 -0
  233. package/dist/shared/trace-source-kind.d.ts +3 -0
  234. package/dist/shared/trace-source-kind.js +12 -0
  235. package/dist/types/diagnosis.d.ts +2 -0
  236. package/dist/types/eval.d.ts +4 -0
  237. package/dist/types/executor.d.ts +32 -5
  238. package/dist/types/index.d.ts +1 -0
  239. package/dist/types/index.js +1 -0
  240. package/dist/types/judge.d.ts +2 -0
  241. package/dist/types/observability.d.ts +116 -9
  242. package/dist/types/report.d.ts +58 -6
  243. package/dist/types/skill-index.d.ts +7 -0
  244. package/dist/types/trace.d.ts +2 -0
  245. package/dist/types/trace.js +1 -0
  246. 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 CC_BUILTIN_COMMANDS = new Set([
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 名: 去掉 plugin 前缀, 过滤 cc 内置命令。
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 (CC_BUILTIN_COMMANDS.has(name))
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 pluginName = colonIdx >= 0 ? trimmed.slice(0, colonIdx) : undefined;
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
- // OpenClaw traces may point at either workspace root; both layouts store skills under <workspace>/skills.
144
- const SKILL_READ_FILE_RE = /(?:\.claude\/skills|\.openclaw\/workspace(?:-main)?\/skills)\/([^/]+)\/SKILL\.md$/;
145
- const SKILL_SCRIPT_PATH_RE = /(?:^|[\s"'`(\[{:,])(?:~|\.|\/)?[^\s"'`]*\/skills\/([^/\s"'`]+)\/scripts\/[^\s"'`]*/;
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
- * 匹配 file_path 形如 ".claude/skills/<name>/SKILL.md"
149
- * 或 ".openclaw/workspace/skills/<name>/SKILL.md" 的模式。
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 m = SKILL_READ_FILE_RE.exec(filePath);
162
- if (m)
163
- return parseSkillRef(m[1]);
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 command = part.input?.command;
188
- if (typeof command === 'string')
189
- texts.push(command);
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
- return parseSkillRef(match[1]);
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
+ }