Skip to content

feat: enable default websearch#271

Merged
Astro-Han merged 8 commits into
devfrom
codex/feat-i132-websearch
Apr 27, 2026
Merged

feat: enable default websearch#271
Astro-Han merged 8 commits into
devfrom
codex/feat-i132-websearch

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Expose WebSearch by default and keep WebFetch allowed by default while CodeSearch remains gated.
  • Add saved Exa key support with precedence: saved key, EXA_API_KEY, bundled anonymous Exa MCP endpoint.
  • Add General settings controls for Web Search, Exa key save/remove, and recovery toasts for quota or invalid-key failures.

Why

PawWork should provide out-of-the-box web search. Users can start without configuring an API key, and only need to add EXA_API_KEY or a saved Exa key when the bundled quota is exhausted or their saved key needs attention.

Related Issue

Closes #132

How To Verify

bun install --frozen-lockfile
cd packages/opencode && bun test test/tool/registry.test.ts test/agent/agent.test.ts test/tool/mcp-exa.test.ts test/tool/websearch-auth.test.ts test/tool/websearch.test.ts
cd packages/opencode && bun run typecheck
cd packages/app && bun test --preload ./happydom.ts ./src/pages/session/websearch-toasts.test.ts ./src/pages/session/settings-websearch-source.test.ts
cd packages/app && bun run typecheck
cd packages/desktop-electron && bun test src/main/websearch-ipc-source.test.ts
cd packages/desktop-electron && bun run typecheck
cd packages/app && bun run test:e2e --grep "PawWork settings opens as a full-pane surface"

All commands above passed in this worktree.

Screenshots or Recordings

No screenshot attached. The visible change is a General settings row and Exa API key control; the Settings surface was checked with the existing Playwright smoke e2e, and the new controls are covered by source contract tests.

Checklist

  • I linked the related issue, or stated why there is no issue
  • This PR has type, scope, and priority labels, or I requested maintainer labeling
  • I listed the relevant verification steps, including tests when behavior changed
  • I manually checked visible UI or copy changes when needed, with screenshots or recordings
  • I considered macOS and Windows impact for desktop, packaging, updater, signing, paths, shell, or permissions changes
  • I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, or generated/local file changes when relevant
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English

Summary by CodeRabbit

  • New Features

    • Web Search: toggle in General settings, Manage Key dialog to add/remove Exa API keys, localized status chips, and guidance treating search results as untrusted.
    • Tools now appear or hide based on the Web Search setting.
  • Notifications

    • Recovery toasts for quota-exceeded and invalid-key with quick Open Settings action; session-scoped deduplication.
  • Translations

    • Complete English and Chinese copy for the Web Search UX.
  • Tests

    • Expanded coverage for auth, tooling exposure, toasts, credential handling, and settings persistence.

@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@Astro-Han has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 37 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6a80f3b3-0610-41b3-89a5-77bbfbf49f7e

📥 Commits

Reviewing files that changed from the base of the PR and between b0bcf56 and 68bd2b6.

📒 Files selected for processing (33)
  • .github/workflows/ci.yml
  • packages/app/src/app.tsx
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/context/settings.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/opencode/src/node.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/tool/websearch-auth.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/src/tool/websearch.txt
  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/github/ci-workflow.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📝 Walkthrough

Walkthrough

Adds end-to-end Web Search (Exa) support: UI toggle and dialog, IPC/preload APIs, persisted settings sync, WebSearchAuth service, MCP Exa client credentialing and failure classification, tool gating in registry, recovery toasts in timeline, and comprehensive tests and i18n.

Changes

Cohort / File(s) Summary
App types & global API
packages/app/src/app.tsx, packages/desktop-electron/src/preload/types.ts, packages/desktop-electron/src/main/env.d.ts
Introduce WebSearchStatus type and augment global/preload/main APIs with setWebSearchEnabled, webSearchStatus/webSearchEnabled, saveExaApiKey, and removeExaApiKey.
Renderer settings & UI
packages/app/src/context/settings.tsx, packages/app/src/components/settings-general.tsx, packages/app/src/components/dialog-connect-websearch.tsx, packages/app/src/components/dialog-connect-websearch-source.test.ts, packages/app/src/pages/session/settings-websearch-source.test.ts
Persist general.webSearchEnabled (default true) with IPC sync/rollback; add settings row + toggle, DialogConnectWebSearch (save/remove flows), and contract tests validating UI/actions.
Renderer toasts & timeline
packages/app/src/pages/session/websearch-toasts.ts, packages/app/src/pages/session/message-timeline.tsx, packages/app/src/pages/session/websearch-toasts.test.ts
Add websearch recovery-toast generator with dedupe by id; MessageTimeline effect surfaces error toasts for websearch failures and attaches Open Settings action; tests for variants and deduplication.
i18n
packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts
Add English and Chinese localization entries for Web Search settings, dialog flows, status chips, toasts, and related actions/messages.
Electron main & preload
packages/desktop-electron/src/main/ipc.ts, packages/desktop-electron/src/preload/index.ts, packages/desktop-electron/src/preload/types.ts, packages/desktop-electron/src/main/websearch-ipc-source.test.ts
Register IPC handlers (websearch-set-enabled, websearch-status, websearch-save-exa-key, websearch-remove-exa-key) that call WebSearchAuth and ToolRegistry invalidation; expose corresponding window.api methods via preload; add source-contract tests.
Core settings & registry
packages/opencode/src/settings/index.ts, packages/opencode/src/tool/registry.ts, packages/opencode/src/node.ts
Persist runtime webSearchEnabled, add async accessors/wrappers, wire WebSearchAuth into ToolRegistry layer/defaultLayer, re-export WebSearchAuth, and gate exposure of the websearch tool on the new setting.
WebSearch auth service
packages/opencode/src/tool/websearch-auth.ts, packages/opencode/test/tool/websearch-auth.test.ts
New WebSearchAuth service: saved/env/anonymous credential resolution, Status shape, save/remove APIs, mark-needs-attention on failures, and tests ensuring persistence and status behavior.
MCP Exa client & websearch tool
packages/opencode/src/tool/mcp-exa.ts, packages/opencode/test/tool/mcp-exa.test.ts, packages/opencode/src/tool/websearch.ts, packages/opencode/test/tool/websearch.test.ts, packages/opencode/src/tool/websearch.txt
Parameterize MCP calls by credential; add McpExaError and failure classification (invalid_key, quota_exceeded, network, unknown); normalize SSE/non-2xx errors; propagate failures into tool metadata and mark auth needs-attention; add tests and guidance text.
Tests & harness updates
packages/app/src/pages/session/settings-websearch-source.test.ts, packages/app/src/components/dialog-connect-websearch-source.test.ts, various packages/opencode/test/*
Add multiple unit/contract tests for IPC surface, dialog branches, tool exposure/invalidation, auth persistence, failure handling; update test layer wiring to include WebSearchAuth.
CI
.github/workflows/ci.yml, packages/opencode/test/github/ci-workflow.test.ts
Windows CI shard invocation updated to include test/settings in Bun test arguments; corresponding test asserting workflow contents adjusted.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant SettingsUI as Settings UI
    participant Renderer as Renderer
    participant Preload as Preload (window.api)
    participant Main as Electron Main
    participant AuthSvc as WebSearchAuth
    participant Store as Auth Store

    User->>SettingsUI: enter Exa API key -> save
    SettingsUI->>Renderer: trigger save flow
    Renderer->>Preload: window.api.saveExaApiKey(key)
    Preload->>Main: invoke websearch-save-exa-key IPC
    Main->>AuthSvc: WebSearchAuth.saveKey(key)
    AuthSvc->>AuthSvc: validate/save -> compute Status
    alt validation succeeds
        AuthSvc->>Store: persist credential metadata
        AuthSvc-->>Main: return Status
        Main-->>Preload: resolve Status
        Preload-->>Renderer: resolve Status
        Renderer->>SettingsUI: update UI, show success toast
    else validation fails
        AuthSvc-->>Main: throw error
        Main-->>Preload: reject
        Preload-->>Renderer: reject
        Renderer->>SettingsUI: show error
    end
Loading
sequenceDiagram
    participant WebTool as WebSearchTool
    participant AuthSvc as WebSearchAuth
    participant Mcp as McpExa.call
    participant HTTP as HTTP Client
    participant Timeline as MessageTimeline
    participant Toast as Toast UI

    WebTool->>AuthSvc: credential()
    AuthSvc-->>WebTool: credential
    WebTool->>Mcp: call(query, credential)
    Mcp->>HTTP: request to endpoint
    alt quota_exceeded / invalid_key
        HTTP-->>Mcp: 429 / 401
        Mcp->>WebTool: throw McpExaError(failure)
        WebTool->>WebTool: record metadata.webSearch.failure
        WebTool->>AuthSvc: markNeedsAttention(failure)
        WebTool-->>Session: fail
        Session->>Timeline: part contains failure metadata
        Timeline->>Toast: webSearchRecoveryToast(part)
        Toast-->>User: show recovery toast with "Open Settings" action
    else network/other
        HTTP-->>Mcp: network error
        Mcp->>WebTool: throw McpExaError(network)
        WebTool-->>Session: fail (no recovery toast)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I found a tiny API key tucked in a nook,

I saved it neat so searches could look,
If quota grumbles or keys go astray,
A hopping toast will show the way,
Click Settings, mend it, then bound on your day.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: enabling web search as a default feature across the application.
Description check ✅ Passed Description covers Summary, Why, Related Issue, How To Verify, and Checklist sections as required by the template, with clear objectives and verification commands.
Linked Issues check ✅ Passed Changes fully address issue #132 requirements: WebSearch/WebFetch exposed by default, saved Exa key support with credential precedence, General settings controls, recovery toasts, and untrusted input guidance in websearch.txt.
Out of Scope Changes check ✅ Passed All changes directly support web search enablement and Exa key management; no unrelated features or extraneous modifications detected in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/feat-i132-websearch

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

@Astro-Han Astro-Han added enhancement New feature or request app Application behavior and product flows harness Model harness, prompts, tool descriptions, and session mechanics ui Design system and user interface platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions P1 High priority labels Apr 27, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive Web Search feature using the Exa API. It includes a new settings UI for toggling the tool and managing API keys (saved, environment, or anonymous), IPC handlers for Electron integration, and localized strings. The core logic refactors the Exa MCP tool to handle credential management, failure classification, and recovery toasts for quota or key issues. Additionally, the tool description is updated with safety guidelines for handling untrusted external text. A potential issue was identified in the error handling logic where nested custom error instances might occur during timeouts.

Comment thread packages/opencode/src/tool/mcp-exa.ts

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Review of #271 (default WebSearch). Surface lines up with the LSP toggle pattern; copy, i18n, and sanitization tests look good. Inline notes below, graded P1 to P3. The two P1s: silent partial failure in websearch-set-enabled (renderer rollback never fires) and Settings.webSearchEnabled only living in an in-memory Ref (lost on restart and racy with first-paint reads).

Comment thread packages/desktop-electron/src/main/ipc.ts
Comment thread packages/opencode/src/settings/index.ts Outdated
Comment thread packages/opencode/src/tool/websearch-auth.ts Outdated
Comment thread packages/opencode/src/tool/websearch-auth.ts Outdated
Comment thread packages/opencode/src/tool/websearch-auth.ts Outdated
Comment thread packages/app/src/pages/session/message-timeline.tsx
Comment thread packages/app/src/components/settings-general.tsx Outdated
Comment thread packages/app/src/components/settings-general.tsx Outdated
Comment thread packages/app/src/components/settings-general.tsx Outdated
Comment thread packages/desktop-electron/src/preload/types.ts

@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: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/src/components/settings-general.tsx`:
- Around line 385-405: The Save and Remove Exa key actions can run concurrently
because each button only disables itself; update the buttons and handlers so
both are disabled while either operation is in progress and guard the functions
to no-op if an operation is already running: change the Remove button's disabled
prop to include store.savingExaKey (e.g., disabled={store.removingExaKey ||
store.savingExaKey}) and the Save button's disabled prop to include
store.removingExaKey (e.g., disabled={store.savingExaKey || store.removingExaKey
|| !window.api?.saveExaApiKey}), and add early-return guards at the top of
removeExaApiKey and saveExaApiKey to return immediately if store.savingExaKey ||
store.removingExaKey to prevent racey concurrent calls.

In `@packages/app/src/pages/session/websearch-toasts.ts`:
- Around line 44-65: The current dedupe uses only input.seen timestamps (with
WEBSEARCH_TOAST_TTL_MS) which re-arms old failures on later rerenders; change
the logic to track that a failure was already surfaced permanently instead of
only by time. Specifically, add or use a persistent per-failure marker (e.g., a
Set like input.surfaced or a boolean flag object in input.seen) and check that
marker (alongside any TTL if still needed) before creating a toast; when you
create the toast for an id (the same id from callID/part.id), mark it as
surfaced (input.surfaced.add(id) or set {seen:true}) so subsequent rerenders
ignore that id regardless of input.now and WEBSEARCH_TOAST_TTL_MS. Ensure you
update references to input.seen, id, input.now and WEBSEARCH_TOAST_TTL_MS
accordingly and remove the re-arming behavior that only relies on timestamp.

In `@packages/opencode/src/tool/mcp-exa.ts`:
- Around line 149-173: The current timeout only wraps http.execute(request)
leaving response.text read outside the timed effect; move the body consumption
into the same timed pipeline by replacing the post-timeout yield* response.text
call with an Effect.flatMap (or Effect.map) on the timed response so that
reading the body (response.text) is executed inside the Effect.timeoutOrElse
block; preserve the existing error mapping (isMcpExaError -> McpExaError) and
failure handling (classifyFailure, messageForFailure) and return parseSse(body,
credential.source) as before, ensuring all references to http.execute,
Effect.timeoutOrElse, response.text, parseSse, classifyFailure,
messageForFailure, credential.source, and McpExaError are handled inside the
same effect chain.

In `@packages/opencode/test/agent/agent.test.ts`:
- Around line 440-447: Replace the manual Instance.provide(...) usage with the
test fixture helper provideTmpdirInstance(...) (or provideInstance(...) if
tmpdir not needed) to ensure proper setup/teardown; specifically, remove the
tmpdir() + Instance.provide block and call provideTmpdirInstance(async ({ path:
tmpPath }) => { const build = await Agent.get("build"); expect(evalPerm(build,
"websearch")).toBe("allow"); }) (or use provideInstance with a tmpdir created
inside) so the test uses the standard instance-scoped fixture helpers instead of
direct Instance.provide.

In `@packages/opencode/test/tool/mcp-exa.test.ts`:
- Around line 1-90: Replace bun:test async tests with the Effect test harness:
import testEffect from "test/lib/effect.ts", add const it = testEffect() at the
top, and convert each test(...) to it(...) whose body is an Effect.gen(function*
() { ... }). Inside those generators remove direct Effect.runPromise calls and
instead yield the Effect returned by McpExa.call (or capture its result via
yield* / yield as appropriate), keep the HttpClient.make handlers and assertions
but run them inside the Effect.gen body, and ensure you reference McpExa.call,
McpExa.SearchArgs, Effect.gen and testEffect when editing each test.

In `@packages/opencode/test/tool/websearch.test.ts`:
- Around line 21-78: The tests in this file should be migrated from manual
Effect.runPromise + Effect.provide(...) chains to the testEffect harness from
test/lib/effect.ts: replace each test that builds an Effect pipeline (the ones
using Effect.runPromise, Effect.provide(WebSearchAuth.layer), authLayer,
Layer.succeed(HttpClient.HttpClient, http), Truncate.defaultLayer,
Agent.defaultLayer) with testEffect(...) so the harness supplies layers and runs
the effect; import testEffect, convert the expectations to return the
harness-run effect (preserving the same tool invocation via
WebSearchTool.init()/execute and the metadata capture callback), and remove
ad-hoc provides and Effect.runPromise calls while ensuring the anonymous-quota
test still asserts the thrown quota error and metadata contents.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 18ec7b79-efe4-4111-9972-dc0fcfea0783

📥 Commits

Reviewing files that changed from the base of the PR and between 99f98ff and 3fd2455.

📒 Files selected for processing (28)
  • packages/app/src/app.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/context/settings.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/opencode/src/node.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/tool/websearch-auth.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/src/tool/websearch.txt
  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts
👮 Files not reviewed due to content moderation or server errors (8)
  • packages/app/src/i18n/zh.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/app.tsx
  • packages/app/src/context/settings.tsx
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/app/src/pages/session/message-timeline.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-desktop
  • GitHub Check: typecheck
  • GitHub Check: unit-opencode
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: analyze-js-ts
  • GitHub Check: e2e-artifacts
🧰 Additional context used
📓 Path-based instructions (5)
packages/opencode/**/*.ts

📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)

packages/opencode/**/*.ts: Use Effect.gen(function* () { ... }) for Effect composition
Use Effect.fn("Domain.method") for named/traced effects and Effect.fnUntraced for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer .pipe() wrappers
Use Effect.callback for callback-based APIs
Prefer DateTime.nowAsDate over new Date(yield* Clock.currentTimeMillis) when you need a Date in Effect code
Use Schema.Class for multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
Use Schema.TaggedErrorClass for typed errors in Effect schemas
Use Schema.Defect instead of unknown for defect-like causes in Effect code
In Effect.gen / Effect.fn, prefer yield* new MyError(...) over yield* Effect.fail(new MyError(...)) for direct early-failure branches
Use makeRuntime from src/effect/run-service.ts for all services; it returns { runPromise, runFork, runCallback } backed by a shared memoMap that deduplicates layers
Use InstanceState from src/effect/instance-state.ts for per-directory or per-project state that needs per-instance cleanup; do work directly in the InstanceState.make closure where ScopedCache handles run-once semantics
Use Effect.addFinalizer or Effect.acquireRelease inside the InstanceState.make closure for cleanup (subscriptions, process teardown, etc.)
Use Effect.forkScoped inside the InstanceState.make closure for background stream consumers — the fiber is interrupted when the instance is disposed
Prefer FileSystem.FileSystem instead of raw fs/promises for effectful file I/O in Effect services
Prefer ChildProcessSpawner.ChildProcessSpawner with ChildProcess.make(...) instead of custom process wrappers in Effect services
Prefer HttpClient.HttpClient instead of raw fetch in Effect services
Prefer Path.Path, Config, Clock, and DateTime services when those concerns are already inside Effect code
For backgroun...

Files:

  • packages/opencode/src/node.ts
  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/src/tool/websearch-auth.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/test/tool/websearch.test.ts
packages/app/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/app/AGENTS.md)

Always prefer createStore over multiple createSignal calls in SolidJS

Files:

  • packages/app/src/i18n/zh.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/app.tsx
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/context/settings.tsx
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/pages/session/message-timeline.tsx
packages/opencode/test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)

packages/opencode/test/**/*.test.{ts,tsx}: Use the tmpdir function from fixture/fixture.ts to create temporary directories for tests with automatic cleanup. Use await using syntax to ensure automatic cleanup when the variable goes out of scope.
When using the tmpdir function with git repository support, pass the git: true option to initialize a git repo with a root commit.
Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object.
Use the init option in tmpdir to define custom setup functions that can return extra data accessible via tmp.extra, and use the dispose option for custom cleanup logic.
Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows.
Use it.effect(...) when the test should run with TestClock and TestConsole. Use it.live(...) when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers from fixture/fixture.ts over building manual runtimes in tests: use tmpdirScoped() for scoped temp directories, provideInstance(dir)(effect) for low-level binding without directory creation, provideTmpdirInstance(...) for single temp instance binding, or provideTmpdirServer(...) for tests that also need the test LLM server.
Define const it = testEffect(...) near the top of the test file and keep the test body inside Effect.gen(function* () { ... }). Yield services directly with yield* MyService.Service or yield* MyTool.
Avoid custom ManagedRuntime, attach(...), or ad hoc run(...) wrappers in Effect tests when testEffect(...) already provides the runtime.
When a test needs instance-local state, prefer provideTmpdirInstance(...) or provideInstance(...) over manual Instance.provide(...) inside Promise-style tests.

Files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts
packages/desktop-electron/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)

Renderer process should only call window.api from src/preload

Files:

  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/main/ipc.ts
packages/desktop-electron/src/main/ipc.ts

📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)

Main process should register IPC handlers in src/main/ipc.ts

Files:

  • packages/desktop-electron/src/main/ipc.ts
🧠 Learnings (39)
📚 Learning: 2026-04-25T17:57:25.923Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 236
File: packages/opencode/src/lsp/index.ts:170-180
Timestamp: 2026-04-25T17:57:25.923Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/settings/index.ts`), `Settings.Service` initializes `lspEnabled` via `Ref.make<boolean>(false)`, so LSP is opt-in (default off) across all runtimes (CLI, embedded server, Electron). The singleton `memoMap` from `makeRuntime` in `src/effect/run-service.ts` guarantees a single `Ref` cell shared across all callers. Do not flag `Settings.defaultLayer` or `Settings.lspEnabled()` as defaulting to `true` or being enabled by default — the default is explicitly `false` and verified by `packages/opencode/test/settings.test.ts`.

Applied to files:

  • packages/opencode/src/node.ts
  • packages/app/src/context/settings.tsx
  • packages/opencode/test/tool/registry.test.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/opencode/src/tool/registry.ts
📚 Learning: 2026-04-27T08:58:00.665Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:531-538
Timestamp: 2026-04-27T08:58:00.665Z
Learning: When using Effect (e.g., `yield*` with `Effect`-style generator yielding), only use `yield* new SomeErrorClass(...)` if `SomeErrorClass` extends `Schema.TaggedErrorClass` (i.e., it implements Effect’s Yieldable interface). For plain `Error` subclasses (like `BlockedLoopError` / `LoopStopError`) or inline `new Error(...)` values, they are not yieldable and must be wrapped as `yield* Effect.fail(new PlainError(...))`. Do not recommend changing `yield* Effect.fail(new SomePlainError(...))` to `yield* new SomePlainError(...)` unless the error class extends `Schema.TaggedErrorClass`.

Applied to files:

  • packages/opencode/src/node.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/websearch-auth.ts
  • packages/opencode/src/tool/mcp-exa.ts
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.

Applied to files:

  • packages/app/src/i18n/zh.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Applied to files:

  • packages/app/src/i18n/zh.ts
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.

Applied to files:

  • packages/app/src/i18n/zh.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/app.tsx
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/context/settings.tsx
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/pages/session/message-timeline.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.

Applied to files:

  • packages/app/src/i18n/zh.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/app.tsx
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/context/settings.tsx
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/pages/session/message-timeline.tsx
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : When a test needs instance-local state, prefer `provideTmpdirInstance(...)` or `provideInstance(...)` over manual `Instance.provide(...)` inside Promise-style tests.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/tool/registry.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `tmpdir` function from `fixture/fixture.ts` to create temporary directories for tests with automatic cleanup. Use `await using` syntax to ensure automatic cleanup when the variable goes out of scope.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `init` option in `tmpdir` to define custom setup functions that can return extra data accessible via `tmp.extra`, and use the `dispose` option for custom cleanup logic.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Define `const it = testEffect(...)` near the top of the test file and keep the test body inside `Effect.gen(function* () { ... })`. Yield services directly with `yield* MyService.Service` or `yield* MyTool`.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/src/tool/registry.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-22T09:32:54.556Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Prefer fluent helpers and drivers when they make intent obvious and reduce locator-heavy noise in tests

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` wrappers in Effect tests when `testEffect(...)` already provides the runtime.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `it.effect(...)` when the test should run with `TestClock` and `TestConsole`. Use `it.live(...)` when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-27T08:27:40.791Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:255-279
Timestamp: 2026-04-27T08:27:40.791Z
Learning: In `packages/opencode/src/session/diagnostics.ts` (PR `#264`, issue `#229`), target-level loop detection is intentionally tool-scoped. The `real` filter includes `r.tool === input.tool` and target signature keys are formatted as `target:${tool}:${targetHash}`. Cross-tool retries on the same URL/path/query are considered legitimate exploration (e.g., webfetch failed → switch to a different tool), NOT a stuck loop. Only same-tool + same-target accumulation counts toward block/stop. Do NOT suggest removing the tool scope from target signature keys or the `real` filter.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (packages/desktop-electron/src/main/), follow the IPC registration convention: the bootstrap entry (packages/desktop-electron/src/main/index.ts) should directly call each module’s exported register*Ipc() function. Do not route/centralize these sub-module IPC registrations through src/main/ipc.ts. Keep sub-module IPC features cohesive (e.g., src/main/ipc/about.ts should own its types/helpers and expose register*Ipc()), and allow index.ts to aggregate by calling each register*Ipc() directly.

