Files
tutti/.github/workflows/windows-desktop-alpha.yml
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

202 lines
10 KiB
YAML

name: Windows Desktop Alpha
on:
pull_request:
paths:
- "apps/desktop/**"
- "services/tuttid/builtin-apps/**"
- "package.json"
- "pnpm-lock.yaml"
workflow_dispatch:
concurrency:
group: windows-desktop-alpha-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
test-and-package:
name: Test and package Windows x64
runs-on: windows-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .node-version
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: "1.24.5"
cache-dependency-path: |
go.work
go.work.sum
packages/agent/daemon/go.mod
packages/agent/daemon/go.sum
services/tuttid/go.mod
services/tuttid/go.sum
- name: Classify changed files
if: github.event_name == 'pull_request'
id: changed-files
shell: pwsh
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: >-
node ./tools/scripts/change-classification.mjs
--base "${env:BASE_SHA}"
--head "${env:HEAD_SHA}"
--github-output "${env:GITHUB_OUTPUT}"
- name: Install dependencies
shell: pwsh
run: pnpm install --frozen-lockfile
- name: Generate Onboarding fat package
shell: pwsh
run: pnpm generate:builtin-apps
- name: Test Windows agent process adapters
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
Push-Location packages/agent/daemon
go test ./runtime -run 'Test(RunVerifiedExecutableUsesVerifiedIdentity|RunVerifiedNodeScriptBoundedExecutesVerifiedScript|PrepareProcessExecutableExecutesVerifiedDescriptorAfterPathReplacement|LocalProcessTransportUsesWindowsEnvironmentKeySemantics)$'
go test ./runtimecmd -run 'Test(ResolverResolveAllReturnsEveryExecutableMatchInPathOrder|ResolverResolveAllNamesKeepsDirectoryOrderBeforeLauncherVariant|ResolveNPMGlobalLayoutUsesPrefixRootForWindowsShims|ResolverFindsWindowsNPMGlobalShim)$'
Pop-Location
- name: Test Windows daemon adapters
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
Push-Location services/tuttid
go test ./data/workspace -run 'Test(SQLiteDSNPathUsesFileURLFormOnWindows|SQLiteStoreListEmptyDatabase)$'
go test ./service/managedruntime -run 'TestNodeReadyAcceptsOfficialWindowsCorepackLayout$'
go test ./service/agentstatus -run 'Test(NewInstallShellCommandUsesCmdInterpreter|PlatformExecutableFileAcceptsPlainWindowsFile|RunDefaultInstallCommandPreservesStructuredArguments)$'
$env:TUTTI_RUN_NPM_ACCOUNT_USAGE_E2E = '1'
go test ./service/agentextension -run 'Test(AgentExtensionCommandUsesCmdForNPMShim|AgentExtensionCommandExecutesBatchLauncher|BuildInstallPlanUsesNativeWindowsNPMLauncher|AccountUsageProbeE2EInstallsActualNPMPackage|AgentTargetSetupReusesManagedRuntimeAcrossExtensionVersions|AgentTargetSetupKeepsACPReadyWhenAccountUsageInstallFails)$' -count=1
go test ./service/browser -run 'TestStableChromeDevToolsActivePortPathUsesLocalAppDataOnWindows$'
go test ./service/workspace -run 'Test(PlatformAppShellAdapter|NormalizeWindowsTerminalInput|TerminalServiceCreatesLocalPTY|TerminalServiceAttachStream|AppCenterServiceInitializesBuiltinCatalogAndInstallState|WorkspaceAppCLI)'
go test ./integration -run 'TestTuttidBlackBoxWorkspaceTerminalWebSocketStreamsInputAndOutput$'
go test ./integration -run 'TestTuttidBlackBoxWorkspaceTerminalWebSocketExitFrameCarriesExitCode$'
Pop-Location
- name: Test Desktop lifecycle
shell: pwsh
run: |
node --import ./apps/desktop/test/register-asset-stub.mjs --test --experimental-strip-types --test-name-pattern='Windows|windows' ./apps/desktop/src/main/cli/cliInstaller.test.ts
node --import ./apps/desktop/test/register-asset-stub.mjs --test --experimental-strip-types ./apps/desktop/src/main/daemon/tuttidManager.test.ts
- name: Typecheck Desktop
shell: pwsh
run: pnpm --filter @tutti-os/desktop typecheck
- name: Build Windows Desktop bundles
if: github.event_name == 'pull_request' && steps.changed-files.outputs.build_windows_installer != 'true'
shell: pwsh
run: pnpm --filter @tutti-os/desktop build
- name: Build unsigned Windows NSIS package
if: github.event_name == 'workflow_dispatch' || steps.changed-files.outputs.build_windows_installer == 'true'
shell: pwsh
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
run: pnpm --filter @tutti-os/desktop build:win:prepared
- name: Smoke test packaged Workspace App shell and Onboarding
if: github.event_name == 'workflow_dispatch' || steps.changed-files.outputs.build_windows_installer == 'true'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
$nativeCli = Resolve-Path 'apps/desktop/dist/win-unpacked/resources/bin/tutti.exe'
if (-not (Test-Path $nativeCli -PathType Leaf)) { throw 'packaged native tutti.exe is missing' }
$shellRuntimeRoot = Resolve-Path 'apps/desktop/dist/win-unpacked/resources/bin/managed-posix-shell'
$shellMetadata = Get-Content (Join-Path $shellRuntimeRoot 'runtime.json') -Raw | ConvertFrom-Json
$shell = Resolve-Path (Join-Path $shellRuntimeRoot $shellMetadata.executable)
$shellBin = Split-Path -Parent $shell
$mutagenRuntimeRoot = Resolve-Path 'apps/desktop/dist/win-unpacked/resources/bin/mutagen'
$mutagenMetadata = Get-Content (Join-Path $mutagenRuntimeRoot 'runtime.json') -Raw | ConvertFrom-Json
$mutagen = Resolve-Path (Join-Path $mutagenRuntimeRoot $mutagenMetadata.executable)
if (-not (Test-Path (Join-Path $mutagenRuntimeRoot 'LICENSE') -PathType Leaf)) { throw 'packaged Mutagen license is missing' }
if (-not (Test-Path (Join-Path $mutagenRuntimeRoot 'SSPL-LICENSE') -PathType Leaf)) { throw 'packaged Mutagen SSPL license is missing' }
& $mutagen version
if ($LASTEXITCODE -ne 0) { throw 'packaged Mutagen validation failed' }
$rtkRuntimeRoot = Resolve-Path 'apps/desktop/dist/win-unpacked/resources/bin/rtk'
$rtkMetadata = Get-Content (Join-Path $rtkRuntimeRoot 'runtime.json') -Raw | ConvertFrom-Json
$rtk = Resolve-Path (Join-Path $rtkRuntimeRoot $rtkMetadata.executable)
if (-not (Test-Path (Join-Path $rtkRuntimeRoot 'LICENSE') -PathType Leaf)) { throw 'packaged RTK license is missing' }
if (-not (Test-Path (Join-Path $rtkRuntimeRoot 'THIRD_PARTY_NOTICES.md') -PathType Leaf)) { throw 'packaged RTK notice is missing' }
$rtkVersion = & $rtk --version
if ($LASTEXITCODE -ne 0 -or $rtkVersion -ne "rtk $($rtkMetadata.version)") { throw 'packaged RTK validation failed' }
$package = Resolve-Path 'services/tuttid/builtin-apps/tutti-onboarding/build/package'
$runtime = Join-Path $env:RUNNER_TEMP 'tutti-onboarding-runtime'
$data = Join-Path $env:RUNNER_TEMP 'tutti-onboarding-data'
New-Item -ItemType Directory -Force -Path $runtime, $data | Out-Null
$originalPath = $env:PATH
$env:PATH = $shellBin
& $shell --noprofile --norc -c 'set -eu; for command in cat cp dirname mkdir mv rm sleep uname; do case "$(command -v "$command")" in /usr/bin/*) ;; *) exit 1 ;; esac; done'
if ($LASTEXITCODE -ne 0) { throw 'packaged shell command validation failed' }
& $shell --noprofile --norc -n (Join-Path $package 'bootstrap.sh')
if ($LASTEXITCODE -ne 0) { throw 'bootstrap.sh syntax validation failed' }
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
$listener.Start()
$port = $listener.LocalEndpoint.Port
$listener.Stop()
$env:TUTTI_PLATFORM = 'windows-amd64'
$env:TUTTI_APP_PACKAGE_DIR = $package
$env:TUTTI_APP_RUNTIME_DIR = $runtime
$env:TUTTI_APP_DATA_DIR = $data
$env:TUTTI_APP_HOST = '127.0.0.1'
$env:TUTTI_APP_PORT = "$port"
$process = Start-Process -FilePath $shell -ArgumentList '--noprofile', '--norc', (Join-Path $package 'bootstrap.sh') -WorkingDirectory $runtime -PassThru -NoNewWindow
try {
$healthy = $false
for ($attempt = 0; $attempt -lt 30; $attempt++) {
if ($process.HasExited) { throw "Onboarding exited early with code $($process.ExitCode)" }
try {
$response = Invoke-WebRequest -UseBasicParsing -NoProxy "http://127.0.0.1:$port/healthz" -TimeoutSec 2
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 300) { $healthy = $true; break }
} catch {}
Start-Sleep -Milliseconds 500
}
if (-not $healthy) { throw 'Onboarding /healthz did not become ready' }
} finally {
$env:PATH = $originalPath
if (-not $process.HasExited) { & (Join-Path $env:SystemRoot 'System32/taskkill.exe') /PID $process.Id /T /F | Out-Null }
}
- name: Upload Windows installer
if: github.event_name == 'workflow_dispatch' || steps.changed-files.outputs.build_windows_installer == 'true'
uses: actions/upload-artifact@v7
with:
name: tutti-windows-amd64-alpha
path: |
apps/desktop/dist/*.exe
apps/desktop/dist/*.blockmap
apps/desktop/dist/*.yml
if-no-files-found: error