fix: unblock v1.138.0 release pipelines

This commit is contained in:
coso
2026-09-02 07:56:28 +08:00
parent 1ac3364de2
commit a5147fe388
10 changed files with 161 additions and 38 deletions
@@ -174,11 +174,11 @@ std::string readElement(IUIAutomationElement* element,
if (SUCCEEDED(element->get_CurrentProcessId(&processId))) {
object << "\"processId\":" << processId << ",";
}
int nativeWindowHandle = 0;
UIA_HWND nativeWindowHandle = nullptr;
if (SUCCEEDED(element->get_CurrentNativeWindowHandle(&nativeWindowHandle)) &&
nativeWindowHandle) {
object << "\"nativeWindowHandle\":"
<< static_cast<uintptr_t>(nativeWindowHandle) << ",";
<< reinterpret_cast<uintptr_t>(nativeWindowHandle) << ",";
}
RECT rect{};
if (SUCCEEDED(element->get_CurrentBoundingRectangle(&rect))) {
+4 -1
View File
@@ -1,6 +1,6 @@
# Lime v1.138.0 发布执行计划
状态:`second-follow-up-ready / Windows CI verification pending`
状态:`third-follow-up-ready / Windows UIA SDK contract verification pending`
日期:2026-09-01
目标版本:`1.138.0`
目标 tag:`v1.138.0`
@@ -39,6 +39,9 @@
- 首次 follow-up commit `ba9dbdaae` 已推送并重建 `v1.138.0` tag。Quality run `33521136350` 中 Bridge、GUI Smoke、Integrity、Rust Full 均通过;Release run `33521150327` 中 macOS arm64/x64 均通过,但发布资产因 Windows job 失败未进入聚合上传。
- 第二轮失败根因已由任务日志确认:Windows native host 在 `get_CurrentNativeWindowHandle` 传入 `HWND*`,而 Windows SDK 接口要求 `int*`;Windows Quality 的 PowerShell 命中系统 `tar` 并对 `.tar.bz2` 超时;Frontend Full 的 `electron/systemUtilityHost.test.ts` 在 Linux runner 硬编码了 macOS Accessibility 状态。
- 第二轮修复:native window handle 改为 SDK 契约的 `int` 后再安全提升为 `uintptr_t`;Windows 7-Zip 解出中间 `.tar` 后用同一 7-Zip 解第二层,继续保留 tar 兜底;环境预览测试按真实宿主平台断言 Accessibility。定向回归 `24/24` 通过,仍待 commit/tag 更新后由 Windows runner 证明 MSVC build、Squirrel、CodeMode Gate B 与 native host Gate B。
- 第二轮 follow-up commit `1ac3364de` 已证明 sherpa 双层解压通过、GUI Smoke 通过,但 Release run `33569593531` 的 Windows MSVC 编译仍在 `get_CurrentNativeWindowHandle` 失败。Windows SDK 事实源显示 `typedef void *UIA_HWND`,接口参数为 `UIA_HWND *`;此前 `HWND` 和 `int` 均不匹配。第三轮修复改为直接使用 `UIA_HWND`,并让 `cl` 继承 CI 标准输出,后续失败不再退化为不可读 Buffer 字节数组。
- Quality run `33569587544` 的 Frontend Full 在第 32/119 批出现 5 个 DeepSWE 失败。根因不是 `PATH` 并发污染:默认测试直接依赖被 `.gitignore` 排除的 `.lime/benchmark/sources/deep-swe` 本地 cache,干净 CI 中 source cwd 不存在,Node 启动 `git` 因此报 `spawnSync git ENOENT`,其余断言随 task metadata 缺失级联失败。默认测试现改用临时自包含 source fixture,production preflight 仍默认执行真实 `git rev-parse` 并校验固定 commit;无 live 授权路径也在读取外部 task cache 前 fail closed。
- 第三轮本地验证:Windows/sherpa/Electron 定向回归 `31/31`;DeepSWE adapter/benchmark `29/29`;对应 smart runner 完整并行批次 `108/108`;真实本地 DeepSWE Release 20 preflight `205/205`;受影响 ESLint、`npm run typecheck`、`npm run test:contracts`、`npm run verify:app-version`、脚本治理和 `git diff --check` 全部通过。仍待 follow-up commit/tag 更新后由 `windows-2022` 证明 MSVC、Squirrel、安装态 CodeMode/native host Gate B,并由干净 Linux runner 复验 Frontend Full。
已通过:
+2 -1
View File
@@ -208,11 +208,12 @@ export function preflightSelectedTasks({
sourceRoot,
sliceName = "release-20",
manifestPath = DEEPSWE_MANIFEST_PATH,
resolveSourceCommit = (root) => runGit(root, ["rev-parse", "HEAD"]),
}) {
const checks = [];
const add = (name, passed, detail) => checks.push({ name, passed, detail });
const { manifest } = loadSliceManifest(repoRoot, manifestPath);
const sourceHead = runGit(sourceRoot, ["rev-parse", "HEAD"]);
const sourceHead = resolveSourceCommit(sourceRoot);
add(
"source-commit",
sourceHead === manifest.source.commit &&
+5 -5
View File
@@ -246,6 +246,11 @@ async function main() {
throw new Error("--verifier-only requires --run-dir");
}
}
if (!options.verifierOnly && !options.allowLiveProvider) {
throw new Error(
"real DeepSWE execution requires --allow-live-provider; preflight remains offline",
);
}
const taskId = options.verifierOnly
? verifierTaskIdForRun({
runDir: options.runDir,
@@ -330,11 +335,6 @@ async function main() {
return;
}
if (!options.allowLiveProvider) {
throw new Error(
"real DeepSWE execution requires --allow-live-provider; preflight remains offline",
);
}
const runId = `${timestampId()}-${task.id}`;
const runDir = path.join(options.runsRoot, runId);
const { workspaceRoot, workspaceDir } = createTaskWorkspaceLocation({
+26 -6
View File
@@ -25,10 +25,11 @@ import {
verifierCompletionStatus,
verifierTaskIdForRun,
} from "./deepswe-adapter-core.mjs";
import { createDeepSweSourceFixture } from "./fixtures/deepswe-source.mjs";
const repoRoot = process.cwd();
const sourceRoot = path.join(repoRoot, ".lime/benchmark/sources/deep-swe");
const temporaryRoots = [];
let sourceFixture = null;
function temporaryRoot() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "deepswe-adapter-test-"));
@@ -36,6 +37,14 @@ function temporaryRoot() {
return root;
}
function deepSweSourceFixture() {
if (!sourceFixture) {
sourceFixture = createDeepSweSourceFixture({ repoRoot });
temporaryRoots.push(sourceFixture.sourceRoot);
}
return sourceFixture;
}
function git(cwd, args) {
return execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
}
@@ -56,6 +65,7 @@ afterEach(() => {
for (const root of temporaryRoots.splice(0)) {
fs.rmSync(root, { recursive: true, force: true });
}
sourceFixture = null;
});
describe("DeepSWE current-chain adapter", () => {
@@ -171,10 +181,12 @@ describe("DeepSWE current-chain adapter", () => {
});
it("validates all selected Release 20 tasks against the pinned source", () => {
const fixture = deepSweSourceFixture();
const result = preflightSelectedTasks({
repoRoot,
sourceRoot,
sourceRoot: fixture.sourceRoot,
sliceName: "release-20",
resolveSourceCommit: () => fixture.sourceCommit,
});
expect(result.status).toBe("pass");
@@ -194,17 +206,22 @@ describe("DeepSWE current-chain adapter", () => {
}, 20_000);
it("loads task metadata with TOML semantics instead of ad hoc line parsing", () => {
const fixture = deepSweSourceFixture();
const task = loadTaskDefinition({
repoRoot,
sourceRoot,
sourceRoot: fixture.sourceRoot,
taskId: "happy-dom-abort-pending-body-reads",
});
const fixtureTask = fixture.tasks.get(
"happy-dom-abort-pending-body-reads",
);
expect(task).toMatchObject({
id: "happy-dom-abort-pending-body-reads",
schemaVersion: "1.3",
repository: "capricorn86/happy-dom",
baseCommit: "82a0888cb2c87a6123e05424b528f8e8c9b3e426",
repositoryUrl: fixtureTask.repositoryUrl,
baseCommit: fixtureTask.baseCommit,
artifacts: ["/logs/artifacts/model.patch"],
agent: { networkMode: "no-network", timeoutSec: 5400 },
verifier: {
@@ -213,14 +230,14 @@ describe("DeepSWE current-chain adapter", () => {
collect: [
{
command: expect.stringContaining(
"git diff --binary 82a0888cb2c87a6123e05424b528f8e8c9b3e426 HEAD",
`git diff --binary ${fixtureTask.baseCommit} HEAD`,
),
timeoutSec: 300,
},
],
},
});
expect(task.instruction).toContain("AbortError");
expect(task.instruction).toBe(fixtureTask.instruction);
});
it("prepares an isolated branch and captures committed plus uncommitted changes", () => {
@@ -1079,6 +1096,7 @@ fs.writeFileSync(path.join(verifierDir, "test-stdout.txt"), "passed");
it("records verifier prerequisite blockers without overwriting product failure evidence", () => {
const root = temporaryRoot();
const fixture = deepSweSourceFixture();
const runDir = path.join(root, "existing-run");
fs.mkdirSync(runDir);
fs.writeFileSync(
@@ -1108,6 +1126,8 @@ fs.writeFileSync(path.join(verifierDir, "test-stdout.txt"), "passed");
"--verifier-only",
"--run-dir",
runDir,
"--source-root",
fixture.sourceRoot,
"--pier-bin",
path.join(root, "missing-pier"),
"--container-bin",
+2
View File
@@ -164,6 +164,7 @@ export function createBatchPlan({
sourceRoot = path.resolve(root, ".lime/benchmark/sources/deep-swe"),
sliceName = "release-20",
trials = 1,
resolveSourceCommit,
} = {}) {
const { manifest } = loadSliceManifest(root, manifestPath);
const taskIds = taskIdsForSlice(manifest, sliceName);
@@ -172,6 +173,7 @@ export function createBatchPlan({
sourceRoot,
sliceName,
manifestPath,
...(resolveSourceCommit ? { resolveSourceCommit } : {}),
});
const tasks = taskIds.flatMap((taskId) =>
Array.from({ length: trials }, (_, index) => ({
+33 -22
View File
@@ -9,35 +9,46 @@ import {
createBatchPlan,
parseArgs,
} from "./deepswe-benchmark.mjs";
import { createDeepSweSourceFixture } from "./fixtures/deepswe-source.mjs";
const repoRoot = process.cwd();
describe("DeepSWE Core batch benchmark", () => {
it("plans Smoke 10 and Release 20 with explicit trial counts", () => {
const smoke = createBatchPlan({
const fixture = createDeepSweSourceFixture({ repoRoot });
const options = {
repoRoot,
sliceName: "smoke-10",
trials: 1,
});
const release = createBatchPlan({
repoRoot,
sliceName: "release-20",
trials: 3,
});
sourceRoot: fixture.sourceRoot,
resolveSourceCommit: () => fixture.sourceCommit,
};
try {
const smoke = createBatchPlan({
...options,
sliceName: "smoke-10",
trials: 1,
});
const release = createBatchPlan({
...options,
sliceName: "release-20",
trials: 3,
});
expect(smoke).toMatchObject({
status: "ready",
taskCount: 10,
expectedTrialCount: 10,
trialsPerTask: 1,
});
expect(release).toMatchObject({
status: "ready",
taskCount: 20,
expectedTrialCount: 60,
trialsPerTask: 3,
});
expect(new Set(release.tasks.map((task) => task.trialKey)).size).toBe(60);
expect(smoke).toMatchObject({
status: "ready",
taskCount: 10,
expectedTrialCount: 10,
trialsPerTask: 1,
});
expect(release).toMatchObject({
status: "ready",
taskCount: 20,
expectedTrialCount: 60,
trialsPerTask: 3,
});
expect(new Set(release.tasks.map((task) => task.trialKey)).size).toBe(60);
} finally {
fs.rmSync(fixture.sourceRoot, { recursive: true, force: true });
}
}, 60_000);
it("rejects ambiguous modes and non-release trial counts", () => {
@@ -0,0 +1,73 @@
import { createHash } from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
export function createDeepSweSourceFixture({
repoRoot,
manifestPath = "internal/test/deepswe-coding-slice-v2.json",
} = {}) {
const manifest = JSON.parse(
fs.readFileSync(path.resolve(repoRoot, manifestPath), "utf8"),
);
const sourceRoot = fs.mkdtempSync(
path.join(os.tmpdir(), "lime-deepswe-source-test-"),
);
const tasksRoot = path.join(sourceRoot, "tasks");
const tasks = new Map();
fs.mkdirSync(tasksRoot, { recursive: true });
fs.writeFileSync(path.join(sourceRoot, "LICENSE"), "Apache-2.0\n", "utf8");
fs.writeFileSync(
path.join(sourceRoot, "PROVENANCE.md"),
"# Test fixture provenance\n",
"utf8",
);
for (const task of manifest.tasks) {
const taskDir = path.join(tasksRoot, task.id);
const baseCommit = createHash("sha1").update(task.id).digest("hex");
const repositoryUrl = `https://github.com/${task.repository}.git`;
const instruction = `Fix the ${task.id} regression.`;
fs.mkdirSync(taskDir, { recursive: true });
fs.writeFileSync(path.join(taskDir, "instruction.md"), instruction, "utf8");
fs.writeFileSync(
path.join(taskDir, "task.toml"),
`schema_version = "${manifest.source.taskSchemaVersion}"
artifacts = ["/logs/artifacts/model.patch"]
[metadata]
repository_url = "${repositoryUrl}"
base_commit_hash = "${baseCommit}"
[agent]
network_mode = "no-network"
timeout_sec = 5400
[environment]
docker_image = "lime/deepswe-test:latest"
cpus = 2
memory_mb = 4096
storage_mb = 8192
[verifier]
network_mode = "no-network"
environment_mode = "separate"
timeout_sec = 600
[[verifier.collect]]
command = "git diff --binary ${baseCommit} HEAD > /logs/artifacts/model.patch"
timeout_sec = 300
`,
"utf8",
);
tasks.set(task.id, { baseCommit, instruction, repositoryUrl });
}
return {
sourceRoot,
sourceCommit: manifest.source.commit,
tasks,
};
}
+1 -1
View File
@@ -442,7 +442,7 @@ function compileWindowsNativeHost({
"-o",
outputPath,
];
execFileSyncImpl(compiler, args, { stdio: "pipe" });
execFileSyncImpl(compiler, args, { stdio: "inherit" });
chmodSync(outputPath, 0o755);
}
@@ -46,6 +46,19 @@ function createWindowsPackage() {
}
describe("electron desktop resource manifest", () => {
it("Windows native host 使用 SDK UIA_HWND 类型接收窗口句柄", () => {
const source = readFileSync(
"electron/native/windows/windows-native-host.cpp",
"utf8",
);
expect(source).toContain("UIA_HWND nativeWindowHandle = nullptr;");
expect(source).not.toContain("int nativeWindowHandle = 0;");
expect(source).not.toMatch(
/^\s*HWND nativeWindowHandle = nullptr;\s*$/mu,
);
});
it("按平台和架构生成稳定的资源清单", () => {
expect(desktopResourcePlatformKey("darwin", "arm64")).toBe("darwin-arm64");
expect(desktopResourcePlatformKey("win32", "x64")).toBe("win32-x64");