Skip to content

Commit 836e035

Browse files
committed
merge: sync dev into main
# Conflicts: # package.json
2 parents 0e2be03 + bfad861 commit 836e035

94 files changed

Lines changed: 10092 additions & 1237 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 25 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,10 @@ jobs:
166166
include:
167167
- arch: arm64
168168
runner: macos-14
169-
dist_command: pnpm dist:mac:arm64
169+
dist_command: pnpm pack:mac:arm64
170170
- arch: x64
171171
runner: macos-15-intel
172-
dist_command: pnpm dist:mac:x64
172+
dist_command: pnpm pack:mac:x64
173173
runs-on: ${{ matrix.runner }}
174174

175175
steps:
@@ -206,32 +206,18 @@ jobs:
206206
VERSION="${GITHUB_REF#refs/tags/v}"
207207
pnpm pkg set version="$VERSION"
208208
209-
- name: Resolve runtime asset name (macOS ${{ matrix.arch }})
210-
if: startsWith(github.ref, 'refs/tags/v')
211-
id: runtime-asset
212-
shell: bash
213-
run: |
214-
set -euo pipefail
215-
if [[ "${{ matrix.arch }}" == "arm64" ]]; then
216-
platform="darwin-arm64"
217-
else
218-
platform="darwin-x64"
219-
fi
220-
echo "asset_name=$(node ./scripts/runtime-lock.mjs asset-name "$platform")" >> "$GITHUB_OUTPUT"
221-
222209
- name: Stage bundled runtime (macOS ${{ matrix.arch }})
223-
if: startsWith(github.ref, 'refs/tags/v')
224210
env:
225211
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
226212
shell: bash
227213
run: |
228214
set -euo pipefail
229-
TAG="${GITHUB_REF#refs/tags/}"
230-
rm -rf .runtime-download resources/runtime
231-
mkdir -p .runtime-download resources/runtime
232-
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern "${{ steps.runtime-asset.outputs.asset_name }}" --dir .runtime-download
233-
tar -xzf ".runtime-download/${{ steps.runtime-asset.outputs.asset_name }}" -C .runtime-download
234-
cp -R .runtime-download/runtime/. resources/runtime/
215+
if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then
216+
TAG="${GITHUB_REF#refs/tags/}"
217+
node ./scripts/stage-runtime.mjs --platform "darwin-${{ matrix.arch }}" --release-tag "$TAG"
218+
else
219+
node ./scripts/stage-runtime.mjs --platform "darwin-${{ matrix.arch }}"
220+
fi
235221
236222
- name: Build app (macOS ${{ matrix.arch }})
237223
env:
@@ -248,9 +234,7 @@ jobs:
248234
test -f dist-electron/preload/index.js
249235
test -f out/renderer/index.html
250236
test -f mcp-server/dist/index.js
251-
if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then
252-
test -f resources/runtime/VERSION
253-
fi
237+
test -f resources/runtime/VERSION
254238
255239
- name: Package (macOS ${{ matrix.arch }})
256240
env:
@@ -321,28 +305,18 @@ jobs:
321305
VERSION="${GITHUB_REF#refs/tags/v}"
322306
pnpm pkg set version="$VERSION"
323307
324-
- name: Resolve runtime asset name (Windows)
325-
if: startsWith(github.ref, 'refs/tags/v')
326-
id: runtime-asset
327-
shell: bash
328-
run: |
329-
echo "asset_name=$(node ./scripts/runtime-lock.mjs asset-name win32-x64)" >> "$GITHUB_OUTPUT"
330-
331308
- name: Stage bundled runtime (Windows)
332-
if: startsWith(github.ref, 'refs/tags/v')
333309
shell: pwsh
334310
env:
335311
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
336312
run: |
337313
$ErrorActionPreference = "Stop"
338-
$tag = $env:GITHUB_REF.Replace('refs/tags/', '')
339-
Remove-Item .runtime-download -Recurse -Force -ErrorAction SilentlyContinue
340-
Remove-Item resources/runtime/* -Recurse -Force -ErrorAction SilentlyContinue
341-
New-Item -ItemType Directory -Force -Path .runtime-download | Out-Null
342-
New-Item -ItemType Directory -Force -Path resources/runtime | Out-Null
343-
gh release download $tag --repo $env:GITHUB_REPOSITORY --pattern "${{ steps.runtime-asset.outputs.asset_name }}" --dir .runtime-download
344-
Expand-Archive -Path ".runtime-download/${{ steps.runtime-asset.outputs.asset_name }}" -DestinationPath .runtime-download/unpacked -Force
345-
Copy-Item .runtime-download/unpacked/runtime/* resources/runtime -Recurse -Force
314+
if ($env:GITHUB_REF -like 'refs/tags/v*') {
315+
$tag = $env:GITHUB_REF.Replace('refs/tags/', '')
316+
node ./scripts/stage-runtime.mjs --platform win32-x64 --release-tag $tag
317+
} else {
318+
node ./scripts/stage-runtime.mjs --platform win32-x64
319+
}
346320
347321
- name: Build app (Windows)
348322
env:
@@ -360,14 +334,12 @@ jobs:
360334
test -f dist-electron/preload/index.js
361335
test -f out/renderer/index.html
362336
test -f mcp-server/dist/index.js
363-
if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then
364-
test -f resources/runtime/VERSION
365-
fi
337+
test -f resources/runtime/VERSION
366338
367339
- name: Package (Windows)
368340
env:
369341
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
370-
run: pnpm dist:win --publish never
342+
run: pnpm pack:win --publish never
371343

372344
- name: Validate packaged bundle (Windows)
373345
shell: bash
@@ -435,26 +407,18 @@ jobs:
435407
VERSION="${GITHUB_REF#refs/tags/v}"
436408
pnpm pkg set version="$VERSION"
437409
438-
- name: Resolve runtime asset name (Linux)
439-
if: startsWith(github.ref, 'refs/tags/v')
440-
id: runtime-asset
441-
shell: bash
442-
run: |
443-
echo "asset_name=$(node ./scripts/runtime-lock.mjs asset-name linux-x64)" >> "$GITHUB_OUTPUT"
444-
445410
- name: Stage bundled runtime (Linux)
446-
if: startsWith(github.ref, 'refs/tags/v')
447411
env:
448412
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
449413
shell: bash
450414
run: |
451415
set -euo pipefail
452-
TAG="${GITHUB_REF#refs/tags/}"
453-
rm -rf .runtime-download resources/runtime
454-
mkdir -p .runtime-download resources/runtime
455-
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern "${{ steps.runtime-asset.outputs.asset_name }}" --dir .runtime-download
456-
tar -xzf ".runtime-download/${{ steps.runtime-asset.outputs.asset_name }}" -C .runtime-download
457-
cp -R .runtime-download/runtime/. resources/runtime/
416+
if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then
417+
TAG="${GITHUB_REF#refs/tags/}"
418+
node ./scripts/stage-runtime.mjs --platform linux-x64 --release-tag "$TAG"
419+
else
420+
node ./scripts/stage-runtime.mjs --platform linux-x64
421+
fi
458422
459423
- name: Build app (Linux)
460424
env:
@@ -471,14 +435,12 @@ jobs:
471435
test -f dist-electron/preload/index.js
472436
test -f out/renderer/index.html
473437
test -f mcp-server/dist/index.js
474-
if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then
475-
test -f resources/runtime/VERSION
476-
fi
438+
test -f resources/runtime/VERSION
477439
478440
- name: Package (Linux)
479441
env:
480442
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
481-
run: pnpm dist:linux --publish never
443+
run: pnpm pack:linux --publish never
482444

483445
- name: Validate packaged bundle (Linux)
484446
run: node ./scripts/electron-builder/verifyBundle.cjs "release/linux-unpacked" linux x64

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ remotion/*
5858
.board-task-log-freshness/
5959

6060
.serena/
61+
.playwright-mcp/
6162

6263
# Local release operator notes
6364
/ORCHESTRATOR_RELEASE_RUNBOOK.local.md
65+
66+
# Local reference captures
67+
/agent-teams-reference-fix-*.png

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,13 @@ pnpm dist:mac:arm64 # macOS Apple Silicon (.dmg)
300300
pnpm dist:mac:x64 # macOS Intel (.dmg)
301301
pnpm dist:win # Windows (.exe)
302302
pnpm dist:linux # Linux (AppImage/.deb/.rpm/.pacman)
303-
pnpm dist # macOS + Windows + Linux
303+
pnpm dist # Current platform
304304
```
305305

306+
Distribution scripts run the production build and stage the bundled multimodel runtime from
307+
`runtime.lock.json` before packaging. Use `pnpm clean:runtime` to remove staged runtime files after
308+
local packaging.
309+
306310
### Scripts
307311

308312
| Command | Description |
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const crypto = require('crypto');
4+
5+
const RENAME_MAX_ATTEMPTS = 8;
6+
const RENAME_RETRY_BASE_DELAY_MS = 40;
7+
const RENAME_RETRY_MAX_DELAY_MS = 250;
8+
const RENAME_RETRY_JITTER_MS = 25;
9+
const RETRYABLE_RENAME_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']);
10+
11+
function sleepSync(ms) {
12+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
13+
}
14+
15+
function getRenameRetryDelayMs(attempt) {
16+
const backoff = Math.min(RENAME_RETRY_BASE_DELAY_MS * attempt, RENAME_RETRY_MAX_DELAY_MS);
17+
return backoff + Math.floor(Math.random() * (RENAME_RETRY_JITTER_MS + 1));
18+
}
19+
20+
function fsyncFileBestEffort(filePath) {
21+
let fd = null;
22+
try {
23+
fd = fs.openSync(filePath, 'r+');
24+
fs.fsyncSync(fd);
25+
} catch {
26+
// Best effort only. Some filesystems do not support fsync for these files.
27+
} finally {
28+
if (fd !== null) {
29+
try {
30+
fs.closeSync(fd);
31+
} catch {
32+
// Best effort only.
33+
}
34+
}
35+
}
36+
}
37+
38+
function renameWithRetrySync(tempPath, filePath) {
39+
for (let attempt = 1; attempt <= RENAME_MAX_ATTEMPTS; attempt += 1) {
40+
try {
41+
fs.renameSync(tempPath, filePath);
42+
return;
43+
} catch (error) {
44+
if (error && error.code === 'EXDEV') {
45+
fs.copyFileSync(tempPath, filePath);
46+
try {
47+
fs.rmSync(tempPath, { force: true });
48+
} catch {
49+
// Best effort cleanup after cross-device fallback.
50+
}
51+
return;
52+
}
53+
54+
if (error && RETRYABLE_RENAME_CODES.has(error.code) && attempt < RENAME_MAX_ATTEMPTS) {
55+
sleepSync(getRenameRetryDelayMs(attempt));
56+
continue;
57+
}
58+
59+
throw error;
60+
}
61+
}
62+
}
63+
64+
function atomicWriteFileSync(filePath, data, options) {
65+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
66+
const tempPath = path.join(path.dirname(filePath), `.tmp.${crypto.randomUUID()}`);
67+
68+
try {
69+
fs.writeFileSync(tempPath, data, options);
70+
fsyncFileBestEffort(tempPath);
71+
renameWithRetrySync(tempPath, filePath);
72+
} catch (error) {
73+
try {
74+
fs.rmSync(tempPath, { force: true });
75+
} catch {
76+
// Cleanup is best effort. Preserve the original write error.
77+
}
78+
throw error;
79+
}
80+
}
81+
82+
function writeJsonFileSync(filePath, value, options = {}) {
83+
const suffix = options.trailingNewline === true ? '\n' : '';
84+
atomicWriteFileSync(filePath, `${JSON.stringify(value, null, 2)}${suffix}`, 'utf8');
85+
}
86+
87+
module.exports = {
88+
atomicWriteFileSync,
89+
writeJsonFileSync,
90+
};

agent-teams-controller/src/internal/crossTeam.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const fs = require('fs');
22
const path = require('path');
33
const crypto = require('crypto');
4+
const { writeJsonFileSync } = require('./atomicFile.js');
45
const { createControllerContext } = require('./context.js');
56
const { withFileLockSync } = require('./fileLock.js');
67
const messageStore = require('./messageStore.js');
@@ -25,10 +26,7 @@ function readJson(filePath, fallbackValue) {
2526
}
2627

2728
function writeJson(filePath, value) {
28-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
29-
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
30-
fs.writeFileSync(tempPath, JSON.stringify(value, null, 2));
31-
fs.renameSync(tempPath, filePath);
29+
writeJsonFileSync(filePath, value);
3230
}
3331

3432
function normalizeMetaMembers(rawMembers) {

agent-teams-controller/src/internal/kanbanStore.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const fs = require('fs');
2-
const path = require('path');
32
const taskStore = require('./taskStore.js');
3+
const { writeJsonFileSync } = require('./atomicFile.js');
44

55
function nowIso() {
66
return new Date().toISOString();
@@ -15,10 +15,7 @@ function readJson(filePath, fallbackValue) {
1515
}
1616

1717
function writeJson(filePath, value) {
18-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
19-
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
20-
fs.writeFileSync(tempPath, JSON.stringify(value, null, 2));
21-
fs.renameSync(tempPath, filePath);
18+
writeJsonFileSync(filePath, value);
2219
}
2320

2421
function getDefaultState(teamName) {

agent-teams-controller/src/internal/messageStore.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
const fs = require('fs');
22
const path = require('path');
33
const crypto = require('crypto');
4+
const { writeJsonFileSync } = require('./atomicFile.js');
45

56
function nowIso() {
67
return new Date().toISOString();
78
}
89

9-
function ensureDir(dirPath) {
10-
fs.mkdirSync(dirPath, { recursive: true });
11-
}
12-
1310
function readJson(filePath, fallbackValue) {
1411
try {
1512
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
@@ -19,10 +16,7 @@ function readJson(filePath, fallbackValue) {
1916
}
2017

2118
function writeJson(filePath, value) {
22-
ensureDir(path.dirname(filePath));
23-
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
24-
fs.writeFileSync(tempPath, JSON.stringify(value, null, 2));
25-
fs.renameSync(tempPath, filePath);
19+
writeJsonFileSync(filePath, value);
2620
}
2721

2822
function getInboxPath(paths, memberName) {

agent-teams-controller/src/internal/processStore.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const fs = require('fs');
2-
const path = require('path');
32
const crypto = require('crypto');
43

4+
const { writeJsonFileSync } = require('./atomicFile.js');
55
const runtimeHelpers = require('./runtimeHelpers.js');
66

77
function nowIso() {
@@ -17,10 +17,7 @@ function readJson(filePath, fallbackValue) {
1717
}
1818

1919
function writeJson(filePath, value) {
20-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
21-
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
22-
fs.writeFileSync(tempPath, JSON.stringify(value, null, 2));
23-
fs.renameSync(tempPath, filePath);
20+
writeJsonFileSync(filePath, value);
2421
}
2522

2623
function readProcesses(paths) {

0 commit comments

Comments
 (0)