mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-08-29 03:46:08 +08:00
Release v1.126.0
This commit is contained in:
@@ -81,10 +81,14 @@ jobs:
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
with:
|
||||
targets: x86_64-pc-windows-msvc
|
||||
|
||||
- name: Configure Windows MSVC linker
|
||||
shell: pwsh
|
||||
run: ./scripts/lib/windows-msvc-linker.ps1 -Target x86_64-pc-windows-msvc
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ jobs:
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -257,7 +257,7 @@ jobs:
|
||||
node-version: "22"
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -288,7 +288,7 @@ jobs:
|
||||
node-version: "22"
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -297,6 +297,9 @@ jobs:
|
||||
shared-key: quality-rust-full
|
||||
cache-on-failure: true
|
||||
|
||||
- name: Configure sandboxed rusty_v8 artifacts
|
||||
run: node scripts/lib/rusty-v8-artifacts.mjs --github-env
|
||||
|
||||
- name: Prepare sherpa-onnx runtime
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -330,7 +333,11 @@ jobs:
|
||||
node-version: "22"
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
|
||||
- name: Configure Windows MSVC linker
|
||||
shell: pwsh
|
||||
run: ./scripts/lib/windows-msvc-linker.ps1 -Target x86_64-pc-windows-msvc
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -342,8 +349,8 @@ jobs:
|
||||
- name: Configure sandboxed rusty_v8 artifacts
|
||||
run: node scripts/lib/rusty-v8-artifacts.mjs --github-env
|
||||
|
||||
- name: Check Windows app-server and code-mode-host sidecars
|
||||
run: cargo check --manifest-path lime-rs/Cargo.toml -p app-server --bin app-server -p tool-runtime --bin code-mode-host
|
||||
- name: Build Windows app-server and code-mode-host sidecars
|
||||
run: cargo build --manifest-path lime-rs/Cargo.toml --target x86_64-pc-windows-msvc -p app-server --bin app-server -p tool-runtime --bin code-mode-host
|
||||
|
||||
- name: Test Windows shell runtime fallback
|
||||
run: cargo test --manifest-path lime-rs/Cargo.toml -p lime-agent --test windows_shell_runtime -- --test-threads=1
|
||||
|
||||
@@ -78,6 +78,7 @@ jobs:
|
||||
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release edit "$TAG" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--target "$TARGET_REF" \
|
||||
--title "Lime $TAG" \
|
||||
--notes-file "$NOTES_FILE"
|
||||
echo "Release $TAG already exists; notes refreshed"
|
||||
@@ -143,10 +144,15 @@ jobs:
|
||||
cache-dependency-path: "pnpm-lock.yaml"
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Configure Windows MSVC linker
|
||||
if: matrix.host_platform == 'win32'
|
||||
shell: pwsh
|
||||
run: ./scripts/lib/windows-msvc-linker.ps1 -Target x86_64-pc-windows-msvc
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
@@ -691,10 +697,15 @@ jobs:
|
||||
node-version: "22"
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Configure Windows MSVC linker
|
||||
if: startsWith(matrix.target, 'x86_64-pc-windows-msvc')
|
||||
shell: pwsh
|
||||
run: ./scripts/lib/windows-msvc-linker.ps1 -Target x86_64-pc-windows-msvc
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
|
||||
@@ -5137,3 +5137,13 @@ resource verifier 校验双 binary。专项 `npm run smoke:code-mode-electron-ga
|
||||
`44203`。17 项 Gate B assertion 全通过,custom exec 两次 Responses 回采样、canonical `dynamicToolCall`、GUI final text、
|
||||
IPC trace 均成立,production mock/invoke/console/page/provider error 为零。该证据不冒充 live OpenAI 或 Windows packaged
|
||||
parity;后者由 release Windows runner 继续验证。
|
||||
|
||||
统一本地门禁收尾:`npm run verify:local` 首轮暴露三项既存事实源偏差。`useAgentChat` provider sync 回归在发送前
|
||||
settings RPC 永久 pending,却等待 `sendMessage()` 完成后才释放 promise,形成测试自锁;测试现改为先观察 turn 未提交,
|
||||
释放门禁后再等待发送完成。DevBridge 已退役 `pluginUiRuntime/start` 按 current 通用 App Server read policy 使用
|
||||
`30000ms`,旧断言仍保留 `5000ms`,现只同步事实期望。App Server 同时直接拥有 `RuntimeProviderProtocol` 映射,违反
|
||||
`model-provider` capability owner;canonical `ProtocolKind -> RuntimeProviderProtocol -> ProviderCapabilities` 转换现统一收回
|
||||
`model-provider::ProviderCapabilities::from_route`,未放宽治理白名单,`runtime.rs` 通过复用 current imports 保持原行数预算。
|
||||
修复后 `npm test -- --resume` 从第 50 批续跑至第 120 批全部通过;随后 fresh `npm run verify:local` 全绿,覆盖 120 个
|
||||
Vitest smart batches、App Server client `303` checks、13 个 current/反向依赖 Rust crate、真实 Electron/App Server GUI
|
||||
smoke、lint、typecheck、i18n、scripts/docs/version 门禁。Rust 仅保留既有 App Server test helper `dead_code` warning。
|
||||
|
||||
@@ -31,18 +31,31 @@ Electron 双 sidecar 资源链、协议/GUI 投影以及对应治理文档;完
|
||||
|
||||
- `npm run verify:app-version`:通过,版本事实源一致为 `1.126.0`。
|
||||
- `npm run typecheck`:通过(发布 metadata 更新后复跑)。
|
||||
- `npm run test:contracts`:通过;App Server client `301` 项检查及 command、harness、modality、scripts、Electron release workflow、docs boundary 子门禁全部通过。
|
||||
- `npm run test:contracts`:通过;App Server client `309` 项检查及 command、harness、modality、scripts、Electron release workflow、docs boundary 子门禁全部通过。
|
||||
- `npm run test:rust:related -- lime-rs/crates/agent-runtime lime-rs/crates/agent lime-rs/crates/app-server lime-rs/crates/model-provider lime-rs/crates/runtime-core lime-rs/crates/tool-runtime lime-rs/crates/services`:通过;相关 owner 与反向依赖 crate 单测无失败,存在一个既有测试辅助函数 dead-code warning。
|
||||
- `npm run smoke:agent-runtime-current-fixture`:通过;history/cache、turn terminal、approval、steer、Plan、Skills、MCP、媒体、Workbench 等 current Electron fixture 闭环通过,`liveProviderUsed=false`。
|
||||
- `npm run verify:gui-smoke`:通过;真实 Electron/App Server `1.126.0` 初始化、工作台 reload 与 memory settings 可见,evidence result 为 `pass`。
|
||||
- `npm run governance:legacy-report`:通过;扫描 `2120` 个源码文件,分类漂移 `0`、边界违规 `0`。
|
||||
- standalone Code Mode Cargo check:通过,无 warning。
|
||||
- Code Mode process Rust tests:`4/4` 通过。
|
||||
- sidecar/assets/fixture/Gate B script tests:`50/50` 通过。
|
||||
- Code Mode process Rust tests:`6/6` 通过。
|
||||
- sidecar/assets/fixture/package/Gate B script tests:`56/56` 通过。
|
||||
- `npm run electron:build:app-server-assets`:通过;macOS arm64 双 sidecar 为 `0755`,manifest 双 SHA 复算一致。
|
||||
- `npm run smoke:code-mode-electron-gate-b`:通过;thread `019ff39f-2ff2-7bb3-b579-cf1dc86ac042`,Electron/App Server/
|
||||
host PID 为 `10485/10494/10926`,host parent PID 为 `10494`,17 项 assertion 全通过。
|
||||
- `npm run smoke:code-mode-electron-gate-b`:通过;最新 rerun thread `019ff3ca-7f26-71d2-be81-6a16b7895515`,Electron/App Server/
|
||||
host PID 为 `44199/44203/44521`,host parent PID 为 `44203`,17 项 assertion 全通过。
|
||||
- `cargo fmt --all -- --check`、`git diff --check`:通过。
|
||||
- `npm run verify:local`:通过;fresh 120 个 Vitest smart batches、contracts、13-crate changed-scope Rust、真实 Electron/App Server GUI smoke、lint、typecheck、i18n、scripts/docs/version 门禁均通过;Rust 仅保留既有 App Server test helper `dead_code` warning。
|
||||
|
||||
## 已公开 Release 修复轮
|
||||
|
||||
- 首次公开提交为 `b48569b83b457eda1a9dac043d5ad4d470fc9e86`;Quality run `31563417376` 与 Release run `31563421104` 失败,GitHub Release 已创建但无 assets。用户明确要求保留 `v1.126.0`,不改发补丁版本。
|
||||
- Frontend Full:修复 provider settings Promise 未释放前等待 `sendMessage()` 导致的测试超时;`useAgentChat` 主套件 `186/186` 通过。
|
||||
- Rust Full:直接 workspace 测试前显式配置已校验 sandbox `rusty_v8` artifact,避免回退到不存在的上游 archive。
|
||||
- Windows:根工具链与目标 workflow 固定 Rust `1.95.0`,复用 `scripts/lib/windows-msvc-linker.ps1` 导出完整 MSVC/UCRT 环境并选择 `rust-lld.exe`;Quality 将双 sidecar 从 `cargo check` 提升为真实 `cargo build` 链接,不使用 `/FORCE:MULTIPLE`。
|
||||
- macOS package:`app-server` 与 `code-mode-host` 统一执行“manifest SHA 一致,或签名后通过 `codesign --verify --strict`”校验;非 macOS 和无效签名仍 fail closed,资源回归 `17/17` 通过。
|
||||
- 修复后本地门禁:`npm run verify:app-version`、`npm run typecheck`、`npm run test:contracts`、Rust related、`cargo fmt --all -- --check`、workflow YAML parse、Prettier、scripts governance 与 `git diff --check` 全通过。
|
||||
- 修复后 Gate B:`npm run verify:gui-smoke` 通过;run id `standalone-shell-01-20260812112209-14292`,真实 Electron/preload/IPC/App Server `1.126.0`、Workbench reload、Memory settings、21 项 assertion 全通过,mock/legacy hit 均为 `0`。
|
||||
- 本地无法证明 Windows MSVC 真链接与三平台 Forge 发布;重建 `v1.126.0` 后必须监控新的 Quality/Release run 到终态,并复核 Release assets。
|
||||
- 已有 GitHub Release 的 workflow 分支会同步刷新 `target_commitish`、标题与 release notes,避免同名 tag 重建后页面元数据仍指向旧提交。
|
||||
|
||||
## 待执行门禁
|
||||
|
||||
|
||||
@@ -128,6 +128,9 @@ pub use crate::file_checkpoint_snapshot::FileCheckpointSnapshotSaveRequest;
|
||||
pub use crate::file_checkpoint_snapshot::FileCheckpointSnapshotStore;
|
||||
pub use crate::file_checkpoint_snapshot::FilesystemFileCheckpointSnapshotStore;
|
||||
pub use crate::file_checkpoint_snapshot::NoopFileCheckpointSnapshotStore;
|
||||
use agent_runtime::{
|
||||
code_mode::RuntimeCodeModeServiceFactory, session_loop::RuntimeSessionRegistry,
|
||||
};
|
||||
pub use app_data::AppDataSource;
|
||||
pub use app_data::AutomationManagementAppDataSource;
|
||||
pub use app_data::AutomationOverviewAppDataSource;
|
||||
@@ -481,7 +484,7 @@ pub trait ExecutionBackend: Send + Sync {
|
||||
#[derive(Clone)]
|
||||
pub struct RuntimeCore {
|
||||
pub(in crate::runtime) state: Arc<Mutex<RuntimeCoreState>>,
|
||||
pub(in crate::runtime) session_loops: agent_runtime::session_loop::RuntimeSessionRegistry,
|
||||
pub(in crate::runtime) session_loops: RuntimeSessionRegistry,
|
||||
pub(in crate::runtime) turn_driver_completions: turn_execution::RuntimeTurnDriverCompletions,
|
||||
mailbox_trigger_flights: agent_mailbox_delivery::MailboxTriggerFlights,
|
||||
route_recovery: model_providers::RouteRecoveryCoordinator,
|
||||
@@ -513,7 +516,7 @@ pub struct RuntimeCoreEventAppender {
|
||||
event_log_writer: Option<Arc<EventLogWriter>>,
|
||||
trace_event_writer: Option<Arc<TraceEventWriter>>,
|
||||
projection_store: Option<Arc<ProjectionStore>>,
|
||||
session_loops: agent_runtime::session_loop::RuntimeSessionRegistry,
|
||||
session_loops: RuntimeSessionRegistry,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -546,12 +549,8 @@ impl Default for RuntimeCore {
|
||||
}
|
||||
|
||||
impl RuntimeCore {
|
||||
pub(crate) fn with_code_mode_factory(
|
||||
mut self,
|
||||
factory: agent_runtime::code_mode::RuntimeCodeModeServiceFactory,
|
||||
) -> Self {
|
||||
self.session_loops =
|
||||
agent_runtime::session_loop::RuntimeSessionRegistry::with_code_mode(factory);
|
||||
pub(crate) fn with_code_mode_factory(mut self, factory: RuntimeCodeModeServiceFactory) -> Self {
|
||||
self.session_loops = RuntimeSessionRegistry::with_code_mode(factory);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -594,7 +593,7 @@ impl RuntimeCore {
|
||||
) -> Self {
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(RuntimeCoreState::default())),
|
||||
session_loops: agent_runtime::session_loop::RuntimeSessionRegistry::default(),
|
||||
session_loops: RuntimeSessionRegistry::default(),
|
||||
turn_driver_completions: turn_execution::RuntimeTurnDriverCompletions::default(),
|
||||
mailbox_trigger_flights: agent_mailbox_delivery::MailboxTriggerFlights::default(),
|
||||
route_recovery: model_providers::RouteRecoveryCoordinator::default(),
|
||||
|
||||
@@ -14,7 +14,7 @@ use lime_agent::{
|
||||
};
|
||||
use lime_core::database::dao::api_key_provider::ProviderWithKeys;
|
||||
use model_provider::provider_capabilities::ProviderCapabilities;
|
||||
use model_provider::runtime_provider::{RuntimeProviderAuth, RuntimeProviderProtocol};
|
||||
use model_provider::runtime_provider::RuntimeProviderAuth;
|
||||
use model_provider::{ModelProviderProtocol, ModelRoute};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
@@ -107,16 +107,11 @@ fn intersect_resolved_provider_capabilities(
|
||||
provider.map(|provider| provider.provider.effective_provider_type().to_string())
|
||||
})
|
||||
.unwrap_or_else(|| selection.provider.clone());
|
||||
let provider_capabilities =
|
||||
RuntimeProviderProtocol::from_route_protocol(&resolved_route.protocol)
|
||||
.map(|protocol| {
|
||||
ProviderCapabilities::from_resolved_route(
|
||||
&provider_name,
|
||||
protocol,
|
||||
resolved_route.endpoint.base_url.as_deref(),
|
||||
)
|
||||
})
|
||||
.unwrap_or(ProviderCapabilities::NONE);
|
||||
let provider_capabilities = ProviderCapabilities::from_route(
|
||||
&provider_name,
|
||||
&resolved_route.protocol,
|
||||
resolved_route.endpoint.base_url.as_deref(),
|
||||
);
|
||||
|
||||
if provider_capabilities.custom_tools {
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::runtime_provider::{RuntimeProviderConfig, RuntimeProviderProtocol};
|
||||
use app_server_protocol::ProtocolKind;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ProviderCapabilities {
|
||||
@@ -25,7 +26,17 @@ impl ProviderCapabilities {
|
||||
Some(Self::from_resolved_route(provider_type, protocol, base_url))
|
||||
}
|
||||
|
||||
pub fn from_resolved_route(
|
||||
pub fn from_route(
|
||||
provider_name: &str,
|
||||
protocol: &ProtocolKind,
|
||||
base_url: Option<&str>,
|
||||
) -> Self {
|
||||
RuntimeProviderProtocol::from_route_protocol(protocol).map_or(Self::NONE, |protocol| {
|
||||
Self::from_resolved_route(provider_name, protocol, base_url)
|
||||
})
|
||||
}
|
||||
|
||||
fn from_resolved_route(
|
||||
provider_name: &str,
|
||||
protocol: RuntimeProviderProtocol,
|
||||
base_url: Option<&str>,
|
||||
@@ -142,33 +153,33 @@ mod tests {
|
||||
Some(ProviderCapabilities::NONE)
|
||||
);
|
||||
assert_eq!(
|
||||
ProviderCapabilities::from_resolved_route(
|
||||
ProviderCapabilities::from_route(
|
||||
"openai",
|
||||
RuntimeProviderProtocol::Responses,
|
||||
&ProtocolKind::OpenaiResponses,
|
||||
Some("https://api.openai.com/v1"),
|
||||
),
|
||||
expected.expect("official Responses capability")
|
||||
);
|
||||
assert_eq!(
|
||||
ProviderCapabilities::from_resolved_route(
|
||||
ProviderCapabilities::from_route(
|
||||
"openai",
|
||||
RuntimeProviderProtocol::ChatCompletions,
|
||||
&ProtocolKind::OpenaiChat,
|
||||
Some("https://api.openai.com/v1"),
|
||||
),
|
||||
ProviderCapabilities::NONE
|
||||
);
|
||||
assert_eq!(
|
||||
ProviderCapabilities::from_resolved_route(
|
||||
ProviderCapabilities::from_route(
|
||||
"gateway",
|
||||
RuntimeProviderProtocol::Responses,
|
||||
&ProtocolKind::OpenaiResponses,
|
||||
Some("https://api.openai.com/v1"),
|
||||
),
|
||||
ProviderCapabilities::NONE
|
||||
);
|
||||
assert_eq!(
|
||||
ProviderCapabilities::from_resolved_route(
|
||||
ProviderCapabilities::from_route(
|
||||
"azure-openai",
|
||||
RuntimeProviderProtocol::AzureResponses,
|
||||
&ProtocolKind::OpenaiResponses,
|
||||
Some("https://resource.openai.azure.com"),
|
||||
),
|
||||
ProviderCapabilities::NONE
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "1.95.0"
|
||||
components = ["clippy", "rustfmt", "rust-src"]
|
||||
@@ -7897,25 +7897,97 @@ const checks = [
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Windows workflows build both App Server sidecars with verified V8 inputs",
|
||||
files: [
|
||||
".github/workflows/build-windows-test.yml",
|
||||
".github/workflows/quality.yml",
|
||||
],
|
||||
name: "Windows test workflow builds both App Server sidecars with pinned Rust and shared linker inputs",
|
||||
file: ".github/workflows/build-windows-test.yml",
|
||||
snippets: [
|
||||
"dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0",
|
||||
"Configure Windows MSVC linker",
|
||||
"./scripts/lib/windows-msvc-linker.ps1 -Target x86_64-pc-windows-msvc",
|
||||
"Configure sandboxed rusty_v8 artifacts",
|
||||
"node scripts/lib/rusty-v8-artifacts.mjs --github-env",
|
||||
"code-mode-host sidecars",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Windows quality explicitly checks both App Server sidecar binaries",
|
||||
name: "Windows quality links both App Server sidecar binaries with pinned Rust and shared inputs",
|
||||
file: ".github/workflows/quality.yml",
|
||||
snippets: [
|
||||
"Check Windows app-server and code-mode-host sidecars",
|
||||
"dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0",
|
||||
"Configure Windows MSVC linker",
|
||||
"./scripts/lib/windows-msvc-linker.ps1 -Target x86_64-pc-windows-msvc",
|
||||
"Configure sandboxed rusty_v8 artifacts",
|
||||
"node scripts/lib/rusty-v8-artifacts.mjs --github-env",
|
||||
"Build Windows app-server and code-mode-host sidecars",
|
||||
"cargo build --manifest-path lime-rs/Cargo.toml --target x86_64-pc-windows-msvc -p app-server --bin app-server -p tool-runtime --bin code-mode-host",
|
||||
],
|
||||
absentSnippets: [
|
||||
"cargo check --manifest-path lime-rs/Cargo.toml -p app-server --bin app-server -p tool-runtime --bin code-mode-host",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Release workflow uses pinned Rust and shared Windows linker inputs",
|
||||
file: ".github/workflows/release.yml",
|
||||
snippets: [
|
||||
"dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0",
|
||||
"Configure Windows MSVC linker",
|
||||
"./scripts/lib/windows-msvc-linker.ps1 -Target x86_64-pc-windows-msvc",
|
||||
],
|
||||
absentSnippets: ["dtolnay/rust-toolchain@stable"],
|
||||
},
|
||||
{
|
||||
name: "Existing GitHub release is retargeted when a stable tag is rebuilt",
|
||||
file: ".github/workflows/release.yml",
|
||||
snippets: [
|
||||
'TARGET_REF="${{ github.event.inputs.source_ref || github.sha }}"',
|
||||
'gh release edit "$TAG"',
|
||||
'--target "$TARGET_REF"',
|
||||
'--notes-file "$NOTES_FILE"',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rust Full configures sandboxed V8 artifacts before testing the workspace",
|
||||
file: ".github/workflows/quality.yml",
|
||||
snippets: [
|
||||
"quality-rust-full",
|
||||
"Configure sandboxed rusty_v8 artifacts",
|
||||
"node scripts/lib/rusty-v8-artifacts.mjs --github-env",
|
||||
"npm run test:rust",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Repository Rust toolchain stays pinned to the Windows V8 release toolchain",
|
||||
file: "rust-toolchain.toml",
|
||||
snippets: [
|
||||
'channel = "1.95.0"',
|
||||
'components = ["clippy", "rustfmt", "rust-src"]',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Windows MSVC linker setup exports the complete SDK environment and rust-lld",
|
||||
file: "scripts/lib/windows-msvc-linker.ps1",
|
||||
snippets: [
|
||||
"VsDevCmd.bat",
|
||||
"-arch=x64 -host_arch=x64",
|
||||
'"INCLUDE"',
|
||||
'"LIB"',
|
||||
'"LIBPATH"',
|
||||
'"UCRTVersion"',
|
||||
'"WindowsSdkDir"',
|
||||
'throw "VsDevCmd.bat did not export $RequiredVariable"',
|
||||
"rust-lld.exe",
|
||||
'"CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=$Linker"',
|
||||
],
|
||||
absentSnippets: ["/FORCE:MULTIPLE"],
|
||||
},
|
||||
{
|
||||
name: "Rust changed-scope selection treats the root toolchain as workspace-wide",
|
||||
file: "scripts/lib/rust-test-scope-core.mjs",
|
||||
snippets: [
|
||||
'relPath === "rust-toolchain.toml"',
|
||||
'relPath === "rust-toolchain"',
|
||||
'relPath === "lime-rs/rust-toolchain.toml"',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Retired App Server agent backend crate stays deleted",
|
||||
file: "lime-rs/Cargo.lock",
|
||||
|
||||
@@ -119,9 +119,9 @@ function sha256(filePath) {
|
||||
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
|
||||
}
|
||||
|
||||
function isMacCodeSigned(filePath) {
|
||||
function isMacCodeSigned(filePath, execFileSyncImpl = execFileSync) {
|
||||
try {
|
||||
execFileSync("codesign", ["--verify", "--strict", filePath], {
|
||||
execFileSyncImpl("codesign", ["--verify", "--strict", filePath], {
|
||||
stdio: "ignore",
|
||||
});
|
||||
return true;
|
||||
@@ -130,13 +130,41 @@ function isMacCodeSigned(filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
function verifySidecarIntegrity(
|
||||
filePath,
|
||||
expectedSha256,
|
||||
{ platform, label, execFileSyncImpl = execFileSync },
|
||||
) {
|
||||
if (!/^[a-f0-9]{64}$/u.test(expectedSha256 ?? "")) {
|
||||
throw new Error(`${label} manifest sha256 is invalid`);
|
||||
}
|
||||
const packagedSha256 = sha256(filePath);
|
||||
const sha256Matches = expectedSha256 === packagedSha256;
|
||||
const signedMacSidecar =
|
||||
platform === "darwin" &&
|
||||
!sha256Matches &&
|
||||
isMacCodeSigned(filePath, execFileSyncImpl);
|
||||
if (!sha256Matches && !signedMacSidecar) {
|
||||
throw new Error(`${label} sha256 mismatch: ${filePath}`);
|
||||
}
|
||||
return {
|
||||
manifest: expectedSha256,
|
||||
packaged: packagedSha256,
|
||||
matches: sha256Matches,
|
||||
acceptedBecause: signedMacSidecar ? "macos-signed-sidecar" : "sha256",
|
||||
};
|
||||
}
|
||||
|
||||
function assertFile(filePath, label) {
|
||||
if (!existsSync(filePath) || !statSync(filePath).isFile()) {
|
||||
throw new Error(`${label} is missing: ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyResourceRoot(root, { platform, arch }) {
|
||||
export function verifyResourceRoot(
|
||||
root,
|
||||
{ platform, arch, execFileSyncImpl = execFileSync },
|
||||
) {
|
||||
const manifestPath = path.join(root, "app-server.release.json");
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
||||
const key = platformKey(platform, arch);
|
||||
@@ -156,19 +184,20 @@ function verifyResourceRoot(root, { platform, arch }) {
|
||||
codeModeHostBinaryName(platform),
|
||||
);
|
||||
assertFile(codeModeHostPath, "code-mode host sidecar");
|
||||
const codeModeHostSha256 = sha256(codeModeHostPath);
|
||||
if (artifact.codeModeHostSha256 !== codeModeHostSha256) {
|
||||
throw new Error(
|
||||
`code-mode host sidecar sha256 mismatch: ${codeModeHostPath}`,
|
||||
);
|
||||
}
|
||||
const sidecarSha256 = sha256(sidecarPath);
|
||||
const sha256Matches = artifact.sha256 === sidecarSha256;
|
||||
const signedMacSidecar =
|
||||
platform === "darwin" && !sha256Matches && isMacCodeSigned(sidecarPath);
|
||||
if (!sha256Matches && !signedMacSidecar) {
|
||||
throw new Error(`app-server sidecar sha256 mismatch: ${sidecarPath}`);
|
||||
}
|
||||
const codeModeHost = verifySidecarIntegrity(
|
||||
codeModeHostPath,
|
||||
artifact.codeModeHostSha256,
|
||||
{
|
||||
platform,
|
||||
label: "code-mode host sidecar",
|
||||
execFileSyncImpl,
|
||||
},
|
||||
);
|
||||
const appServer = verifySidecarIntegrity(sidecarPath, artifact.sha256, {
|
||||
platform,
|
||||
label: "app-server sidecar",
|
||||
execFileSyncImpl,
|
||||
});
|
||||
|
||||
for (const name of [
|
||||
"icon.png",
|
||||
@@ -190,13 +219,9 @@ function verifyResourceRoot(root, { platform, arch }) {
|
||||
resourceRoot: root,
|
||||
sidecarPath,
|
||||
codeModeHostPath,
|
||||
codeModeHostSha256,
|
||||
sha256: {
|
||||
manifest: artifact.sha256,
|
||||
packaged: sidecarSha256,
|
||||
matches: sha256Matches,
|
||||
acceptedBecause: signedMacSidecar ? "macos-signed-sidecar" : "sha256",
|
||||
},
|
||||
codeModeHostSha256: codeModeHost.packaged,
|
||||
codeModeHostIntegrity: codeModeHost,
|
||||
sha256: appServer,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
verifyElectronRuntimeBundles,
|
||||
verifyMacAppIdentity,
|
||||
verifyMacAppSignatures,
|
||||
verifyResourceRoot,
|
||||
} from "./verify-package-resources.mjs";
|
||||
|
||||
const tmpRoots = [];
|
||||
@@ -46,6 +48,58 @@ function createRuntimeBundleRoot({ main, preload }) {
|
||||
return root;
|
||||
}
|
||||
|
||||
function sha256(content) {
|
||||
return createHash("sha256").update(content).digest("hex");
|
||||
}
|
||||
|
||||
function createResourceRoot({
|
||||
appServer = "signed app-server",
|
||||
codeModeHost = "signed code-mode-host",
|
||||
manifestAppServer = "unsigned app-server",
|
||||
manifestCodeModeHost = "unsigned code-mode-host",
|
||||
platformKey = "darwin-arm64",
|
||||
} = {}) {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "lime-electron-resources-"));
|
||||
tmpRoots.push(root);
|
||||
const sidecarDir = path.join(root, "app-server", platformKey);
|
||||
const desktopAssetsDir = path.join(root, "desktop-assets");
|
||||
mkdirSync(sidecarDir, { recursive: true });
|
||||
mkdirSync(desktopAssetsDir, { recursive: true });
|
||||
const executableSuffix = platformKey.startsWith("win32-") ? ".exe" : "";
|
||||
writeFileSync(
|
||||
path.join(sidecarDir, `app-server${executableSuffix}`),
|
||||
appServer,
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(sidecarDir, `code-mode-host${executableSuffix}`),
|
||||
codeModeHost,
|
||||
);
|
||||
for (const name of [
|
||||
"icon.png",
|
||||
"trayTemplate.png",
|
||||
"trayTemplate@2x.png",
|
||||
"tray-running.png",
|
||||
"tray-stopped.png",
|
||||
"tray-warning.png",
|
||||
"tray-error.png",
|
||||
]) {
|
||||
writeFileSync(path.join(desktopAssetsDir, name), name);
|
||||
}
|
||||
writeFileSync(
|
||||
path.join(root, "app-server.release.json"),
|
||||
JSON.stringify({
|
||||
artifacts: [
|
||||
{
|
||||
platform: platformKey,
|
||||
sha256: sha256(manifestAppServer),
|
||||
codeModeHostSha256: sha256(manifestCodeModeHost),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
return root;
|
||||
}
|
||||
|
||||
function buildInfoPlist(entries) {
|
||||
return [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
@@ -124,6 +178,103 @@ describe("verify-electron-package-resources runtime bundles", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("verify-electron-package-resources sidecar integrity", () => {
|
||||
it("两个 sidecar 哈希一致时不调用 codesign", () => {
|
||||
const root = createResourceRoot({
|
||||
appServer: "app-server",
|
||||
codeModeHost: "code-mode-host",
|
||||
manifestAppServer: "app-server",
|
||||
manifestCodeModeHost: "code-mode-host",
|
||||
});
|
||||
|
||||
const result = verifyResourceRoot(root, {
|
||||
platform: "darwin",
|
||||
arch: "arm64",
|
||||
execFileSyncImpl: () => {
|
||||
throw new Error("unexpected codesign call");
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.sha256.acceptedBecause).toBe("sha256");
|
||||
expect(result.codeModeHostIntegrity.acceptedBecause).toBe("sha256");
|
||||
});
|
||||
|
||||
it("接受两个经过严格 codesign 验证的 macOS sidecar", () => {
|
||||
const root = createResourceRoot();
|
||||
const calls = [];
|
||||
|
||||
const result = verifyResourceRoot(root, {
|
||||
platform: "darwin",
|
||||
arch: "arm64",
|
||||
execFileSyncImpl: (...args) => calls.push(args),
|
||||
});
|
||||
|
||||
expect(result.sha256.acceptedBecause).toBe("macos-signed-sidecar");
|
||||
expect(result.codeModeHostIntegrity.acceptedBecause).toBe(
|
||||
"macos-signed-sidecar",
|
||||
);
|
||||
expect(calls).toEqual([
|
||||
[
|
||||
"codesign",
|
||||
["--verify", "--strict", expect.stringMatching(/code-mode-host$/u)],
|
||||
{ stdio: "ignore" },
|
||||
],
|
||||
[
|
||||
"codesign",
|
||||
["--verify", "--strict", expect.stringMatching(/app-server$/u)],
|
||||
{ stdio: "ignore" },
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it("拒绝哈希变化且严格 codesign 失败的 macOS code-mode host", () => {
|
||||
const root = createResourceRoot();
|
||||
|
||||
expect(() =>
|
||||
verifyResourceRoot(root, {
|
||||
platform: "darwin",
|
||||
arch: "arm64",
|
||||
execFileSyncImpl: () => {
|
||||
throw new Error("invalid signature");
|
||||
},
|
||||
}),
|
||||
).toThrow(/code-mode host sidecar sha256 mismatch/u);
|
||||
});
|
||||
|
||||
it("拒绝哈希变化且严格 codesign 失败的 macOS app-server", () => {
|
||||
const root = createResourceRoot({
|
||||
codeModeHost: "code-mode-host",
|
||||
manifestCodeModeHost: "code-mode-host",
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
verifyResourceRoot(root, {
|
||||
platform: "darwin",
|
||||
arch: "arm64",
|
||||
execFileSyncImpl: () => {
|
||||
throw new Error("invalid signature");
|
||||
},
|
||||
}),
|
||||
).toThrow(/app-server sidecar sha256 mismatch/u);
|
||||
});
|
||||
|
||||
it("非 macOS sidecar 哈希变化时不接受签名例外", () => {
|
||||
const root = createResourceRoot({ platformKey: "win32-x64" });
|
||||
let codesignCalled = false;
|
||||
|
||||
expect(() =>
|
||||
verifyResourceRoot(root, {
|
||||
platform: "win32",
|
||||
arch: "x64",
|
||||
execFileSyncImpl: () => {
|
||||
codesignCalled = true;
|
||||
},
|
||||
}),
|
||||
).toThrow(/code-mode host sidecar sha256 mismatch/u);
|
||||
expect(codesignCalled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("verify-electron-package-resources macOS app identity", () => {
|
||||
it("接受完整 Lime macOS app identity", () => {
|
||||
const root = createPackageRoot(cleanLimeInfoPlist());
|
||||
|
||||
@@ -27,6 +27,8 @@ function normalizeRepoPath(repoRoot, inputPath) {
|
||||
|
||||
function isWorkspaceWideRustPath(relPath) {
|
||||
return (
|
||||
relPath === "rust-toolchain.toml" ||
|
||||
relPath === "rust-toolchain" ||
|
||||
relPath === "lime-rs" ||
|
||||
relPath === "lime-rs/" ||
|
||||
relPath === "lime-rs/Cargo.toml" ||
|
||||
@@ -114,17 +116,17 @@ export function resolveRustPathSelection(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!relPath.startsWith("lime-rs")) {
|
||||
skippedPaths.push(relPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isWorkspaceWideRustPath(relPath)) {
|
||||
rustPaths.push(relPath);
|
||||
workspaceReasons.push(relPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!relPath.startsWith("lime-rs")) {
|
||||
skippedPaths.push(relPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isVendoredRustPath(relPath)) {
|
||||
rustPaths.push(relPath);
|
||||
workspaceReasons.push(`${relPath} (vendored Rust dependency)`);
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Target
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ($Target -ne "x86_64-pc-windows-msvc") {
|
||||
throw "Unsupported Windows MSVC target: $Target"
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($env:GITHUB_ENV)) {
|
||||
throw "GITHUB_ENV is required"
|
||||
}
|
||||
|
||||
$VsWhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
if (-not (Test-Path $VsWhere)) {
|
||||
throw "vswhere.exe not found at $VsWhere"
|
||||
}
|
||||
|
||||
$RequiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
|
||||
$InstallPath = & $VsWhere -latest -products * -requires $RequiredComponent -property installationPath 2>$null
|
||||
if (-not $InstallPath) {
|
||||
throw "Visual Studio with $RequiredComponent was not found"
|
||||
}
|
||||
|
||||
$VsDevCmd = Join-Path $InstallPath "Common7\Tools\VsDevCmd.bat"
|
||||
if (-not (Test-Path $VsDevCmd)) {
|
||||
throw "VsDevCmd.bat not found at $VsDevCmd"
|
||||
}
|
||||
|
||||
$VariablesToExport = @(
|
||||
"INCLUDE",
|
||||
"LIB",
|
||||
"LIBPATH",
|
||||
"PATH",
|
||||
"UCRTVersion",
|
||||
"UniversalCRTSdkDir",
|
||||
"VCINSTALLDIR",
|
||||
"VCToolsInstallDir",
|
||||
"WindowsLibPath",
|
||||
"WindowsSdkBinPath",
|
||||
"WindowsSdkDir",
|
||||
"WindowsSDKLibVersion",
|
||||
"WindowsSDKVersion"
|
||||
)
|
||||
|
||||
$EnvironmentLines = & cmd.exe /c ('"{0}" -no_logo -arch=x64 -host_arch=x64 >nul && set' -f $VsDevCmd)
|
||||
$VsDevCmdExitCode = $LASTEXITCODE
|
||||
if ($VsDevCmdExitCode -ne 0) {
|
||||
throw "VsDevCmd.bat failed with exit code $VsDevCmdExitCode"
|
||||
}
|
||||
|
||||
$ExportedVariables = @{}
|
||||
foreach ($Line in $EnvironmentLines) {
|
||||
if ($Line -notmatch "^(.*?)=(.*)$") {
|
||||
continue
|
||||
}
|
||||
|
||||
$Name = $Matches[1]
|
||||
$Value = $Matches[2]
|
||||
if ($VariablesToExport -contains $Name) {
|
||||
if ($Name -ieq "Path") {
|
||||
$Name = "PATH"
|
||||
}
|
||||
$ExportedVariables[$Name] = $Value
|
||||
"$Name=$Value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($RequiredVariable in @("INCLUDE", "LIB", "LIBPATH", "PATH", "UCRTVersion", "VCToolsInstallDir", "WindowsSdkDir")) {
|
||||
if ([string]::IsNullOrWhiteSpace($ExportedVariables[$RequiredVariable])) {
|
||||
throw "VsDevCmd.bat did not export $RequiredVariable"
|
||||
}
|
||||
}
|
||||
|
||||
$RustSysroot = (& rustc --print sysroot).Trim()
|
||||
$RustHostLine = & rustc -vV | Select-String "^host: "
|
||||
if (-not $RustHostLine) {
|
||||
throw "rustc did not report a host target"
|
||||
}
|
||||
$RustHost = $RustHostLine.Line.Substring(6).Trim()
|
||||
$Linker = Join-Path $RustSysroot "lib\rustlib\$RustHost\bin\rust-lld.exe"
|
||||
if (-not (Test-Path $Linker)) {
|
||||
throw "rust-lld.exe not found at $Linker"
|
||||
}
|
||||
|
||||
Write-Output "Using Windows linker: $Linker"
|
||||
"CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=$Linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
@@ -189,8 +189,9 @@ describe("useAgentChat 兼容接口 - provider sync", () => {
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
let sendPromise: Promise<void>;
|
||||
await act(async () => {
|
||||
await harness
|
||||
sendPromise = harness
|
||||
.getValue()
|
||||
.sendMessage(
|
||||
"切换到同 provider 的另一个模型,但 session 还没同步完",
|
||||
@@ -200,6 +201,7 @@ describe("useAgentChat 兼容接口 - provider sync", () => {
|
||||
false,
|
||||
"react",
|
||||
);
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(mockUpdateAgentRuntimeThreadSettings).toHaveBeenCalledWith({
|
||||
@@ -208,6 +210,13 @@ describe("useAgentChat 兼容接口 - provider sync", () => {
|
||||
model: nextModel,
|
||||
effort: null,
|
||||
});
|
||||
expect(mockSubmitAgentRuntimeTurn).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
(resolveProviderSync as (() => void) | null)?.();
|
||||
await sendPromise!;
|
||||
});
|
||||
|
||||
expect(mockSubmitAgentRuntimeTurn).toHaveBeenCalledTimes(1);
|
||||
expect(getSubmittedTurnStart()).not.toHaveProperty("provider");
|
||||
expect(getSubmittedTurnStart()).not.toHaveProperty("model");
|
||||
|
||||
@@ -818,7 +818,7 @@ describe("http-client", () => {
|
||||
],
|
||||
},
|
||||
}),
|
||||
).toBe(5000);
|
||||
).toBe(30000);
|
||||
expect(
|
||||
resolveBridgeRequestTimeoutMs("app_server_handle_json_lines", {
|
||||
request: {
|
||||
|
||||
Reference in New Issue
Block a user