feat(search): web_search onto the injected port — Google CSE retired, research honestly disabled

Chunk A of the search port wave (2026-09-20), per master's assignment:

- web.ts: the Google Custom Search backend is gone — web_search now runs
  on the core search port (SearchProvider), injected as a dependency
  (import law: the tool never imports server code). createWebTools(search)
  resolves the LIVE provider fresh per call; null = the honest
  unavailability string (no silent fallback, owner decision). web_fetch
  unchanged (needs no engine).
- runner: JobRunnerDeps.searchProvider — the server composition threaded
  into the researcher; jobs already in flight when a key disappears still
  fail honestly through the same seam, never crash.
- jobTools: the research dispatch tool DROPS from the teacher's registry
  when ctx.searchAvailable() is false (owner decision: registry-level
  drop — the model never sees the tool, no doomed dispatch, no error
  card). Absent seam = available (tests). chat-handler injects
  server/searchAvailable + searchProvider.
- tests: stubFetch (the Google wire stub) retired; jobs.test.ts threads
  a recording stub provider through all four research runners and asserts
  the scripted query hit the port; new web-search.test.ts covers the
  factory contract (null → honest string, hits, error strings, fresh
  resolution, web_fetch independence) + the registry drop matrix.

Gates: typecheck, 793/793 tests, lint, build — all green.
This commit is contained in:
amosblomqvist
2026-09-22 13:59:25 +03:00
parent 48fbd988f1
commit ed264c936d
8 changed files with 385 additions and 232 deletions
+9
View File
@@ -11,6 +11,7 @@ import { claimSteers, deliverJobResult, setContextRespawn, setJobRespawn, setTur
import { recordToPiMessages } from "./pi/record-adapter.ts";
import { createPiTurnResponse } from "./pi/ui-stream.ts";
import { effectiveSettings, MODEL_PINS, stores } from "./stores.ts";
import { searchAvailable, searchProvider } from "./search.ts";
import { extractionEnabled, graphContextEnabled, resolveRoleSpec } from "../core/settings-resolution.ts";
import { beginTurn, endTurn } from "./turn-liveness.ts";
import { persistTurn, triggerExtraction, triggerGraphContext, withMessageIds } from "./turn-service.ts";
@@ -45,6 +46,11 @@ const jobRunner = createJobRunner({
// per-role overrides reached rolePins but never the engines.
engineFor: (role) =>
piEngineForSpec(resolveRoleSpec(role, stores.settingsStore.readSettings(), MODEL_PINS)),
// Search port wave (2026-09-20): the researcher's web_search runs on the
// injected port — resolved FRESH per job (key rotations land immediately).
// null = honestly unavailable; the dispatch-level drop (below) uses the
// same signal so keyless servers never see the research tool at all.
searchProvider,
});
/** The app's ONE extraction pipeline instance — both front doors share it:
* quiz-triggered session runs (turn-service) and notes imports
@@ -252,6 +258,9 @@ export async function handleChatRequest(
chatStore: stores.chatStore,
jobStore: stores.jobStore,
spawn: (job, input) => void jobRunner.runJob(job.id, input),
// The registry drop's signal (2026-09-20): no live engine
// = no research tool in the teacher's registry.
searchAvailable,
},
agentOpts,
)