Skip to content

Commit 5898181

Browse files
committed
Merge remote-tracking branch 'upstream/dev' into dev
Brings in 721 upstream commits (merge base d93568f, upstream tip 5f1f8d0 "test: deflake shutdown-deadline cleanup and PowerShell permission timeout (anomalyco#59667)") on top of 63 fork commits. The guiding rule for every conflict: keep fork behavior documented in PATCH.md (session roots instead of session move, GitHub-hosted runners, fork release workflow, vertical tabs, provider colors, etc.) and take upstream's changes everywhere else. Conflict resolutions ==================== CI workflows ------------ - .github/workflows/check.yml, check-test.yml (upstream renamed test.yml to check-test.yml): took upstream's new `if: !pull_request.draft` guard, kept the fork's `runs-on: ubuntu-latest` for the typecheck and "affected packages" jobs. The unit test matrices already used ubuntu-latest/windows-latest; the e2e jobs stay on Blacksmith, as they were in the fork. - .github/workflows/release.yml (add/add): kept the fork's datever GitHub Release workflow. Upstream's signing/publishing pipeline is gated on `github.repository == 'anomalyco/opencode'` and depends on secrets this fork does not have. Session move removal vs upstream move improvements -------------------------------------------------- The fork removed session move in favor of session roots and recovers a missing directory by forking. Upstream kept iterating on move; those changes are dropped where they only concern move, and the unrelated parts are kept. - packages/app/src/session/composer/location-missing.tsx: took upstream's redesigned recovery dock (Loader, per-button pending state, searchable worktree list once there are 10+, accessible group/ description, location-footer slot) but kept the fork's action: the mutation is `fork` and calls `session.fork({ directory })`, remembers the new session and navigates to it. The `source` field from upstream is kept so each button shows its own pending state. - packages/app/src/runtime/i18n/en.ts: kept the fork's "Fork this session..." / "Forking session..." strings, added upstream's new `session.rewind.toast.*` keys. - packages/app/e2e/regression/session-location-sync.spec.ts and session-summary.spec.ts: kept the fork's removal of the move-only tests (move-to-worktree recovery, live session.moved follow, tabs kept across a move, reconnect after a move, details following a move). - packages/core/test/session-runner.test.ts: kept the fork's removal of the two `move` inbox-item scenarios (upstream only changed `s.llm.gate` to `s.llm.gate()` inside them). - packages/client/src/solid/data.ts: kept the fork's `session.root.added` / `session.root.removed` reducers in place of upstream's refactored `session.moved` reducer. - packages/plugin-browser/src/connection.ts: took upstream's `subscribe({ types })` filter API, subscribed only to `session.deleted` (no `session.moved`). - services/www/src/docs/content/cli/keybinds.mdx: added upstream's `session.copy.id` row, kept `session.move` removed. Protocol / server ----------------- - packages/protocol/src/groups/session.ts: upstream dropped `sessionLocationMiddleware` from `session.fork` and `session.update` (a missing directory must not block them). Took that, and kept the fork's `directory` payload field and its description on `session.fork`. `session.root.add` / `session.root.remove` keep their middleware. Core ---- - packages/core/src/session/prompt.ts: upstream flattened `prepare` (no `instances.provide(...)` wrapper) and added blob attachments (SessionBlob/SessionStored, RESIZABLE_IMAGE_MIMES). Took that shape and kept the fork's skill resolution through `SessionRoots.skill(sessionID, id)` instead of `Skill.Service.get`. - packages/core/src/file-access.ts: upstream replaced FSUtil with Environment; deps are now [Environment, Location, Permission, SessionStore] (SessionStore from the fork, for root-aware access). - packages/core/test/session-skill.test.ts: took upstream's org-policy setup through `Config.testLayer(...)` (ManagedPolicy is gone) and re-added the fork's `SessionRoots.node` plus the `SessionStore` replacement, wrapping the LayerMap in `Effect.gen` to read the store. Database migrations ------------------- - packages/core/src/database/migration.gen.ts: the fork's 20261016185441_session_roots is m50; upstream's session_allowance, drop_session_allowance, blob and drop_session_pending follow as m51-m54, in timestamp order. - packages/core/schema.json: both branches advanced the drizzle snapshot. The merged snapshot has a new id with `prevIds: [fork 05ce65d9..., upstream 44bd8ca5...]`. Its DDL is upstream's plus the fork's `session.roots` column. `bun script/migration.ts --check` reports no pending changes. App settings ------------ - packages/app/src/settings/model.tsx and model.test.ts: kept the fork's default `tabLayout: "vertical"` and added upstream's new `reduceMotion: "system"`. TUI --- - packages/tui/src/component/dialog-model.tsx: kept the fork's theme and `model_keywords` lookup and added upstream's usage limits (`dialog`, `useUsageLimits`, per-model "$ left" / "No limit" badges). - packages/tui/src/component/prompt/index.tsx: kept the fork's agent cycle tab strip and provider/keyword colors on PromptMetadataRow, and passed upstream's new `limit={limit()}` prop. gui-extensions -> gui-plugins rename ----------------------------------- Upstream renamed packages/gui-extensions to packages/gui-plugins and redesigned the session details panel (location menu, location workspace, new panel.tsx). - details/workspace-menu.tsx: took upstream's `items()` structure (used by both the standalone menu and the new `sub` submenu mode) and carried over the fork's change: `add()` calls `session.root.add`, labels use `root.menu` / `root.failed`, and there is no `blocked()` running-session guard. - details/i18n/en.ts: replaced `move.title|menu|failed` with `root.menu` / `root.failed`. Upstream's panel no longer has the "Move to worktree" row, so `root.title` is gone. - gui-extensions/src/details/{panel.tsx,i18n/en.ts} (modify/delete): accepted upstream's deletion; the fork's change is ported above. Generated files --------------- - services/www/openapi.json, services/www/public/openapi.json (modify/delete): accepted upstream's deletion. The spec now lives in packages/protocol/openapi.json. - Regenerated packages/protocol/openapi.json (`bun run generate`), packages/client generated promise/effect clients and types (`bun run generate`, which also resolves the types.ts conflict), and services/www generated docs, then ran prettier on the outputs. The client exposes `session.root.add/remove` and `session.fork({ directory })`, and no `session.move`. Follow-up fixes required by the merge ===================================== - packages/core/src/plugin/custom/auto-load-skills.ts and packages/core/src/tool/plugin/cancel.ts: upstream turned the `@opencode/ai` root into a promise-first entrypoint, and the Effect API (SystemPart, ToolFailure, ...) moved to `@opencode/ai/effect`. Updated both fork imports. - packages/app/src/runtime/server/data.ts: removed `session.moved` from SESSION_ACTIVITY. Upstream added it, but the fork does not publish it as a public event. Verification ============ - `bun install` (picked up new upstream dependencies) - `bun turbo typecheck --continue`: 35/35 packages pass - `bun run lint`: 0 errors - `bun script/migration.ts --check` in packages/core: clean
2 parents ebff11e + 5f1f8d0 commit 5898181

3,778 files changed

Lines changed: 172099 additions & 129518 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.

‎.gitattributes‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
packages/core/migration/**/snapshot.json linguist-generated
22
packages/core/src/database/migration.gen.ts linguist-generated
33
packages/core/src/models-dev/snapshot.txt linguist-generated
4+
packages/core/src/database/schema.gen.ts linguist-generated
5+
packages/desktop/src/main/storage/migration.gen.ts linguist-generated
6+
packages/protocol/openapi.json linguist-generated
7+
packages/client/src/promise/generated/** linguist-generated
8+
packages/client/src/effect/generated/** linguist-generated
9+
packages/client/src/effect/api/** linguist-generated
410
packages/core/src/**/*.txt text eol=lf
511
packages/httpapi-codegen/test/generated/*.ts text eol=lf

‎.github/CODEOWNERS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
# web + desktop packages
22
packages/app/ @Hona @Brendonovich
33
packages/desktop/ @Hona @Brendonovich
4+
5+
# CI and releases. Release jobs that hold secrets run only .github/workflows/release.yml and .github/release/,
6+
# which makes this path the release security boundary.
7+
/.github/ @anomalyco/releasers

‎.github/actions/setup-bun/action.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ runs:
5858
with:
5959
path: ${{ steps.cache.outputs.dir }}
6060
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ steps.cache.outputs.version }}-${{ hashFiles('bun.lock', 'patches/**') }}
61+
# Each dev merge changes beta's bun.lock. Beta falls back to the newest cache for the same Bun version
62+
# (Bun's cache stores every package version separately) instead of installing everything from npm.
63+
restore-keys: ${{ (github.ref_name == 'beta' && format('{0}-{1}-bun-{2}-', runner.os, runner.arch, steps.cache.outputs.version)) || '' }}
6164

6265
- name: Install setuptools for distutils compatibility
6366
run: python3 -m pip install setuptools || pip install setuptools || true

‎.github/nix-hashes/publish.sh‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env bash
2+
# publish.sh <hashes.json>: run from a checkout of dev. Puts <hashes.json> on bot/nix-hashes as one commit on top of dev,
3+
# opens or updates the pull request into dev, dispatches its required checks and enables auto-merge. Closes the pull
4+
# request instead when dev already has these hashes. Uses git, gh and jq only; never runs repository code.
5+
set -euo pipefail
6+
7+
here=$(cd "$(dirname "$0")" && pwd)
8+
new=$(realpath "$1")
9+
branch=bot/nix-hashes
10+
file=nix/hashes.json
11+
title="chore: update nix node_modules hashes"
12+
: "${GH_REPO:?}" "${GITHUB_SHA:?}"
13+
git=(git -c core.hooksPath=/dev/null -c user.name='github-actions[bot]' -c user.email='41898282+github-actions[bot]@users.noreply.github.com')
14+
15+
bash "$here/validate.sh" "$new"
16+
17+
"${git[@]}" fetch --no-tags --depth=1 origin "+refs/heads/dev:refs/remotes/origin/dev" "$GITHUB_SHA"
18+
19+
# A newer push that changed the inputs has its own run, which publishes hashes for it. Mirrors on.push.paths.
20+
if ! git diff --quiet "$GITHUB_SHA" origin/dev -- bun.lock package.json ':(glob)packages/*/package.json' \
21+
':(glob)services/*/package.json' flake.lock nix/node_modules.nix nix/scripts patches; then
22+
echo "::notice::dev changed the hash inputs since $GITHUB_SHA; leaving it to the newer run"
23+
exit 0
24+
fi
25+
26+
# Only a same-repository pull request is the bot's; a fork can use the same branch name.
27+
find_pr() {
28+
gh pr list --repo "$GH_REPO" --base dev --head "$branch" --state open --json number,isCrossRepository \
29+
--jq 'map(select(.isCrossRepository | not)) | .[0].number // empty'
30+
}
31+
pr=$(find_pr)
32+
33+
"${git[@]}" checkout -B "$branch" origin/dev
34+
if cmp -s "$new" "$file"; then
35+
echo "dev already has these hashes"
36+
if [ -n "$pr" ]; then
37+
gh pr close "$pr" --repo "$GH_REPO" --delete-branch --comment "dev already has these hashes."
38+
fi
39+
exit 0
40+
fi
41+
42+
cp "$new" "$file"
43+
bash "$here/validate.sh" "$file"
44+
"${git[@]}" add -- "$file"
45+
"${git[@]}" commit --no-verify -m "$title"
46+
[ "$(git diff --name-only origin/dev HEAD)" = "$file" ] || {
47+
echo "::error::the commit changes more than $file" >&2
48+
exit 1
49+
}
50+
"${git[@]}" push --no-verify --force origin "HEAD:refs/heads/$branch"
51+
52+
if [ -z "$pr" ]; then
53+
gh pr create --repo "$GH_REPO" --base dev --head "$branch" --title "$title" \
54+
--body "Updates \`$file\` to the node_modules hashes computed for $GITHUB_SHA by bot-nix-hashes. check-bot-scope keeps this branch to that file."
55+
pr=$(find_pr)
56+
fi
57+
58+
# Pushes and pull requests made with GITHUB_TOKEN start no workflows; dispatches do. These produce the checks dev
59+
# requires on the new head.
60+
for workflow in check.yml check-test.yml check-bot-scope.yml; do
61+
gh workflow run "$workflow" --repo "$GH_REPO" --ref "$branch"
62+
done
63+
64+
gh pr merge "$pr" --repo "$GH_REPO" --auto --squash
65+
echo "### Nix hashes" >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
66+
echo "Updated pull request #$pr" >> "${GITHUB_STEP_SUMMARY:-/dev/null}"

‎.github/nix-hashes/validate.sh‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env bash
2+
# validate.sh <file>: fails unless <file> has exactly the shape of nix/hashes.json, so the hashes bot can only ever publish
3+
# three SRI hashes. Used by bot-nix-hashes.yml before it commits and by check-bot-scope.yml on the bot's pull request.
4+
# Adding a system to the flake means adding it to SYSTEMS here.
5+
set -euo pipefail
6+
7+
file=${1:?usage: validate.sh <file>}
8+
SYSTEMS='["aarch64-darwin","aarch64-linux","x86_64-linux"]'
9+
10+
fail() {
11+
echo "::error::$file: $*" >&2
12+
exit 1
13+
}
14+
15+
if [ ! -f "$file" ] || [ -L "$file" ]; then fail "not a regular file"; fi
16+
[ "$(wc -c < "$file")" -lt 4096 ] || fail "4096 bytes or larger"
17+
jq -se 'length == 1' "$file" > /dev/null 2>&1 || fail "not a single JSON value"
18+
jq -e --argjson systems "$SYSTEMS" '
19+
type == "object"
20+
and keys == ["nodeModules"]
21+
and (.nodeModules | type == "object")
22+
and (.nodeModules | keys == $systems)
23+
and (.nodeModules | all(.[]; type == "string" and test("\\Asha256-[A-Za-z0-9+/]{43}=\\z")))
24+
' "$file" > /dev/null || fail "expected {\"nodeModules\": {<$SYSTEMS>: \"sha256-<44 base64 chars>\"}}"
25+
# The canonical form rules out duplicate keys and anything else jq would drop on parse.
26+
jq . "$file" | cmp -s - "$file" || fail "not formatted as jq . prints it"
27+
echo "$file: valid"

‎.github/publish-python-sdk.yml‎

Lines changed: 0 additions & 71 deletions
This file was deleted.

‎.github/pull_request_template.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ Please provide a description of the issue, the changes you made to fix it, and w
1717

1818
### How did you verify your code works?
1919

20+
### Visual
21+
22+
_Optional, for user-facing changes: a small looping animated SVG (240×72) of the change, shown in the desktop app's "What's new in beta". Replace the block below or delete this section._
23+
24+
```svg
25+
```
26+
2027
### Screenshots / recordings
2128

2229
_If this is a UI change, please include a screenshot or recording._
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Download release artifacts
2+
description: Downloads artifacts into $RUNNER_TEMP/in/<name> and verifies each against its SHA256SUMS.
3+
inputs:
4+
pattern:
5+
description: Artifact name pattern
6+
required: true
7+
digests:
8+
description: Space-separated name=digest pairs for artifacts made by sign jobs, from their job outputs
9+
required: false
10+
default: ""
11+
runs:
12+
using: composite
13+
steps:
14+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
15+
with:
16+
pattern: ${{ inputs.pattern }}
17+
path: ${{ runner.temp }}/in
18+
- shell: bash
19+
run: bash "${GITHUB_ACTION_PATH//\\//}/../verify-artifacts.sh" "$RUNNER_TEMP/in"
20+
env:
21+
OPENCODE_DIGESTS: ${{ inputs.digests }}
File renamed without changes.

0 commit comments

Comments
 (0)