@kkeve/suite 0.0.1 → 0.0.2

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 (152) hide show
  1. package/README.md +53 -0
  2. package/dist/bin/keve.d.ts +13 -0
  3. package/dist/bin/keve.js +164 -0
  4. package/dist/commands/auth-update.d.ts +16 -0
  5. package/dist/commands/auth-update.js +88 -0
  6. package/dist/commands/clean.d.ts +10 -0
  7. package/dist/commands/clean.js +78 -0
  8. package/dist/commands/config.d.ts +17 -0
  9. package/dist/commands/config.js +99 -0
  10. package/dist/commands/init.d.ts +15 -0
  11. package/dist/commands/init.js +292 -0
  12. package/dist/commands/report.d.ts +12 -0
  13. package/dist/commands/report.js +159 -0
  14. package/dist/commands/run.d.ts +29 -0
  15. package/dist/commands/run.js +417 -0
  16. package/dist/commands/test.d.ts +12 -0
  17. package/dist/commands/test.js +102 -0
  18. package/dist/commands/vitest.d.ts +14 -0
  19. package/dist/commands/vitest.js +166 -0
  20. package/dist/config.d.ts +109 -0
  21. package/dist/config.js +291 -0
  22. package/dist/core/action-log-writer.d.ts +47 -0
  23. package/dist/{tools/write-file.js → core/action-log-writer.js} +42 -26
  24. package/dist/core/auth.d.ts +17 -0
  25. package/dist/core/auth.js +224 -0
  26. package/dist/core/cdp-resolver.d.ts +5 -0
  27. package/dist/core/cdp-resolver.js +93 -0
  28. package/dist/core/generateReport/report.d.ts +19 -0
  29. package/dist/core/generateReport/report.js +91 -0
  30. package/dist/core/generateReport/reportData.d.ts +215 -0
  31. package/dist/core/generateReport/reportData.js +735 -0
  32. package/dist/core/global-setup.d.ts +6 -0
  33. package/dist/core/global-setup.js +113 -0
  34. package/dist/core/keve-aspect.d.ts +54 -0
  35. package/dist/core/keve-aspect.js +52 -0
  36. package/dist/core/keve-decorators.d.ts +51 -0
  37. package/dist/core/keve-decorators.js +95 -0
  38. package/dist/core/keve-registry.d.ts +38 -0
  39. package/dist/core/keve-registry.js +25 -0
  40. package/dist/core/keve-report.d.ts +27 -0
  41. package/dist/core/keve-report.js +357 -0
  42. package/dist/core/keve-test.d.ts +36 -0
  43. package/dist/core/keve-test.js +364 -0
  44. package/dist/core/learned-actions.d.ts +24 -0
  45. package/dist/core/learned-actions.js +65 -0
  46. package/dist/core/pw-config.d.ts +19 -0
  47. package/dist/core/pw-config.js +180 -0
  48. package/dist/index.d.ts +8 -23
  49. package/dist/index.js +36 -43
  50. package/dist/page-agent/agent.d.ts +129 -0
  51. package/dist/page-agent/agent.js +585 -0
  52. package/dist/page-agent/hooks.d.ts +13 -0
  53. package/dist/page-agent/hooks.js +218 -0
  54. package/dist/page-agent/tools.d.ts +29 -0
  55. package/dist/page-agent/tools.js +297 -0
  56. package/doc/README.md +124 -0
  57. package/doc/agents/keve-dev-test-agent.md +140 -0
  58. package/doc/agents/keve-test-agent.md +178 -0
  59. package/doc/asserts//346/265/213/350/257/225/345/220/204/351/230/266/346/256/265/350/257/264/346/230/216.png +0 -0
  60. package/doc/keve-agent-demo.html +370 -0
  61. package/doc/skills/keve-bddcase-gen/SKILL.md +332 -0
  62. package/doc/skills/keve-bddcase-gen/references/test-case-rules.md +91 -0
  63. package/doc/skills/keve-bddcase-gen/references/test-cases-example.md +103 -0
  64. package/doc/skills/keve-component-gen/SKILL.md +255 -0
  65. package/doc/skills/keve-component-gen/references/component-mock-strategy.md +74 -0
  66. package/doc/skills/keve-component-gen/references/component-model-template.md +98 -0
  67. package/doc/skills/keve-component-gen/references/component-test-template.md +100 -0
  68. package/doc/skills/keve-component-gen/references/vue-test-utils-guide.md +91 -0
  69. package/doc/skills/keve-e2e-gen/SKILL.md +179 -0
  70. package/doc/skills/keve-e2e-gen/references/code-analysis-guide.md +208 -0
  71. package/doc/skills/keve-e2e-gen/references/decisions.md +36 -0
  72. package/doc/skills/keve-e2e-gen/references/pom-generation-guide.md +140 -0
  73. package/doc/skills/keve-e2e-gen/references/script-generation-guide.md +236 -0
  74. package/doc/skills/keve-e2e-gen/references/test-auditor-guide.md +189 -0
  75. package/doc/skills/keve-e2e-gen/scripts/extract-components.sh +85 -0
  76. package/doc/skills/keve-e2e-gen/scripts/inject-testids.sh +95 -0
  77. package/doc/skills/keve-static-analysis/SKILL.md +362 -0
  78. package/doc/skills/keve-static-analysis/references/codegraph-analysis-guide.md +108 -0
  79. package/doc/skills/keve-static-analysis/references/git-impact-analysis.md +81 -0
  80. package/doc/skills/keve-static-analysis/references/lint-fix-guide.md +69 -0
  81. package/doc/skills/keve-static-analysis/references/runtime-risk-patterns.md +92 -0
  82. package/doc/skills/keve-static-analysis/references/security-scan-guide.md +32 -0
  83. package/doc/skills/keve-static-analysis/references/ts-error-patterns.md +98 -0
  84. package/doc/skills/keve-static-analysis/references/vue-security-guide.md +72 -0
  85. package/doc/skills/keve-test-eval/SKILL.md +78 -0
  86. package/doc/skills/keve-test-eval/references/common-fix-guide.md +166 -0
  87. package/doc/skills/keve-test-eval/references/confidence-data-guide.md +78 -0
  88. package/doc/skills/keve-test-eval/references/fix-loop-guide.md +67 -0
  89. package/doc/skills/keve-unit-gen/SKILL.md +203 -0
  90. package/doc/skills/keve-unit-gen/references/boundary-conditions.md +78 -0
  91. package/doc/skills/keve-unit-gen/references/unit-mock-strategy.md +71 -0
  92. package/doc/skills/keve-unit-gen/references/unit-test-template.md +105 -0
  93. package/package.json +47 -25
  94. package/dist/agent/factory.d.ts +0 -25
  95. package/dist/agent/factory.js +0 -99
  96. package/dist/agent/index.d.ts +0 -3
  97. package/dist/agent/index.js +0 -7
  98. package/dist/agent/loader.d.ts +0 -22
  99. package/dist/agent/loader.js +0 -117
  100. package/dist/agent/types.d.ts +0 -9
  101. package/dist/agent/types.js +0 -2
  102. package/dist/agent-loop/complete-task.d.ts +0 -28
  103. package/dist/agent-loop/complete-task.js +0 -70
  104. package/dist/agent-loop/index.d.ts +0 -16
  105. package/dist/agent-loop/index.js +0 -26
  106. package/dist/agent-loop/loop.d.ts +0 -42
  107. package/dist/agent-loop/loop.js +0 -282
  108. package/dist/agent-loop/runtime.d.ts +0 -40
  109. package/dist/agent-loop/runtime.js +0 -163
  110. package/dist/agent-loop/types.d.ts +0 -161
  111. package/dist/agent-loop/types.js +0 -105
  112. package/dist/context/index.d.ts +0 -8
  113. package/dist/context/index.js +0 -29
  114. package/dist/context/tokenCalculation.d.ts +0 -55
  115. package/dist/context/tokenCalculation.js +0 -95
  116. package/dist/context/truncation.d.ts +0 -67
  117. package/dist/context/truncation.js +0 -142
  118. package/dist/context/types.d.ts +0 -7
  119. package/dist/context/types.js +0 -27
  120. package/dist/llm/OpenAIClient.d.ts +0 -31
  121. package/dist/llm/OpenAIClient.js +0 -455
  122. package/dist/llm/constants.d.ts +0 -2
  123. package/dist/llm/constants.js +0 -6
  124. package/dist/llm/errors.d.ts +0 -25
  125. package/dist/llm/errors.js +0 -51
  126. package/dist/llm/index.d.ts +0 -49
  127. package/dist/llm/index.js +0 -158
  128. package/dist/llm/types.d.ts +0 -124
  129. package/dist/llm/types.js +0 -2
  130. package/dist/llm/utils.d.ts +0 -29
  131. package/dist/llm/utils.js +0 -284
  132. package/dist/skill/frontmatter-parser.d.ts +0 -50
  133. package/dist/skill/frontmatter-parser.js +0 -152
  134. package/dist/skill/index.d.ts +0 -12
  135. package/dist/skill/index.js +0 -17
  136. package/dist/skill/loader.d.ts +0 -96
  137. package/dist/skill/loader.js +0 -256
  138. package/dist/tools/ask-user.d.ts +0 -8
  139. package/dist/tools/ask-user.js +0 -47
  140. package/dist/tools/bash.d.ts +0 -47
  141. package/dist/tools/bash.js +0 -213
  142. package/dist/tools/edit-file.d.ts +0 -9
  143. package/dist/tools/edit-file.js +0 -76
  144. package/dist/tools/glob.d.ts +0 -8
  145. package/dist/tools/glob.js +0 -101
  146. package/dist/tools/grep.d.ts +0 -10
  147. package/dist/tools/grep.js +0 -76
  148. package/dist/tools/index.d.ts +0 -24
  149. package/dist/tools/index.js +0 -47
  150. package/dist/tools/read-files.d.ts +0 -8
  151. package/dist/tools/read-files.js +0 -81
  152. package/dist/tools/write-file.d.ts +0 -8
