Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.

Commit 1740b9c

Browse files
committed
FEA-1554: Resolve merge conflict — bump version to 0.15.115
2 parents 651e32a + 09cf797 commit 1740b9c

16 files changed

Lines changed: 335 additions & 345 deletions

.github/workflows/compatibility-smoke.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,12 @@ jobs:
171171
if: steps.lkg.outputs.is_placeholder != 'true' && steps.compat-auth.outputs.available == 'true'
172172
uses: pnpm/action-setup@v4
173173

174-
# Node 20 (not 22) because symphony-alpha requires ^20.19 for Prisma 7.x
174+
# Follow symphony-alpha's target monorepo tooling baseline: engines.node >=24.
175175
- name: Setup Node.js
176176
if: steps.lkg.outputs.is_placeholder != 'true' && steps.compat-auth.outputs.available == 'true'
177177
uses: actions/setup-node@v4
178178
with:
179-
node-version: 20
179+
node-version: 24
180180
cache: 'pnpm'
181181
cache-dependency-path: symphony-checkout/pnpm-lock.yaml
182182

.github/workflows/release.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
if: steps.check_release.outputs.skip != 'true'
5050
uses: actions/setup-node@v4
5151
with:
52-
node-version: 22
52+
node-version: 24
5353
cache: pnpm
5454

5555
- name: Install dependencies
@@ -58,6 +58,10 @@ jobs:
5858
env:
5959
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6060

61+
- name: Verify Electron binary install
62+
if: steps.check_release.outputs.skip != 'true'
63+
run: pnpm -C apps/desktop verify:electron-binary
64+
6165
- name: Generate build info
6266
if: steps.check_release.outputs.skip != 'true'
6367
run: pnpm -r prebuild

.github/workflows/test.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@ jobs:
2828
- name: Setup Node.js
2929
uses: actions/setup-node@v4
3030
with:
31-
node-version: 22
31+
node-version: 24
3232
cache: pnpm
3333

3434
- name: Install dependencies
3535
run: pnpm install --frozen-lockfile
3636
env:
3737
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3838

39+
# Node 24 Ubuntu runners have installed the electron package without a
40+
# usable platform binary/path; verify first and repair only if needed.
41+
- name: Verify Electron binary install
42+
run: pnpm -C apps/desktop verify:electron-binary
43+
3944
- name: Generate build info
4045
run: pnpm -r prebuild
4146

.github/workflows/version-check.yml

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ name: Desktop Version Bump Check
33
on:
44
pull_request:
55
types: [opened, synchronize, reopened]
6-
paths:
7-
- "apps/desktop/**"
86

97
jobs:
108
version-bump-check:
@@ -16,17 +14,67 @@ jobs:
1614
with:
1715
fetch-depth: 0
1816

