@kkeve/suite 0.0.15 → 0.0.18

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 (115) hide show
  1. package/dist/bin/keve.js +2 -1
  2. package/dist/commands/clean.js +2 -2
  3. package/dist/commands/config.js +7 -7
  4. package/dist/commands/init.js +2 -2
  5. package/dist/commands/report.js +7 -7
  6. package/dist/commands/run.js +12 -11
  7. package/dist/commands/test.js +7 -7
  8. package/dist/config.js +7 -7
  9. package/dist/core/action-log-writer.js +4 -1
  10. package/dist/core/decorator/keve-decorators.d.ts +128 -0
  11. package/dist/core/decorator/keve-decorators.js +254 -0
  12. package/dist/core/decorator/keve-host.d.ts +40 -0
  13. package/dist/core/decorator/keve-host.js +33 -0
  14. package/dist/core/{keve-registry.d.ts → decorator/keve-registry.d.ts} +19 -0
  15. package/dist/core/{keve-registry.js → decorator/keve-registry.js} +13 -1
  16. package/dist/core/generateReport/report.d.ts +1 -1
  17. package/dist/core/generateReport/report.js +2 -2
  18. package/dist/core/generateReport/reportData.d.ts +3 -0
  19. package/dist/core/generateReport/reportData.js +49 -15
  20. package/dist/core/judge/confidence.d.ts +119 -0
  21. package/dist/core/judge/confidence.js +295 -0
  22. package/dist/core/judge/goal-heuristics.d.ts +32 -0
  23. package/dist/core/judge/goal-heuristics.js +128 -0
  24. package/dist/core/judge/pngCodec.d.ts +23 -0
  25. package/dist/core/judge/pngCodec.js +215 -0
  26. package/dist/core/judge/screenshotDiff.d.ts +112 -0
  27. package/dist/core/judge/screenshotDiff.js +420 -0
  28. package/dist/core/page-object/definePage.d.ts +95 -0
  29. package/dist/core/page-object/definePage.js +62 -0
  30. package/dist/core/page-object/elementResolver.d.ts +32 -0
  31. package/dist/core/page-object/elementResolver.js +236 -0
  32. package/dist/core/page-object/engine-types.d.ts +140 -0
  33. package/dist/core/page-object/engine-types.js +34 -0
  34. package/dist/core/sso-cookie.d.ts +18 -0
  35. package/dist/core/sso-cookie.js +10 -0
  36. package/dist/dsl/index.d.ts +15 -0
  37. package/dist/dsl/index.js +29 -0
  38. package/dist/dsl/locatorSource.d.ts +25 -0
  39. package/dist/dsl/locatorSource.js +323 -0
  40. package/dist/dsl/ops.d.ts +46 -0
  41. package/dist/dsl/ops.js +95 -0
  42. package/dist/dsl/toDecorator.d.ts +98 -0
  43. package/dist/dsl/toDecorator.js +747 -0
  44. package/dist/engine-cypress/adapter.d.ts +108 -0
  45. package/dist/engine-cypress/adapter.js +514 -0
  46. package/dist/engine-cypress/agent-page.d.ts +159 -0
  47. package/dist/engine-cypress/agent-page.js +566 -0
  48. package/dist/engine-cypress/agentDom.d.ts +22 -0
  49. package/dist/engine-cypress/agentDom.js +590 -0
  50. package/dist/engine-cypress/bridge.d.ts +39 -0
  51. package/dist/engine-cypress/bridge.js +598 -0
  52. package/dist/engine-cypress/cdp.d.ts +78 -0
  53. package/dist/engine-cypress/cdp.js +339 -0
  54. package/dist/engine-cypress/host.d.ts +22 -0
  55. package/dist/engine-cypress/host.js +480 -0
  56. package/dist/engine-cypress/index.d.ts +26 -0
  57. package/dist/engine-cypress/index.js +58 -0
  58. package/dist/engine-cypress/keveGoal.d.ts +43 -0
  59. package/dist/engine-cypress/keveGoal.js +265 -0
  60. package/dist/engine-cypress/pageOps.d.ts +41 -0
  61. package/dist/engine-cypress/pageOps.js +439 -0
  62. package/dist/engine-cypress/reporter.d.ts +20 -0
  63. package/dist/engine-cypress/reporter.js +128 -0
  64. package/dist/engine-cypress/setup.d.ts +21 -0
  65. package/dist/engine-cypress/setup.js +27 -0
  66. package/dist/engine-cypress/shotBuffer.d.ts +40 -0
  67. package/dist/engine-cypress/shotBuffer.js +41 -0
  68. package/dist/engine-cypress/state.d.ts +56 -0
  69. package/dist/engine-cypress/state.js +81 -0
  70. package/dist/engine-playwright/engineAdapter.d.ts +78 -0
  71. package/dist/engine-playwright/engineAdapter.js +371 -0
  72. package/dist/engine-playwright/global-setup.d.ts +28 -0
  73. package/dist/engine-playwright/global-setup.js +142 -0
  74. package/dist/{core → engine-playwright}/keve-report.d.ts +0 -1
  75. package/dist/engine-playwright/keve-report.js +137 -0
  76. package/dist/{core → engine-playwright}/keve-test.d.ts +10 -8
  77. package/dist/{core → engine-playwright}/keve-test.js +204 -200
  78. package/dist/{core → engine-playwright}/pw-config.js +8 -7
  79. package/dist/index.d.ts +13 -8
  80. package/dist/index.js +43 -28
  81. package/dist/page-agent/agent.d.ts +82 -7
  82. package/dist/page-agent/agent.js +252 -88
  83. package/dist/page-agent/diagnostics.d.ts +184 -0
  84. package/dist/page-agent/diagnostics.js +377 -0
  85. package/dist/page-agent/hooks.d.ts +3 -2
  86. package/dist/page-agent/hooks.js +4 -3
  87. package/dist/page-agent/page-like.d.ts +116 -0
  88. package/dist/page-agent/page-like.js +14 -0
  89. package/dist/page-agent/playwright-page.d.ts +20 -0
  90. package/dist/page-agent/playwright-page.js +98 -0
  91. package/dist/page-agent/system-prompt.d.ts +7 -0
  92. package/dist/page-agent/system-prompt.js +10 -0
  93. package/dist/page-agent/tools.d.ts +1 -1
  94. package/dist/page-agent/tools.js +144 -8
  95. package/package.json +24 -11
  96. package/scripts/gen-system-prompt.mjs +36 -0
  97. package/scripts/run-dsl-task.mjs +652 -0
  98. package/scripts/runtime/cypress-preprocessor.mjs +44 -0
  99. package/scripts/runtime/cypress.config.mjs +35 -0
  100. package/scripts/runtime/playwright.config.mjs +41 -0
  101. package/scripts/verify-shot-diff.mjs +243 -0
  102. package/dist/commands/auth-update.d.ts +0 -16
  103. package/dist/commands/auth-update.js +0 -88
  104. package/dist/core/auth.d.ts +0 -17
  105. package/dist/core/auth.js +0 -224
  106. package/dist/core/cdp-resolver.d.ts +0 -5
  107. package/dist/core/cdp-resolver.js +0 -93
  108. package/dist/core/global-setup.d.ts +0 -6
  109. package/dist/core/global-setup.js +0 -123
  110. package/dist/core/keve-decorators.d.ts +0 -51
  111. package/dist/core/keve-decorators.js +0 -95
  112. package/dist/core/keve-report.js +0 -376
  113. /package/dist/core/{keve-aspect.d.ts → decorator/keve-aspect.d.ts} +0 -0
  114. /package/dist/core/{keve-aspect.js → decorator/keve-aspect.js} +0 -0
  115. /package/dist/{core → engine-playwright}/pw-config.d.ts +0 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * confidence — 用例结论与置信度的**引擎无关**判定
