Skip to content

[pre-flight] chat model selector CI pre-check - #1

Open
seeones wants to merge 39 commits into
mainfrom
pr/chat-model-selector
Open

seeones wants to merge 39 commits into
mainfrom
pr/chat-model-selector

Conversation

@seeones

@seeones seeones commented Sep 27, 2026

Copy link
Copy Markdown
Owner

Internal pre-flight PR to run the full CI suite (code-qa, mutation-diff, visual-regression, e2e) on the fork before syncing to upstream PR Zoo-Code-Org#1822.

easonLiangWorldedtech and others added 14 commits September 26, 2026 18:59
…own-sourced openFile requests (Zoo-Code-Org#1762)

* fix(webview-message-handler): enforce workspace containment for markdown-sourced openFile requests

* fix(webview-message-handler): harden markdown containment against encoded and symlinked paths

Address the automated review findings on the split PR: percent-decode
tagged openFile paths to a fixed point at the containment boundary
(openFile decodes after the check, so %2e%2e traversal would escape
only after that later decode), and re-check containment on the real
filesystem path so a workspace-internal symlink cannot resolve
outside the workspace (fail closed; unresolvable targets reject).

Tests now exercise the production isPathOutsideWorkspace against a
mutable mock workspace (multi-root, no-folders cases) and a
deterministic realpath model: encoded and double-encoded traversal
rejected, legitimately encoded filenames still open, symlink escape
rejected, file creation under a new directory allowed.

* test(extension): pin the new containment mutants and fail-closed branches

Mutation preflight on e98713a found blocking survivors in the new code:
- direct tests for decodeUntrustedPathToStable (stable/fixed-point/invalid%
  escape/bound) and isRealPathOutsideWorkspace (no-folders, in-workspace,
  creation ancestor, symlink escape, EACCES fail-closed, unrealizable folder)
  cover the previously NoCoverage fail-closed returns
- untagged percent-encoded paths stay undecoded at the boundary (legacy
  openFile decode applies exactly once) - kills the fromMarkdown gate mutant
- a lexically outside path whose symlinked ancestor resolves inside is still
  rejected by the lexical check - kills the defense-in-depth ordering mutant
- Stryker directives for the equivalent/bound mutants with concrete reasons

* test(extension): cover the hostile-bound reject branch and the root guard

- handler: a tagged encoding that never stabilizes inside the fixed-point
  bound is rejected at the boundary (covers the defensive null branch)
- helper: the ancestor walk reaches the root guard when nothing exists and
  containment fails closed
- locale-bundles.spec.ts imports all 18 common bundles and pins the
  path_outside_workspace key in each (completeness + puts the bundles into
  the coverage report for changed-line coverage)

* fix(extension): type the realpath spy parameter as PathLike

@types/node declares promises.realpath(path: PathLike, ...); string | URL is
not assignable (PathLike also admits Buffer)

* test(extension): assert the exact rejection message for the hostile-bound case

Assert cannotAccessPathError with the raw path instead of only that an
error was shown (CodeRabbit re-review, assertion identity)

* chore: re-trigger CodeRabbit review

* fix(extension): fail closed on dangling symlinks in realpath containment

A dangling symlink inside a workspace folder fails realpath with ENOENT
without being a nonexistent path: the ancestor walk would treat it as
missing and check the (in-workspace) ancestor instead, while a creation
flow (mkdir -p) would follow the link and escape the workspace.

ENOENT from realpath now lstat's the entry first: an existing symlink
whose target cannot be resolved fails closed; a genuinely absent entry
keeps walking to its deepest existing ancestor. Unexpected lstat errors
also fail closed.

Adds the regression tests: dangling symlink rejected (handler and helper),
a symlinked workspace root realized to the real root, and the lstat
EACCES fail-closed path

* chore(extension): document the equivalent lstat-symlink mutant

Stryker marks the ConditionalExpression true/false replacements on the
dangling-symlink check unobservable: lstat succeeding after a realpath
ENOENT can only be a dangling symlink, so the condition is always true
at every reachable point. The false replacement is pinned by the
dangling-symlink tests; the directive documents the equivalence.

---------

Co-authored-by: Eason Liang <easonliang28@gmail.com>
Extract the chat input model picker (ChatModelSelector +
useChatModelSelector) into its own PR so each PR stays under the
mutation-diff changed-lines gate. Mounts between ApiConfigSelector
and AutoApproveDropdown in the chat composer action bar.

Includes the Tab-navigation visual test (loop widened to 50 stops for
the extra button) and the composer snapshots rendered with the
selector present.
…ion (Zoo-Code-Org#1818)

* refactor(code-index): separate service factories and embedder validation

* fix(code-index): localize validation errors and identify missing settings
* fix(task): keep the first abort reason

* fix(task): apply first-reason-wins to both abort paths in catch block
…#1801) (Zoo-Code-Org#1812)

* fix: reset didFinishAbortingStream for each request

* test: wait for cancelTask to enter its wait before asserting pending
…Code-Org#1766)

* refactor(code-index): introduce workspace scope behind registry

Refs Zoo-Code-Org#1594

* test(code-index): cover registry retry after construction failure
…very (Zoo-Code-Org#1768)

Centralize indexing status subscriptions in the extension scope and keep state ownership in workspace scopes. Honor the configured workspace root resolution when selecting the status source, with regression coverage for both selection strategies.
… race)

- Enforce the organization allow-list on both the webview and the extension host
- Make the selector trigger keyboard accessible (div -> button)
- Correct Z.AI China API line handling so the mainland model list loads
- Guard the OpenAI model list request against identity races
- Show the currently saved model in the trigger
- Cover allow-list gating, keyboard interaction, race handling and trigger display
- Align assertions with the div -> button markup change
- Add the selectModel label to all supported chat locales
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • coderabbit-review-active

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: seeones/Zoo-Code/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: daa0f991-142c-4221-b4a7-87ed2c859e6f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Comment @coderabbitai help to get the list of available commands.

seeones and others added 15 commits September 29, 2026 17:24
…ests

- Enforce the organization model allow-list solely in ClineProvider.upsertProviderProfile so webview writes and direct callers (OAuth callbacks, sign-out) share a single enforcement point and the same user notification, instead of duplicating the check in webviewMessageHandler.
- Add an internal bypassAllowList option reserved for Zoo Gateway credential writes (token refresh / sign-out) that ProfileValidator cannot map to a model id; it is never set from a webview-originated path.
- Echo the caller requestId on Ollama/LM Studio/VSCode LM model replies and gate adoption per channel so a stale response cannot overwrite the active model list.
- Compact the model selector hook (Object.fromEntries, optional chaining, merged comments) to keep the mutation-diff gate within budget.
- Prune the now-unused any suppressions for webviewMessageHandler.spec.ts (35 -> 29).
Compact ChatModelSelector so the webview package stays within the mutation-diff changed-executable-line budget: fold the custom-model allow check into a derived boolean, merge the filter/return in the model-id memo, collapse the onSelect guards into one early return and drop a redundant return. Behaviour, DOM structure and allow-list gating are unchanged.
- ClineProvider: assert the organization allow-list rejects a disallowed profile in upsertProviderProfile and that a bypassAllowList write (Zoo Gateway credential cleanup) still succeeds.
- webviewMessageHandler: assert the Ollama/LM Studio/VSCode LM replies echo the caller requestId, that a failing Zoo Gateway sign-out cleanup surfaces an error instead of a false success, and that upsertApiConfiguration delegates validation to the provider.
- useChatModelSelector: assert replies carrying a superseded request id are dropped and only the request most recently issued per channel is adopted.
- ChatModelSelector: assert the custom-model row is gated by the organization allow-list.
…n-out error

ProfileValidator.getModelIdFromProfile now returns zooGatewayModelId so a listed Zoo Gateway model passes the organization allow-list without the internal bypass. Sign-out no longer reports an allow-list violation when the cleaned profile fails to persist (the allow-list check is bypassed on that call). Adds focused ProfileValidator and upsertProviderProfile coverage.
)

* refactor(code-index): extract single-file preparation

* refactor(code-index): use constructor-injected file preparation class

* refactor(code-index): separate file preparation dependencies

* test(code-index): cover platform-specific file preparation paths

* refactor(code-index): inject preparation service objects

* refactor(code-index): simplify file preparation stages

* fix(code-index): decode prepared file bytes as UTF-8

* test(code-index): assert preparation receiver identity
…atomic

Security Boundaries: only fall back to allow-all when no cloud instance exists; if a cloud instance is present but its allow-list cannot be read, reject the write (fail-closed) and notify the user.

Persistence Integrity: snapshot prior profile/active state and the mode mapping before the activation writes; on failure, restore the previous profile, mode mapping, currentApiConfigName, listApiConfigMeta and provider settings, then rethrow so callers see the error.

Tests: cover rejection when the allow-list is unavailable, allow-all when no cloud instance exists, and rollback after a post-save activation failure; complete the @roo-code/cloud mocks in ClineProvider.spec.ts and ClineProvider.sticky-mode.spec.ts with an allow-all getAllowList to match the real contract.
…ode-Org#695) (Zoo-Code-Org#700)

* fix: streaming tool-call argument loss in NativeToolCallParser (Zoo-Code-Org#695)

* chore: remove agent-generated changeset file per policy

* test: add direct coverage for finalizeRawChunks() in NativeToolCallParser

* fix: address PR Zoo-Code-Org#700 review feedback for tool-call streaming reassembly

- Guard finalize results with not.toBeNull() in parallel-index and single-chunk tests so a null result fails instead of passing silently
- Add reverse-ordering test (name -> buffered args -> id) covering the start-gate id requirement
- Use name !== undefined recording plus a nameSeen flag in the start-gate as a defensive guard against an empty tool name
- Clear rawChunkTracker in processFinishReason so finalizeRawChunks is a safe no-op; add a regression test asserting no double tool_call_end
- Remove unrelated ask_followup_question wording change from PR scope
- Remove prs/fix-toolcall-dropped-leading-deltas.md from the diff

* fix: handle provider-emitted tool_call_end chunks during streaming

Task.ts had no stream-level case for tool_call_end, so end chunks emitted
by providers on finish_reason: "tool_calls" were silently dropped; tool
calls only finalized at stream end via finalizeRawChunks(). Add a
tool_call_end case so tools finalize and present during streaming, and
extract the triplicated finalize/present logic into a shared idempotent
helper. Correct the NativeToolCallParser test drive helper to finalize via
finalizeRawChunks() (matching production) instead of processFinishReason().

* test: cover Task.finalizeStreamingToolCallById streaming finalization

Add a focused spec that invokes the real Task.prototype.finalizeStreamingToolCallById
via .call() with mocked presentAssistantMessage and NativeToolCallParser, covering
the success, null-finalize (malformed JSON), untracked-id no-op, and idempotent
re-finalize paths. Closes the codecov/patch gap on the new helper.

* test: adapt tool-call streaming coverage to scoped parser state

* test: type finalizeStreamingToolCallById fixtures as FinalizeStub

* fix(task): defer finalized tool execution until history persists

* refactor(parser): cut PR Zoo-Code-Org#700 to parser-only streaming scope

Restore Task.ts persistence coordination and stream-time tool_call_end finalization to main; retain only the Zoo-Code-Org#695 parser fixes with their focused tests.

---------

Co-authored-by: Elliott de Launay <edelauna@gmail.com>
… failure

getProfile({ name }) wraps both not-found and transient read failures in the same error, so a swallowed failure could be mistaken for absence. After a later saveConfig succeeded, a failing rollback would then call deleteConfig(name) and remove an existing profile and its secrets.

Probe existence explicitly via hasConfig: only deleteConfig when absence is confirmed; when the profile is known to exist but cannot be read, propagate before saveConfig and abort the write. Update the apiHandlerRebuild mock with hasConfig and add a test covering the read-failure abort path.
…ent (Zoo-Code-Org#1606)

* fix(vscode-lm): trim oversized tool_results to fit the model context window

Copilot's backend trims an over-window request without preserving tool_use/tool_result pairing, orphaning a tool_result and triggering a 400. Shrink oversized tool_result payloads middle-out on our side, and refuse a request that still cannot fit rather than send one we know is over-window.

* fix(vscode-lm): restore em-dashes mangled during extraction

* fix(vscode-lm): keep middleOutTruncate within maxChars for small limits

* fix(vscode-lm): mark the over-budget refusal as a context-window error

* fix(vscode-lm): remove unreachable truncation guard and strengthen tests

Delete the target >= text.length guard, which cannot be reached. Move the middleOutTruncate JSDoc onto the function it documents. Make the fits-budget test actually assert no trimming, and assert the refusal error's reported character count so it fails if trimming is skipped.

* refactor(vscode-lm): cache tool_result text, return remaining chars, share 400 constant

F6: derive each tool_result's text once into a Map before sorting, removing the repeated filter+map+join in the comparator and the truncation loop. F7: return the running total as remainingChars so createMessage reuses it instead of re-scanning via estimateMessagesChars; a new test asserts the returned total matches an independent re-scan across eight paths. F8: export CONTEXT_WINDOW_EXCEEDED_STATUS from context-error-handling and use it on both the detector and the local over-budget refusal.

* test(vscode-lm): widen raw-budget refusal margin to 1999 chars

---------

Co-authored-by: Bertan Ari <bertanari@microsoft.com>
Co-authored-by: edelauna <54631123+edelauna@users.noreply.github.com>
…e-Org#1778)

Extract workspace enablement coordination and status publication, expose full workspace scopes, and replace direct manager access in webview handlers. Add toggle guard and default coverage, sync upstream main, and align the CodeRabbit test with upstream chat access policy.

Refs Zoo-Code-Org#1594
* feat(openai): support GPT-6.1 Sol in native and Codex providers

* fix(openai): use supported 872K Codex context for GPT-6.1 Sol

* fix(build): rebuild dependents when dependency packages change

---------

Co-authored-by: Elliott de Launay <edelauna@gmail.com>
…-Code-Org#1873)

* feat: add community-approved advisory label for community code approvals (Zoo-Code-Org#1872)

* test(workflow): cover community-approved label removal paths

---------

Co-authored-by: @taltas <6816042+taltas@users.noreply.github.com>
Co-authored-by: Elliott de Launay <edelauna@gmail.com>
renovate Bot and others added 10 commits October 1, 2026 03:43
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…olution) (Zoo-Code-Org#1589)

* fix(dev): use 127.0.0.1 instead of localhost for dev server to avoid IPv6 resolution issues

* test(webview): assert HMR html targets the 127.0.0.1 dev server

* test(webview): assert the dev-server probe targets the 127.0.0.1 health-check URL

* test(webview): isolate the dev-server probe test from a stray .vite-port file and restore env in finally

---------

Co-authored-by: hnbdr <hnbdr@users.noreply.github.com>
* fix(webview): batch repeated tool preambles

Amp-Thread-ID: https://ampcode.com/threads/T-01a0aa02-b6de-7763-97b8-1650ea4b46da

* fix(webview): preserve batching boundaries

---------

Co-authored-by: Amp <amp@ampcode.com>
…ol-policy layer (Zoo-Code-Org#1724)

* fix(mcp): validate native mcp_tool_use dispatch through the shared tool-policy layer

The native mcp_tool_use arm now consults the shared validateToolUse
layer (under the canonical use_mcp_tool name) before dispatching to the
handler. A disabledTools or excludedTools entry naming use_mcp_tool is
rejected at dispatch with a structured is_error tool_result and the
server method is never invoked; the enabled path is unchanged.

Issue: Zoo-Code-Org#1646

* test(mcp): cover native mcp validation guard branches; drop redundant optional chains

Three tests pin the guard's deref-undefined fallback, the includedTools
alias-map resolution, and the id-omitted rejection branch, taking patch
coverage of the changed window from 78.9% to 100% and clearing codecov/patch.
The modelInfo and toolError simplifications are behavior-preserving per the
typed contracts (non-nullable getModel() return; toolError returns string).
Mutation-diff survivor forms are eliminated: all 15 mutants in the regression
map are killed.

Issue: Zoo-Code-Org#1646
…oo-Code-Org#1852)

`addToClineMessages` called `flushPostStateToWebviewThrottled()` for every
message with `partial === true`. With lodash debounce configured
`{ leading: true, trailing: true }`, a flush issued right after a leading-edge
invocation has no pending trailing invocation to run, so it only cancels the
trailing timer. The next call then hits the leading edge again and posts
immediately, so the debounce never coalesced anything on the streaming path.

Measured against lodash.debounce 4.0.8 with the provider's own settings
(500ms wait, 1000ms maxWait), 20 messages arriving 100ms apart produced:

  with the interleaved flush: 20 full-state posts
  without it:                  3 full-state posts

Because `requiresImmediateState` matched every partial first chunk, throttling
was effectively inert for tool-heavy tasks and the gray-screen OOM that Zoo-Code-Org#1078
set out to fix remained reproducible at high message counts.

Drop `partial` from the condition. Partial `say` messages are safe on the
throttled path: the trailing/maxWait post carries the message's current text,
so a `messageUpdated` dropped for a `ts` the webview does not know yet is
superseded rather than lost. Partial asks are unaffected — `Task#ask` adds them
without `isAnswered`, so they keep flushing through the ask clause and retain
the ordering guarantee unanswered asks depend on.

The existing suite could not catch this: every Task-level test mocks the
provider, so it asserted that flush was *called* rather than that state posts
were actually coalesced. Retarget the partial-message test at the new contract
and add a characterization test in ClineProvider.spec.ts that pins the
debounce semantics against the real timer.
* chore(deps): update dependency mocha to v11.8.0

* fix(deps): override mocha diff to 8.0.4

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Elliott de Launay <edelauna@gmail.com>
* fix(opencode-go): add DeepSeek V4.1 Flash metadata

Amp-Thread-ID: https://ampcode.com/threads/T-01a0aa02-b6de-7763-97b8-1650ea4b46da

* fix(opencode-go): enable DeepSeek Flash vision

* test(api): pin DeepSeek V4.1 metadata

---------

Co-authored-by: Amp <amp@ampcode.com>

This branch has not been deployed

No deployments
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.