oh-my-knowledge 0.28.0 → 0.30.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 +49 -3
- package/README.zh.md +49 -3
- package/dist/src/analysis/failure-clusterer.js +1 -1
- package/dist/src/analysis/failure-clusterer.js.map +1 -1
- package/dist/src/analysis/gap-analyzer.d.ts +8 -1
- package/dist/src/analysis/gap-analyzer.d.ts.map +1 -1
- package/dist/src/analysis/gap-analyzer.js +65 -33
- package/dist/src/analysis/gap-analyzer.js.map +1 -1
- package/dist/src/analysis/report-diagnostics.js +2 -0
- package/dist/src/analysis/report-diagnostics.js.map +1 -1
- package/dist/src/analysis/sample-diagnostics.js +14 -14
- package/dist/src/analysis/sample-diagnostics.js.map +1 -1
- package/dist/src/authoring/evolver.d.ts +7 -1
- package/dist/src/authoring/evolver.d.ts.map +1 -1
- package/dist/src/authoring/evolver.js +8 -5
- package/dist/src/authoring/evolver.js.map +1 -1
- package/dist/src/authoring/generator.d.ts +21 -23
- package/dist/src/authoring/generator.d.ts.map +1 -1
- package/dist/src/authoring/generator.js +621 -40
- package/dist/src/authoring/generator.js.map +1 -1
- package/dist/src/authoring/sample-fixer.d.ts +45 -0
- package/dist/src/authoring/sample-fixer.d.ts.map +1 -0
- package/dist/src/authoring/sample-fixer.js +186 -0
- package/dist/src/authoring/sample-fixer.js.map +1 -0
- package/dist/src/cli/commands/evolve.d.ts.map +1 -1
- package/dist/src/cli/commands/evolve.js +17 -0
- package/dist/src/cli/commands/evolve.js.map +1 -1
- package/dist/src/cli/commands/observe.d.ts.map +1 -1
- package/dist/src/cli/commands/observe.js +170 -0
- package/dist/src/cli/commands/observe.js.map +1 -1
- package/dist/src/cli/commands/registry.d.ts.map +1 -1
- package/dist/src/cli/commands/registry.js +9 -1
- package/dist/src/cli/commands/registry.js.map +1 -1
- package/dist/src/cli/commands/sample.d.ts +13 -0
- package/dist/src/cli/commands/sample.d.ts.map +1 -1
- package/dist/src/cli/commands/sample.js +307 -13
- package/dist/src/cli/commands/sample.js.map +1 -1
- package/dist/src/cli/commands/studio.d.ts.map +1 -1
- package/dist/src/cli/commands/studio.js +8 -0
- package/dist/src/cli/commands/studio.js.map +1 -1
- package/dist/src/cli/i18n-dict.d.ts +1 -1
- package/dist/src/cli/i18n-dict.d.ts.map +1 -1
- package/dist/src/cli/i18n-dict.js +189 -17
- package/dist/src/cli/i18n-dict.js.map +1 -1
- package/dist/src/cli/parse-run-config.d.ts +14 -2
- package/dist/src/cli/parse-run-config.d.ts.map +1 -1
- package/dist/src/cli/parse-run-config.js +62 -10
- package/dist/src/cli/parse-run-config.js.map +1 -1
- package/dist/src/doctor/health/builtin-dimensions.d.ts.map +1 -1
- package/dist/src/doctor/health/builtin-dimensions.js +20 -23
- package/dist/src/doctor/health/builtin-dimensions.js.map +1 -1
- package/dist/src/doctor/health/composer.d.ts.map +1 -1
- package/dist/src/doctor/health/composer.js +7 -3
- package/dist/src/doctor/health/composer.js.map +1 -1
- package/dist/src/doctor/health/dimension-spec.d.ts +1 -0
- package/dist/src/doctor/health/dimension-spec.d.ts.map +1 -1
- package/dist/src/doctor/health/parser.d.ts.map +1 -1
- package/dist/src/doctor/health/parser.js +1 -0
- package/dist/src/doctor/health/parser.js.map +1 -1
- package/dist/src/doctor/health/prompt-builder.js +25 -17
- package/dist/src/doctor/health/prompt-builder.js.map +1 -1
- package/dist/src/doctor/html-renderer.d.ts.map +1 -1
- package/dist/src/doctor/html-renderer.js +76 -66
- package/dist/src/doctor/html-renderer.js.map +1 -1
- package/dist/src/doctor/rules.d.ts.map +1 -1
- package/dist/src/doctor/rules.js +32 -0
- package/dist/src/doctor/rules.js.map +1 -1
- package/dist/src/eval-core/cache.d.ts +16 -2
- package/dist/src/eval-core/cache.d.ts.map +1 -1
- package/dist/src/eval-core/cache.js +86 -15
- package/dist/src/eval-core/cache.js.map +1 -1
- package/dist/src/eval-core/evaluation-execution.d.ts +11 -1
- package/dist/src/eval-core/evaluation-execution.d.ts.map +1 -1
- package/dist/src/eval-core/evaluation-execution.js +78 -6
- package/dist/src/eval-core/evaluation-execution.js.map +1 -1
- package/dist/src/eval-core/evaluation-job.d.ts +2 -1
- package/dist/src/eval-core/evaluation-job.d.ts.map +1 -1
- package/dist/src/eval-core/evaluation-job.js +2 -1
- package/dist/src/eval-core/evaluation-job.js.map +1 -1
- package/dist/src/eval-core/evaluation-reporting.d.ts +7 -0
- package/dist/src/eval-core/evaluation-reporting.d.ts.map +1 -1
- package/dist/src/eval-core/evaluation-reporting.js +19 -2
- package/dist/src/eval-core/evaluation-reporting.js.map +1 -1
- package/dist/src/eval-core/execution-strategy.d.ts +1 -1
- package/dist/src/eval-core/execution-strategy.d.ts.map +1 -1
- package/dist/src/eval-core/execution-strategy.js +12 -1
- package/dist/src/eval-core/execution-strategy.js.map +1 -1
- package/dist/src/eval-core/mock-hook.cjs +203 -0
- package/dist/src/eval-core/mocks-runtime.d.ts +96 -0
- package/dist/src/eval-core/mocks-runtime.d.ts.map +1 -0
- package/dist/src/eval-core/mocks-runtime.js +323 -0
- package/dist/src/eval-core/mocks-runtime.js.map +1 -0
- package/dist/src/eval-core/schema.d.ts.map +1 -1
- package/dist/src/eval-core/schema.js +18 -5
- package/dist/src/eval-core/schema.js.map +1 -1
- package/dist/src/eval-core/task-planner.d.ts +9 -1
- package/dist/src/eval-core/task-planner.d.ts.map +1 -1
- package/dist/src/eval-core/task-planner.js +40 -1
- package/dist/src/eval-core/task-planner.js.map +1 -1
- package/dist/src/eval-workflows/batch-evaluation-workflow.d.ts +5 -1
- package/dist/src/eval-workflows/batch-evaluation-workflow.d.ts.map +1 -1
- package/dist/src/eval-workflows/batch-evaluation-workflow.js +2 -1
- package/dist/src/eval-workflows/batch-evaluation-workflow.js.map +1 -1
- package/dist/src/eval-workflows/evaluation-pipeline.d.ts +22 -1
- package/dist/src/eval-workflows/evaluation-pipeline.d.ts.map +1 -1
- package/dist/src/eval-workflows/evaluation-pipeline.js +39 -12
- package/dist/src/eval-workflows/evaluation-pipeline.js.map +1 -1
- package/dist/src/eval-workflows/evaluation-preparation.d.ts +5 -0
- package/dist/src/eval-workflows/evaluation-preparation.d.ts.map +1 -1
- package/dist/src/eval-workflows/evaluation-preparation.js +3 -1
- package/dist/src/eval-workflows/evaluation-preparation.js.map +1 -1
- package/dist/src/eval-workflows/run-evaluation.d.ts +12 -2
- package/dist/src/eval-workflows/run-evaluation.d.ts.map +1 -1
- package/dist/src/eval-workflows/run-evaluation.js +17 -5
- package/dist/src/eval-workflows/run-evaluation.js.map +1 -1
- package/dist/src/executors/claude-cli.d.ts +1 -1
- package/dist/src/executors/claude-cli.d.ts.map +1 -1
- package/dist/src/executors/claude-cli.js +57 -6
- package/dist/src/executors/claude-cli.js.map +1 -1
- package/dist/src/executors/claude-sdk-trace.d.ts.map +1 -1
- package/dist/src/executors/claude-sdk-trace.js +10 -4
- package/dist/src/executors/claude-sdk-trace.js.map +1 -1
- package/dist/src/executors/claude-sdk.d.ts +1 -1
- package/dist/src/executors/claude-sdk.d.ts.map +1 -1
- package/dist/src/executors/claude-sdk.js +25 -2
- package/dist/src/executors/claude-sdk.js.map +1 -1
- package/dist/src/executors/shared.d.ts +1 -1
- package/dist/src/executors/shared.d.ts.map +1 -1
- package/dist/src/executors/shared.js +6 -1
- package/dist/src/executors/shared.js.map +1 -1
- package/dist/src/grading/assertions.d.ts +5 -0
- package/dist/src/grading/assertions.d.ts.map +1 -1
- package/dist/src/grading/assertions.js +27 -1
- package/dist/src/grading/assertions.js.map +1 -1
- package/dist/src/grading/debias-validate.js +4 -4
- package/dist/src/grading/debias-validate.js.map +1 -1
- package/dist/src/grading/diagnostic.d.ts +40 -0
- package/dist/src/grading/diagnostic.d.ts.map +1 -0
- package/dist/src/grading/diagnostic.js +450 -0
- package/dist/src/grading/diagnostic.js.map +1 -0
- package/dist/src/grading/gold-cli.js +2 -2
- package/dist/src/grading/gold-cli.js.map +1 -1
- package/dist/src/grading/index.d.ts +5 -0
- package/dist/src/grading/index.d.ts.map +1 -1
- package/dist/src/grading/judge.d.ts.map +1 -1
- package/dist/src/grading/judge.js +54 -2
- package/dist/src/grading/judge.js.map +1 -1
- package/dist/src/grading/layered-scores.d.ts.map +1 -1
- package/dist/src/grading/layered-scores.js +1 -0
- package/dist/src/grading/layered-scores.js.map +1 -1
- package/dist/src/inputs/eval-config.js +19 -0
- package/dist/src/inputs/eval-config.js.map +1 -1
- package/dist/src/inputs/load-samples.d.ts +22 -2
- package/dist/src/inputs/load-samples.d.ts.map +1 -1
- package/dist/src/inputs/load-samples.js +193 -22
- package/dist/src/inputs/load-samples.js.map +1 -1
- package/dist/src/inputs/skill-loader.d.ts.map +1 -1
- package/dist/src/inputs/skill-loader.js +11 -3
- package/dist/src/inputs/skill-loader.js.map +1 -1
- package/dist/src/observability/experience.d.ts +260 -0
- package/dist/src/observability/experience.d.ts.map +1 -0
- package/dist/src/observability/experience.js +1233 -0
- package/dist/src/observability/experience.js.map +1 -0
- package/dist/src/observability/inbox-view-model.d.ts +27 -0
- package/dist/src/observability/inbox-view-model.d.ts.map +1 -0
- package/dist/src/observability/inbox-view-model.js +135 -0
- package/dist/src/observability/inbox-view-model.js.map +1 -0
- package/dist/src/observability/inbox.d.ts +125 -0
- package/dist/src/observability/inbox.d.ts.map +1 -0
- package/dist/src/observability/inbox.js +741 -0
- package/dist/src/observability/inbox.js.map +1 -0
- package/dist/src/observability/problem-patterns.d.ts +51 -0
- package/dist/src/observability/problem-patterns.d.ts.map +1 -0
- package/dist/src/observability/problem-patterns.js +205 -0
- package/dist/src/observability/problem-patterns.js.map +1 -0
- package/dist/src/observability/review-state.d.ts +54 -0
- package/dist/src/observability/review-state.d.ts.map +1 -0
- package/dist/src/observability/review-state.js +131 -0
- package/dist/src/observability/review-state.js.map +1 -0
- package/dist/src/observability/skill-chain-advisories.d.ts +25 -0
- package/dist/src/observability/skill-chain-advisories.d.ts.map +1 -0
- package/dist/src/observability/skill-chain-advisories.js +69 -0
- package/dist/src/observability/skill-chain-advisories.js.map +1 -0
- package/dist/src/observability/skill-chain.d.ts +61 -0
- package/dist/src/observability/skill-chain.d.ts.map +1 -0
- package/dist/src/observability/skill-chain.js +233 -0
- package/dist/src/observability/skill-chain.js.map +1 -0
- package/dist/src/observability/text-signals.d.ts +7 -0
- package/dist/src/observability/text-signals.d.ts.map +1 -0
- package/dist/src/observability/text-signals.js +22 -0
- package/dist/src/observability/text-signals.js.map +1 -0
- package/dist/src/observability/trace-adapter.d.ts +12 -64
- package/dist/src/observability/trace-adapter.d.ts.map +1 -1
- package/dist/src/observability/trace-adapter.js +9 -355
- package/dist/src/observability/trace-adapter.js.map +1 -1
- package/dist/src/observability/trace-attribution.d.ts +56 -0
- package/dist/src/observability/trace-attribution.d.ts.map +1 -0
- package/dist/src/observability/trace-attribution.js +200 -0
- package/dist/src/observability/trace-attribution.js.map +1 -0
- package/dist/src/observability/trace-segmenter.d.ts +52 -0
- package/dist/src/observability/trace-segmenter.d.ts.map +1 -0
- package/dist/src/observability/trace-segmenter.js +320 -0
- package/dist/src/observability/trace-segmenter.js.map +1 -0
- package/dist/src/observability/trace-source.d.ts +99 -0
- package/dist/src/observability/trace-source.d.ts.map +1 -0
- package/dist/src/observability/trace-source.js +492 -0
- package/dist/src/observability/trace-source.js.map +1 -0
- package/dist/src/renderer/html-renderer.d.ts.map +1 -1
- package/dist/src/renderer/html-renderer.js +324 -218
- package/dist/src/renderer/html-renderer.js.map +1 -1
- package/dist/src/renderer/layout.d.ts.map +1 -1
- package/dist/src/renderer/layout.js +155 -71
- package/dist/src/renderer/layout.js.map +1 -1
- package/dist/src/renderer/observation-inbox-renderer.d.ts +4 -0
- package/dist/src/renderer/observation-inbox-renderer.d.ts.map +1 -0
- package/dist/src/renderer/observation-inbox-renderer.js +5376 -0
- package/dist/src/renderer/observation-inbox-renderer.js.map +1 -0
- package/dist/src/renderer/skill-detail-renderer.d.ts +15 -0
- package/dist/src/renderer/skill-detail-renderer.d.ts.map +1 -0
- package/dist/src/renderer/skill-detail-renderer.js +1234 -0
- package/dist/src/renderer/skill-detail-renderer.js.map +1 -0
- package/dist/src/renderer/skill-list-renderer.d.ts +4 -0
- package/dist/src/renderer/skill-list-renderer.d.ts.map +1 -0
- package/dist/src/renderer/skill-list-renderer.js +327 -0
- package/dist/src/renderer/skill-list-renderer.js.map +1 -0
- package/dist/src/renderer/summary.d.ts +17 -0
- package/dist/src/renderer/summary.d.ts.map +1 -1
- package/dist/src/renderer/summary.js +346 -74
- package/dist/src/renderer/summary.js.map +1 -1
- package/dist/src/renderer/table.d.ts.map +1 -1
- package/dist/src/renderer/table.js +213 -167
- package/dist/src/renderer/table.js.map +1 -1
- package/dist/src/renderer/test-view.d.ts +66 -0
- package/dist/src/renderer/test-view.d.ts.map +1 -0
- package/dist/src/renderer/test-view.js +905 -0
- package/dist/src/renderer/test-view.js.map +1 -0
- package/dist/src/renderer/trends.d.ts.map +1 -1
- package/dist/src/renderer/trends.js +3 -1
- package/dist/src/renderer/trends.js.map +1 -1
- package/dist/src/server/report-server.d.ts +6 -1
- package/dist/src/server/report-server.d.ts.map +1 -1
- package/dist/src/server/report-server.js +182 -6
- package/dist/src/server/report-server.js.map +1 -1
- package/dist/src/server/report-store.d.ts.map +1 -1
- package/dist/src/server/report-store.js +33 -1
- package/dist/src/server/report-store.js.map +1 -1
- package/dist/src/server/skill-index.d.ts +77 -0
- package/dist/src/server/skill-index.d.ts.map +1 -0
- package/dist/src/server/skill-index.js +331 -0
- package/dist/src/server/skill-index.js.map +1 -0
- package/dist/src/server/skill-insights.d.ts +92 -0
- package/dist/src/server/skill-insights.d.ts.map +1 -0
- package/dist/src/server/skill-insights.js +676 -0
- package/dist/src/server/skill-insights.js.map +1 -0
- package/dist/src/shared/hard-rules.d.ts +40 -0
- package/dist/src/shared/hard-rules.d.ts.map +1 -0
- package/dist/src/shared/hard-rules.js +200 -0
- package/dist/src/shared/hard-rules.js.map +1 -0
- package/dist/src/shared/time.d.ts +2 -0
- package/dist/src/shared/time.d.ts.map +1 -0
- package/dist/src/shared/time.js +10 -0
- package/dist/src/shared/time.js.map +1 -0
- package/dist/src/shared/tool-search.d.ts +9 -0
- package/dist/src/shared/tool-search.d.ts.map +1 -0
- package/dist/src/shared/tool-search.js +104 -0
- package/dist/src/shared/tool-search.js.map +1 -0
- package/dist/src/types/eval.d.ts +83 -0
- package/dist/src/types/eval.d.ts.map +1 -1
- package/dist/src/types/executor.d.ts +47 -0
- package/dist/src/types/executor.d.ts.map +1 -1
- package/dist/src/types/judge.d.ts +76 -0
- package/dist/src/types/judge.d.ts.map +1 -1
- package/dist/src/types/judge.js +3 -1
- package/dist/src/types/judge.js.map +1 -1
- package/dist/src/types/report.d.ts +63 -3
- package/dist/src/types/report.d.ts.map +1 -1
- package/dist/src/util/safe-slice.d.ts +23 -0
- package/dist/src/util/safe-slice.d.ts.map +1 -0
- package/dist/src/util/safe-slice.js +33 -0
- package/dist/src/util/safe-slice.js.map +1 -0
- package/package.json +5 -4
|
@@ -0,0 +1,1233 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { buildExperienceProblemPatterns, mergeExperienceProblemPatterns, } from './problem-patterns.js';
|
|
3
|
+
import { observationMetricAnnotationVerdict } from './review-state.js';
|
|
4
|
+
import { extractCommandEnvelopeText, stripCommandEnvelopeText } from './trace-attribution.js';
|
|
5
|
+
import { hasAssistantDeliverySignalText, hasUserHardRuleText, isScheduledTaskPromptText } from './text-signals.js';
|
|
6
|
+
import { durationMsBetween } from '../shared/time.js';
|
|
7
|
+
const TEXT_BOUNDARY_CHARS = new Set([
|
|
8
|
+
' ', '\n', '\r', '\t',
|
|
9
|
+
',', ',', ';', ';', '、', '.', '。', '!', '!', '?', '?', ':', ':',
|
|
10
|
+
'"', "'", '“', '”', '‘', '’', '(', ')', '[', ']', '{', '}', '<', '>', '《', '》',
|
|
11
|
+
]);
|
|
12
|
+
const BOUNDED_USER_CORRECTION_TERMS = ['不对', '不是', '错了'];
|
|
13
|
+
const PHRASE_USER_CORRECTION_TERMS = [
|
|
14
|
+
'不是这个',
|
|
15
|
+
'不要这样',
|
|
16
|
+
'理解错',
|
|
17
|
+
'看错',
|
|
18
|
+
'你应该',
|
|
19
|
+
'应该是',
|
|
20
|
+
'直接用',
|
|
21
|
+
'直接按',
|
|
22
|
+
'改成',
|
|
23
|
+
'重来',
|
|
24
|
+
];
|
|
25
|
+
const USER_GOAL_SHIFT_TERMS = [
|
|
26
|
+
'换个方向',
|
|
27
|
+
'重新来',
|
|
28
|
+
'重来',
|
|
29
|
+
'先不',
|
|
30
|
+
'不用这个',
|
|
31
|
+
'先不用',
|
|
32
|
+
'暂时不用',
|
|
33
|
+
'不看这个',
|
|
34
|
+
'换一个',
|
|
35
|
+
'换下一个',
|
|
36
|
+
'另一个问题',
|
|
37
|
+
'另外一个问题',
|
|
38
|
+
'先看别的',
|
|
39
|
+
'先处理别的',
|
|
40
|
+
];
|
|
41
|
+
const NEGATIVE_FEEDBACK_TERMS = [
|
|
42
|
+
'没有用',
|
|
43
|
+
'没用',
|
|
44
|
+
'不行',
|
|
45
|
+
'太慢',
|
|
46
|
+
'看不懂',
|
|
47
|
+
'不需要',
|
|
48
|
+
'别再',
|
|
49
|
+
'怎么又',
|
|
50
|
+
'赶紧',
|
|
51
|
+
'有问题',
|
|
52
|
+
'不符合',
|
|
53
|
+
'做错了',
|
|
54
|
+
'做错',
|
|
55
|
+
'完全错',
|
|
56
|
+
'错得离谱',
|
|
57
|
+
'很垃圾',
|
|
58
|
+
'太垃圾',
|
|
59
|
+
'乱来',
|
|
60
|
+
'瞎搞',
|
|
61
|
+
'白干',
|
|
62
|
+
'浪费时间',
|
|
63
|
+
'没有价值',
|
|
64
|
+
'没价值',
|
|
65
|
+
'没意义',
|
|
66
|
+
'不好用',
|
|
67
|
+
'用不了',
|
|
68
|
+
'没有帮助',
|
|
69
|
+
'没帮助',
|
|
70
|
+
'毫无帮助',
|
|
71
|
+
'太菜',
|
|
72
|
+
'很菜',
|
|
73
|
+
'真菜',
|
|
74
|
+
'菜了',
|
|
75
|
+
'菜啊',
|
|
76
|
+
];
|
|
77
|
+
const BOUNDED_NEGATIVE_FEEDBACK_TERMS = ['失败', '垃圾', '菜'];
|
|
78
|
+
const POSITIVE_FEEDBACK_TERMS = [
|
|
79
|
+
'非常好',
|
|
80
|
+
'很好',
|
|
81
|
+
'做得好',
|
|
82
|
+
'做的好',
|
|
83
|
+
'做得不错',
|
|
84
|
+
'做的不错',
|
|
85
|
+
'不错',
|
|
86
|
+
'很棒',
|
|
87
|
+
'优秀',
|
|
88
|
+
'厉害',
|
|
89
|
+
'很厉害',
|
|
90
|
+
'值得鼓励',
|
|
91
|
+
'很有用',
|
|
92
|
+
'非常有用',
|
|
93
|
+
'很有价值',
|
|
94
|
+
'非常有价值',
|
|
95
|
+
'很有帮助',
|
|
96
|
+
'非常有帮助',
|
|
97
|
+
'good job',
|
|
98
|
+
'great job',
|
|
99
|
+
'well done',
|
|
100
|
+
'nice work',
|
|
101
|
+
'excellent',
|
|
102
|
+
'awesome',
|
|
103
|
+
];
|
|
104
|
+
const USER_INTERRUPTION_RE = /\[Request interrupted by user(?: for tool use)?\]|interrupted by user|用户中断/i;
|
|
105
|
+
const TIMELINE_PREVIEW_EVENT_LIMIT = 240;
|
|
106
|
+
const ZERO_INDICATORS = {
|
|
107
|
+
userMessageCount: 0,
|
|
108
|
+
userFollowUpCount: 0,
|
|
109
|
+
userCorrectionCount: 0,
|
|
110
|
+
userInterruptionCount: 0,
|
|
111
|
+
negativeFeedbackCount: 0,
|
|
112
|
+
positiveFeedbackCount: 0,
|
|
113
|
+
userGoalShiftCount: 0,
|
|
114
|
+
hardRuleTextHitCount: 0,
|
|
115
|
+
assistantDeliverySignalCount: 0,
|
|
116
|
+
toolCallCount: 0,
|
|
117
|
+
toolFailureCount: 0,
|
|
118
|
+
highObservationCount: 0,
|
|
119
|
+
mediumObservationCount: 0,
|
|
120
|
+
hedgingCount: 0,
|
|
121
|
+
explicitMarkerCount: 0,
|
|
122
|
+
};
|
|
123
|
+
export function buildObservationExperienceReport(input) {
|
|
124
|
+
const sessionsBySourceTrace = new Map(input.sessions.map((session) => [session.sourcePath, session]));
|
|
125
|
+
const sessionGroupsById = groupSessionsByLogicalId(input.sessions);
|
|
126
|
+
const goalSlices = [];
|
|
127
|
+
const invocations = [];
|
|
128
|
+
for (const segment of input.segments) {
|
|
129
|
+
if (segment.skillName === 'general')
|
|
130
|
+
continue;
|
|
131
|
+
const session = sessionsBySourceTrace.get(segment.sourceTrace ?? '') ?? sessionGroupsById.get(segment.sessionId)?.[0];
|
|
132
|
+
const sourceTrace = segment.sourceTrace ?? session?.sourcePath ?? '';
|
|
133
|
+
const relatedItems = relatedObservationItems(segment, input.items);
|
|
134
|
+
const bounds = session ? segmentRecordBounds(session, segment) : { start: 0, end: 0 };
|
|
135
|
+
const timeline = session ? buildTimeline(session, bounds.start, bounds.end) : [];
|
|
136
|
+
const userRefs = timeline.filter((event) => event.kind === 'user_message');
|
|
137
|
+
const indicators = indicatorsForSegment(segment, relatedItems, timeline, input.reviewState);
|
|
138
|
+
const observationRefs = relatedItems.map(observationEvidenceRef);
|
|
139
|
+
const evidenceChain = evidenceChainForTimeline(timeline, observationRefs);
|
|
140
|
+
const ruleFindings = ruleFindingsForEvidence(indicators, timeline, observationRefs, evidenceChain, input.reviewState);
|
|
141
|
+
const assistiveInference = assistiveInferenceForEvidence(indicators, evidenceChain, ruleFindings);
|
|
142
|
+
const problemPatterns = buildExperienceProblemPatterns({
|
|
143
|
+
skillName: segment.skillName,
|
|
144
|
+
sessionId: segment.sessionId,
|
|
145
|
+
timeline,
|
|
146
|
+
reviewState: input.reviewState,
|
|
147
|
+
});
|
|
148
|
+
const hasGoalShift = userRefs.some((ref) => hasUserGoalShiftSignal(ref.snippet ?? ''));
|
|
149
|
+
// 包含 sourceTrace 防止 main + subagent 因 segmentIndex 各自从 0 计数而撞 hash
|
|
150
|
+
// (segmenter 给 segment.sessionId = sessionGroupId,main 和 subagent 共享)
|
|
151
|
+
const goalSliceId = hashParts('goal', segment.sessionId, sourceTrace, segment.skillName, String(segment.segmentIndex));
|
|
152
|
+
const invocationId = hashParts('invocation', segment.sessionId, sourceTrace, segment.skillName, String(segment.segmentIndex));
|
|
153
|
+
goalSlices.push({
|
|
154
|
+
id: goalSliceId,
|
|
155
|
+
skillName: segment.skillName,
|
|
156
|
+
sessionId: segment.sessionId,
|
|
157
|
+
sourceTrace,
|
|
158
|
+
cwd: segment.cwd,
|
|
159
|
+
startTimestamp: segment.startTimestamp,
|
|
160
|
+
endTimestamp: segment.endTimestamp,
|
|
161
|
+
sliceReasonCode: hasGoalShift ? 'explicit_user_goal_shift' : 'skill_segment_boundary',
|
|
162
|
+
sliceConfidence: hasGoalShift ? 'medium' : 'high',
|
|
163
|
+
inferredUserGoal: inferUserGoal(userRefs),
|
|
164
|
+
userMessageRefs: userRefs.slice(0, 8).map(evidenceRefFromTimeline),
|
|
165
|
+
});
|
|
166
|
+
invocations.push({
|
|
167
|
+
id: invocationId,
|
|
168
|
+
skillName: segment.skillName,
|
|
169
|
+
sessionId: segment.sessionId,
|
|
170
|
+
sourceTrace,
|
|
171
|
+
sourceKind: segment.sourceKind ?? sourceKindForPath(sourceTrace),
|
|
172
|
+
entrypoint: session ? session.entrypoint ?? inferEntrypointFromRecords(session) : undefined,
|
|
173
|
+
sourceMetadata: session?.sourceMetadata,
|
|
174
|
+
cwd: segment.cwd,
|
|
175
|
+
segmentIndex: segment.segmentIndex,
|
|
176
|
+
goalSliceId,
|
|
177
|
+
startTimestamp: segment.startTimestamp,
|
|
178
|
+
endTimestamp: segment.endTimestamp,
|
|
179
|
+
attribution: {
|
|
180
|
+
source: segment.attribution?.source ?? 'unknown',
|
|
181
|
+
confidence: segment.attribution?.confidence ?? 0.3,
|
|
182
|
+
rawSkillRef: segment.attribution?.rawSkillRef,
|
|
183
|
+
pluginName: segment.attribution?.pluginName,
|
|
184
|
+
commandName: segment.attribution?.commandName,
|
|
185
|
+
},
|
|
186
|
+
metrics: segment.metrics,
|
|
187
|
+
toolCounts: countTools(segment),
|
|
188
|
+
indicators,
|
|
189
|
+
evidenceChain,
|
|
190
|
+
ruleFindings,
|
|
191
|
+
assistiveInference,
|
|
192
|
+
problemPatterns,
|
|
193
|
+
relatedObservationIds: relatedItems.map((item) => item.id),
|
|
194
|
+
evidenceRefs: [
|
|
195
|
+
...observationRefs,
|
|
196
|
+
...userRefs.slice(0, 5).map(evidenceRefFromTimeline),
|
|
197
|
+
],
|
|
198
|
+
timeline,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const sessions = summarizeExperienceSessions(invocations, sessionGroupsById);
|
|
202
|
+
const skills = summarizeExperienceSkills(sessions, invocations);
|
|
203
|
+
return {
|
|
204
|
+
kind: 'observe-experience',
|
|
205
|
+
schemaVersion: 1,
|
|
206
|
+
scope: 'evidence-only',
|
|
207
|
+
generatedAt: input.generatedAt,
|
|
208
|
+
meta: {
|
|
209
|
+
sessionCount: sessions.length,
|
|
210
|
+
skillCount: skills.length,
|
|
211
|
+
invocationCount: invocations.length,
|
|
212
|
+
goalSliceCount: goalSlices.length,
|
|
213
|
+
noteCodes: ['no_llm_judge', 'no_auto_verdict', 'default_goal_slice_is_allowed', 'deterministic_assistive_inference'],
|
|
214
|
+
},
|
|
215
|
+
goalSlices,
|
|
216
|
+
invocations,
|
|
217
|
+
sessions,
|
|
218
|
+
skills,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function relatedObservationItems(segment, items) {
|
|
222
|
+
return items.filter((item) => item.skillName === segment.skillName
|
|
223
|
+
&& (item.sessionId === segment.sessionId || item.recentSessionIds.includes(segment.sessionId))
|
|
224
|
+
&& timestampsOverlap(item.firstSeen, item.lastSeen, segment.startTimestamp, segment.endTimestamp));
|
|
225
|
+
}
|
|
226
|
+
function logicalSessionId(session) {
|
|
227
|
+
return session.sessionGroupId ?? session.sessionId;
|
|
228
|
+
}
|
|
229
|
+
function groupSessionsByLogicalId(sessions) {
|
|
230
|
+
const groups = new Map();
|
|
231
|
+
for (const session of sessions) {
|
|
232
|
+
const key = logicalSessionId(session);
|
|
233
|
+
const group = groups.get(key) ?? [];
|
|
234
|
+
group.push(session);
|
|
235
|
+
groups.set(key, group);
|
|
236
|
+
}
|
|
237
|
+
for (const [key, group] of groups.entries()) {
|
|
238
|
+
groups.set(key, group.sort(compareSessionsForTimeline));
|
|
239
|
+
}
|
|
240
|
+
return groups;
|
|
241
|
+
}
|
|
242
|
+
function compareSessionsForTimeline(a, b) {
|
|
243
|
+
const roleRank = (session) => session.traceRole === 'main' ? 0 : session.traceRole === 'standalone' ? 1 : 2;
|
|
244
|
+
const rank = roleRank(a) - roleRank(b);
|
|
245
|
+
if (rank !== 0)
|
|
246
|
+
return rank;
|
|
247
|
+
const time = (a.startTimestamp ?? '').localeCompare(b.startTimestamp ?? '');
|
|
248
|
+
if (time !== 0)
|
|
249
|
+
return time;
|
|
250
|
+
return a.sourcePath.localeCompare(b.sourcePath);
|
|
251
|
+
}
|
|
252
|
+
function timestampsOverlap(aStart, aEnd, bStart, bEnd) {
|
|
253
|
+
if (!aStart || !aEnd || !bStart || !bEnd)
|
|
254
|
+
return true;
|
|
255
|
+
return aStart <= bEnd && bStart <= aEnd;
|
|
256
|
+
}
|
|
257
|
+
function segmentRecordBounds(session, segment) {
|
|
258
|
+
if (typeof segment.startRecordIndex === 'number' && typeof segment.endRecordIndex === 'number') {
|
|
259
|
+
const rawStart = clampRecordIndex(session, segment.startRecordIndex);
|
|
260
|
+
const rawEnd = clampRecordIndex(session, Math.max(segment.startRecordIndex, segment.endRecordIndex));
|
|
261
|
+
const humanStart = Math.min(rawStart, previousHumanUserRecordIndex(session, rawStart) ?? rawStart);
|
|
262
|
+
return {
|
|
263
|
+
start: includeLeadingRuntimeContext(session, humanStart),
|
|
264
|
+
end: includeTrailingDeliveryContext(session, Math.min(session.records.length, rawEnd + 1)),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const indexes = segment.toolCalls
|
|
268
|
+
.map((toolCall) => toolCall.messageIndex)
|
|
269
|
+
.filter((index) => typeof index === 'number' && index >= 0);
|
|
270
|
+
if (indexes.length > 0) {
|
|
271
|
+
const start = Math.max(0, Math.min(...indexes) - 3);
|
|
272
|
+
return {
|
|
273
|
+
start: Math.min(start, previousHumanUserRecordIndex(session, start) ?? start),
|
|
274
|
+
end: includeTrailingDeliveryContext(session, Math.min(session.records.length, Math.max(...indexes) + 5)),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const timestampIndexes = [];
|
|
278
|
+
session.records.forEach((record, index) => {
|
|
279
|
+
const ts = timestampOf(record);
|
|
280
|
+
if (ts && ts >= segment.startTimestamp && ts <= segment.endTimestamp)
|
|
281
|
+
timestampIndexes.push(index);
|
|
282
|
+
});
|
|
283
|
+
if (timestampIndexes.length === 0)
|
|
284
|
+
return { start: 0, end: Math.min(session.records.length, 12) };
|
|
285
|
+
const start = Math.max(0, Math.min(...timestampIndexes) - 2);
|
|
286
|
+
return {
|
|
287
|
+
start: Math.min(start, previousHumanUserRecordIndex(session, start) ?? start),
|
|
288
|
+
end: includeTrailingDeliveryContext(session, Math.min(session.records.length, Math.max(...timestampIndexes) + 3)),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function clampRecordIndex(session, index) {
|
|
292
|
+
return Math.max(0, Math.min(session.records.length - 1, index));
|
|
293
|
+
}
|
|
294
|
+
function includeLeadingRuntimeContext(session, start) {
|
|
295
|
+
let nextStart = start;
|
|
296
|
+
for (let index = start - 1; index >= 0; index -= 1) {
|
|
297
|
+
const events = timelineEventsFromRecord(session, session.records[index], index);
|
|
298
|
+
if (events.length === 0)
|
|
299
|
+
continue;
|
|
300
|
+
if (events.some((event) => event.kind === 'runtime_context')) {
|
|
301
|
+
nextStart = index;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
return nextStart;
|
|
307
|
+
}
|
|
308
|
+
function includeTrailingDeliveryContext(session, end) {
|
|
309
|
+
const safeEnd = Math.min(session.records.length, Math.max(0, end));
|
|
310
|
+
const lookaheadEnd = Math.min(session.records.length, safeEnd + 8);
|
|
311
|
+
for (let index = safeEnd; index < lookaheadEnd; index += 1) {
|
|
312
|
+
const events = timelineEventsFromRecord(session, session.records[index], index);
|
|
313
|
+
if (events.some((event) => event.kind === 'user_message'))
|
|
314
|
+
break;
|
|
315
|
+
if (events.some(isAssistantDeliveryEvent))
|
|
316
|
+
return index + 1;
|
|
317
|
+
}
|
|
318
|
+
return safeEnd;
|
|
319
|
+
}
|
|
320
|
+
function previousHumanUserRecordIndex(session, start) {
|
|
321
|
+
for (let index = Math.min(start, session.records.length - 1); index >= 0; index -= 1) {
|
|
322
|
+
const events = timelineEventsFromRecord(session, session.records[index], index);
|
|
323
|
+
if (events.some((event) => event.kind === 'user_message'))
|
|
324
|
+
return index;
|
|
325
|
+
}
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
function isAssistantDeliveryEvent(event) {
|
|
329
|
+
if (event.kind !== 'assistant_message')
|
|
330
|
+
return false;
|
|
331
|
+
const text = event.fullText ?? event.snippet ?? '';
|
|
332
|
+
return hasAssistantDeliverySignalText(text);
|
|
333
|
+
}
|
|
334
|
+
function buildTimeline(session, start, end) {
|
|
335
|
+
return buildTimelineWindow(session, start, end).slice(0, TIMELINE_PREVIEW_EVENT_LIMIT);
|
|
336
|
+
}
|
|
337
|
+
function buildTimelineWindow(session, start, end) {
|
|
338
|
+
const events = [];
|
|
339
|
+
const safeEnd = Math.min(session.records.length, Math.max(start, end));
|
|
340
|
+
for (let index = start; index < safeEnd; index += 1) {
|
|
341
|
+
events.push(...timelineEventsFromRecord(session, session.records[index], index));
|
|
342
|
+
}
|
|
343
|
+
return events
|
|
344
|
+
.sort((a, b) => a.order - b.order);
|
|
345
|
+
}
|
|
346
|
+
function timelineEventsFromRecord(session, record, messageIndex) {
|
|
347
|
+
if (!record || typeof record !== 'object')
|
|
348
|
+
return [];
|
|
349
|
+
const rec = record;
|
|
350
|
+
const uuid = typeof rec.uuid === 'string' ? rec.uuid : undefined;
|
|
351
|
+
const timestamp = timestampOf(record);
|
|
352
|
+
const base = {
|
|
353
|
+
sourceTrace: session.sourcePath,
|
|
354
|
+
sessionId: logicalSessionId(session),
|
|
355
|
+
traceRole: session.traceRole,
|
|
356
|
+
traceLabel: session.traceLabel,
|
|
357
|
+
messageIndex,
|
|
358
|
+
messageUuid: uuid,
|
|
359
|
+
timestamp,
|
|
360
|
+
};
|
|
361
|
+
if (rec.type === 'user') {
|
|
362
|
+
const user = rec;
|
|
363
|
+
return userEvents(user, base, messageIndex);
|
|
364
|
+
}
|
|
365
|
+
if (rec.type === 'assistant') {
|
|
366
|
+
const assistant = rec;
|
|
367
|
+
return assistantEvents(assistant, base, messageIndex);
|
|
368
|
+
}
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
function userEvents(record, base, messageIndex) {
|
|
372
|
+
const content = record.message.content;
|
|
373
|
+
const events = [];
|
|
374
|
+
if (typeof content === 'string') {
|
|
375
|
+
events.push(...userTextEventsFromText(record, content, base, messageIndex, 0));
|
|
376
|
+
return events;
|
|
377
|
+
}
|
|
378
|
+
let textIndex = 0;
|
|
379
|
+
let resultIndex = 0;
|
|
380
|
+
for (const part of content) {
|
|
381
|
+
if (part.type === 'text') {
|
|
382
|
+
const nextEvents = userTextEventsFromText(record, part.text, base, messageIndex, textIndex);
|
|
383
|
+
events.push(...nextEvents);
|
|
384
|
+
if (nextEvents.length > 0)
|
|
385
|
+
textIndex += nextEvents.length;
|
|
386
|
+
}
|
|
387
|
+
else if (part.type === 'tool_result') {
|
|
388
|
+
const full = fullText(part.content);
|
|
389
|
+
const text = snippet(part.content, 900);
|
|
390
|
+
events.push(timelineEvent({
|
|
391
|
+
...base,
|
|
392
|
+
kind: 'tool_result',
|
|
393
|
+
role: 'tool',
|
|
394
|
+
order: messageIndex * 10 + 5 + resultIndex,
|
|
395
|
+
toolUseId: part.tool_use_id,
|
|
396
|
+
isError: part.is_error === true,
|
|
397
|
+
snippet: text,
|
|
398
|
+
fullText: full,
|
|
399
|
+
label: part.is_error === true ? 'tool result error' : 'tool result',
|
|
400
|
+
}));
|
|
401
|
+
resultIndex += 1;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return events;
|
|
405
|
+
}
|
|
406
|
+
function userTextEventsFromText(record, rawText, base, messageIndex, offset) {
|
|
407
|
+
const events = [];
|
|
408
|
+
const commandEnvelope = extractCommandEnvelopeText(rawText);
|
|
409
|
+
if (commandEnvelope) {
|
|
410
|
+
events.push(timelineEvent({
|
|
411
|
+
...base,
|
|
412
|
+
kind: 'runtime_context',
|
|
413
|
+
role: 'tool',
|
|
414
|
+
order: messageIndex * 10 + offset,
|
|
415
|
+
snippet: snippet(commandEnvelope, 700),
|
|
416
|
+
fullText: fullText(commandEnvelope),
|
|
417
|
+
label: 'command envelope',
|
|
418
|
+
}));
|
|
419
|
+
}
|
|
420
|
+
const humanText = commandEnvelope ? stripCommandEnvelopeText(rawText) : rawText;
|
|
421
|
+
const full = fullText(humanText);
|
|
422
|
+
const text = snippet(humanText, 700);
|
|
423
|
+
if (text) {
|
|
424
|
+
const kind = userTextEventKind(record, text);
|
|
425
|
+
events.push(timelineEvent({
|
|
426
|
+
...base,
|
|
427
|
+
kind,
|
|
428
|
+
role: kind === 'user_message' ? 'user' : 'tool',
|
|
429
|
+
order: messageIndex * 10 + offset + (commandEnvelope ? 1 : 0),
|
|
430
|
+
snippet: text,
|
|
431
|
+
fullText: full,
|
|
432
|
+
label: userTextEventLabel(kind),
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
435
|
+
return events;
|
|
436
|
+
}
|
|
437
|
+
function assistantEvents(record, base, messageIndex) {
|
|
438
|
+
const events = [];
|
|
439
|
+
const textParts = [];
|
|
440
|
+
let index = 0;
|
|
441
|
+
for (const part of record.message.content) {
|
|
442
|
+
if (part.type === 'text' && part.text)
|
|
443
|
+
textParts.push(part.text);
|
|
444
|
+
if (part.type === 'tool_use' && part.id && part.name) {
|
|
445
|
+
const inputText = JSON.stringify(part.input ?? {});
|
|
446
|
+
events.push(timelineEvent({
|
|
447
|
+
...base,
|
|
448
|
+
kind: 'tool_use',
|
|
449
|
+
role: 'assistant',
|
|
450
|
+
order: messageIndex * 10 + 5 + index,
|
|
451
|
+
toolUseId: part.id,
|
|
452
|
+
toolName: part.name,
|
|
453
|
+
snippet: snippet(inputText, 900),
|
|
454
|
+
fullText: fullText(inputText),
|
|
455
|
+
label: `tool_use ${part.name}`,
|
|
456
|
+
}));
|
|
457
|
+
index += 1;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const rawText = textParts.join('\n');
|
|
461
|
+
const text = snippet(rawText, 700);
|
|
462
|
+
if (text) {
|
|
463
|
+
events.unshift(timelineEvent({
|
|
464
|
+
...base,
|
|
465
|
+
kind: 'assistant_message',
|
|
466
|
+
role: 'assistant',
|
|
467
|
+
order: messageIndex * 10,
|
|
468
|
+
snippet: text,
|
|
469
|
+
fullText: fullText(rawText),
|
|
470
|
+
label: 'assistant message',
|
|
471
|
+
}));
|
|
472
|
+
}
|
|
473
|
+
return events;
|
|
474
|
+
}
|
|
475
|
+
function timelineEvent(input) {
|
|
476
|
+
return {
|
|
477
|
+
...input,
|
|
478
|
+
id: hashParts(input.sourceTrace, input.sessionId, String(input.messageIndex ?? ''), input.kind, input.toolUseId ?? '', input.snippet ?? ''),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function userTextEventKind(record, text) {
|
|
482
|
+
if (isSkillContextRecord(record, text))
|
|
483
|
+
return 'skill_context';
|
|
484
|
+
if (isRuntimeContextRecord(record, text))
|
|
485
|
+
return 'runtime_context';
|
|
486
|
+
return 'user_message';
|
|
487
|
+
}
|
|
488
|
+
function userTextEventLabel(kind) {
|
|
489
|
+
if (kind === 'skill_context')
|
|
490
|
+
return 'skill context';
|
|
491
|
+
if (kind === 'runtime_context')
|
|
492
|
+
return 'runtime context';
|
|
493
|
+
return 'user message';
|
|
494
|
+
}
|
|
495
|
+
function observationEvidenceRef(item) {
|
|
496
|
+
return {
|
|
497
|
+
id: hashParts('observation', item.id),
|
|
498
|
+
kind: 'observation',
|
|
499
|
+
sourceTrace: item.sourceTrace,
|
|
500
|
+
sessionId: item.sessionId,
|
|
501
|
+
messageIndex: item.evidence.messageIndex,
|
|
502
|
+
messageUuid: item.evidence.messageUuid,
|
|
503
|
+
toolUseId: item.evidence.toolUseId,
|
|
504
|
+
timestamp: item.evidence.segmentTimestamp,
|
|
505
|
+
role: 'other',
|
|
506
|
+
label: `${item.signalType}/${item.signalSubtype}`,
|
|
507
|
+
snippet: snippet(item.evidence.query || item.evidence.path || item.evidence.assistantSnippet || item.evidence.outputSnippet || item.evidence.markerToken, 700),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function evidenceRefFromTimeline(event) {
|
|
511
|
+
return {
|
|
512
|
+
id: event.id,
|
|
513
|
+
kind: event.kind,
|
|
514
|
+
sourceTrace: event.sourceTrace,
|
|
515
|
+
sessionId: event.sessionId,
|
|
516
|
+
traceRole: event.traceRole,
|
|
517
|
+
traceLabel: event.traceLabel,
|
|
518
|
+
messageIndex: event.messageIndex,
|
|
519
|
+
messageUuid: event.messageUuid,
|
|
520
|
+
toolUseId: event.toolUseId,
|
|
521
|
+
timestamp: event.timestamp,
|
|
522
|
+
role: event.role,
|
|
523
|
+
label: event.label,
|
|
524
|
+
snippet: event.snippet,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function evidenceChainForTimeline(timeline, observationRefs) {
|
|
528
|
+
const events = uniqueTimelineEvents(timeline).sort(compareTimelineEvents);
|
|
529
|
+
const userEvents = events.filter((event) => event.kind === 'user_message');
|
|
530
|
+
const runtimeEvents = events.filter((event) => event.kind === 'runtime_context');
|
|
531
|
+
const skillEvents = events.filter((event) => event.kind === 'skill_context');
|
|
532
|
+
const assistantEvents = events.filter((event) => event.kind === 'assistant_message');
|
|
533
|
+
const toolUseEvents = events.filter((event) => event.kind === 'tool_use');
|
|
534
|
+
const toolResultEvents = events.filter((event) => event.kind === 'tool_result');
|
|
535
|
+
const toolFailureEvents = toolResultEvents.filter((event) => event.isError === true);
|
|
536
|
+
return {
|
|
537
|
+
userMessageCount: userEvents.length,
|
|
538
|
+
runtimeContextCount: runtimeEvents.length,
|
|
539
|
+
skillContextCount: skillEvents.length,
|
|
540
|
+
assistantMessageCount: assistantEvents.length,
|
|
541
|
+
toolUseCount: toolUseEvents.length,
|
|
542
|
+
toolResultCount: toolResultEvents.length,
|
|
543
|
+
toolFailureResultCount: toolFailureEvents.length,
|
|
544
|
+
observationCount: observationRefs.length,
|
|
545
|
+
firstUserMessage: userEvents[0] ? evidenceRefFromTimeline(userEvents[0]) : undefined,
|
|
546
|
+
firstRuntimeContext: runtimeEvents[0] ? evidenceRefFromTimeline(runtimeEvents[0]) : undefined,
|
|
547
|
+
firstSkillContext: skillEvents[0] ? evidenceRefFromTimeline(skillEvents[0]) : undefined,
|
|
548
|
+
firstToolUse: toolUseEvents[0] ? evidenceRefFromTimeline(toolUseEvents[0]) : undefined,
|
|
549
|
+
firstToolFailure: toolFailureEvents[0] ? evidenceRefFromTimeline(toolFailureEvents[0]) : undefined,
|
|
550
|
+
lastAssistantMessage: assistantEvents.at(-1) ? evidenceRefFromTimeline(assistantEvents.at(-1)) : undefined,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function ruleFindingsForEvidence(indicators, timeline, observationRefs, evidenceChain, reviewState) {
|
|
554
|
+
const events = uniqueTimelineEvents(timeline);
|
|
555
|
+
const userEvents = events.filter((event) => event.kind === 'user_message');
|
|
556
|
+
const metricUserEvents = userEvents.filter((event) => !isScheduledTaskPromptText(event.snippet ?? ''));
|
|
557
|
+
const refs = (matches) => matches.slice(0, 5).map(evidenceRefFromTimeline);
|
|
558
|
+
const findings = [];
|
|
559
|
+
const push = (code, level, count, evidenceRefs = []) => {
|
|
560
|
+
if (count <= 0)
|
|
561
|
+
return;
|
|
562
|
+
findings.push({ code, level, count, evidenceRefs: uniqueEvidenceRefs(evidenceRefs).slice(0, 5) });
|
|
563
|
+
};
|
|
564
|
+
push('high_observation_seen', 'attention', indicators.highObservationCount, observationRefs);
|
|
565
|
+
push('user_correction_seen', 'attention', indicators.userCorrectionCount, refs(metricUserEvents.filter((event) => metricIsActive(event, 'user_correction', hasUserCorrectionSignal(event.snippet ?? ''), reviewState))));
|
|
566
|
+
push('user_interruption_seen', 'attention', indicators.userInterruptionCount, refs(metricUserEvents.filter((event) => metricIsActive(event, 'user_interruption', USER_INTERRUPTION_RE.test(event.snippet ?? ''), reviewState))));
|
|
567
|
+
push('negative_feedback_seen', 'attention', indicators.negativeFeedbackCount, refs(metricUserEvents.filter((event) => metricIsActive(event, 'negative_feedback', hasNegativeFeedbackSignal(event.snippet ?? ''), reviewState))));
|
|
568
|
+
push('tool_failure_seen', 'sample', indicators.toolFailureCount, refs(events.filter((event) => event.kind === 'tool_result' && event.isError === true)));
|
|
569
|
+
push('medium_observation_seen', 'sample', indicators.mediumObservationCount, observationRefs);
|
|
570
|
+
push('hedging_seen', 'sample', indicators.hedgingCount, observationRefs);
|
|
571
|
+
push('explicit_marker_seen', 'sample', indicators.explicitMarkerCount, observationRefs);
|
|
572
|
+
push('hard_rule_seen', 'sample', indicators.hardRuleTextHitCount, refs(metricUserEvents.filter((event) => metricIsActive(event, 'hard_rule', hasUserHardRuleText(event.snippet ?? ''), reviewState))));
|
|
573
|
+
push('positive_feedback_seen', 'normal', indicators.positiveFeedbackCount, refs(metricUserEvents.filter((event) => metricIsActive(event, 'positive_feedback', hasPositiveFeedbackSignal(event.snippet ?? ''), reviewState))));
|
|
574
|
+
push('user_goal_shift_seen', 'normal', indicators.userGoalShiftCount, refs(metricUserEvents.filter((event) => metricIsActive(event, 'user_goal_shift', hasUserGoalShiftSignal(event.snippet ?? ''), reviewState))));
|
|
575
|
+
push('runtime_context_excluded', 'normal', evidenceChain.runtimeContextCount, evidenceChain.firstRuntimeContext ? [evidenceChain.firstRuntimeContext] : []);
|
|
576
|
+
push('skill_context_excluded', 'normal', evidenceChain.skillContextCount, evidenceChain.firstSkillContext ? [evidenceChain.firstSkillContext] : []);
|
|
577
|
+
if (findings.filter((finding) => finding.level !== 'normal').length === 0) {
|
|
578
|
+
findings.push({ code: 'no_priority_signal', level: 'normal', count: 1, evidenceRefs: [] });
|
|
579
|
+
}
|
|
580
|
+
return findings;
|
|
581
|
+
}
|
|
582
|
+
function assistiveInferenceForEvidence(indicators, evidenceChain, ruleFindings) {
|
|
583
|
+
const attentionFindings = ruleFindings.filter((finding) => finding.level === 'attention');
|
|
584
|
+
const sampleFindings = ruleFindings.filter((finding) => finding.level === 'sample');
|
|
585
|
+
const normalFindings = ruleFindings.filter((finding) => finding.level === 'normal');
|
|
586
|
+
const basisRuleCodes = unique([
|
|
587
|
+
...attentionFindings.map((finding) => finding.code),
|
|
588
|
+
...sampleFindings.map((finding) => finding.code),
|
|
589
|
+
...normalFindings
|
|
590
|
+
.filter((finding) => finding.code === 'positive_feedback_seen' || finding.code === 'hard_rule_seen' || finding.code === 'user_goal_shift_seen' || finding.code === 'no_priority_signal')
|
|
591
|
+
.map((finding) => finding.code),
|
|
592
|
+
]);
|
|
593
|
+
const cautionCodes = ['no_llm_judge', 'rule_only'];
|
|
594
|
+
if (evidenceChain.runtimeContextCount > 0)
|
|
595
|
+
cautionCodes.push('runtime_context_excluded');
|
|
596
|
+
if (evidenceChain.skillContextCount > 0)
|
|
597
|
+
cautionCodes.push('skill_context_excluded');
|
|
598
|
+
if (evidenceChain.userMessageCount === 0)
|
|
599
|
+
cautionCodes.push('no_human_user_message');
|
|
600
|
+
if (evidenceChain.toolUseCount + evidenceChain.toolResultCount + evidenceChain.assistantMessageCount > 24)
|
|
601
|
+
cautionCodes.push('limited_timeline_window');
|
|
602
|
+
let code;
|
|
603
|
+
if (attentionFindings.length > 0) {
|
|
604
|
+
code = 'review_recommended';
|
|
605
|
+
}
|
|
606
|
+
else if (sampleFindings.length > 0) {
|
|
607
|
+
code = 'sample_recommended';
|
|
608
|
+
}
|
|
609
|
+
else if (indicators.positiveFeedbackCount > 0) {
|
|
610
|
+
code = 'positive_signal_observed';
|
|
611
|
+
}
|
|
612
|
+
else if (indicators.userGoalShiftCount > 0) {
|
|
613
|
+
code = 'user_switched_topic_neutral';
|
|
614
|
+
}
|
|
615
|
+
else if (evidenceChain.userMessageCount === 0 && evidenceChain.toolUseCount === 0 && evidenceChain.observationCount === 0) {
|
|
616
|
+
code = 'insufficient_human_context';
|
|
617
|
+
}
|
|
618
|
+
else if (evidenceChain.userMessageCount === 0 && indicators.hardRuleTextHitCount === 0) {
|
|
619
|
+
code = 'insufficient_human_context';
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
code = 'no_obvious_issue_from_rules';
|
|
623
|
+
}
|
|
624
|
+
const confidence = attentionFindings.length > 0 || indicators.positiveFeedbackCount > 0
|
|
625
|
+
? 'high'
|
|
626
|
+
: sampleFindings.length > 0 || indicators.userGoalShiftCount > 0 || evidenceChain.userMessageCount > 0 || evidenceChain.toolUseCount > 0
|
|
627
|
+
? 'medium'
|
|
628
|
+
: 'low';
|
|
629
|
+
const evidenceRefs = uniqueEvidenceRefs([
|
|
630
|
+
...attentionFindings.flatMap((finding) => finding.evidenceRefs),
|
|
631
|
+
...sampleFindings.flatMap((finding) => finding.evidenceRefs),
|
|
632
|
+
...(evidenceChain.firstUserMessage ? [evidenceChain.firstUserMessage] : []),
|
|
633
|
+
...(evidenceChain.firstToolUse ? [evidenceChain.firstToolUse] : []),
|
|
634
|
+
...(evidenceChain.firstToolFailure ? [evidenceChain.firstToolFailure] : []),
|
|
635
|
+
]).slice(0, 8);
|
|
636
|
+
return {
|
|
637
|
+
mode: 'deterministic_rules_only',
|
|
638
|
+
code,
|
|
639
|
+
confidence,
|
|
640
|
+
basisRuleCodes,
|
|
641
|
+
cautionCodes: unique(cautionCodes),
|
|
642
|
+
evidenceRefs,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function indicatorsForSegment(segment, relatedItems, timeline, reviewState) {
|
|
646
|
+
const userRefs = timeline.filter((event) => event.kind === 'user_message');
|
|
647
|
+
const humanUserRefs = userRefs.filter((ref) => Boolean(ref.snippet));
|
|
648
|
+
const interactionUserRefs = humanUserRefs.filter((ref) => !isScheduledTaskPromptText(ref.snippet ?? ''));
|
|
649
|
+
return {
|
|
650
|
+
userMessageCount: humanUserRefs.length,
|
|
651
|
+
userFollowUpCount: interactionUserRefs.reduce((sum, ref, index) => sum + (metricIsActive(ref, 'user_follow_up', index > 0, reviewState) ? 1 : 0), 0),
|
|
652
|
+
userCorrectionCount: interactionUserRefs.reduce((sum, ref) => sum + metricCount(ref, 'user_correction', findUserCorrectionMatches(ref.snippet ?? '').length, reviewState), 0),
|
|
653
|
+
userInterruptionCount: interactionUserRefs.reduce((sum, ref) => sum + (metricIsActive(ref, 'user_interruption', USER_INTERRUPTION_RE.test(ref.snippet ?? ''), reviewState) ? 1 : 0), 0),
|
|
654
|
+
negativeFeedbackCount: interactionUserRefs.reduce((sum, ref) => sum + metricCount(ref, 'negative_feedback', findNegativeFeedbackMatches(ref.snippet ?? '').length, reviewState), 0),
|
|
655
|
+
positiveFeedbackCount: interactionUserRefs.reduce((sum, ref) => sum + metricCount(ref, 'positive_feedback', findPositiveFeedbackMatches(ref.snippet ?? '').length, reviewState), 0),
|
|
656
|
+
userGoalShiftCount: interactionUserRefs.reduce((sum, ref) => sum + metricCount(ref, 'user_goal_shift', findUserGoalShiftMatches(ref.snippet ?? '').length, reviewState), 0),
|
|
657
|
+
hardRuleTextHitCount: interactionUserRefs.reduce((sum, ref) => sum + (metricIsActive(ref, 'hard_rule', hasUserHardRuleText(ref.snippet ?? ''), reviewState) ? 1 : 0), 0),
|
|
658
|
+
assistantDeliverySignalCount: timeline.filter(isAssistantDeliveryEvent).length,
|
|
659
|
+
toolCallCount: segment.metrics.numToolCalls,
|
|
660
|
+
toolFailureCount: segment.metrics.numToolFailures,
|
|
661
|
+
highObservationCount: relatedItems.filter((item) => item.severity === 'high').length,
|
|
662
|
+
mediumObservationCount: relatedItems.filter((item) => item.severity === 'medium').length,
|
|
663
|
+
hedgingCount: relatedItems.filter((item) => item.signalType === 'hedging').reduce((sum, item) => sum + item.occurrences, 0),
|
|
664
|
+
explicitMarkerCount: relatedItems.filter((item) => item.signalType === 'explicit_marker').reduce((sum, item) => sum + item.occurrences, 0),
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function metricIsActive(ref, metricKey, ruleDetected, reviewState) {
|
|
668
|
+
const verdict = observationMetricAnnotationVerdict(reviewState, ref, metricKey);
|
|
669
|
+
if (verdict === 'confirmed')
|
|
670
|
+
return true;
|
|
671
|
+
if (verdict === 'rejected')
|
|
672
|
+
return false;
|
|
673
|
+
return ruleDetected;
|
|
674
|
+
}
|
|
675
|
+
function metricCount(ref, metricKey, ruleCount, reviewState) {
|
|
676
|
+
const verdict = observationMetricAnnotationVerdict(reviewState, ref, metricKey);
|
|
677
|
+
if (verdict === 'confirmed')
|
|
678
|
+
return Math.max(1, ruleCount);
|
|
679
|
+
if (verdict === 'rejected')
|
|
680
|
+
return 0;
|
|
681
|
+
return ruleCount;
|
|
682
|
+
}
|
|
683
|
+
function summarizeExperienceSessions(invocations, sessionGroupsById) {
|
|
684
|
+
const byKey = new Map();
|
|
685
|
+
for (const invocation of invocations) {
|
|
686
|
+
const key = `${invocation.skillName}\u0000${invocation.sessionId}`;
|
|
687
|
+
const group = byKey.get(key) ?? [];
|
|
688
|
+
group.push(invocation);
|
|
689
|
+
byKey.set(key, group);
|
|
690
|
+
}
|
|
691
|
+
return Array.from(byKey.values()).map((group) => {
|
|
692
|
+
const first = group[0];
|
|
693
|
+
const indicators = sumIndicators(group.map((invocation) => invocation.indicators));
|
|
694
|
+
const relatedObservationIds = unique(group.flatMap((invocation) => invocation.relatedObservationIds));
|
|
695
|
+
const reviewBasisCodes = basisCodesForIndicators(indicators);
|
|
696
|
+
const reviewPriorityScore = scoreForIndicators(indicators);
|
|
697
|
+
const timeline = uniqueTimelineEvents(group.flatMap((invocation) => invocation.timeline)).sort(compareTimelineEvents);
|
|
698
|
+
const sessionGroup = sessionGroupsById.get(first.sessionId) ?? [];
|
|
699
|
+
const sourceSessionStartTimestamp = minString(sessionGroup.map((session) => session.startTimestamp));
|
|
700
|
+
const sourceSessionEndTimestamp = maxString(sessionGroup.map((session) => session.endTimestamp));
|
|
701
|
+
const timelineTree = sessionGroup.length > 0 ? buildSessionTimelineTree(first.sessionId, sessionGroup) : undefined;
|
|
702
|
+
const fullSessionTimeline = timelineTree
|
|
703
|
+
? uniqueTimelineEvents([
|
|
704
|
+
...timelineTree.main,
|
|
705
|
+
...timelineTree.branches.flatMap((branch) => branch.events),
|
|
706
|
+
]).sort(compareTimelineEvents)
|
|
707
|
+
: timeline;
|
|
708
|
+
const fullSessionEventCount = fullSessionTimeline.length;
|
|
709
|
+
const previewEvents = timeline.slice(0, TIMELINE_PREVIEW_EVENT_LIMIT);
|
|
710
|
+
const previewIndexes = previewEvents
|
|
711
|
+
.map((event) => event.messageIndex)
|
|
712
|
+
.filter((index) => typeof index === 'number');
|
|
713
|
+
const groupStartRecordIndex = minDefined(group.map((invocation) => minDefined(invocation.timeline.map((event) => event.messageIndex))));
|
|
714
|
+
const groupEndRecordIndex = maxDefined(group.map((invocation) => maxDefined(invocation.timeline.map((event) => event.messageIndex))));
|
|
715
|
+
const previewStartRecordIndex = minDefined(previewIndexes);
|
|
716
|
+
const previewEndRecordIndex = maxDefined(previewIndexes);
|
|
717
|
+
const fullSessionIndexes = fullSessionTimeline
|
|
718
|
+
.map((event) => event.messageIndex)
|
|
719
|
+
.filter((index) => typeof index === 'number');
|
|
720
|
+
const sessionStartRecordIndex = minDefined(fullSessionIndexes) ?? 0;
|
|
721
|
+
const sessionEndRecordIndex = maxDefined(fullSessionIndexes) ?? Math.max(0, (sessionGroup[0]?.records.length ?? 1) - 1);
|
|
722
|
+
const omittedBeforeCount = previewStartRecordIndex === undefined
|
|
723
|
+
? 0
|
|
724
|
+
: fullSessionTimeline.filter((event) => typeof event.messageIndex === 'number' && event.messageIndex < previewStartRecordIndex).length;
|
|
725
|
+
const omittedAfterCount = previewEndRecordIndex === undefined
|
|
726
|
+
? 0
|
|
727
|
+
: fullSessionTimeline.filter((event) => typeof event.messageIndex === 'number' && event.messageIndex > previewEndRecordIndex).length;
|
|
728
|
+
const observationRefs = uniqueEvidenceRefs(group.flatMap((invocation) => invocation.evidenceRefs.filter((ref) => ref.kind === 'observation')));
|
|
729
|
+
const evidenceChain = evidenceChainForTimeline(timeline, observationRefs);
|
|
730
|
+
const ruleFindings = ruleFindingsForEvidence(indicators, timeline, observationRefs, evidenceChain);
|
|
731
|
+
const assistiveInference = assistiveInferenceForEvidence(indicators, evidenceChain, ruleFindings);
|
|
732
|
+
const problemPatterns = mergeExperienceProblemPatterns(group.flatMap((invocation) => invocation.problemPatterns));
|
|
733
|
+
return {
|
|
734
|
+
id: hashParts('session', first.skillName, first.sessionId),
|
|
735
|
+
skillName: first.skillName,
|
|
736
|
+
sessionId: first.sessionId,
|
|
737
|
+
sourceTrace: first.sourceTrace,
|
|
738
|
+
sourceKind: first.sourceKind,
|
|
739
|
+
entrypoint: first.entrypoint,
|
|
740
|
+
sourceMetadata: mergeSourceMetadata(group.map((invocation) => invocation.sourceMetadata)),
|
|
741
|
+
cwd: first.cwd,
|
|
742
|
+
sourceSessionStartTimestamp,
|
|
743
|
+
sourceSessionEndTimestamp,
|
|
744
|
+
sourceSessionDurationMs: durationMsBetween(sourceSessionStartTimestamp, sourceSessionEndTimestamp),
|
|
745
|
+
startTimestamp: group.reduce((min, invocation) => invocation.startTimestamp < min ? invocation.startTimestamp : min, first.startTimestamp),
|
|
746
|
+
endTimestamp: group.reduce((max, invocation) => invocation.endTimestamp > max ? invocation.endTimestamp : max, first.endTimestamp),
|
|
747
|
+
invocationIds: group.map((invocation) => invocation.id),
|
|
748
|
+
goalSliceIds: unique(group.map((invocation) => invocation.goalSliceId)),
|
|
749
|
+
reviewPriority: priorityForScore(reviewPriorityScore),
|
|
750
|
+
reviewPriorityScore,
|
|
751
|
+
reviewBasisCodes,
|
|
752
|
+
indicators,
|
|
753
|
+
evidenceChain,
|
|
754
|
+
ruleFindings,
|
|
755
|
+
assistiveInference,
|
|
756
|
+
problemPatterns,
|
|
757
|
+
relatedObservationIds,
|
|
758
|
+
timelinePreview: previewEvents,
|
|
759
|
+
fullSessionTimeline,
|
|
760
|
+
timelineTree,
|
|
761
|
+
timelineScope: {
|
|
762
|
+
mode: 'skill_segment_window',
|
|
763
|
+
segmentStartRecordIndex: groupStartRecordIndex,
|
|
764
|
+
segmentEndRecordIndex: groupEndRecordIndex,
|
|
765
|
+
previewStartRecordIndex,
|
|
766
|
+
previewEndRecordIndex,
|
|
767
|
+
sessionStartRecordIndex,
|
|
768
|
+
sessionEndRecordIndex,
|
|
769
|
+
previewEventCount: previewEvents.length,
|
|
770
|
+
fullSessionEventCount,
|
|
771
|
+
truncated: timeline.length > previewEvents.length || omittedBeforeCount > 0 || omittedAfterCount > 0,
|
|
772
|
+
omittedBeforeCount,
|
|
773
|
+
omittedAfterCount,
|
|
774
|
+
},
|
|
775
|
+
attributionSources: unique(group.map((invocation) => invocation.attribution.source).filter(Boolean)).sort(),
|
|
776
|
+
pluginNames: unique(group.map((invocation) => invocation.attribution.pluginName).filter((value) => Boolean(value))).sort(),
|
|
777
|
+
rawSkillRefs: unique(group.map((invocation) => invocation.attribution.rawSkillRef).filter((value) => Boolean(value))).sort(),
|
|
778
|
+
commandNames: unique(group.map((invocation) => invocation.attribution.commandName).filter((value) => Boolean(value))).sort(),
|
|
779
|
+
};
|
|
780
|
+
}).sort((a, b) => {
|
|
781
|
+
if (b.reviewPriorityScore !== a.reviewPriorityScore)
|
|
782
|
+
return b.reviewPriorityScore - a.reviewPriorityScore;
|
|
783
|
+
return b.endTimestamp.localeCompare(a.endTimestamp);
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
function summarizeExperienceSkills(sessions, invocations) {
|
|
787
|
+
const bySkill = new Map();
|
|
788
|
+
for (const session of sessions) {
|
|
789
|
+
const group = bySkill.get(session.skillName) ?? [];
|
|
790
|
+
group.push(session);
|
|
791
|
+
bySkill.set(session.skillName, group);
|
|
792
|
+
}
|
|
793
|
+
const invocationCountBySkill = invocations.reduce((acc, invocation) => {
|
|
794
|
+
acc[invocation.skillName] = (acc[invocation.skillName] ?? 0) + 1;
|
|
795
|
+
return acc;
|
|
796
|
+
}, {});
|
|
797
|
+
const invocationGroupBySkill = invocations.reduce((acc, invocation) => {
|
|
798
|
+
const group = acc.get(invocation.skillName) ?? [];
|
|
799
|
+
group.push(invocation);
|
|
800
|
+
acc.set(invocation.skillName, group);
|
|
801
|
+
return acc;
|
|
802
|
+
}, new Map());
|
|
803
|
+
return Array.from(bySkill.entries()).map(([skillName, group]) => {
|
|
804
|
+
const first = group[0];
|
|
805
|
+
const skillInvocations = invocationGroupBySkill.get(skillName) ?? [];
|
|
806
|
+
const indicators = sumIndicators(group.map((session) => session.indicators));
|
|
807
|
+
const evidenceChain = sumEvidenceChains(group.map((session) => session.evidenceChain));
|
|
808
|
+
const ruleFindings = mergeRuleFindings(group.flatMap((session) => session.ruleFindings));
|
|
809
|
+
const problemPatterns = mergeExperienceProblemPatterns(group.flatMap((session) => session.problemPatterns));
|
|
810
|
+
return {
|
|
811
|
+
skillName,
|
|
812
|
+
invocationCount: invocationCountBySkill[skillName] ?? 0,
|
|
813
|
+
sessionCount: group.length,
|
|
814
|
+
sourceKinds: unique(group.map((session) => session.sourceKind)).sort(),
|
|
815
|
+
entrypoints: unique(group.map((session) => session.entrypoint).filter((value) => Boolean(value))).sort(),
|
|
816
|
+
entrypointCounts: countBy(skillInvocations.map((invocation) => invocation.entrypoint ?? invocation.sourceKind ?? 'unknown')),
|
|
817
|
+
sourceMetadataCounts: summarizeSourceMetadataCounts(skillInvocations.map((invocation) => invocation.sourceMetadata)),
|
|
818
|
+
attributionCounts: countBy(skillInvocations.map((invocation) => invocation.attribution.source || 'unknown')),
|
|
819
|
+
pluginNames: unique(skillInvocations.map((invocation) => invocation.attribution.pluginName).filter((value) => Boolean(value))).sort(),
|
|
820
|
+
rawSkillRefs: unique(skillInvocations.map((invocation) => invocation.attribution.rawSkillRef).filter((value) => Boolean(value))).sort(),
|
|
821
|
+
commandNames: unique(skillInvocations.map((invocation) => invocation.attribution.commandName).filter((value) => Boolean(value))).sort(),
|
|
822
|
+
toolCounts: sumRecordCounts(skillInvocations.map((invocation) => invocation.toolCounts)),
|
|
823
|
+
firstSeen: group.reduce((min, session) => session.startTimestamp < min ? session.startTimestamp : min, first.startTimestamp),
|
|
824
|
+
lastSeen: group.reduce((max, session) => session.endTimestamp > max ? session.endTimestamp : max, first.endTimestamp),
|
|
825
|
+
reviewFirstSessionCount: group.filter((session) => session.reviewPriority === 'review_first').length,
|
|
826
|
+
sampleReviewSessionCount: group.filter((session) => session.reviewPriority === 'sample_review').length,
|
|
827
|
+
indicators,
|
|
828
|
+
evidenceChain,
|
|
829
|
+
ruleFindings,
|
|
830
|
+
assistiveInference: assistiveInferenceForEvidence(indicators, evidenceChain, ruleFindings),
|
|
831
|
+
problemPatterns,
|
|
832
|
+
relatedObservationIds: unique(group.flatMap((session) => session.relatedObservationIds)),
|
|
833
|
+
};
|
|
834
|
+
}).sort((a, b) => {
|
|
835
|
+
const aScore = a.reviewFirstSessionCount * 100 + a.sampleReviewSessionCount * 10 + a.indicators.highObservationCount;
|
|
836
|
+
const bScore = b.reviewFirstSessionCount * 100 + b.sampleReviewSessionCount * 10 + b.indicators.highObservationCount;
|
|
837
|
+
if (bScore !== aScore)
|
|
838
|
+
return bScore - aScore;
|
|
839
|
+
return b.invocationCount - a.invocationCount;
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
function mergeSourceMetadata(values) {
|
|
843
|
+
const channels = unique(values.map((value) => value?.channel).filter((value) => Boolean(value)));
|
|
844
|
+
const senders = unique(values.map((value) => value?.sender).filter((value) => Boolean(value)));
|
|
845
|
+
const senderIds = unique(values.map((value) => value?.senderId).filter((value) => Boolean(value)));
|
|
846
|
+
const providers = unique(values.map((value) => value?.provider).filter((value) => Boolean(value)));
|
|
847
|
+
const models = unique(values.map((value) => value?.model).filter((value) => Boolean(value)));
|
|
848
|
+
const modelApis = unique(values.map((value) => value?.modelApi).filter((value) => Boolean(value)));
|
|
849
|
+
const aimaCommands = unique(values.flatMap((value) => value?.aimaCommands ?? []));
|
|
850
|
+
const merged = {};
|
|
851
|
+
if (channels.length > 0)
|
|
852
|
+
merged.channel = channels.join(', ');
|
|
853
|
+
if (senders.length > 0)
|
|
854
|
+
merged.sender = senders.join(', ');
|
|
855
|
+
if (senderIds.length > 0)
|
|
856
|
+
merged.senderId = senderIds.join(', ');
|
|
857
|
+
if (providers.length > 0)
|
|
858
|
+
merged.provider = providers.join(', ');
|
|
859
|
+
if (models.length > 0)
|
|
860
|
+
merged.model = models.join(', ');
|
|
861
|
+
if (modelApis.length > 0)
|
|
862
|
+
merged.modelApi = modelApis.join(', ');
|
|
863
|
+
if (aimaCommands.length > 0)
|
|
864
|
+
merged.aimaCommands = aimaCommands.sort();
|
|
865
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
866
|
+
}
|
|
867
|
+
function summarizeSourceMetadataCounts(values) {
|
|
868
|
+
return {
|
|
869
|
+
channels: countBy(values.map((value) => value?.channel).filter((value) => Boolean(value))),
|
|
870
|
+
senders: countBy(values.map((value) => sourceSenderLabel(value)).filter((value) => Boolean(value))),
|
|
871
|
+
aimaCommands: countBy(values.flatMap((value) => value?.aimaCommands ?? [])),
|
|
872
|
+
providers: countBy(values.map((value) => value?.provider).filter((value) => Boolean(value))),
|
|
873
|
+
models: countBy(values.map((value) => value?.model).filter((value) => Boolean(value))),
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function sourceSenderLabel(value) {
|
|
877
|
+
if (!value?.sender && !value?.senderId)
|
|
878
|
+
return undefined;
|
|
879
|
+
if (value.sender && value.senderId)
|
|
880
|
+
return `${value.sender}(${value.senderId})`;
|
|
881
|
+
return value.sender ?? value.senderId;
|
|
882
|
+
}
|
|
883
|
+
function scoreForIndicators(indicators) {
|
|
884
|
+
return indicators.highObservationCount * 3
|
|
885
|
+
+ indicators.mediumObservationCount
|
|
886
|
+
+ indicators.userCorrectionCount * 2
|
|
887
|
+
+ indicators.userInterruptionCount * 2
|
|
888
|
+
+ indicators.negativeFeedbackCount * 2
|
|
889
|
+
+ indicators.hardRuleTextHitCount
|
|
890
|
+
+ indicators.toolFailureCount
|
|
891
|
+
+ indicators.hedgingCount
|
|
892
|
+
+ indicators.explicitMarkerCount * 2;
|
|
893
|
+
}
|
|
894
|
+
function priorityForScore(score) {
|
|
895
|
+
if (score >= 3)
|
|
896
|
+
return 'review_first';
|
|
897
|
+
if (score > 0)
|
|
898
|
+
return 'sample_review';
|
|
899
|
+
return 'routine_sample';
|
|
900
|
+
}
|
|
901
|
+
function basisCodesForIndicators(indicators) {
|
|
902
|
+
const codes = [];
|
|
903
|
+
if (indicators.highObservationCount > 0)
|
|
904
|
+
codes.push('has_high_observation');
|
|
905
|
+
if (indicators.mediumObservationCount > 0)
|
|
906
|
+
codes.push('has_medium_observation');
|
|
907
|
+
if (indicators.userCorrectionCount > 0)
|
|
908
|
+
codes.push('user_correction');
|
|
909
|
+
if (indicators.userInterruptionCount > 0)
|
|
910
|
+
codes.push('user_interruption');
|
|
911
|
+
if (indicators.negativeFeedbackCount > 0)
|
|
912
|
+
codes.push('negative_feedback');
|
|
913
|
+
if (indicators.hardRuleTextHitCount > 0)
|
|
914
|
+
codes.push('hard_rule_text_hit');
|
|
915
|
+
if (indicators.toolFailureCount > 0)
|
|
916
|
+
codes.push('tool_failure');
|
|
917
|
+
if (indicators.hedgingCount > 0)
|
|
918
|
+
codes.push('hedging_signal');
|
|
919
|
+
if (indicators.explicitMarkerCount > 0)
|
|
920
|
+
codes.push('explicit_marker');
|
|
921
|
+
return codes;
|
|
922
|
+
}
|
|
923
|
+
function countTools(segment) {
|
|
924
|
+
const counts = {};
|
|
925
|
+
for (const toolCall of segment.toolCalls) {
|
|
926
|
+
counts[toolCall.tool] = (counts[toolCall.tool] ?? 0) + 1;
|
|
927
|
+
}
|
|
928
|
+
return counts;
|
|
929
|
+
}
|
|
930
|
+
function countBy(values) {
|
|
931
|
+
const counts = {};
|
|
932
|
+
for (const value of values) {
|
|
933
|
+
counts[value] = (counts[value] ?? 0) + 1;
|
|
934
|
+
}
|
|
935
|
+
return counts;
|
|
936
|
+
}
|
|
937
|
+
function sumRecordCounts(values) {
|
|
938
|
+
const counts = {};
|
|
939
|
+
for (const value of values) {
|
|
940
|
+
for (const [key, count] of Object.entries(value)) {
|
|
941
|
+
counts[key] = (counts[key] ?? 0) + count;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return counts;
|
|
945
|
+
}
|
|
946
|
+
function sumIndicators(values) {
|
|
947
|
+
return values.reduce((acc, value) => ({
|
|
948
|
+
userMessageCount: acc.userMessageCount + value.userMessageCount,
|
|
949
|
+
userFollowUpCount: acc.userFollowUpCount + value.userFollowUpCount,
|
|
950
|
+
userCorrectionCount: acc.userCorrectionCount + value.userCorrectionCount,
|
|
951
|
+
userInterruptionCount: acc.userInterruptionCount + value.userInterruptionCount,
|
|
952
|
+
negativeFeedbackCount: acc.negativeFeedbackCount + (value.negativeFeedbackCount ?? 0),
|
|
953
|
+
positiveFeedbackCount: acc.positiveFeedbackCount + (value.positiveFeedbackCount ?? 0),
|
|
954
|
+
userGoalShiftCount: acc.userGoalShiftCount + (value.userGoalShiftCount ?? 0),
|
|
955
|
+
hardRuleTextHitCount: acc.hardRuleTextHitCount + value.hardRuleTextHitCount,
|
|
956
|
+
assistantDeliverySignalCount: acc.assistantDeliverySignalCount + (value.assistantDeliverySignalCount ?? 0),
|
|
957
|
+
toolCallCount: acc.toolCallCount + value.toolCallCount,
|
|
958
|
+
toolFailureCount: acc.toolFailureCount + value.toolFailureCount,
|
|
959
|
+
highObservationCount: acc.highObservationCount + value.highObservationCount,
|
|
960
|
+
mediumObservationCount: acc.mediumObservationCount + value.mediumObservationCount,
|
|
961
|
+
hedgingCount: acc.hedgingCount + value.hedgingCount,
|
|
962
|
+
explicitMarkerCount: acc.explicitMarkerCount + value.explicitMarkerCount,
|
|
963
|
+
}), { ...ZERO_INDICATORS });
|
|
964
|
+
}
|
|
965
|
+
function sumEvidenceChains(values) {
|
|
966
|
+
return values.reduce((acc, value) => ({
|
|
967
|
+
userMessageCount: acc.userMessageCount + (value?.userMessageCount ?? 0),
|
|
968
|
+
runtimeContextCount: acc.runtimeContextCount + (value?.runtimeContextCount ?? 0),
|
|
969
|
+
skillContextCount: acc.skillContextCount + (value?.skillContextCount ?? 0),
|
|
970
|
+
assistantMessageCount: acc.assistantMessageCount + (value?.assistantMessageCount ?? 0),
|
|
971
|
+
toolUseCount: acc.toolUseCount + (value?.toolUseCount ?? 0),
|
|
972
|
+
toolResultCount: acc.toolResultCount + (value?.toolResultCount ?? 0),
|
|
973
|
+
toolFailureResultCount: acc.toolFailureResultCount + (value?.toolFailureResultCount ?? 0),
|
|
974
|
+
observationCount: acc.observationCount + (value?.observationCount ?? 0),
|
|
975
|
+
firstUserMessage: acc.firstUserMessage ?? value?.firstUserMessage,
|
|
976
|
+
firstRuntimeContext: acc.firstRuntimeContext ?? value?.firstRuntimeContext,
|
|
977
|
+
firstSkillContext: acc.firstSkillContext ?? value?.firstSkillContext,
|
|
978
|
+
firstToolUse: acc.firstToolUse ?? value?.firstToolUse,
|
|
979
|
+
firstToolFailure: acc.firstToolFailure ?? value?.firstToolFailure,
|
|
980
|
+
lastAssistantMessage: value?.lastAssistantMessage ?? acc.lastAssistantMessage,
|
|
981
|
+
}), {
|
|
982
|
+
userMessageCount: 0,
|
|
983
|
+
runtimeContextCount: 0,
|
|
984
|
+
skillContextCount: 0,
|
|
985
|
+
assistantMessageCount: 0,
|
|
986
|
+
toolUseCount: 0,
|
|
987
|
+
toolResultCount: 0,
|
|
988
|
+
toolFailureResultCount: 0,
|
|
989
|
+
observationCount: 0,
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
function mergeRuleFindings(values) {
|
|
993
|
+
const byCode = new Map();
|
|
994
|
+
for (const value of values) {
|
|
995
|
+
const existing = byCode.get(value.code);
|
|
996
|
+
if (existing) {
|
|
997
|
+
existing.count += value.count;
|
|
998
|
+
existing.evidenceRefs = uniqueEvidenceRefs([...existing.evidenceRefs, ...value.evidenceRefs]).slice(0, 5);
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
byCode.set(value.code, { ...value, evidenceRefs: uniqueEvidenceRefs(value.evidenceRefs).slice(0, 5) });
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return Array.from(byCode.values()).sort((a, b) => {
|
|
1005
|
+
const rank = { attention: 0, sample: 1, normal: 2 };
|
|
1006
|
+
if (rank[a.level] !== rank[b.level])
|
|
1007
|
+
return rank[a.level] - rank[b.level];
|
|
1008
|
+
return b.count - a.count;
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
function uniqueTimelineEvents(events) {
|
|
1012
|
+
const byId = new Map();
|
|
1013
|
+
for (const event of events) {
|
|
1014
|
+
byId.set(event.id, event);
|
|
1015
|
+
}
|
|
1016
|
+
return Array.from(byId.values());
|
|
1017
|
+
}
|
|
1018
|
+
function compareTimelineEvents(a, b) {
|
|
1019
|
+
const ta = a.timestamp;
|
|
1020
|
+
const tb = b.timestamp;
|
|
1021
|
+
// 双方都有非空 timestamp 且不同 → 按时间穿插(主线 + subagent 真实交互序)
|
|
1022
|
+
if (ta && tb && ta !== tb) {
|
|
1023
|
+
return ta.localeCompare(tb);
|
|
1024
|
+
}
|
|
1025
|
+
// 同一条 trace 内 → 按 messageIndex 派生的 order(跨 trace 比 order 没意义)
|
|
1026
|
+
if (a.sourceTrace === b.sourceTrace) {
|
|
1027
|
+
return a.order - b.order;
|
|
1028
|
+
}
|
|
1029
|
+
// 跨 trace 且 timestamp 不可比 → 主线优先(避免缺 timestamp 时 subagent 顶到最前)
|
|
1030
|
+
const roleRank = (event) => event.traceRole === 'main' || event.traceRole === 'standalone' ? 0 : 1;
|
|
1031
|
+
const rankDiff = roleRank(a) - roleRank(b);
|
|
1032
|
+
if (rankDiff !== 0)
|
|
1033
|
+
return rankDiff;
|
|
1034
|
+
// 同 traceRole 跨文件兜底:sourceTrace 字典序保证稳定
|
|
1035
|
+
return a.sourceTrace.localeCompare(b.sourceTrace);
|
|
1036
|
+
}
|
|
1037
|
+
function buildSessionTimelineTree(sessionId, sessions) {
|
|
1038
|
+
const mainSession = sessions.find((session) => session.traceRole === 'main')
|
|
1039
|
+
?? sessions.find((session) => session.traceRole === 'standalone');
|
|
1040
|
+
const main = mainSession ? buildTimelineWindow(mainSession, 0, mainSession.records.length) : [];
|
|
1041
|
+
const branches = sessions
|
|
1042
|
+
.filter((session) => !mainSession || session !== mainSession)
|
|
1043
|
+
.map((session) => {
|
|
1044
|
+
const events = buildTimelineWindow(session, 0, session.records.length);
|
|
1045
|
+
const attachTo = inferSubagentAttachment(main, session);
|
|
1046
|
+
return {
|
|
1047
|
+
id: hashParts('timeline-branch', session.sourcePath),
|
|
1048
|
+
label: session.traceLabel ?? session.sourcePath.split('/').pop() ?? 'subagent',
|
|
1049
|
+
sourceTrace: session.sourcePath,
|
|
1050
|
+
traceRole: session.traceRole ?? 'subagent',
|
|
1051
|
+
attachTo,
|
|
1052
|
+
events,
|
|
1053
|
+
};
|
|
1054
|
+
});
|
|
1055
|
+
return {
|
|
1056
|
+
sessionId,
|
|
1057
|
+
main,
|
|
1058
|
+
branches,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function inferSubagentAttachment(mainEvents, branchSession) {
|
|
1062
|
+
const startedAt = branchSession.startTimestamp;
|
|
1063
|
+
const taskUses = mainEvents.filter((event) => event.kind === 'tool_use' && /^(Task|Agent|Skill)$/i.test(event.toolName ?? ''));
|
|
1064
|
+
const candidates = startedAt
|
|
1065
|
+
? taskUses.filter((event) => !event.timestamp || event.timestamp <= startedAt)
|
|
1066
|
+
: taskUses;
|
|
1067
|
+
const event = candidates.at(-1) ?? taskUses.at(-1);
|
|
1068
|
+
if (!event)
|
|
1069
|
+
return undefined;
|
|
1070
|
+
return {
|
|
1071
|
+
sourceTrace: event.sourceTrace,
|
|
1072
|
+
messageIndex: event.messageIndex,
|
|
1073
|
+
toolUseId: event.toolUseId,
|
|
1074
|
+
label: event.toolName,
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
function minDefined(values) {
|
|
1078
|
+
const filtered = values.filter((value) => typeof value === 'number');
|
|
1079
|
+
return filtered.length > 0 ? Math.min(...filtered) : undefined;
|
|
1080
|
+
}
|
|
1081
|
+
function maxDefined(values) {
|
|
1082
|
+
const filtered = values.filter((value) => typeof value === 'number');
|
|
1083
|
+
return filtered.length > 0 ? Math.max(...filtered) : undefined;
|
|
1084
|
+
}
|
|
1085
|
+
function minString(values) {
|
|
1086
|
+
const filtered = values.filter((value) => Boolean(value));
|
|
1087
|
+
return filtered.length > 0 ? filtered.reduce((min, value) => value < min ? value : min, filtered[0]) : undefined;
|
|
1088
|
+
}
|
|
1089
|
+
function maxString(values) {
|
|
1090
|
+
const filtered = values.filter((value) => Boolean(value));
|
|
1091
|
+
return filtered.length > 0 ? filtered.reduce((max, value) => value > max ? value : max, filtered[0]) : undefined;
|
|
1092
|
+
}
|
|
1093
|
+
function uniqueEvidenceRefs(refs) {
|
|
1094
|
+
const byId = new Map();
|
|
1095
|
+
for (const ref of refs) {
|
|
1096
|
+
byId.set(ref.id, ref);
|
|
1097
|
+
}
|
|
1098
|
+
return Array.from(byId.values());
|
|
1099
|
+
}
|
|
1100
|
+
function inferUserGoal(userRefs) {
|
|
1101
|
+
const first = userRefs.find((ref) => ref.snippet && !ref.snippet.includes('tool_result'));
|
|
1102
|
+
return snippet(first?.snippet, 180);
|
|
1103
|
+
}
|
|
1104
|
+
export function findUserCorrectionMatches(value) {
|
|
1105
|
+
const ranges = [];
|
|
1106
|
+
for (const term of PHRASE_USER_CORRECTION_TERMS) {
|
|
1107
|
+
pushTermMatches(value, term, ranges, false);
|
|
1108
|
+
}
|
|
1109
|
+
for (const term of BOUNDED_USER_CORRECTION_TERMS) {
|
|
1110
|
+
pushTermMatches(value, term, ranges, true);
|
|
1111
|
+
}
|
|
1112
|
+
return ranges.sort((a, b) => a.start - b.start);
|
|
1113
|
+
}
|
|
1114
|
+
export function hasUserCorrectionSignal(value) {
|
|
1115
|
+
return findUserCorrectionMatches(value).length > 0;
|
|
1116
|
+
}
|
|
1117
|
+
export function findUserGoalShiftMatches(value) {
|
|
1118
|
+
const ranges = [];
|
|
1119
|
+
for (const term of USER_GOAL_SHIFT_TERMS) {
|
|
1120
|
+
pushTermMatches(value, term, ranges, false);
|
|
1121
|
+
}
|
|
1122
|
+
return ranges.sort((a, b) => a.start - b.start);
|
|
1123
|
+
}
|
|
1124
|
+
export function hasUserGoalShiftSignal(value) {
|
|
1125
|
+
return findUserGoalShiftMatches(value).length > 0;
|
|
1126
|
+
}
|
|
1127
|
+
export function findNegativeFeedbackMatches(value) {
|
|
1128
|
+
const ranges = [];
|
|
1129
|
+
for (const term of [...NEGATIVE_FEEDBACK_TERMS].sort((a, b) => b.length - a.length)) {
|
|
1130
|
+
pushTermMatches(value, term, ranges, false);
|
|
1131
|
+
}
|
|
1132
|
+
for (const term of [...BOUNDED_NEGATIVE_FEEDBACK_TERMS].sort((a, b) => b.length - a.length)) {
|
|
1133
|
+
pushTermMatches(value, term, ranges, true);
|
|
1134
|
+
}
|
|
1135
|
+
return ranges.sort((a, b) => a.start - b.start);
|
|
1136
|
+
}
|
|
1137
|
+
export function hasNegativeFeedbackSignal(value) {
|
|
1138
|
+
return findNegativeFeedbackMatches(value).length > 0;
|
|
1139
|
+
}
|
|
1140
|
+
export function findPositiveFeedbackMatches(value) {
|
|
1141
|
+
const ranges = [];
|
|
1142
|
+
for (const term of POSITIVE_FEEDBACK_TERMS) {
|
|
1143
|
+
pushTermMatches(value, term, ranges, false, true);
|
|
1144
|
+
}
|
|
1145
|
+
return ranges.sort((a, b) => a.start - b.start);
|
|
1146
|
+
}
|
|
1147
|
+
export function hasPositiveFeedbackSignal(value) {
|
|
1148
|
+
return findPositiveFeedbackMatches(value).length > 0;
|
|
1149
|
+
}
|
|
1150
|
+
function pushTermMatches(value, term, ranges, requireBoundary, caseInsensitive = false) {
|
|
1151
|
+
const haystack = caseInsensitive ? value.toLowerCase() : value;
|
|
1152
|
+
const needle = caseInsensitive ? term.toLowerCase() : term;
|
|
1153
|
+
let index = haystack.indexOf(needle);
|
|
1154
|
+
while (index >= 0) {
|
|
1155
|
+
const end = index + needle.length;
|
|
1156
|
+
if ((!requireBoundary || (isTextBoundary(value[index - 1]) && isTextBoundary(value[end])))
|
|
1157
|
+
&& !ranges.some((range) => index < range.end && end > range.start)) {
|
|
1158
|
+
ranges.push({ start: index, end });
|
|
1159
|
+
}
|
|
1160
|
+
index = haystack.indexOf(needle, index + needle.length);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function isTextBoundary(value) {
|
|
1164
|
+
if (value === undefined)
|
|
1165
|
+
return true;
|
|
1166
|
+
return TEXT_BOUNDARY_CHARS.has(value);
|
|
1167
|
+
}
|
|
1168
|
+
function isSkillContextRecord(record, text) {
|
|
1169
|
+
const meta = record;
|
|
1170
|
+
if (meta.isMeta === true && typeof meta.sourceToolUseID === 'string')
|
|
1171
|
+
return true;
|
|
1172
|
+
return /^Base directory for this skill:\s+.+(?:\n| )#\s+[a-z0-9][\w.-]*/i.test(text);
|
|
1173
|
+
}
|
|
1174
|
+
function isRuntimeContextRecord(record, text) {
|
|
1175
|
+
const meta = record;
|
|
1176
|
+
if (/^Conversation info \(untrusted metadata\):\s*```json/i.test(text))
|
|
1177
|
+
return true;
|
|
1178
|
+
if (meta.entrypoint !== 'sdk-ts' || typeof meta.promptId !== 'string')
|
|
1179
|
+
return false;
|
|
1180
|
+
return /^进入.+流程。当前页面已经完成本地工作区恢复/.test(text)
|
|
1181
|
+
|| /gui-workflow route/.test(text)
|
|
1182
|
+
|| /当前页面已经完成本地工作区恢复/.test(text);
|
|
1183
|
+
}
|
|
1184
|
+
function timestampOf(record) {
|
|
1185
|
+
if (!record || typeof record !== 'object')
|
|
1186
|
+
return undefined;
|
|
1187
|
+
const value = record.timestamp;
|
|
1188
|
+
return typeof value === 'string' ? value : undefined;
|
|
1189
|
+
}
|
|
1190
|
+
function sourceKindForPath(path) {
|
|
1191
|
+
if (path.includes('/openclaw') || path.includes('/.openclaw/'))
|
|
1192
|
+
return 'openclaw';
|
|
1193
|
+
if (path.endsWith('.jsonl'))
|
|
1194
|
+
return 'claude';
|
|
1195
|
+
if (path.endsWith('.log'))
|
|
1196
|
+
return 'markdown_log';
|
|
1197
|
+
return 'unknown';
|
|
1198
|
+
}
|
|
1199
|
+
function inferEntrypointFromRecords(session) {
|
|
1200
|
+
for (const record of session.records) {
|
|
1201
|
+
if (!record || typeof record !== 'object')
|
|
1202
|
+
continue;
|
|
1203
|
+
const entrypoint = record.entrypoint;
|
|
1204
|
+
if (typeof entrypoint === 'string' && entrypoint.trim())
|
|
1205
|
+
return entrypoint;
|
|
1206
|
+
}
|
|
1207
|
+
if (session.entrypoint)
|
|
1208
|
+
return session.entrypoint;
|
|
1209
|
+
if (session.sourceKind === 'openclaw')
|
|
1210
|
+
return 'openclaw';
|
|
1211
|
+
if (session.sourceKind === 'markdown_log')
|
|
1212
|
+
return 'markdown_log';
|
|
1213
|
+
if (session.sourcePath.endsWith('.log'))
|
|
1214
|
+
return 'markdown_log';
|
|
1215
|
+
return undefined;
|
|
1216
|
+
}
|
|
1217
|
+
function snippet(value, max = 240) {
|
|
1218
|
+
const text = typeof value === 'string' ? value : String(value ?? '');
|
|
1219
|
+
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
1220
|
+
return normalized ? normalized.slice(0, max) : undefined;
|
|
1221
|
+
}
|
|
1222
|
+
function fullText(value) {
|
|
1223
|
+
const text = typeof value === 'string' ? value : String(value ?? '');
|
|
1224
|
+
const normalized = text.trim();
|
|
1225
|
+
return normalized || undefined;
|
|
1226
|
+
}
|
|
1227
|
+
function unique(values) {
|
|
1228
|
+
return Array.from(new Set(values));
|
|
1229
|
+
}
|
|
1230
|
+
function hashParts(...parts) {
|
|
1231
|
+
return createHash('sha256').update(parts.join('\u0000')).digest('hex').slice(0, 16);
|
|
1232
|
+
}
|
|
1233
|
+
//# sourceMappingURL=experience.js.map
|