@kkeve/suite 0.0.23 → 0.0.24
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/dist/dsl/toDecorator.js +29 -2
- package/dist/page-agent/tools.js +7 -2
- package/package.json +1 -1
package/dist/dsl/toDecorator.js
CHANGED
|
@@ -160,7 +160,17 @@ function renderStep(d, ctx) {
|
|
|
160
160
|
const executableLineCount = [...body, ...expLines].length;
|
|
161
161
|
// P1 纵深防御:断言里若残留未解析占位符,说明解析链路有漏(正常已在
|
|
162
162
|
// stringExpr 处编译期报错),一律不许短路 —— 自证式断言不能当作确定性事实。
|
|
163
|
-
|
|
163
|
+
// stringExpr 对已知合法引用生成 ${String(X)} 模板,运行时变量在这里不是
|
|
164
|
+
// 「未解析占位符」,不应阻断确定性短路。其余残留 ${...} 仍是解析漏洞。
|
|
165
|
+
const hasUnresolved = [...body, ...expLines].some((b) => {
|
|
166
|
+
for (const m of String(b).matchAll(/\$\{([^}]+)\}/g)) {
|
|
167
|
+
const raw = String(m[1]).trim();
|
|
168
|
+
const name = /^String\(\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*\)$/.exec(raw)?.[1] || raw;
|
|
169
|
+
if (!ctx.runtimeVars.has(name))
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
});
|
|
164
174
|
const deterministic = expectedFullyCovered && !pageLevelOp && executableLineCount > 0 && !hasUnresolved;
|
|
165
175
|
out.push(`await keveGoal({ step: ${q(stepText)}, expected: ${q(expected)}${deterministic ? ', deterministic: true' : ''} }, async () => {`);
|
|
166
176
|
for (const b of [...body, ...expLines])
|
|
@@ -285,7 +295,7 @@ function renderStep(d, ctx) {
|
|
|
285
295
|
: undefined);
|
|
286
296
|
const targetCtx = valueCtx || d.child?.context || d.child;
|
|
287
297
|
if (Array.isArray(targetCtx) && targetCtx.length) {
|
|
288
|
-
emit([`await engine.drag(${
|
|
298
|
+
emit([`await engine.drag(${chainExpr(normalizeDragSource(d.context), ctx)}, ${chainExpr(targetCtx, ctx)});`, ...waitExprs(d)]);
|
|
289
299
|
}
|
|
290
300
|
else {
|
|
291
301
|
throw new Error(`DRAGGING 缺少目标元素,拒绝退化为悬停: ${stepText}`);
|
|
@@ -1063,6 +1073,23 @@ function collectHops(context) {
|
|
|
1063
1073
|
}
|
|
1064
1074
|
return out;
|
|
1065
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* 拖拽源链归一化:录制器常见的「父容器 → 文本 → 列表项」链会把列表项限制在
|
|
1078
|
+
* 文本叶节点后代内,导致确定性定位落空。同用例里可稳定命中的点击链是
|
|
1079
|
+
* 「列表项 → 文本/includes」,拖拽源也应优先使用该形态。
|
|
1080
|
+
*/
|
|
1081
|
+
function normalizeDragSource(context) {
|
|
1082
|
+
const hops = Array.isArray(context) ? context : [];
|
|
1083
|
+
if (hops.length < 2)
|
|
1084
|
+
return context;
|
|
1085
|
+
const prev = hops[hops.length - 2];
|
|
1086
|
+
const tail = hops[hops.length - 1];
|
|
1087
|
+
const isListItemTail = String(tail?.value ?? '') === '[data-e2e-name="列表项"]';
|
|
1088
|
+
const isTextHop = String(prev?.type || '') === 'includes' || String(prev?.type || '') === 'text';
|
|
1089
|
+
if (isListItemTail && isTextHop)
|
|
1090
|
+
return [tail, prev];
|
|
1091
|
+
return context;
|
|
1092
|
+
}
|
|
1066
1093
|
/** 截图基线 key 兜底:步骤文案 → 稳定 key(同步骤重复生成不变) */
|
|
1067
1094
|
function sanitizeShotKey(text) {
|
|
1068
1095
|
return String(text || 'shot').replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, '_').slice(0, 40);
|
package/dist/page-agent/tools.js
CHANGED
|
@@ -158,7 +158,9 @@ exports.tools.set('done', tool({
|
|
|
158
158
|
execute: async function (input) {
|
|
159
159
|
const icons = { pass: '✅', fail: '❌', blocked: '🚫' };
|
|
160
160
|
let verdict = input.verdict;
|
|
161
|
-
// 强制校验:verdict 缺失或非法时从 text 推断(MacroTool 扁平化导致 verdict 变 optional
|
|
161
|
+
// 强制校验:verdict 缺失或非法时从 text 推断(MacroTool 扁平化导致 verdict 变 optional)。
|
|
162
|
+
// verdict 与可推断文本都缺失时不能默认 pass —— Agent 已把动作做完但没给出结论,
|
|
163
|
+
// 交给上层按保守语义判定,而不是在工具层制造假绿。
|
|
162
164
|
if (!icons[verdict]) {
|
|
163
165
|
console.warn(`[done] ⚠️ verdict missing or invalid ("${input.verdict}"), inferring from text`);
|
|
164
166
|
const t = String(input.text || '').toLowerCase();
|
|
@@ -168,9 +170,12 @@ exports.tools.set('done', tool({
|
|
|
168
170
|
else if (t.includes('blocked') || t.includes('阻塞') || t.includes('阻止')) {
|
|
169
171
|
verdict = 'blocked';
|
|
170
172
|
}
|
|
171
|
-
else {
|
|
173
|
+
else if (t.length > 0) {
|
|
172
174
|
verdict = 'pass'; // 有 text 且无否定词 → 默认 pass
|
|
173
175
|
}
|
|
176
|
+
else {
|
|
177
|
+
throw new Error('done 缺少 verdict 且没有可推断的 text,不能默认判定通过');
|
|
178
|
+
}
|
|
174
179
|
}
|
|
175
180
|
return `${icons[verdict]} Task ${verdict}: ${input.text}`;
|
|
176
181
|
},
|
package/package.json
CHANGED