Skip to content

0.10.1 integration: Engine convergence, reviewed TypeScript mods and Ratatui UX - #6815

Merged
Hmbown merged 187 commits into
mainfrom
wave/0.10.1-next
Oct 5, 2026
Merged

Hmbown merged 187 commits into
mainfrom
wave/0.10.1-next

Conversation

@Hmbown

@Hmbown Hmbown commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Codewhale uses one Rust Engine for execution, provider identity, permissions, events, sessions, storage and accounting. ACP, child agents and recursive RLM share its turn path; reviewed TypeScript mods reuse captured authority, cancellation, checkpoints, parent delivery and usage settlement. Node remains the default, Bun Native is opt-in, and Rust MCP remains the default backend. Session controls use the authenticated canonical owner, complete history and durable retry receipts.

The Ratatui components are mounted in Codewhale itself: composer, transcript, Tasks/Fleet workbars, approval controls, posture/metrics and guarded Ocean. The component gallery uses the same public components and genuine Rust renders, with desktop/narrow layouts, searchable details, shareable examples and reduced-motion controls. Homepage/product/guide and all 19 README languages use genuine native captures. Account-scoped ChatGPT sign-in source is integrated; live sign-in and plan inference remain unverified.

Original contributor history is preserved for #6819 by @Guan0923, #6829 and #6831 by @Lstarsky0, #6830 by @SparkofSpike, #6806 by @dependabot, and #6820 by @Guan0923. All three original #6820 author commits and all eighteen acceptance cases are retained.

The latest repairs (89ee629) target the previous head's hosted failures. Windows Native admission re-opened directories the grant walk already held with MAXIMUM_ALLOWED and read-only sharing, so every host launch hit a sharing violation; grant and retirement now extend the held pin chain and open only new child leaves, keeping no-follow, no-write/no-delete sharing, exact descendant validation, the traversal bound and no ACL propagation. Windows private-directory checks compare physical identity instead of lexical spelling, so 8.3 runner paths pass while replacement and reparse points are still refused. Nested extension-host dist files stay LF so materialized modules keep their admitted SHA-256. Platform fixtures keep their original assertions (canonical macOS credential homes, post-sync plugin view, OS transport kind, absolute ledger roots, the intentional Windows Fleet rename fence, the Windows unclassified-invocation floor, path-component roster checks, a real Windows launch plan, and a Linux check requiring a distinct kernel network namespace plus ECONNREFUSED). Safety, Lint, Ubuntu Test and release parity share one guarded GitHub-hosted Ubuntu headroom script; commands, filters, budgets and timeouts are unchanged. The CLI diagnostic no longer interpolates the credential line (CodeQL 541); CodeQL 533/540 were dismissed as flows from test-only MCP fixtures after the credential/proxy refusal tests passed.

The newest commit (e0a758f) follows the first hosted results. Hosted Safety reached its tests for the first time and found a real Linux defect: with Node 24.21 on x64 the host's watchdog Worker aborted at startup because each V8 isolate's code range counts in full against the 1 GiB RLIMIT_DATA cap; the watchdog now requests a 16 MiB code range (bundle rebuilt deterministically; one-line diff). Windows admission errors now name the failing step and the exact token flag or OS error, still fail-closed. The Runtime API test harness shuts the owner daemon down like the product, the scoped MCP test reports the server's error body, and Lint gets 75 minutes based on measured step times.

Hosted results on 89ee629: Test (ubuntu-latest) passed; Windows compiled the ACL and private-path repairs and dropped from 142 to 92 failures (ACL sharing violations gone; admission now stops at the LPAC token check, which never ran on hosted Windows before); Lint's runtime-contract budget passed for the first time, then the persistence-backlog budget hit the old 45-minute limit; Safety exposed the Node 24 defect above.

The newest commit (af5fa5c) follows hosted results on 02fcc3e, where the LPAC token check and the data-directory share conflict were confirmed fixed and the isolation probe ran inside the LPAC for the first time. Node then failed WSAStartup: an LPAC cannot open registry keys without the registryRead capability, so Native hosts now get exactly that one capability (the token check requires exactly it; network stays denied). Bun now reads an empty config from the granted directory instead of the NUL device, unprotected file ACL edits let Windows re-derive inherited entries, and ACL pointer reads are checked (CodeQL). Codewhale's own atomic writes on Windows rename through the temp handle, so they work beside Fleet's new ancestor pins (they failed with os error 32 during Fleet runs). The extension-host wire-JSON guard now refuses sparse arrays, getters and symbol keys (Copilot finding). Release notes add entries and credits for #6820, #6829, #6830, #6831 and #6827; the website credit-parity test enforced the matching website and docs credits.

Source15 validation: npm test and npm run check:web 1,185 passed / 0 failed / 7 skips; macOS extension_host 177/0, DSH 28/0, mcp 407/0, utils 38/0, tools::file 190/0; macOS release build plus npm wrapper smoke passed; Windows type-check clean. Windows runtime of the LPAC repairs is this head's hosted run.

The newest commit (02fcc3e) addresses what hosted Windows and macOS reported on e0a758f. Windows: the probe token is a genuine AppContainer, but the runner rejects the LPAC token-flag query (ERROR_INVALID_PARAMETER), so Codewhale now falls back to the token's WIN://NOALLAPPPKG attribute (aligned, bounds-checked; still fail-closed). ACL edits write the exact DACL with SetKernelObjectSecurity, which fixes inherited ACEs becoming explicit after grant/retire and never propagates; that lets ACL targets drop MAXIMUM_ALLOWED, whose DELETE right collided with a live host using the data directory as its working directory (also two sessions sharing one). macOS: memory-cap tests use production hang detection so the memory limit, not heartbeat supervision, stops the hog. Release notes: an independent claim-by-claim audit corrected 13 CHANGELOG statements and the matching docs (English and zh_hans); the Native sandbox sentences now match the source.

Hosted results on e0a758f: Safety, Lint (runtime-contract and persistence-backlog budgets), Test (ubuntu-latest), CodeQL and npm wrapper smoke passed; macOS 18,741 passed / 1 failed (the memory-cap race above); Windows 17,952 passed / 91 failed (the LPAC query and data-directory share causes above).

Source14 validation: npm test and npm run check:web 1,184 passed / 0 failed / 7 explicit skips; macOS memory-cap tests 9/9 across three runs and compiled/Node containment 3/3 with compiled host markers; Windows cargo check --target x86_64-pc-windows-gnu of codewhale-config and codewhale-tui lib and tests clean under deny(warnings); two adversarial reviews of the Windows changes; crates publish dry run packages 26 of 28 crates (codewhale-tui/cli need codewhale-ratatui 0.1.0 published first).

Validation (source13, 3,792 source/mode/absence rows; owning checkout HEAD and index unchanged):

  • npm test and npm run check:web: 1,184 passed / 0 failed / 7 explicit skips (wrapper 101, SDK 19, host 382, website 682); strict TypeScript and production web build passed.
  • macOS locked/offline all-features TUI build: runtime_api::tests::runtime_store_convergence 14 passed / 0 failed, and 8 host cases including the four that failed in hosted Safety: 8 passed / 0 failed. Source12's 24 focused cases (including three MCP credential/proxy refusal tests and compiled containment, memory and network markers) passed earlier.
  • Earlier proofs keep their original scopes: Source10 lint ratchets; Bun source suite 383 / 0 / 6 compiled-host skips; 28-crate publication-order graph; earlier focused native qualification 341 / 0 / 0 including all eighteen fix(tui): apply per-call execution policy to Python and JavaScript tools #6820 cases.

Open on this head: Windows LPAC admission (diagnostics land with this push), the Windows data-directory share conflict seen in DSH import tests, one Windows retirement test where a file's inherited ACEs become explicit after grant/retire (under investigation, not hidden), Node 24 on hosted Linux, the persistence budget, macOS hosted tests and native exports. Package/install qualification, real ChatGPT inference and native/IDE acceptance remain separate release gates. Durable Objects hosting is proposed and unimplemented. Source integration does not authorize deployment or publication.

Refs #6818
Refs #6816
Refs #6828
Fixes #6827

🤖 Generated with Claude Code

CodeWhale Bot and others added 22 commits September 28, 2026 21:02
The founder reported three problems with two ChatGPT accounts. The browser
was signed into one account and Codewhale's own ChatGPT sign-in held another
account that had run out of usage. Login gave no way to choose the account.
Nothing showed which account Codewhale was using. The usage-limit error did
not say which account hit the limit or how to switch.

Choose: the ChatGPT parameter row now sends prompt=login (OIDC Core 1.0
section 3.1.2.1) on the authorize URL, and the login prints a
private-window fallback. The only change is the row's authorize_extras;
refresh never visits the authorize endpoint.

Evidence for prompt=login, and its limits:
- openai/codex @ f53f5a6 codex-rs/login/src/server.rs build_authorize_url
  sends id_token_add_organizations, codex_cli_simplified_flow and originator,
  and no prompt. openai/codex issue #17092 "No way to switch accounts
  without clearing browser session" is still open. So upstream Codex does
  not use prompt, and openai/codex uses prompt=login only in generic OAuth
  and gateway test fixtures (oauth/client_tests.rs, gateway_auth_tests.rs).