3
+ *
4
+ * 为什么单独成模块:Playwright 侧走 KeveReporter.onTestEnd,Cypress 侧没有
5
+ * in-process reporter,只能在 spec 里把结果经 bridge 送回 Node 后自行落盘。
6
+ * 两端若各写一份分类逻辑,报告必然漂移(同一失败在一边是 env、另一边是
7
+ * unknown)。这里把「附件解析 → Agent 结论 → 错误分类 → confidence 记录」
8
+ * 收敛成唯一实现,两端都只是喂数据。
9
+ *
10
+ * 约束:本模块只依赖类型与纯函数,Node 侧运行(读取 Buffer)。
11
+ */
12
+ import { type RuntimeDiagnostics } from '../../page-agent/diagnostics.js';
13
+ /** keveGoalResult attachment 的数据形态(Playwright / Cypress 完全一致) */
14
+ export interface StepResultAttachment {
15
+ step: string;
16
+ expected: string;
17
+ precondition?: string;
18
+ order: number;
19
+ success: boolean;
20
+ conclusion?: 'pass' | 'fail' | 'blocked';
21
+ actions: Array<{
22
+ tool?: string;
23
+ role?: string;
24
+ name?: string;
25
+ url?: string;
26
+ text?: string;
27
+ reason?: string;
28
+ success?: boolean;
29
+ verdict?: string;
30
+ conclusion?: 'pass' | 'fail' | 'blocked';
31
+ result?: string;
32
+ error?: string;
33
+ toolOutput?: string;
34
+ evaluation?: string;
35
+ memory?: string;
36
+ nextGoal?: string;
37
+ screenshotPath?: string;
38
+ }>;
39
+ finalSnapshot?: string;
40
+ diagnosticHints?: string[];
41
+ /** 运行诊断:性能、网络、浏览器错误与质量信号 */
42
+ diagnostics?: RuntimeDiagnostics;
43
+ goalScreenshotBefore?: string;
44
+ goalScreenshotAfter?: string;
45
+ agentScreenshots?: string[];
46
+ }
47
+ /** 供分类使用的错误摘要(Error 实例跨引擎不可序列化,统一降级成 name+message) */
48
+ export interface ConfidenceError {
49
+ message: string;
50
+ name?: string;
51
+ }
52
+ export interface AttachmentLike {
53
+ name?: string;
54
+ contentType?: string;
55
+ body?: any;
56
+ /** 附件在磁盘上的路径(与 Playwright 报告 attachments[].path 同形) */
57
+ path?: string;
58
+ }
59
+ export interface ClassifyInput {
60
+ /** 用例完整标题(`${sceneId}: ${description}`) */
61
+ title: string;
62
+ /** 执行状态:passed | failed | timedOut | skipped | unknown(Playwright 语义) */
63
+ status: string;
64
+ /** 失败摘要;无失败传 null */
65
+ error?: ConfidenceError | string | null;
66
+ /** keveGoalResult 附件(已解析或原始附件均可,二选一) */
67
+ steps: StepResultAttachment[];
68
+ /** 原始附件列表(用于诊断提示与 keveScreenshots 标签) */
69
+ attachments?: AttachmentLike[];
70
+ /** 是否跳过 AI 评估(由调用方强制指定,如 Cypress 侧无 AI 阶段) */
71
+ forceSkipAI?: boolean;
72
+ }
73
+ /** confidence-data.jsonl 的单行记录 */
74
+ export interface ConfidenceRecord {
75
+ title: string;
76
+ data: string;
77
+ confidence: number;
78
+ thought: string;
79
+ errorCategory: ErrorCategory;
80
+ keveScreenshots?: string[];
81
+ steps: StepResultAttachment[];
82
+ diagnosticHint?: string;
83
+ /** 运行诊断(用例级聚合:各步骤求和/去重) */
84
+ diagnostics?: RuntimeDiagnostics;
85
+ }
86
+ export type ErrorCategory = 'script' | 'env' | 'assert' | 'visual' | 'text-mismatch' | 'incomplete' | 'pass' | 'unknown';
87
+ /** confidence values by errorCategory — 给出有意义的分值而不是一律 0 */
88
+ export declare const CONFIDENCE_MAP: Record<string, number>;
89
+ /** 环境/基础设施错误特征(与 keve-decorators 的历史判定保持同源) */
90
+ export declare const ENV_ERROR_PATTERNS: RegExp;
91
+ /** 解码附件体:Playwright JSON reporter 会把 Buffer 序列化成 base64 字符串 */
92
+ export declare function decodeAttachmentBody(body: any): string;
93
+ /** 从附件列表解析全部 keveGoalResult → 统一步骤数组 */
94
+ export declare function parseStepsFromAttachments(attachments: AttachmentLike[] | undefined): StepResultAttachment[];
95
+ /** 从附件提取 Agent 结论(result + text)。串行执行下取最后一步 */
96
+ export declare function extractAgentConclusion(steps: StepResultAttachment[]): {
97
+ result: 'pass' | 'fail' | 'blocked' | 'incomplete';
98
+ text: string;
99
+ } | undefined;
100
+ /** 由 keveGoalResult(优先)+ keveDiagnosticHint(兜底)拼诊断提示 */
101
+ export declare function buildDiagnosticHint(attachments: AttachmentLike[] | undefined): string | undefined;
102
+ /** keveAssert-* 附件 → 截图标签列表(报告只保留 label) */
103
+ export declare function parseKeveScreenshotLabels(attachments: AttachmentLike[] | undefined): string[];
104
+ /**
105
+ * 分类并生成 confidence 记录。
106
+ *
107
+ * 判定优先级:状态 → Agent 结论 → 错误特征兜底。两端共用同一条链路,
108
+ * 所以「同一个失败模式在 Playwright 与 Cypress 下得到同一个 errorCategory」。
109
+ */
110
+ export declare function buildConfidenceRecord(input: ClassifyInput): {
111
+ record: ConfidenceRecord;
112
+ skipAI: boolean;
113
+ log: string;
114
+ };
115
+ /**
116
+ * 落盘前的记录清洗:剔除大字段(base64 截图与整页快照),
117
+ * 截图以文件路径形式存在于 goalScreenshotBefore/After 与 actions[].screenshotPath。
118
+ */
119
+ export declare function sanitizeRecord(record: ConfidenceRecord): ConfidenceRecord;
@@ -0,0 +1,295 @@
1
+ "use strict";
2
+ /**
3
+ * confidence — 用例结论与置信度的**引擎无关**判定
4
+ *
5
+ * 为什么单独成模块:Playwright 侧走 KeveReporter.onTestEnd,Cypress 侧没有
6
+ * in-process reporter,只能在 spec 里把结果经 bridge 送回 Node 后自行落盘。
7
+ * 两端若各写一份分类逻辑,报告必然漂移(同一失败在一边是 env、另一边是
8
+ * unknown)。这里把「附件解析 → Agent 结论 → 错误分类 → confidence 记录」
9
+ * 收敛成唯一实现,两端都只是喂数据。
10
+ *
11
+ * 约束:本模块只依赖类型与纯函数,Node 侧运行(读取 Buffer)。
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.ENV_ERROR_PATTERNS = exports.CONFIDENCE_MAP = void 0;
15
+ exports.decodeAttachmentBody = decodeAttachmentBody;
16
+ exports.parseStepsFromAttachments = parseStepsFromAttachments;
17
+ exports.extractAgentConclusion = extractAgentConclusion;
18
+ exports.buildDiagnosticHint = buildDiagnosticHint;
19
+ exports.parseKeveScreenshotLabels = parseKeveScreenshotLabels;
20
+ exports.buildConfidenceRecord = buildConfidenceRecord;
21
+ exports.sanitizeRecord = sanitizeRecord;
22
+ const diagnostics_js_1 = require("../../page-agent/diagnostics.js");
23
+ // ─── 常量 ────────────────────────────────────────────────────────────
24
+ /** confidence values by errorCategory — 给出有意义的分值而不是一律 0 */
25
+ exports.CONFIDENCE_MAP = {
26
+ pass: 100, // 测试通过,确定性最高
27
+ script: 95, // 脚本代码错误,结果确定性高
28
+ visual: 80, // 视觉断言已评估,置信度较高
29
+ 'text-mismatch': 70, // UI 文案格式不匹配(功能逻辑正确,措辞变了)
30
+ assert: 50, // 断言失败,需人工确认
31
+ incomplete: 0, // 执行被中断(超时/action 耗尽),无法得出结论
32
+ env: 0, // 环境问题,测试结果不可信
33
+ unknown: 0, // 未知异常,无参考价值
34
+ };
35
+ /** 环境/基础设施错误特征(与 keve-decorators 的历史判定保持同源) */
36
+ exports.ENV_ERROR_PATTERNS = /timeout|timed out|locator.*not found|navigation.*intercepted|net::|ERR_|authenticate|redirect|ECONNREFUSED|socket hang up|Cannot navigate to invalid URL|Protocol error.*Page\.navigate/i;
37
+ // ─── 附件解析 ────────────────────────────────────────────────────────
38
+ /** 解码附件体:Playwright JSON reporter 会把 Buffer 序列化成 base64 字符串 */
39
+ function decodeAttachmentBody(body) {
40
+ if (typeof body === 'string') {
41
+ // 先按 base64 解一次,仍是 JSON 就用解码结果;否则按原始字符串处理
42
+ try {
43
+ const decoded = Buffer.from(body, 'base64').toString('utf8');
44
+ JSON.parse(decoded);
45
+ return decoded;
46
+ }
47
+ catch {
48
+ return body;
49
+ }
50
+ }
51
+ if (Buffer.isBuffer(body))
52
+ return body.toString('utf8');
53
+ if (typeof body === 'object' && body !== null)
54
+ return JSON.stringify(body);
55
+ return String(body || '');
56
+ }
57
+ /** 从附件列表解析全部 keveGoalResult → 统一步骤数组 */
58
+ function parseStepsFromAttachments(attachments) {
59
+ const results = [];
60
+ for (const att of attachments || []) {
61
+ if (att?.name !== 'keveGoalResult' || !att.body)
62
+ continue;
63
+ try {
64
+ results.push(JSON.parse(decodeAttachmentBody(att.body)));
65
+ }
66
+ catch { /* skip malformed attachment */ }
67
+ }
68
+ return results;
69
+ }
70
+ /** 从附件提取 Agent 结论(result + text)。串行执行下取最后一步 */
71
+ function extractAgentConclusion(steps) {
72
+ if (steps.length === 0)
73
+ return undefined;
74
+ const lastStep = steps[steps.length - 1];
75
+ if (lastStep.conclusion) {
76
+ const doneAction = lastStep.actions?.find((a) => a.tool === 'done');
77
+ return { result: lastStep.conclusion, text: doneAction?.text || '' };
78
+ }
79
+ if (lastStep.actions) {
80
+ for (let j = lastStep.actions.length - 1; j >= 0; j--) {
81
+ const action = lastStep.actions[j];
82
+ if (action?.tool === 'done' && action?.conclusion) {
83
+ return { result: action.conclusion, text: action.text || '' };
84
+ }
85
+ }
86
+ }
87
+ return { result: 'incomplete', text: '执行被中断,Agent未完成目标' };
88
+ }
89
+ /** 由 keveGoalResult(优先)+ keveDiagnosticHint(兜底)拼诊断提示 */
90
+ function buildDiagnosticHint(attachments) {
91
+ const allHints = [];
92
+ for (const gr of parseStepsFromAttachments(attachments)) {
93
+ if (gr.success)
94
+ continue;
95
+ if (gr.diagnosticHints?.length)
96
+ allHints.push(...gr.diagnosticHints);
97
+ }
98
+ if (allHints.length)
99
+ return allHints.join(';');
100
+ const hint = (attachments || []).find((a) => a.name === 'keveDiagnosticHint' && a.body);
101
+ if (!hint)
102
+ return undefined;
103
+ try {
104
+ const data = JSON.parse(decodeAttachmentBody(hint.body));
105
+ return data.hints?.join(';') || undefined;
106
+ }
107
+ catch {
108
+ return undefined;
109
+ }
110
+ }
111
+ /** keveAssert-* 附件 → 截图标签列表(报告只保留 label) */
112
+ function parseKeveScreenshotLabels(attachments) {
113
+ const labels = [];
114
+ for (const att of attachments || []) {
115
+ if (att?.name?.startsWith('keveAssert-') && att.contentType === 'application/json' && att.body) {
116
+ labels.push(att.name.replace('keveAssert-', ''));
117
+ }
118
+ }
119
+ return labels;
120
+ }
121
+ // ─── 分类主逻辑 ──────────────────────────────────────────────────────
122
+ function normalizeError(error) {
123
+ if (!error)
124
+ return undefined;
125
+ if (typeof error === 'string')
126
+ return { message: error };
127
+ return { message: String(error.message || ''), name: error.name };
128
+ }
129
+ /**
130
+ * 分类并生成 confidence 记录。
131
+ *
132
+ * 判定优先级:状态 → Agent 结论 → 错误特征兜底。两端共用同一条链路,
133
+ * 所以「同一个失败模式在 Playwright 与 Cypress 下得到同一个 errorCategory」。
134
+ */
135
+ function buildConfidenceRecord(input) {
136
+ const attachments = input.attachments || [];
137
+ const steps = input.steps?.length ? input.steps : parseStepsFromAttachments(attachments);
138
+ const agentConclusion = extractAgentConclusion(steps);
139
+ const diagnosticHint = buildDiagnosticHint(attachments);
140
+ const diagnostics = (0, diagnostics_js_1.mergeRuntimeDiagnostics)(steps.map((s) => s.diagnostics));
141
+ let effectiveCategory = 'unknown';
142
+ let skipAI = false;
143
+ let classifiedMessage = '';
144
+ if (input.status === 'skipped') {
145
+ const record = {
146
+ title: input.title,
147
+ data: '跳过',
148
+ confidence: 0,
149
+ thought: '用例被跳过(非自动执行)',
150
+ errorCategory: 'unknown',
151
+ steps,
152
+ diagnosticHint: undefined,
153
+ diagnostics,
154
+ };
155
+ return { record, skipAI: true, log: 'SKIPPED' };
156
+ }
157
+ if (input.status === 'passed') {
158
+ effectiveCategory = 'pass';
159
+ }
160
+ else if (agentConclusion) {
161
+ classifiedMessage = agentConclusion.text.substring(0, 500);
162
+ if (agentConclusion.result === 'pass') {
163
+ // Agent 说通过但用例仍失败 → 脚本问题
164
+ effectiveCategory = 'pass';
165
+ skipAI = true;
166
+ }
167
+ else if (agentConclusion.result === 'blocked') {
168
+ effectiveCategory = 'env';
169
+ skipAI = true;
170
+ }
171
+ else if (agentConclusion.result === 'incomplete') {
172
+ effectiveCategory = 'incomplete';
173
+ skipAI = true;
174
+ }
175
+ else {
176
+ if (diagnosticHint && (diagnosticHint.includes('实际显示') || diagnosticHint.includes('页面实际') || diagnosticHint.includes('而非预期'))) {
177
+ effectiveCategory = 'text-mismatch';
178
+ skipAI = false;
179
+ }
180
+ else {
181
+ effectiveCategory = 'assert';
182
+ skipAI = false;
183
+ }
184
+ }
185
+ }
186
+ else {
187
+ const err = normalizeError(input.error);
188
+ if (err) {
189
+ const rawError = err.message || '';
190
+ classifiedMessage = rawError.substring(0, 500);
191
+ const errName = err.name || '';
192
+ if (errName === 'TypeError' || errName === 'ReferenceError' || errName === 'SyntaxError') {
193
+ effectiveCategory = 'script';
194
+ skipAI = true;
195
+ }
196
+ else if (rawError.includes('视觉断言失败')) {
197
+ effectiveCategory = 'visual';
198
+ skipAI = true;
199
+ }
200
+ else if (rawError.includes('expect(') || errName === 'AssertionError') {
201
+ effectiveCategory = 'assert';
202
+ skipAI = false;
203
+ }
204
+ else if (exports.ENV_ERROR_PATTERNS.test(rawError.substring(0, 300))) {
205
+ effectiveCategory = 'env';
206
+ skipAI = true;
207
+ }
208
+ else if (rawError.includes('Re-Act loop did not achieve') || rawError.includes('Expected not achieved after agent explore')) {
209
+ effectiveCategory = 'incomplete';
210
+ skipAI = true;
211
+ }
212
+ else if (rawError.includes('Expected not achieved')) {
213
+ if (diagnosticHint && (diagnosticHint.includes('实际显示') || diagnosticHint.includes('页面实际'))) {
214
+ effectiveCategory = 'text-mismatch';
215
+ skipAI = false;
216
+ }
217
+ else {
218
+ effectiveCategory = 'incomplete';
219
+ skipAI = true;
220
+ }
221
+ }
222
+ else {
223
+ effectiveCategory = 'unknown';
224
+ skipAI = true;
225
+ }
226
+ }
227
+ }
228
+ const keveScreenshots = parseKeveScreenshotLabels(attachments);
229
+ if (skipAI) {
230
+ const thought = effectiveCategory === 'script'
231
+ ? `脚本本身存在代码错误(${classifiedMessage}),测试结果无效`
232
+ : effectiveCategory === 'env'
233
+ ? `环境/基础设施异常(${classifiedMessage}),非应用功能问题`
234
+ : effectiveCategory === 'visual'
235
+ ? `视觉断言失败(${classifiedMessage}),已由keveAssert评估`
236
+ : effectiveCategory === 'unknown'
237
+ ? `无法分类的异常(${classifiedMessage}),跳过AI评估`
238
+ : effectiveCategory === 'pass'
239
+ ? `Agent判定通过但fn脚本执行失败(${classifiedMessage}),脚本需修复`
240
+ : effectiveCategory === 'incomplete'
241
+ ? `Agent执行超过探索上限(${classifiedMessage}),未完成目标,需要人工确认`
242
+ : `无法分类的异常(${classifiedMessage})`;
243
+ // AI 评估列:assert/text-mismatch → 不通过;其余无法得出结论 → 阻塞
244
+ const aiData = (effectiveCategory === 'assert' || effectiveCategory === 'text-mismatch') ? '不通过' : '阻塞';
245
+ return {
246
+ record: {
247
+ title: input.title,
248
+ data: aiData,
249
+ confidence: exports.CONFIDENCE_MAP[effectiveCategory] ?? 0,
250
+ thought,
251
+ errorCategory: effectiveCategory,
252
+ ...(keveScreenshots.length > 0 ? { keveScreenshots } : {}),
253
+ steps,
254
+ diagnosticHint,
255
+ diagnostics,
256
+ },
257
+ skipAI: true,
258
+ log: `SHORT-CIRCUIT skipAI (category=${effectiveCategory})`,
259
+ };
260
+ }
261
+ return {
262
+ record: {
263
+ title: input.title,
264
+ data: input.status === 'passed' ? '通过' : '不通过',
265
+ confidence: exports.CONFIDENCE_MAP[effectiveCategory] ?? 0,
266
+ thought: input.status === 'passed' && agentConclusion?.result === 'pass' ? agentConclusion.text : '',
267
+ errorCategory: effectiveCategory,
268
+ ...(keveScreenshots.length > 0 ? { keveScreenshots } : {}),
269
+ steps,
270
+ diagnosticHint,
271
+ diagnostics,
272
+ },
273
+ skipAI: false,
274
+ log: `${input.status} (category=${effectiveCategory})`,
275
+ };
276
+ }
277
+ /**
278
+ * 落盘前的记录清洗:剔除大字段(base64 截图与整页快照),
279
+ * 截图以文件路径形式存在于 goalScreenshotBefore/After 与 actions[].screenshotPath。
280
+ */
281
+ function sanitizeRecord(record) {
282
+ if (!record.steps)
283
+ return record;
284
+ record.steps = record.steps.map((step) => {
285
+ const { screenshotBase64, finalSnapshot, ...stepRest } = step;
286
+ if (stepRest.actions) {
287
+ stepRest.actions = stepRest.actions.map((a) => {
288
+ const { snapshotPreview, ...actionRest } = a;
289
+ return actionRest;
290
+ });
291
+ }
292
+ return stepRest;
293
+ });
294
+ return record;
295
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * goal-heuristics — keveGoal 的错误归因判定(引擎无关)
3
+ *
4
+ * 为什么单独成模块:Playwright 的 keveGoal(core/keve-test.ts 的 fixture)与
5
+ * Cypress 的 keveGoal(engine-cypress/keveGoal.ts)必须对同一段错误字符串给出
6
+ * 完全相同的结论 —— 否则「环境问题」在一端是 BLOCKED、另一端是 FAIL,
7
+ * 报告与置信度都会漂移。
8
+ *
9
+ * 约束:纯函数 + 零依赖(Cypress 浏览器 bundle 会直接打包本模块)。
10
+ */
11
+ /**
12
+ * 判断错误是否属于「环境阻塞」:这类错误 Agent Re-Act 无法修复,
13
+ * 应直接判 BLOCKED 而不是烧 token 探索。
14
+ *
15
+ * 覆盖:空 URL、连接拒绝、导航超时、IDC 网段限制、SSO 跳转、页面/浏览器已关闭。
16
+ * 注意:定位器超时**不算**(那是脚本选择器问题,应交给 Agent 自愈)。
17
+ */
18
+ export declare function isEnvironmentBlockedError(err: any): boolean;
19
+ /**
20
+ * 判断错误是否为「确定性断言失败」(Expected/Received 形态)。
21
+ *
22
+ * 这类失败是事实而非判断:即使 Agent 认为「通过」,也必须由确定性结果覆盖。
23
+ * 同时用于在错误文案里保留精确的 expected/received 信息。
24
+ */
25
+ export declare function isAssertionFailure(error: string): boolean;
26
+ /**
27
+ * 推导 done action 的三态结论(pass/fail/blocked)。
28
+ *
29
+ * 4 级回退:step 级 conclusion → action.verdict → action.result → action.success / 文本关键词。
30
+ * 文本匹配必须先判 fail/blocked 再判 pass,否则「不通过」会被「通过」命中。
31
+ */
32
+ export declare function deriveActionConclusion(stepConclusion: string | undefined, action: any): 'pass' | 'fail' | 'blocked' | undefined;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ /**
3
+ * goal-heuristics — keveGoal 的错误归因判定(引擎无关)
4
+ *
5
+ * 为什么单独成模块:Playwright 的 keveGoal(core/keve-test.ts 的 fixture)与
6
+ * Cypress 的 keveGoal(engine-cypress/keveGoal.ts)必须对同一段错误字符串给出
7
+ * 完全相同的结论 —— 否则「环境问题」在一端是 BLOCKED、另一端是 FAIL,
8
+ * 报告与置信度都会漂移。
9
+ *
10
+ * 约束:纯函数 + 零依赖(Cypress 浏览器 bundle 会直接打包本模块)。
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.isEnvironmentBlockedError = isEnvironmentBlockedError;
14
+ exports.isAssertionFailure = isAssertionFailure;
15
+ exports.deriveActionConclusion = deriveActionConclusion;
16
+ /**
17
+ * 判断错误是否属于「环境阻塞」:这类错误 Agent Re-Act 无法修复,
18
+ * 应直接判 BLOCKED 而不是烧 token 探索。
19
+ *
20
+ * 覆盖:空 URL、连接拒绝、导航超时、IDC 网段限制、SSO 跳转、页面/浏览器已关闭。
21
+ * 注意:定位器超时**不算**(那是脚本选择器问题,应交给 Agent 自愈)。
22
+ */
23
+ function isEnvironmentBlockedError(err) {
24
+ const msg = (err?.message || String(err)).toLowerCase();
25
+ // Empty/undefined URL: page.goto('') or page.goto(undefined)
26
+ if (msg.includes('url must not be empty') || msg.includes('url is empty')
27
+ || msg.includes('invalid url') || msg.includes('url is undefined')
28
+ || msg.includes('navigation to ""') || msg.includes("navigation to ''")) {
29
+ return true;
30
+ }
31
+ // 参数为 undefined(如 PAGE_* 环境变量缺失)
32
+ if ((msg.includes('expected string') || msg.includes('expected a string'))
33
+ && (msg.includes('got undefined') || msg.includes('got null') || msg.includes('received undefined'))) {
34
+ return true;
35
+ }
36
+ // 连接被拒 / 网络不可达
37
+ if (msg.includes('err_connection_refused') || msg.includes('connection refused')
38
+ || msg.includes('net::err_connection') || msg.includes('err_name_not_resolved')
39
+ || msg.includes('err_address_unreachable') || msg.includes('err_internet_disconnected')
40
+ || msg.includes('err_connection_timed_out') || msg.includes('err_connection_reset')) {
41
+ return true;
42
+ }
43
+ // 导航超时(页面不可达)
44
+ if (msg.includes('navigation timeout of') || msg.includes('timeout of') && msg.includes('exceeded')
45
+ || msg.includes('page.goto: timeout') || msg.includes('navigating to') && msg.includes('timed out')
46
+ || msg.includes('[cypress-engine] 导航超时') || msg.includes('[cypress-engine] 刷新超时')) {
47
+ return true;
48
+ }
49
+ // IDC 网段限制
50
+ if (msg.includes('unable to access on the idc network segment')
51
+ || msg.includes('idc network segment') || msg.includes('40314')) {
52
+ return true;
53
+ }
54
+ // SSO / 登录跳转(非应用自身问题)
55
+ if (msg.includes('sso redirect') || msg.includes('login redirect detected')
56
+ || msg.includes('err_too_many_redirects')) {
57
+ return true;
58
+ }
59
+ // 页面/上下文/浏览器已关闭 —— Agent 无法探索
60
+ if (msg.includes('target page, context or browser has been closed')
61
+ || msg.includes('page has been closed') || msg.includes('browser has been closed')
62
+ || msg.includes('context has been closed') || msg.includes('未找到 aut frame')) {
63
+ return true;
64
+ }
65
+ return false;
66
+ }
67
+ /**
68
+ * 判断错误是否为「确定性断言失败」(Expected/Received 形态)。
69
+ *
70
+ * 这类失败是事实而非判断:即使 Agent 认为「通过」,也必须由确定性结果覆盖。
71
+ * 同时用于在错误文案里保留精确的 expected/received 信息。
72
+ */
73
+ function isAssertionFailure(error) {
74
+ const msg = (error || '').toLowerCase();
75
+ // 排除 API 参数类型错误(不是值断言)
76
+ if (msg.includes('expected string') || msg.includes('expected a string')
77
+ || msg.includes('expected number') || msg.includes('expected boolean')) {
78
+ if (msg.includes('got undefined') || msg.includes('got null')
79
+ || msg.includes('received undefined') || msg.includes('received null')) {
80
+ return false;
81
+ }
82
+ }
83
+ // Jest/Vitest/Playwright 断言:Expected: X, Received: Y
84
+ if (msg.includes('expected') && msg.includes('received'))
85
+ return true;
86
+ if (msg.includes('assertionerror') || msg.includes('assertion failed')
87
+ || msg.includes('assertion error'))
88
+ return true;
89
+ // 引擎侧断言文案(CypressEngine.expect* / PlaywrightEngine.expect*)
90
+ if (msg.includes('断言失败'))
91
+ return true;
92
+ if ((msg.includes(' to be ') && !msg.includes('timeout') && !msg.includes('waiting for') && !msg.includes('exceeded'))
93
+ || msg.includes(' to equal ') || msg.includes(' to deeply equal '))
94
+ return true;
95
+ if (msg.includes('expected ') && (msg.includes(' but ') || msg.includes(' got ') || msg.includes(' received ')))
96
+ return true;
97
+ return false;
98
+ }
99
+ /**
100
+ * 推导 done action 的三态结论(pass/fail/blocked)。
101
+ *
102
+ * 4 级回退:step 级 conclusion → action.verdict → action.result → action.success / 文本关键词。
103
+ * 文本匹配必须先判 fail/blocked 再判 pass,否则「不通过」会被「通过」命中。
104
+ */
105
+ function deriveActionConclusion(stepConclusion, action) {
106
+ if (action?.tool !== 'done')
107
+ return undefined;
108
+ if (stepConclusion)
109
+ return stepConclusion;
110
+ const verdict = action?.verdict;
111
+ if (verdict === 'pass' || verdict === 'fail' || verdict === 'blocked')
112
+ return verdict;
113
+ const raw = action?.result;
114
+ if (raw === 'pass' || raw === 'fail' || raw === 'blocked')
115
+ return raw;
116
+ if (typeof action?.success === 'boolean')
117
+ return action.success ? 'pass' : 'fail';
118
+ const tv = String(action?.text || '').trim().toLowerCase();
119
+ if (tv.includes('fail') || tv.includes('failure')
120
+ || tv.includes('失败') || tv.includes('未通过') || tv.includes('不通过'))
121
+ return 'fail';
122
+ if (tv.includes('blocked') || tv.includes('阻塞') || tv.includes('阻止'))
123
+ return 'blocked';
124
+ if (tv.includes('pass') || tv.includes('success') || tv.includes('ok')
125
+ || tv.includes('完成') || tv.includes('成功') || tv.includes('通过') || tv.includes('验证通过'))
126
+ return 'pass';
127
+ return undefined;
128
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * pngCodec — 纯 Node PNG 编解码(零依赖,zlib 内置)
3
+ *
4
+ * 为什么不用 pngjs/pixelmatch:@kkeve/suite 依赖树里没有这两个包,
5
+ * 执行环境(keve-wiki / 调度节点)也不保证能现场安装 —— 而引擎侧截图对比
6
+ * 是核心链路,不能带可选依赖。Playwright 截图恒为 8-bit、非交错、
7
+ * colorType 2(RGB)/6(RGBA),覆盖这两类 + 灰度(0/4)即可覆盖全部引擎产物。
8
+ *
9
+ * 支持范围(超出范围抛错,不静默出错误结果):
10
+ * - bitDepth: 8
11
+ * - colorType: 0(灰度) / 2(RGB) / 4(灰+A) / 6(RGBA)
12
+ * - interlace: 0(Progressive PNG 抛错)
13
+ */
14
+ export interface RawImage {
15
+ width: number;
16
+ height: number;
17
+ /** RGBA,4 通道,每像素 4 字节 */
18
+ data: Uint8Array;
19
+ }
20
+ /** 解码 PNG → RGBA。仅支持 8-bit / colorType 0,2,4,6 / 非交错 */
21
+ export declare function decodePng(buf: Buffer): RawImage;
22
+ /** RGBA → PNG(colorType 6,filter 0)。data 长度必须为 w*h*4 */
23
+ export declare function encodePng(width: number, height: number, data: Uint8Array): Buffer;