Skip to content

feat(extensions): ship web access as a ThinkRail extension whose switch also covers its skills - #965

Merged
Rinat S (rsolmano) merged 4 commits into
mainfrom
extension-extraction-candidates
Oct 28, 2026
Merged

Rinat S (rsolmano) merged 4 commits into
mainfrom
extension-extraction-candidates

Conversation

@rsolmano

@rsolmano Rinat S (rsolmano) commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Web access (web_search, fetch_content, get_search_content) comes from the third-party pi package pi-web-access, and it was the last pi capability ThinkRail wired in through special cases instead of as a ThinkRail extension:

  • Build special case: both packagers special-cased it (BundledExtensions.webAccessFactory, BuildRuntimeSources.webAccessIndex, a generated bundledWebAccessFactory).
  • Dev-mode loading: it was path-loaded for chats and loaded through a separate jiti loader for subagents.
  • No switch: users couldn't turn it off from Settings → Extensions.
  • Skill never loaded: the package ships a librarian skill (research open-source libraries with GitHub permalinks), which ThinkRail never loaded, even though pi install would.

Approach

  • thinkrail-extensions/web-access (server half only) wraps the package unchanged for chats and subagents. The host keeps its headless-search policy (default web_search workflow to "none"), composed unconditionally: it compensates for the host's UI and must also cover a user's own pi-web-access, which runs exactly when the extension is off. It loads everything the package declares, tools and librarian, the way pi install would. There's nothing of ours to publish, so the usual three-PR template collapses to this one (documented in thinkrail-extensions/SPEC.md). The chat cards stay app-local and join by tool name until Markdown / Collapsible / ToolFileLink are in the UI SDK.
  • Raw .ts vs strict tsc: server/piWebAccess.js + .d.ts, so tsc stops at the declaration. The .js loads the package lazily:
    • Bun: a literal import, which bundlers include in the CLI binary and the desktop bundle.
    • Node: jiti, with a non-literal specifier so bundlers never pull jiti in. Only the Playwright workflow harness loads the server barrel under Node. A static import broke that; I reproduced the break with a probe, and main passes the same probe.
  • The switch covers skills now (closing the deferral in thinkrail-extensions/SPEC.md), like removing a package in pi:
    • Not a post-filter. A disabled extension's skill folders are left out of pi's additionalSkillPaths, rebuilt in place on each reload. pi resolves same-name skills first-wins, so dropping a winner afterwards would also lose a user's own ~/.claude/skills/librarian.
    • Bundled builds: they stage host skills under skills/host/ and extension skills under skills/extensions/<name>/, so ownership survives flattening.
    • Open chats: switching off an extension whose skills an idle chat has loaded triggers one gated reload, because skills live in the system prompt. Skill-less extensions (visualize, ask-user-question) still never reload on disable.
    • Mid-reload toggles: a toggle that lands while a reload runs now queues a follow-up reload. Before, the stale result could stick, for example after re-enabling during a disable reload.

Changes

  • New package: @thinkrail/ext-web-access (./server), registered in packages/server/src/extensions/registry.ts. pi-web-access and jiti move from packages/server to it.
  • Removed: BundledExtensions.webAccessFactory, BuildRuntimeSources.webAccessIndex, bundledWebAccessFactory, the dev jiti loader, and the dev pi-web-access extension path. The headless policy moved to agent/headlessSearch.ts, unchanged in behavior.
  • Build manifest: BuildRuntimeSources.skillRoots is now { source, route }[]. New exports from extensions: STAGED_HOST_SKILLS_ROUTE, stagedExtensionSkillRoute, extensionSkillRoots. Staged skills layout changed (skills/host/…, skills/extensions/web-access/librarian/…); the CLI smoke and artifact probes are updated.
  • Agent: agent/extensions.ts builds skill inputs from the disabled-extensions snapshot; agentSessionManager.applyExtensionTools adds the skill reload and the mid-reload follow-up.
  • UI: a new Settings row with a localized description (settings.extensions.web-access.description in en/es/ja/zh-CN), and the Settings blurb now says tools and skills.
  • Specs: architecture decision 21, thinkrail-extensions, new module-ext-web-access, server extensions / agent, cli, desktop, web tools, spec-graph.
  • Not in this PR: the web half (renderers into the extension), upgrading pi-web-access from 0.13.0, and listing an extension's skills in its Settings row.

