Skip to content

Commit 8d6219e

Browse files
committed
Merge remote-tracking branch 'upstream/dev' into dev
Brings in 178 upstream commits (merge base 5f1f8d0, upstream tip 6b11312 "feat(process-daemon): rename terminals (anomalyco#60324)"). 15 files conflicted. None involved session move: since move was restored in 58fe982, upstream's move changes merge cleanly. Same rule as the last merge: keep fork behavior from PATCH.md, take upstream everywhere else. Conflict resolutions ==================== CI - .github/workflows/release.yml: kept the fork's datever release workflow. Upstream's pipeline only runs on anomalyco/opencode. Core - core/src/database/migration.gen.ts: the fork's 20261016185441_session_roots stays m50. Upstream's migrations follow in timestamp order as m51-m58, including the four new ones: schedule, session_part, session_time_busy, schedule_checks. - core/schema.json: both sides advanced the drizzle snapshot. The new snapshot id has prevIds [fork 8c8158eb..., upstream 0ac88673...]. Its DDL is upstream's plus the fork's `session.roots` column. `bun script/migration.ts --check` reports no pending changes. - core/src/plugin/internal.ts: kept the fork's CancelTool and upstream's new ScheduleTools next to each other. - core/src/session.ts: took upstream's fork publish, which now provides SessionBlob.ForkMedia (media copied as blobs based on the parent's location), and kept the fork's `destination` field so `session.fork({ directory })` still places the fork in another directory. - core/test/session-skill.test.ts: kept the fork's test setup (SessionRoots node, SessionStore replacement) and took upstream's renamed policy rule: `skill.use` on resource `effect` instead of `integration.use` on `skill:effect`. Plugin API - plugin/src/effect/session.ts and plugin/src/promise/session.ts: upstream moved `wait` and `instructions` out of SessionDomain into a Pick of the new ExperimentalSessionApi. Took that and kept `move` and the fork's `root` in SessionDomain. TUI - tui/src/config/index.tsx: kept the fork's `model_keywords` and took upstream's change that makes `cursor` always resolved (no longer optional). - tui/src/routes/session/dialog-message.tsx: upstream dropped the `setPrompt` prop in favor of usePromptRestore. Took that and kept the fork's `jump` prop, which shows "Jump to" only when opened from the timeline. - tui/src/routes/session/dialog-timeline.tsx: upstream's call without setPrompt, plus the fork's `jump`. - tui/src/ui/dialog-select.tsx: kept the fork's category `color` prop (provider colors) and took upstream's token change from `theme.text.faint` to `theme.text.muted` for the fallback. Generated files - packages/client/src/effect/api/api.ts, packages/client/src/promise/generated/types.ts, packages/protocol/openapi.json: regenerated with `bun run generate` in packages/protocol, packages/client and services/www, then formatted with prettier. The spec has both the move and root endpoints. Verification - bun install (new upstream dependencies) - bun turbo typecheck: 35/35 packages - oxlint: 0 errors (run without --fix) - bun script/migration.ts --check: no schema changes - make tests: tui 187, core 455, schema 7, sdk 3 pass, 0 fail
2 parents 58fe982 + 6b11312 commit 8d6219e

1,071 files changed

Lines changed: 61116 additions & 18658 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/CODEOWNERS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
packages/app/ @Hona @Brendonovich
33
packages/desktop/ @Hona @Brendonovich
44

5+
# GUI plugin SDK
6+
# changes here must be meticulous - pls chat first :)
7+
packages/plugin/src/gui/ @Hona
8+
59
# CI and releases. Release jobs that hold secrets run only .github/workflows/release.yml and .github/release/,
610
# which makes this path the release security boundary.
711
/.github/ @anomalyco/releasers

‎.github/release/publish-discord.sh‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env bash
2+
# publish-discord.sh: announces the v<version> release in Discord with OPENCODE_RELEASE_NOTES (DISCORD_WEBHOOK).
3+
# Discord caps an embed description at 4096 characters, so longer notes are cut and link to the GitHub release.
4+
. "$(dirname "$0")/lib.sh"
5+
6+
tag="v$OPENCODE_VERSION"
7+
payload=$(jq -n --arg tag "$tag" --arg notes "${OPENCODE_RELEASE_NOTES:-}" \
8+
--arg url "https://github.com/$GITHUB_REPOSITORY/releases/tag/$tag" '
9+
($notes | if length > 4000 then .[:4000] + "\n\n[Full release notes](\($url))" else . end) as $description |
10+
{
11+
allowed_mentions: {parse: []},
12+
embeds: [{title: "OpenCode V2 \($tag)", url: $url, color: 2829099} + if $description == "" then {} else {description: $description} end]
13+
}')
14+
echo "$payload" | jq .
15+
if dry_run; then exit 0; fi
16+
17+
test -n "${DISCORD_WEBHOOK:-}" || die "DISCORD_WEBHOOK is not set"
18+
curl -fsS --retry 3 -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK"

‎.github/release/sign-desktop-macos.sh‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# notarizes them in one submission, staples them, and only then builds opencode-desktop-mac-<arch>.{zip,dmg,app.tar.gz}
44
# from the stapled apps. Dry runs sign ad-hoc and skip notarization. Dev and beta trade larger files for faster
55
# packaging (zip and dmg at zlib level 1) and skip the .app.tar.gz, which only the retired Tauri updater feed read.
6-
# Notarization reads APPLE_API_KEY_CONTENT, APPLE_API_KEY_ID and APPLE_API_ISSUER.
6+
# Notarization reads APPLE_NOTARY_KEY (the .p8 contents), APPLE_NOTARY_KEY_ID and APPLE_NOTARY_ISSUER_ID.
77
. "$(dirname "$0")/lib.sh"
88

99
out=$1
@@ -60,9 +60,9 @@ done
6060

6161
if ! dry_run; then
6262
key=$(mktemp)
63-
printf '%s\n' "$APPLE_API_KEY_CONTENT" > "$key"
63+
printf '%s\n' "$APPLE_NOTARY_KEY" > "$key"
6464
ditto -c -k --sequesterRsrc "$apps" "$apps.zip"
65-
xcrun notarytool submit "$apps.zip" --key "$key" --key-id "$APPLE_API_KEY_ID" --issuer "$APPLE_API_ISSUER" \
65+
xcrun notarytool submit "$apps.zip" --key "$key" --key-id "$APPLE_NOTARY_KEY_ID" --issuer "$APPLE_NOTARY_ISSUER_ID" \
6666
--wait --timeout 60m --output-format json > "$apps.json"
6767
rm -f "$key"
6868
cat "$apps.json"

‎.github/release/tag-release.sh‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
. "$(dirname "$0")/lib.sh"
66

77
tag="v$OPENCODE_VERSION"
8-
git ls-files -z -- '*package.json' bun.lock | xargs -0 sed -i -E "s/\"version\": \"[^\"]+\"/\"version\": \"$OPENCODE_VERSION\"/g"
8+
# @opencode/pty is versioned separately, by process-daemon-v<version> tags.
9+
git ls-files -z -- '*package.json' bun.lock ':!packages/process-daemon/**' | xargs -0 sed -i -E "s/\"version\": \"[^\"]+\"/\"version\": \"$OPENCODE_VERSION\"/g"
910

1011
if [ -n "${OPENCODE_RELEASE_NOTES:-}" ] && ! grep -qF -x "## $tag" CHANGELOG.md && ! grep -qF "## $tag " CHANGELOG.md; then
1112
notes=$(mktemp)
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
name: Process daemon
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- packages/process-daemon/**
7+
- .github/workflows/check-process-daemon.yml
8+
push:
9+
branches: [dev]
10+
paths:
11+
- packages/process-daemon/**
12+
- .github/workflows/check-process-daemon.yml
13+
workflow_dispatch:
14+
15+
permissions:
16+
contents: read
17+
18+
concurrency:
19+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
20+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
21+
22+
defaults:
23+
run:
24+
working-directory: packages/process-daemon
25+
26+
jobs:
27+
miri:
28+
name: Miri (callback ownership)
29+
runs-on: ubuntu-22.04
30+
timeout-minutes: 15
31+
env:
32+
CARGO_TARGET_DIR: target/miri
33+
steps:
34+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
35+
- name: Install Miri
36+
run: rustup toolchain install nightly-2026-09-02 --profile minimal --component miri --component rust-src
37+
- run: cargo +nightly-2026-09-02 miri setup
38+
- name: Check Stacked Borrows
39+
run: |
40+
cargo +nightly-2026-09-02 miri test --locked --manifest-path tests/ghostty-effects/Cargo.toml
41+
cargo +nightly-2026-09-02 miri test --locked --manifest-path tests/transport-retained/Cargo.toml
42+
- name: Check Tree Borrows
43+
env:
44+
MIRIFLAGS: -Zmiri-tree-borrows
45+
run: |
46+
cargo +nightly-2026-09-02 miri test --locked --manifest-path tests/ghostty-effects/Cargo.toml
47+
cargo +nightly-2026-09-02 miri test --locked --manifest-path tests/transport-retained/Cargo.toml
48+
49+
test:
50+
name: Core (${{ matrix.os }})
51+
runs-on: ${{ matrix.os }}
52+
timeout-minutes: 30
53+
strategy:
54+
fail-fast: false
55+
matrix:
56+
os: [ubuntu-22.04, macos-15]
57+
steps:
58+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
59+
- uses: mlugg/setup-zig@8d6198c65fb0feaa111df26e6b467fea8345e46f # v2.0.5
60+
with:
61+
version: 0.16.0
62+
- name: Install Rust
63+
run: rustup toolchain install 1.90.0 --profile minimal --component rustfmt --component clippy
64+
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
65+
with:
66+
key: core-${{ matrix.os }}-zig-0.16.0
67+
workspaces: packages/process-daemon -> target
68+
cache-workspace-crates: true
69+
- run: cargo fmt --check
70+
- run: cargo clippy --locked --all-targets --all-features -- -D warnings
71+
- name: Test Linux
72+
if: runner.os == 'Linux'
73+
run: cargo test --locked
74+
# Match the release workflow's macOS playground exclusion.
75+
- name: Test macOS
76+
if: runner.os == 'macOS'
77+
run: cargo test --locked --lib --test service --test runtime --test ownership --test rows --test ghostty-effects
78+
79+
windows:
80+
name: Windows (${{ matrix.arch }})
81+
runs-on: ${{ matrix.runner }}
82+
timeout-minutes: 30
83+
strategy:
84+
fail-fast: false
85+
matrix:
86+
include:
87+
- arch: x64
88+
runner: windows-2025
89+
target: x86_64-pc-windows-msvc
90+
machine: "8664"
91+
- arch: arm64
92+
runner: windows-11-arm
93+
target: aarch64-pc-windows-msvc
94+
machine: "aa64"
95+
defaults:
96+
run:
97+
shell: pwsh
98+
working-directory: packages/process-daemon
99+
env:
100+
CARGO_TERM_COLOR: always
101+
CARGO_BUILD_TARGET: ${{ matrix.target }}
102+
RUSTUP_TOOLCHAIN: 1.90.0-x86_64-pc-windows-msvc
103+
RUST_BACKTRACE: "1"
104+
steps:
105+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
106+
- name: Enable long dependency paths
107+
run: git config --global core.longpaths true
108+
- uses: mlugg/setup-zig@8d6198c65fb0feaa111df26e6b467fea8345e46f # v2.0.5
109+
if: matrix.arch == 'x64'
110+
with:
111+
version: 0.16.0
112+
cache-key: ${{ matrix.target }}
113+
# Zig 0.16 crashes when building Ghostty on the native ARM64 host.
114+
# Use x64-hosted compilers on this ARM64 runner, but execute ARM64 tests natively.
115+
- name: Install x64 Zig for ARM64 cross-compilation
116+
if: matrix.arch == 'arm64'
117+
run: |
118+
$archive = Join-Path $env:RUNNER_TEMP 'zig-x64.zip'
119+
Invoke-WebRequest 'https://ziglang.org/download/0.16.0/zig-x86_64-windows-0.16.0.zip' -OutFile $archive
120+
if ((Get-FileHash $archive -Algorithm SHA256).Hash -ne '68659eb5f1e4eb1437a722f1dd889c5a322c9954607f5edcf337bc3684a75a7e') { throw 'Zig archive checksum mismatch' }
121+
Expand-Archive $archive -DestinationPath $env:RUNNER_TEMP
122+
Join-Path $env:RUNNER_TEMP 'zig-x86_64-windows-0.16.0' >> $env:GITHUB_PATH
123+
- name: Fix Zig 0.16 ARM64 function-pointer alignment
124+
if: matrix.arch == 'arm64'
125+
run: |
126+
# ARM64 function pointers require 4-byte alignment, unlike *anyopaque.
127+
# Zig 0.16's Windows stack-trace helper omits the required alignment assertion.
128+
$file = Join-Path $env:RUNNER_TEMP 'zig-x86_64-windows-0.16.0/lib/std/debug/SelfInfo/Windows.zig'
129+
$source = [System.IO.File]::ReadAllText($file)
130+
$old = '@ptrCast(try si.loadNtdllProc(@tagName(proc)))'
131+
if (!$source.Contains($old)) { throw 'Zig alignment workaround no longer matches; re-evaluate it' }
132+
[System.IO.File]::WriteAllText($file, $source.Replace($old, '@ptrCast(@alignCast(try si.loadNtdllProc(@tagName(proc))))'))
133+
- name: Install Rust toolchain and target
134+
run: rustup toolchain install $env:RUSTUP_TOOLCHAIN --force-non-host --profile minimal --component rustfmt --component clippy --target $env:CARGO_BUILD_TARGET
135+
- name: Verify runner and compiler architecture
136+
env:
137+
EXPECTED_ARCH: ${{ matrix.arch }}
138+
run: |
139+
$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()
140+
Write-Output "Windows architecture: $arch"
141+
if ($arch -ine $env:EXPECTED_ARCH) { throw "Expected native $env:EXPECTED_ARCH runner, got $arch" }
142+
$compiler = rustc -vV
143+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
144+
$compiler
145+
if ($compiler -notcontains 'host: x86_64-pc-windows-msvc') { throw 'Unexpected Rust compiler host' }
146+
Write-Output "Executable target: $env:CARGO_BUILD_TARGET"
147+
zig version
148+
# No cache-on-failure: a timed-out run once saved a partial cache that broke re-runs.
149+
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
150+
with:
151+
key: windows-${{ matrix.target }}-zig-0.16.0
152+
workspaces: packages/process-daemon -> target
153+
# The native Ghostty build is now an output of the workspace crate.
154+
cache-workspace-crates: true
155+
- name: Check formatting
156+
run: cargo fmt --check
157+
- name: Check Clippy
158+
run: cargo clippy --locked --all-targets --all-features -- -D warnings
159+
- name: Build executable
160+
id: build
161+
run: cargo build --locked
162+
- name: Run tests
163+
id: tests
164+
timeout-minutes: 10
165+
run: cargo test --locked --all-targets --all-features
166+
- name: Run standalone library tests
167+
env:
168+
EXPECTED_MACHINE: ${{ matrix.machine }}
169+
run: |
170+
$artifacts = cargo test --locked --lib --no-run --message-format=json | ForEach-Object { $_ | ConvertFrom-Json }
171+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
172+
$binary = ($artifacts | Where-Object { $_.reason -eq 'compiler-artifact' -and $_.profile.test -and $_.executable }).executable
173+
# Cargo adds native library paths to PATH, which can hide accidental DLL dependencies.
174+
# Copy only the executable so neither Cargo nor adjacent build artifacts can supply them.
175+
$directory = Join-Path $env:RUNNER_TEMP 'pty-standalone-tests'
176+
New-Item -ItemType Directory -Force $directory | Out-Null
177+
$standalone = Join-Path $directory 'opencode-pty-tests.exe'
178+
Copy-Item $binary $standalone
179+
$bytes = [System.IO.File]::ReadAllBytes($standalone)
180+
$header = [BitConverter]::ToInt32($bytes, 0x3c)
181+
$machine = [BitConverter]::ToUInt16($bytes, $header + 4)
182+
if ($machine -ne [Convert]::ToUInt16($env:EXPECTED_MACHINE, 16)) { throw 'Test executable architecture does not match the runner' }
183+
& $standalone
184+
- name: Diagnose failed library tests
185+
if: failure() && steps.tests.outcome == 'failure'
186+
timeout-minutes: 10
187+
run: cargo test --locked --lib --all-features -- --test-threads=1 --nocapture
188+
- name: Diagnose failed runtime tests
189+
if: failure() && steps.tests.outcome == 'failure'
190+
timeout-minutes: 3
191+
run: cargo test --locked --test runtime -- --test-threads=1 --nocapture
192+
- name: Diagnose failed daemon tests
193+
if: failure() && steps.tests.outcome == 'failure'
194+
timeout-minutes: 3
195+
run: cargo test --locked --test windows-daemon -- --test-threads=1 --nocapture
196+
- name: Smoke test executable
197+
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
198+
run: |
199+
$metadata = cargo metadata --locked --no-deps --format-version 1 | ConvertFrom-Json
200+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
201+
$version = ($metadata.packages | Where-Object name -eq 'opencode-pty').version
202+
$binary = Join-Path $metadata.target_directory "$env:CARGO_BUILD_TARGET/debug/opencode-pty.exe"
203+
$output = & $binary --version
204+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
205+
Write-Output $output
206+
if ($output -ne "opencode-pty $version (protocol 7)") { throw "Unexpected executable version: $output" }
207+
- name: Document current coverage
208+
if: always()
209+
run: |
210+
@"
211+
## Windows ${{ matrix.arch }}
212+
213+
Target: $env:CARGO_BUILD_TARGET
214+
215+
This job builds the executable and runs all enabled tests natively, including libghostty parser, protocol, direct TerminalService ConPTY runtime, named-pipe transport, and daemon tests.
216+
The runtime suite spawns real Rust console children and checks input/output, Unicode, cwd/environment/argv/PATH, OS resize, snapshots, replay, terminal replies, and independent terminals.
217+
Daemon tests cover private atomic registration, ownership/handoff, stale registration/locking, partial requests, blocked subscribers, and live ConPTY operations over real named pipes.
218+
The older service, ownership, playground, and rows integration suites remain Unix-only.
219+
Basic tests do not establish exhaustive lifecycle coverage or Windows interactive CLI parity.
220+
No packages or releases are published.
221+
"@ >> $env:GITHUB_STEP_SUMMARY

‎.github/workflows/release-dry-run.yml‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -380,13 +380,23 @@ jobs:
380380
uses: ./.github/release/download
381381
with:
382382
pattern: "{build-npm,build-desktop-darwin-*}"
383+
# The certificate has no password, which apple-actions/import-codesign-certs rejects. The sign scripts look up
384+
# the identity in build.keychain, and codesign finds the key through the user search list.
383385
- name: Import certificate
384386
if: needs.plan.outputs.dry_run != 'true'
385-
uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0
386-
with:
387-
keychain: build
388-
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
389-
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
387+
run: |
388+
cert="$RUNNER_TEMP/cert.p12"
389+
keychain_password=$(openssl rand -base64 32)
390+
printf '%s' "$APPLE_SIGNING_CERTIFICATE" | base64 --decode > "$cert"
391+
security create-keychain -p "$keychain_password" build.keychain
392+
security set-keychain-settings -lut 21600 build.keychain
393+
security unlock-keychain -p "$keychain_password" build.keychain
394+
security import "$cert" -k build.keychain -P "" -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productsign
395+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$keychain_password" build.keychain > /dev/null
396+
security list-keychains -d user -s build.keychain $(security list-keychains -d user | tr -d '"')
397+
rm -f "$cert"
398+
env:
399+
APPLE_SIGNING_CERTIFICATE: ${{ secrets.APPLE_SIGNING_CERTIFICATE }}
390400
- name: Sign CLI
391401
if: needs.plan.outputs.cli == 'true'
392402
run: bash .github/release/sign-cli-macos.sh "$RUNNER_TEMP/in/build-npm" "$RUNNER_TEMP/out"
@@ -396,15 +406,20 @@ jobs:
396406
bash .github/release/sign-desktop-macos.sh "$RUNNER_TEMP/out" \
397407
"$RUNNER_TEMP"/in/build-desktop-darwin-*/opencode-desktop-mac-*.app.tar
398408
env:
399-
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY_PATH }}
400-
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
401-
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
409+
APPLE_NOTARY_KEY: ${{ secrets.APPLE_NOTARY_KEY }}
410+
APPLE_NOTARY_KEY_ID: ${{ vars.APPLE_NOTARY_KEY_ID }}
411+
APPLE_NOTARY_ISSUER_ID: ${{ vars.APPLE_NOTARY_ISSUER_ID }}
402412
- id: upload
403413
name: Upload
404414
uses: ./.github/release/upload
405415
with:
406416
name: sign-macos
407417
path: ${{ runner.temp }}/out
418+
- name: Delete keychain
419+
if: always() && needs.plan.outputs.dry_run != 'true'
420+
run: |
421+
rm -f "$RUNNER_TEMP/cert.p12"
422+
security delete-keychain build.keychain || true
408423
409424
# Signs the Windows CLI and every executable and native module of the unpacked desktop apps.
410425
sign-windows:

0 commit comments

Comments
 (0)