# Manual Electron desktop release

macOS and Windows releases are whole Electron applications. The application,
renderer, Go sidecar, and bundled first-party OpenCode runtime share one
canonical version and move through Electron Builder together. Linux remains a
daemon/CLI-only release and does not use Electron packaging.

The release is split by signing host:

1. build/upload macOS Electron assets on macOS;
2. build/upload the Windows arm64/x64 Electron installers on Windows;
3. publish manifests only after all desktop and Linux/headless assets are
   reachable.

## macOS

```bash
cd desktop/release
KS3UTIL_BIN="$HOME/bin/ks3util" \
AGENTTEAM_MACOS_CODESIGN_IDENTITY='Developer ID Application: PingCAP ...' \
AGENTTEAM_MACOS_NOTARY_PROFILE=agentteam-notary \
./build-macos-assets.sh v1.2.3
```

The entrypoint stages and packages both architectures with Electron Builder:

- `AgentTeam-v1.2.3-darwin-arm64.dmg`
- `AgentTeam-v1.2.3-darwin-amd64.dmg`
- `SHA256SUMS.txt`

The Desktop output directory contains only the two customer installers and
their checksum file. ZIPs, ZIP blockmaps, and `latest-mac.yml` remain
release-owned `electron-updater` assets under
`desktop/release/.artifacts/manual-owner-client-assets/v1.2.3/macos/`; they are
not installation choices for manual testing.

Electron Builder signs the hardened `.app` and DMG with the same Developer ID
identity. The repo hook verifies the complete nested application signature, then
the release entrypoint submits each signed DMG to Apple exactly once before it
starts bounded status polling. Transient status-query failures are retried by
submission ID without uploading another copy. After acceptance it staples and
validates both the DMG and the nested application ticket before generating the
updater ZIP from that stapled application. DMG blockmap generation is disabled
because stapling changes the final DMG bytes; macOS updates use the ZIP and its
matching blockmap from `latest-mac.yml`.

The existing certificate-file convention is translated to Electron Builder:

```bash
AGENTTEAM_MACOS_CODESIGN_P12=/secure/developer-id.p12 \
AGENTTEAM_MACOS_CODESIGN_P12_PASSWORD='password' \
AGENTTEAM_MACOS_CODESIGN_CERT=/secure/developer-id-application.cer \
AGENTTEAM_MACOS_NOTARY_PROFILE=agentteam-notary \
./build-macos-assets.sh v1.2.3
```

`AGENTTEAM_MACOS_CODESIGN_CERT` is required when the P12 contains only the private
key. The entrypoint verifies that the key matches the certificate and assembles
a temporary Electron Builder identity that is deleted when the build exits.

For a local ad-hoc smoke build:

```bash
AGENTTEAM_MACOS_CODESIGN=0 AGENTTEAM_MACOS_NOTARIZE=0 \
./build-macos-assets.sh v1.2.3 --build-only
```

This mode still signs the complete application with one ad-hoc identity and a
smoke-only library-validation entitlement so Electron can start. It is not a
substitute for Developer ID signing and notarization in a public release. The
entrypoint rejects this mode without `--build-only`, so the package cannot be
uploaded to the CDN or activated in an updater feed by mistake.

Output defaults to:

```text
~/Desktop/AgentTeam-macOS-Installers/v1.2.3/
```

Override it with `AGENTTEAM_MACOS_INSTALLER_OUT_DIR`.

## Windows arm64 and x64

Run on the Windows signing host:

```bash
cd desktop/release
KS3UTIL_BIN=/path/to/ks3util \
AGENTTEAM_WINDOWS_SIGN_CERT_SHA1=<certificate-thumbprint> \
./build-windows-installers.sh v1.2.3
```

The entrypoint invokes Electron Builder's maintained NSIS target and produces:

- `AgentTeam-1.2.3-win-arm64.exe`
- `AgentTeam-1.2.3-win-x64.exe`
- `latest.yml`
- `SHA256SUMS.txt`

There is no custom launcher, custom NSIS script, or WebView2
prerequisite/bootstrap payload. Electron owns the native window and updater.
The merged `latest.yml` contains exactly one arm64 EXE and one x64 EXE with
their own `sha512`; publication fails if either architecture is missing.

The signing script accepts the established variables:

- `AGENTTEAM_WINDOWS_SIGN_CERT_SHA1`
- `AGENTTEAM_WINDOWS_SIGN_CERT_SUBJECT`
- `AGENTTEAM_WINDOWS_SIGN_TIMESTAMP_URL`
- `AGENTTEAM_WINDOWS_SIGNTOOL`

Electron Builder is the only Windows signing owner. It signs the staged daemon
and OpenCode executables while copying them, writes the AgentTeam executable's icon
and version resources before signing it, then signs the NSIS elevation helper,
uninstaller, and final installer. Repository hooks verify the packaged
application and final installer signatures; they never sign an executable a
second time. `AGENTTEAM_WINDOWS_SIGNTOOL`, when set, is passed to Electron Builder as
`SIGNTOOL_PATH` and is also used for the final verification.
When no path is provided, the Windows entrypoint discovers `signtool.exe` from
`PATH` or the newest installed Windows 10 SDK before starting the build.

When neither certificate selector is supplied on Windows, the entrypoint tries
to find the newest PingCAP code-signing certificate in the current-user store.
For a local unsigned smoke build:

```bash
AGENTTEAM_WINDOWS_SIGN=0 ./build-windows-installers.sh v1.2.3 --build-only
```