Applied to files:

  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/main/ipc.ts
📚 Learning: 2026-04-20T14:36:08.774Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/**/*.{ts,tsx,js,jsx} : Renderer process should only call `window.api` from `src/preload`

Applied to files:

  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : In terminal tests, type through the browser using `runTerminal()` and `waitTerminalReady()` instead of writing to the PTY through the SDK

Applied to files:

  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
📚 Learning: 2026-04-24T17:12:23.931Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/desktop-electron/electron-builder.config.ts:14-18
Timestamp: 2026-04-24T17:12:23.931Z
Learning: In Astro-Han/pawwork, the `localizedMacDisplayNameByChannel` map in `packages/desktop-electron/electron-builder.config.ts` is intentionally kept separate from `localizedAppDisplayName` in `packages/desktop-electron/src/main/app-display-name.ts`. The former is a build-time packaging helper; the latter is a runtime UI helper that localizes the current app name by locale. Coupling them would introduce a build-time dependency on runtime main logic. Do not suggest deduplicating or sharing this mapping — the explicit local table is covered by focused regression tests in `electron-builder-app-update.test.ts`.

Applied to files:

  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Import test utilities from `../fixtures` instead of `playwright/test`

Applied to files:

  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (`packages/desktop-electron/src/main/`), the convention for IPC registration is that `index.ts` (the bootstrap entry) directly calls each registration function. `registerIpcHandlers({...})` in `src/main/ipc.ts` is a single fat options-bag with inline handler bodies. New, cohesive IPC features (e.g., About) are placed in sub-modules like `src/main/ipc/about.ts`, which own their own exported types, helpers, and a `register*Ipc()` function. The bootstrap in `index.ts` calls each `register*Ipc()` directly — this is intentional, not fragmentation. Do NOT suggest routing sub-module IPC registrations through `ipc.ts`.

Applied to files:

  • packages/desktop-electron/src/preload/index.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/packages/app/src/testing/**/*.ts : Test-only hooks must be inert unless explicitly enabled and should not add normal-runtime listeners, reactive subscriptions, or per-update allocations

Applied to files:

  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Test one feature per test file

Applied to files:

  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use `expect.poll(...)` for probing mock or backend state rather than transient DOM visibility

Applied to files:

  • packages/app/src/pages/session/websearch-toasts.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use SCREAMING_SNAKE_CASE for constants in tests

Applied to files:

  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Prefer the `project` fixture for tests that need a dedicated project with LLM mocking

Applied to files:

  • packages/opencode/test/tool/registry.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `HttpClient.HttpClient` instead of raw `fetch` in Effect services

Applied to files:

  • packages/opencode/src/tool/websearch.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use lowercase, descriptive test names (e.g., 'sidebar can be toggled')

Applied to files:

  • packages/app/src/pages/session/settings-websearch-source.test.ts
📚 Learning: 2026-04-25T17:57:25.923Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 236
File: packages/opencode/src/lsp/index.ts:170-180
Timestamp: 2026-04-25T17:57:25.923Z
Learning: When reviewing pawwork’s settings implementation, confirm that LSP is opt-in: `Settings.Service` should initialize `lspEnabled` as `Ref.make<boolean>(false)` (default explicitly off) and not be enabled by default. Do not flag `Settings.lspEnabled()` or related defaults as defaulting to `true`; the default must remain `false` and should be consistent with the coverage in `packages/opencode/test/settings.test.ts`. If changes affect how the LSP runtime state is shared, ensure the intended single `Ref` cell behavior (via the `memoMap`/`makeRuntime` mechanism) is preserved.

Applied to files:

  • packages/opencode/src/settings/index.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `makeRuntime` from `src/effect/run-service.ts` for all services; it returns `{ runPromise, runFork, runCallback }` backed by a shared `memoMap` that deduplicates layers

Applied to files:

  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-26T16:34:54.895Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:54.895Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.

Applied to files:

  • packages/app/src/pages/session/message-timeline.tsx
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Defect` instead of `unknown` for defect-like causes in Effect code

Applied to files:

  • packages/opencode/src/tool/mcp-exa.ts
🔇 Additional comments (16)
packages/desktop-electron/src/preload/types.ts (2)

25-29: WebSearchStatus contract is clear and consistent.

The new status type cleanly models source, configured state, and attention state for renderer consumers.


109-112: Preload API expansion is well-typed and cohesive.

The new web search methods fit the existing ElectronAPI async surface and keep status-returning calls explicit.

packages/opencode/src/settings/index.ts (2)

8-9: State split and defaults are implemented correctly.

Separating lspEnabled and webSearchEnabled into distinct refs avoids shared-state coupling and keeps defaults explicit.

Also applies to: 17-23


34-35: Public async wrappers are consistent with existing settings accessors.

The new forwarding methods keep the module-level API aligned with the service contract.

packages/desktop-electron/src/main/env.d.ts (1)

40-42: Type declarations are aligned with the new server contract.

Settings web-search toggles and WebSearchAuth.Status shape are consistent and complete for the virtual module boundary.

Also applies to: 44-52

packages/opencode/src/node.ts (1)

10-10: Entrypoint export wiring looks correct.

Re-exporting WebSearchAuth here cleanly exposes the new auth/status API to downstream consumers.

packages/opencode/src/tool/websearch.txt (1)

12-14: The new source-safety guidance is strong and explicit.

These bullets clearly reinforce that fetched/search content is evidence only and must not override system/developer/user instructions.

packages/opencode/test/session/snapshot-tool-race.test.ts (1)

54-54: Test layer wiring update is correct.

Providing WebSearchAuth.defaultLayer in the registry stack keeps this harness aligned with the updated tool dependency graph.

Also applies to: 134-134

packages/opencode/test/session/prompt-effect.test.ts (1)

39-39: Prompt-effect harness dependency update looks good.

The added WebSearchAuth provision keeps ToolRegistry construction complete under the new web search auth requirements.

Also applies to: 181-181

packages/desktop-electron/src/preload/index.ts (1)

86-89: Preload API wiring is clean and correctly scoped.

These additions keep renderer-facing capabilities in preload and map directly to dedicated IPC channels.

packages/opencode/src/tool/registry.ts (1)

99-100: ToolRegistry integration for WebSearch auth/enablement looks coherent.

Service requirements, default layer provisioning, and per-tool enablement filtering are consistent with the intended behavior split between Web Search and Code Search.

Also applies to: 325-330, 393-394

packages/desktop-electron/src/main/websearch-ipc-source.test.ts (1)

11-33: Good contract coverage for IPC/preload/source boundaries.

The assertions validate channel presence and exposed API/type surface end-to-end at the source level.

packages/app/src/pages/session/settings-websearch-source.test.ts (1)

10-42: Static settings-source contract checks are well targeted.

This captures the default toggle behavior, API-key state boundary, and localization surface expected by the feature.

packages/desktop-electron/src/main/ipc.ts (1)

209-244: Web Search IPC handlers are correctly integrated in main process.

The toggle/status/key handlers map cleanly to server APIs, and per-instance invalidation is failure-tolerant.

packages/opencode/test/tool/websearch-auth.test.ts (1)

35-47: 🛠️ Refactor suggestion | 🟠 Major

Replace custom runWith helper with testEffect + it.live harness to align with test patterns.

This file tests Effect services (WebSearchAuth.Service) but uses an ad hoc runWith(...) runtime wrapper with Effect.runPromise instead of the shared testEffect harness. Per packages/opencode/test/AGENTS.md, all Effect service tests should use testEffect(...) and yield services directly, avoiding custom runtime wrappers.

Migrate lines 35–47 and refactor the test body (lines 52–147) to use:

const it = testEffect(Layer.mergeAll(WebSearchAuth.layer, ...))

describe("WebSearchAuth", () => {
  it.live("test name", () =>
    Effect.gen(function* () {
      // test body
    }),
  )
})
Original pattern to replace:
function runWith(input: {
  auth: ReturnType<typeof authLayer>
  http: HttpClient.HttpClient
  effect: Effect.Effect<unknown, unknown, WebSearchAuth.Service>
}) {
  return Effect.runPromise(
    input.effect.pipe(
      Effect.provide(WebSearchAuth.layer),
      Effect.provide(input.auth.layer),
      Effect.provide(Layer.succeed(HttpClient.HttpClient, input.http)),
    ),
  )
}
⛔ Skipped due to learnings
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Define `const it = testEffect(...)` near the top of the test file and keep the test body inside `Effect.gen(function* () { ... })`. Yield services directly with `yield* MyService.Service` or `yield* MyTool`.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` wrappers in Effect tests when `testEffect(...)` already provides the runtime.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `it.effect(...)` when the test should run with `TestClock` and `TestConsole`. Use `it.live(...)` when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `makeRuntime` from `src/effect/run-service.ts` for all services; it returns `{ runPromise, runFork, runCallback }` backed by a shared `memoMap` that deduplicates layers
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `FileSystem.FileSystem` instead of raw `fs/promises` for effectful file I/O in Effect services
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `ChildProcessSpawner.ChildProcessSpawner` with `ChildProcess.make(...)` instead of custom process wrappers in Effect services
packages/opencode/src/tool/websearch-auth.ts (1)

47-121: Nice separation of credential state and runtime wiring.

The saved → env → anonymous precedence is centralized cleanly, and the service/layer/runtime wrapper setup matches the established Effect service pattern.

Comment thread packages/app/src/components/settings-general.tsx Outdated
Comment thread packages/app/src/pages/session/websearch-toasts.ts Outdated
Comment thread packages/opencode/src/tool/mcp-exa.ts Outdated
Comment thread packages/opencode/test/agent/agent.test.ts
Comment thread packages/opencode/test/tool/mcp-exa.test.ts
Comment thread packages/opencode/test/tool/websearch.test.ts

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Thorough review of the websearch default-enable feature. No confirmed P0. Several P1 findings worth attention before merge.

Security review: key never leaks to renderer (verified by tests). exaApiKey in query string is pre-existing and follows the Exa MCP API contract. type="password" on the API key input field. Good.

Test coverage: strong. Unit tests for McpExa (credential routing, isError, unknown), WebSearchAuth (validate-then-save, key precedence, removal), websearch tool (failure metadata), toasts (dedupe, TTL, network exclusion), registry visibility toggle, IPC contract test, and source-level assertion tests for settings UI surface. Integration tests wire up all layers including prompt-effect and snapshot-tool-race.