- cnlimiter/codex-manager, a third-party multi-account tool,
  src/core/openai/oauth.py sends prompt=login to auth.openai.com
  /oauth/authorize with the same public client and parameters.
- auth.openai.com/.well-known/openid-configuration does not list
  prompt_values_supported.
- It was not tested against the live issuer. Unauthenticated curl probes
  of /oauth/authorize, with and without prompt, get a Cloudflare 403. If the
  issuer ignores prompt, the printed "open the URL in a private window"
  fallback still lets the user choose the account.

xAI's device flow already lets the user choose the account on the issuer's
page.

Replace: activate_login used to merge a new grant into the previous entry
for the same scope. When the new grant had no refresh token, id token or
account id, account A's value stayed next to account B's access token. A
login now writes a fresh entry. OAuthActivation reports the replaced
account.

Show: account_label_from_id_token decodes the ID token already stored with
the credential. It reads email or the https://api.openai.com/profile email,
plus the chatgpt_plan_type claim, entirely locally. It strips control
characters and caps the length. The label is display-only, so the
signature is not verified. Nothing new is stored because the label is
derived from the existing id_token. Where it appears:
- the end of login: "Signed in to ChatGPT as <email> (<plan>). Replaced
  the previous Codewhale ChatGPT sign-in (<old>)."
- the TUI transcript after /provider login
- the provider picker "Credential:" line
- `codewhale auth status` (active source for openai-codex, "signed-in
  account" for xai) and `codewhale auth list`

`codewhale auth list` also now reports the owned ChatGPT sign-in as
owned-oauth. Before, it printed "missing" for that row.

Switch: `auth chatgpt` and `auth xai-device` help now says "run again to
switch accounts", and status prints a switch-account line.

Usage limit: a 429 with {"error":{"type":"usage_limit_reached"}} (the
openai/codex codex-api/src/api_bridge.rs mapping) is now typed
QuotaExhausted, which is not retried. Before, it was a retryable
RateLimited. On a ChatGPT or xAI OAuth route, plan-quota errors now include
the signed-in account label and the exact switch command. The command is
`codewhale auth chatgpt` or `codewhale auth xai-device`. The guidance is
left out when a process token (OPENAI_CODEX_ACCESS_TOKEN) outranks the
sign-in.

Docs: docs/PROVIDERS.md, "Subscription sign-in accounts (ChatGPT, xAI)".

Known limits: a consented Codex CLI or Grok CLI import shows no account
label, because only Codewhale-owned files are read. The org title from
id_token_add_organizations is not shown.

Tests (targeted, local):
- codewhale-tui: 11 new or touched tests, "test result: ok. 11 passed;
  0 failed". Broader oauth:: / provider_picker:: / llm_client:: /
  credential_resolve / native_xai_oauth run: "test result: ok. 304 passed;
  0 failed".
- To show the fixes matter, I reverted each one (prompt=login, the
  usagelimitreached code, and the fresh-entry replacement). The 4
  regression tests then failed ("0 passed; 4 failed"); relogin failed on
  refresh-a surviving into account B's credential.
- codewhale-cli: "test result: ok. 26 passed; 0 failed" (owned / xai /
  codex / status / list filter, including the new
  owned_subscription_sign_ins_show_account_label_without_token_material).
- cargo fmt --all -- --check clean. cargo clippy -p codewhale-tui
  -p codewhale-cli --all-targets --all-features --locked with the CI flags
  (-D warnings plus the CI allow list) printed no warnings or errors.
  Blocking-call and dead-code budgets pass.

Refs #5778

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ZwqatxgVFxHvovngywnks
… one

Review follow-up for #6715.

- Quota guidance comes from the credential the client actually sends.
  ChatGPT: the label is read from the same credential snapshot
  (owned entry or consented Codex CLI file), so a stale owned generation
  that fell through to consent no longer names the owned account. xAI:
  guidance only when the resolver's source is the xAI OAuth branch
  (`XAI_OAUTH_KEY_SOURCE`); an `auth_mode = "oauth"` route that fell back
  to an API key gets none, and the label is kept only when it belongs to
  credentials holding the same token.
- Account labels use the runtime's usability test (fresh access token or
  a refresh token), shared with `credentials_valid`, so an expired entry
  with no refresh token names no account in the picker, `auth status` or
  `auth list`.
- A login replaces the whole owned sign-in: other scopes are no longer
  carried into the new generation, so an older account under a
  differently spelled issuer cannot outrank the new login or keep its
  refresh token on disk. `replaced` names the entry that was in use; a
  re-login as the same account says so and how to pick another.
- Guidance points to `/auth chatgpt` / `/auth xai-device` inside
  Codewhale and says to restart open sessions after a shell login.
- `usage_not_included` (plan without Codex) is classified with
  `usage_limit_reached` instead of retried as a rate limit.
- `prompt=login` moves to `account_choice_extras` with an opt-out,
  `CODEWHALE_CHATGPT_OAUTH_NO_PROMPT`.
- The xAI device flow prints the same account-choice hint as ChatGPT.
- Workspace-plan labels (team, business, enterprise, unknown) carry an
  8-character account-id prefix so two workspaces on one email differ.
- `auth status`: with an env token set, the switch line says to unset it
  first; login prints the same warning. xAI status reports why the
  account label could not be read, and no longer claims storage was
  unprobed on the line that now reads it.

Tests (targeted, CARGO_TARGET_DIR isolated):
- cargo test -p codewhale-tui --lib -- account_label usage_limit relogin
  stale_owned_entry authorize_url subscription_quota xai_oauth_mode
  chatgpt_usage_limit activation
  test result: ok. 46 passed; 0 failed
- cargo test -p codewhale-tui --lib -- oauth codex xai provider_picker quota
  test result: ok. 406 passed; 0 failed
- cargo test -p codewhale-cli --lib -- auth xai owned
  test result: ok. 44 passed; 0 failed
- New regressions relogin_drops_other_scopes_and_names_the_account_it_replaced,
  stale_owned_entry_names_no_account and
  xai_oauth_mode_that_fell_back_to_an_api_key_gets_no_sign_in_guidance
  fail with the fixes reverted (0 passed; 3 failed) and pass with them
  (3 passed; 0 failed).
- cargo fmt --all -- --check: clean.
Not run: npm test / check:web (no web surface touched), full workspace suite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ZwqatxgVFxHvovngywnks
Chain-segment ("<!-- scoped instructions: ... -->") and
<project_rule source=...> labels carried absolute paths into the pinned
system prompt, so a checkout move or recase changed the prompt prefix
and could emit a spurious <context_update> history append.

Render both labels repo-relative (git-root-relative, forward slashes),
falling back to workspace-relative for rules outside any checkout and
to the absolute spelling only for paths outside the label root
(unreachable by construction). The label root is computed once and
threaded through context_chain_dirs, so the chain bounds and the
chain-segment labels derive from one and the same git walk.

Regression tests pin repo-relative chain and rule labels, the
workspace-relative fallback without a git root, the absolute escape
hatch for unplaceable paths, and byte-identity of the whole system
block for the same tree checked out at two different locations.

Adapted from the Pinvou fork (Pinvou/CodeWhale 7f04c90, PR #70);
builds on the file-name labels from the previous commit.

Signed-off-by: asto18089 <asto18089@126.com>
Bring PR #6715 current with main (341 commits) for a clean CI run.
No conflicts.

Refs #6715

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
The launcher integration doc claimed rc.6 was the latest release; it now
states once that rc.6 (August 2026) is the last verified version, and what
the code does with other versions: older or no --patch is incompatible, a
newer parseable version is stale-version, and a version that does not parse
(for example 0.1.7-alpha.2, since only -rc.N suffixes parse) is offline and
refused. The authoring guide and the importer's module doc now separate the
static importer (never executes plugin code; TUI slash command and Runtime
API only, no CLI subcommand) from the launcher integration and from the
experimental extension host, and say which CI job exercises the pinned
five-file fixture.

Docs and one comment only; no cargo run. web/scripts/check-docs.mjs passes
but does not cover these files.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
The launcher integration parsed only MAJOR.MINOR.PATCH[-rc.N], so a current
DSH release such as 0.1.7-alpha.2 was reported as offline and refused. The
hand-rolled parser is replaced by the semver crate the tui crate already
uses; precedence comparison ignores build metadata. Such a version is now
stale-version (launchable once connected, unverified), like rc.7 was.
VERIFIED_DSH_VERSION, the --patch check and the offline mapping for text
that is not a version are unchanged; no newer version is claimed verified.

cargo test -p codewhale-tui --lib integrations::dsh: 42 passed; 0 failed
(run by the implementing agent; rustfmt check clean on both files). Full
gate not run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Cargo.toml declared rust-version 1.88, but a locked dependency
(serde-saphyr) requires 1.89 and CI's MSRV job builds 1.89. docs/INSTALL.md
already recorded that a 1.88 install of v0.10.0 fails. The declaration, the
English and Chinese install guides and the npm wrapper's build-from-source
hints now say 1.89.

cargo metadata --locked: ok. npm wrapper tests: 75 passed, 0 failed. No Rust
build run for this change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…ign-in summary

Review repair for #6715 (choose, show and switch ChatGPT and xAI accounts):

- Account labels come from the read that proved the sign-in usable:
  `usable_sign_in` replaces `credentials_present` in the credential resolver
  and `CredentialSource::OAuth` carries the label, so the provider picker
  opens no credential file of its own (`owned_account_label` and
  `get_xai_access_token` are removed).
- xAI quota guidance reads its account label from the credential the client
  sends (`active_route_api_key_with_xai_sign_in`), one read shared by the
  resolver, the guidance and the picker.
- `OAuthActivation::summary` / `env_override_warning` render through
  `tr(locale, MessageId::Auth*)`; the six keys ship in all 15 locale packs.
  The TUI passes its UI locale; shell commands pass `Locale::En` beside
  their English-only surrounding text.
- A new login replaces the whole generation (it holds only the new scope), so
  an older account's entry can no longer outrank it in `select_entry`.

Focused test results for this and the commits that follow are recorded in the
merge commit of origin/wave/0.10.1-next that follows them (this commit was
first authored as an untested WIP).

Refs #6715

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
`codewhale auth status xai` opens the owned generation to read the account
label, so "storage unprobed" misdescribed it. `auth get xai` and the
generation line now say the account label is read and only the token's
availability is not verified. The owned-OAuth status test pins both lines.

Focused test results are recorded in the merge commit that follows.

Refs #6715

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Plan-limit guidance names the account whose credential the client sends:
a consented Grok CLI import names the Grok file's account (the switch
command replaces the consent), and a ChatGPT process token gets no
guidance because a re-login would not change the sending account. The
Codex consented-import, xAI owned sign-in and xAI API-key fallback cases
were already pinned; the three provenance classes the review listed are
now all covered.

