mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 01:48:20 -10:00
fix(engine): keep injected-route config when preflight fails [skip ci]
Fixes the remaining hosted test failures on 549ff01ebb (CI 42381817495).
Runtime: a host-provided route with no preflighted client took its config
with mem::take before the optional credential preflight, so a failed
preflight left the route with a default config and lost its session
isolation and approval deadlines. It now clones, so the configured route
survives a failure. The existing privacy/timeout assertions are unchanged.
Exports: the four capabilities the runtime already serializes
(thread_scoped_git_writes, thread_scoped_snapshots, worktrees,
job_operations) are restored to the committed JSON schema, the TypeScript
declarations and the runtime SDK copy; the JSON matches the Rust-generated
contract.
Existing expectations realigned with shipped behavior: `present` is an eager
core tool (hook category stays other) in the five tool-surface tests; Native
extension entries accept .cjs and .cts; account logout is account-only and
keeps provider keys (two TUI tests renamed to say so). The router HTTP-status
unit test disables retry because it mounts a single response; runtime retries
are unchanged. THIRD_PARTY_NOTICES restores the semver 7.8.5 ISC text the
bundle-notice check requires. Both changelogs record the fixes.
Verified on this Mac: the 13 hosted failing cases, each run by exact name and
each executing exactly one test, 13/13 pass (RUST_MIN_STACK=8388608, hermetic
test home, locked/offline/-j1); clippy --lib --bins --all-features over 11
packages exit 0 with zero diagnostics; runtime SDK tests and strict TypeScript
passed earlier on these same bytes (exit 0); rustfmt --check and git diff
--check pass. The full suite runs in the final hosted validation.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ed2b69e4dd
commit
b175b1f9ed
14 files changed
+92
-15
No files matched your search
@@ -275,6 +275,10 @@ until the matching tag, packages, checksums, and release assets exist.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Host-provided model clients:** keep configured session isolation and
|
||||
approval deadlines when optional credential preflight fails.
|
||||
- **Runtime SDK exports:** include the existing thread-scoped Git, snapshot,
|
||||
worktree and job-operation capabilities in the committed schema and types.
|
||||
- **Plugin enablement revalidates its review:** enabling rechecks the staged
|
||||
snapshot and, under the existing state lock, refuses a changed review
|
||||
generation or trust receipt. The macOS stale-enable check passed (1/1);
|
||||
|
||||
@@ -933,6 +933,27 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
### `semver` 7.8.5
|
||||
|
||||
```text
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
```
|
||||
|
||||
## Optional compiled extension host
|
||||
|
||||
Node remains the default extension-host runtime. An explicitly installed
|
||||
|
||||
@@ -289,6 +289,25 @@
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"thread_scoped_git_writes": {
|
||||
"type": "boolean",
|
||||
"description": "Git stage, unstage, discard, commit, push and branch accept\n`?thread_id=` and act on that thread's workspace.",
|
||||
"default": false
|
||||
},
|
||||
"thread_scoped_snapshots": {
|
||||
"type": "boolean",
|
||||
"description": "`GET /v1/snapshots` and `POST /v1/snapshots/{id}/restore` accept\n`?thread_id=` and act on that thread's workspace.",
|
||||
"default": false
|
||||
},
|
||||
"worktrees": {
|
||||
"type": "boolean",
|
||||
"description": "`GET`/`POST /v1/worktrees` and `POST /v1/worktrees/remove`.",
|
||||
"default": false
|
||||
},
|
||||
"job_operations": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"thread_shell_consent": {
|
||||
"type": "boolean",
|
||||
"description": "Explicit per-thread shell opt-in is checked against loaded policy and\ncannot broaden a conversation while it has an active turn.",
|
||||
@@ -454,6 +473,10 @@
|
||||
"thread_idle_precondition",
|
||||
"thread_patch_undo_turn_id",
|
||||
"thread_scoped_reads",
|
||||
"thread_scoped_git_writes",
|
||||
"thread_scoped_snapshots",
|
||||
"worktrees",
|
||||
"job_operations",
|
||||
"thread_shell_consent",
|
||||
"turns",
|
||||
"turn_operation_idempotency",
|
||||
|
||||
@@ -62,6 +62,10 @@ export type RuntimeCapabilities = {
|
||||
thread_idle_precondition: boolean
|
||||
thread_patch_undo_turn_id: boolean
|
||||
thread_scoped_reads: boolean
|
||||
thread_scoped_git_writes: boolean
|
||||
thread_scoped_snapshots: boolean
|
||||
worktrees: boolean
|
||||
job_operations: boolean
|
||||
thread_shell_consent: boolean
|
||||
turns: boolean
|
||||
turn_operation_idempotency: boolean
|
||||
|
||||
@@ -2056,7 +2056,7 @@ impl Engine {
|
||||
let concrete_client = match route.take_preflighted_client() {
|
||||
Some(client) => Ok(client),
|
||||
None => {
|
||||
let config = std::mem::take(&mut route.config);
|
||||
let config = route.config.clone();
|
||||
match CodewhaleClient::capture_candidate_preflight(config, &route.candidate).await {
|
||||
Ok((config, captured)) => {
|
||||
route.config = config;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#[test]
|
||||
fn default_active_contract_keeps_discovery_and_core_tools_eager() {
|
||||
const EXPECTED_NATIVE: [&str; 15] = [
|
||||
const EXPECTED_NATIVE: [&str; 16] = [
|
||||
"read",
|
||||
"write",
|
||||
"edit",
|
||||
@@ -13,6 +13,7 @@ fn default_active_contract_keeps_discovery_and_core_tools_eager() {
|
||||
"get_goal",
|
||||
"update_goal",
|
||||
"task_shell_wait",
|
||||
"present",
|
||||
"load_skill",
|
||||
"context_archive_get",
|
||||
"context_archive_search",
|
||||
|
||||
@@ -113,6 +113,7 @@ fn first_turn_surface_is_stable_across_plan_work_and_operate() {
|
||||
"get_goal",
|
||||
"update_goal",
|
||||
"task_shell_wait",
|
||||
"present",
|
||||
"load_skill",
|
||||
"context_archive_get",
|
||||
"context_archive_search"
|
||||
@@ -127,6 +128,7 @@ fn first_turn_surface_is_stable_across_plan_work_and_operate() {
|
||||
"edit",
|
||||
"file_search",
|
||||
"load_skill",
|
||||
"present",
|
||||
"read",
|
||||
"task_shell_wait",
|
||||
"todo_write",
|
||||
@@ -152,6 +154,7 @@ fn first_turn_surface_is_stable_across_plan_work_and_operate() {
|
||||
"get_goal",
|
||||
"update_goal",
|
||||
"task_shell_wait",
|
||||
"present",
|
||||
"Git",
|
||||
"Run",
|
||||
"tasks",
|
||||
|
||||
@@ -1956,7 +1956,7 @@ fn native_entry_rule_fails_validation_when_the_host_is_enabled() {
|
||||
let errors = native_errors(®istry, name);
|
||||
assert_eq!(errors.len(), 1, "{name}: {errors:?}");
|
||||
assert!(
|
||||
errors[0].contains(".mjs, .js or .mts"),
|
||||
errors[0].contains(".mjs, .js, .cjs, .mts or .cts"),
|
||||
"{name}: {errors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6806,6 +6806,7 @@ command = "echo project"
|
||||
("get_goal", "other"),
|
||||
("update_goal", "other"),
|
||||
("task_shell_wait", "other"),
|
||||
("present", "other"),
|
||||
// Reads the skill registry, not caller-named paths, so it keeps
|
||||
// the classification it already had as a deferred tool. Making it
|
||||
// eager must not silently re-gate it.
|
||||
|
||||
@@ -3951,7 +3951,8 @@ mod decision_router_tests {
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let config = decision_config(&format!("{}/api/v1", server.uri()), false, 2);
|
||||
let mut config = decision_config(&format!("{}/api/v1", server.uri()), false, 2);
|
||||
config.retry = Some(toml::from_str("enabled = false").unwrap());
|
||||
let selection = route(&config, "Explain the diff", "").await;
|
||||
|
||||
assert_eq!(selection.model, "deepseek-v4-pro", "status {status}");
|
||||
|
||||
@@ -8883,6 +8883,7 @@ fn small_surface_starts_with_core_tools_and_read_only_goal_control() {
|
||||
"file_search",
|
||||
"get_goal",
|
||||
"load_skill",
|
||||
"present",
|
||||
"read",
|
||||
"task_shell_wait",
|
||||
"todo_write",
|
||||
@@ -9269,6 +9270,7 @@ fn small_surface_depth_cap_removes_only_agent() {
|
||||
"file_search",
|
||||
"get_goal",
|
||||
"load_skill",
|
||||
"present",
|
||||
"read",
|
||||
"task_shell_wait",
|
||||
"todo_write",
|
||||
|
||||
@@ -275,6 +275,10 @@ until the matching tag, packages, checksums, and release assets exist.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Host-provided model clients:** keep configured session isolation and
|
||||
approval deadlines when optional credential preflight fails.
|
||||
- **Runtime SDK exports:** include the existing thread-scoped Git, snapshot,
|
||||
worktree and job-operation capabilities in the committed schema and types.
|
||||
- **Plugin enablement revalidates its review:** enabling rechecks the staged
|
||||
snapshot and, under the existing state lock, refuses a changed review
|
||||
generation or trust receipt. The macOS stale-enable check passed (1/1);
|
||||
|
||||
@@ -6191,7 +6191,7 @@ context_window = 262144
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_logout_clears_api_key_state() {
|
||||
fn test_logout_starts_account_logout_without_clearing_api_key_state() {
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
@@ -6209,19 +6209,23 @@ context_window = 262144
|
||||
fs::write(&config_path, "api_key = \"test-key\"\n").unwrap();
|
||||
|
||||
let mut app = create_test_app();
|
||||
app.onboarding = OnboardingState::None;
|
||||
app.onboarding_needs_api_key = false;
|
||||
app.onboarding_missing_key_recovery = false;
|
||||
let result = logout_command(&mut app);
|
||||
assert!(result.message.is_some());
|
||||
assert_eq!(app.onboarding, OnboardingState::Provider);
|
||||
assert!(app.onboarding_needs_api_key);
|
||||
assert!(app.onboarding_missing_key_recovery);
|
||||
assert_eq!(result.action, Some(AppAction::OpenProviderPicker));
|
||||
assert!(!result.is_error);
|
||||
assert!(result.message.is_none());
|
||||
assert_eq!(app.onboarding, OnboardingState::None);
|
||||
assert!(!app.onboarding_needs_api_key);
|
||||
assert!(!app.onboarding_missing_key_recovery);
|
||||
assert_eq!(result.action, Some(AppAction::StartAccountLogout));
|
||||
|
||||
let updated = fs::read_to_string(config_path).unwrap();
|
||||
assert!(!updated.contains("api_key"));
|
||||
assert_eq!(updated, "api_key = \"test-key\"\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logout_clears_only_exact_named_custom_provider_key() {
|
||||
fn logout_preserves_named_custom_provider_keys() {
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
@@ -6245,10 +6249,15 @@ context_window = 262144
|
||||
|
||||
let result = logout_command(&mut app);
|
||||
|
||||
assert!(result.message.is_some());
|
||||
assert!(!result.is_error);
|
||||
assert!(result.message.is_none());
|
||||
assert_eq!(result.action, Some(AppAction::StartAccountLogout));
|
||||
assert_eq!(app.provider_identity_for_persistence(), "custom-a");
|
||||
let updated = fs::read_to_string(config_path).unwrap();
|
||||
assert!(!updated.contains("a-key"), "{updated}");
|
||||
assert!(updated.contains("b-key"), "{updated}");
|
||||
assert_eq!(
|
||||
updated,
|
||||
"[providers.custom-a]\napi_key = \"a-key\"\n\n[providers.custom-b]\napi_key = \"b-key\"\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+4
@@ -62,6 +62,10 @@ export type RuntimeCapabilities = {
|
||||
thread_idle_precondition: boolean
|
||||
thread_patch_undo_turn_id: boolean
|
||||
thread_scoped_reads: boolean
|
||||
thread_scoped_git_writes: boolean
|
||||
thread_scoped_snapshots: boolean
|
||||
worktrees: boolean
|
||||
job_operations: boolean
|
||||
thread_shell_consent: boolean
|
||||
turns: boolean
|
||||
turn_operation_idempotency: boolean
|
||||
|
||||
Reference in new issue
Block a user