fix(build): improve macOS packaging reliability and clean up Rust FFI
- Implement 'inside-out' signing strategy in package-flutter-mac-app.sh to fix nested code validity errors - Fix install_name of embedded FFI library to use @rpath for portability - Remove manual 'cargo build' triggers from Makefile and integration scripts (externalize management) - Clean up unused types and structs in Rust source (lib.rs and types.rs) - Update architecture docs to reflect AcpBridgeServerModeConfig priority logic
This commit is contained in:
parent
1f13ce7514
commit
1f6d8bd4e0
9
Makefile
9
Makefile
@ -129,11 +129,10 @@ check-export-compliance: ## Verify source and built Apple plist export-complianc
|
||||
rust-build: rust-build-release ## Build Rust FFI library (release mode)
|
||||
|
||||
rust-build-release: ## Build Rust FFI library for macOS (release)
|
||||
cd rust && cargo build --release
|
||||
@echo "Rust FFI library built successfully"
|
||||
@echo "Skip cargo build (external management)"
|
||||
|
||||
rust-build-debug: ## Build Rust FFI library in debug mode
|
||||
cd rust && cargo build
|
||||
@echo "Skip cargo build (external management)"
|
||||
|
||||
rust-test: ## Run Rust tests
|
||||
cd rust && cargo test
|
||||
@ -144,7 +143,7 @@ ffi-copy: ## Copy FFI library to macOS Frameworks
|
||||
ffi-generate: ## Generate FFI bindings using flutter_rust_bridge
|
||||
bash scripts/generate_ffi_bindings.sh
|
||||
|
||||
ffi-integrate: rust-build-release ffi-copy ## Build and copy FFI library (full integration)
|
||||
ffi-integrate: ffi-copy ## Copy FFI library (full integration)
|
||||
|
||||
# Build with FFI integration
|
||||
build-macos-ffi: rust-build-release ffi-copy build-macos ## Build macOS app with FFI integration
|
||||
build-macos-ffi: ffi-copy build-macos ## Build macOS app with FFI integration
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
# Settings Integration Configuration Model
|
||||
|
||||
Last Updated: 2026-04-14
|
||||
Last Updated: 2026-04-19
|
||||
|
||||
本文件记录当前 `Settings -> Integrations` 在主链中的职责边界。
|
||||
本文件记录当前 `Settings -> Integrations` 在主链中的职责边界,以及
|
||||
`acpBridgeServerModeConfig` 的有效配置仲裁规则。
|
||||
|
||||
## Current Rule
|
||||
|
||||
- Settings 只管理 bridge connection 参数与 account sync 元数据
|
||||
- Settings 只管理 Bridge 连接参数、account sync 元数据和本地编辑态
|
||||
- `AcpBridgeServerModeConfig.effective` 是运行时实际生效配置
|
||||
- `selfHosted` 优先级高于 `cloudSynced`
|
||||
- `cloudSynced` 只在 manual Bridge 未配置时作为有效回退来源
|
||||
- app 不从本地 endpoint preset、旧 module 配置、历史 fallback 恢复 provider catalog
|
||||
- `xworkmate-bridge` 是 provider catalog、gateway capability、routing resolve 的唯一真源
|
||||
- `xworkmate-bridge` 仍然是 provider catalog、gateway capability、routing resolve 的唯一真源
|
||||
|
||||
## Bridge-Owned Source Of Truth
|
||||
|
||||
@ -63,6 +67,9 @@ flowchart TD
|
||||
|
||||
- bridge host / transport / auth input
|
||||
- account-linked bridge configuration metadata
|
||||
- `acpBridgeServerModeConfig.cloudSynced`
|
||||
- `acpBridgeServerModeConfig.selfHosted`
|
||||
- `acpBridgeServerModeConfig.effective`
|
||||
- secure secret references
|
||||
- gateway connection test / connect / disconnect affordance
|
||||
|
||||
@ -75,6 +82,10 @@ flowchart TD
|
||||
|
||||
## Notes
|
||||
|
||||
- `AcpBridgeServerModeConfig` 的实际仲裁顺序是 `selfHosted -> cloudSynced -> default`
|
||||
- `selfHosted.isConfigured == true` 时,`effective.source == 'bridge'`
|
||||
- `selfHosted` 未配置且 `accountSyncState` 提供了可用云端桥接信息时,`effective.source == 'cloud'`
|
||||
- 两者都不可用时,`effective.source == 'default'`
|
||||
- 当前任务对话框 provider 选择主链固定为 `providerCatalogForExecutionTarget() -> resolveProviderForExecutionTarget() -> setAssistantProvider()`
|
||||
- `agent` catalog 只对应 bridge 广告的 ACP server bridges
|
||||
- `gateway` catalog 只对应 bridge 返回的 gateway provider 列表;当前为 `openclaw`,未来可扩展 `hermes` 等项
|
||||
@ -84,6 +95,37 @@ flowchart TD
|
||||
- bridge 若未返回 catalog,provider 菜单为空或禁用;app 不伪造 `codex / opencode / gemini / openclaw`
|
||||
- production provider / gateway 选择继续由 bridge 拥有,app 只保留消费与展示
|
||||
|
||||
## Effective Config Mermaid
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> EvaluateEffective
|
||||
|
||||
EvaluateEffective --> BridgeEffective: selfHosted.isConfigured == true
|
||||
EvaluateEffective --> CloudEffective: selfHosted 未配置 且 cloudSynced 可用
|
||||
EvaluateEffective --> DefaultEffective: 两者都不可用
|
||||
|
||||
BridgeEffective --> CloudEffective: 关闭 manual Bridge
|
||||
CloudEffective --> BridgeEffective: manual Bridge 配置生效
|
||||
CloudEffective --> DefaultEffective: cloud sync 失效
|
||||
DefaultEffective --> CloudEffective: cloud sync 恢复
|
||||
|
||||
note right of BridgeEffective
|
||||
source = bridge
|
||||
effective.endpoint = selfHosted.serverUrl
|
||||
end note
|
||||
|
||||
note right of CloudEffective
|
||||
source = cloud
|
||||
effective.endpoint = accountSyncState.syncedDefaults.bridgeServerUrl
|
||||
end note
|
||||
|
||||
note right of DefaultEffective
|
||||
source = default
|
||||
effective.endpoint = kManagedBridgeServerUrl
|
||||
end note
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Task Dialog Provider Selection Mainline](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/task-dialog-provider-selection-mainline.md)
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
Status: Implementing V1
|
||||
|
||||
Date: 2026-04-11
|
||||
Date: 2026-04-19
|
||||
|
||||
Scope:
|
||||
- `xworkmate-app`
|
||||
@ -10,9 +10,12 @@ Scope:
|
||||
|
||||
## V1 Decision
|
||||
|
||||
Production cloud mode is bridge-only:
|
||||
Production cloud mode is bridge-owned, with explicit effective-config priority:
|
||||
|
||||
- app-facing cloud endpoint is fixed to `https://xworkmate-bridge.svc.plus`
|
||||
- `selfHosted` manual Bridge configuration has highest priority
|
||||
- `cloudSynced` svc.plus configuration is the fallback when manual Bridge is not configured
|
||||
- `default` managed server is the last-resort fallback
|
||||
- app-facing cloud entry remains fixed to `https://xworkmate-bridge.svc.plus`
|
||||
- production provider catalog is bridge-owned
|
||||
- production gateway upstream is bridge-owned
|
||||
- account sync is metadata-only for session state, status, and managed secret references
|
||||
@ -71,6 +74,12 @@ The app only talks to:
|
||||
- may retain `openclawUrl` / `apisixUrl` as account profile metadata
|
||||
- does not overwrite executable cloud routing targets
|
||||
|
||||
`acpBridgeServerModeConfig.effective`
|
||||
|
||||
- represents the actual runtime source of truth
|
||||
- resolves to `selfHosted` first, then `cloudSynced`, then `default`
|
||||
- is what UI and runtime should read when deciding the active Bridge endpoint
|
||||
|
||||
`acpBridgeServerModeConfig.cloudSynced.remoteServerSummary.endpoint`
|
||||
|
||||
- represents bridge cloud entry only
|
||||
@ -78,6 +87,12 @@ The app only talks to:
|
||||
- is not an upstream provider URL
|
||||
- is not a gateway upstream URL
|
||||
|
||||
`acpBridgeServerModeConfig.selfHosted.serverUrl`
|
||||
|
||||
- represents the manual Bridge endpoint
|
||||
- overrides cloud-synced bridge endpoint when configured
|
||||
- is the first priority in effective-config resolution
|
||||
|
||||
## Workflow
|
||||
|
||||
```mermaid
|
||||
@ -99,6 +114,11 @@ flowchart TD
|
||||
APPLY --> SKIP1["do not overwrite gateway executable endpoint"]
|
||||
APPLY --> SKIP2["do not overwrite ACP executable endpoint"]
|
||||
|
||||
LOAD --> RESOLVE["resolveAcpBridgeServerEffectiveConfigInternal()"]
|
||||
RESOLVE --> BRIDGE["selfHosted.isConfigured == true"]
|
||||
RESOLVE --> CLOUD["cloudSynced usable"]
|
||||
RESOLVE --> DEFAULT["managed default fallback"]
|
||||
|
||||
UI --> BRIDGE_CAPS["acp.capabilities via bridge"]
|
||||
UI --> BRIDGE_ROUTE["xworkmate.routing.resolve via bridge"]
|
||||
UI --> BRIDGE_RUN["session.* via bridge"]
|
||||
@ -111,3 +131,5 @@ flowchart TD
|
||||
- account sync may update metadata, but not production execution targets.
|
||||
- gateway runtime status shown in the app must come from bridge runtime results.
|
||||
- bridge capability/provider availability shown in the app must come from `acp.capabilities`.
|
||||
- `effective` must never be inferred from stale local fallback state when `selfHosted` is configured.
|
||||
- cloud sync is allowed to coexist with manual Bridge config, but it does not outrank it.
|
||||
|
||||
@ -9,7 +9,7 @@ mod types;
|
||||
|
||||
pub use error::CodexError;
|
||||
pub use runtime::{CodexRuntime, CodexConfig, CodexConfigRust, ThreadHandle, RuntimeState};
|
||||
pub use types::{CodexResult, CodexMessage, CodexEvent};
|
||||
pub use types::{CodexResult, CodexEvent};
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
@ -33,19 +33,6 @@ impl CodexResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI-safe message type.
|
||||
#[repr(C)]
|
||||
pub struct CodexMessage {
|
||||
/// Message type (text, code, tool_call, etc.).
|
||||
pub message_type: *const c_char,
|
||||
/// Message content.
|
||||
pub content: *const c_char,
|
||||
/// Thread ID.
|
||||
pub thread_id: *const c_char,
|
||||
/// Turn ID.
|
||||
pub turn_id: *const c_char,
|
||||
}
|
||||
|
||||
/// FFI-safe event type.
|
||||
#[repr(C)]
|
||||
pub struct CodexEvent {
|
||||
@ -61,34 +48,6 @@ pub struct CodexEvent {
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
/// FFI-safe model info.
|
||||
#[repr(C)]
|
||||
pub struct CodexModelInfo {
|
||||
/// Model ID.
|
||||
pub id: *const c_char,
|
||||
/// Model name.
|
||||
pub name: *const c_char,
|
||||
/// Provider name.
|
||||
pub provider: *const c_char,
|
||||
/// Is online.
|
||||
pub is_online: bool,
|
||||
}
|
||||
|
||||
/// FFI-safe account info.
|
||||
#[repr(C)]
|
||||
pub struct CodexAccountInfo {
|
||||
/// Email.
|
||||
pub email: *const c_char,
|
||||
/// Plan type.
|
||||
pub plan: *const c_char,
|
||||
/// Has credits.
|
||||
pub has_credits: bool,
|
||||
/// Credits balance.
|
||||
pub credits_balance: f64,
|
||||
/// Rate limits JSON.
|
||||
pub rate_limits: *const c_char,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -12,9 +12,7 @@ echo "Integrating Rust FFI with Flutter..."
|
||||
# Build Rust library if not exists
|
||||
RUST_LIB="$PROJECT_ROOT/rust/target/universal/libcodex_ffi.dylib"
|
||||
if [[ ! -f "$RUST_LIB" ]]; then
|
||||
echo "Rust library not found, building..."
|
||||
# Attempt to build using Makefile target if available
|
||||
(cd "$PROJECT_ROOT" && make rust-build-release)
|
||||
echo "Rust library not found. Please build it manually or ensure it exists in target/."
|
||||
fi
|
||||
|
||||
# Ensure Frameworks directory exists
|
||||
@ -31,9 +29,6 @@ else
|
||||
if [[ -f "$ARM_LIB" ]]; then
|
||||
cp "$ARM_LIB" "$FRAMEWORKS_DIR/"
|
||||
echo "Copied arm64 library to $FRAMEWORKS_DIR/"
|
||||
else
|
||||
echo "Error: No Rust library found. Please run 'make rust-build-release' first."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@ -109,6 +109,8 @@ if [[ -f "$SOURCE_FFI_LIB" ]]; then
|
||||
echo "Embedding FFI library into app bundle..."
|
||||
mkdir -p "$(dirname "$TARGET_FFI_LIB")"
|
||||
cp "$SOURCE_FFI_LIB" "$TARGET_FFI_LIB"
|
||||
# Fix install name to be @rpath-based so it is portable within the bundle
|
||||
install_name_tool -id "@rpath/$(basename "$TARGET_FFI_LIB")" "$TARGET_FFI_LIB"
|
||||
fi
|
||||
|
||||
# Embed xworkmate-go-core for local/non-App-Store builds if available
|
||||
@ -140,18 +142,34 @@ validate_bundle_dependencies "$BUILD_APP_PATH"
|
||||
|
||||
rm -rf "$DIST_APP_PATH" "$DIST_DMG_PATH"
|
||||
ditto "$BUILD_APP_PATH" "$DIST_APP_PATH"
|
||||
if [[ -n "$SIGN_IDENTITY" ]]; then
|
||||
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 "Ad-hoc re-signing app bundle to account for manual additions..."
|
||||
codesign --force --deep --sign - \
|
||||
--preserve-metadata=entitlements,requirements,flags,runtime \
|
||||
--timestamp=none "$DIST_APP_PATH"
|
||||
|
||||
echo "Re-signing app bundle to account for manual additions..."
|
||||
# Components must be signed from inside out.
|
||||
# 1. Sign all dylibs and frameworks
|
||||
find "$DIST_APP_PATH/Contents/Frameworks" -name "*.dylib" -type f | while read -r dylib; do
|
||||
echo "Signing nested dylib: $dylib"
|
||||
codesign --force --sign "${SIGN_IDENTITY:--}" --timestamp=none "$dylib"
|
||||
done
|
||||
|
||||
find "$DIST_APP_PATH/Contents/Frameworks" -name "*.framework" -type d | while read -r framework; do
|
||||
echo "Signing nested framework: $framework"
|
||||
codesign --force --sign "${SIGN_IDENTITY:--}" --timestamp=none "$framework"
|
||||
done
|
||||
|
||||
# 2. Sign our manually added binaries if any
|
||||
if [[ -f "$DIST_APP_PATH/Contents/MacOS/build/bin/xworkmate-go-core" ]]; then
|
||||
echo "Signing embedded helper: xworkmate-go-core"
|
||||
codesign --force --sign "${SIGN_IDENTITY:--}" --timestamp=none "$DIST_APP_PATH/Contents/MacOS/build/bin/xworkmate-go-core"
|
||||
fi
|
||||
|
||||
# 3. Sign the main executable
|
||||
echo "Signing main executable..."
|
||||
codesign --force --sign "${SIGN_IDENTITY:--}" --timestamp=none "$DIST_APP_PATH/Contents/MacOS/$APP_NAME"
|
||||
|
||||
# 4. Finally sign the app bundle itself
|
||||
echo "Signing app bundle..."
|
||||
codesign --force --sign "${SIGN_IDENTITY:--}" --timestamp=none "$DIST_APP_PATH"
|
||||
|
||||
verify_bundle_signature "$DIST_APP_PATH"
|
||||
validate_bundle_dependencies "$DIST_APP_PATH"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user