Comment thread packages/desktop-electron/src/main/ipc.ts
Comment thread packages/opencode/src/tool/mcp-exa.ts
Comment thread packages/opencode/src/tool/mcp-exa.ts
Comment thread packages/opencode/src/tool/mcp-exa.ts
Comment thread packages/opencode/src/tool/mcp-exa.ts
Comment thread packages/app/src/pages/session/websearch-toasts.ts Outdated
Comment thread packages/opencode/src/tool/mcp-exa.ts Outdated
Comment thread packages/opencode/src/tool/websearch-auth.ts

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/src/components/dialog-connect-websearch.tsx`:
- Around line 196-213: The Remove and Save buttons allow concurrent operations
because their disabled props only check their own loading state; update the
Button disabled checks to consider both states (e.g., use disabled={removing()
|| saving()} for the Remove button and disabled={saving() || removing() ||
apiKeyInput().trim() === ""} for the Save button) and add early-return guards in
the handlers (handleRemove and handleSave) so they no-op if either saving() or
removing() is true to prevent race conditions.
- Around line 133-148: The save and remove buttons can run concurrently because
the Save/Update Button only checks saving() while the Remove Button only checks
removing(); update the button disabled logic so each pair prevents the other
operation: add removing() to the Save/Update button's disabled condition and add
saving() to the Remove button's disabled condition for the current pair (buttons
wired to handleSave and handleRemove) and apply the same symmetric change to the
other button pairs in this component (the other buttons that call
handleSave/handleRemove or use saving()/removing()) so no save and remove
requests can start concurrently.

In `@packages/opencode/src/tool/websearch-auth.ts`:
- Around line 18-23: The saveKey method currently leaks a generic Error in
Interface.saveKey; define a specific typed error (e.g., EmptyKeyError) using
Schema.TaggedErrorClass and replace the bare Error in the saveKey return type
(so signature becomes Effect.Effect<Status, Auth.AuthError | EmptyKeyError |
McpExa.McpExaError>), then update the saveKey implementation to fail early by
yielding a new EmptyKeyError (use yield* new EmptyKeyError(...) inside
Effect.gen / Effect.fn) for the empty-key validation path; ensure imports and
exports reflect the new TaggedErrorClass.

In `@packages/opencode/test/tool/websearch-auth.test.ts`:
- Around line 1-3: Replace the custom runtime wrapper `runWith` by adopting the
shared test harness: import `testEffect` from "test/lib/effect.ts", add `const
it = testEffect(...)` near the top of the file, and change each `runWith("...")`
or `test("...")` usage to `it("...", () => Effect.gen(function* () { ... }))`
(or wrap existing test bodies inside `Effect.gen` as other tests do); remove the
local `runWith` helper and any `bun:test`-specific runtime setup so the file
uses `testEffect` consistently with other tests in the directory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 30b7ad43-ceb8-4466-97b9-6e939881e584

📥 Commits

Reviewing files that changed from the base of the PR and between 3fd2455 and c076853.

📒 Files selected for processing (11)
  • packages/app/src/app.tsx
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/opencode/src/tool/websearch-auth.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-opencode
  • GitHub Check: unit-desktop
  • GitHub Check: analyze-js-ts
  • GitHub Check: e2e-artifacts
🧰 Additional context used
📓 Path-based instructions (4)
packages/desktop-electron/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)

Renderer process should only call window.api from src/preload

Files:

  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/main/env.d.ts
packages/app/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/app/AGENTS.md)

Always prefer createStore over multiple createSignal calls in SolidJS

Files:

  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/app.tsx
  • packages/app/src/i18n/en.ts
packages/opencode/**/*.ts

📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)

packages/opencode/**/*.ts: Use Effect.gen(function* () { ... }) for Effect composition
Use Effect.fn("Domain.method") for named/traced effects and Effect.fnUntraced for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer .pipe() wrappers
Use Effect.callback for callback-based APIs
Prefer DateTime.nowAsDate over new Date(yield* Clock.currentTimeMillis) when you need a Date in Effect code
Use Schema.Class for multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
Use Schema.TaggedErrorClass for typed errors in Effect schemas
Use Schema.Defect instead of unknown for defect-like causes in Effect code
In Effect.gen / Effect.fn, prefer yield* new MyError(...) over yield* Effect.fail(new MyError(...)) for direct early-failure branches
Use makeRuntime from src/effect/run-service.ts for all services; it returns { runPromise, runFork, runCallback } backed by a shared memoMap that deduplicates layers
Use InstanceState from src/effect/instance-state.ts for per-directory or per-project state that needs per-instance cleanup; do work directly in the InstanceState.make closure where ScopedCache handles run-once semantics
Use Effect.addFinalizer or Effect.acquireRelease inside the InstanceState.make closure for cleanup (subscriptions, process teardown, etc.)
Use Effect.forkScoped inside the InstanceState.make closure for background stream consumers — the fiber is interrupted when the instance is disposed
Prefer FileSystem.FileSystem instead of raw fs/promises for effectful file I/O in Effect services
Prefer ChildProcessSpawner.ChildProcessSpawner with ChildProcess.make(...) instead of custom process wrappers in Effect services
Prefer HttpClient.HttpClient instead of raw fetch in Effect services
Prefer Path.Path, Config, Clock, and DateTime services when those concerns are already inside Effect code
For backgroun...

Files:

  • packages/opencode/src/tool/websearch-auth.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
packages/opencode/test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)

packages/opencode/test/**/*.test.{ts,tsx}: Use the tmpdir function from fixture/fixture.ts to create temporary directories for tests with automatic cleanup. Use await using syntax to ensure automatic cleanup when the variable goes out of scope.
When using the tmpdir function with git repository support, pass the git: true option to initialize a git repo with a root commit.
Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object.
Use the init option in tmpdir to define custom setup functions that can return extra data accessible via tmp.extra, and use the dispose option for custom cleanup logic.
Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows.
Use it.effect(...) when the test should run with TestClock and TestConsole. Use it.live(...) when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers from fixture/fixture.ts over building manual runtimes in tests: use tmpdirScoped() for scoped temp directories, provideInstance(dir)(effect) for low-level binding without directory creation, provideTmpdirInstance(...) for single temp instance binding, or provideTmpdirServer(...) for tests that also need the test LLM server.
Define const it = testEffect(...) near the top of the test file and keep the test body inside Effect.gen(function* () { ... }). Yield services directly with yield* MyService.Service or yield* MyTool.
Avoid custom ManagedRuntime, attach(...), or ad hoc run(...) wrappers in Effect tests when testEffect(...) already provides the runtime.
When a test needs instance-local state, prefer provideTmpdirInstance(...) or provideInstance(...) over manual Instance.provide(...) inside Promise-style tests.

Files:

  • packages/opencode/test/tool/websearch-auth.test.ts
🧠 Learnings (19)
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Define `const it = testEffect(...)` near the top of the test file and keep the test body inside `Effect.gen(function* () { ... })`. Yield services directly with `yield* MyService.Service` or `yield* MyTool`.

Applied to files:

  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Test one feature per test file

Applied to files:

  • packages/app/src/components/dialog-connect-websearch-source.test.ts
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.

Applied to files:

  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/app.tsx
  • packages/app/src/i18n/en.ts
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.

Applied to files:

  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/app.tsx
  • packages/app/src/i18n/en.ts
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Applied to files:

  • packages/app/src/i18n/zh.ts
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.

Applied to files:

  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/i18n/en.ts
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.

Applied to files:

  • packages/app/src/components/settings-general.tsx
📚 Learning: 2026-04-24T05:39:56.086Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.

Applied to files:

  • packages/app/src/components/settings-general.tsx
📚 Learning: 2026-04-25T17:57:25.923Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 236
File: packages/opencode/src/lsp/index.ts:170-180
Timestamp: 2026-04-25T17:57:25.923Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/settings/index.ts`), `Settings.Service` initializes `lspEnabled` via `Ref.make<boolean>(false)`, so LSP is opt-in (default off) across all runtimes (CLI, embedded server, Electron). The singleton `memoMap` from `makeRuntime` in `src/effect/run-service.ts` guarantees a single `Ref` cell shared across all callers. Do not flag `Settings.defaultLayer` or `Settings.lspEnabled()` as defaulting to `true` or being enabled by default — the default is explicitly `false` and verified by `packages/opencode/test/settings.test.ts`.

Applied to files:

  • packages/desktop-electron/src/main/env.d.ts
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (packages/desktop-electron/src/main/), follow the IPC registration convention: the bootstrap entry (packages/desktop-electron/src/main/index.ts) should directly call each module’s exported register*Ipc() function. Do not route/centralize these sub-module IPC registrations through src/main/ipc.ts. Keep sub-module IPC features cohesive (e.g., src/main/ipc/about.ts should own its types/helpers and expose register*Ipc()), and allow index.ts to aggregate by calling each register*Ipc() directly.

Applied to files:

  • packages/desktop-electron/src/main/env.d.ts
📚 Learning: 2026-04-22T09:32:58.310Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.

Applied to files:

  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `makeRuntime` from `src/effect/run-service.ts` for all services; it returns `{ runPromise, runFork, runCallback }` backed by a shared `memoMap` that deduplicates layers

Applied to files:

  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-27T08:58:00.665Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:531-538
Timestamp: 2026-04-27T08:58:00.665Z
Learning: When using Effect (e.g., `yield*` with `Effect`-style generator yielding), only use `yield* new SomeErrorClass(...)` if `SomeErrorClass` extends `Schema.TaggedErrorClass` (i.e., it implements Effect’s Yieldable interface). For plain `Error` subclasses (like `BlockedLoopError` / `LoopStopError`) or inline `new Error(...)` values, they are not yieldable and must be wrapped as `yield* Effect.fail(new PlainError(...))`. Do not recommend changing `yield* Effect.fail(new SomePlainError(...))` to `yield* new SomePlainError(...)` unless the error class extends `Schema.TaggedErrorClass`.

Applied to files:

  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.

Applied to files:

  • packages/opencode/test/tool/websearch-auth.test.ts
📚 Learning: 2026-04-22T09:32:54.556Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.

Applied to files:

  • packages/opencode/test/tool/websearch-auth.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` wrappers in Effect tests when `testEffect(...)` already provides the runtime.

Applied to files:

  • packages/opencode/test/tool/websearch-auth.test.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.

Applied to files:

  • packages/opencode/test/tool/websearch-auth.test.ts
🔇 Additional comments (13)
packages/desktop-electron/src/preload/types.ts (2)

25-30: WebSearchStatus is defined in multiple places.

This type is also defined identically in packages/app/src/app.tsx (lines 69-74). Consider exporting this type from a single source (e.g., here in preload/types.ts or via @opencode-ai/app/desktop-api) and importing it elsewhere to prevent drift when adding fields later.


110-113: LGTM!

The new ElectronAPI methods are correctly typed and align with the preload implementation in packages/desktop-electron/src/preload/index.ts.

packages/app/src/app.tsx (2)

69-74: Duplicate type definition.

This WebSearchStatus type is identical to the one in packages/desktop-electron/src/preload/types.ts. See prior comment about consolidating to a single source.


88-97: LGTM!

The Window.api augmentation correctly adds the optional websearch methods, matching the ElectronAPI interface in preload/types.ts. The optional markers (?) are appropriate since these APIs are only available in the Electron environment.

packages/app/src/components/settings-general.tsx (2)

190-199: Transient wrong-status flash on initial load.

Using webSearchStatusResource.latest ?? { source: "anonymous", ... } renders the "Free · bundled" chip immediately, even when a saved key exists, until the resource resolves. Consider wrapping status-dependent UI in <Show when={webSearchStatusResource.state !== "pending"}> or displaying a brief loading state to avoid showing transiently incorrect information.


289-330: LGTM!

The Web Search settings row is well-implemented:

  • Status chip correctly maps all source/needsAttention/quotaExceeded combinations.
  • Secondary text provides recovery guidance for failed/exhausted states.
  • "Manage key" button correctly opens DialogConnectWebSearch via useDialog.
  • Toggle is properly wired to settings.general.webSearchEnabled().
packages/app/src/i18n/en.ts (1)

843-880: LGTM!

The new i18n keys are well-structured, follow the established naming conventions, and cover all the documented web search states (bundled, exhausted, personal, env, invalid) for settings, dialogs, and toast notifications. The keys align with their Chinese counterparts in zh.ts.

packages/app/src/components/dialog-connect-websearch-source.test.ts (1)

1-36: LGTM!

The contract test follows the established string-matching pattern used elsewhere in this repo. It verifies:

  • Component export
  • All four state branches (anonymous, env, saved, needsAttention)
  • API method calls via regex patterns
  • Correct Dialog import
  • Absence of disallowed dependencies (useProviders, globalSDK)

Based on learnings, this lightweight contract guard approach is the intentional pattern for this codebase.

packages/app/src/i18n/zh.ts (1)

732-768: LGTM!

The Chinese translations are well-crafted, natural, and consistent with the existing zh.ts patterns. The branding correctly uses 爪印 without the PawWork prefix, per the established Chinese localization guidelines. All keys align with their English counterparts in en.ts.

packages/app/src/pages/session/settings-websearch-source.test.ts (1)

1-53: LGTM!

This contract test provides comprehensive coverage of the web search settings integration:

  • Verifies webSearchEnabled defaults to true and IPC wiring exists
  • Confirms General settings UI includes expected data-action attributes and dialog usage
  • Ensures API key state isn't leaked into settings context
  • Validates all websearch i18n keys exist in both en.ts and zh.ts

The string-matching approach follows the established pattern for contract tests in this codebase.

packages/app/src/components/dialog-connect-websearch.tsx (1)

12-97: LGTM on component structure and validation handling.

The component correctly:

  • Fetches status via createResource and provides a sensible fallback
  • Clears validation errors on input change (addressing prior review feedback)
  • Disables Save when input is empty (addressing prior review feedback)
  • Uses separate signals for independent state, which is appropriate per project learnings
  • Shows inline validation errors for key-related failures and toasts for other errors
packages/desktop-electron/src/main/env.d.ts (1)

40-53: Type surface matches the new IPC contract.

These additions line up cleanly with the websearch IPC handlers and keep the renderer-facing shape explicit without exposing key material in the Status contract. Nice, minimal expansion of the virtual module surface.

packages/opencode/src/tool/websearch-auth.ts (1)

30-51: Good separation between credential storage and UI-facing status.

savedCredential(), statusFrom(), and markNeedsAttention() keep the API key out of the returned status shape while still preserving the state the UI needs (saved vs env vs anonymous, attention, quota exhaustion). That’s the right boundary for this service.

Also applies to: 92-118

Comment thread packages/app/src/components/dialog-connect-websearch.tsx Outdated
Comment thread packages/app/src/components/dialog-connect-websearch.tsx
Comment thread packages/opencode/src/tool/websearch-auth.ts
Comment thread packages/opencode/test/tool/websearch-auth.test.ts Outdated
@Astro-Han Astro-Han force-pushed the codex/feat-i132-websearch branch 2 times, most recently from 2306f87 to b0bcf56 Compare April 27, 2026 11:27

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/src/components/dialog-connect-websearch.tsx`:
- Around line 16-25: The code currently fabricates an "anonymous" default in the
status memo when webSearchStatusResource.latest is undefined, which hides
initial load errors; instead stop returning a default status and surface the
resource's loading/error state until the first successful fetch. Change the
status logic (the createMemo that reads webSearchStatusResource.latest) to
return undefined (or forward the resource state) when latest is undefined, and
update any rendering to branch on webSearchStatusResource.state or the presence
of status (isLoading/isError) so you show a loading/error UI rather than the
anonymous setup; ensure createResource calling window.api?.webSearchStatus
properly propagates promise rejections so errors are visible.

