From 4e9c90e98d78bcf4b9ab58ad83b269e910910633 Mon Sep 17 00:00:00 2001 From: Fartown Date: Thu, 22 Oct 2026 18:30:27 +0800 Subject: [PATCH 1/5] feat(integration-builds): publish Linux x64 AppImage packages --- .github/workflows/fork-integration-build.yml | 44 ++++++++++++++++++- config/fork-features.jsonc | 4 +- .../integration-builds/electron-builder.cjs | 9 ++++ .../integration-builds.test.mjs | 34 +++++++++++--- .../integration-builds/publish-release.mjs | 24 +++++++--- docs/issue/README.md | 34 +++++++------- .../journal.md" | 11 ++++- ...52\345\212\250\345\207\272\345\214\205.md" | 15 +++++-- ...52\345\212\250\345\207\272\345\214\205.md" | 8 ++++ .../tests/runs/2026-10-22-linux-contracts.md" | 19 ++++++++ .../integration-update-feed.test.ts | 40 ++++++++++++++++- .../integration-update-feed.ts | 14 ++++-- .../release-catalog.test.ts | 40 +++++++++++++++++ .../integration-builds/release-catalog.ts | 30 +++++++++++-- 14 files changed, 284 insertions(+), 42 deletions(-) create mode 100644 "docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" diff --git a/.github/workflows/fork-integration-build.yml b/.github/workflows/fork-integration-build.yml index 05588545eff5..109a5af5f2c3 100644 --- a/.github/workflows/fork-integration-build.yml +++ b/.github/workflows/fork-integration-build.yml @@ -210,6 +210,48 @@ jobs: compression-level: 0 retention-days: 14 + linux: + needs: identity + runs-on: ubuntu-22.04 + timeout-minutes: 90 + env: + ORCA_LOCAL_BUILD_VERSION: ${{ needs.identity.outputs.version }} + ORCA_INTEGRATION_TAG: ${{ needs.identity.outputs.tag }} + ORCA_BUILD_COMMIT: ${{ needs.identity.outputs.sha }} + NODE_OPTIONS: --max-old-space-size=4096 + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ needs.identity.outputs.sha }} + persist-credentials: false + - uses: pnpm/setup@v2 + with: + install: false + - uses: actions/setup-node@v6 + with: + node-version-file: package.json + cache: pnpm + - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/install-mobile-dependencies + - uses: ./.github/actions/prepare-wayland-clipboard + - name: Test integration Linux build and update contracts + run: pnpm exec vitest run --config config/vitest.config.ts config/scripts/integration-builds/integration-builds.test.mjs src/shared/integration-builds/release-catalog.test.ts src/main/integration-builds/integration-update-feed.test.ts + - name: Build desktop and native helpers + run: pnpm run build:release + - name: Package Linux x64 AppImage + run: | + pnpm run ensure:electron-runtime + pnpm exec electron-builder --config config/scripts/integration-builds/electron-builder.cjs --linux AppImage --x64 --publish never + - name: Verify packaged Linux CLI launch + run: node config/scripts/run-linux-cli-launch-contract-docker.mjs --appimage dist/orca-integration-linux-x64.AppImage + - uses: actions/upload-artifact@v7 + with: + name: integration-linux-x64-${{ needs.identity.outputs.sha }} + path: dist/orca-integration-linux-x64.AppImage + if-no-files-found: error + compression-level: 0 + retention-days: 14 + android: needs: identity env: @@ -317,7 +359,7 @@ jobs: retention-days: 14 publish: - needs: [identity, macos, android, release-summary] + needs: [identity, macos, linux, android, release-summary] if: github.repository == 'Fartown/orca' && github.ref == 'refs/heads/fork/integration' && github.event_name != 'pull_request' runs-on: ubuntu-latest timeout-minutes: 20 diff --git a/config/fork-features.jsonc b/config/fork-features.jsonc index 233a02702dcd..900795ba5799 100644 --- a/config/fork-features.jsonc +++ b/config/fork-features.jsonc @@ -24,12 +24,14 @@ "features": [ { "id": "integration-builds", - "title": "集成分支自动出包:同一提交的 macOS 与 Android 内测包", + "title": "集成分支自动出包:同一提交的 macOS、Linux 与 Android 内测包", "goal": "同源发布 fork 内测包,并提供桌面更新清单和 Android 应用内更新", "journal": "docs/issue/集成分支自动出包/journal.md", "dependsOn": [ "config/electron-builder.config.cjs", "config/scripts/build-mac-local.mjs", + "config/scripts/build-wayland-clipboard.mjs", + "config/scripts/run-linux-cli-launch-contract-docker.mjs", "mobile/scripts/prepare-android-release.mjs", "mobile/src/theme/mobile-theme.ts", "src/main/startup/configure-process.ts", diff --git a/config/scripts/integration-builds/electron-builder.cjs b/config/scripts/integration-builds/electron-builder.cjs index ae6ce96e2070..ae6f26911697 100644 --- a/config/scripts/integration-builds/electron-builder.cjs +++ b/config/scripts/integration-builds/electron-builder.cjs @@ -34,5 +34,14 @@ module.exports = { dmg: { ...upstream.dmg, artifactName: 'orca-integration-macos-${arch}.${ext}' + }, + linux: { + ...upstream.linux, + target: ['AppImage'], + artifactName: 'orca-integration-linux-${arch}.${ext}' + }, + appImage: { + ...upstream.appImage, + artifactName: 'orca-integration-linux-${arch}.${ext}' } } diff --git a/config/scripts/integration-builds/integration-builds.test.mjs b/config/scripts/integration-builds/integration-builds.test.mjs index 456ff860aeae..337c292bcc47 100644 --- a/config/scripts/integration-builds/integration-builds.test.mjs +++ b/config/scripts/integration-builds/integration-builds.test.mjs @@ -208,6 +208,9 @@ describe('integration package identity and signing', () => { expect(config.mac.target).toEqual(['dmg', 'zip']) expect(config.extraMetadata.orcaUpdateChannel).toBe('integration') expect(config.extraMetadata.version).toBe(env.ORCA_LOCAL_BUILD_VERSION) + expect(config.linux.extraResources).toBe(upstream.linux.extraResources) + expect(config.linux.target).toEqual(['AppImage']) + expect(config.appImage.artifactName).toBe('orca-integration-linux-${arch}.${ext}') }) it('refuses mixing formal release credentials and integration packaging', () => { @@ -238,9 +241,22 @@ describe('complete, immutable fork prereleases', () => { const manifest = JSON.parse(readFileSync(join(directory, 'build-info.json'), 'utf8')) expect(manifest.sha).toBe(sha) expect(manifest.assets.map((asset) => asset.name)).toEqual([ - ...PACKAGE_NAMES, + ...PACKAGE_NAMES.filter((name) => !name.includes('-linux-')), LEGACY_INTEL_PLACEHOLDER ]) + expect(manifest.linuxAssets.map((asset) => asset.name)).toEqual([ + 'orca-integration-linux-x64.AppImage' + ]) + const linuxUpdate = parse(readFileSync(join(directory, 'latest-linux.yml'), 'utf8')) + expect(linuxUpdate.version).toBe(env.ORCA_LOCAL_BUILD_VERSION) + expect(linuxUpdate.files).toEqual( + manifest.linuxAssets.map((asset) => ({ + url: asset.name, + sha512: asset.sha512, + size: asset.bytes + })) + ) + expect(gh.mock.calls[3][0]).toContain(join(directory, 'latest-linux.yml')) expect(manifest.androidVersionCode).toBe(androidConfig.androidVersionCode(timestamp)) expect(manifest.desktopVersion).toBe('1.2.3-preview.2') expect(manifest.macCertificateSha256).toBe(signingCertificate().sha256) @@ -284,15 +300,15 @@ describe('complete, immutable fork prereleases', () => { ) }) - it('does not create a release when one platform is missing or empty', async () => { + it.each(PACKAGE_NAMES)('does not publish when %s is missing or empty', async (name) => { const directory = packages() const gh = github() - writeFileSync(join(directory, PACKAGE_NAMES[2]), '') + writeFileSync(join(directory, name), '') await expect(publishRelease({ env, directory, mobile, gh, git: repository() })).rejects.toThrow( /empty/ ) expect(gh).not.toHaveBeenCalled() - rmSync(join(directory, PACKAGE_NAMES[2])) + rmSync(join(directory, name)) await expect(hashPackages(directory)).rejects.toThrow() }) @@ -488,7 +504,7 @@ describe('workflow wiring', () => { "${{ github.event_name == 'pull_request' }}" ) expect(workflow.jobs.identity.if).toContain("github.repository == 'Fartown/orca'") - for (const name of ['macos', 'android']) { + for (const name of ['macos', 'linux', 'android']) { const job = workflow.jobs[name] expect(job.needs).toBe('identity') expect(job.steps[0].with.ref).toBe('${{ needs.identity.outputs.sha }}') @@ -504,7 +520,13 @@ describe('workflow wiring', () => { (step) => step.name === 'Package internal-test DMG and update ZIP' ).run ).toContain('--publish never') - expect(workflow.jobs.publish.needs).toEqual(['identity', 'macos', 'android', 'release-summary']) + expect(workflow.jobs.publish.needs).toEqual([ + 'identity', + 'macos', + 'linux', + 'android', + 'release-summary' + ]) expect(workflow.jobs.macos.env.CSC_FOR_PULL_REQUEST).toBe('true') expect(workflow.jobs.publish.if).toContain("github.ref == 'refs/heads/fork/integration'") expect(workflow.jobs.publish.if).not.toContain('always()') diff --git a/config/scripts/integration-builds/publish-release.mjs b/config/scripts/integration-builds/publish-release.mjs index e750e64de1be..d74f029a10b1 100644 --- a/config/scripts/integration-builds/publish-release.mjs +++ b/config/scripts/integration-builds/publish-release.mjs @@ -21,7 +21,8 @@ import { export const PACKAGE_NAMES = [ 'orca-integration-macos-arm64.dmg', 'orca-integration-macos-arm64.zip', - 'orca-integration-android.apk' + 'orca-integration-android.apk', + 'orca-integration-linux-x64.AppImage' ] const MAC_UPDATE_ZIP = 'orca-integration-macos-arm64.zip' @@ -69,6 +70,7 @@ const INSTALL_NOTES = [ '提供 Apple Silicon ZIP 与 latest-mac.yml,支持同一固定签名身份之间的原生自动更新。现有 ad-hoc 旧版需先手动安装一次 DMG,之后可应用内下载并确认重启更新。', `Intel 版已停止提供。${LEGACY_INTEL_PLACEHOLDER} 不是安装包,只是一份说明,让 2026-09-17 之前的集成包仍把本次发布识别为完整并继续自动更新。`, 'Android 集成包自动检查 fork 更新,可在应用内下载 APK 后通过系统确认安装。旧版需先手动安装一次;不同签名安装不能覆盖。APK 使用 Expo debug 内测签名,不用于商店发布。', + 'Linux x64 提供 AppImage 与 latest-linux.yml,复用上游原生依赖和 glibc 2.31 门槛。已解包运行的安装需要手动替换;安装前保留原应用和配置。', 'build-info.json 和 SHA256SUMS.txt 记录来源及下载校验和。' ] @@ -103,7 +105,7 @@ function buildNotes( foldedSection( '构建信息', bulletList([ - `版本:macOS ${version};Android ${android.version}(versionCode ${android.versionCode})`, + `版本:macOS / Linux ${version};Android ${android.version}(versionCode ${android.versionCode})`, `提交:[\`${sha.slice(0, 12)}\`](https://github.com/${repo}/commit/${sha})`, `构建:${runUrl}`, since @@ -232,11 +234,14 @@ export async function publishRelease({ androidCertificateSha256: ANDROID_CERT_SHA256, macSigning: 'fixed self-signed publisher, not notarized', macCertificateSha256, - assets: published, + // Older clients reject unknown names in assets, but ignore optional manifest fields. + assets: published.filter((asset) => !asset.name.startsWith('orca-integration-linux-')), + linuxAssets: published.filter((asset) => asset.name.startsWith('orca-integration-linux-')), changes: manifestChanges(context.buildChanges, syncSummaries) } writeFileSync(join(directory, 'build-info.json'), `${JSON.stringify(manifest, null, 2)}\n`) writeFileSync(join(directory, 'latest-mac.yml'), macUpdateManifest(version, assets)) + writeFileSync(join(directory, 'latest-linux.yml'), linuxUpdateManifest(version, assets)) writeFileSync( join(directory, 'SHA256SUMS.txt'), published.map((a) => `${a.sha256} ${a.name}\n`).join('') @@ -304,6 +309,7 @@ export async function publishRelease({ ...PACKAGE_NAMES, LEGACY_INTEL_PLACEHOLDER, 'latest-mac.yml', + 'latest-linux.yml', 'build-info.json', 'SHA256SUMS.txt' ].map((name) => join(directory, name)) @@ -337,9 +343,17 @@ export async function publishRelease({ } export function macUpdateManifest(version, assets) { - const files = assets.filter((asset) => asset.name === MAC_UPDATE_ZIP) + return updateManifest(version, assets, MAC_UPDATE_ZIP) +} + +export function linuxUpdateManifest(version, assets) { + return updateManifest(version, assets, 'orca-integration-linux-x64.AppImage') +} + +function updateManifest(version, assets, name) { + const files = assets.filter((asset) => asset.name === name) if (files.length !== 1) { - throw new Error('The Apple Silicon update ZIP is required.') + throw new Error(`The update package is required: ${name}`) } return [ `version: ${JSON.stringify(version)}`, diff --git a/docs/issue/README.md b/docs/issue/README.md index 28c336de6a9e..7686aabd7afd 100644 --- a/docs/issue/README.md +++ b/docs/issue/README.md @@ -2,23 +2,23 @@ 本索引由 `pnpm generate:issue-index` 从 `config/fork-features.jsonc` 和各需求的 `journal.md` 生成,不要手改;`pnpm check:fork-docs` 会核对它与事实源一致。每个 Journal 是本需求的文档入口;目标及 P0/P1/P2 在各自需求正文中维护,技术与测试不跨需求合并。 -| 需求 | 目标 | 状态 | 需求文档 | 方案 | 测试用例 | 更新 | -| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| [集成分支自动出包](集成分支自动出包/journal.md) | 同源发布 fork 内测包,并提供桌面更新清单和 Android 应用内更新 | done | [集成分支自动出包](集成分支自动出包/requirements/集成分支自动出包.md) | [2026-10-21-orcad上游同步](集成分支自动出包/solutions/2026-10-21-orcad上游同步.md)、[macOS自动更新签名修复方案](集成分支自动出包/solutions/macOS自动更新签名修复方案.md)、[发布页分层与可读发布说明方案](集成分支自动出包/solutions/发布页分层与可读发布说明方案.md) | [自动出包](集成分支自动出包/tests/cases/自动出包.md) | 2026-10-21 | -| [文档预览大小](文档预览大小/journal.md) | 扩大文档文本预览上限,不扩大普通文件读取和 paired RPC 预算 | testing | [文档预览大小](文档预览大小/requirements/文档预览大小.md) | - | [预览大小边界](文档预览大小/tests/cases/预览大小边界.md) | 2026-10-21 | -| [Goal 目标模式](Goal目标模式/journal.md) | 持久目标、既有会话续跑、独立验收与目标管理 UI | testing | [Goal目标模式](Goal目标模式/requirements/Goal目标模式.md) | [Goal守卫监工与唤醒兜底修订方案](Goal目标模式/solutions/Goal守卫监工与唤醒兜底修订方案.md)、[Goal守卫监工与唤醒兜底修订方案.review](Goal目标模式/solutions/Goal守卫监工与唤醒兜底修订方案.review.md)、[Goal目标模式技术说明](Goal目标模式/solutions/Goal目标模式技术说明.md)、[Goal目标管理与交互闭环方案](Goal目标模式/solutions/Goal目标管理与交互闭环方案.md) | [Goal功能测试](Goal目标模式/tests/cases/Goal功能测试.md) | 2026-10-21 | -| [Issues 看板与会话](Issues看板与会话/journal.md) | 组织跨工作区会话,复用公共会话名称与原生恢复 | implementing | [Issues看板与会话](Issues看板与会话/requirements/Issues看板与会话.md) | [Issues看板与会话技术说明](Issues看板与会话/solutions/Issues看板与会话技术说明.md) | [Issues功能测试](Issues看板与会话/tests/cases/Issues功能测试.md)、[Issues摘要容量回归](Issues看板与会话/tests/cases/Issues摘要容量回归.md) | 2026-10-21 | -| [会话命名与身份保护](会话命名与身份保护/journal.md) | 同一会话在各入口使用一致名称,后台调用不顶替主身份 | implementing | [会话命名与身份保护](会话命名与身份保护/requirements/会话命名与身份保护.md) | [Claude在位会话活跃窗口保护方案](会话命名与身份保护/solutions/Claude在位会话活跃窗口保护方案.md)、[Provider优先的会话命名统一方案](会话命名与身份保护/solutions/Provider优先的会话命名统一方案.md)、[会话命名统一方案](会话命名与身份保护/solutions/会话命名统一方案.md) | [Claude活跃窗口保护测试](会话命名与身份保护/tests/cases/Claude活跃窗口保护测试.md)、[Codex嵌套调用保护测试](会话命名与身份保护/tests/cases/Codex嵌套调用保护测试.md)、[Provider优先命名验收](会话命名与身份保护/tests/cases/Provider优先命名验收.md)、[顶部名称投影测试](会话命名与身份保护/tests/cases/顶部名称投影测试.md) | 2026-10-21 | -| [自托管产物后端](自托管产物后端/journal.md) | 文件在哪台电脑就由那台电脑的 Orca 在局域网提供链接,本机文件与 SSH 主机上的文件走同一套规则 | testing | [自托管产物后端](自托管产物后端/requirements/自托管产物后端.md) | [局域网Artifact分享服务重构方案](自托管产物后端/solutions/局域网Artifact分享服务重构方案.md)、[自托管产物后端技术说明](自托管产物后端/solutions/自托管产物后端技术说明.md) | [自托管产物后端功能测试](自托管产物后端/tests/cases/自托管产物后端功能测试.md) | 2026-10-21 | -| ['远程工作区绝对路径打开'](远程工作区绝对路径打开/journal.md) | 弹窗输入绝对路径时本地开本地、SSH 开远端;不按路径位置拦截,归属未解析才不可用 | implementing | [远程工作区绝对路径打开](远程工作区绝对路径打开/requirements/远程工作区绝对路径打开.md) | [标签栏新建Tab弹窗远程工作区打开绝对路径方案](远程工作区绝对路径打开/solutions/标签栏新建Tab弹窗远程工作区打开绝对路径方案.md) | [远程工作区绝对路径打开功能测试](远程工作区绝对路径打开/tests/cases/远程工作区绝对路径打开功能测试.md) | 2026-10-21 | -| ['HTML预览在当前面板打开'](HTML预览在当前面板打开/journal.md) | 预览 tab 建在发起操作的 pane 内并切过去,本地与远程工作区落位一致 | testing | [HTML预览在当前面板打开](HTML预览在当前面板打开/requirements/HTML预览在当前面板打开.md) | - | [HTML预览在当前面板打开功能测试](HTML预览在当前面板打开/tests/cases/HTML预览在当前面板打开功能测试.md) | 2026-10-21 | -| ['远程大文件编辑器读取'](远程大文件编辑器读取/journal.md) | files.read 报 truncated 时用已有的 files.readChunk 读全文,不新增 wire 面 | testing | [远程大文件编辑器读取](远程大文件编辑器读取/requirements/远程大文件编辑器读取.md) | - | [远程大文件编辑器读取功能测试](远程大文件编辑器读取/tests/cases/远程大文件编辑器读取功能测试.md) | 2026-10-21 | -| ['远程主机绝对路径打开'](远程主机绝对路径打开/journal.md) | 主机为任意绝对路径发放 grant,客户端按 grant 读写,不新增 wire 方法之外的协议面 | testing | [远程主机绝对路径打开](远程主机绝对路径打开/requirements/远程主机绝对路径打开.md) | - | [远程主机绝对路径打开功能测试](远程主机绝对路径打开/tests/cases/远程主机绝对路径打开功能测试.md) | 2026-10-21 | -| ['移动端在新会话中继续'](移动端在新会话中继续/journal.md) | 移动端从当前 claude / codex 会话新建续接会话,复用主机既有 RPC,不改 wire | testing | [移动端在新会话中继续](移动端在新会话中继续/requirements/移动端在新会话中继续.md) | [移动端续接会话终端通道方案](移动端在新会话中继续/solutions/移动端续接会话终端通道方案.md) | [移动端续接会话](移动端在新会话中继续/tests/cases/移动端续接会话.md) | 2026-10-21 | -| ['富文本Markdown强制打开'](富文本Markdown强制打开/journal.md) | 把「只能在代码模式下编辑」从硬拦截改为可由用户显式承担的选择,并在强制期间持续提示改写风险 | done | [富文本Markdown强制打开](富文本Markdown强制打开/requirements/富文本Markdown强制打开.md) | - | [富文本Markdown强制打开功能测试](富文本Markdown强制打开/tests/cases/富文本Markdown强制打开功能测试.md) | 2026-10-21 | -| ['远程Tab发布血缘修复'](远程Tab发布血缘修复/journal.md) | headless 内容修改沿用现任发布者,避免远程 Tab 列表因误退役停止同步 | testing | [requirement](远程Tab发布血缘修复/requirements/requirement.md) | [overview](远程Tab发布血缘修复/solutions/overview.md) | [远程Tab发布血缘修复测试](远程Tab发布血缘修复/tests/cases/远程Tab发布血缘修复测试.md) | 2026-10-21 | -| ['移动端上传任意文件'](移动端上传任意文件/journal.md) | 移动端把任意类型文件流式上传到工作区所在机器的临时目录,并交给终端或 agent | testing | [移动端上传任意文件](移动端上传任意文件/requirements/移动端上传任意文件.md) | [移动端文件附件上传方案](移动端上传任意文件/solutions/移动端文件附件上传方案.md) | [移动端上传任意文件](移动端上传任意文件/tests/cases/移动端上传任意文件.md) | 2026-10-21 | -| ['终端 ID 复制主机路由'](终端ID复制主机路由/journal.md) | 复制终端 ID 时查询所属本地、SSH 或配对主机,失联或归属不明时不回退本机 | done | [终端ID复制主机路由](终端ID复制主机路由/requirements/终端ID复制主机路由.md) | [主机路由](终端ID复制主机路由/solutions/主机路由.md) | [主机路由](终端ID复制主机路由/tests/cases/主机路由.md) | 2026-10-21 | +| 需求 | 目标 | 状态 | 需求文档 | 方案 | 测试用例 | 更新 | +| --- | --- | --- | --- | --- | --- | --- | +| [集成分支自动出包](集成分支自动出包/journal.md) | 同源发布 fork 内测包,并提供桌面更新清单和 Android 应用内更新 | testing | [集成分支自动出包](集成分支自动出包/requirements/集成分支自动出包.md) | [2026-10-21-orcad上游同步](集成分支自动出包/solutions/2026-10-21-orcad上游同步.md)、[macOS自动更新签名修复方案](集成分支自动出包/solutions/macOS自动更新签名修复方案.md)、[发布页分层与可读发布说明方案](集成分支自动出包/solutions/发布页分层与可读发布说明方案.md) | [自动出包](集成分支自动出包/tests/cases/自动出包.md) | 2026-10-21 | +| [文档预览大小](文档预览大小/journal.md) | 扩大文档文本预览上限,不扩大普通文件读取和 paired RPC 预算 | testing | [文档预览大小](文档预览大小/requirements/文档预览大小.md) | - | [预览大小边界](文档预览大小/tests/cases/预览大小边界.md) | 2026-10-21 | +| [Goal 目标模式](Goal目标模式/journal.md) | 持久目标、既有会话续跑、独立验收与目标管理 UI | testing | [Goal目标模式](Goal目标模式/requirements/Goal目标模式.md) | [Goal守卫监工与唤醒兜底修订方案](Goal目标模式/solutions/Goal守卫监工与唤醒兜底修订方案.md)、[Goal守卫监工与唤醒兜底修订方案.review](Goal目标模式/solutions/Goal守卫监工与唤醒兜底修订方案.review.md)、[Goal目标模式技术说明](Goal目标模式/solutions/Goal目标模式技术说明.md)、[Goal目标管理与交互闭环方案](Goal目标模式/solutions/Goal目标管理与交互闭环方案.md) | [Goal功能测试](Goal目标模式/tests/cases/Goal功能测试.md) | 2026-10-21 | +| [Issues 看板与会话](Issues看板与会话/journal.md) | 组织跨工作区会话,复用公共会话名称与原生恢复 | implementing | [Issues看板与会话](Issues看板与会话/requirements/Issues看板与会话.md) | [Issues看板与会话技术说明](Issues看板与会话/solutions/Issues看板与会话技术说明.md) | [Issues功能测试](Issues看板与会话/tests/cases/Issues功能测试.md)、[Issues摘要容量回归](Issues看板与会话/tests/cases/Issues摘要容量回归.md) | 2026-10-21 | +| [会话命名与身份保护](会话命名与身份保护/journal.md) | 同一会话在各入口使用一致名称,后台调用不顶替主身份 | implementing | [会话命名与身份保护](会话命名与身份保护/requirements/会话命名与身份保护.md) | [Claude在位会话活跃窗口保护方案](会话命名与身份保护/solutions/Claude在位会话活跃窗口保护方案.md)、[Provider优先的会话命名统一方案](会话命名与身份保护/solutions/Provider优先的会话命名统一方案.md)、[会话命名统一方案](会话命名与身份保护/solutions/会话命名统一方案.md) | [Claude活跃窗口保护测试](会话命名与身份保护/tests/cases/Claude活跃窗口保护测试.md)、[Codex嵌套调用保护测试](会话命名与身份保护/tests/cases/Codex嵌套调用保护测试.md)、[Provider优先命名验收](会话命名与身份保护/tests/cases/Provider优先命名验收.md)、[顶部名称投影测试](会话命名与身份保护/tests/cases/顶部名称投影测试.md) | 2026-10-21 | +| [自托管产物后端](自托管产物后端/journal.md) | 文件在哪台电脑就由那台电脑的 Orca 在局域网提供链接,本机文件与 SSH 主机上的文件走同一套规则 | testing | [自托管产物后端](自托管产物后端/requirements/自托管产物后端.md) | [局域网Artifact分享服务重构方案](自托管产物后端/solutions/局域网Artifact分享服务重构方案.md)、[自托管产物后端技术说明](自托管产物后端/solutions/自托管产物后端技术说明.md) | [自托管产物后端功能测试](自托管产物后端/tests/cases/自托管产物后端功能测试.md) | 2026-10-21 | +| ['远程工作区绝对路径打开'](远程工作区绝对路径打开/journal.md) | 弹窗输入绝对路径时本地开本地、SSH 开远端;不按路径位置拦截,归属未解析才不可用 | implementing | [远程工作区绝对路径打开](远程工作区绝对路径打开/requirements/远程工作区绝对路径打开.md) | [标签栏新建Tab弹窗远程工作区打开绝对路径方案](远程工作区绝对路径打开/solutions/标签栏新建Tab弹窗远程工作区打开绝对路径方案.md) | [远程工作区绝对路径打开功能测试](远程工作区绝对路径打开/tests/cases/远程工作区绝对路径打开功能测试.md) | 2026-10-21 | +| ['HTML预览在当前面板打开'](HTML预览在当前面板打开/journal.md) | 预览 tab 建在发起操作的 pane 内并切过去,本地与远程工作区落位一致 | testing | [HTML预览在当前面板打开](HTML预览在当前面板打开/requirements/HTML预览在当前面板打开.md) | - | [HTML预览在当前面板打开功能测试](HTML预览在当前面板打开/tests/cases/HTML预览在当前面板打开功能测试.md) | 2026-10-21 | +| ['远程大文件编辑器读取'](远程大文件编辑器读取/journal.md) | files.read 报 truncated 时用已有的 files.readChunk 读全文,不新增 wire 面 | testing | [远程大文件编辑器读取](远程大文件编辑器读取/requirements/远程大文件编辑器读取.md) | - | [远程大文件编辑器读取功能测试](远程大文件编辑器读取/tests/cases/远程大文件编辑器读取功能测试.md) | 2026-10-21 | +| ['远程主机绝对路径打开'](远程主机绝对路径打开/journal.md) | 主机为任意绝对路径发放 grant,客户端按 grant 读写,不新增 wire 方法之外的协议面 | testing | [远程主机绝对路径打开](远程主机绝对路径打开/requirements/远程主机绝对路径打开.md) | - | [远程主机绝对路径打开功能测试](远程主机绝对路径打开/tests/cases/远程主机绝对路径打开功能测试.md) | 2026-10-21 | +| ['移动端在新会话中继续'](移动端在新会话中继续/journal.md) | 移动端从当前 claude / codex 会话新建续接会话,复用主机既有 RPC,不改 wire | testing | [移动端在新会话中继续](移动端在新会话中继续/requirements/移动端在新会话中继续.md) | [移动端续接会话终端通道方案](移动端在新会话中继续/solutions/移动端续接会话终端通道方案.md) | [移动端续接会话](移动端在新会话中继续/tests/cases/移动端续接会话.md) | 2026-10-21 | +| ['富文本Markdown强制打开'](富文本Markdown强制打开/journal.md) | 把「只能在代码模式下编辑」从硬拦截改为可由用户显式承担的选择,并在强制期间持续提示改写风险 | done | [富文本Markdown强制打开](富文本Markdown强制打开/requirements/富文本Markdown强制打开.md) | - | [富文本Markdown强制打开功能测试](富文本Markdown强制打开/tests/cases/富文本Markdown强制打开功能测试.md) | 2026-10-21 | +| ['远程Tab发布血缘修复'](远程Tab发布血缘修复/journal.md) | headless 内容修改沿用现任发布者,避免远程 Tab 列表因误退役停止同步 | testing | [requirement](远程Tab发布血缘修复/requirements/requirement.md) | [overview](远程Tab发布血缘修复/solutions/overview.md) | [远程Tab发布血缘修复测试](远程Tab发布血缘修复/tests/cases/远程Tab发布血缘修复测试.md) | 2026-10-21 | +| ['移动端上传任意文件'](移动端上传任意文件/journal.md) | 移动端把任意类型文件流式上传到工作区所在机器的临时目录,并交给终端或 agent | testing | [移动端上传任意文件](移动端上传任意文件/requirements/移动端上传任意文件.md) | [移动端文件附件上传方案](移动端上传任意文件/solutions/移动端文件附件上传方案.md) | [移动端上传任意文件](移动端上传任意文件/tests/cases/移动端上传任意文件.md) | 2026-10-21 | +| ['终端 ID 复制主机路由'](终端ID复制主机路由/journal.md) | 复制终端 ID 时查询所属本地、SSH 或配对主机,失联或归属不明时不回退本机 | done | [终端ID复制主机路由](终端ID复制主机路由/requirements/终端ID复制主机路由.md) | [主机路由](终端ID复制主机路由/solutions/主机路由.md) | [主机路由](终端ID复制主机路由/tests/cases/主机路由.md) | 2026-10-21 | 状态是需求整体状态(clarifying / designing / implementing / testing / done / blocked);各文档自己的状态以 Journal 的关键文档链接表为准。文档 ready、代码存在、单测通过和真实产品验收是不同状态;各自 Test Run 只证明记录的执行范围。 diff --git "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" index 31844c98d11f..f18a5762ad9f 100644 --- "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" +++ "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" @@ -1,7 +1,7 @@ --- title: 集成分支自动出包 slug: 集成分支自动出包 -status: done +status: testing created: 2026-09-12 updated: 2026-10-21 external_ids: [] @@ -75,6 +75,15 @@ external_ids: [] ## 3. 开发记录 +### 2026-10-22 Linux x64 集成包与本机安装(testing) + +- 本轮目标:REQ-413;补齐fork Linux x64安装包并将本机安装从上游切换到fork。 +- 完成内容:已核对上游AppImage摘要与当前运行路径;fork最新发布没有Linux;在独立feat/integration-linux-builds工作树扩展既有功能。 +- 代码或文档变更:先补需求与TC-414、登记标题;实现将复用Linux打包、glibc门槛与fork发布流程,Linux校验资料使用可选字段兼容旧客户端。 +- 验证证据:[Linux合同验证](tests/runs/2026-10-22-linux-contracts.md):桌面148通过/1跳过、移动21通过、完整类型、三个本地化、16组架构、登记/文档和定向lint通过。产物与实际安装待验;原服务尚未退出。 +- 未解决问题:本地原生构建正在恢复容器网络;CI与实际安装尚未完成。 +- 下一步:实现、测试、生成PR,取得Linux产物后隔离验收并安装。 + ### 2026-10-06 上游同步:撤下 mobile 网页包打包工作流普查的接缝 - 本轮目标:合并上游 `5cf3585b78` 时解决「上游删除、fork 修改」的冲突,保住功能本身 diff --git "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/requirements/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205.md" "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/requirements/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205.md" index f79ea58d4ed2..22eae3866a35 100644 --- "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/requirements/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205.md" +++ "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/requirements/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205.md" @@ -3,7 +3,7 @@ title: 集成分支自动出包 document_type: requirement status: ready created: 2026-09-12 -updated: 2026-10-06 +updated: 2026-10-22 --- # 集成分支自动出包 @@ -16,8 +16,8 @@ updated: 2026-10-06 ## 范围 -- In Scope:集成分支自动构建、手动重跑、macOS Apple Silicon DMG/ZIP/更新清单、Android APK、fork 更新检查及 Android 应用内下载和安装确认。Intel 版自 2026-09-17 起不再出包(D-005)。 -- Out of Scope:Apple 正式签名/公证、应用商店、稳定更新频道、Windows/Linux 出包、本机 App 替换。 +- In Scope:集成分支自动构建、手动重跑、macOS Apple Silicon DMG/ZIP/更新清单、Linux x64 AppImage/更新清单、Android APK、fork 更新检查及 Android 应用内下载和安装确认。Intel 版自 2026-09-17 起不再出包(D-005)。 +- Out of Scope:Apple 正式签名/公证、应用商店、稳定更新频道、Windows 与 Linux arm64 出包、Linux 已解包安装的自动替换。 ## 需求与验收 @@ -119,3 +119,12 @@ updated: 2026-10-06 | 2026-09-17 | 用户要求发布内容随合入的 PR 变化、版本号改为常规格式;分两步上线 | REQ-406、REQ-407 | D-004 | | 2026-09-17 | 用户要求去掉 Intel 出包;保留占位说明文件兼容旧集成包 | REQ-401、REQ-402、REQ-404 | D-005 | | 2026-10-06 | 用户要求发布页像官方一样清晰:构建发布分层与清理、按用户可见变化分组、同步上游摘要、整理版 AI 短版本、改动经 PR;预览版编号随之改为现有最大编号加一 | REQ-407、REQ-408~REQ-412 | D-006 | + +### REQ-413 Linux x64 同源安装包与兼容更新清单 + +- 当前状态:实现中。 +- 来源:用户2026-10-22要求“改成我的仓库里的安装包吧,如果没有linux版本改一下;加上linux然后安装”。当前本机安装为上游1.4.214,fork发布仅有macOS/Android。 +- 业务规则:Linux x64与macOS/Android锁定同一SHA、预览版号和发布tag;复用既有Linux打包钩子、原生依赖和glibc2.31门槛;全部必需包成功才发布。提供AppImage、latest-linux.yml与校验和,安装包内标记integration并指向Fartown更新源。 +- 兼容性:旧schemaVersion2客户端严格限制assets文件名,Linux校验资料使用可选linuxAssets,原assets保持旧版可读。Linux客户端只选择含完整Linux资产的fork发布;Mac和Android继续读取原字段。Windows保持既有行为。 +- 执行归属:CI runner负责构建与发布;本机安装目录、配置和6768监听由本机服务拥有,不读取SSH远端文件。Linux版同样用于本地工作区和SSH工作区,更新只替换运行客户端或服务的执行主机,不把本地路径用于远端。保留混合版本协议和原SSH功能。 +- 验收标准:TC-414覆盖缺失Linux包拒绝发布、新旧清单兼容与Linux更新选择;真实Linux产物可启动CLI和隔离headless服务,来源SHA/版本/集成渠道可核验;安装前保留原应用与配置,确认运行旧实例的处理方式后再切换服务。 diff --git "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/cases/\350\207\252\345\212\250\345\207\272\345\214\205.md" "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/cases/\350\207\252\345\212\250\345\207\272\345\214\205.md" index d8061717a6cf..7eb6ef8abdf3 100644 --- "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/cases/\350\207\252\345\212\250\345\207\272\345\214\205.md" +++ "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/cases/\350\207\252\345\212\250\345\207\272\345\214\205.md" @@ -164,3 +164,11 @@ updated: 2026-10-06 - 关联需求:REQ-412;模块:文档、发布说明;优先级:P2;类型:文档核对、单元。 - 步骤:检查 AGENTS.md 与 `docs/reference/fork-maintenance.md`;运行 `release-notes.test.mjs` 的分类用例。 - 预期:两份文档写明除同步上游及其修复外经 PR 合入;发布说明里 PR 带「(#编号)」,直接提交不带。 + +### TC-414 Linux x64 同源发布、安装与旧版兼容 + +- 关联需求:REQ-413。 +- 场景:同一SHA的Linux AppImage与macOS/Android齐备可发布;Linux缺失或空文件时不得公开发布;SHA256与latest-linux.yml的SHA512来自实际AppImage。 +- 旧版:build-info.json原assets仍只含旧版支持的macOS/Android文件,额外linuxAssets不影响旧客户端;无Linux字段的旧发布仍可由Mac/Android读取。 +- Linux:新客户端筛选完整Linux发布,固定fork更新源;不会因最新发布只有Mac而误选上游包;未知Linux名称、空/坏摘要拒绝。 +- 真实安装:检查架构、glibc门槛、CLI版本/帮助、集成渠道;使用隔离本地配置与端口启动headless服务,并按既有SSH验证契约检查远端入口。原安装与用户配置保留备份;没有授权退出时不终止用户运行实例。 diff --git "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" new file mode 100644 index 000000000000..00d2d92536b2 --- /dev/null +++ "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" @@ -0,0 +1,19 @@ +--- +title: Linux x64 出包与旧版更新兼容合同 +status: completed +created: 2026-10-22 +updated: 2026-10-22 +--- + +关联需求:REQ-413。关联用例:TC-414。 + +本记录只确认合同、类型与静态门禁;真实安装和CI产物验收另记,不把本地通过视为安装完成。 + +- 桌面发布/清单/更新相关:12个文件,148通过、1个平台探针跳过;Linux包缺失/空文件拒绝发布,SHA512清单、旧assets兼容、新linuxAssets校验与Linux完整发布选择通过。 +- 移动端集成功能:3个文件21项通过。 +- 完整pnpm tc:4个项目通过;Electron net.fetch不接受URL对象,已使用类型安全的URL字符串适配,不增加断言。 +- pnpm check:fork-features、check:fork-docs通过;完整check:architecture-policies共16组通过。 +- 三个本地化门禁通过,定向oxlint通过。 +- 本机Node24与仓库指定pnpm放在忽略的验证目录,没有修改全局Node/pnpm。 +- Docker基础镜像直连超时;通过用户指定代理取回官方Ubuntu20.04 amd64清单/层,校验digest后导入Docker。容器bridge无法解析代理,改以host网络准备同一Dockerfile的构建缓存,未修改系统Docker配置或降低glibc要求。 +- 真实Linux构建、CLI/headless/SSH与安装验证仍待完成。原工作树未提交的其他需求文档未动。 diff --git a/src/main/integration-builds/integration-update-feed.test.ts b/src/main/integration-builds/integration-update-feed.test.ts index 7a82c5d135b7..21f9474d218d 100644 --- a/src/main/integration-builds/integration-update-feed.test.ts +++ b/src/main/integration-builds/integration-update-feed.test.ts @@ -15,8 +15,10 @@ import { } from './integration-update-feed' const platform = Object.getOwnPropertyDescriptor(process, 'platform')! +const architecture = Object.getOwnPropertyDescriptor(process, 'arch')! afterEach(() => { Object.defineProperty(process, 'platform', platform) + Object.defineProperty(process, 'arch', architecture) mock.app.isPackaged = true vi.resetAllMocks() }) @@ -39,7 +41,7 @@ describe('integration-only default updater feed', () => { mock.readFileSync.mockReturnValue('{}') expect(await pinIntegrationReleaseFeed({} as AppUpdater)).toBe(false) fork() - Object.defineProperty(process, 'platform', { value: 'linux' }) + Object.defineProperty(process, 'platform', { value: 'win32' }) expect(isIntegrationBuild()).toBe(false) expect(mock.fetch).not.toHaveBeenCalled() }) @@ -65,7 +67,13 @@ describe('integration-only default updater feed', () => { tag_name: tag, draft: false, prerelease: true, - assets: [...assets, { name: 'build-info.json' }, { name: 'latest-mac.yml' }] + assets: [ + ...assets, + { name: 'build-info.json' }, + { name: 'latest-mac.yml' }, + { name: 'latest-linux.yml' }, + { name: 'orca-integration-linux-x64.AppImage' } + ] } ]) ) @@ -86,6 +94,34 @@ describe('integration-only default updater feed', () => { ) } + it('pins packaged Linux builds to the fork Linux feed', async () => { + fork() + Object.defineProperty(process, 'platform', { value: 'linux' }) + Object.defineProperty(process, 'arch', { value: 'x64' }) + publish({ + linuxAssets: [ + { name: 'orca-integration-linux-x64.AppImage', bytes: 123, sha256: 'b'.repeat(64) } + ] + }) + const updater = feedUpdater() + expect(isIntegrationBuild()).toBe(true) + expect(await pinIntegrationReleaseFeed(updater)).toBe(true) + expect(updater.setFeedURL).toHaveBeenCalledWith({ + provider: 'generic', + url: `https://github.com/Fartown/orca/releases/download/${tag}` + }) + expect(integrationChangelog(localVersion)?.release.description).toContain('Linux x64') + expect(updater.allowDowngrade).toBe(false) + }) + + it('does not offer an x64 package to an unsupported Linux architecture', () => { + fork() + Object.defineProperty(process, 'platform', { value: 'linux' }) + Object.defineProperty(process, 'arch', { value: 'arm64' }) + expect(isIntegrationBuild()).toBe(false) + expect(mock.fetch).not.toHaveBeenCalled() + }) + it('pins the concrete fork feed and matching notes without allowing downgrades', async () => { fork() const version = localVersion diff --git a/src/main/integration-builds/integration-update-feed.ts b/src/main/integration-builds/integration-update-feed.ts index 86f1ca18a443..a47af0ace683 100644 --- a/src/main/integration-builds/integration-update-feed.ts +++ b/src/main/integration-builds/integration-update-feed.ts @@ -41,7 +41,9 @@ export function integrationChangelog(version: string) { : `Orca Integration ${release.sha.slice(0, 12)}`, description: changeSummary(release) ?? - 'Fork integration build, signed by a fixed self-signed publisher and not notarized. Native updates work between builds with this identity. Older ad-hoc builds require one manual DMG installation first.', + (process.platform === 'linux' + ? 'Fork integration build for Linux x64. AppImage updates come from Fartown/orca. Extracted installations require manual replacement.' + : 'Fork integration build, signed by a fixed self-signed publisher and not notarized. Native updates work between builds with this identity. Older ad-hoc builds require one manual DMG installation first.'), releaseNotesUrl: `${INTEGRATION_RELEASES_URL}/tag/${release.tag}` } } @@ -49,7 +51,10 @@ export function integrationChangelog(version: string) { export function isIntegrationBuild(): boolean { try { - if (!app.isPackaged || process.platform !== 'darwin') { + if (!app.isPackaged || !['darwin', 'linux'].includes(process.platform)) { + return false + } + if (process.platform === 'linux' && process.arch !== 'x64') { return false } return ( @@ -70,7 +75,10 @@ export async function pinIntegrationReleaseFeed( if (!isIntegrationBuild()) { return false } - const release = await fetchIntegrationRelease('mac', net.fetch.bind(net) as typeof fetch) + const release = await fetchIntegrationRelease( + process.platform === 'linux' ? 'linux' : 'mac', + (input, init) => net.fetch(input instanceof URL ? input.toString() : input, init) + ) resolvedRelease = release updater.allowPrerelease = true updater.allowDowngrade = false diff --git a/src/shared/integration-builds/release-catalog.test.ts b/src/shared/integration-builds/release-catalog.test.ts index 6c160cae09ac..42c964256fd4 100644 --- a/src/shared/integration-builds/release-catalog.test.ts +++ b/src/shared/integration-builds/release-catalog.test.ts @@ -35,6 +35,46 @@ const atom = (...tags: string[]) => ) describe('fork integration release catalog', () => { + const linuxAsset = { + name: 'orca-integration-linux-x64.AppImage', + bytes: 1234, + sha256: 'c'.repeat(64) + } + + it('keeps the legacy asset list readable while Linux uses its optional field', () => { + const value = { ...manifest, linuxAssets: [linuxAsset] } + expect(parseIntegrationRelease(value, tag).assets).toEqual(manifest.assets) + expect(parseIntegrationRelease(value, tag).linuxAssets).toEqual([linuxAsset]) + expect(parseIntegrationRelease(manifest, tag).linuxAssets).toBeUndefined() + }) + + it('selects a complete Linux build even when a newer release is Mac-only', async () => { + const newer = { ...release, tag_name: 'integration-124-aaaaaaaaaaaa' } + const linuxRelease = { + ...release, + assets: [...release.assets, { name: 'latest-linux.yml' }, { name: linuxAsset.name }] + } + const fetcher = vi + .fn() + .mockResolvedValueOnce(json([newer, linuxRelease])) + .mockResolvedValueOnce(json({ ...manifest, linuxAssets: [linuxAsset] })) + expect((await fetchIntegrationRelease('linux', fetcher)).tag).toBe(tag) + expect(fetcher.mock.calls[1][0]).toBe(integrationDownloadUrl(tag, 'build-info.json')) + }) + + it.each([ + { linuxAssets: [] }, + { + linuxAssets: [ + { name: 'orca-integration-linux-x64.AppImage', bytes: 0, sha256: 'c'.repeat(64) } + ] + }, + { linuxAssets: [{ name: 'orca-integration-linux-x64.AppImage', bytes: 123, sha256: 'bad' }] }, + { linuxAssets: [{ name: 'other.AppImage', bytes: 123, sha256: 'c'.repeat(64) }] } + ])('rejects malformed optional Linux assets: %j', ({ linuxAssets }) => { + expect(() => parseIntegrationRelease({ ...manifest, linuxAssets }, tag)).toThrow() + }) + it.each([403, 429])('recovers from API HTTP %i using the public fork feed', async (status) => { const fetcher = vi .fn() diff --git a/src/shared/integration-builds/release-catalog.ts b/src/shared/integration-builds/release-catalog.ts index 3a8304625571..5e9377981e5c 100644 --- a/src/shared/integration-builds/release-catalog.ts +++ b/src/shared/integration-builds/release-catalog.ts @@ -25,6 +25,7 @@ export type IntegrationRelease = { androidVersion: string androidVersionCode: number assets: IntegrationAsset[] + linuxAssets?: IntegrationAsset[] /** Unvalidated manifest list; read it through `integrationChanges`. */ changes?: unknown } @@ -109,6 +110,26 @@ export function parseIntegrationRelease(value: unknown, tag: string): Integratio } names.add(asset.name) } + if (item.linuxAssets !== undefined) { + if (!Array.isArray(item.linuxAssets) || !item.linuxAssets.length) { + throw new Error('Invalid integration Linux assets.') + } + for (const asset of item.linuxAssets) { + if ( + !asset || + asset.name !== 'orca-integration-linux-x64.AppImage' || + names.has(asset.name) || + !Number.isSafeInteger(asset.bytes) || + asset.bytes <= 0 || + asset.bytes > 2_000_000_000 || + typeof asset.sha256 !== 'string' || + !/^[a-f0-9]{64}$/.test(asset.sha256) + ) { + throw new Error('Invalid integration Linux asset.') + } + names.add(asset.name) + } + } return item as IntegrationRelease } @@ -152,9 +173,10 @@ async function readManifest(tag: string, fetcher: typeof fetch): Promise name.startsWith('orca-')) - .every((name) => release.assets.some((asset) => asset.name === name)) + .every((name) => assets.some((asset) => asset.name === name)) } async function fetchPublicRelease(required: string[], fetcher: typeof fetch) { @@ -194,14 +216,16 @@ async function fetchPublicRelease(required: string[], fetcher: typeof fetch) { } export async function fetchIntegrationRelease( - platform: 'android' | 'mac', + platform: 'android' | 'mac' | 'linux', fetcher: typeof fetch = fetch ): Promise { const required = [ 'build-info.json', ...(platform === 'android' ? ['orca-integration-android.apk'] - : ['latest-mac.yml', 'orca-integration-macos-arm64.zip']) + : platform === 'linux' + ? ['latest-linux.yml', 'orca-integration-linux-x64.AppImage'] + : ['latest-mac.yml', 'orca-integration-macos-arm64.zip']) ] let releases: unknown try { From ea77ff51000800f3091784a62af278fadee5ed3c Mon Sep 17 00:00:00 2001 From: Fartown Date: Thu, 22 Oct 2026 18:39:33 +0800 Subject: [PATCH 2/5] fix(integration-builds): install Linux accessibility build dependencies --- .github/workflows/fork-integration-build.yml | 4 ++++ .../journal.md" | 2 +- .../tests/runs/2026-10-22-linux-contracts.md" | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fork-integration-build.yml b/.github/workflows/fork-integration-build.yml index 109a5af5f2c3..a954ce64f9df 100644 --- a/.github/workflows/fork-integration-build.yml +++ b/.github/workflows/fork-integration-build.yml @@ -236,6 +236,10 @@ jobs: - uses: ./.github/actions/prepare-wayland-clipboard - name: Test integration Linux build and update contracts run: pnpm exec vitest run --config config/vitest.config.ts config/scripts/integration-builds/integration-builds.test.mjs src/shared/integration-builds/release-catalog.test.ts src/main/integration-builds/integration-update-feed.test.ts + - name: Install Linux accessibility verification dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends python3-gi gir1.2-gtk-3.0 gir1.2-atspi-2.0 - name: Build desktop and native helpers run: pnpm run build:release - name: Package Linux x64 AppImage diff --git "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" index f18a5762ad9f..5b2fcae93093 100644 --- "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" +++ "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" @@ -81,7 +81,7 @@ external_ids: [] - 完成内容:已核对上游AppImage摘要与当前运行路径;fork最新发布没有Linux;在独立feat/integration-linux-builds工作树扩展既有功能。 - 代码或文档变更:先补需求与TC-414、登记标题;实现将复用Linux打包、glibc门槛与fork发布流程,Linux校验资料使用可选字段兼容旧客户端。 - 验证证据:[Linux合同验证](tests/runs/2026-10-22-linux-contracts.md):桌面148通过/1跳过、移动21通过、完整类型、三个本地化、16组架构、登记/文档和定向lint通过。产物与实际安装待验;原服务尚未退出。 -- 未解决问题:本地原生构建正在恢复容器网络;CI与实际安装尚未完成。 +- 未解决问题:首轮Linux CI在原生验证中缺Atspi typelib,已补runner的GTK/AT-SPI依赖,不跳过验证;本地原生构建仍在恢复容器网络。CI产物与实际安装尚未完成。 - 下一步:实现、测试、生成PR,取得Linux产物后隔离验收并安装。 ### 2026-10-06 上游同步:撤下 mobile 网页包打包工作流普查的接缝 diff --git "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" index 00d2d92536b2..8ff33ccc6460 100644 --- "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" +++ "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" @@ -17,3 +17,5 @@ updated: 2026-10-22 - 本机Node24与仓库指定pnpm放在忽略的验证目录,没有修改全局Node/pnpm。 - Docker基础镜像直连超时;通过用户指定代理取回官方Ubuntu20.04 amd64清单/层,校验digest后导入Docker。容器bridge无法解析代理,改以host网络准备同一Dockerfile的构建缓存,未修改系统Docker配置或降低glibc要求。 - 真实Linux构建、CLI/headless/SSH与安装验证仍待完成。原工作树未提交的其他需求文档未动。 + +首轮真实CI已运行Linux原生helper且glibc门槛通过;之后verify-computer-native的Python导入报Namespace Atspi not available。工作流补装python3-gi、gir1.2-gtk-3.0、gir1.2-atspi-2.0,保持原验证执行。JavaScript实际发布构建已在本机通过,包括CLI、主进程/renderer、Web投影、技能闭包与mobile-web校验;Linux实际打包/安装仍待验。 From 5597b7502a5d79fa5ab7e15039a489ebc64d5489 Mon Sep 17 00:00:00 2001 From: Fartown Date: Thu, 22 Oct 2026 18:51:11 +0800 Subject: [PATCH 3/5] fix(integration-builds): preserve the upstream Linux AppImage contract --- .github/workflows/fork-integration-build.yml | 4 ++-- config/scripts/integration-builds/electron-builder.cjs | 7 +------ .../integration-builds/integration-builds.test.mjs | 6 +++--- config/scripts/integration-builds/publish-release.mjs | 8 ++++---- .../tests/runs/2026-10-22-linux-contracts.md" | 4 ++++ .../integration-builds/integration-update-feed.test.ts | 4 ++-- src/shared/integration-builds/release-catalog.test.ts | 6 +++--- src/shared/integration-builds/release-catalog.ts | 4 ++-- 8 files changed, 21 insertions(+), 22 deletions(-) diff --git a/.github/workflows/fork-integration-build.yml b/.github/workflows/fork-integration-build.yml index a954ce64f9df..5c69b83a49fb 100644 --- a/.github/workflows/fork-integration-build.yml +++ b/.github/workflows/fork-integration-build.yml @@ -247,11 +247,11 @@ jobs: pnpm run ensure:electron-runtime pnpm exec electron-builder --config config/scripts/integration-builds/electron-builder.cjs --linux AppImage --x64 --publish never - name: Verify packaged Linux CLI launch - run: node config/scripts/run-linux-cli-launch-contract-docker.mjs --appimage dist/orca-integration-linux-x64.AppImage + run: node config/scripts/run-linux-cli-launch-contract-docker.mjs --appimage dist/orca-linux.AppImage - uses: actions/upload-artifact@v7 with: name: integration-linux-x64-${{ needs.identity.outputs.sha }} - path: dist/orca-integration-linux-x64.AppImage + path: dist/orca-linux.AppImage if-no-files-found: error compression-level: 0 retention-days: 14 diff --git a/config/scripts/integration-builds/electron-builder.cjs b/config/scripts/integration-builds/electron-builder.cjs index ae6f26911697..48731121d456 100644 --- a/config/scripts/integration-builds/electron-builder.cjs +++ b/config/scripts/integration-builds/electron-builder.cjs @@ -37,11 +37,6 @@ module.exports = { }, linux: { ...upstream.linux, - target: ['AppImage'], - artifactName: 'orca-integration-linux-${arch}.${ext}' - }, - appImage: { - ...upstream.appImage, - artifactName: 'orca-integration-linux-${arch}.${ext}' + target: ['AppImage'] } } diff --git a/config/scripts/integration-builds/integration-builds.test.mjs b/config/scripts/integration-builds/integration-builds.test.mjs index 337c292bcc47..c52d68f65eb2 100644 --- a/config/scripts/integration-builds/integration-builds.test.mjs +++ b/config/scripts/integration-builds/integration-builds.test.mjs @@ -210,7 +210,7 @@ describe('integration package identity and signing', () => { expect(config.extraMetadata.version).toBe(env.ORCA_LOCAL_BUILD_VERSION) expect(config.linux.extraResources).toBe(upstream.linux.extraResources) expect(config.linux.target).toEqual(['AppImage']) - expect(config.appImage.artifactName).toBe('orca-integration-linux-${arch}.${ext}') + expect(config.appImage).toBe(upstream.appImage) }) it('refuses mixing formal release credentials and integration packaging', () => { @@ -241,11 +241,11 @@ describe('complete, immutable fork prereleases', () => { const manifest = JSON.parse(readFileSync(join(directory, 'build-info.json'), 'utf8')) expect(manifest.sha).toBe(sha) expect(manifest.assets.map((asset) => asset.name)).toEqual([ - ...PACKAGE_NAMES.filter((name) => !name.includes('-linux-')), + ...PACKAGE_NAMES.filter((name) => name !== 'orca-linux.AppImage'), LEGACY_INTEL_PLACEHOLDER ]) expect(manifest.linuxAssets.map((asset) => asset.name)).toEqual([ - 'orca-integration-linux-x64.AppImage' + 'orca-linux.AppImage' ]) const linuxUpdate = parse(readFileSync(join(directory, 'latest-linux.yml'), 'utf8')) expect(linuxUpdate.version).toBe(env.ORCA_LOCAL_BUILD_VERSION) diff --git a/config/scripts/integration-builds/publish-release.mjs b/config/scripts/integration-builds/publish-release.mjs index d74f029a10b1..2cd0ef128963 100644 --- a/config/scripts/integration-builds/publish-release.mjs +++ b/config/scripts/integration-builds/publish-release.mjs @@ -22,7 +22,7 @@ export const PACKAGE_NAMES = [ 'orca-integration-macos-arm64.dmg', 'orca-integration-macos-arm64.zip', 'orca-integration-android.apk', - 'orca-integration-linux-x64.AppImage' + 'orca-linux.AppImage' ] const MAC_UPDATE_ZIP = 'orca-integration-macos-arm64.zip' @@ -235,8 +235,8 @@ export async function publishRelease({ macSigning: 'fixed self-signed publisher, not notarized', macCertificateSha256, // Older clients reject unknown names in assets, but ignore optional manifest fields. - assets: published.filter((asset) => !asset.name.startsWith('orca-integration-linux-')), - linuxAssets: published.filter((asset) => asset.name.startsWith('orca-integration-linux-')), + assets: published.filter((asset) => asset.name !== 'orca-linux.AppImage'), + linuxAssets: published.filter((asset) => asset.name === 'orca-linux.AppImage'), changes: manifestChanges(context.buildChanges, syncSummaries) } writeFileSync(join(directory, 'build-info.json'), `${JSON.stringify(manifest, null, 2)}\n`) @@ -347,7 +347,7 @@ export function macUpdateManifest(version, assets) { } export function linuxUpdateManifest(version, assets) { - return updateManifest(version, assets, 'orca-integration-linux-x64.AppImage') + return updateManifest(version, assets, 'orca-linux.AppImage') } function updateManifest(version, assets, name) { diff --git "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" index 8ff33ccc6460..e57cb9d11dbf 100644 --- "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" +++ "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/tests/runs/2026-10-22-linux-contracts.md" @@ -19,3 +19,7 @@ updated: 2026-10-22 - 真实Linux构建、CLI/headless/SSH与安装验证仍待完成。原工作树未提交的其他需求文档未动。 首轮真实CI已运行Linux原生helper且glibc门槛通过;之后verify-computer-native的Python导入报Namespace Atspi not available。工作流补装python3-gi、gir1.2-gtk-3.0、gir1.2-atspi-2.0,保持原验证执行。JavaScript实际发布构建已在本机通过,包括CLI、主进程/renderer、Web投影、技能闭包与mobile-web校验;Linux实际打包/安装仍待验。 + +实际本地AppImage打包确认electron-builder将Linux架构宏展开为x86_64,已将CI收集、发布清单和客户端资产名统一为orca-integration-linux-x86_64.AppImage,避免仅模拟配置测试通过而实际文件漏收。19个随包原生二进制的glibc门槛、打包Node PTY与daemon入口校验已通过。 + +实际打包随后验证到上游static-appimage-package-contract只接受orca-linux.AppImage/orca-linux-arm64.AppImage。最终保留上游标准orca-linux.AppImage名称,fork身份由下载仓库、版本、渠道和SHA确定;同步统一CI、发布、清单和客户端资产名,不放宽或跳过静态AppImage契约。 diff --git a/src/main/integration-builds/integration-update-feed.test.ts b/src/main/integration-builds/integration-update-feed.test.ts index 21f9474d218d..f5b8205f58c1 100644 --- a/src/main/integration-builds/integration-update-feed.test.ts +++ b/src/main/integration-builds/integration-update-feed.test.ts @@ -72,7 +72,7 @@ describe('integration-only default updater feed', () => { { name: 'build-info.json' }, { name: 'latest-mac.yml' }, { name: 'latest-linux.yml' }, - { name: 'orca-integration-linux-x64.AppImage' } + { name: 'orca-linux.AppImage' } ] } ]) @@ -100,7 +100,7 @@ describe('integration-only default updater feed', () => { Object.defineProperty(process, 'arch', { value: 'x64' }) publish({ linuxAssets: [ - { name: 'orca-integration-linux-x64.AppImage', bytes: 123, sha256: 'b'.repeat(64) } + { name: 'orca-linux.AppImage', bytes: 123, sha256: 'b'.repeat(64) } ] }) const updater = feedUpdater() diff --git a/src/shared/integration-builds/release-catalog.test.ts b/src/shared/integration-builds/release-catalog.test.ts index 42c964256fd4..f9b612e7f2ef 100644 --- a/src/shared/integration-builds/release-catalog.test.ts +++ b/src/shared/integration-builds/release-catalog.test.ts @@ -36,7 +36,7 @@ const atom = (...tags: string[]) => describe('fork integration release catalog', () => { const linuxAsset = { - name: 'orca-integration-linux-x64.AppImage', + name: 'orca-linux.AppImage', bytes: 1234, sha256: 'c'.repeat(64) } @@ -66,10 +66,10 @@ describe('fork integration release catalog', () => { { linuxAssets: [] }, { linuxAssets: [ - { name: 'orca-integration-linux-x64.AppImage', bytes: 0, sha256: 'c'.repeat(64) } + { name: 'orca-linux.AppImage', bytes: 0, sha256: 'c'.repeat(64) } ] }, - { linuxAssets: [{ name: 'orca-integration-linux-x64.AppImage', bytes: 123, sha256: 'bad' }] }, + { linuxAssets: [{ name: 'orca-linux.AppImage', bytes: 123, sha256: 'bad' }] }, { linuxAssets: [{ name: 'other.AppImage', bytes: 123, sha256: 'c'.repeat(64) }] } ])('rejects malformed optional Linux assets: %j', ({ linuxAssets }) => { expect(() => parseIntegrationRelease({ ...manifest, linuxAssets }, tag)).toThrow() diff --git a/src/shared/integration-builds/release-catalog.ts b/src/shared/integration-builds/release-catalog.ts index 5e9377981e5c..487e4a44bc30 100644 --- a/src/shared/integration-builds/release-catalog.ts +++ b/src/shared/integration-builds/release-catalog.ts @@ -117,7 +117,7 @@ export function parseIntegrationRelease(value: unknown, tag: string): Integratio for (const asset of item.linuxAssets) { if ( !asset || - asset.name !== 'orca-integration-linux-x64.AppImage' || + asset.name !== 'orca-linux.AppImage' || names.has(asset.name) || !Number.isSafeInteger(asset.bytes) || asset.bytes <= 0 || @@ -224,7 +224,7 @@ export async function fetchIntegrationRelease( ...(platform === 'android' ? ['orca-integration-android.apk'] : platform === 'linux' - ? ['latest-linux.yml', 'orca-integration-linux-x64.AppImage'] + ? ['latest-linux.yml', 'orca-linux.AppImage'] : ['latest-mac.yml', 'orca-integration-macos-arm64.zip']) ] let releases: unknown From df2df413d7073ff7da58cef54600c25677529404 Mon Sep 17 00:00:00 2001 From: Fartown Date: Thu, 22 Oct 2026 19:58:52 +0800 Subject: [PATCH 4/5] fix(integration-builds): run fork PR gates on available hosted runners --- .../ci-cache-warmup-platform-jobs.yml | 4 +++- .github/workflows/pr.yml | 10 +++++----- config/architecture-policies.jsonc | 6 ++++++ config/fork-features.jsonc | 20 +++++++++++++++++++ .../ci-dependency-download-cache.test.mjs | 8 ++++++-- config/scripts/pr-preflight-gates.test.mjs | 4 +++- docs/issue/README.md | 2 +- .../journal.md" | 11 +++++++++- ...52\345\212\250\345\207\272\345\214\205.md" | 3 ++- 9 files changed, 56 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci-cache-warmup-platform-jobs.yml b/.github/workflows/ci-cache-warmup-platform-jobs.yml index 38ca56e57500..4d4a802a40f5 100644 --- a/.github/workflows/ci-cache-warmup-platform-jobs.yml +++ b/.github/workflows/ci-cache-warmup-platform-jobs.yml @@ -15,7 +15,9 @@ jobs: strategy: fail-fast: false matrix: - runner: [ubuntu-24.04-arm, blacksmith-4vcpu-ubuntu-2404] + runner: + - ubuntu-24.04-arm + - ${{ github.repository == 'Fartown/orca' && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }} runs-on: ${{ matrix.runner }} timeout-minutes: 10 env: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 085355c4fe4b..81e548d68720 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -28,7 +28,7 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.draft != true name: detect changes and check repository guards # Start routing without waiting behind the GitHub-hosted test queue. - runs-on: blacksmith-2vcpu-ubuntu-2404 + runs-on: ${{ github.repository == 'Fartown/orca' && 'ubuntu-24.04' || 'blacksmith-2vcpu-ubuntu-2404' }} timeout-minutes: 5 permissions: contents: read @@ -181,7 +181,7 @@ jobs: needs: [code_paths] if: needs.code_paths.outputs.static_analysis == 'true' || needs.code_paths.outputs.typecheck == 'true' # Leave headroom for typechecking and overlapping checks. - runs-on: blacksmith-8vcpu-ubuntu-2404 + runs-on: ${{ github.repository == 'Fartown/orca' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }} outputs: shards: ${{ steps.unit-plan.outputs.shards }} managed_hook_node18: ${{ steps.managed_hook_node18.outcome }} @@ -945,7 +945,7 @@ jobs: uses: ./.github/workflows/unit-tests.yml with: node_versions: '["24"]' - runner: blacksmith-4vcpu-ubuntu-2404 + runner: ${{ github.repository == 'Fartown/orca' && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }} phase_diagnostics: true shards: ${{ needs.preflight.outputs.shards }} run_shards: ${{ needs.code_paths.outputs.mobile_ci_tools != 'true' }} @@ -1567,7 +1567,7 @@ jobs: !cancelled() && needs.code_paths.result == 'success' && needs.code_paths.outputs.dependency_isolation == 'true' - runs-on: blacksmith-4vcpu-ubuntu-2404 + runs-on: ${{ github.repository == 'Fartown/orca' && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }} timeout-minutes: 30 env: ORCA_PNPM_FORCE_GLOBAL_VIRTUAL_STORE_IN_CI: '1' @@ -1637,7 +1637,7 @@ jobs: - layout_oracle - dependency_isolation # Report settled results without competing with the GitHub-hosted test queue. - runs-on: blacksmith-2vcpu-ubuntu-2204 + runs-on: ${{ github.repository == 'Fartown/orca' && 'ubuntu-22.04' || 'blacksmith-2vcpu-ubuntu-2204' }} timeout-minutes: 5 steps: diff --git a/config/architecture-policies.jsonc b/config/architecture-policies.jsonc index ccff1f9d9cc4..def489fc5b18 100644 --- a/config/architecture-policies.jsonc +++ b/config/architecture-policies.jsonc @@ -26,6 +26,10 @@ ], "allowedPaths": [ ".github/workflows/fork-integration-build.yml", + ".github/workflows/pr.yml", + "config/scripts/pr-preflight-gates.test.mjs", + "config/scripts/ci-dependency-download-cache.test.mjs", + ".github/workflows/ci-cache-warmup-platform-jobs.yml", "config/scripts/mobile-web-bundle-packaging-workflow-contract.test.mjs", "config/scripts/integration-builds/**", "src/shared/integration-builds/**", @@ -507,6 +511,8 @@ "mobile/src/mobile-web-shell/bridge-host-init.test.ts", ".github/workflows/fork-integration-build.yml", "config/scripts/mobile-web-bundle-packaging-workflow-contract.test.mjs", + "config/scripts/ci-dependency-download-cache.test.mjs", + ".github/workflows/ci-cache-warmup-platform-jobs.yml", "config/scripts/integration-builds/**", "src/shared/integration-builds/**", "src/main/integration-builds/**", diff --git a/config/fork-features.jsonc b/config/fork-features.jsonc index 900795ba5799..b9edb76d67f0 100644 --- a/config/fork-features.jsonc +++ b/config/fork-features.jsonc @@ -65,6 +65,26 @@ "config/scripts/integration-builds/ai-release-summary.mjs" ], "seams": [ + { + "file": ".github/workflows/ci-cache-warmup-platform-jobs.yml", + "mustContain": "github.repository == 'Fartown/orca'", + "why": "Warm the same Linux runtime dependency cache used by the fork's hosted PR runner." + }, + { + "file": ".github/workflows/pr.yml", + "mustContain": "github.repository == 'Fartown/orca'", + "why": "Fork release PRs use available GitHub-hosted runners while retaining every required check and upstream runner choice." + }, + { + "file": "config/scripts/pr-preflight-gates.test.mjs", + "mustContain": "github.repository == 'Fartown/orca'", + "why": "Keep the preflight runner contract aligned with the fork-specific hosted-runner route." + }, + { + "file": "config/scripts/ci-dependency-download-cache.test.mjs", + "mustContain": "github.repository == 'Fartown/orca'", + "why": "Keep dependency-cache runner assertions accurate for the fork and upstream." + }, { "file": "mobile/app.config.js", "mustContain": "integration-builds", diff --git a/config/scripts/ci-dependency-download-cache.test.mjs b/config/scripts/ci-dependency-download-cache.test.mjs index ed6853151150..b80d65e4f297 100644 --- a/config/scripts/ci-dependency-download-cache.test.mjs +++ b/config/scripts/ci-dependency-download-cache.test.mjs @@ -383,8 +383,12 @@ describe('CI dependency download caches', () => { const unitInstall = units.steps.find((step) => step.uses === installer) expect(warm['runs-on']).toBe('${{ matrix.runner }}') expect(warm.strategy.matrix.runner).toEqual(['ubuntu-24.04-arm', pr.jobs.test.with.runner]) - expect(pr.jobs.preflight['runs-on']).toBe('blacksmith-8vcpu-ubuntu-2404') - expect(pr.jobs.test.with.runner).toBe('blacksmith-4vcpu-ubuntu-2404') + expect(pr.jobs.preflight['runs-on']).toBe( + "${{ github.repository == 'Fartown/orca' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}" + ) + expect(pr.jobs.test.with.runner).toBe( + "${{ github.repository == 'Fartown/orca' && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }}" + ) expect(units['runs-on']).toBe('${{ inputs.runner }}') expect(JSON.parse(pr.jobs.test.with.node_versions)).toEqual(['24']) expect(warmInstall.if).toBeUndefined() diff --git a/config/scripts/pr-preflight-gates.test.mjs b/config/scripts/pr-preflight-gates.test.mjs index b1c99f09294b..67c7cfb05595 100644 --- a/config/scripts/pr-preflight-gates.test.mjs +++ b/config/scripts/pr-preflight-gates.test.mjs @@ -16,7 +16,9 @@ it('shares one setup and overlaps the compiler only after type-aware checks fini expect(workflow.jobs.static_analysis).toBeUndefined() expect(workflow.jobs.typecheck).toBeUndefined() expect(preflight.needs).toEqual(['code_paths']) - expect(preflight['runs-on']).toBe('blacksmith-8vcpu-ubuntu-2404') + expect(preflight['runs-on']).toBe( + "${{ github.repository == 'Fartown/orca' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}" + ) const installs = steps.filter( (step) => step.uses === './.github/actions/install-node-dependencies' ) diff --git a/docs/issue/README.md b/docs/issue/README.md index 7686aabd7afd..bd415d4d22a1 100644 --- a/docs/issue/README.md +++ b/docs/issue/README.md @@ -4,7 +4,7 @@ | 需求 | 目标 | 状态 | 需求文档 | 方案 | 测试用例 | 更新 | | --- | --- | --- | --- | --- | --- | --- | -| [集成分支自动出包](集成分支自动出包/journal.md) | 同源发布 fork 内测包,并提供桌面更新清单和 Android 应用内更新 | testing | [集成分支自动出包](集成分支自动出包/requirements/集成分支自动出包.md) | [2026-10-21-orcad上游同步](集成分支自动出包/solutions/2026-10-21-orcad上游同步.md)、[macOS自动更新签名修复方案](集成分支自动出包/solutions/macOS自动更新签名修复方案.md)、[发布页分层与可读发布说明方案](集成分支自动出包/solutions/发布页分层与可读发布说明方案.md) | [自动出包](集成分支自动出包/tests/cases/自动出包.md) | 2026-10-21 | +| [集成分支自动出包](集成分支自动出包/journal.md) | 同源发布 fork 内测包,并提供桌面更新清单和 Android 应用内更新 | testing | [集成分支自动出包](集成分支自动出包/requirements/集成分支自动出包.md) | [2026-10-21-orcad上游同步](集成分支自动出包/solutions/2026-10-21-orcad上游同步.md)、[macOS自动更新签名修复方案](集成分支自动出包/solutions/macOS自动更新签名修复方案.md)、[发布页分层与可读发布说明方案](集成分支自动出包/solutions/发布页分层与可读发布说明方案.md) | [自动出包](集成分支自动出包/tests/cases/自动出包.md) | 2026-10-22 | | [文档预览大小](文档预览大小/journal.md) | 扩大文档文本预览上限,不扩大普通文件读取和 paired RPC 预算 | testing | [文档预览大小](文档预览大小/requirements/文档预览大小.md) | - | [预览大小边界](文档预览大小/tests/cases/预览大小边界.md) | 2026-10-21 | | [Goal 目标模式](Goal目标模式/journal.md) | 持久目标、既有会话续跑、独立验收与目标管理 UI | testing | [Goal目标模式](Goal目标模式/requirements/Goal目标模式.md) | [Goal守卫监工与唤醒兜底修订方案](Goal目标模式/solutions/Goal守卫监工与唤醒兜底修订方案.md)、[Goal守卫监工与唤醒兜底修订方案.review](Goal目标模式/solutions/Goal守卫监工与唤醒兜底修订方案.review.md)、[Goal目标模式技术说明](Goal目标模式/solutions/Goal目标模式技术说明.md)、[Goal目标管理与交互闭环方案](Goal目标模式/solutions/Goal目标管理与交互闭环方案.md) | [Goal功能测试](Goal目标模式/tests/cases/Goal功能测试.md) | 2026-10-21 | | [Issues 看板与会话](Issues看板与会话/journal.md) | 组织跨工作区会话,复用公共会话名称与原生恢复 | implementing | [Issues看板与会话](Issues看板与会话/requirements/Issues看板与会话.md) | [Issues看板与会话技术说明](Issues看板与会话/solutions/Issues看板与会话技术说明.md) | [Issues功能测试](Issues看板与会话/tests/cases/Issues功能测试.md)、[Issues摘要容量回归](Issues看板与会话/tests/cases/Issues摘要容量回归.md) | 2026-10-21 | diff --git "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" index 5b2fcae93093..0b226d873b5f 100644 --- "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" +++ "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" @@ -3,12 +3,21 @@ title: 集成分支自动出包 slug: 集成分支自动出包 status: testing created: 2026-09-12 -updated: 2026-10-21 +updated: 2026-10-22 external_ids: [] --- # 集成分支自动出包 开发 Journal +## 2026-10-22:补齐正式发布前的 PR 运行器入口(implementing) + +- 本轮目标:按用户要求合入并发布 Linux 包,以发布包覆盖原安装,后续不创建安装备份。 +- 完成内容:已确认 PR #61 的 Linux、macOS arm64、Android 实际构建成功;完整 PR 检查在 Blacksmith runner 排队,尚不能合并。 +- 代码或文档变更:登记 PR workflow 的 runner 选择接缝及对应合同测试;仅 Fartown/orca 使用 GitHub-hosted runner,上游仍用既有 Blacksmith 规格。 +- 验证证据:Actions 39766206400 的首个 job 119782178485 为 queued,标签 blacksmith-2vcpu-ubuntu-2404;Actions 39766206408 的三平台构建通过。 +- 未解决问题:完整 PR 与 aggregate 尚未执行完成,Linux release 未发布;实际网页的 Chat View 菜单缺少切换入口,独立记录该功能问题,不将启动成功视为全部功能正常。 +- 下一步:运行器/缓存/发布相关 217 项测试通过(1 项平台跳过),架构与 fork 功能门禁通过;推送修复后等待完整 PR,再合并发布、覆盖安装并复验。 + ## 1. 关键文档链接 | 类型 | 文档 | 状态 | 说明 | diff --git "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/requirements/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205.md" "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/requirements/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205.md" index 22eae3866a35..5ca1dacfccdc 100644 --- "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/requirements/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205.md" +++ "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/requirements/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205.md" @@ -124,7 +124,8 @@ updated: 2026-10-22 - 当前状态:实现中。 - 来源:用户2026-10-22要求“改成我的仓库里的安装包吧,如果没有linux版本改一下;加上linux然后安装”。当前本机安装为上游1.4.214,fork发布仅有macOS/Android。 +- 交付补充:用户要求通过完整 PR 门禁合入集成分支、生成正式发布包,再覆盖原安装位置;后续安装不创建备份。Fartown 的 PR 检查使用可用的 GitHub-hosted runner,上游保留 Blacksmith;检查项目、依赖与 aggregate 判定保持不变。 - 业务规则:Linux x64与macOS/Android锁定同一SHA、预览版号和发布tag;复用既有Linux打包钩子、原生依赖和glibc2.31门槛;全部必需包成功才发布。提供AppImage、latest-linux.yml与校验和,安装包内标记integration并指向Fartown更新源。 - 兼容性:旧schemaVersion2客户端严格限制assets文件名,Linux校验资料使用可选linuxAssets,原assets保持旧版可读。Linux客户端只选择含完整Linux资产的fork发布;Mac和Android继续读取原字段。Windows保持既有行为。 - 执行归属:CI runner负责构建与发布;本机安装目录、配置和6768监听由本机服务拥有,不读取SSH远端文件。Linux版同样用于本地工作区和SSH工作区,更新只替换运行客户端或服务的执行主机,不把本地路径用于远端。保留混合版本协议和原SSH功能。 -- 验收标准:TC-414覆盖缺失Linux包拒绝发布、新旧清单兼容与Linux更新选择;真实Linux产物可启动CLI和隔离headless服务,来源SHA/版本/集成渠道可核验;安装前保留原应用与配置,确认运行旧实例的处理方式后再切换服务。 +- 验收标准:TC-414覆盖缺失Linux包拒绝发布、新旧清单兼容与Linux更新选择;真实Linux产物可启动CLI和隔离headless服务,来源SHA/版本/集成渠道可核验;在原安装位置覆盖应用文件,保留用户配置与现有终端 daemon,不创建安装备份;复验正在运行的服务和网页。 From bb9a914ad53b2387b4eeb0d23f8ea0fc0f585175 Mon Sep 17 00:00:00 2001 From: Fartown Date: Thu, 22 Oct 2026 20:08:08 +0800 Subject: [PATCH 5/5] chore(integration-builds): format Linux tests and record validation gaps --- config/scripts/integration-builds/integration-builds.test.mjs | 4 +--- .../journal.md" | 2 +- src/main/integration-builds/integration-update-feed.test.ts | 4 +--- src/shared/integration-builds/release-catalog.test.ts | 4 +--- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/config/scripts/integration-builds/integration-builds.test.mjs b/config/scripts/integration-builds/integration-builds.test.mjs index c52d68f65eb2..3cfb6ffe650b 100644 --- a/config/scripts/integration-builds/integration-builds.test.mjs +++ b/config/scripts/integration-builds/integration-builds.test.mjs @@ -244,9 +244,7 @@ describe('complete, immutable fork prereleases', () => { ...PACKAGE_NAMES.filter((name) => name !== 'orca-linux.AppImage'), LEGACY_INTEL_PLACEHOLDER ]) - expect(manifest.linuxAssets.map((asset) => asset.name)).toEqual([ - 'orca-linux.AppImage' - ]) + expect(manifest.linuxAssets.map((asset) => asset.name)).toEqual(['orca-linux.AppImage']) const linuxUpdate = parse(readFileSync(join(directory, 'latest-linux.yml'), 'utf8')) expect(linuxUpdate.version).toBe(env.ORCA_LOCAL_BUILD_VERSION) expect(linuxUpdate.files).toEqual( diff --git "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" index 0b226d873b5f..3b72ffc3e09b 100644 --- "a/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" +++ "b/docs/issue/\351\233\206\346\210\220\345\210\206\346\224\257\350\207\252\345\212\250\345\207\272\345\214\205/journal.md" @@ -15,7 +15,7 @@ external_ids: [] - 完成内容:已确认 PR #61 的 Linux、macOS arm64、Android 实际构建成功;完整 PR 检查在 Blacksmith runner 排队,尚不能合并。 - 代码或文档变更:登记 PR workflow 的 runner 选择接缝及对应合同测试;仅 Fartown/orca 使用 GitHub-hosted runner,上游仍用既有 Blacksmith 规格。 - 验证证据:Actions 39766206400 的首个 job 119782178485 为 queued,标签 blacksmith-2vcpu-ubuntu-2404;Actions 39766206408 的三平台构建通过。 -- 未解决问题:完整 PR 与 aggregate 尚未执行完成,Linux release 未发布;实际网页的 Chat View 菜单缺少切换入口,独立记录该功能问题,不将启动成功视为全部功能正常。 +- 未解决问题:runner 修复后完整 PR 已实际执行,Actions 39774725680 在全仓格式检查发现 97 个文件,aggregate 未通过,尚不能合并发布。已修正本次 Linux 新增测试的三处格式;其余仓库检查仍待处理。实际网页当前会话可通过快捷键切换并读取对话,恢复的历史标签缺少 provider session 关联而显示空白,终端右键入口仍缺失;这些功能问题未计为通过。 - 下一步:运行器/缓存/发布相关 217 项测试通过(1 项平台跳过),架构与 fork 功能门禁通过;推送修复后等待完整 PR,再合并发布、覆盖安装并复验。 ## 1. 关键文档链接 diff --git a/src/main/integration-builds/integration-update-feed.test.ts b/src/main/integration-builds/integration-update-feed.test.ts index f5b8205f58c1..e5e86a47bf8e 100644 --- a/src/main/integration-builds/integration-update-feed.test.ts +++ b/src/main/integration-builds/integration-update-feed.test.ts @@ -99,9 +99,7 @@ describe('integration-only default updater feed', () => { Object.defineProperty(process, 'platform', { value: 'linux' }) Object.defineProperty(process, 'arch', { value: 'x64' }) publish({ - linuxAssets: [ - { name: 'orca-linux.AppImage', bytes: 123, sha256: 'b'.repeat(64) } - ] + linuxAssets: [{ name: 'orca-linux.AppImage', bytes: 123, sha256: 'b'.repeat(64) }] }) const updater = feedUpdater() expect(isIntegrationBuild()).toBe(true) diff --git a/src/shared/integration-builds/release-catalog.test.ts b/src/shared/integration-builds/release-catalog.test.ts index f9b612e7f2ef..6308e9efd444 100644 --- a/src/shared/integration-builds/release-catalog.test.ts +++ b/src/shared/integration-builds/release-catalog.test.ts @@ -65,9 +65,7 @@ describe('fork integration release catalog', () => { it.each([ { linuxAssets: [] }, { - linuxAssets: [ - { name: 'orca-linux.AppImage', bytes: 0, sha256: 'c'.repeat(64) } - ] + linuxAssets: [{ name: 'orca-linux.AppImage', bytes: 0, sha256: 'c'.repeat(64) }] }, { linuxAssets: [{ name: 'orca-linux.AppImage', bytes: 123, sha256: 'bad' }] }, { linuxAssets: [{ name: 'other.AppImage', bytes: 123, sha256: 'c'.repeat(64) }] }