Output defaults to:

```text
~/Desktop/agentteam_window/v1.2.3/
```

Override it with `AGENTTEAM_WINDOWS_INSTALLER_OUT_DIR`.

## Managed OpenCode

`desktop/release/managed-opencode.env` pins the managed runtime version and npm
registry. The release entrypoints acquire only the targets they package:

- macOS: `opencode-darwin-arm64` and `opencode-darwin-x64`;
- Windows: `opencode-windows-arm64` and `opencode-windows-x64`.

The release entrypoint checks an explicit source first, then this local cache,
and accesses npm or `models.dev` only when the required version is absent. A
valid cached `models.json` is never refreshed during a build. By default the
cache is under the current checkout:

```text
desktop/release/.artifacts/managed-opencode/<runtime-version>/
```

To reuse one cache across tag worktrees or build without network access, set
`AGENTTEAM_MANAGED_OPENCODE_CACHE_DIR` before invoking the release entrypoint:

```bash
AGENTTEAM_MANAGED_OPENCODE_CACHE_DIR=/path/to/shared/managed-opencode \
./build-windows-installers.sh v1.2.3 --build-only
```

Offline/hotfix builds can set:

- `AGENTTEAM_MANAGED_OPENCODE_DARWIN_ARM64_SOURCE`
- `AGENTTEAM_MANAGED_OPENCODE_DARWIN_AMD64_SOURCE`
- `AGENTTEAM_MANAGED_OPENCODE_WINDOWS_ARM64_SOURCE`
- `AGENTTEAM_MANAGED_OPENCODE_WINDOWS_AMD64_SOURCE`
- `AGENTTEAM_MANAGED_OPENCODE_MODELS_SOURCE`

Sources must be native binaries; text/npm shims are rejected.

## Upload layout

Electron application assets stay in the existing artifact prefix, while updater
metadata is published with the Desktop release catalog:

```text
agentteam/desktop/AgentTeam-<version>-mac-<arch>.dmg
agentteam/desktop/AgentTeam-<version>-mac-<arch>.zip
agentteam/desktop/AgentTeam-<version>-mac-<arch>.<ext>.blockmap
agentteam/desktop/AgentTeam-<version>-win-<arch>.exe
agentteam/releases/desktop/latest-mac.yml
agentteam/releases/desktop/latest.yml
agentteam/releases/desktop/v<version>/latest-mac.yml
agentteam/releases/desktop/v<version>/latest.yml
agentteam/desktop/v<version>/SHA256SUMS.txt
```

Linux/headless archives keep the existing daemon/CLI layout under
`agentteam/client/v<version>/`. Server bundles stay under
`agentteam/server/v<version>/`.

## Manifest publication

After the platform uploads and Linux/server jobs complete:

```bash
cd desktop/release
KS3UTIL_BIN="$HOME/bin/ks3util" \
./publish-release-manifests.sh v1.2.3
```

The publish command writes the version manifest, `index.json`, `stable.json`,
and both root updater metadata files to the KS3 origin without waiting on their
CDN caches. It verifies every origin object, then prints the five public URLs
that must be refreshed together. After refreshing those URLs, run the read-only
verification:

```bash
./publish-release-manifests.sh v1.2.3 --verify-cdn
```

The verification mode compares the CDN with the origin and never regenerates or
uploads release metadata.

The script verifies public assets, checksums, and Electron updater metadata,
then publishes the final Desktop download metadata:

- `agentteam/releases/daemon/` is a temporary bridge owned by the automatic
  Linux/server CI job for clients that still use the legacy default URL;
- `agentteam/releases/desktop/` is the canonical release namespace and contains one
  six-package version manifest: Linux
  amd64/arm64 daemon archives, macOS arm64/x64 DMGs, and Windows arm64/x64 EXEs,
  plus immutable `v<version>/latest-mac.yml`/`v<version>/latest.yml` references.
  Electron's automatic update feed uses the root `latest-*.yml` files in the
  same namespace.

`index.json` indexes versions only; architectures live in each version manifest.
Each version entry records its product. Existing `agentteam-daemon` history keeps its
original identity while releases finalized by this workflow use `agentteam-desktop`.
`stable.json`, `index.json`, each version manifest, and Electron Builder metadata
must agree on one version. Platform upload steps publish only immutable assets
and versioned updater metadata. This finalizer is the sole writer of public
Desktop JSON, writes the catalog before the root updater metadata in one origin
publication, and leaves CDN refresh plus read-only verification as the final
gate. It does not overwrite the CI-owned bridge manifest.

Common overrides:

- `AGENTTEAM_KS3_BUCKET`
- `AGENTTEAM_KS3_CLIENT_PREFIX`
- `AGENTTEAM_KS3_SERVER_PREFIX`
- `AGENTTEAM_DESKTOP_ASSET_PREFIX`
- `AGENTTEAM_MANIFEST_DESKTOP_PREFIX`
- `AGENTTEAM_MANIFEST_WORK_DIR`
- `AGENTTEAM_PUBLIC_DOWNLOAD_BASE_URL`
- `AGENTTEAM_GENERATE_MANIFEST_SCRIPT`

## Cleanup

Safe to regenerate after preserving uploaded/signed deliverables:

- `desktop/electron/dist/`
- `desktop/release/.artifacts/managed-opencode/`
- `desktop/release/.artifacts/manual-release-manifests/`
- the versioned Desktop output directories above.

Never place signing certificates, passwords, tokens, or signed release outputs
in git.