In `@packages/opencode/src/settings/index.ts`:
- Around line 29-43: The persistLock currently only wraps the storage read/write
so Ref mutations (Ref.set in setLspEnabled and setWebSearchEnabled) happen after
the permit is released, allowing races; move the Ref.set calls inside the same
persistLock.withPermit block so the in-memory Ref updates occur while the permit
is held (e.g. adjust persist or the withPermit callback to perform storage.write
and Ref.set(lspEnabled, value) / Ref.set(webSearchEnabled, value) together),
ensuring setLspEnabled and setWebSearchEnabled invoke that combined effect
(referencing persistLock, persist, Ref.set, storage.write, setLspEnabled,
setWebSearchEnabled, lspEnabled, webSearchEnabled).

In `@packages/opencode/src/tool/mcp-exa.ts`:
- Around line 103-107: The SSE decode call can throw a raw parse error that
bypasses McpExaError handling; wrap the decode invocation (the call to
decode(line.substring(6))) in a try/catch (or the Effect equivalent) and on any
thrown/failed decode create a failure object { kind: "unknown", source } and
return yield* Effect.fail(new McpExaError(failure, messageForFailure(failure))).
Keep the existing path that uses classifyFailure only for data.result.isError,
but ensure all decode exceptions are normalized to McpExaError using the same
messageForFailure helper and the same source variable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d7dd4a8d-b9b0-47ab-b37b-52218c0bb607

📥 Commits

Reviewing files that changed from the base of the PR and between c076853 and 2306f87.

📒 Files selected for processing (31)
  • packages/app/src/app.tsx
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/context/settings.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/opencode/src/node.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/tool/websearch-auth.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/src/tool/websearch.txt
  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-opencode
  • GitHub Check: unit-desktop
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: e2e-artifacts
  • GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (5)
packages/opencode/**/*.ts

📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)

packages/opencode/**/*.ts: Use Effect.gen(function* () { ... }) for Effect composition
Use Effect.fn("Domain.method") for named/traced effects and Effect.fnUntraced for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer .pipe() wrappers
Use Effect.callback for callback-based APIs
Prefer DateTime.nowAsDate over new Date(yield* Clock.currentTimeMillis) when you need a Date in Effect code
Use Schema.Class for multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
Use Schema.TaggedErrorClass for typed errors in Effect schemas
Use Schema.Defect instead of unknown for defect-like causes in Effect code
In Effect.gen / Effect.fn, prefer yield* new MyError(...) over yield* Effect.fail(new MyError(...)) for direct early-failure branches
Use makeRuntime from src/effect/run-service.ts for all services; it returns { runPromise, runFork, runCallback } backed by a shared memoMap that deduplicates layers
Use InstanceState from src/effect/instance-state.ts for per-directory or per-project state that needs per-instance cleanup; do work directly in the InstanceState.make closure where ScopedCache handles run-once semantics
Use Effect.addFinalizer or Effect.acquireRelease inside the InstanceState.make closure for cleanup (subscriptions, process teardown, etc.)
Use Effect.forkScoped inside the InstanceState.make closure for background stream consumers — the fiber is interrupted when the instance is disposed
Prefer FileSystem.FileSystem instead of raw fs/promises for effectful file I/O in Effect services
Prefer ChildProcessSpawner.ChildProcessSpawner with ChildProcess.make(...) instead of custom process wrappers in Effect services
Prefer HttpClient.HttpClient instead of raw fetch in Effect services
Prefer Path.Path, Config, Clock, and DateTime services when those concerns are already inside Effect code
For backgroun...

Files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/src/node.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/websearch-auth.ts
packages/opencode/test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)

packages/opencode/test/**/*.test.{ts,tsx}: Use the tmpdir function from fixture/fixture.ts to create temporary directories for tests with automatic cleanup. Use await using syntax to ensure automatic cleanup when the variable goes out of scope.
When using the tmpdir function with git repository support, pass the git: true option to initialize a git repo with a root commit.
Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object.
Use the init option in tmpdir to define custom setup functions that can return extra data accessible via tmp.extra, and use the dispose option for custom cleanup logic.
Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows.
Use it.effect(...) when the test should run with TestClock and TestConsole. Use it.live(...) when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers from fixture/fixture.ts over building manual runtimes in tests: use tmpdirScoped() for scoped temp directories, provideInstance(dir)(effect) for low-level binding without directory creation, provideTmpdirInstance(...) for single temp instance binding, or provideTmpdirServer(...) for tests that also need the test LLM server.
Define const it = testEffect(...) near the top of the test file and keep the test body inside Effect.gen(function* () { ... }). Yield services directly with yield* MyService.Service or yield* MyTool.
Avoid custom ManagedRuntime, attach(...), or ad hoc run(...) wrappers in Effect tests when testEffect(...) already provides the runtime.
When a test needs instance-local state, prefer provideTmpdirInstance(...) or provideInstance(...) over manual Instance.provide(...) inside Promise-style tests.

Files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
packages/app/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/app/AGENTS.md)

Always prefer createStore over multiple createSignal calls in SolidJS

Files:

  • packages/app/src/app.tsx
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/context/settings.tsx
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/i18n/zh.ts
packages/desktop-electron/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)

Renderer process should only call window.api from src/preload

Files:

  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/main/ipc.ts
packages/desktop-electron/src/main/ipc.ts

📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)

Main process should register IPC handlers in src/main/ipc.ts

Files:

  • packages/desktop-electron/src/main/ipc.ts
🧠 Learnings (60)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch.test.ts:21-78
Timestamp: 2026-04-27T11:18:45.847Z
Learning: In `packages/opencode/test/tool/websearch.test.ts`, the tests intentionally use manual `Effect.runPromise` with explicit `Effect.provide(...)` chains (including `Layer.succeed(Auth.Service, ...)`, `Layer.succeed(HttpClient.HttpClient, http)`, `WebSearchAuth.layer`, `Truncate.defaultLayer`, and `Agent.defaultLayer`) rather than the `testEffect(...)` harness. This is by design: the fake Auth and HTTP recovery-metadata layers must be explicitly injected and kept visible/scoped at the test site. Do NOT suggest migrating these tests to `testEffect` or removing the manual layer provides.
📚 Learning: 2026-04-27T11:18:45.149Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/agent/agent.test.ts:440-447
Timestamp: 2026-04-27T11:18:45.149Z
Learning: In `packages/opencode/test/agent/agent.test.ts` (Astro-Han/pawwork), all agent-permission tests intentionally use the manual `tmpdir()` + `Instance.provide(...)` pattern. Do not flag individual tests in this file for conversion to `provideTmpdirInstance(...)` or `provideInstance(...)`; a full harness migration would be a separate PR if the pattern ever needs to change.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
📚 Learning: 2026-04-27T11:18:45.847Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch.test.ts:21-78
Timestamp: 2026-04-27T11:18:45.847Z
Learning: In `packages/opencode/test/tool/websearch.test.ts`, the tests intentionally use manual `Effect.runPromise` with explicit `Effect.provide(...)` chains (including `Layer.succeed(Auth.Service, ...)`, `Layer.succeed(HttpClient.HttpClient, http)`, `WebSearchAuth.layer`, `Truncate.defaultLayer`, and `Agent.defaultLayer`) rather than the `testEffect(...)` harness. This is by design: the fake Auth and HTTP recovery-metadata layers must be explicitly injected and kept visible/scoped at the test site. Do NOT suggest migrating these tests to `testEffect` or removing the manual layer provides.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/src/node.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/app/src/app.tsx
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/app/src/context/settings.tsx
  • packages/opencode/src/tool/registry.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-27T11:19:21.835Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:21.835Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/src/node.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/src/tool/registry.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : When a test needs instance-local state, prefer `provideTmpdirInstance(...)` or `provideInstance(...)` over manual `Instance.provide(...)` inside Promise-style tests.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/desktop-electron/src/main/ipc.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/src/settings/index.ts
📚 Learning: 2026-04-27T11:18:45.896Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:45.896Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/src/tool/mcp-exa.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `tmpdir` function from `fixture/fixture.ts` to create temporary directories for tests with automatic cleanup. Use `await using` syntax to ensure automatic cleanup when the variable goes out of scope.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Prefer the `project` fixture for tests that need a dedicated project with LLM mocking

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/tool/registry.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `init` option in `tmpdir` to define custom setup functions that can return extra data accessible via `tmp.extra`, and use the `dispose` option for custom cleanup logic.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/tool/registry.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/websearch.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Import test utilities from `../fixtures` instead of `playwright/test`

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Prefer fluent helpers and drivers when they make intent obvious and reduce locator-heavy noise in tests

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.

Applied to files:

  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