Focused test results are recorded in the merge commit that follows.

Refs #6715

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
`OAuthActivation::summary` substitutes `{provider}` and `{account}` into the
six Auth* messages, so a pack that drops a placeholder or ships English
would silently lose the account name or stay untranslated. The test checks
all complete packs for placeholder parity and non-English text.

Focused test results are recorded in the merge commit that follows.

Refs #6715

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…run)

An extension can register a slash command through the host's `commands`
service (registration kind `command`); invoking it sends `command/run` to
the host. A command returns text shown to the user, a prompt submitted as
the user's next message, or both; it never calls the model or a tool
itself, so anything that follows goes through the normal turn and its
approvals.

- A command cannot take a built-in's name or alias, or another plugin's
  command name (refused at registration with a reason). A markdown command
  wins a clash when the user registry loads.
- Registrations are owned like tools: removed on disable, crash and
  generation change; handles are never reused, so a stale reference fails
  rather than running a newer command.
- Result text is stripped of terminal escapes and bounded; an oversized
  prompt is refused, never truncated. The call is bounded by a 30 s
  deadline, then cancelled.
- DSH-style command registrations work unchanged.
- Protocol: generated TypeScript regenerated, corpus cases 37-45, the
  core-authority lint still passes.

Known limits (recorded in the design's "As built" section): no keypress
cancel before the deadline; TUI only (the Runtime API omits extension
commands); no agent/session handle or attachments for the handler.

Still behind `[features] extension_host` (experimental, off by default).

cargo test -p codewhale-tui --lib -- extension_host:: commands::user_registry
(real Node host, macOS): 85 passed; 0 failed. Host suite on Node 22.20:
79 passed, 1 skipped (Bun-only). Reported by the implementing agent and
not re-run by the committer: host suite on Bun 1.4.0, 80 passed. CI-policy
clippy not yet run on this commit; Linux and Windows unrun.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…o 15

Test infrastructure only; no production code and no visibility change in
crates/tui/src/mcp*. The suite is the gate a host-backed MCP dispatch must
pass before the Rust pool can be removed (CURRENT_DECISIONS section 26).

New cases pin, for the Rust pool: stdio over a real child (exit mid-call is
"outcome unknown, not retried", next call reconnects), Streamable HTTP
session lifecycle and stale-session replay, an unsupported protocol version,
catalog pagination and the three catalog budget caps, approval hints,
needs-auth on 401, a bearer that is sent but never recorded, refusal of a
cross-origin redirect, and tool and server deny rules. Each has a negative
control, and three deviant dispatches (replays a failed call, refreshes its
own catalog, leaks the bearer) are caught.

The dispatch trait gains `approval_hint`, the factory takes
`DispatchSetup { config, disallowed_tools }`, and `boot` may fail while
`catalog`/`call` still answer.

Recorded as current behaviour: `notifications/tools/list_changed` is
ignored by the Rust pool (the catalog refreshes on reconnect only).
Not covered: real OAuth login, legacy SSE transport, concurrent calls, the
32 MiB aggregate cap, reviewed-launch hash refusal for stdio; stdio cases
are skipped off Unix. The fixtures README still says two transcripts.

cargo test -p codewhale-tui --lib -- conformance:: (macOS): 32 passed;
0 failed. Blocking-call and dead-code budgets pass (implementing agent's
run). CI-policy clippy not yet run on this commit.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Brings the branch up to date with main, which now carries #6737 (the
file-name labels this PR was stacked on) as merge 1d42af9.

One conflict, in the import block of crates/tui/src/project_context.rs:
this branch's side imported project_instructions_source_label (already
present on main's side, after the #6737 rustfmt-ordering fix) and added
repo_relative_source_label. Resolved by keeping main's single
`pub(crate) use ...project_instructions_source_label;` and this PR's
`use ...repo_relative_source_label;`, dropping the duplicate re-export.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Merges 9626357 (the 0.10.1 wave) into the #6715 review repair. One textual
conflict, crates/localization/locales/zh-Hant.json, resolved keeping both
sides: this branch's six Auth* sign-in keys and the wave's TranscriptThought /
TranscriptThoughtFor keys (all 15 packs now hold 2446 keys). The wave's own
fix for the Rust 1.99 `fetch_update` deprecation removes the two build errors
this branch hit on the stable 1.99 toolchain. No semantic break surfaced: the
wave's oauth.rs-adjacent, credentials and endpoint-boundary changes compiled
and passed against the account-label work.

Evidence on the merged tree, via cargowhale, warnings as errors:
- cargo fmt --all -- --check: clean
- cargo clippy -p codewhale-tui -p codewhale-cli -p codewhale-localization
  --all-targets --all-features --locked -- -D warnings -A
  clippy::uninlined_format_args -A clippy::too_many_arguments -A
  clippy::unnecessary_map_or: exit 0 (after the type alias in the next
  commit; the merged tree alone tripped clippy::type_complexity)
- cargo test -p codewhale-tui --lib -- oauth:: credential_resolve
  credentials:: provider_picker quota_guidance quota_errors sign_in_guidance
  relogin_ activation_ xai_ auth_: 408 passed, 0 failed
- cargo test -p codewhale-cli --lib -- xai_ auth_ owned_oauth: 33 passed, 0
  failed
- cargo test -p codewhale-localization: 53 passed, 0 failed
- Regression proof: with activation reverting to `previous_file.clone()` +
  `remove(scope)` and `summary()` forced to Locale::En, `cargo test -p
  codewhale-tui --lib -- relogin_` gives 0 passed, 3 failed
  (relogin_under_another_client_id_switches_runtime_credentials_and_label:
  left "access-a" right "access-b"; relogin_with_another_account_replaces_the_owned_entry:
  localized summary equals English; relogin_drops_other_scopes_and_names_the_account_it_replaced).
  Restored, the same filter passes.
- python3 scripts/check-blocking-calls-budget.py: within budget (707 sites);
  check-dead-code-budget.py and check-provider-registry.py pass.

Local tests only; no hosted CI, provider call or deploy was run.

Refs #6715

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…er read

