From 5ab79c73981bcc856a78d8d23206cd2e3714d5aa Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sat, 11 Apr 2026 14:45:50 +0800 Subject: [PATCH] Remove bundled app helper binaries --- CHANGELOG.md | 2 +- .../xworkmate-bridge-migration.md | 3 +- lib/runtime/aris_llm_chat_client.dart | 2 +- lib/runtime/embedded_agent_launch_policy.dart | 9 ++-- lib/runtime/go_acp_stdio_bridge.dart | 7 ++- lib/runtime/go_core.dart | 43 ++----------------- macos/Runner.xcodeproj/project.pbxproj | 21 --------- releases/v0.5/README.md | 2 +- scripts/embed-go-core-helper.sh | 35 --------------- scripts/package-flutter-mac-app.sh | 5 +-- .../embedded_agent_launch_policy_test.dart | 31 +++++++++++++ test/runtime/go_core_test.dart | 25 +++++++++++ 12 files changed, 74 insertions(+), 111 deletions(-) delete mode 100755 scripts/embed-go-core-helper.sh create mode 100644 test/runtime/embedded_agent_launch_policy_test.dart create mode 100644 test/runtime/go_core_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 921190b0..0d7e4220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,7 @@ - 任务列表按 `单机智能体 / 本地 OpenClaw Gateway / 远程 OpenClaw Gateway` 分组,保持极简列表布局。 - Multi-Agent 协作正式升级为 `Architect / Engineer / Tester`,并可选 `ARIS` 作为最强协作框架。 - ARIS bundle 作为只读资产内嵌进 App,`skills/` 直接复用 upstream,`llm-chat` 与 `claude-review` 切到 Go core。 -- `Ollama Cloud` 文案与默认地址统一,打包后的 `.app` 会随同分发 `xworkmate-go-core` helper。 +- `Ollama Cloud` 文案与默认地址统一,Go core 保持为包外开发态能力,不再内嵌进 `.app`。 ### Current Delivery Scope - 已交付:Single Agent streaming threads、OpenClaw 本地/远程任务线程、手动归档与持续会话恢复。 diff --git a/docs/architecture/xworkmate-bridge-migration.md b/docs/architecture/xworkmate-bridge-migration.md index 8deca5c7..53859776 100644 --- a/docs/architecture/xworkmate-bridge-migration.md +++ b/docs/architecture/xworkmate-bridge-migration.md @@ -29,13 +29,12 @@ The following app-side concerns remain in `xworkmate-app`: - Flutter UI and settings pages - ACP Bridge client-side configuration and secure-storage handling - Dart runtime launch/locator logic for the helper binary -- packaging logic that embeds the helper into the app bundle ## Build Contract `xworkmate-app` expects the helper artifact named `xworkmate-go-core`. -This is the current cross-repo runtime contract, not a legacy compatibility shim. The helper is built from `xworkmate-bridge` and consumed by `xworkmate-app`. +This is the current cross-repo runtime contract for local development flows, not a legacy compatibility shim. The helper is built from `xworkmate-bridge` and consumed by `xworkmate-app` outside the shipped app bundle. ## App Repository Changes diff --git a/lib/runtime/aris_llm_chat_client.dart b/lib/runtime/aris_llm_chat_client.dart index ef1673ae..a91435b2 100644 --- a/lib/runtime/aris_llm_chat_client.dart +++ b/lib/runtime/aris_llm_chat_client.dart @@ -97,7 +97,7 @@ class ArisLlmChatClient { isAppleHost: Platform.isIOS || Platform.isMacOS, )) { throw UnsupportedError( - 'App Store builds only allow the bundled Go core helper inside the app bundle.', + 'App Store builds do not allow launching local Go core processes.', ); } diff --git a/lib/runtime/embedded_agent_launch_policy.dart b/lib/runtime/embedded_agent_launch_policy.dart index b587df97..277da361 100644 --- a/lib/runtime/embedded_agent_launch_policy.dart +++ b/lib/runtime/embedded_agent_launch_policy.dart @@ -12,15 +12,12 @@ bool shouldBlockEmbeddedAgentLaunch({ } bool shouldBlockGoCoreLaunch( - GoCoreLaunch launch, { + GoCoreLaunch _, { required bool isAppleHost, bool? enabled, }) { - if (!shouldApplyAppleAppStorePolicy( + return shouldBlockEmbeddedAgentLaunch( isAppleHost: isAppleHost, enabled: enabled, - )) { - return false; - } - return launch.source != GoCoreLaunchSource.bundledHelper; + ); } diff --git a/lib/runtime/go_acp_stdio_bridge.dart b/lib/runtime/go_acp_stdio_bridge.dart index e16aa835..2163dcea 100644 --- a/lib/runtime/go_acp_stdio_bridge.dart +++ b/lib/runtime/go_acp_stdio_bridge.dart @@ -154,7 +154,7 @@ class GoAcpStdioBridge { isAppleHost: Platform.isIOS || Platform.isMacOS, )) { throw UnsupportedError( - 'App Store builds only allow the bundled Go core helper inside the app bundle.', + 'App Store builds do not allow launching local Go core processes.', ); } final process = await _processStarter( @@ -183,7 +183,10 @@ class GoAcpStdioBridge { ); }), ); - await request(method: 'acp.capabilities', params: const {}); + await request( + method: 'acp.capabilities', + params: const {}, + ); } void _handleStdoutLine(String line) { diff --git a/lib/runtime/go_core.dart b/lib/runtime/go_core.dart index 9688fae3..32ba2bd0 100644 --- a/lib/runtime/go_core.dart +++ b/lib/runtime/go_core.dart @@ -1,9 +1,6 @@ import 'dart:io'; -enum GoCoreLaunchSource { - bundledHelper, - buildArtifact, -} +enum GoCoreLaunchSource { buildArtifact } class GoCoreLaunch { const GoCoreLaunch({ @@ -35,11 +32,6 @@ class GoCoreLocator { final String Function()? _resolvedExecutableResolver; Future locate() async { - final bundled = await _bundledHelper(); - if (bundled != null) { - return bundled; - } - for (final root in _candidateRoots()) { final path = '$root/build/bin/xworkmate-go-core'; if (await _binaryExists(path)) { @@ -54,35 +46,6 @@ class GoCoreLocator { Future isAvailable() async => await locate() != null; - Future _bundledHelper() async { - final resolvedExecutable = - (_resolvedExecutableResolver?.call() ?? Platform.resolvedExecutable) - .trim(); - if (resolvedExecutable.isEmpty) { - return null; - } - final executableFile = File(resolvedExecutable); - final executableDirectory = executableFile.parent; - final contentsDirectory = executableDirectory.parent; - final macOsDirectoryName = executableDirectory.path - .split(Platform.pathSeparator) - .last; - final contentsDirectoryName = contentsDirectory.path - .split(Platform.pathSeparator) - .last; - if (macOsDirectoryName != 'MacOS' || contentsDirectoryName != 'Contents') { - return null; - } - final bundledPath = '${contentsDirectory.path}/Helpers/xworkmate-go-core'; - if (await _binaryExists(bundledPath)) { - return GoCoreLaunch( - executable: bundledPath, - source: GoCoreLaunchSource.bundledHelper, - ); - } - return null; - } - List _candidateRoots() { final roots = {}; final explicitRoot = _workspaceRoot?.trim() ?? ''; @@ -106,7 +69,9 @@ class GoCoreLocator { roots.addAll(_ancestorPaths(executableDirectory)); } - return roots.where((path) => path.trim().isNotEmpty).toList(growable: false); + return roots + .where((path) => path.trim().isNotEmpty) + .toList(growable: false); } List _ancestorPaths(Directory start) { diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index d996252d..8d54f9e0 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -248,7 +248,6 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - A1B2C3084F0A000100000001 /* Embed Bundled Go Core Helper */, 93B26977D4D2EC7AFAB54C8E /* [CP] Embed Pods Frameworks */, A1B2C3074F0A000100000001 /* Generate Missing Framework dSYMs */, ); @@ -454,26 +453,6 @@ shellScript = "/bin/bash \"${PROJECT_DIR}/../scripts/macos_generate_missing_dsyms.sh\"\n"; showEnvVarsInLog = 0; }; - A1B2C3084F0A000100000001 /* Embed Bundled Go Core Helper */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Embed Bundled Go Core Helper"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/bash \"${PROJECT_DIR}/../scripts/embed-go-core-helper.sh\" \"${TARGET_BUILD_DIR}/${WRAPPER_NAME}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/releases/v0.5/README.md b/releases/v0.5/README.md index 6e1acf5b..bd1169a7 100644 --- a/releases/v0.5/README.md +++ b/releases/v0.5/README.md @@ -18,7 +18,7 @@ - `assets/aris/skills` 继续直接复用 upstream `skills/` - `llm-chat` 与 `claude-review` 统一由 `xworkmate-go-core` 提供 -- macOS `.app` 会把 helper 打进 `Contents/Helpers/xworkmate-go-core` +- macOS App Store 交付不再在 `.app` 内嵌 `xworkmate-go-core` ## Validation diff --git a/scripts/embed-go-core-helper.sh b/scripts/embed-go-core-helper.sh deleted file mode 100755 index 3709decf..00000000 --- a/scripts/embed-go-core-helper.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -APP_BUNDLE_PATH="${1:-${APP_BUNDLE_PATH:-}}" -BRIDGE_BINARY_NAME="${BRIDGE_BINARY_NAME:-xworkmate-go-core}" -BRIDGE_BUILD_PATH="${ROOT_DIR}/build/bin/${BRIDGE_BINARY_NAME}" - -if [[ -z "$APP_BUNDLE_PATH" ]]; then - echo "Missing app bundle path for embedded go-core helper" >&2 - exit 1 -fi - -if [[ ! -d "$APP_BUNDLE_PATH" ]]; then - echo "App bundle does not exist: $APP_BUNDLE_PATH" >&2 - exit 1 -fi - -HELPERS_DIR="$APP_BUNDLE_PATH/Contents/Helpers" -HELPER_PATH="$HELPERS_DIR/$BRIDGE_BINARY_NAME" - -bash "$ROOT_DIR/scripts/build-go-core.sh" - -mkdir -p "$HELPERS_DIR" -ditto "$BRIDGE_BUILD_PATH" "$HELPER_PATH" -chmod +x "$HELPER_PATH" - -SIGN_IDENTITY="${EXPANDED_CODE_SIGN_IDENTITY:-${CODE_SIGN_IDENTITY:--}}" -if [[ -n "$SIGN_IDENTITY" && "$SIGN_IDENTITY" != "-" ]]; then - codesign --force --sign "$SIGN_IDENTITY" --timestamp=none "$HELPER_PATH" -else - echo "Skipping helper codesign: no explicit signing identity provided." -fi - -echo "Embedded go-core helper: $HELPER_PATH" diff --git a/scripts/package-flutter-mac-app.sh b/scripts/package-flutter-mac-app.sh index 855aa4cf..ada68925 100755 --- a/scripts/package-flutter-mac-app.sh +++ b/scripts/package-flutter-mac-app.sh @@ -102,13 +102,12 @@ bash "$ROOT_DIR/scripts/check-apple-export-compliance.sh" "$BUILD_APP_PATH" rm -rf "$DIST_APP_PATH" "$DIST_DMG_PATH" ditto "$BUILD_APP_PATH" "$DIST_APP_PATH" if [[ -n "$SIGN_IDENTITY" ]]; then - echo "Refreshing bundled helper and re-signing with explicit identity..." - XWORKMATE_SIGN_IDENTITY="$SIGN_IDENTITY" bash "$ROOT_DIR/scripts/embed-go-core-helper.sh" "$DIST_APP_PATH" + echo "Re-signing app bundle with explicit identity..." codesign --force --deep --sign "$SIGN_IDENTITY" \ --preserve-metadata=entitlements,requirements,flags,runtime \ --timestamp=none "$DIST_APP_PATH" else - echo "Preserving Flutter build output signature and embedded helper." + echo "Preserving Flutter build output signature." fi verify_bundle_signature "$DIST_APP_PATH" diff --git a/test/runtime/embedded_agent_launch_policy_test.dart b/test/runtime/embedded_agent_launch_policy_test.dart new file mode 100644 index 00000000..b44030c3 --- /dev/null +++ b/test/runtime/embedded_agent_launch_policy_test.dart @@ -0,0 +1,31 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:xworkmate/runtime/embedded_agent_launch_policy.dart'; +import 'package:xworkmate/runtime/go_core.dart'; + +void main() { + group('embedded agent launch policy', () { + test('blocks Go core launch for App Store policy on Apple hosts', () { + const launch = GoCoreLaunch( + executable: '/tmp/build/bin/xworkmate-go-core', + source: GoCoreLaunchSource.buildArtifact, + ); + + expect( + shouldBlockGoCoreLaunch(launch, isAppleHost: true, enabled: true), + isTrue, + ); + }); + + test('allows Go core launch when App Store policy is disabled', () { + const launch = GoCoreLaunch( + executable: '/tmp/build/bin/xworkmate-go-core', + source: GoCoreLaunchSource.buildArtifact, + ); + + expect( + shouldBlockGoCoreLaunch(launch, isAppleHost: true, enabled: false), + isFalse, + ); + }); + }); +} diff --git a/test/runtime/go_core_test.dart b/test/runtime/go_core_test.dart new file mode 100644 index 00000000..03783545 --- /dev/null +++ b/test/runtime/go_core_test.dart @@ -0,0 +1,25 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:xworkmate/runtime/go_core.dart'; + +void main() { + group('GoCoreLocator', () { + test( + 'finds workspace build artifact and never depends on app bundle helpers', + () async { + final locator = GoCoreLocator( + workspaceRoot: '/repo/app', + resolvedExecutableResolver: () => + '/repo/app/build/macos/Build/Products/Release/XWorkmate.app/Contents/MacOS/XWorkmate', + binaryExistsResolver: (path) async => + path == '/repo/app/build/bin/xworkmate-go-core', + ); + + final launch = await locator.locate(); + + expect(launch, isNotNull); + expect(launch!.executable, '/repo/app/build/bin/xworkmate-go-core'); + expect(launch.source, GoCoreLaunchSource.buildArtifact); + }, + ); + }); +}