📚 Learning: 2026-04-25T17:57:25.923Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 236
File: packages/opencode/src/lsp/index.ts:170-180
Timestamp: 2026-04-25T17:57:25.923Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/settings/index.ts`), `Settings.Service` initializes `lspEnabled` via `Ref.make<boolean>(false)`, so LSP is opt-in (default off) across all runtimes (CLI, embedded server, Electron). The singleton `memoMap` from `makeRuntime` in `src/effect/run-service.ts` guarantees a single `Ref` cell shared across all callers. Do not flag `Settings.defaultLayer` or `Settings.lspEnabled()` as defaulting to `true` or being enabled by default — the default is explicitly `false` and verified by `packages/opencode/test/settings.test.ts`.

Applied to files:

  • packages/opencode/src/node.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/app/src/context/settings.tsx
  • packages/opencode/src/tool/registry.ts
  • packages/desktop-electron/src/main/ipc.ts
📚 Learning: 2026-04-27T08:58:00.665Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:531-538
Timestamp: 2026-04-27T08:58:00.665Z
Learning: When using Effect (e.g., `yield*` with `Effect`-style generator yielding), only use `yield* new SomeErrorClass(...)` if `SomeErrorClass` extends `Schema.TaggedErrorClass` (i.e., it implements Effect’s Yieldable interface). For plain `Error` subclasses (like `BlockedLoopError` / `LoopStopError`) or inline `new Error(...)` values, they are not yieldable and must be wrapped as `yield* Effect.fail(new PlainError(...))`. Do not recommend changing `yield* Effect.fail(new SomePlainError(...))` to `yield* new SomePlainError(...)` unless the error class extends `Schema.TaggedErrorClass`.

Applied to files:

  • packages/opencode/src/node.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Define `const it = testEffect(...)` near the top of the test file and keep the test body inside `Effect.gen(function* () { ... })`. Yield services directly with `yield* MyService.Service` or `yield* MyTool`.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/src/settings/index.ts
📚 Learning: 2026-04-22T09:32:54.556Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
📚 Learning: 2026-04-21T12:14:30.524Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 93
File: packages/opencode/test/server/cors-middleware.test.ts:15-23
Timestamp: 2026-04-21T12:14:30.524Z
Learning: In `packages/opencode/test/server/cors-middleware.test.ts` (and similar Hono-based test files), `app.request(...)` on the Hono `Hono` app type returns `Promise<Response> | Response` — a union that includes a non-PromiseLike branch. `Effect.promise(...)` requires a `PromiseLike`, so wrapping with `Promise.resolve(app.request(...))` is necessary to normalize the union type. Removing the wrapper causes a TS2322 typecheck error in `packages/opencode`.

Applied to files:

  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/src/tool/mcp-exa.ts
📚 Learning: 2026-04-27T08:27:40.791Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:255-279
Timestamp: 2026-04-27T08:27:40.791Z
Learning: In `packages/opencode/src/session/diagnostics.ts` (PR `#264`, issue `#229`), target-level loop detection is intentionally tool-scoped. The `real` filter includes `r.tool === input.tool` and target signature keys are formatted as `target:${tool}:${targetHash}`. Cross-tool retries on the same URL/path/query are considered legitimate exploration (e.g., webfetch failed → switch to a different tool), NOT a stuck loop. Only same-tool + same-target accumulation counts toward block/stop. Do NOT suggest removing the tool scope from target signature keys or the `real` filter.

Applied to files:

  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/app/src/pages/session/websearch-toasts.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use fixture-managed cleanup with `withSession(sdk, title, callback)` for temporary sessions instead of calling `sdk.session.delete(...)` directly

Applied to files:

  • packages/opencode/test/session/snapshot-tool-race.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` wrappers in Effect tests when `testEffect(...)` already provides the runtime.

Applied to files:

  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `it.effect(...)` when the test should run with `TestClock` and `TestConsole`. Use `it.live(...)` when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.

Applied to files:

  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/websearch.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.

Applied to files:

  • packages/app/src/app.tsx
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/context/settings.tsx
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/i18n/zh.ts
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.

Applied to files:

  • packages/app/src/app.tsx
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/context/settings.tsx
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/i18n/zh.ts
📚 Learning: 2026-04-20T14:36:08.774Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/**/*.{ts,tsx,js,jsx} : Renderer process should only call `window.api` from `src/preload`

Applied to files:

  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (`packages/desktop-electron/src/main/`), the convention for IPC registration is that `index.ts` (the bootstrap entry) directly calls each registration function. `registerIpcHandlers({...})` in `src/main/ipc.ts` is a single fat options-bag with inline handler bodies. New, cohesive IPC features (e.g., About) are placed in sub-modules like `src/main/ipc/about.ts`, which own their own exported types, helpers, and a `register*Ipc()` function. The bootstrap in `index.ts` calls each `register*Ipc()` directly — this is intentional, not fragmentation. Do NOT suggest routing sub-module IPC registrations through `ipc.ts`.

Applied to files:

  • packages/desktop-electron/src/preload/index.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Test one feature per test file

Applied to files:

  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.

Applied to files:

  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (packages/desktop-electron/src/main/), follow the IPC registration convention: the bootstrap entry (packages/desktop-electron/src/main/index.ts) should directly call each module’s exported register*Ipc() function. Do not route/centralize these sub-module IPC registrations through src/main/ipc.ts. Keep sub-module IPC features cohesive (e.g., src/main/ipc/about.ts should own its types/helpers and expose register*Ipc()), and allow index.ts to aggregate by calling each register*Ipc() directly.

Applied to files:

  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/main/ipc.ts
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.

Applied to files:

  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/dialog-connect-websearch.tsx
📚 Learning: 2026-04-24T05:39:56.086Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.

Applied to files:

  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/dialog-connect-websearch.tsx
📚 Learning: 2026-04-27T10:33:08.974Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:08.974Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.

Applied to files:

  • packages/app/src/components/settings-general.tsx
  • packages/desktop-electron/src/main/ipc.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/websearch-auth.ts
  • packages/app/src/pages/session/websearch-toasts.ts
📚 Learning: 2026-04-24T17:12:23.931Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/desktop-electron/electron-builder.config.ts:14-18
Timestamp: 2026-04-24T17:12:23.931Z
Learning: In Astro-Han/pawwork, the `localizedMacDisplayNameByChannel` map in `packages/desktop-electron/electron-builder.config.ts` is intentionally kept separate from `localizedAppDisplayName` in `packages/desktop-electron/src/main/app-display-name.ts`. The former is a build-time packaging helper; the latter is a runtime UI helper that localizes the current app name by locale. Coupling them would introduce a build-time dependency on runtime main logic. Do not suggest deduplicating or sharing this mapping — the explicit local table is covered by focused regression tests in `electron-builder-app-update.test.ts`.

Applied to files:

  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : In terminal tests, type through the browser using `runTerminal()` and `waitTerminalReady()` instead of writing to the PTY through the SDK

Applied to files:

  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
📚 Learning: 2026-04-25T12:52:32.462Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/desktop-electron/src/main/ipc.ts:238-263
Timestamp: 2026-04-25T12:52:32.462Z
Learning: In Astro-Han/pawwork (`packages/desktop-electron/src/main/ipc.ts`), `deps.getServerReadyData()` (backed by `serverReady.promise` in `index.ts`) resolves once at server startup and remains settled; it is not expected to reject in practice. Do not flag the absence of a try-catch around it in the `export-session` IPC handler — the network/fetch layer in `server-client.ts` already has a 10-second AbortController timeout and returns a typed `{ok: false, error}` payload, covering the real failure modes.

Applied to files:

  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/opencode/src/tool/mcp-exa.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `HttpClient.HttpClient` instead of raw `fetch` in Effect services

Applied to files:

  • packages/opencode/src/tool/websearch.ts
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Applied to files:

  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
📚 Learning: 2026-04-26T16:34:54.895Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:54.895Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.

Applied to files:

  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/pages/session/websearch-toasts.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `InstanceState` from `src/effect/instance-state.ts` for per-directory or per-project state that needs per-instance cleanup; do work directly in the `InstanceState.make` closure where `ScopedCache` handles run-once semantics

Applied to files:

  • packages/opencode/src/tool/registry.ts
  • packages/desktop-electron/src/main/ipc.ts
📚 Learning: 2026-04-24T13:03:10.835Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 222
File: packages/desktop-electron/src/main/index.ts:686-692
Timestamp: 2026-04-24T13:03:10.835Z
Learning: In `packages/desktop-electron/src/main/index.ts`, the `checkForUpdates()` function intentionally uses recursive self-invocation for the "Retry" path in the update-check failure dialog. This is mandated by the v5.2 design spec (`#213`): "Await the retry recursion and log any rejection so support can see repeated failures." Because retries are user-paced (require a button click), all prior async frames have already unwound through microtasks before the next attempt, so there is no stack/frame-nesting problem in practice. Do not suggest refactoring this to an iterative loop.

Applied to files:

  • packages/desktop-electron/src/main/ipc.ts
  • packages/app/src/pages/session/websearch-toasts.ts
📚 Learning: 2026-04-20T17:03:40.214Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 73
File: packages/opencode/src/cli/cmd/tui/context/sync.tsx:486-489
Timestamp: 2026-04-20T17:03:40.214Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/cli/cmd/tui/context/sync.tsx`), `sync.ready` returning `true` when `process.env.OPENCODE_FAST_BOOT` is set is intentional. The plugin-facing data properties `state.config` (initialized to `{}`) and `state.provider` (initialized to `[]`) expose safe-empty defaults, so they are safe to access before bootstrap completes. Do not flag these as needing null-guards or conditional patterns to match `vcs` — the difference is intentional because `vcs` starts as `undefined` while the others have initialized defaults. Changing this would alter the plugin API contract without a concrete failing case.

Applied to files:

  • packages/desktop-electron/src/main/ipc.ts
  • packages/opencode/src/settings/index.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition

Applied to files:

  • packages/opencode/test/tool/mcp-exa.test.ts
📚 Learning: 2026-04-25T17:57:25.923Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 236
File: packages/opencode/src/lsp/index.ts:170-180
Timestamp: 2026-04-25T17:57:25.923Z
Learning: When reviewing pawwork’s settings implementation, confirm that LSP is opt-in: `Settings.Service` should initialize `lspEnabled` as `Ref.make<boolean>(false)` (default explicitly off) and not be enabled by default. Do not flag `Settings.lspEnabled()` or related defaults as defaulting to `true`; the default must remain `false` and should be consistent with the coverage in `packages/opencode/test/settings.test.ts`. If changes affect how the LSP runtime state is shared, ensure the intended single `Ref` cell behavior (via the `memoMap`/`makeRuntime` mechanism) is preserved.

Applied to files:

  • packages/opencode/src/settings/index.ts
📚 Learning: 2026-04-25T12:52:47.074Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/share/session.ts:27-27
Timestamp: 2026-04-25T12:52:47.074Z
Learning: In `packages/opencode/src/share/session.ts`, the local `ensureEnabled` closure is intentionally duplicated from `ShareRuntime.ensureEnabled` in `runtime.ts`. Using the shared `ShareRuntime.ensureEnabled` effect directly would leak `CloudShareGate` back into the `R` (requirement) type of `share`, `unshare`, and `create`, because the shared effect re-yields the service rather than capturing an already-resolved gate reference. The current pattern resolves `gate` once at the top of the outer `Effect.gen` and then closes over it in a local `ensureEnabled`, keeping the inner effects' requirement types clean. A comment in the file points to `runtime.ts` to make the intentional duplication discoverable. The real fix would be refactoring `ensureEnabled` to accept a gate parameter, but that change is larger than the DRY benefit warrants. Do not flag this duplication as a maintenance issue.

Applied to files:

  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-22T09:32:58.310Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.

Applied to files:

  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `makeRuntime` from `src/effect/run-service.ts` for all services; it returns `{ runPromise, runFork, runCallback }` backed by a shared `memoMap` that deduplicates layers

Applied to files:

  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `Path.Path`, `Config`, `Clock`, and `DateTime` services when those concerns are already inside Effect code

Applied to files:

  • packages/opencode/src/settings/index.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.cached` when multiple concurrent callers should share a single in-flight computation rather than storing `Fiber | undefined` or `Promise | undefined` manually

Applied to files:

  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : In `Effect.gen` / `Effect.fn`, prefer `yield* new MyError(...)` over `yield* Effect.fail(new MyError(...))` for direct early-failure branches

Applied to files:

  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.addFinalizer` or `Effect.acquireRelease` inside the `InstanceState.make` closure for cleanup (subscriptions, process teardown, etc.)

Applied to files:

  • packages/opencode/src/tool/mcp-exa.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : For background loops or scheduled tasks, use `Effect.repeat` or `Effect.schedule` with `Effect.forkScoped` in the layer definition

Applied to files:

  • packages/opencode/src/tool/mcp-exa.ts
📚 Learning: 2026-04-25T12:52:36.999Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/session/export.ts:75-88
Timestamp: 2026-04-25T12:52:36.999Z
Learning: In `packages/opencode/src/session/export.ts` (Astro-Han/pawwork), the `extractReasonFromCause` function intentionally uses a lightweight cast-based inspection of the Cause `reasons` array instead of `Cause.failures()` / `Cause.defects()` utilities. This is a deliberate choice: the function is diagnostic-only (used to populate a best-effort reason string for export diagnostics), and the maintainer explicitly prefers not to add coupling to Effect's Cause API surface for this purpose. Do not flag this pattern as fragile or suggest replacing it with Cause utilities.

Applied to files:

  • packages/opencode/src/tool/mcp-exa.ts
  • packages/app/src/pages/session/websearch-toasts.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Defect` instead of `unknown` for defect-like causes in Effect code

Applied to files:

  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.TaggedErrorClass` for typed errors in Effect schemas

Applied to files:

  • packages/opencode/src/tool/websearch-auth.ts
📚 Learning: 2026-04-27T10:32:59.274Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:245-252
Timestamp: 2026-04-27T10:32:59.274Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/diagnostics.ts`, PR `#264`), `loopLastError` is stored as raw (unscrubbed) error text in loop metadata intentionally. The trust boundary for sensitive data scrubbing is the renderer (`LoopRenderer.render` already applies `scrubErrorText` before any user/model-facing output), not the storage layer. Failed tool parts also already store raw errors in `state.error` (PR `#204` default behavior), so scrubbing only `loopLastError` would not close that pre-existing leak and would add lossy storage. If export-side hardening is needed, the correct approach is a single sanitizer pass over both `state.error` and metadata in the export layer. Do NOT suggest scrubbing `loopLastError` at the `observeToolError` call site.

Applied to files:

  • packages/opencode/src/tool/websearch-auth.ts

Comment thread packages/app/src/components/dialog-connect-websearch.tsx Outdated
Comment thread packages/opencode/src/settings/index.ts Outdated
Comment thread packages/opencode/src/tool/mcp-exa.ts Outdated

@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: 4

♻️ Duplicate comments (3)
packages/opencode/src/settings/index.ts (1)

29-43: ⚠️ Potential issue | 🟠 Major

Keep Ref.set inside the semaphore critical section.

persistLock currently serializes only storage read/write. Concurrent same-key setter calls can still leave the in-memory Ref with an older value than persisted storage due to post-lock reordering.

Proposed fix
-      const persist = (patch: Stored) =>
+      const persist = (patch: Stored, apply: Effect.Effect<void>) =>
         persistLock.withPermit(
           Effect.gen(function* () {
             const current = yield* storage
               .read<Stored>(STORAGE_KEY)
               .pipe(Effect.catch(() => Effect.succeed({} as Stored)))
             yield* storage.write(STORAGE_KEY, { ...current, ...patch })
+            yield* apply
           }),
         )
...
-        setLspEnabled: (value) => persist({ lspEnabled: value }).pipe(Effect.flatMap(() => Ref.set(lspEnabled, value))),
+        setLspEnabled: (value) => persist({ lspEnabled: value }, Ref.set(lspEnabled, value)),
...
-        setWebSearchEnabled: (value) =>
-          persist({ webSearchEnabled: value }).pipe(Effect.flatMap(() => Ref.set(webSearchEnabled, value))),
+        setWebSearchEnabled: (value) =>
+          persist({ webSearchEnabled: value }, Ref.set(webSearchEnabled, value)),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/settings/index.ts` around lines 29 - 43, The
persistLock currently only guards storage.read/write, causing a race where
Ref.set (for lspEnabled and webSearchEnabled) happens after the lock and may be
reordered; move the Ref.set calls into the critical section so the in-memory Ref
and persisted storage are updated atomically: update persist (used by
setLspEnabled and setWebSearchEnabled) to perform both storage.write and the
corresponding Ref.set(lspEnabled, value) or Ref.set(webSearchEnabled, value)
inside the persistLock.withPermit Effect.gen block (i.e., do the storage.read,
storage.write and Ref.set within the same critical section) so no concurrent
setter can leave Ref out-of-sync with storage.
packages/opencode/src/tool/mcp-exa.ts (1)

98-107: ⚠️ Potential issue | 🟠 Major

Normalize malformed SSE frames into McpExaError.

decode(...) can still fail with a raw parse error here. packages/opencode/src/tool/websearch.ts only catches McpExaError to persist metadata.webSearch.failure, so a malformed or truncated data: frame bypasses the recovery-state path entirely and the session toast never appears. Please wrap decode failures as new McpExaError({ kind: "unknown", source }, ...).

Suggested fix
 const parseSse = Effect.fn("McpExa.parseSse")(function* (body: string, source: Credential["source"]) {
   let sawData = false
   for (const line of body.split("\n")) {
     if (!line.startsWith("data: ")) continue
     sawData = true
-    const data = yield* decode(line.substring(6))
+    const data = yield* decode(line.substring(6)).pipe(
+      Effect.mapError(
+        (cause) =>
+          new McpExaError(
+            { kind: "unknown", source },
+            "Web Search returned an invalid Exa response.",
+            { cause },
+          ),
+      ),
+    )
     const text = data.result.content[0]?.text
     if (data.result.isError) {
       const failure = classifyFailure({ body: text ?? "", source })
       return yield* Effect.fail(new McpExaError(failure, messageForFailure(failure)))
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/tool/mcp-exa.ts` around lines 98 - 107, In parseSse,
protect the yield* decode(...) call so any raw parse/throwing failure is
converted into an McpExaError with kind "unknown" and the incoming source;
specifically, wrap the decode invocation (inside parseSse) in a try/catch or
equivalent Effect-handling so that if decode throws or fails you return/yield*
Effect.fail(new McpExaError({ kind: "unknown", source }, messageForFailure({
kind: "unknown", source }))). Keep the existing logic that classifies
data.result errors via classifyFailure/messageForFailure, but ensure
malformed/truncated frames never escape as raw errors from parseSse.
packages/app/src/components/dialog-connect-websearch.tsx (1)

16-25: ⚠️ Potential issue | 🟠 Major

Don't default first-load failures into the anonymous setup flow.

If window.api.webSearchStatus() rejects before the first successful fetch, latest stays undefined and this memo fabricates { source: "anonymous" }. That hides real failures for saved/env-backed setups and renders the wrong recovery UI. Keep an explicit loading/error branch until the resource has resolved successfully.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/components/dialog-connect-websearch.tsx` around lines 16 -
25, The memo `status` currently fabricates a default anonymous status when
`webSearchStatusResource.latest` is undefined, hiding real load errors; change
`status` (and any consumers) to propagate undefined while the resource is
unresolved or errored instead of returning `{ source: "anonymous" }`, i.e. have
`status` simply return `webSearchStatusResource.latest` (or `undefined`) and
update UI logic to explicitly handle loading/error/undefined states based on
`webSearchStatusResource()` / `webSearchStatusResource.latest` (or use resource
meta like `.loading`/`.error`) rather than assuming an anonymous configuration;
update references to `status` in this component so the recovery UI only appears
when a resolved value indicates `source: "anonymous"`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/src/context/settings.tsx`:
- Around line 194-206: The rollback uses the stale `value` captured at effect
start, so when the IPC fails and we flip the setting we may revert to the wrong
previous state; change the catch handler to read the current store/default value
at the time of failure instead of using the closure `value` (i.e. call `const
current = store.general?.webSearchEnabled ??
defaultSettings.general.webSearchEnabled` inside the catch and then
`setStore("general", "webSearchEnabled", !current)`), and apply the same fix to
the analogous lspEnabled effect (the effect that calls `setLspEnabled`/reads
`lspEnabled`) to ensure rollbacks use the most recent state.

In `@packages/app/src/pages/session/websearch-toasts.test.ts`:
- Around line 32-45: The existing test "builds an actionable quota toast without
copying key material" doesn't include a secret key in the input so it doesn't
fully verify leakage protection; update or add a test that calls
webSearchRecoveryToast with part({ callID: "call-1", kind: "quota_exceeded",
source: "anonymous", key: "sk-secret-123" }) (using the same surfaced set) and
assert the produced toast equals the expected shape and that
JSON.stringify(toast) does not contain "key" or the secret string; reference the
test name, the webSearchRecoveryToast function, and the part helper when making
the change.

In `@packages/app/src/pages/session/websearch-toasts.ts`:
- Around line 52-68: The current mapping for invalid_key in the websearch toast
always shows the saved-key/settings recovery copy; update the logic that builds
toast (using context.failure.kind, context.failure.source, and the
WebSearchRecoveryToast shapes) to differentiate by context.failure.source: if
kind === "invalid_key" and source === "saved" (or whatever signal means
user-editable key) keep the existing "openSettings" title/description/actionKey,
but if source === "env" (or non-editable) either pick a different
title/description without an "openSettings" action or set actionKey to undefined
so users aren’t directed to incorrect remediation; ensure you use context.id for
the toast id and preserve the quota_exceeded branch unchanged.

In `@packages/opencode/test/tool/registry.test.ts`:
- Around line 556-575: The test "exposes websearch for non-opencode providers by
default while codesearch stays gated" sets Settings.setWebSearchEnabled(true)
but does not restore the original setting; wrap the test body (the call to
Instance.provide and assertions) in a try/finally (or capture the previous value
before calling Settings.setWebSearchEnabled) and in the finally block call
Settings.setWebSearchEnabled(previousValue) so the global webSearchEnabled state
is restored even if the test fails; reference Settings.setWebSearchEnabled and
the test function name to locate where to add the cleanup.

---

Duplicate comments:
In `@packages/app/src/components/dialog-connect-websearch.tsx`:
- Around line 16-25: The memo `status` currently fabricates a default anonymous
status when `webSearchStatusResource.latest` is undefined, hiding real load
errors; change `status` (and any consumers) to propagate undefined while the
resource is unresolved or errored instead of returning `{ source: "anonymous"
}`, i.e. have `status` simply return `webSearchStatusResource.latest` (or
`undefined`) and update UI logic to explicitly handle loading/error/undefined
states based on `webSearchStatusResource()` / `webSearchStatusResource.latest`
(or use resource meta like `.loading`/`.error`) rather than assuming an
anonymous configuration; update references to `status` in this component so the
recovery UI only appears when a resolved value indicates `source: "anonymous"`.

In `@packages/opencode/src/settings/index.ts`:
- Around line 29-43: The persistLock currently only guards storage.read/write,
causing a race where Ref.set (for lspEnabled and webSearchEnabled) happens after
the lock and may be reordered; move the Ref.set calls into the critical section
so the in-memory Ref and persisted storage are updated atomically: update
persist (used by setLspEnabled and setWebSearchEnabled) to perform both
storage.write and the corresponding Ref.set(lspEnabled, value) or
Ref.set(webSearchEnabled, value) inside the persistLock.withPermit Effect.gen
block (i.e., do the storage.read, storage.write and Ref.set within the same
critical section) so no concurrent setter can leave Ref out-of-sync with
storage.

In `@packages/opencode/src/tool/mcp-exa.ts`:
- Around line 98-107: In parseSse, protect the yield* decode(...) call so any
raw parse/throwing failure is converted into an McpExaError with kind "unknown"
and the incoming source; specifically, wrap the decode invocation (inside
parseSse) in a try/catch or equivalent Effect-handling so that if decode throws
or fails you return/yield* Effect.fail(new McpExaError({ kind: "unknown", source
}, messageForFailure({ kind: "unknown", source }))). Keep the existing logic
that classifies data.result errors via classifyFailure/messageForFailure, but
ensure malformed/truncated frames never escape as raw errors from parseSse.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ad0ae50a-6438-43b9-bed8-8ddf85f4fc37

📥 Commits

Reviewing files that changed from the base of the PR and between 2306f87 and b0bcf56.

📒 Files selected for processing (33)
  • .github/workflows/ci.yml
  • packages/app/src/app.tsx
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/context/settings.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/opencode/src/node.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/tool/websearch-auth.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/src/tool/websearch.txt
  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/github/ci-workflow.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts

Comment thread packages/app/src/context/settings.tsx
Comment thread packages/app/src/pages/session/websearch-toasts.test.ts
Comment thread packages/app/src/pages/session/websearch-toasts.ts
Comment thread packages/opencode/test/tool/registry.test.ts
@Astro-Han Astro-Han force-pushed the codex/feat-i132-websearch branch 2 times, most recently from 341c6ea to a799348 Compare April 27, 2026 11:43

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opencode/src/tool/mcp-exa.ts (1)

144-182: 🛠️ Refactor suggestion | 🟠 Major

Wrap the exported call function with Effect.fn("McpExa.call") to enable tracing.

The call function is a public effect entrypoint that should follow the same tracing convention as parseSse (already defined at line 98 using Effect.fn("McpExa.parseSse")). This ensures consistent traces across the service boundary.

Change:

export const call = <F extends Schema.Struct.Fields>(
  http: HttpClient.HttpClient,
  tool: string,
  args: Schema.Struct<F>,
  value: Schema.Struct.Type<F>,
  timeout: Duration.Input,
  credential: Credential = credentialFromEnv(),
) =>
  Effect.gen(function* () {

to:

export const call = <F extends Schema.Struct.Fields>(
  http: HttpClient.HttpClient,
  tool: string,
  args: Schema.Struct<F>,
  value: Schema.Struct.Type<F>,
  timeout: Duration.Input,
  credential: Credential = credentialFromEnv(),
) =>
  Effect.fn("McpExa.call")(function* () {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/tool/mcp-exa.ts` around lines 144 - 182, The exported
Effect generator call function lacks a tracing label; wrap the existing
Effect.gen entrypoint for export with Effect.fn("McpExa.call") so it matches the
tracing convention used by parseSse (which uses Effect.fn("McpExa.parseSse"));
specifically replace the top-level exporter that returns Effect.gen(...) for the
function named call with a wrapper that returns Effect.fn("McpExa.call")(...)
while leaving the internal generator body unchanged and keeping the same generic
signature and default credential logic.
♻️ Duplicate comments (3)
packages/opencode/test/tool/registry.test.ts (1)

556-575: 🧹 Nitpick | 🔵 Trivial

Consider adding cleanup for test isolation.

This test sets webSearchEnabled = true but lacks a try/finally block to restore the setting, unlike the test at lines 577-615. While true is the default value, explicit cleanup would ensure test isolation if the default ever changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/tool/registry.test.ts` around lines 556 - 575, The
test "exposes websearch for non-opencode providers by default while codesearch
stays gated" sets Settings.setWebSearchEnabled(true) but doesn't restore it;
wrap the test body that calls Instance.provide(...) in a try/finally (or capture
the previous value via Settings.getWebSearchEnabled()) and in the finally call
Settings.setWebSearchEnabled(original) to ensure cleanup; locate the call to
Settings.setWebSearchEnabled in this test and add the restore logic around the
Instance.provide + ToolRegistry.tools usage so the global setting is reset
regardless of test outcome.
packages/app/src/pages/session/websearch-toasts.test.ts (1)

32-45: 🧹 Nitpick | 🔵 Trivial

Consider strengthening the key leakage test.

The test at line 44 asserts not.toContain("key"), but no actual secret value is passed via the part() helper. Adding key: "sk-secret-123" to the part() call would directly verify that sensitive key material from failure metadata doesn't leak into the toast output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/pages/session/websearch-toasts.test.ts` around lines 32 -
45, The test for webSearchRecoveryToast should explicitly verify that secret key
strings from failure metadata aren't leaked: update the test that calls
webSearchRecoveryToast (the "builds an actionable quota toast without copying
key material" test) to pass a secret-like key value in the part() input (e.g.,
include key: "sk-secret-123" in the object passed to part), keep the surfaced
Set usage the same, and assert the resulting toast object equals the expected
shape and that JSON.stringify(toast) does not contain the secret string or the
substring "key"; this ensures webSearchRecoveryToast properly strips key
material from its output.
packages/app/src/pages/session/websearch-toasts.ts (1)

11-16: ⚠️ Potential issue | 🟠 Major

Don't route env-backed invalid keys to the saved-key recovery toast.

invalid_key always produces the saved-key copy plus openSettings. When the failure source is "env", the settings dialog is read-only and cannot remove/update that key, so this sends users to a dead end. Branch on context.failure.source here and either suppress the settings action for env-backed failures or use env-specific copy without an action.

Also applies to: 52-67

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/pages/session/websearch-toasts.ts` around lines 11 - 16, The
WebSearchRecoveryToast currently always uses the saved-key copy and includes the
"toast.websearch.action.openSettings" action even for env-backed failures;
update the code that constructs toasts (referencing WebSearchRecoveryToast and
the toast-creation logic around the earlier block and lines 52-67) to branch on
context.failure.source: when source === "env" use env-specific title/description
keys (or the same keys but omit actionKey) and do not include the openSettings
action, otherwise keep the existing saved-key title/description and actionKey;
ensure the toast payload for env-backed failures contains no actionKey (or uses
env-specific copy) so the read-only settings path is not offered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/desktop-electron/src/main/ipc.ts`:
- Around line 209-233: The handler registered in
ipcMain.handle("websearch-set-enabled") persists the new setting via
Settings.setWebSearchEnabled(value) before calling Instance.provide(...) to run
ToolRegistry.invalidate(), which leaves storage inconsistent if invalidate
fails; move the persistence step after successfully invalidating all instances
(i.e., run the Promise.allSettled over directories -> Instance.provide(...
ToolRegistry.invalidate()) first, check failures and throw if any, and only then
call Settings.setWebSearchEnabled(value)), or alternatively implement a rollback
on failure by reverting Settings.setWebSearchEnabled(!value) inside the failure
branch; mirror the same change/behavior for the analogous "lsp-set-enabled"
handler so both are consistent and reference the symbols
ipcMain.handle("websearch-set-enabled"), Settings.setWebSearchEnabled,
Instance.provide, and ToolRegistry.invalidate when locating the code to change.

In `@packages/opencode/src/settings/index.ts`:
- Around line 25-35: The read fallback currently swallows all errors by using
Effect.catch(... => {}), which hides real read failures; update both startup
read (the stored assignment) and the read inside persist() to only convert a
missing-file error into an empty Stored object and rethrow any other errors.
Replace Effect.catch(() => Effect.succeed({} as Stored)) with a selective
handler (e.g., Effect.catchSome / Effect.catchTag or an error predicate) that
returns Effect.succeed({} as Stored) only when the storage error indicates a
missing/non-existent file (e.g., ENOENT or your storage's NotFound variant) and
otherwise returns Effect.fail(err) so other storage.read(STORAGE_KEY) errors
propagate. Ensure this change is applied to the two places where
storage.read<Stored>(STORAGE_KEY) is called (the initial stored assignment and
the current read inside persist) so persist() no longer silently overwrites
settings when the file is corrupted or temporarily unreadable.

In `@packages/opencode/src/tool/mcp-exa.ts`:
- Around line 98-117: parseSse currently decodes each "data: " line
independently which breaks multi-line SSE events; change it to accumulate
consecutive "data: " lines into a buffer until you hit the SSE event terminator
(blank line), then join that buffer into one payload string and call decode on
the joined payload (preserving the existing Effect.mapError to wrap with
McpExaError). Keep sawData tracking when any data lines are seen, and for each
completed event handle data.result.isError via classifyFailure/messageForFailure
or extract text as before; if the event yields neither continue to the next
event, and only fail at the end if no usable event was produced.

---

Outside diff comments:
In `@packages/opencode/src/tool/mcp-exa.ts`:
- Around line 144-182: The exported Effect generator call function lacks a
tracing label; wrap the existing Effect.gen entrypoint for export with
Effect.fn("McpExa.call") so it matches the tracing convention used by parseSse
(which uses Effect.fn("McpExa.parseSse")); specifically replace the top-level
exporter that returns Effect.gen(...) for the function named call with a wrapper
that returns Effect.fn("McpExa.call")(...) while leaving the internal generator
body unchanged and keeping the same generic signature and default credential
logic.

---

Duplicate comments:
In `@packages/app/src/pages/session/websearch-toasts.test.ts`:
- Around line 32-45: The test for webSearchRecoveryToast should explicitly
verify that secret key strings from failure metadata aren't leaked: update the
test that calls webSearchRecoveryToast (the "builds an actionable quota toast
without copying key material" test) to pass a secret-like key value in the
part() input (e.g., include key: "sk-secret-123" in the object passed to part),
keep the surfaced Set usage the same, and assert the resulting toast object
equals the expected shape and that JSON.stringify(toast) does not contain the
secret string or the substring "key"; this ensures webSearchRecoveryToast
properly strips key material from its output.

In `@packages/app/src/pages/session/websearch-toasts.ts`:
- Around line 11-16: The WebSearchRecoveryToast currently always uses the
saved-key copy and includes the "toast.websearch.action.openSettings" action
even for env-backed failures; update the code that constructs toasts
(referencing WebSearchRecoveryToast and the toast-creation logic around the
earlier block and lines 52-67) to branch on context.failure.source: when source
=== "env" use env-specific title/description keys (or the same keys but omit
actionKey) and do not include the openSettings action, otherwise keep the
existing saved-key title/description and actionKey; ensure the toast payload for
env-backed failures contains no actionKey (or uses env-specific copy) so the
read-only settings path is not offered.

In `@packages/opencode/test/tool/registry.test.ts`:
- Around line 556-575: The test "exposes websearch for non-opencode providers by
default while codesearch stays gated" sets Settings.setWebSearchEnabled(true)
but doesn't restore it; wrap the test body that calls Instance.provide(...) in a
try/finally (or capture the previous value via Settings.getWebSearchEnabled())
and in the finally call Settings.setWebSearchEnabled(original) to ensure
cleanup; locate the call to Settings.setWebSearchEnabled in this test and add
the restore logic around the Instance.provide + ToolRegistry.tools usage so the
global setting is reset regardless of test outcome.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 18b071d8-9f83-498e-99a9-7d4d77f61759

📥 Commits

Reviewing files that changed from the base of the PR and between b0bcf56 and 341c6ea.

📒 Files selected for processing (33)
  • .github/workflows/ci.yml
  • packages/app/src/app.tsx
  • packages/app/src/components/dialog-connect-websearch-source.test.ts
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/context/settings.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/pages/session/settings-websearch-source.test.ts
  • packages/app/src/pages/session/websearch-toasts.test.ts
  • packages/app/src/pages/session/websearch-toasts.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/desktop-electron/src/main/websearch-ipc-source.test.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/opencode/src/node.ts
  • packages/opencode/src/settings/index.ts
  • packages/opencode/src/tool/mcp-exa.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/tool/websearch-auth.ts
  • packages/opencode/src/tool/websearch.ts
  • packages/opencode/src/tool/websearch.txt
  • packages/opencode/test/agent/agent.test.ts
  • packages/opencode/test/github/ci-workflow.test.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/snapshot-tool-race.test.ts
  • packages/opencode/test/settings/settings.test.ts
  • packages/opencode/test/tool/mcp-exa.test.ts
  • packages/opencode/test/tool/registry.test.ts
  • packages/opencode/test/tool/websearch-auth.test.ts
  • packages/opencode/test/tool/websearch.test.ts

Comment thread packages/desktop-electron/src/main/ipc.ts
Comment thread packages/opencode/src/settings/index.ts Outdated
Comment thread packages/opencode/src/tool/mcp-exa.ts
@Astro-Han Astro-Han force-pushed the codex/feat-i132-websearch branch from a799348 to 68bd2b6 Compare April 27, 2026 11:58
@Astro-Han Astro-Han merged commit 85a726f into dev Apr 27, 2026
23 checks passed
@Astro-Han Astro-Han deleted the codex/feat-i132-websearch branch April 27, 2026 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows enhancement New feature or request harness Model harness, prompts, tool descriptions, and session mechanics P1 High priority platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Define default web access and source safety strategy

1 participant