Skip to content

fix(search): keep legacy host search and negotiate agent support - #25009

Merged
nwparker merged 6 commits into
mainfrom
fix/session-search-agent-negotiation
Oct 4, 2026
Merged

nwparker merged 6 commits into
mainfrom
fix/session-search-agent-negotiation

Conversation

@nwparker

@nwparker nwparker commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​426 $\color{#cf222e}{\Huge{\mathbf{−}}}$​13 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​413
Prod 3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​41 $\color{#cf222e}{\Huge{\mathbf{−}}}$​27 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​14

ELI5

Search works with older Orca servers even when they do not have the newer search-status method. Current peers keep their supported providers, while older peers receive only filters and results they understand.

What Changed

The client now treats an explicit missing status method as a legacy server and continues the supported search. Authentication, connection, and malformed-response failures still propagate. It uses the existing compatibility policy, including the Qoder and Jcode capabilities already on main.

The host keeps empty provider intersections narrow using its existing no-match scope. Normal consent, readiness, and unknown-workspace responses retain their precedence, and empty results have no next-page cursor. This join retains the newer main implementation and the original PR's valid coverage and ancestry.

Why

Status and search were introduced independently. Treating a missing status probe as missing search incorrectly hid a working service. Negotiating every explicit remote filter also respects an advertised empty or narrower catalog. Reusing the existing policy avoids a second provider list that could drift.

Linked Issue

Addresses the missing-status review in this PR. No additional issue was opened.

Visual Proof

N/A — this corrects backend request compatibility and has no layout or rendered UI change. Before/after production-dispatcher and SQLite controls are retained.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated

On macOS, the real RPC dispatcher reproduced failure with only the status method absent and passed after the fix. Actual pinned older request/response parsers, SQLite retrieval, remote error propagation, and runtime/relay/IPC boundaries pass: 215 focused tests and 103 cross-version tests (300 distinct). Node, CLI, and web typechecks, formatting, strict lint, and changed-code gates pass. No fresh native Windows or OpenCode UI claim is made for this contract correction.

Review

Filtered remote searches make one status probe. Unknown-method fallback is narrow and each execution host negotiates separately; this adds no client cache or path interpretation.

Agent skill upstream boundary

  • Not applicable; no upstream skill source is copied or translated.

Checklist

  • This PR is small and focused
  • I explained the before/after, mechanism, and reuse choice
  • N/A visual proof is explained
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH, mobile, and folder contexts considered
  • Relevant local tests, typechecks, formatting, and lint pass; build remains covered by CI

The branch now includes main 94f5669 in b1dcb04. All 16 reviewed file decisions, including two intentional deletions, match the preceding qualified source, and every other main file is preserved. Independent full-source and artifact reviews pass. Fresh Node/CLI/web typechecks pass. The focused checks cover 133 distinct cases across eight files: 115 unaffected cases from the initial run, plus 18 cases rerun after restoring the exact existing release tag in the private test repository. The initial five setup failures are retained. Earlier quality, dispatcher, SQLite and runtime receipts remain tied to their tested sources; fresh public CI covers this composition before merge.

Keep older request and reply parsers usable while current peers retain all supported history.
@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3abdfee7-f53a-48eb-b8a2-6de57d8c4995
📥 Commits

Reviewing files that changed from the base of the PR and between 583650d and f323450.

📒 Files selected for processing (3)
  • config/scripts/mobile-web-app-html-preview-render.test.mjs
  • config/scripts/mobile-web-app-preview-arm-driver.mjs
  • src/cli/handlers/search.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

The changes update session-search agent negotiation and fallback behavior. They add OpenCode 1.x TUI plugin registration and status reporting, plus session identity and launch-token handling for local and relay hooks. Relay build configurations and test bundles use a shared JSONC parser ESM alias. The preview-arm driver can wait for a post-action CSP report.

Priority: ⬇️ Low

Merge Risk: 🔵 Low · up to f3234

An invalid OpenCode TUI configuration can stop plugin installation or refresh before the server plugin is written, leaving the integration absent or stale until the configuration is corrected.

Security Architecture Review

Security architecture risk: 🔵 Low · up to f3234

No authentication bypass or expanded data access was established. However, a new configuration-registration failure can prevent an existing reporter from being installed or updated, leaving reporting components at different versions. Recovery and ownership propagation are not fully established.

Retained concerns

  • Medium · reliability · observed: The new TUI configuration-registration step can prevent server-plugin installation or repair. Both installation paths publish or validate the TUI entry, then register it, before writing the server plugin. Invalid configuration or a registration write failure exits this sequence, including when the TUI entry is already current. A newly written TUI entry can therefore coexist with unchanged configuration and an absent or stale server reporter. Existing catches allow launch or refresh processing to continue without completing that transition. This is a failure-containment and reporter-rollout concern, not a demonstrated authentication bypass.
Security review details

Security Blast Radius

  • inferred — The established installation failure affects reporting components sharing the failing host configuration, potentially across multiple panes. The inspected path does not demonstrate credential elevation, a new unauthenticated entrypoint or cross-tenant data access.

Trust Boundaries and Controls

  • observed — Search negotiation retains non-agent request fields and transport redaction. The host preserves unknown-workspace handling and supplies its no-match resolved scope for empty provider intersections, then filters incompatible hits before returning results.
  • observed — Local TUI normalization consults status disposition before admission, and spool ingestion explicitly marks replay. Inspected remote ingress also validates identity and replay authority before status application; complete ownership propagation between producer and consumer remains partially assessed.

Resilience and Maintainability Implications

  • observed — Local session binding is deferred through an acceptance callback, but the inspected identity-only status branch invokes that callback before writing the status row. This is not evidence of transactional binding/status commitment; failure and recovery behavior at that boundary remains incompletely assessed.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.64% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 44 functions across 57 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: preserving legacy-host search while negotiating supported agents.
Description check ✅ Passed The description is detailed and covers the change, rationale, testing, visual proof, review, and checklist. The linked item is a PR discussion rather than an issue; the template requires an actual iss…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 021d1d51-f23b-4ba2-8ca4-fded49b213a4
📥 Commits

Reviewing files that changed from the base of the PR and between 6245192 and 7f71ffe.

📒 Files selected for processing (13)
  • docs/reference/remote-wire-compatibility.md
  • src/main/ai-vault-search/session-search-scope-entry-points.test.ts
  • src/main/ai-vault-search/session-search-scope-service.test.ts
  • src/main/ai-vault-search/session-search-service-registry.test.ts
  • src/main/ai-vault-search/session-search-service-registry.ts
  • src/main/ipc/ai-vault-search.test.ts
  • src/main/runtime/rpc/methods/ai-vault-search.test.ts
  • src/shared/ai-vault-search-agent-negotiation.ts
  • src/shared/ai-vault-search-client.ts
  • src/shared/ai-vault-search-contract.ts
  • src/shared/ai-vault-search-qoder-client.test.ts
  • tests/e2e/cross-version-wire/qoder-history-search-downgrade.unit.test.ts
  • tests/e2e/cross-version-wire/session-search-agent-set-downgrade.unit.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Comment thread src/shared/ai-vault-search-client.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The negotiation itself looks correct, but three unchanged test suites assert the pre-negotiation wire shape and now fail (7 tests). CI will be red until they are updated.

Reviewed changes

  • Agent negotiation helper. New src/shared/ai-vault-search-agent-negotiation.ts defines the frozen LEGACY_SESSION_SEARCH_AGENTS vocabulary and sessionSearchAgentsForPeer, which intersects a peer's advertised list with AI_VAULT_AGENTS and otherwise falls back to the legacy set (plus qoder when supportsQoderHistory is set).
  • Contract. Optional supportedAgents: string[] added to both the search request and the search status schemas, so a peer without the field stays parseable.
  • Host narrowing. searchSessionService filters requested agents to the peer's set before search; an empty intersection keeps the narrow filter and retrieves under a no-match scope, returning an empty page rather than widening. sessionSearchServiceStatus advertises the build's full list.
  • Client negotiation. For any non-IPC request carrying an agent filter, the client reads searchStatus, narrows the filter to the host's advertised list, and returns unsupported-agent when nothing is shared. Every request now carries supportsQoderHistory: true and supportedAgents: [...AI_VAULT_AGENTS].
  • Docs and tests. remote-wire-compatibility.md gains a section, and new/updated cross-version controls pin the b49 request and response parsers.

Verified while reviewing: b49 (pinned pre-Jcode) has aiVault.searchStatus, so the client's new unconditional status call does not break it, and its non-strict request/status schemas strip the added fields. b49's unavailable.reason enum lacks unsupported-agent, and the host never emits it (it returns an empty page instead), so old clients stay parseable.

⚠️ Three unchanged suites assert the pre-negotiation wire shape and now fail

The shared client now always appends supportedAgents, and the host now withholds Jcode from peers that do not advertise a list — but three test files that pin the old shape were not updated. I ran them on this branch: src/cli/handlers/search.test.ts (5 failures), src/renderer/src/web/preload-api/web-ai-vault-search-compat.test.ts (1), and src/relay/session-search-transport.test.ts (1). Each uses an exact-argument assertion that no longer matches, so CI cannot go green as-is.

Technical details
# Unchanged tests pin the pre-negotiation search wire shape

## Affected sites
- `src/cli/handlers/search.test.ts:105` and `:164` — `toHaveBeenCalledWith('aiVault.searchSessions', { query, limit, supportsQoderHistory: true })`; the actual call also carries `supportedAgents: [...AI_VAULT_AGENTS]`.
- `src/renderer/src/web/preload-api/web-ai-vault-search-compat.test.ts:21` — `toHaveBeenCalledExactlyOnceWith(... { query, limit, supportsQoderHistory: true })`; same missing `supportedAgents`.
- `src/relay/session-search-transport.test.ts:56` — expects `filters: { agents: AI_VAULT_AGENTS.filter((agent) => agent !== 'qoder') }` (which includes `jcode`); a request without `supportedAgents` is now narrowed to `LEGACY_SESSION_SEARCH_AGENTS`, which drops `jcode` too.

## Required outcome
- The three suites should assert the new wire shape so CI passes.
- The relay case should expect `LEGACY_SESSION_SEARCH_AGENTS` (the intended behavior change: a peer that does not advertise a list cannot receive Jcode), not the old "everything but qoder" set.

## Suggested approach
- Import `LEGACY_SESSION_SEARCH_AGENTS` (and/or `AI_VAULT_AGENTS`) into the two client-transport suites and include `supportedAgents: [...AI_VAULT_AGENTS]` in the expected params.
- In `session-search-transport.test.ts`, replace the inline `AI_VAULT_AGENTS.filter(...)` expectation with `[...LEGACY_SESSION_SEARCH_AGENTS]`.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Join current per-agent negotiation while retaining empty-peer no-match scope and consent precedence.
Join current per-agent negotiation while retaining empty-peer no-match scope and consent precedence.
@nwparker nwparker changed the title Keep session search compatible across agent versions fix(search): keep legacy host search and negotiate agent support Oct 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3ff31216-a632-4b0d-973a-98fad8787229
📥 Commits

Reviewing files that changed from the base of the PR and between 7f71ffe and 583650d.

📒 Files selected for processing (49)
  • config/build-plugins/jsonc-parser-esm.ts
  • config/scripts/build-relay.mjs
  • docs/reference/remote-wire-compatibility.md
  • src/main/agent-hooks/server-opencode-tui-identity.test.ts
  • src/main/agent-hooks/server/server-ingest-normalization.ts
  • src/main/agent-hooks/server/server-runtime-env.ts
  • src/main/ai-vault-search/session-search-agent-compatibility.test.ts
  • src/main/ai-vault-search/session-search-service-registry.test.ts
  • src/main/ai-vault-search/session-search-service-registry.ts
  • src/main/opencode/hook-plugin-legacy-tui-ownership.test.ts
  • src/main/opencode/hook-service-legacy-acl.test.ts
  • src/main/opencode/hook-service.test.ts
  • src/main/opencode/opencode-legacy-plugin-loader-fixture.ts
  • src/main/opencode/opencode-legacy-tui-fixture.ts
  • src/main/opencode/status-plugin-delivery-source.ts
  • src/main/opencode/status-plugin-endpoint-source.ts
  • src/main/opencode/status-plugin-legacy-tui-source.ts
  • src/main/opencode/status-plugin-post-source.ts
  • src/main/opencode/status-plugin-runtime-state-source.ts
  • src/main/opencode/status-plugin-shared-server-capability.test.ts
  • src/main/opencode2/status-plugin-setup-source.ts
  • src/main/opencode2/status-plugin-tui-source.ts
  • src/main/runtime/rpc/methods/ai-vault-search.test.ts
  • src/main/ssh/relay-native-dependency-coverage.test.ts
  • src/relay/agent-hook-endpoint-coordinates.ts
  • src/relay/agent-hook-opencode-tui-identity.test.ts
  • src/relay/agent-hook-request.ts
  • src/relay/agent-hook-server-contract.ts
  • src/relay/agent-hook-server.ts
  • src/relay/agent-hook-spool-ingest.ts
  • src/relay/pty-handler-agent-launch-token.test.ts
  • src/relay/pty-handler.ts
  • src/relay/relay-agent-hook-runtime.ts
  • src/relay/relay-endpoint-credential-publication.test.ts
  • src/relay/skill-upload-multi-relay.integration.test.ts
  • src/relay/subprocess.test.ts
  • src/shared/agent-hook-listener-opencode-tui-identity.test.ts
  • src/shared/agent-hook-listener-relay-dependency.test.ts
  • src/shared/agent-hook-listener.ts
  • src/shared/agent-hook-listener/endpoint-publication.ts
  • src/shared/agent-hook-listener/opencode-session-registry.ts
  • src/shared/agent-hook-spool.ts
  • src/shared/ai-vault-search-client.ts
  • src/shared/ai-vault-search-status-fallback.test.ts
  • src/shared/opencode-tui-config-registration.test.ts
  • src/shared/opencode-tui-config-registration.ts
  • src/shared/opencode-tui-plugin-install.ts
  • tests/e2e/cross-version-wire/qoder-history-search-downgrade.unit.test.ts
  • tests/e2e/cross-version-wire/session-search-agent-set-downgrade.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/e2e/cross-version-wire/session-search-agent-set-downgrade.unit.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Comment thread src/shared/opencode-tui-plugin-install.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

CI will be red: the client's new unconditional status probe (ai-vault-search-client.ts:55) breaks src/cli/handlers/search.test.ts "sends filters", whose call mock still answers every method with a search-results envelope.

Reviewed changes

  • Adopted main's compatibility policy. The join drops the first commit's parallel ai-vault-search-agent-negotiation.ts helper and reuses main's existing ai-vault-search-agent-compatibility.ts (Qoder/Jcode flags, frozen legacy vocabulary).
  • Client negotiates every remote agent filter. Any non-IPC request with agent filters now probes aiVault.searchStatus (new readSessionSearchStatus), narrows the filter to the peer's advertised list, and returns unavailable/unsupported-agent on an empty intersection. A missing status method still searches the conservative legacy subset; other status errors propagate.
  • Host returns an empty page on an empty intersection. searchSessionService keeps the narrow filter, retrieves under the existing no-match scope {kind:'resolved', paths:['']}, and publishes {hits:[], page:{cursor:null,hasMore:false}} instead of unavailable/unsupported-agent, preserving disabled/not-ready and within-unknown precedence.
  • Coverage and docs. Updated CLI/web/relay suites pin the new wire shape; added ai-vault-search-status-fallback.test.ts and the cross-version session-search-agent-set-downgrade.unit.test.ts; documented the negotiation in remote-wire-compatibility.md.

The prior review's one finding (three suites pinning the old wire shape) is now mostly resolved — the CLI/web expectations include the added fields and the relay suite expects the legacy subset — but one CLI case still fails, flagged inline.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/shared/ai-vault-search-client.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

The delta since the prior pullfrog review (583650d225) is merge commit f323450345, which brings main up to date and fixes the one CLI fixture the previous review flagged. Production search code is byte-identical to the previously reviewed source.

  • CLI runSearch fixture routes by RPC method. The call mock now answers aiVault.searchStatus with a valid status envelope and every other method with the configured search result, so the unconditional status probe no longer throws a ZodError.
  • sends filters asserts the probe-then-search sequence. It now expects two calls, checks the aiVault.searchStatus probe ({}) runs first, and still asserts aiVault.searchSessions carries the filter plus supportedAgents, supportsQoderHistory, and supportsJcodeHistory.

Verified locally: pnpm test src/cli/handlers/search.test.ts (19/19) and the compatibility + status-fallback suites (32/32). The client thread from the prior review (ai-vault-search-client.ts:55) is resolved.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Preserve every accepted component postimage and all unowned main entries.
Retain the public component ancestry and prior source-qualified proof.
@nwparker
nwparker merged commit 136b990 into main Oct 4, 2026
39 checks passed
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.

1 participant