`active_route_api_key_with_xai_sign_in` tripped clippy::type_complexity under
CI's -D warnings; `ResolvedApiKey` and `XaiSignInLabel` aliases name its parts.
The provider picker comment records the known limit for the account label:
it adds no credential read (the resolver's structural read, formerly
`credentials_present`, already ran synchronously when a row is built before
#6715), and the blocking-calls ratchet stays within budget.

Verified by the clippy, test and ratchet runs listed in the merge commit
that precedes this one (re-run after this change: clippy exit 0; tui 408,
cli 33 and localization 53 passed, 0 failed).

Refs #6715

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…y, linked state and file-mode fixes

- Task workers (#6728, #6573): an idle worker with nothing claimable naps
  1 s instead of stat-ing the queue every 200 ms (about 10 stats a second
  with two workers, which 0.9.12 did not do). Pending work and in-process
  notifies are unchanged. A settled queue now notices another process's
  write within about 1 s rather than 200 ms.
- Task store lock (#6573): the holder records its pid and acquisition time
  in the lock file, so "busy" names the holder and how long it has held the
  lock when that can be read. The repeated "Task claim unavailable" error is
  logged once per episode, then at debug.
- Web search (#6746): when the Bing fallback is allowed, DuckDuckGo gets
  60% of the budget and Bing the rest, so a hanging DuckDuckGo no longer
  starves the fallback. A custom search_base_url still has no public
  fallback and keeps the full budget.
- PowerShell (#6745): CODEWHALE_POWERSHELL_EXECUTION_POLICY=inherit omits
  the process-scope -ExecutionPolicy Bypass. The default is unchanged.
  Documented in ENVIRONMENTS (en, zh_hans). Not run on Windows.
- Linked .codewhale: the workflow journal creates nothing until a record is
  appended and appends through the confined helpers; the coordination lock
  rejects a linked state path before creating directories.
- File modes: the runtime log is created 0600 without following symlinks
  and an existing log is tightened; .reconcile.lock and current.json.lock
  reuse the private lock-file opener. fleet.lock was already 0600; a test
  pins it.

Verified on macOS, Rust 1.99, together with the UI fixes in the next
commit: cargo fmt --all --check clean; CI-policy clippy on codewhale-tui
and codewhale-localization clean; focused tui lib tests 518 passed, 0
failed, 3 ignored; blocking-call budget within budget (one ratchet entry
tightened for journal.rs); dead-code budget at budget. Full suite and
other platforms not run locally.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e revision; short paste toast

- #6800: a locally cancelled or stall-recovered turn cancels its in-flight
  dispatch instead of leaving input blocked for up to the 60 s dispatch
  bound. The cancel goes through the existing dispatch-failure path, so the
  unsent prompt returns to the composer.
- /edit: when the rollback is refused, the revised text is restored to the
  composer with edit mode re-armed instead of being dropped; opening /edit
  while a queued draft is being edited returns that draft to the queue
  first.
- Oversized paste: the toast is a short sentence that fits, and the full
  message with the write error goes to the transcript once. New key in all
  15 locales.
- #6652: the thinking-fold set is no longer cloned every frame.

Verified with the previous commit: focused tui lib tests 518 passed, 0
failed, 3 ignored; codewhale-localization 52 passed, 0 failed; fmt and
CI-policy clippy clean. macOS only; the larger scroll-lag change and the
main-loop idle backoff are not in this commit.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… segments

Merges the head of #6739 (38e04c5, by asto18089) unmodified, so the
original commit keeps its author. The contributor's fork refuses
maintainer pushes (HTTP 403), so the merge with main that resolves the
import-block conflict in crates/tui/src/project_context.rs is carried here
instead of on their branch. No change to the contributor's code.

cargo test -p codewhale-tui --lib project_context: 88 passed; 0 failed
(run on the merge with main, before this merge with the wave; the wave's
two extra commits do not touch project_context). rustfmt clean on the
touched files; CI-policy clippy on codewhale-tui printed no warnings.

Refs #6739

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ins, plugin config and context

- Licence notices: the embedded host bundle carries MIT-licensed packages
  (cordis, schemastery, cosmokit, dsh-util-values, dsh-tools excerpts).
  dist/LICENSES.txt is now generated at build time from the bundler's
  metafile and each package's own licence, embedded, and written beside
  the materialised bundle. THIRD_PARTY_NOTICES.md lists the packages. A
  test reads the package markers in the embedded bundle and requires a
  notice and a third-party entry for each.
- Tool input is checked against the tool's registered JSON Schema before
  an approval card exists and again before the call is sent; an invalid
  input is a tool error the model can correct. A schema that cannot be
  compiled is refused at registration. jsonschema moves from a
  dev-dependency to a dependency of codewhale-tui (already in the lock
  file through codewhale-workflow-js; Cargo.lock unchanged).
- A plugin with several native entries activates all of them under one
  owner; a failing entry fails the owner and the earlier entries are
  disposed.
- Plugin context: tools and commands receive the calling workspace and a
  private per-plugin data directory. `[plugins."<name>".config]` in the
  user's config.toml reaches the plugin (16 KiB cap; project config cannot
  set it); a change starts a new generation; `/plugin show` lists the
  configured keys, never values.
- `codewhale --enable/--disable <feature>` before a subcommand is passed
  through by the dispatcher instead of being rejected.
- Docs and config comments: Linux sandbox wording, the 30 s handshake, the
  macOS sandbox write allowances.

Known gaps: the trust review screen does not show config keys; a running
session does not watch config.toml; no end-to-end launch of a rebuilt
binary for the --enable fix.

Verified by the committer on macOS, Rust 1.99, real Node 22.20 host:
cargo fmt --all --check clean; CI-policy clippy on codewhale-tui and
codewhale-cli clean; cargo test -p codewhale-tui --lib -- extension_host::
plugins:: commands::user_registry conformance:: integrations::dsh: 411
passed, 0 failed, 1 ignored; codewhale-cli lib: 475 passed, 0 failed; host
suite on Node: 88 passed, 0 failed, 1 skipped. Reported by the implementing
agent, not re-run: host suite on Bun 1.4.0, 89 passed. Linux, Windows and
the Rust-side Bun tests unrun.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nput validation, plugin config, MCP parity suite

Brings the TypeScript extension work onto the 0.10.1 release branch:
extension slash commands (command/run), licence notices for the embedded
host bundle, tool input validation against the registered schema,
multi-entry plugins, plugin config and context, the recorded MCP parity
suite (2 to 15 cases), the DeepSeek Harness version fix and docs, and the
declared Rust floor of 1.89. The extension host stays behind
[features] extension_host (experimental, off by default).

No textual conflicts. Verified on the merged tree (macOS, Rust 1.99, real
Node 22.20 host): cargo fmt --all --check clean; CI-policy clippy across
the workspace, all targets, clean; blocking-call and dead-code budgets
pass; root npm test green (wrappers, SDK 19, extension host 88 passed and
1 skipped, web 653). Focused codewhale-tui lib tests (extension_host,
plugins, commands, conformance, DSH, task manager, web search, project
context, dispatch and edit paths): one failure,
commands::debug_diagnostics_baseline_tests::
context_routing_and_report_branches_match_baseline, which reads the
developer's real home directory and fails on this machine before and
after this merge; its isolation is in progress separately. Linux and
Windows are left to hosted CI.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Adds entries for what landed after 9626357: the experimental TypeScript
extension host (new in this release, off by default) with slash commands,
input validation, multi-entry plugins, plugin config and context, the
--enable/--disable passthrough and third-party notices; the Rust 1.89
floor; the deferred-tool first call; the Git 2.31 floor; idle task
workers; the task-store lock holder; the web search fallback and its time
budget; cancelled turns releasing input; /edit restoring the revision; the
oversized-paste notice; repo-relative labels (#6739); linked .codewhale
and owner-only log and lock files. The DeepSeek Harness version bullet
moves from Unreleased into 0.10.1.

Four existing bullets change wording: the PowerShell bullet gains the
inherit opt-out, the stall-watchdog bullet loses the sentence that the
dispatch wait still blocks input, the audit-log sentence is qualified as
Unix-only, and the Bun bullet's opening is reworded.

The 0.10.1 section's repeated headings (Security seven times, Fixed three)
are merged into one each; a byte-level compare shows every other original
bullet present verbatim and in its original order (187 bullets before,
207 after: 19 new, 1 moved).

scripts/sync-changelog.sh --check, check-contributor-credit.py,
check-versions.sh --range-audit-advisory and the release-body test pass.
No Rust change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Comment thread crates/tui/src/conformance/mcp/controls.rs Fixed
CodeWhale Bot and others added 7 commits October 1, 2026 19:38
Per-frame transcript work grew with history length: ensure_iter collected
every cell into a Vec, ran two full backward scans, and moved all N cached
cells out of and back into a fresh Vec; the collapsed path allocated three
N-length vectors with three hash lookups per cell.

What changed
- transcript.rs ensure_iter: cells are reached by index, the cache is
  updated in place, and a new contiguous `seen_revisions` mirror gives the
  first changed cell with one slice compare. A frame whose revisions, width,
  options and folds are unchanged renders, moves and allocates nothing; a
  tail-only frame renders only the tail. The newest-user-turn / newest-Work-
  receipt scans reuse the previous pass for the unchanged prefix
  (newest_matching_cell), asserted against the full scan in test builds.
  No new trust is added: reuse is still decided by the per-cell revision the
  cache already trusted.
- app.rs / widgets/mod.rs: the collapsed row mapping (filtered->original
  index, summary slots) is cached per tool-run projection generation and the
  user's hidden-cell set; only revisions are gathered fresh each frame.
- dispatch.rs: a failed dispatch rolled `history_version` back to its old
  value, so a cache keyed on (version, len) could match a different cell
  that later landed at the same version. It now bumps instead (snapshot
  field removed; test asserts the version moves).
- transcript.rs: a tool, hidden or other cell replaced in place by a
  streaming answer at an unchanged cell count took the tail-only flatten and
  left its neighbour's group rail and spacer stale (found by the new cold-
  render property test; same in the pre-change code). The shortcut now
  requires the cell's kind/groupable/empty shape to be unchanged.

Writer audit (every mutation of the state the caches key on; non-test code)
- history_version bumps: app.rs add_message, maybe_fold_history,
  mark_history_updated, mark_live_motion_updated_inner, bump_history_cell,
  push_history_cell, extend_history, clear_history, pop_history,
  truncate_history_to, bump_active_cell_revision, cell_at_virtual_index_mut
  (both arms), flush_active_cell; ui/dispatch.rs failed-dispatch rollback (was
  a rewind, now a bump).
- history_revisions writers: all in app.rs (the push/insert/drain/truncate/
  clear/pop sites above, resync_history_revisions, bump_history_cell,
  mark_live_motion_updated_inner, cell_at_virtual_index_mut) plus the
  dispatch.rs rollback truncate. Every in-place `history.get_mut` /
  `history[i] =` site (event_loop x2, frame.rs append_streaming_text,
  streaming_thinking, subagent_routing x4, ui/apply.rs, ui/dispatch.rs,
  app.rs workflow/interrupt paths) calls bump_history_cell after it.
- active_cell_revision: app.rs bump_active_cell_revision/mark_live_motion/
  cell_at_virtual_index_mut, tool_routing.rs x2, ui/apply.rs; it is itself
  part of the tool-run key and of every active entry's revision.
- collapsed_cells / thinking_folds / expanded_tool_runs: compared by value
  against a snapshot each frame, so their writers need no bump.
The transcript cache itself keys on per-cell revisions, not on history_version,
so a missed version bump cannot leave it stale; only the tool-run projection
and the collapsed row mapping lean on the version, as before.

history_has_live_motion is left O(N) (~3 ns/cell per tick): has_live_motion is
derived from cell state mutated in place at many sites, so a maintained
counter could not be proven correct. Also still O(N): the tool-run projection
rebuild on every history_version bump (each streamed chunk) and
mark_live_motion_updated_inner's per-tick scan.

Measured (release profile, thin LTO, TestBackend 140x40, 200 frames,
`cargo test -p codewhale-tui --lib --release -- --ignored
bench_full_frame_scroll_cost_by_history_length --nocapture`; machine shared
with other builds, so +/-10%):
  plain history   before            after
  400 cells       136.3 us/scroll   155.5 us   (noise)
  4000 cells      198.8             181.5
  20000 cells     454.2             287.2   (-37%; 16.2 -> 6.8 ns/cell)
  height-change   164.6/228.4/492.7 -> 172.1/206.3/309.4
An earlier run of the same final hot path gave 142.6/160.3/270.6.
Collapsed history (new variant, after only): 500 cells 148.8 us, 5000 cells
199.8, 25000 cells 462.5. Isolated collapsed-input prep, same binary: 58.4 us
-> 12.7 us/frame at 5000 cells, 52.3 -> 9.8 at 4021.

Tests (debug, focused; code first, tests after)
- New: cached_transcript_matches_a_cold_render_after_every_mutation (8 seeds x
  150 mutations: append, replace, stream, finish, active replace, fold,
  width, options, clear, truncate, hide, owner, retarget; settled frames
  render zero cells), warm_chat_frame_matches_a_cold_frame_after_every_
  mutation (6 seeds x 120 App-level mutations through the real widget),
  an_unchanged_frame_renders_and_moves_nothing, a_tail_only_change_renders_
  only_the_tail_in_place, a_tool_slot_taken_by_a_streaming_answer_refreshes_
  its_neighbour; rollback test now asserts the version moves.
- tui::transcript:: 46 passed 0 failed 2 ignored; tui::widgets:: 258/0/2;
  tui::ui::tests:: 863/0/1; `tui:: golden commands::debug_diagnostics`
  4049 passed 0 failed 7 ignored. Not run: the full suite, hosted CI.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
`commands::debug_diagnostics_baseline_tests::context_routing_and_report_
branches_match_baseline` failed on any machine whose real home carries global
instructions or installed skills: `/context report` counts both, so the frozen
token totals moved (here ~/.codewhale/instructions.md is 126,359 bytes, past the
102,400-byte cap, and the user's skills load too). The fixture owned its
workspace and skills directory but not the user's home.

Fix: `SealedHome` (debug_diagnostics_test_support)
takes lock_test_env and pins HOME, USERPROFILE and CODEWHALE_HOME to empty
temp dirs for the test's life, the same seal the restore-route tests use
(81d9ec8). Applied to the two baseline tests that read the report.

Sibling found by the real-home vs empty-home comparison:
`context_alias_and_bare_action_are_preserved` compares `/ctx report` with
`/context report` byte for byte and failed 6 of 40 real-home runs (the skills
block moved between the two calls: 8103 vs 8589 tokens); 0 failures in 60
real-home and 10 empty-home runs after sealing. Nothing else in the module
differed.

Comparison runs (family filter `commands::debug_diagnostics`, 87 tests; the
real-home loop is read-only for this family, checked by file mtimes):
- before, baseline release binary: real home 86 passed 1 failed (the target);
  empty temp HOME+CODEWHALE_HOME 87 passed 0 failed.
- after: real home 60/60 runs 87 passed 0 failed; empty home 10/10 87 passed.
- `commands::debug_diagnostics_baseline_tests` 14 passed 0 failed.
Not run: the full suite, hosted CI, Windows (the seal sets USERPROFILE too).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…pproved

Records saved before the field existed now load as not auto-approved, so a
missing grant never widens authority. A test loads a record with the field
removed and checks the thread and turn requests built from it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Every update request and every redirect hop must be HTTPS and must name
GitHub's release hosts, the CNB mirror, the host of the configured release
mirror, or a host listed in CODEWHALE_UPDATE_ALLOWED_HOSTS. A release tag
read from a page must look like a tag. The 512 MB response cap and the
HTTPS-only redirect rule are now tested through the real HTTP client.
docs/INSTALL.md says how to use a private mirror.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Connecting, stalling, total time and response size are limited for the
status, text and JSON requests the release check makes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…the workspace

Agent profiles and skills read from a workspace no longer follow a link out
of it; a refused entry is named in a warning or load issue. User-owned
directories (personal profiles, ~/.claude/agents, home skill roots) still
follow links. Adds one shared link check for paths taken by name. The pinned
writer also asks again when macOS fails a concurrent create with ENOENT.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…ined

The destination is created through the pinned no-follow writer: a linked
directory or file name is refused, new files are owner-only, and a dangling
link at the destination name is no longer written through.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
CodeWhale Bot and others added 22 commits October 4, 2026 15:17
03da342 used a matrix expression, which the #5496 guard in
release-workflows.test.js rejects (every job must set a literal
timeout-minutes). Use 165 for every leg; Linux/Windows finish well under it.

Validation: node .github/scripts/release-workflows.test.js exit 0; the
Version drift job's steps all pass locally (check-versions, feature
release notes, contributor credit, bundled plugin claims, OHOS deps and
all 20 release-helper contract scripts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
CodeQL rust/access-invalid-pointer alerts #551 and #552 (windows.rs) flagged
the header reads in SecurityDescriptor::from_storage: they dereferenced the
pointers GetSecurityDescriptorDacl and GetAce write into null-initialized
out-parameters. The reads were already bounded (null check, then checked
offsets inside the owned descriptor), but the provenance was the FFI
out-pointer. Rebuild both pointers from the owned Vec storage plus the
already-validated acl_offset/entry_offset — the same addresses, matching
the existing acl() accessor — so every dereference is provably inside
owned memory. Behavior is unchanged.

Validation: cargo check -p codewhale-tui --lib --tests --all-features
--locked --target x86_64-pc-windows-gnu: Finished, no errors; rustfmt
--check clean. Hosted CodeQL and Windows tests are the proof.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…n branch

Vendor the paired canonical terms/privacy body and metadata from platform4cd9450; keep existing localized wrappers, website usage disclosure and preference control. Preserve current sign-in/sign-up and error counters.

Source changes: ef179c3abb2a01e9d79a0fdf3f45c0dface3bb55 and e725b2c625. Transplant exactly seven reviewed legal paths onto the fresh integration tree using an isolated index; all seven preimages verified, with the sole website disclosure reconciliation retained. Shared working tree, index, branch and concurrent Engine/merch changes stay intact.

Validation: source checkout legal/usage26 passed, TypeScript/lint and Next production build875 pages passed; canonical platform sibling guard5 passed. The paragraph disclosure follow-up passed legal/usage26 and canonical guard5. Hosted validation of this integration commit remains required. No deployment or commerce qualification.

Refs APPS-227
Use WEBSITE_USAGE_DISCLOSURE.body after the canonical legal adapter retired PRIVACY_SECTIONS. Preserve all assertions for actual counters and English/Chinese disclosure.

Verification: original exact-wave test reproduces undefined.find; corrected usage/legal/telemetry tests 34 pass, 0 fail; scoped TypeScript check pass. Independent root usage test 8 pass, 0 fail. Local focused proof only; hosted CI pending.

Source commit: 4c99a3f1a1b9e8bb5b87ae8a9bcb6fa6c12809a1. Only this verified blob integrated; shared checkout and index left unchanged. No deploy.

Signed-off-by: CodeWhale Bot <noreply@codewhale.net>
Hosted Windows on 561de7c dropped from 92 to 64 failures: the isolation
probe now passes, and the remaining Node hosts die before their handshake
with `EPERM: operation not permitted, lstat 'C:\'`. Node resolves the
main bundle with realpathSync, which lstats every ancestor from the drive
root, and a Windows LPAC cannot read `C:\`. On Windows only, launch Node
hosts with --preserve-symlinks and --preserve-symlinks-main so resolution
keeps the granted path as given; the LPAC still decides every access.
Bun's equivalent 'Module not found' failure is a separate fix.

Validation: cargo test -p codewhale-tui --lib --
extension_host::tests::launch_plan_gives_each_runtime_its_own_flags: 1
passed, 0 failed (macOS). npm --prefix crates/tui/extension-host test: 405
tests, 398 passed, 0 failed, 7 skipped. cargo check -p codewhale-tui --lib
--tests --all-features --locked --target x86_64-pc-windows-gnu: Finished,
no errors. Hosted Windows CI is the proof of the runtime behavior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Hunter moved the Codewhale repositories to github.com/codewhale-hq on
2026-10-05. Rewrite Hmbown/<moved repo> slugs to codewhale-hq/<repo> in
docs, READMEs (all 19), website, CHANGELOGs, workflows, issue templates,
Cargo/npm/VS Code/nix/winget metadata, install and updater code, the
bundled first-party marketplace catalog and their tests: 1,795 references
in 434 files, plus four escaped regex literals. Docker images publish to
ghcr.io/codewhale-hq/codewhale from the next release.

Old URLs keep working through GitHub's transfer redirects (verified live:
API releases/latest, HTML releases/latest, release asset downloads,
codeload tarballs, git clone), so released clients keep updating.

Left unchanged on purpose: Hmbown/codewhale-cu-plugin (not moved yet; its
shipped updater refuses redirects), Hmbown/deepseek-skills, cwc and other
repos that did not move; package identities (winget Hmbown.CodeWhale, NSIS
publisher); vendor/codewhale-ratatui (frozen reviewed package); and the
extension-host GitHub adapter with tools/github/report.rs and their parity
fixture, which need a bundle rebuild and move together in a follow-up.

README translation stamps updated to the new README.md hash.

Validation: npm test (wrapper 101/0, SDK 19/0, extension host 398 passed /
0 failed / 7 skipped, web 763/0) and npm run check:web exit 0 (one
pre-existing facts-lib.mjs lint warning). cargo check --workspace --tests
--all-features --locked: exit 0. cargo test -p codewhale-cli -p
codewhale-release -p codewhale-protocol and the touched codewhale-tui
modules (client, cloud_dispatch, commands, dispatch_runner, llm_client::mock,
oauth, operate, plugins::marketplace, remote_control, remote_setup,
tools::mcp_registry, tools::web::fetch, working_set, tui::ui::tests): all
ok, 0 failed. check-readme-translations (18 in sync), readme locales, TUI
locale parity, product vocabulary, web locales, contributor credit, plugin
claims, release-workflows.test.js and check-versions all pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
README, modeled on the strongest agent READMEs in our reference set
(opencode, minimax-code, grokbuild, codex):
- centered hero: wordmark, one-line tagline, five badges, nav row, locales
- the real terminal capture right under the hero
- one install line first; Windows/npm/Cargo folded into <details>
- numbered three-step quickstart, then tables for entry points
  (codewhale / exec / web / review --pr / Runtime API) and for
  modes (Plan/Work/Operate) vs postures (Ask/Auto-Review/Full Access)
- every feature claim checked against docs/features.toml
- all 18 locale READMEs retranslated and restamped (sha256:bf9d6c3588f0)

Website:
- sync vendor/codewhale-design to tokens 1.1.2 (muted_foreground holds
  5.5:1 on every ground) and regenerate web/app/tokens.css
- re-capture the terminal frames from this source (0.10.1 dev, b131357)
  via launch_card_pty::website_current_terminal_capture; regenerate
  terminal-capture.generated.ts and the README PNG from the same cells;
  update media-manifest, public-surface-facts and the capture manifest
- availability copy: the desktop app is becoming the main client and the
  hosted web app is being rebuilt to match it (en, zh, and 16 locales)
- remove orphaned images (three old TUI screenshots, codewhalelogo.png,
  computer-use.png, the superseded 5765d80 capture); fix stale
  references in docs/PRODUCT.md and "Tidal Folio" comments

Evidence (run locally in this tree):
- web: vitest 65 files / 654 tests passed, 0 failed
- web: npm run check passed (facts, latest-release, docs, tokens, lint,
  tsc --noEmit, next build)
- scripts/check-readme-translations.py OK (18 in sync)
- scripts/check-readme-locales.sh PASS
- scripts/check-provider-registry.py passed
- capture test: 1 passed; 0 failed
Not run: root `npm test` workspaces/extension-host suites (untouched).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…live whale

Founder direction (2026-10-04): replace the blue-wall ocean with "paper
above, sea below", animate the real GPUI whale, deslop the copy, fix the
sideways-scrolling terminal, and show Windows.

Design
- Paper (the GPUI light palette) is the site's appearance; dark is a pinned
  choice in the theme toggle, no longer the OS default.
- One named sea ramp (--sea, --sea-band) and the tide ombré (--tide: paper ->
  light aqua -> logo light -> logo deep -> navy), taken from the merch's Tide
  ombré tee. Retired the blurred strata bands, the sea texture and the
  mirrored reflection whale; strata.tsx deleted.
- Homepage: paper hero (category headline, install plate, CTAs), the live
  whale resting where the tide begins, the real terminal in the deep water,
  reading sections back on paper (no "01 /" eyebrows, terms in the brand
  face, getting-started steps as ruled columns), install band into the
  footer. Footer: one horizon line, no waterline band.
- Terminal captures scale to their frame (container query on column count)
  instead of scrolling sideways; scroll hint removed.

Whale
- Vendored whale-character-v2 (acting/rig/props/habitat/mark-data) from
  codewhale-app bc1044887b4e, publication approved by the owner; see
  web/vendor/whale-character-v2/PROVENANCE.md.
- components/whale-live.tsx: the app's own Director on a canvas, 30 fps only
  while visible, poster pose under prefers-reduced-motion, pointer gaze at
  rest. It performs a session loop (ready, reading request, planning,
  reading files, editing, running tests, done) with a word for each phase;
  picking a terminal view (Composer/Workbar/Fleet/Providers/Help) cues the
  matching state, Fleet shows three calves.

Copy (English; other locales follow in a translation pass)
- COPY_STYLE.md (UT Dallas JSOM business-communication conventions) and
  MESSAGING.md (positioning research across opencode, Pi, Codex, Claude
  Code, Cline, Zed, Kilo, Crush, Aider; translation-safety rules; glossary).
- Hero: "The open-source coding agent for any model"; section headings,
  product/plugins/models/getting-started/runtime/community/faq strings
  rewritten; claims narrowed to shipped behaviour.

Install
- Windows tab (Scoop main bucket carries codewhale 0.10.0). No winget: the
  manifest is not in microsoft/winget-pkgs (checked 2026-10-04).

Tests: rewrote the design-contract tests that pinned the retired look
(ocean-contract, docs-theme-contract, gpui-role-tokens). The contrast test
now resolves var() stops and caught a real defect (muted ink on the logo-deep
tide stop was 4.38:1); the tide was re-stopped so muted text sits >= 4.5:1.

Evidence (local, this tree):
- web: vitest 74 files / 764 tests passed, 0 failed
- web: npm run check exit 0 (facts, latest-release, docs, tokens, lint,
  tsc --noEmit, next build); 1 pre-existing lint warning in
  scripts/facts-lib.mjs
- headless Chrome: whale cycles all 7 phases and cues "pod" on Fleet; pages
  checked at 1440 and 390 wide: home, product, install, docs/guide, models

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The docs told Windows users to run `winget install Hmbown.CodeWhale`, which
does not exist. microsoft/winget-pkgs publishes HunterBown.CodeWhale
(manifests/h/HunterBown/CodeWhale, versions 0.8.43..0.10.0; merged PRs

The published manifest also differs from what we documented: it is a
portable x64 package (codewhale-tui-windows-x64.exe), installs only the
`codewhale` command, depends on the VC++ 2015+ x64 runtime, and has no ARM64
installer. The NSIS/ZIP, x64+arm64, codewhale+codew description belonged to
the repo's own singleton manifest, which winget never served.

- docs/INSTALL.md, docs/zh_hans/INSTALL.md: rewrite the winget section from
  the published manifest (id, portable x64, codewhale only, VC++ dep, lag,
  ARM64 alternatives); fix the known-contradictions note and anchor.
- packaging/winget: rename the singleton and its .winget mirror to
  HunterBown.CodeWhale.yaml and fix PackageIdentifier (submitting the old id
  would have created a second package); README states the published manifest
  differs and the singleton is stale at 0.9.6.
- README.md + 18 locales: the Windows install line uses winget again with the
  correct id (code fence only; prose unchanged, stamps refreshed).
- Website: hero Windows tab uses `winget install HunterBown.CodeWhale`;
  install guide regenerated from docs/INSTALL.md.

Evidence (local):
- gh api repos/microsoft/winget-pkgs/contents/manifests/h/HunterBown/CodeWhale
  lists 0.10.0; its installer.yaml: PackageIdentifier HunterBown.CodeWhale,
  InstallerType portable, Commands [codewhale], x64 only
- web: vitest 74 files / 764 tests passed, 0 failed; derive-install rendered
- scripts/check-readme-translations.py OK (18 in sync, sha256:0b635e0eb430)
- scripts/check-readme-locales.sh PASS
- grep -F "Hmbown.CodeWhale": only the dated history note remains

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Founder approved the new English copy (2026-10-04). Translated the changed
home/chrome strings into zh, ja, ko, vi, id, es, pt-BR, ca, fr, de, it, pl,
ru, uk, tr, hi and ar; zh also covers community, contribute, faq, runtime and
the {en, zh} pairs in lib/content (getting-started, install, models, plugins,
product). Several locales were behind even the old English (five-row
surfaces list, availability rows, availabilityNote); those were retranslated
from the current English rather than patched.

Rules: web/MESSAGING.md translation rules + glossary: plain declarative
sentences, product terms untranslated (Latin script in Arabic), placeholders,
commands and URLs exact, keys and array lengths unchanged.

Also:
- whale state labels (lib/content/whale-states.ts) now carry all 18 locales;
  pickText only knew en/zh, so other locales showed English.
- home title measured in em with hyphenation, and a CJK step (smaller size,
  wider measure, keep-all), so German and Japanese headlines wrap in 2-3
  lines instead of 4-5.

Evidence (local): tsc --noEmit clean; web vitest 74 files / 764 tests passed;
npm run check exit 0 before the label/CSS follow-up; headless renders of
en/de/ja/zh/ar heroes checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebasing onto 59e941a kept this branch's README rewrite, which still linked
github.com/Hmbown/CodeWhale; apply the sweep's mapping (codewhale-hq/CodeWhale)
to all 19 READMEs and packaging/winget/README.md, and restamp the 18
translations (sha256:604da19bff2c; links only, prose unchanged).

Kept the winget-pkgs path manifests/h/HunterBown/CodeWhale: that is the
published package directory, not a GitHub repository (the sweep had rewritten
it to manifests/h/codewhale-hq/... in packaging/winget/README.md).

Gate (local, this tree): npm test exit 0 (npm 101/101 + 19/19, extension
host 398 pass / 0 fail of 405, web 766/766); npm run check:web exit 0;
check-readme-translations OK (18); check-readme-locales PASS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing it

59e941a rewrote the plugin homepage links inside the generated
first-party catalog by hand, so the Marketplace connection check found
drift: sync-marketplace.py copies each plugin's homepage verbatim from the
pinned marketplace revision (ae3dd225), whose marketplace.json still names
the old owner. Regenerate from that revision. Install sources keep the
codewhale-hq codeload URLs the script now writes (verified 200); homepage
links redirect until the marketplace repo updates them and the pin moves.

Validation: python3 scripts/sync-marketplace.py --marketplace <clone at
ae3dd225> --check: First-party catalog matches marketplace ae3dd225.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
… posture

The 0.10.1 entry describing d1655c4, 0526412 and 124331e (agents
follow the live permission posture, approvals delivered while busy, the
workspace-delete floor) did not link the issue it fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
A tool call that failed (ToolCallComplete Err) was persisted with only
status=failed and detail, never tool_result_for/is_error. After a runtime
restart the rebuilt transcript held a function_call with no output and the
provider rejected every later turn (`No tool output found for tool call`,
#6803 on 0.10.0). 9ddf26b already repairs such records on rebuild
(unanswered_call_result); this records the failure correctly at the
source, matching how a success:false result is stored. Old stores and
interrupted/canceled calls still go through the existing repair, so there
is still exactly one repair mechanism.

Regression test: runtime_threads::tests::execution_identity::
a_failed_tool_call_records_its_result_and_replays_after_restart (persist
a failed read, reopen the manager, restore messages, assert one ToolUse
and one matching is_error ToolResult).

Validation: cargo test -p codewhale-tui --lib --
runtime_threads::tests::execution_identity
runtime_threads::tests::monitor_preserves_admitted_correlation_and_redacts_private_answers:
4 passed, 0 failed. With the source hunk reverted, the new test fails
(0 passed; 1 failed). rustfmt --check clean.

Refs #6803

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
windows_atomic_target_name refused reserved DOS names but not ':'. A name
like `notes:private` or `con:stream` passed the stem check and the
lexical absolute() round trip, so the native rename wrote an NTFS
alternate data stream while write_atomic reported success. Refuse any ':'
in the target name; Fleet artifact paths already reject the same class
(path_is_confined, fleet/files.rs).

Found by an independent Muse Code review of 2680ddc (medium) and
confirmed against the code. The Windows-only test
write_atomic_writes_beside_live_fleet_pins_and_refuses_rewritten_names now
also covers notes:private, config.json:stream, con:stream and file::$DATA.
Not run locally (cfg(windows)); hosted Windows CI is the proof. rustfmt
--check clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…#6842)

A 6-day session kept 49,258 journal entries (99.6% off the active branch),
a 98.7 MB document and 872 MB RSS. Three unbounded growers, fixed together:

1. Journal. `SessionJournal::prune_plan` (pure) keeps the root->leaf path and
   whole recent off-branch chains (Compaction/BranchSummary first) within a
   1,024-entry budget once more than 4,096 entries sit off-branch;
   `remove_entries` is atomic (validates or restores). Autosaves on the
   persistence actor (`persistence_actor.rs` flush_inner ->
   `SessionManager::save_session_bounded`), never the UI loop, append the
   planned entries to `sessions/.journal-archive/<id>.jsonl` (0600, fsync,
   dir fsync on create) and only then write the pruned document. Archive
   failure prunes nothing. Archived ids are handed to the UI through an
   in-memory registry and dropped from the live journal in
   `build_session_snapshot` (no I/O), so RAM matches disk. Plain
   `save_session` never prunes. `/branch <id>` restores an archived entry and
   its missing ancestors; `/tree` reports the archived count; export merges
   the archive into the container; session delete removes it; a Runtime API
   fork of an archived entry fails closed with a message naming the archive.

2. Work graph. New reducer change `PruneEndedOperations` keeps the newest
   256 ended, non-durable Operation nodes (not Stale, no non-Contains edges,
   not referenced by activities or proposals); applied at registration.
   These are a derived index of tool calls already in the transcript.

3. Metadata prefix. `load_session_metadata` grows its read geometrically
   (to 16 MB) until the metadata block closes instead of reading the whole
   file. `usage_source_fingerprints` is deliberately not capped: it is the
   money replay-idempotency set; documented as a known limit.

Not addressed here: snapshot construction still runs on the UI task (now
cheap with a bounded journal); resume still reads the document twice; a
child forked before a prune cannot /branch into the parent's archive.

Evidence (local, this checkout):
- cargo test -p codewhale-runtime --lib session_tree:
  test result: ok. 22 passed; 0 failed (4 new prune tests)
- cargo test -p codewhale-tui --lib -- journal_bound_tests
  canonical_journal_admission_tests ended_shell_operations
  container_includes_journal_archive:
  test result: ok. 9 passed; 0 failed
  (bounded save + live-journal follow, archive failure prunes nothing,
  archive-then-document-write-failure loses nothing, /branch to archived id
  round-trip, delete removes archive, >64 KB metadata listing, work-graph
  cap, export merge, existing superseded-entries-survive test unchanged)
- Negative control: with the work-graph prune disabled,
  ended_shell_operations_are_capped_oldest_first FAILED (0 passed; 1 failed).
- cargo test -p codewhale-tui --lib -- session_manager:: session_export::
  persistence_actor:: work_graph:: thread_history session_tree
  commands::contract: test result: FAILED. 290 passed; 1 failed. The one
  failure, control_hosted_work_target_resolves_and_never_echoes_credentials,
  expects repo slug Hmbown/CodeWhale but the checkout's remote is
  codewhale-hq/CodeWhale; unrelated to this change.
- RUST_MIN_STACK=16777216 cargo test -p codewhale-tui --lib -- fork canonical:
  test result: ok. 232 passed; 0 failed
- python3 scripts/check-blocking-calls-budget.py: 530 sites across 165
  files, within budget
- python3 scripts/check-dead-code-budget.py: 253 suppressions, budget 258

Refs #6842

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
59e941a rewrote the git remotes these tests feed in
(git@github.com:codewhale-hq/CodeWhale.git) but not the URL-encoded
expectations (Hmbown%2FCodeWhale), so
control_hosted_work_target_resolves_and_never_echoes_credentials and
remote_env_open_encodes_branch_and_never_echoes_credentials failed.

Validation: RUST_MIN_STACK=8388608 cargo test -p codewhale-tui --lib --
commands:: session_manager:: session_export:: persistence_actor::
work_graph:: thread_history: 1403 passed; 0 failed; 2 ignored (also covers
the #6842 slice 91ad8c5). No Hmbown%2F references remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Release QA on 8b20d48: resume a session, run one turn, quit, and every
later `codewhale resume` / `--continue` / `fork --last` failed for good:
"canonical history operation mounted-history:<uuid>; retained outcome must
be recovered using this exact key / caused by: saved holder checkpoint does
not cover the full source". A 0.10.1 regression (guard added in 5cf09da).

Cause: the first resume mounts the saved document into a runtime holder
thread; the TUI then runs later turns in its own engine and saves them to
the document, so the holder stays at the history it was seeded with. On the
next resume the guard demanded that the holder's seed checkpoint cover the
WHOLE document (unlike every sibling checkpoint guard, which requires a
prefix), and the exact live == document check then also failed because the
holder is behind (local repro: holder 2 messages, document 4).

Fix: the checkpoint must cover a prefix, as elsewhere. A holder that matches
the document exactly is reused as before. A holder that is strictly behind
(its messages are a strict prefix of the document's) and has no live work
holds nothing the document lacks: release its binding through the existing
compare-and-swap unbind (reconcile receipt kept, turns kept) and mount the
document fresh. A holder that is ahead or diverged still fails closed.

Regression test (PTY, hermetic loopback model server, sealed home):
plugin_e2e_acceptance::a_continued_session_resumes_again_after_each_turn
— first session with a turn, then resume + turn + quit twice. Against the
installed 8b20d48 build (QA_TUI_BIN) it fails at "resume 2" with the QA
error; against this fix: test result: ok. 1 passed; 0 failed. Manual repro
on the actually bricked session: three more resume+turn rounds, 10 messages.

cargo test -p codewhale-tui --lib -- thread_history
runtime_api::tests::session_resume canonical saved_session
runtime_threads::tests: 471 passed; 1 failed — the failure,
monitor_separates_lifecycle_start_from_billing_dispatch_and_child_usage, is
unrelated and passed 3/3 when rerun alone (timing under load).

Known limit: each resume after local turns leaves the previous holder as an
unbound thread with its turns (receipt recorded) rather than advancing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…vider serves

Two first-run defects from release QA on 8b20d48 (real DeepSeek key):

Invalid key loop. With an invalid DEEPSEEK_API_KEY, sending a message
dropped the user into "Getting started → Choose your model provider" with
no explanation; the picker showed DeepSeek as "key saved · not checked"
and Enter re-applied the same rejected key, so send → setup → send looped.
The rejection was written to the transcript but hidden behind the setup
screen, the readiness snapshot could not record a rejection for a key with
no fingerprint (env / secret-store keys), and Enter applied any keyed row.
Now: the setup screen states the key was rejected (existing
OnboardApiKeyRejectedEnv wording, error colour); provider readiness records
rejected credentials by provider, endpoint, auth type and fingerprint
(cleared by the next success); the rejected row reads "last check failed
(authentication)" and Enter opens key entry instead of reusing the key; Esc
returns to the composer. The key value is never displayed or logged.

Stale model list. After "The provider accepted the key" the first-run
picker listed every catalog DeepSeek model (19 on the QA machine, all
"price unknown"); picking one the API no longer serves (deepseek-v3.1)
failed the first message with 400. verify_provider_api_key already fetched
/models but discarded the body. It now returns the roster from a complete
page, and setup publishes it for the exact route through the existing
live-catalog path (no second catalog), filtered on a route's first roster
to ids the catalog knows for that provider so embeddings/speech ids stay
out. DeepSeek setup then offers deepseek-flash and deepseek-v4-pro.
Catalog-unpriced rows fall back to pricing::model_rate_label. Without a live
roster, an alias the route would rewrite to another listed id is hidden.

Tests: tui::provider_picker::tests::
rejected_env_key_is_marked_and_enter_asks_for_a_new_key and
tui::ui::provider_key_validation_tests::
provider_key_probe_roster_becomes_the_model_pick_roster (new), plus
env_only_auth_failure_reopens_provider_onboarding, provider readiness,
verify_provider, provider_key_, fetch_catalog_delta, the provider picker
and onboarding suites: test result: ok. 253 passed; 0 failed.

Follow-ups (data decisions, not in this change): DEEPSEEK_ALIAS_REPLACEMENT
in catalog_corrections.json points deepseek-chat/-reasoner at
deepseek-v4-flash; the bundled seed still carries rows Models.dev marks
deprecated; setup writes [providers.deepseek].model without updating
default_text_model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
The codewhale-hq link sweep lengthened string literals that rustfmt now
wraps differently (update.rs, dispatch_runner.rs, marketplace store,
session control tests); the Lint job failed cargo fmt --check on
0c79ef2. Also apply clippy 1.99's manual_contains in the setup model
picker and needless_borrows_for_generic_args in a work-graph test.

Validation: cargo fmt --all -- --check clean; cargo clippy --workspace
--all-targets --all-features --locked -- -D warnings (CI allow-list):
exit 0 with stable 1.99.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Hosted Windows on 54f2b1a still failed 13 tests (down from 92). Six
native MCP / preset activations died with `EPERM: operation not
permitted, lstat 'C:\'`: the reviewed-closure checks in the extension host
(admitReviewedClosure, importReviewedModule, the Bun onLoad symlink check
and the shell-hook config check) call JS realpathSync, which lstats every
ancestor from the drive root, and a Windows LPAC host cannot read C:\.
On Windows only, use realpathSync.native, which asks the OS for the opened
file's final path and needs access to that file alone. macOS/Linux keep the
JS implementation unchanged. The symlink/closure checks are unchanged.

Rebuilt with a clean npm ci + node build.mjs: exactly the four bundles that
embed these modules changed; the builtin harness/mcp bundles and
builtin-modules.json rebuilt byte-identically.

Validation (macOS): extension-host tsc clean (outside vendored upstream);
npm --prefix crates/tui/extension-host test: 405 tests, 398 passed, 0
failed, 7 skipped; cargo test -p codewhale-tui --lib --
plugins::install::dsh::tests: 26 passed, 0 failed (twice). Windows runtime
proof is hosted Windows CI. Not addressed here: Bun's own entry-module
resolution under the LPAC (3 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…p to C:\\"

This reverts 4e1905b. On hosted Windows (job: Extension host runtimes,
windows-latest) realpathSync.native returns a different spelling than the
raw input (8.3 short names, drive casing, \\?\ prefix), so the
`canonicalPath(path) !== path` symlink checks refused legitimate reviewed
files (tests 49/50: 'hook config is absent from the reviewed regular-file
closure'). Worse, closureAt() compares raw module paths against the
now-native-canonical root, so on the Bun path a reviewed module could fail
to match its closure and skip the receipt hash check. A correct fix must
canonicalize consistently on both sides of every comparison and be proven
on real Windows; it is not a release-night change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@Hmbown
Hmbown merged commit b059d4f into main Oct 5, 2026
35 of 36 checks passed
@Hmbown
Hmbown deleted the wave/0.10.1-next branch October 5, 2026 07:27
@asto18089

Copy link
Copy Markdown
Contributor

Follow-up on the fork-push 403 noted in 18477f3 (the #6739 landing): #6739 had been created with allow-edits-by-maintainers off — that was the 403, and it's on me. All my currently open PRs (#6847–#6860, #6863, #6864) have maintainer edits enabled, so the wave process can push conflict/fix merges straight to those branches. Two limits of that GitHub grant, in case they matter for the wave tooling: it doesn't permit force-pushes to the branch, and it's scoped to the PR head branch only. If a force-push or whole-fork access is ever needed, tell me and I'll add write collaborator access instead.

Hmbown pushed a commit that referenced this pull request Oct 5, 2026
…der LPAC

Hosted Windows (LPAC) refuses lstat on the drive root, so Node's JS
realpathSync — which walks every ancestor — failed the reviewed-closure
checks with EPERM lstat 'C:\' (13 failures on #6815, mostly
extension_host::native_mcp and raw_agent_presets). The earlier attempt
(4e1905b, reverted) used realpathSync.native but compared its spelling
against raw input.

New src/dsh/canonical-path.ts is the one comparison rule: canonicalize
with realpathSync.native on win32 (GetFinalPathNameByHandle, no ancestor
walk) and realpathSync elsewhere, strip \\?\ and \\?\UNC\, compare
case-insensitively on win32, and express containment as
relative(canonical root, canonical target). A canonical path is never
compared to a raw one. resolve-hooks keys closures by canonical root and
remembers the raw root; the Node load hook now also refuses symlinked
closure modules (under --preserve-symlinks an in-closure symlink pointing
outside kept its in-closure URL and loaded — reproduced on HEAD).

Evidence (macOS): npm --prefix crates/tui/extension-host test: 406 tests,
399 pass, 0 fail, 7 skipped (new canonical-path test passes);
cargo test -p codewhale-tui --lib -- extension_host dsh: 264-265 pass,
2-3 plugins::install::dsh failures that differ per run and pass in
isolation (parallel-load flake under investigation). Windows LPAC itself
cannot run here; hosted Windows CI is the proof.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Hmbown commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

thank you!! all's fair in love and open source PRs I say — the agents will sort it out 🙂 thank you for changing it and contributing so much!!

VitorEAFeliciano pushed a commit to Navegos/Codewhale that referenced this pull request Oct 6, 2026
…der LPAC

Hosted Windows (LPAC) refuses lstat on the drive root, so Node's JS
realpathSync — which walks every ancestor — failed the reviewed-closure
checks with EPERM lstat 'C:\' (13 failures on codewhale-hq#6815, mostly
extension_host::native_mcp and raw_agent_presets). The earlier attempt
(4e1905b, reverted) used realpathSync.native but compared its spelling
against raw input.

New src/dsh/canonical-path.ts is the one comparison rule: canonicalize
with realpathSync.native on win32 (GetFinalPathNameByHandle, no ancestor
walk) and realpathSync elsewhere, strip \\?\ and \\?\UNC\, compare
case-insensitively on win32, and express containment as
relative(canonical root, canonical target). A canonical path is never
compared to a raw one. resolve-hooks keys closures by canonical root and
remembers the raw root; the Node load hook now also refuses symlinked
closure modules (under --preserve-symlinks an in-closure symlink pointing
outside kept its in-closure URL and loaded — reproduced on HEAD).

Evidence (macOS): npm --prefix crates/tui/extension-host test: 406 tests,
399 pass, 0 fail, 7 skipped (new canonical-path test passes);
cargo test -p codewhale-tui --lib -- extension_host dsh: 264-265 pass,
2-3 plugins::install::dsh failures that differ per run and pass in
isolation (parallel-load flake under investigation). Windows LPAC itself
cannot run here; hosted Windows CI is the proof.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows (npm install): killing node.exe instantly terminates Codewhale with no cleanup; the agent's own "stop node" commands can kill the session

4 participants