Closest look: the reload rule in applyExtensionTools and resolveSkillInputs / bundledSkillPaths in agent/extensions.ts.

Screenshots

Settings → Extensions, fixture project, same viewport:

Before After
before after

Related issues

Part of #600 (the web-access step: the first extension wrapping a third-party pi package).

Checklist

  • Fast gates pass: bun run lint, bun run typecheck, bun run test
  • E2E suite passes for app-affecting changes (bun run e2e; agent behavior covered by the focused live spec bun run e2e:full e2e/web-tools.live.spec.ts, not the complete e2e:full)
  • Before/after screenshots are included for frontend changes, or marked not applicable
  • Relevant SPEC.md / top-level specs updated to reflect any boundary, contract, or behavior change
  • I have read the Contributing guide and agree to the Code of Conduct

Testing

On the rebased head unless noted:

  • bun run lint, bun run typecheck (25/25), bun run test (25/25), check:compiler, check:deps, check:boundaries, check:seams, check:spec-surface, check:i18n → green
  • bun run e2e → 7/8 shards passed; the 1 failure (changes.spec.ts "A failed read says so", unrelated) passes on rerun: bun run e2e e2e/changes.spec.ts → 31/31
  • bun run e2e:full e2e/web-tools.live.spec.ts → 2 passed (real fetch_content / web_search cards) — run before the rebase; covers unchanged inputs
  • bun run build:binary + bun run smoke:binary → OK, including the staged librarian probe
  • bun run e2e:binary → 621 passed, 11 failed in terminal/workspace/layout/model-picker specs. Those 4 specs rerun → 46/46 passed (load flakes, unrelated; before the rebase)
  • New regressions, each confirmed failing without its fix: idle-chat skill reload, re-enable during a running reload, and a personal same-name skill surviving while the extension is off
  • Workflow-harness Node loading: a Playwright probe importing @thinkrail/server/agent and building a loader → passes (fails with a static import)
  • Manual, compiled binary (isolated data dir and cache, real provider):
    • On: a real web_search works; the agent sees all 3 tools and librarian; /skill:librarian and Settings → Skills are present.
    • Off: an open idle chat and a new chat both lose tools and skill; a scout subagent has no web tools.
    • Restart while off: the setting persists and the restored chat stays off.
    • Back on: the restored chat and the chat created while off both regain everything; a subagent runs fetch_content https://example.com → "Example Domain".
    • Off mid-turn: the running turn finishes its three searches, then off applies.
    • Rapid on/off/on/off: the final state holds in both chats.
  • Reviewed by gpt-6-astra over 3 rounds: two majors (mid-reload race, same-name skill shadowing) and one CLI-smoke gap fixed → Approve

Review round 1 (danyaberezun)

  • f20728597: the headless policy is host-owned and unconditional again. Regression: a user's own pi-web-access with the extension off → workflow: "none".
  • e8c26c943: a follow-up reload happens only when the extension policy generation moved during the reload. Regression: an unchanged or skill-less refresh during a reload → no second reload. Both race tests pass 10/10.
  • Shared module-level pi-web-access state: pre-existing, filed as pi-web-access shares fetch/result/clone state across chats, so one chat's reload aborts another's web work #976.
  • On the final tree: bun run e2e → all 8 shards passed; bun run e2e:full e2e/web-tools.live.spec.ts → 2 passed; bun run test → green except the terminal test "closing a tab removes it and reports closed", which fails intermittently on the base too (2/6 runs).

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🌐 Website preview

Preview retired when this pull request closed.

@jetbrains-air jetbrains-air 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.

Approved — ready to merge.

@danyaberezun danyaberezun left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One regression worth fixing before merge, plus two notes on races (the second pre-existing and non-blocking) — all inline.

Comment thread thinkrail-extensions/web-access/server/index.ts Outdated
Comment thread packages/server/src/agent/agentSessionManager.ts Outdated
Comment thread packages/server/src/agent/agentSessionManager.ts

@jetbrains-air jetbrains-air 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.

Approved — ready to merge.

@rsolmano
Rinat S (rsolmano) added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit d2e707f Oct 28, 2026
19 checks passed
@rsolmano
Rinat S (rsolmano) deleted the extension-extraction-candidates branch October 28, 2026 10:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants