chore: bump version to 9.7.0#1148
Merged
Merged
Conversation
…972307282 chore: sync main (v9.6.8) into beta
Use ScreenCapture.CaptureScreenshotAsTexture() for game_view screenshots when include_image=true and in Play mode. This captures the final composited frame including UI Toolkit overlays, which camera.Render() misses since UI Toolkit renders at the compositor level after camera rendering. The camera-based path is still used when a specific camera is requested or when not in Play mode. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
PlayMode tests require entering play mode which triggers a domain reload. On large projects this can take >15s, causing the hardcoded 15s init timeout to auto-fail the test job before tests actually start. This adds an `init_timeout` parameter to `run_tests` that flows through the Python server → C# RunTests handler → TestJobManager. When set, the per-job timeout overrides the 15s default. The value is persisted across domain reloads via SessionState. Changes: - Python: Add `init_timeout` param to `run_tests()` function signature - C# RunTests: Read `initTimeout` param and pass to `StartJob()` - C# TestJobManager: Per-job `InitTimeoutMs` field with fallback to `DefaultInitializationTimeoutMs` (15s), persisted in SessionState Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Clamp initTimeoutMs in StartJob: negative values → 0, cap at 600s - Python: reject init_timeout <= 0 with explicit error before calling Unity - Add 3 C# EditMode tests for per-job InitTimeoutMs behavior (custom timeout, default timeout auto-fail, persist/restore) - Add 4 Python tests for init_timeout forwarding and validation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused _originalJobs field - Add Assume.That guards for EditorApplication.isCompiling/isUpdating so the test is skipped (inconclusive) rather than producing misleading results when the editor is mid-compilation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The persist/restore test writes synthetic jobs to SessionState, which survives domain reloads and would be re-hydrated by TestJobManager's [InitializeOnLoadMethod] hook. Flush the cleaned in-memory state back to SessionState in TearDown so test artifacts don't leak across runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat: Add configurable init_timeout for PlayMode test initialization
…16604754 chore: update Unity package to beta version 9.6.9-beta.2
…33350008 chore: update Unity package to beta version 9.6.9-beta.3
1.Add Compat based scripts revolving around UnityCompatShims.cs, that will document our current API Compatibility changes in several files. 2.Add custom screenshot folder selection
…-2026-04-27 Update0503
…94688169 chore: update Unity package to beta version 9.6.9-beta.4
…-screenshot-capture fix: include UI Toolkit overlays in game_view screenshots with include_image
…94857075 chore: update Unity package to beta version 9.6.9-beta.5
* fix: align CaptureComposited with renamed Project*-folder API PR #1040 added CaptureComposited referencing the old Assets-folder names (CaptureFromCameraToAssetsFolder, AssetsRelativePath) and called PrepareCaptureResult without the now-required folderOverride argument. Renames into the Project* equivalents; same fix at the call sites in ManageScene.cs. Closes #1100 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: gate releases on test success and trigger tests on PRs Beta-release was publishing to PyPI in parallel with Unity Tests, so broken commits could ship if Unity tests failed (as happened with #1100 on 9.6.9-beta.5). Make publish/version-bump jobs depend on the test jobs in both beta-release.yml and release.yml. The whole release halts before any irreversible commit/tag/push if either test job fails. Also extends the test workflows to fire on PRs so failures are caught before merge: - python-tests: pull_request trigger; runs on every PR (no secrets). - unity-tests: pull_request_target [labeled] trigger gated on the safe-to-test label and on the PR being from a fork. Maintainers apply the label after reviewing the diff; the workflow then runs with UNITY_LICENSE in scope against the PR head SHA. Re-pushed commits do NOT auto-trigger; maintainer must remove and re-apply the label to re-run after additional review. In-repo PRs continue to be tested via the existing push trigger, so no labeling friction for collaborator branches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(screenshot): propagate folderOverride to composited and specific-camera paths Two pre-existing inconsistencies surfaced by CodeRabbit on #1103: 1. CaptureComposited dropped the caller's output_folder by hardcoding folderOverride: null in PrepareCaptureResult and the camera fallbacks. Adds the parameter to CaptureComposited's signature and plumbs it through both fallback paths. 2. The targetCamera and includeImage-in-play paths in ManageScene's game_view screenshot did not resolve cmd.outputFolder, so a request that selected a specific camera would always write to the default folder. Resolve via ScreenshotPreferences.Resolve as the other paths already do, and gate AssetDatabase.ImportAsset on IsUnderAssets so non-Assets folders don't trigger a futile import. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(unity-tests): harden pull_request_target and gate artifact upload Address CodeRabbit security review on #1103: - persist-credentials: false on the checkout step, so GITHUB_TOKEN is not written to disk and cannot be read by subsequent steps running PR-controlled code. - Explicit permissions: contents: read on the testAllModes job, scoping the workflow's token down from the default read/write set. - Skip upload-artifact when the main test step was skipped (e.g., because the preceding domain-reload step failed without continue-on-error). Avoids the noisy "No files were found" error on top of an already-failed run. The label-gated trigger plus these mitigations narrow the blast radius of the pull_request_target + checkout-PR-head pattern. The remaining trust boundary is the maintainer review before applying safe-to-test; documenting the review checklist (especially TestProjects/UnityMCPTests diffs) is a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…39361610 chore: update Unity package to beta version 9.6.9-beta.6
The (Type, bool includeInactive) overload guarded its modern-API branch with #elif UNITY_2023_1_OR_NEWER while the legacy reflection helper LegacyFindObjectsOfType is gated by #if !UNITY_2022_3_OR_NEWER. That left the entire Unity 2022.3.x band falling through to a legacy helper that the preprocessor had already excluded, producing CS0103: 'LegacyFindObjectsOfType' does not exist in the current context (e.g. on 2022.3.62f2, see #1105). The 3-arg FindObjectsByType(Type, FindObjectsInactive, FindObjectsSortMode) overload has been available since Unity 2022.2, so 2022.3 can use the modern API directly. Aligning the threshold with the rest of the file (2022_3_OR_NEWER) closes the gap with no API loss. Closes #1105 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both the push and pull_request_target paths filters listed MCPForUnity/Editor/** but not Runtime/, so a PR (or push) that modified only files under MCPForUnity/Runtime/** would not trigger Unity tests at all -- including the safe-to-test label flow on a fork PR. CodeRabbit flagged this as a Low/💤 nitpick on #1103; PR #1106 made it a concrete recurrence: applying safe-to-test had no effect because the Runtime-only diff was filtered out. Adding Runtime/** mirrors the asmdef layout (Editor and Runtime are the two assembly roots whose code can break Unity compilation), matches the codebase's "domain symmetry" convention, and closes the silent-skip path the label gate was designed to prevent. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…50517238 chore: update Unity package to beta version 9.6.9-beta.7
Multi-agent and sub-agent workflows churn through stdio connections — each new agent triggers a connect → close-stale → exit cycle, so the three associated McpLog.Info calls in StdioBridgeHost flooded the Unity console (the reporter saw 185+ lines in a single session). They're useful for debugging a single hung connection but pure noise during normal multi-agent operation. Move all three to always:false so they only surface when verbose logging is on, matching the existing recv-frame log on the same path. Lifecycle messages that fire once (startup, port switch) and warnings about anomalies (TCS timeout, queue eviction) are left at INFO.
Now that "Configure All Detected Clients" actually does what its name says (auto-rewrite + per-client transport coercion + IsInstalled filtering all landed in the recent client-config work), it's the path we want first-run users on — not buried at the bottom of the panel. Layout changes in McpClientConfigSection.uxml: - Move the Configure-All button to the top of the section, right under the "Client Configuration" header, with a one-line helper underneath. - Wrap the dropdown / status / single-client Configure button / Claude CLI path / project-dir / Manual Configuration foldout in a new "Configure a single client" foldout, collapsed by default. Persist its open/closed state via a new EditorPrefs key. Style changes in Common.uss: - New .primary-button class (bright green, 34px, bold) for the one-click action so it visually distinguishes itself from the regular blue .action-button rows. - Light/dark-aware foldout header styling for the new client-details foldout that matches the existing manual-command-foldout treatment.
…t setup" Helper line under the green button was visual noise — the button label already says what it does. "Configure a single client" was redundant inside a section titled "Client Configuration"; "Per-client setup" reads cleaner. Also drop the now-orphan .primary-button-hint style.
…allowlist
The whole manage_vfx tool dispatcher and every VfxGraph* helper is
gated behind '#if UNITY_VFX_GRAPH', but nothing in the Editor asmdef
ever defined that symbol. Users with com.unity.visualeffectgraph
properly installed (e.g. Unity 6.3 + VFX Graph 17.x) hit the false
"VFX Graph package (com.unity.visualeffectgraph) not installed" branch
for every action, including read-only ones like list_templates and
get_info — reported via Discord.
Add a versionDefines entry to MCPForUnity.Editor.asmdef so Unity sets
UNITY_VFX_GRAPH whenever the VFX Graph package is present at any
version (`expression: 0.0.0`). This is the canonical way to detect
optional packages and lets the existing #if branches do their job.
Also drop ValidateVfxGraphVersion's hard-coded {"12.1"} allowlist,
which only matches Unity 2022.3-era VFX Graph and would still block
CreateAsset on modern installs even after the compile-gate fix. The
asset-level APIs we touch (VisualEffectAsset, AssetDatabase.CopyAsset,
template enumeration via PackageInfo) are stable across the
12.x → 17.x range, so the safer guard is just "package present" with
the compile-time gate handling the real "not installed" path.
…ailure AddDependencyRow's click handlers flipped the button to "Installing..." and only restored it inside a synchronous try/catch. But the per-package call sites passed `() => InstallUpmPackage(...)` — no onComplete — so when the UPM AddAndRemove request eventually completed (success OR failure), nothing in PollUpmRequest's continuation ever told the button to revert. A network timeout on com.unity.cinemachine therefore parked the button on "Installing..." forever (Discord report); same for Removing... on uninstall. The "Install All" path was fine because it already threaded an onComplete callback through. Change the install/uninstall hooks to Action<Action>, thread a `restore` callback from the click handler into the UPM helpers, and invoke it from PollUpmRequest's completion regardless of StatusCode. Roslyn (the one synchronous install) just invokes the callback inline after Install returns. The unrelated CRLF/LF noise in the diff is a side effect of normalizing the file (988 CRLF lines, 41 LF lines pre-existing) onto the dominant CRLF convention while my edits were in flight.
Pre-existing CI ran only Unity 2021.3.45f2, so compile errors gated behind #if UNITY_2022_*_OR_NEWER / #if UNITY_6000_*_OR_NEWER slipped through (#1100, #1105). PRs now run only the floor version; pushes to beta, workflow_call from release pipelines, and manual dispatch run the wide matrix (2022.3.62f1, 6000.0.75f1, 6000.4.8f1 added). All four versions verified present as GameCI Docker images. The existing publish gate (update_unity_beta_version, publish_pypi_prerelease) already 'needs: [unity_tests]', so widening the matrix automatically widens the gate — no changes to beta-release.yml or release.yml. tools/check-unity-versions.{sh,ps1} let developers reproduce CI locally via Unity Hub install OR --docker mode (no install required, runs the same GameCI containers CI uses). Opt-in pre-push hook (tools/install-hooks.sh) runs the compile-only check when a push touches Unity code paths. Shared source of truth: tools/unity-versions.json — consumed by both the CI matrix preamble job and the local scripts. Coverage gap documented in JSON \$coverageGap: UNITY_6000_5_OR_NEWER and UNITY_6000_6_OR_NEWER branches in UnityObjectIdCompat.cs are not exercised because GameCI has not yet published 6000.5+/6000.6+ images. Bump the 'rolling' row when available.
- Local + Docker runners now pass -quit alongside -runTests in --full mode.
Unity batchmode test runs can hang on shutdown without -quit; this was a
real bug for anyone exercising the script's --full path.
- matrix job inherits the same `if:` gate as testAllModes so unauthorized
fork-PR label events don't spin up an unnecessary runner or check out fork
code.
- Hoist `github.event_name` and `github.ref` to step `env:` keys so the shell
body uses $EVENT_NAME / $GH_REF instead of inline `${{ ... }}` expansion.
Defense-in-depth against template-injection lint findings; behavior unchanged.
- pre-push hook falls back to git's empty-tree SHA
(4b825dc) when origin/beta and origin/main
are both unavailable, so the diff check runs instead of silently skipping
on shallow clones or freshly forked repos.
- Bash arg parser validates value-taking flags (--only, --docker-image-tag)
via a shared require_value() helper; missing or flag-as-value cases now
print a clear error and exit 2 instead of producing cryptic output.
- Replaced em-dashes in tools/check-unity-versions.ps1 with ASCII '--' so
Windows PowerShell 5 doesn't mis-decode them (the file is UTF-8 without
BOM and shipped with non-ASCII bytes).
- Adds a `run-wide-matrix` label trigger. Applying it to any PR (in-repo or
fork) causes pull_request_target to re-run the workflow with the full
4-version matrix. Mirrors the existing `safe-to-test` opt-in pattern.
- Fork PRs still need `safe-to-test` as the base gate for secret safety;
`run-wide-matrix` layers on top.
- In-repo PRs need only the new label (the push-event run already covered
the default leg).
- Adds `defaultVersion` field to tools/unity-versions.json and points it at
6000.0.75f1 (Unity 6.0 LTS). The narrow-matrix path now runs Unity 6 on
PRs and feature-branch pushes instead of the 2021.3 floor. The 'floor'
role still identifies the package minimum and is exercised in the wide
matrix; it just no longer doubles as the default-leg version.
- Documents both changes in docs/development/README-DEV.md.
Dry-run verification of the matrix selector:
- push to feature branch (no label) -> ["6000.0.75f1"]
- push to beta -> all 4 versions
- pull_request_target + WIDE_LABEL -> all 4 versions
ci: tier Unity test matrix + local parity check (#1107)
Previously the step only echoed the NUnit summary counts (e.g. '844 passed, 1 failed, 18 inconclusive, 46 skipped') and exited non-zero. To find out WHICH test failed, you had to download the editmode-results.xml artifact and parse it locally — see the post-merge beta run on #1139 where the only signal was 'Error: 1 test(s) failed' with no test name. This step now uses Python (preinstalled on ubuntu-latest) to parse the NUnit XML and, for each Failed test-case, emits: - A GitHub workflow annotation: ::error title=Failed: <fullname>::<first line of message> Renders as a clickable annotation on the run page. - A collapsible group with the full failure message + stack trace: ::group::Failure details — <fullname> Message: <full message> Stack trace: <full stack> ::endgroup:: Dry-run against the real editmode-results.xml from the run that triggered this fix (Unity 6000.4.8f1, ManageUITests.Create_Uss_SkipsUxmlValidation) confirmed the output is actionable without downloading the artifact. Behavior is unchanged on green runs: summary line, exit 0. The artifact upload is still performed (kept for deep diagnostics like the editmode.log).
Unity 6000.4's USS importer logs an [Error] for the deliberately-invalid
content the test passes ('This is not valid XML <broken>'), which NUnit's
LogAssert catches as an unhandled message and fails the test on. Older
Unity versions (2021.3 / 2022.3 / 6000.0) don't surface that log so the
test passed there — exactly the kind of version-specific regression the
new matrix is meant to catch (see #1139).
The test's contract is that ManageUI's create-USS path doesn't
pre-validate the content as UXML; that contract is independent of what
the asset importer does after the file lands on disk. Wrap the call in
LogAssert.ignoreFailingMessages = true / false, matching the existing
pattern used by Create_MissingNamespace_WritesWithWarning and
Create_WrongRootElement_WritesWithWarning in the same file.
Verified by replaying the failure XML from the post-merge run on #1139.
Two Copilot findings on #1140: 1. `set -euo pipefail` + `find ... | head -1`: if the artifacts path is missing (Unity crashed before producing any), `find` exits non-zero, `pipefail` propagates, and `set -e` aborts the step BEFORE reaching the explicit "No test results XML found" diagnostic — losing the clear error in the exact failure mode we wrote it for. Append `|| true` to the pipeline so the explicit empty check runs. 2. Workflow-command escaping: `name` and `first_line` come from the NUnit XML and are interpolated into `::error title=...::...` and `::group::...` lines. Under `pull_request_target` (fork-supplied code), unescaped `\n` / `%` / `::` in a test name or message could break annotation rendering or inject extra workflow commands. Add esc_data() (escapes %/CR/LF for data sections) and esc_prop() (additionally escapes :/, for command-property values) per GitHub's workflow-command spec, and apply them at every interpolation point. Verified with a hostile-input test: `Evil::group::injected\n` becomes `Evil::group::injected%0A` — no new line, no nested command. Real-XML dry-run output is identical for normal test names (no special chars in the existing failing case).
Shorter, less verb-y. Same semantics: applying the label to a PR opts into the 4-version matrix on the next pull_request_target event, on top of safe-to-test for fork PRs and standalone for in-repo PRs. Also renames the related internal identifiers for consistency: - WIDE_LABEL env var -> FULL_MATRIX_LABEL - "wide matrix" wording in comments + echo output -> "full matrix" - docs/development/README-DEV.md prose mirrors the new name Selector dry-run verified for both label states: pull_request_target + FULL_MATRIX_LABEL=true -> full matrix pull_request_target + FULL_MATRIX_LABEL=false -> default only
ci: rename 'run-wide-matrix' label to 'full-matrix'
ci: surface failing test details + fix Unity 6.4 USS log assertion
Closes the asymmetry between python-tests.yml (auto-fires on every PR via pull_request) and unity-tests.yml (only fires on labeled pull_request_target, or on push events). Same-repo PRs now get a unity-tests status check immediately on open; fork PRs also get the check but run in the fork's secret-less context, so the existing detect step writes unity_ok=false and the job exits clean with a "missing license secrets" notice. The status appears but signals the fork-PR contributor that a maintainer needs to apply 'safe-to-test' for a real run (existing gating pattern preserved). Three changes: 1. Add 'pull_request: branches: [main, beta]' to the workflow triggers with the same path filter as pull_request_target. The job-level if: gates already pass through non-pull_request_target events, so no gate edits are needed. 2. Extend the matrix selector to honor 'full-matrix' label on pull_request events too, not just pull_request_target. Lets in-repo PR contributors opt into the wide matrix at PR-open time without waiting for the labeled-pull_request_target event. 3. Add a workflow-level concurrency group keyed on `github.head_ref || github.ref`. Same-repo PRs would otherwise fire both push (on the branch SHA) and pull_request (on the PR SHA) and run the matrix twice; concurrency dedupes them. Selector dry-run across the seven trigger cases confirms correct behavior: default leg on unlabeled PR open / feature push; FULL on labeled PR open + pr_target / push to beta / workflow_call / workflow_dispatch. Doc update in docs/development/README-DEV.md explains the new PR status-check behavior and the fork-PR caveat.
ci: fire unity-tests on every PR (mirrors python-tests pattern)
…fety (PR review) Two correctness bugs flagged on PR #1142 (Copilot + CodeRabbit). CoerceTransportFor only treated `ConfiguredTransport.Http` as an HTTP choice, so a client that declares `SupportedTransports = { HttpRemote }` would get flipped to stdio whenever the user preferred HTTP. The fallback selection was also order-dependent (`supported[0]`), which could pick stdio over Http when the user wanted HTTP and the client supported both. Now: any HTTP variant satisfies an HTTP request, and when we must fall back we prefer the variant that matches user intent (HTTP family vs stdio). StartupConfigRewrite shared two problems with the CommandRegistry path from #1134: it fired in the AssetImportWorker subprocess too (where half-loaded domains can crash Mono on reflection and where writing client configs from a worker is plain wrong), and it skipped rewrite when in-memory Status was NotConfigured *before* refreshing from disk — on a fresh editor load that miscategorized clients that were already configured on disk and prevented the auto-rewrite. Added the same AssetImportWorker guard as #1134 (reflective lookup + cmdline fallback) and dropped the pre-refresh status skip so CheckStatus always reads the file before deciding.
…custom_tool (PR review) Two review nits from PR #1142. McpClientConfiguratorBase.IsInstalled defaulted to `true`, which meant any future configurator that derives directly from the base (without going through JsonFile/Codex/ClaudeCli) would be treated as "detected" by ConfigureAllDetectedClients and could end up writing config files for apps that aren't on the machine. Default to a cheap filesystem check via ParentDirectoryExists(GetConfigPath()); the three existing base classes that override with the same check are harmlessly redundant now, and CLI configurators (where GetConfigPath isn't a real path) keep their own overrides. execute_custom_tool declared `parameters: dict[str, Any] | None = None` in its signature but then rejected `None` at runtime with "parameters must be an object/dictionary". For parameter-less custom tools the type hint and the behavior contradicted each other. Coerce `None` to an empty dict; reject only genuinely-wrong types.
…ish (PR review) Four smaller items flagged on PR #1142. MCPSetupWindow.OnConfigureSelectedClicked marked setup as completed and closed the window even when the user hadn't ticked any client — clicking "Configure Selected" with everything unchecked would silently skip setup forever. Show a prompt and return early instead. README listed Claude Desktop under the HTTP-default group while a neighboring paragraph correctly says Claude Desktop is stdio-only; removed it from the HTTP list and pointed at the stdio block. StartupConfigRewrite_TypeExists only checked that the type resolved, not that it's public — added an explicit IsPublic assertion so the test matches its own message about the [InitializeOnLoad] requirement. ConfigureDetectedClientsTests calls the real ClientConfigurationService which walks McpClientRegistry and Configure()s every detected client — on CI this is harmless (no clients installed) but on a dev machine it mutates real user config files. Marked both tests [Explicit] until we DI the configurator list through the service.
feat+fix: one-click client connection + autotest bug-fix batch
…96034695 chore: update Unity package to beta version 9.6.9-beta.9
A push to beta (or main) that touches MCPForUnity/** or Server/** triggers
the test workflow via two paths:
push to beta ─┬─► unity-tests.yml (direct push trigger, branches: ["**"])
│
└─► beta-release.yml ──workflow_call──► unity-tests.yml
Both invocations land in GitHub's auto-generated concurrency group
`unity-tests-refs/heads/beta`, so the later one cancels the earlier with
the noisy "Canceling since a higher priority waiting request for
unity-tests-refs/heads/beta exists" annotation. The cancelled run shows
red on the Actions page even though the workflow_call sibling completed
fine.
Switch the direct push trigger to `branches-ignore: [beta, main]` on both
unity-tests.yml and python-tests.yml. Coverage on those branches is still
delivered through workflow_call from beta-release.yml / release.yml; we
just stop firing a duplicate run that gets killed by concurrency. PR
checks, feature-branch pushes, and manual workflow_dispatch invocations
are unaffected.
…ns-on-beta ci: stop firing unity-tests/python-tests twice on beta and main pushes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated version bump to 9.7.0.