@@ -0,0 +1,364 @@
1
+ "use strict";
2
+ /**
3
+ * keve-test — Playwright test fixture: keveGoal (唯一操作原语)
4
+ *
5
+ * keveGoal 执行 fn 后 → 截图+fn逻辑+expected → AI判断是否一致
6
+ * 一致 → 通过
7
+ * 不一致 → AI 告诉你还差什么 → Re-Act 探索 → 探索成功
8
+ * fn 无 → Re-Act 直接探索
9
+ * fn 异常 → Re-Act 自愈
10
+ *
11
+ * keveAssert 已被吸收:keveGoal 的 expected 即断言语义
12
+ *
13
+ * Usage:
14
+ * import { test, expect } from '@ks-data/keve-suite/keve-test';
15
+ *
16
+ * test('my test', async ({ page, keveGoal }) => {
17
+ * await keveGoal({ step: '导航到列表页', expected: '页面加载完成' }, async () => {
18
+ * await page.goto('/agents');
19
+ * });
20
+ *
21
+ * // AI explore (no fn):
22
+ * await keveGoal({ step: '点击新建按钮', expected: '弹出创建对话框' });
23
+ * });
24
+ */
25
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ var desc = Object.getOwnPropertyDescriptor(m, k);
28
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
29
+ desc = { enumerable: true, get: function() { return m[k]; } };
30
+ }
31
+ Object.defineProperty(o, k2, desc);
32
+ }) : (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ o[k2] = m[k];
35
+ }));
36
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
37
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
38
+ }) : function(o, v) {
39
+ o["default"] = v;
40
+ });
41
+ var __importStar = (this && this.__importStar) || (function () {
42
+ var ownKeys = function(o) {
43
+ ownKeys = Object.getOwnPropertyNames || function (o) {
44
+ var ar = [];
45
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
46
+ return ar;
47
+ };
48
+ return ownKeys(o);
49
+ };
50
+ return function (mod) {
51
+ if (mod && mod.__esModule) return mod;
52
+ var result = {};
53
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
54
+ __setModuleDefault(result, mod);
55
+ return result;
56
+ };
57
+ })();
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.test = exports.sceneGoalsMap = exports.sceneEvalMetaMap = exports.sceneCodeMap = exports.getModelScenes = exports.keveScene = exports.keveModel = exports.expect = void 0;
60
+ const test_1 = require("@playwright/test");
61
+ Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_1.expect; } });
62
+ const keve_registry_1 = require("./keve-registry");
63
+ const agent_1 = require("../page-agent/agent");
64
+ const keve_aspect_1 = require("./keve-aspect");
65
+ const learned_actions_1 = require("./learned-actions");
66
+ const fs = __importStar(require("node:fs"));
67
+ const path = __importStar(require("node:path"));
68
+ // ─── CDP Mode ──────────────────────────────────────────────────────
69
+ // CDP 连接逻辑已移到 global-setup.ts,通过全局变量共享 Browser 对象
70
+ const global_setup_1 = require("./global-setup");
71
+ var keve_decorators_1 = require("./keve-decorators");
72
+ Object.defineProperty(exports, "keveModel", { enumerable: true, get: function () { return keve_decorators_1.keveModel; } });
73
+ Object.defineProperty(exports, "keveScene", { enumerable: true, get: function () { return keve_decorators_1.keveScene; } });
74
+ Object.defineProperty(exports, "getModelScenes", { enumerable: true, get: function () { return keve_decorators_1.getModelScenes; } });
75
+ var keve_registry_2 = require("./keve-registry");
76
+ Object.defineProperty(exports, "sceneCodeMap", { enumerable: true, get: function () { return keve_registry_2.sceneCodeMap; } });
77
+ Object.defineProperty(exports, "sceneEvalMetaMap", { enumerable: true, get: function () { return keve_registry_2.sceneEvalMetaMap; } });
78
+ // Re-export goal metadata for consumer convenience
79
+ var keve_registry_3 = require("./keve-registry");
80
+ Object.defineProperty(exports, "sceneGoalsMap", { enumerable: true, get: function () { return keve_registry_3.sceneGoalsMap; } });
81
+ // ─── Register Built-in Aspects ───────────────────────────────────────
82
+ // NOTE: action-log-write aspect 已废弃。
83
+ // AI 探索过程数据现在通过 keveGoalResult attachment 直接流到 KeveReporter,
84
+ // 由 KeveReporter 统一写入 confidence-data.jsonl(含 reactSteps)。
85
+ keve_aspect_1.keveAspect.register({
86
+ name: 'learnedActions-update',
87
+ phase: 'after',
88
+ order: 20,
89
+ async execute(ctx, result) {
90
+ if (!result?.actions?.length || !result.success)
91
+ return;
92
+ learned_actions_1.learnedActions.add(ctx.step, result.actions);
93
+ },
94
+ });
95
+ // ─── (diagnostic extraction moved to page-agent/diagnostic.ts, called via onAfterTask hook) ──
96
+ // ─── Fixture ────────────────────────────────────────────────────────
97
+ exports.test = test_1.test.extend({
98
+ browser: async ({}, use, testInfo) => {
99
+ if ((0, global_setup_1.isCdpMode)()) {
100
+ const browser = await (0, global_setup_1.getCdpBrowser)(); // 复用全局共享的 CDP browser
101
+ await use(browser);
102
+ // CDP 模式下不关闭浏览器(共享连接,关闭会影响后续用例)
103
+ }
104
+ else {
105
+ const browser = await test_1.chromium.launch(testInfo.project?.use?.launchOptions || {});
106
+ await use(browser);
107
+ await browser.close();
108
+ }
109
+ },
110
+ page: async ({ browser }, use) => {
111
+ if ((0, global_setup_1.isCdpMode)()) {
112
+ const contexts = browser.contexts();
113
+ const cdpContext = contexts.length > 0 ? contexts[0] : await browser.newContext();
114
+ // CDP 模式:优先复用已有 page(保留登录状态),仅在无可用 page 时新开
115
+ const existingPages = cdpContext.pages();
116
+ let page;
117
+ if (existingPages.length > 0) {
118
+ // 复用第一个非 about:blank 的 page(通常是用户已登录的 tab)
119
+ page = existingPages.find(p => p.url() !== 'about:blank') || existingPages[0];
120
+ // 导航到 about:blank 作为起始页(清空之前的状态,但保留 cookie)
121
+ try {
122
+ await page.goto('about:blank', { timeout: 3000 });
123
+ }
124
+ catch { /* ignore */ }
125
+ }
126
+ else {
127
+ page = await cdpContext.newPage();
128
+ }
129
+ await use(page);
130
+ // CDP 模式下不关闭 page(保留浏览器 tab 给下一个用例复用)
131
+ }
132
+ else {
133
+ const page = await browser.newPage();
134
+ await use(page);
135
+ await page.close();
136
+ }
137
+ },
138
+ // ── keveGoal: 唯一操作原语 ────────────────────────────────────────
139
+ keveGoal: async ({ page }, use, testInfo) => {
140
+ let goalOrder = 0;
141
+ const keveGoalFn = async (options, fn) => {
142
+ const goalMeta = {
143
+ precondition: options.precondition,
144
+ step: options.step,
145
+ expected: options.expected,
146
+ order: goalOrder++,
147
+ };
148
+ const sceneTitle = testInfo.title;
149
+ if (!keve_registry_1.sceneGoalsMap.has(sceneTitle))
150
+ keve_registry_1.sceneGoalsMap.set(sceneTitle, []);
151
+ keve_registry_1.sceneGoalsMap.get(sceneTitle).push(goalMeta);
152
+ const ctx = {
153
+ page,
154
+ step: options.step,
155
+ expected: options.expected,
156
+ precondition: options.precondition,
157
+ order: goalMeta.order,
158
+ testTitle: sceneTitle,
159
+ specFilePath: testInfo.file,
160
+ };
161
+ // Run before aspects
162
+ await keve_aspect_1.keveAspect.runPhase('before', ctx);
163
+ let result;
164
+ const fnSource = fn?.toString() || undefined;
165
+ let fnError;
166
+ if (fn) {
167
+ try {
168
+ await fn();
169
+ console.log(`[keveGoal] "${options.step}" fn executed`);
170
+ }
171
+ catch (err) {
172
+ fnError = err?.message || String(err);
173
+ console.log(`[keveGoal] "${options.step}" fn error: ${fnError}`);
174
+ }
175
+ }
176
+ // ── Capture "before" screenshot (page state before agent starts) ──
177
+ let goalScreenshotBefore = '';
178
+ try {
179
+ const buf = await page.screenshot({ type: 'png', timeout: 15000 });
180
+ const taskDir = process.env.KEVE_TASK_DIR || '.keve';
181
+ const round = process.env.KEVE_ROUND || 'latest';
182
+ const screenshotsDir = path.join(taskDir, 'test-artifacts', `round-${round}`, 'screenshots');
183
+ fs.mkdirSync(screenshotsDir, { recursive: true });
184
+ const safeName = options.step.replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, '_').slice(0, 30);
185
+ const file = path.join(screenshotsDir, `goal-before-${goalMeta.order}-${safeName}-${Date.now()}.png`);
186
+ fs.writeFileSync(file, buf);
187
+ goalScreenshotBefore = path.relative(taskDir, file);
188
+ }
189
+ catch { /* non-critical */ }
190
+ // ── Hand off to agent: agent does Re-Act ──
191
+ const learnedHint = learned_actions_1.learnedActions.getHint(options.step);
192
+ let reactResult;
193
+ let reactTimedOut = false;
194
+ // ── Per-goal timeout: prevent one slow goal from exhausting the test timeout ──
195
+ // Default 120s per goal; total test timeout should be ≥ (goals × 120s + overhead)
196
+ const GOAL_TIMEOUT_MS = 120_000;
197
+ const goalTimeoutController = new AbortController();
198
+ const goalTimeout = setTimeout(() => {
199
+ goalTimeoutController.abort();
200
+ console.log(`[keveGoal] ⏰ "${options.step}" timed out after ${GOAL_TIMEOUT_MS}ms — stopping agent`);
201
+ }, GOAL_TIMEOUT_MS);
202
+ try {
203
+ reactResult = await (0, agent_1.reactLoop)(page, options.step, options.expected, {
204
+ learnedActionsHint: learnedHint,
205
+ specFilePath: ctx.specFilePath,
206
+ fnSource,
207
+ fnResult: fnError ? { error: fnError } : { success: true },
208
+ signal: goalTimeoutController.signal,
209
+ });
210
+ }
211
+ catch (reactErr) {
212
+ reactTimedOut = true;
213
+ const isGoalTimeout = goalTimeoutController.signal.aborted;
214
+ const msg = isGoalTimeout
215
+ ? `Goal timed out (${GOAL_TIMEOUT_MS}ms)`
216
+ : (reactErr?.message || String(reactErr));
217
+ console.log(`[keveGoal] reactLoop interrupted: ${msg.slice(0, 200)}`);
218
+ reactResult = {
219
+ expectedMet: false,
220
+ actions: [],
221
+ finalSnapshot: '',
222
+ agentScreenshots: [],
223
+ conclusion: isGoalTimeout ? 'blocked' : 'fail',
224
+ };
225
+ }
226
+ finally {
227
+ clearTimeout(goalTimeout);
228
+ }
229
+ result = {
230
+ success: reactResult.expectedMet,
231
+ actions: reactResult.actions,
232
+ finalSnapshot: reactResult.finalSnapshot,
233
+ error: reactResult.expectedMet
234
+ ? undefined
235
+ : reactResult.conclusion === 'blocked'
236
+ ? new Error(`Blocked: ${reactTimedOut ? `Agent 超时 (${GOAL_TIMEOUT_MS}ms)` : (reactResult.actions?.filter((a) => a.action?.tool === 'done').pop()?.action?.text || 'Agent blocked')}`)
237
+ : new Error(`Expected not achieved: ${options.expected}`),
238
+ };
239
+ // Attach Agent conclusion for downstream consumers
240
+ if (reactResult.conclusion)
241
+ result.conclusion = reactResult.conclusion;
242
+ console.log(reactResult.expectedMet
243
+ ? `[keveGoal] ✅ "${options.step}" PASSED`
244
+ : `[keveGoal] ❌ "${options.step}" FAILED (${reactResult.conclusion || 'fail'}) — ${result.error?.message || 'expected not achieved'}`);
245
+ // if (reactResult.refinePatch) (result as any).refinePatch = reactResult.refinePatch; // 已注释:scriptRefine 已禁用
246
+ if (reactResult.agentScreenshots)
247
+ result.agentScreenshots = reactResult.agentScreenshots;
248
+ // ── 核心:在 keveGoal 内部、throw 之前,写 attachment ──
249
+ // AI 探索的完整数据(actions、diagnosticHint)
250
+ // 通过 Playwright attachment 流到 KeveReporter.onTestEnd
251
+ // 注意:screenshotBase64 不再写入 attachment(截图已保存到文件系统,路径见 goalScreenshotBefore/goalScreenshotAfter)
252
+ const diagnosticHints = reactResult.diagnosticHints || [];
253
+ let goalScreenshotAfter = '';
254
+ try {
255
+ const buf = await page.screenshot({ type: 'png', timeout: 15000 });
256
+ const taskDir = process.env.KEVE_TASK_DIR || '.keve';
257
+ const round = process.env.KEVE_ROUND || 'latest';
258
+ const screenshotsDir = path.join(taskDir, 'test-artifacts', `round-${round}`, 'screenshots');
259
+ fs.mkdirSync(screenshotsDir, { recursive: true });
260
+ const safeName = options.step.replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, '_').slice(0, 30);
261
+ const file = path.join(screenshotsDir, `goal-after-${goalMeta.order}-${safeName}-${Date.now()}.png`);
262
+ fs.writeFileSync(file, buf);
263
+ goalScreenshotAfter = path.relative(taskDir, file);
264
+ console.log(`[keveGoal] screenshot captured: ${buf.length} bytes (path: ${goalScreenshotAfter})`);
265
+ }
266
+ catch (e) {
267
+ console.log(`[keveGoal] screenshot failed: ${e.message}`);
268
+ }
269
+ await testInfo.attach('keveGoalResult', {
270
+ contentType: 'application/json',
271
+ body: Buffer.from(JSON.stringify({
272
+ step: options.step,
273
+ expected: options.expected,
274
+ precondition: options.precondition,
275
+ order: goalMeta.order,
276
+ success: result.success,
277
+ actions: (result.actions || []).map((a) => {
278
+ // Derive done action conclusion reliably (4-level backoff):
279
+ // - Level 1: step-level conclusion (from agent.ts, handles verdict/result/success/text)
280
+ // - Level 2: a.action?.verdict (new, no schema conflict)
281
+ // - Level 3: a.action?.result if it's a valid 3-state value
282
+ // - Level 4: a.action?.success (legacy boolean) or text parsing
283
+ // Do NOT use a.action?.result blindly (MacroTool schema conflict: "ok" ≠ "pass")
284
+ const actionTool = a.action?.tool;
285
+ let actionConclusion;
286
+ if (actionTool === 'done') {
287
+ if (result.conclusion) {
288
+ actionConclusion = result.conclusion;
289
+ }
290
+ else {
291
+ const verdict = a.action?.verdict;
292
+ if (verdict === 'pass' || verdict === 'fail' || verdict === 'blocked') {
293
+ actionConclusion = verdict;
294
+ }
295
+ else {
296
+ const raw = a.action?.result;
297
+ if (raw === 'pass' || raw === 'fail' || raw === 'blocked')
298
+ actionConclusion = raw;
299
+ else if (typeof a.action?.success === 'boolean')
300
+ actionConclusion = a.action.success ? 'pass' : 'fail';
301
+ else {
302
+ // Level 4: parse text for verdict keyword (includes match, not exact)
303
+ // IMPORTANT: Check fail/blocked BEFORE pass to avoid "不通过" hitting "通过"
304
+ const tv = String(a.action?.text || '').trim().toLowerCase();
305
+ if (tv.includes('fail') || tv.includes('failure')
306
+ || tv.includes('失败') || tv.includes('未通过') || tv.includes('不通过'))
307
+ actionConclusion = 'fail';
308
+ else if (tv.includes('blocked') || tv.includes('阻塞') || tv.includes('阻止'))
309
+ actionConclusion = 'blocked';
310
+ else if (tv.includes('pass') || tv.includes('success') || tv.includes('ok')
311
+ || tv.includes('完成') || tv.includes('成功') || tv.includes('通过') || tv.includes('验证通过'))
312
+ actionConclusion = 'pass';
313
+ }
314
+ }
315
+ }
316
+ }
317
+ return {
318
+ tool: actionTool,
319
+ role: a.action?.role,
320
+ name: a.action?.name,
321
+ url: a.action?.url,
322
+ text: a.action?.text,
323
+ toolOutput: a.toolOutput?.slice(0, 500),
324
+ reason: a.action?.reason,
325
+ success: a.action?.success,
326
+ // Agent 3-state verdict (pass/fail/blocked) — new field, no schema conflict
327
+ verdict: a.action?.verdict,
328
+ // Agent 3-state conclusion (pass/fail/blocked) — reliably derived
329
+ conclusion: actionConclusion,
330
+ result: a.result,
331
+ error: a.error,
332
+ evaluation: a.evaluation?.slice(0, 300),
333
+ memory: a.memory?.slice(0, 200),
334
+ nextGoal: a.nextGoal?.slice(0, 200),
335
+ screenshotPath: a.screenshotPath,
336
+ };
337
+ }),
338
+ finalSnapshot: result.finalSnapshot ? String(result.finalSnapshot).slice(0, 500) : undefined,
339
+ diagnosticHints,
340
+ goalScreenshotBefore: goalScreenshotBefore || undefined,
341
+ goalScreenshotAfter: goalScreenshotAfter || undefined,
342
+ agentScreenshots: result.agentScreenshots || [],
343
+ // refinePatch: (result as any).refinePatch || undefined, // 已注释:scriptRefine 已禁用
344
+ conclusion: result.conclusion || undefined,
345
+ }), 'utf-8'),
346
+ });
347
+ // Run after aspects
348
+ await keve_aspect_1.keveAspect.runPhase('after', ctx, result, result.error);
349
+ // Throw if not successful
350
+ if (!result.success) {
351
+ const failReason = result.error?.message || 'expected state not achieved';
352
+ console.log(`[keveGoal] ❌ "${options.step}" FAILED — reason: ${failReason}`);
353
+ throw result.error || new Error(`keveGoal "${options.step}" failed: ${failReason}`);
354
+ }
355
+ };
356
+ await use(keveGoalFn);
357
+ // keveGoals attachment 已删除:step/expected/precondition/order 已合并进
358
+ // keveGoalResult attachment,不再单独输出 keveGoals 附件
359
+ // sceneGoalsMap 保留供内部分类使用
360
+ }
361
+ });
362
+ // Register extended test with keve-decorators so @keveModel uses the correct test
363
+ const keve_decorators_2 = require("./keve-decorators");
364
+ (0, keve_decorators_2.setKeveTest)(exports.test);
@@ -0,0 +1,24 @@
1
+ /**
2
+ * learned-actions — In-memory cache for same-round Re-Act discovery sharing
3
+ *
4
+ * When one keveGoal's Re-Act loop discovers a working action sequence,
5
+ * other goals in the same round can use those discoveries as hints.
6
+ *
7
+ * Key format: "${step}" — the step description
8
+ * Value: ActionLogEntry[] — the successful action sequence
9
+ */
10
+ export declare class LearnedActions {
11
+ private cache;
12
+ /** Store discovered actions for a step */
13
+ add(step: string, actions: any[]): void;
14
+ /** Retrieve discovered actions for a step */
15
+ get(step: string): any[] | undefined;
16
+ /** Get a JSON hint string for injecting into Re-Act prompt */
17
+ getHint(step: string): string | undefined;
18
+ /** Get all cached entries */
19
+ getAll(): Map<string, any[]>;
20
+ /** Clear all cached entries (typically at round start) */
21
+ clear(): void;
22
+ }
23
+ /** Global singleton for same-round sharing */
24
+ export declare const learnedActions: LearnedActions;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * learned-actions — In-memory cache for same-round Re-Act discovery sharing
4
+ *
5
+ * When one keveGoal's Re-Act loop discovers a working action sequence,
6
+ * other goals in the same round can use those discoveries as hints.
7
+ *
8
+ * Key format: "${step}" — the step description
9
+ * Value: ActionLogEntry[] — the successful action sequence
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.learnedActions = exports.LearnedActions = void 0;
13
+ class LearnedActions {
14
+ cache = new Map();
15
+ /** Store discovered actions for a step */
16
+ add(step, actions) {
17
+ // Only store successful action sequences (ending with expected met)
18
+ if (actions.length > 0) {
19
+ this.cache.set(step, actions);
20
+ }
21
+ }
22
+ /** Retrieve discovered actions for a step */
23
+ get(step) {
24
+ return this.cache.get(step);
25
+ }
26
+ /** Get a JSON hint string for injecting into Re-Act prompt */
27
+ getHint(step) {
28
+ const actions = this.cache.get(step);
29
+ if (!actions || actions.length === 0)
30
+ return undefined;
31
+ // Summarize: only include action type + role/name, not full snapshot
32
+ const summary = actions
33
+ .filter(a => a.action.tool !== 'done')
34
+ .map(a => {
35
+ const act = a.action;
36
+ if (act.tool === 'navigate')
37
+ return `navigate(${act.url})`;
38
+ if (act.tool === 'click')
39
+ return `click(${act.role} "${act.name}")`;
40
+ if (act.tool === 'type')
41
+ return `type(${act.role} "${act.name}", "${act.text}"${act.submit ? ', Enter' : ''})`;
42
+ if (act.tool === 'hover')
43
+ return `hover(${act.role} "${act.name}")`;
44
+ if (act.tool === 'pressKey')
45
+ return `pressKey(${act.key})`;
46
+ if (act.tool === 'scroll')
47
+ return `scroll(${act.direction})`;
48
+ if (act.tool === 'wait')
49
+ return `wait(${act.time}ms)`;
50
+ return act.tool;
51
+ });
52
+ return `Previous successful actions for "${step}": ${summary.join(' → ')}`;
53
+ }
54
+ /** Get all cached entries */
55
+ getAll() {
56
+ return this.cache;
57
+ }
58
+ /** Clear all cached entries (typically at round start) */
59
+ clear() {
60
+ this.cache.clear();
61
+ }
62
+ }
63
+ exports.LearnedActions = LearnedActions;
64
+ /** Global singleton for same-round sharing */
65
+ exports.learnedActions = new LearnedActions();
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Playwright config template generator
3
+ *
4
+ * Shared by `keve config`, `keve init`, and pipeline agents
5
+ * to produce consistent keve-test.config.{ts,mjs} files.
6
+ */
7
+ export interface PwConfigOptions {
8
+ baseUrl?: string;
9
+ cdp?: boolean;
10
+ cdpEndpoint?: string;
11
+ webServer?: boolean;
12
+ workers?: number;
13
+ timeout?: number;
14
+ retries?: number;
15
+ format?: 'ts' | 'mjs';
16
+ }
17
+ export declare function generatePwConfig(options?: PwConfigOptions): string;
18
+ export declare function resolveConfigOutputPath(explicitOutput?: string, format?: 'ts' | 'mjs'): string;
19
+ export declare function findExistingConfig(keveDir: string | null, taskDir: string | null): string | null;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ /**
3
+ * Playwright config template generator
4
+ *
5
+ * Shared by `keve config`, `keve init`, and pipeline agents
6
+ * to produce consistent keve-test.config.{ts,mjs} files.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.generatePwConfig = generatePwConfig;
43
+ exports.resolveConfigOutputPath = resolveConfigOutputPath;
44
+ exports.findExistingConfig = findExistingConfig;
45
+ const path = __importStar(require("node:path"));
46
+ const config_1 = require("../config");
47
+ function generatePwConfig(options = {}) {
48
+ const { baseUrl, cdp = false, cdpEndpoint, webServer = !cdp, workers = 3, timeout = 1200000, retries = 0, format = 'ts', } = options;
49
+ const baseURLLine = baseUrl
50
+ ? `process.env.BASE_URL || '${baseUrl}'`
51
+ : `process.env.BASE_URL || process.env.PWGEN_TARGET_URL || 'http://localhost:3000'`;
52
+ const cdpBlock = cdp
53
+ ? `\n // CDP: connect to running Chrome (preserves login state)\n // Set KEVE_CDP_ENDPOINT=http://[::1]:9222 at runtime\n // Or: KEVE_CDP_WS_ENDPOINT=ws://...\n connectOptions: ${cdpEndpoint ? `{ endpoint: '${cdpEndpoint}' }` : `process.env.KEVE_CDP_ENDPOINT ? { endpoint: process.env.KEVE_CDP_ENDPOINT } : undefined`},`
54
+ : '';
55
+ const webServerBlock = webServer
56
+ ? `\n webServer: {\n command: 'pnpm dev',\n url: ${baseUrl ? `'${baseUrl}'` : `'http://localhost:3000'`},\n reuseExistingServer: true,\n },`
57
+ : `\n // webServer disabled — CDP mode assumes app is already running`;
58
+ if (format === 'mjs') {
59
+ return generateMjsConfig({ baseURLLine, cdp, cdpBlock, webServerBlock, workers, timeout, retries });
60
+ }
61
+ return `import { defineConfig } from '@playwright/test';
62
+ import * as path from 'node:path';
63
+
64
+ const projectRoot = path.resolve(__dirname, '..');
65
+
66
+ // Task directory — set KEVE_TASK_DIR env at execution time
67
+ // e.g. KEVE_TASK_DIR=.keve/task_xxx npx playwright test --config=.keve/task_xxx/keve-test.config.ts
68
+ const taskDir = process.env.KEVE_TASK_DIR
69
+ ? path.resolve(projectRoot, process.env.KEVE_TASK_DIR)
70
+ : path.resolve(projectRoot, '.keve/default');
71
+
72
+ const round = process.env.KEVE_ROUND || 'latest';
73
+ const resultDir = path.join(taskDir, 'test-artifacts', \`round-\${round}\`);
74
+
75
+ export default defineConfig({
76
+ globalSetup: require.resolve('@ks-data/keve-suite/global-setup'),
77
+ testDir: path.join(taskDir, 'keve_test_spec'),
78
+ fullyParallel: true,
79
+ workers: ${workers},
80
+ retries: ${retries},
81
+ timeout: ${timeout},
82
+ expect: { timeout: 10000 },
83
+ reporter: [
84
+ ['list'],
85
+ ['json', { outputFile: path.join(resultDir, 'test-results.json') }],
86
+ [require.resolve('@ks-data/keve-suite/keve-report')],
87
+ ],
88
+ use: {
89
+ baseURL: ${baseURLLine},${cdp ? `\n // CDP mode: login state preserved via connected Chrome` : `\n storageState: path.resolve(projectRoot, '.auth/storage-state.json'),`}
90
+ trace: 'on-first-retry',
91
+ screenshot: 'only-on-failure',
92
+ video: 'retain-on-failure',
93
+ actionTimeout: 5000,${cdpBlock}
94
+ },
95
+ outputDir: path.join(resultDir, 'test-results'),${webServerBlock}
96
+ });
97
+ `;
98
+ }
99
+ function generateMjsConfig(params) {
100
+ const { baseURLLine, cdp, cdpBlock, webServerBlock, workers, timeout, retries } = params;
101
+ const keveSuiteDir = `resolve(dirname(fileURLToPath(import.meta.url)), '..', 'node_modules', '@ks-data', 'keve-suite')`;
102
+ const globalSetupExpr = `resolve(${keveSuiteDir}, 'global-setup.js')`;
103
+ const keveReportExpr = `resolve(${keveSuiteDir}, 'keve-report.js')`;
104
+ return `import { defineConfig } from '@playwright/test';
105
+ import { resolve, join, dirname } from 'node:path';
106
+ import { fileURLToPath } from 'node:url';
107
+
108
+ const __dirname = dirname(fileURLToPath(import.meta.url));
109
+ const projectRoot = resolve(__dirname, '..');
110
+
111
+ const taskDir = process.env.KEVE_TASK_DIR
112
+ ? resolve(projectRoot, process.env.KEVE_TASK_DIR)
113
+ : resolve(projectRoot, '.keve/default');
114
+
115
+ process.env.KEVE_TASK_DIR = taskDir;
116
+
117
+ const round = process.env.KEVE_ROUND || 'latest';
118
+ const resultDir = join(taskDir, 'test-artifacts', \`round-\${round}\`);
119
+
120
+ const globalSetup = ${globalSetupExpr};
121
+ const keveReporter = ${keveReportExpr};
122
+
123
+ export default defineConfig({
124
+ globalSetup,
125
+ testDir: join(taskDir, 'keve_test_spec'),
126
+ fullyParallel: true,
127
+ workers: ${workers},
128
+ retries: ${retries},
129
+ timeout: ${timeout},
130
+ expect: { timeout: 10000 },
131
+ reporter: [
132
+ ['list'],
133
+ ['json', { outputFile: join(resultDir, 'test-results.json') }],
134
+ [keveReporter],
135
+ ],
136
+ use: {
137
+ baseURL: ${baseURLLine},${cdp ? `\n // CDP mode: login state preserved via connected Chrome` : `\n storageState: resolve(projectRoot, '.auth', 'storage-state.json'),`}
138
+ trace: 'on-first-retry',
139
+ screenshot: 'only-on-failure',
140
+ video: 'retain-on-failure',
141
+ actionTimeout: 5000,${cdpBlock}
142
+ },
143
+ outputDir: join(resultDir, 'test-results'),${webServerBlock}
144
+ });
145
+ `;
146
+ }
147
+ function resolveConfigOutputPath(explicitOutput, format = 'ts') {
148
+ const ext = format === 'mjs' ? '.mjs' : '.ts';
149
+ if (explicitOutput)
150
+ return path.resolve(explicitOutput);
151
+ const keveDir = (0, config_1.findKeveDir)();
152
+ const taskDir = (0, config_1.resolveTaskDir)(keveDir);
153
+ if (taskDir)
154
+ return path.join(taskDir, `keve-test.config${ext}`);
155
+ if (keveDir) {
156
+ const branchDir = (0, config_1.getBranchDir)(keveDir);
157
+ if (branchDir)
158
+ return path.join(branchDir, `keve-test.config${ext}`);
159
+ return path.join(keveDir, `keve-test.config${ext}`);
160
+ }
161
+ return path.join(process.cwd(), 'keve_test_spec', `keve-test.config${ext}`);
162
+ }
163
+ function findExistingConfig(keveDir, taskDir) {
164
+ const searchPaths = [];
165
+ if (taskDir) {
166
+ searchPaths.push(path.join(taskDir, 'keve-test.config.mjs'));
167
+ searchPaths.push(path.join(taskDir, 'keve-test.config.ts'));
168
+ }
169
+ if (keveDir) {
170
+ searchPaths.push(path.join(keveDir, 'keve-test.config.mjs'));
171
+ searchPaths.push(path.join(keveDir, 'keve-test.config.ts'));
172
+ }
173
+ searchPaths.push(path.join(process.cwd(), 'keve_test_spec', 'keve-test.config.mjs'));
174
+ searchPaths.push(path.join(process.cwd(), 'keve_test_spec', 'keve-test.config.ts'));
175
+ for (const p of searchPaths) {
176
+ if (require('fs').existsSync(p))
177
+ return p;
178
+ }
179
+ return null;
180
+ }