17+
- name: Detect desktop changes
18+
id: desktop_changes
19+
run: |
20+
BASE_REF="${{ github.event.pull_request.base.ref }}"
21+
BASE=$(git merge-base "origin/$BASE_REF" HEAD)
22+
23+
if git diff --quiet "$BASE" HEAD -- apps/desktop; then
24+
echo "changed=false" >> "$GITHUB_OUTPUT"
25+
echo "No apps/desktop changes detected; desktop version bump is not required."
26+
else
27+
echo "changed=true" >> "$GITHUB_OUTPUT"
28+
echo "apps/desktop changes detected; desktop version bump is required."
29+
fi
30+
1931
- name: Check desktop version bump
32+
if: steps.desktop_changes.outputs.changed == 'true'
2033
run: |
2134
BASE_REF="${{ github.event.pull_request.base.ref }}"
2235
BASE=$(git merge-base "origin/$BASE_REF" HEAD)
2336
2437
OLD_VERSION=$(git show "$BASE":apps/desktop/package.json | jq -r .version)
38+
CURRENT_BASE_VERSION=$(git show "origin/$BASE_REF":apps/desktop/package.json | jq -r .version)
2539
NEW_VERSION=$(jq -r .version apps/desktop/package.json)
2640
41+
version_gt() {
42+
local candidate="$1"
43+
local baseline="$2"
44+
local candidate_major candidate_minor candidate_patch
45+
local baseline_major baseline_minor baseline_patch
46+
47+
IFS=. read -r candidate_major candidate_minor candidate_patch <<< "$candidate"
48+
IFS=. read -r baseline_major baseline_minor baseline_patch <<< "$baseline"
49+
50+
for part in "$candidate_major" "$candidate_minor" "$candidate_patch" "$baseline_major" "$baseline_minor" "$baseline_patch"; do
51+
if [[ ! "$part" =~ ^[0-9]+$ ]]; then
52+
echo "::error::Desktop versions must use numeric major.minor.patch format for comparison. Saw '$candidate' and '$baseline'."
53+
exit 1
54+
fi
55+
done
56+
57+
if (( candidate_major != baseline_major )); then
58+
(( candidate_major > baseline_major ))
59+
return
60+
fi
61+
62+
if (( candidate_minor != baseline_minor )); then
63+
(( candidate_minor > baseline_minor ))
64+
return
65+
fi
66+
67+
(( candidate_patch > baseline_patch ))
68+
}
69+
2770
if [ "$OLD_VERSION" = "$NEW_VERSION" ]; then
2871
echo "::error::apps/desktop/ was modified but version in apps/desktop/package.json was not bumped (still $OLD_VERSION). Please bump the version."
2972
exit 1
3073
fi
3174
32-
echo "Version bumped: $OLD_VERSION -> $NEW_VERSION"
75+
if ! version_gt "$NEW_VERSION" "$CURRENT_BASE_VERSION"; then
76+
echo "::error::apps/desktop/package.json version ($NEW_VERSION) must be greater than the current base branch version ($CURRENT_BASE_VERSION). Fetch the latest base branch and bump the desktop version again."
77+
exit 1
78+
fi
79+
80+
echo "Version bumped: $OLD_VERSION -> $NEW_VERSION; current base branch version is $CURRENT_BASE_VERSION"

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ TypeScript is strict-mode (`tsconfig.base.json`) and ESM (`NodeNext`).
3737
- In Desktop main/server NodeNext ESM code, dependency subpath imports must either use an exported package subpath or the concrete runtime file extension, and new subpath imports should be validated against built output before shipping.
3838
- Prefix intentionally unused variables/args with `_` to satisfy lint rules.
3939
- Do not edit `apps/desktop/src/shared/build-info.ts` manually (auto-generated in prebuild).
40+
- When changing the desktop Node tooling baseline or Electron runtime assumptions, keep `@types/node` pinned to the lowest supported Node runtime major so TypeScript cannot accept newer Node-only APIs.
41+
- For GitHub Actions jobs that run desktop tests or package/release under Node 24, verify Electron's platform binary with `pnpm -C apps/desktop verify:electron-binary` immediately after `pnpm install --frozen-lockfile`. Do not inline ad hoc Electron download logic in workflows; keep the shared verifier out of static/headless audit jobs that do not launch or import Electron.
4042
- Avoid unnecessary TypeScript casts. Prefer importing concrete shared types, narrowing with type guards, or shaping helper return types so call sites do not need `as` to satisfy the compiler.
4143
- Use shared constants, generated enums, or exported enum-like objects for statuses, reasons, protocol modes, channel names, storage keys, and other contract values. Do not duplicate hardcoded strings when a constant or enum exists.
4244
- Export and reuse shared TypeScript types for cross-module contracts or metadata patches instead of duplicating inline `Pick`/`Partial` shapes in callers.

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ We welcome contributions! This guide covers everything you need to get started.
66

77
### Prerequisites
88

9-
- **Node.js** 22+
10-
- **pnpm** 9.15+ (`corepack enable && corepack prepare pnpm@9.15.0 --activate`)
9+
- **Node.js** 24+ for development, build, and test tooling; Electron's bundled runtime Node version is unchanged.
10+
- **pnpm** 11.3.0 (`corepack enable && corepack prepare pnpm@11.3.0 --activate`)
1111
- **just** command runner (`brew install just`)
1212
- **macOS** (Electron desktop builds target macOS only)
1313

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ Electron desktop app for the [ClosedLoop](https://closedloop.ai) platform. Provi
44

55
## Prerequisites
66

7-
- **Node.js** 22+
8-
- **pnpm** 9.15+ (`corepack enable && corepack prepare pnpm@9.15.0 --activate`)
7+
- **Node.js** 24+ for development, build, and test tooling; Electron's bundled runtime Node version is unchanged.
8+
- **pnpm** 11.3.0 (`corepack enable && corepack prepare pnpm@11.3.0 --activate`)
99
- **just** command runner (`brew install just`)
1010
- **macOS** or **Linux** (packaging currently macOS-only; Linux runs via `just desktop-dev`)
1111

apps/desktop/ci/audit-gate.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
- uses: pnpm/action-setup@v4
4141
- uses: actions/setup-node@v4
4242
with:
43-
node-version: 22
43+
node-version: 24
4444
cache: pnpm
4545
- name: Install dependencies
4646
run: pnpm install --frozen-lockfile
@@ -86,7 +86,7 @@ jobs:
8686
- uses: pnpm/action-setup@v4
8787
- uses: actions/setup-node@v4
8888
with:
89-
node-version: 22
89+
node-version: 24
9090
cache: pnpm
9191
- name: Install dependencies
9292
run: pnpm install --frozen-lockfile

apps/desktop/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "desktop",
3-
"version": "0.15.113",
3+
"version": "0.15.115",
44
"description": "ClosedLoop Desktop",
55
"author": "ClosedLoop AI <support@closedloop.ai>",
66
"private": true,
@@ -24,6 +24,7 @@
2424
"assert:design-system-boundary": "tsx --test test/agent-dashboard-boundary.test.ts",
2525
"test:boot:design-system-off": "node --import tsx scripts/assert-design-system-boot-off.mjs",
2626
"measure:agent-dashboard-storage": "node scripts/measure-agent-dashboard-storage.mjs",
27+
"verify:electron-binary": "node scripts/ensure-electron-binary.mjs",
2728
"test": "tsx --test --test-concurrency=1 test/*.test.ts && node --test \"scripts/agent-monitor-packs/__tests__/*.test.js\" \"scripts/agent-monitor-pull-requests/__tests__/*.test.js\"",
2829
"pretest:contract": "pnpm build:agent-monitor",
2930
"test:contract": "node --test \"test-e2e/agent-monitor/specs/api-contract/*.test.mjs\"",
@@ -65,12 +66,12 @@
6566
"@tailwindcss/typography": "^0.5.19",
6667
"@tailwindcss/vite": "^4.3.0",
6768
"@types/busboy": "^1.5.4",
68-
"@types/node": "^22.13.8",
69+
"@types/node": "24.12.4",
6970
"@types/react": "^19.2.0",
7071
"@types/react-dom": "^19.2.0",
7172
"@typescript-eslint/eslint-plugin": "^8.57.1",
7273
"@typescript-eslint/parser": "^8.57.1",
73-
"@vitejs/plugin-react": "4.3.4",
74+
"@vitejs/plugin-react": "^5.1.3",
7475
"agent-dashboard-client": "github:hoangsonww/Claude-Code-Agent-Monitor#840c518d7fa69231de049e41b893938228b67e40&path:/client",
7576
"autoprefixer": "10.4.20",
7677
"electron": "^35.0.2",
@@ -83,6 +84,6 @@
8384
"tw-animate-css": "^1.4.0",
8485
"typescript": "^5.8.2",
8586
"typescript-eslint": "^8.57.1",
86-
"vite": "6.1.0"
87+
"vite": "^7.3.2"
8788
}
8889
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Verifies Electron's platform binary and repairs the install when GitHub
4+
* Actions leaves the npm package present but without a usable dist/path.txt.
5+
*
6+
* The repair path intentionally avoids @electron/get because that helper was
7+
* unreliable on the Node 24 Ubuntu runner during FEA-1543 CI remediation.
8+
*/
9+
import { spawnSync } from "node:child_process";
10+
import { createHash } from "node:crypto";
11+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
12+
import { createRequire } from "node:module";
13+
import { tmpdir } from "node:os";
14+
import path from "node:path";
15+
16+
const appRequire = createRequire(import.meta.url);
17+
const electronPackageJsonPath = appRequire.resolve("electron/package.json");
18+
const electronDir = path.dirname(electronPackageJsonPath);
19+
const electronRequire = createRequire(path.join(electronDir, "install.js"));
20+
const electronIndexPath = appRequire.resolve("electron");
21+
const electronPackage = JSON.parse(readFileSync(electronPackageJsonPath, "utf8"));
22+
23+
function getPlatformPath(platform) {
24+
const platformPaths = {
25+
darwin: "Electron.app/Contents/MacOS/Electron",
26+
freebsd: "electron",
27+
linux: "electron",
28+
mas: "Electron.app/Contents/MacOS/Electron",
29+
openbsd: "electron",
30+
win32: "electron.exe",
31+
};
32+
const platformPath = platformPaths[platform];
33+
if (platformPath == null) {
34+
throw new Error(`Electron builds are not available on platform: ${platform}`);
35+
}
36+
return platformPath;
37+
}
38+
39+
function getArch(platform) {
40+
if (process.env.npm_config_arch) {
41+
return process.env.npm_config_arch;
42+
}
43+
if (platform !== "darwin" || process.platform !== "darwin" || process.arch !== "x64") {
44+
return process.arch;
45+
}
46+
47+
const translated = spawnSync("sysctl", ["-in", "sysctl.proc_translated"], { encoding: "utf8" });
48+
return translated.status === 0 && translated.stdout.trim() === "1" ? "arm64" : process.arch;
49+
}
50+
51+
function verifyInstalledBinary() {
52+
delete appRequire.cache[electronIndexPath];
53+
const electronBinary = appRequire("electron");
54+
if (typeof electronBinary !== "string" || electronBinary.length === 0) {
55+
throw new Error("Electron module did not resolve to a binary path.");
56+
}
57+
if (!existsSync(electronBinary)) {
58+
throw new Error(`Electron binary is missing at ${electronBinary}`);
59+
}
60+
const binaryStats = statSync(electronBinary);
61+
if (!binaryStats.isFile() || binaryStats.size === 0) {
62+
throw new Error(`Electron binary is not a non-empty file at ${electronBinary}`);
63+
}
64+
return electronBinary;
65+
}
66+
67+
function runChecked(command, args) {
68+
const result = spawnSync(command, args, { stdio: "inherit" });
69+
if (result.error) {
70+
throw result.error;
71+
}
72+
if (result.status !== 0) {
73+
throw new Error(`${command} ${args.join(" ")} exited with status ${result.status}`);
74+
}
75+
}
76+
77+
function repairElectronBinary() {
78+
const version = electronPackage.version;
79+
const platform = process.env.npm_config_platform || process.platform;
80+
const arch = getArch(platform);
81+
const platformPath = getPlatformPath(platform);
82+
const artifact = `electron-v${version}-${platform}-${arch}.zip`;
83+
const checksums = electronRequire("./checksums.json");
84+
const expectedChecksum = checksums[artifact];
85+
if (expectedChecksum == null) {
86+
throw new Error(`Missing Electron checksum for ${artifact}`);
87+
}
88+
89+
const downloadDir = process.env.RUNNER_TEMP || path.join(tmpdir(), "closedloop-electron-binary");
90+
mkdirSync(downloadDir, { recursive: true });
91+
const zipPath = path.join(downloadDir, artifact);
92+
const downloadUrl = `https://github.com/electron/electron/releases/download/v${version}/${artifact}`;
93+
94+
console.log(`Downloading ${artifact}`);
95+
runChecked("curl", ["--fail", "--location", "--retry", "3", "--output", zipPath, downloadUrl]);
96+
97+
const actualChecksum = createHash("sha256").update(readFileSync(zipPath)).digest("hex");
98+
if (actualChecksum !== expectedChecksum) {
99+
throw new Error(`Electron checksum mismatch for ${artifact}: expected ${expectedChecksum}, got ${actualChecksum}`);
100+
}
101+
102+
const distDir = path.join(electronDir, "dist");
103+
rmSync(distDir, { recursive: true, force: true });
104+
mkdirSync(distDir, { recursive: true });
105+
runChecked("unzip", ["-q", zipPath, "-d", distDir]);
106+
107+
const extractedTypes = path.join(distDir, "electron.d.ts");
108+
if (existsSync(extractedTypes)) {
109+
renameSync(extractedTypes, path.join(electronDir, "electron.d.ts"));
110+
}
111+
writeFileSync(path.join(electronDir, "path.txt"), platformPath);
112+
}
113+
114+
try {
115+
try {
116+
const electronBinary = verifyInstalledBinary();
117+
console.log(`Electron binary verified at ${electronBinary}`);
118+
} catch (verificationError) {
119+
console.log(`Electron binary verification failed: ${verificationError.message}`);
120+
repairElectronBinary();
121+
const electronBinary = verifyInstalledBinary();
122+
console.log(`Electron binary repaired and verified at ${electronBinary}`);
123+
}
124+
} catch (error) {
125+
console.error(error.stack || error);
126+
process.exitCode = 1;
127+
}

0 commit comments

Comments
 (0)