fix: keep research available without search keys
This commit is contained in:
@@ -49,6 +49,8 @@ Tool outputs may be error strings (e.g. search unconfigured, a page that failed
|
||||
Adapt: try another URL or another search angle; if research is genuinely impossible, say so
|
||||
in the brief instead of inventing facts.
|
||||
|
||||
If web_search reports that search is unavailable because credentials are missing, do not retry it with different queries. Use web_fetch when you have source URLs, and continue with any other available sources. If the missing search capability materially limits your work, state the limitation and the missing-key setup needed in your brief. Never invent search results or sources.
|
||||
|
||||
Your FINAL assistant message is your entire deliverable — it must stand alone, using this format:
|
||||
|
||||
## Summary
|
||||
@@ -70,8 +72,7 @@ export interface ResearcherRunOptions {
|
||||
/** The search seam (search port wave, 2026-09-20): resolves the LIVE
|
||||
* provider per call, null = honestly unavailable (the web_search tool
|
||||
* reports that in its output — no crash, no silent fallback). Absent
|
||||
* seam reads as unavailable: the server wiring always injects one; the
|
||||
* dispatch-level drop (jobTools) keeps doomed jobs from even starting. */
|
||||
* seam reads as unavailable; web_fetch remains usable without it. */
|
||||
search?: () => SearchProvider | null;
|
||||
/** The researcher's PDF dir (PDF wave, 2026-10-07): when present, the
|
||||
* toolset gains web_fetch_pdf + pdf_info/pdf_extract/pdf_search scoped
|
||||
|
||||
@@ -82,9 +82,8 @@ export interface JobRunnerDeps {
|
||||
/** The search seam (search port wave, 2026-09-20): resolves the LIVE
|
||||
* provider fresh per job — server/search.ts composition injected by
|
||||
* the wiring; null = honestly unavailable (the researcher's web_search
|
||||
* reports it). Absent seam (tests) reads the same. The dispatch-level
|
||||
* drop (jobTools' searchAvailable) uses the same signal — jobs already
|
||||
* in flight when a key disappears still fail honestly, never crash. */
|
||||
* reports it as recoverable text). Absent seam (tests) reads the same;
|
||||
* research dispatch and web_fetch do not require search credentials. */
|
||||
searchProvider?: () => SearchProvider | null;
|
||||
/** Key-presence gate (provider-key interlude, 2026-09-25): given the
|
||||
* resolved engine's provider + the kind's slot tier, return the
|
||||
|
||||
+5
-13
@@ -268,8 +268,7 @@ export interface ProberToolContext extends ProbeDoneContext {
|
||||
jobStore: JobStore;
|
||||
/** Fire the background runner for a freshly created job. */
|
||||
spawn: (job: JobRecord, input: ResearchInput | DemoInput | VisualInput | PdfInput) => void;
|
||||
/** Search availability — the research dispatch tool's honest-disable
|
||||
* signal (same gate as the teacher's). Absent = available. */
|
||||
/** Retained search-status seam; research dispatch is available keylessly. */
|
||||
searchAvailable?: () => boolean;
|
||||
/** The prober's graph-map reader (wave 7): present = the two map tools
|
||||
* (list_sessions / list_propositions) are registered. The composition
|
||||
@@ -331,13 +330,12 @@ export function createProberTools(
|
||||
// research + pdf are ALWAYS available in every phase — the
|
||||
// prober can verify facts or read learner PDFs while assessing.
|
||||
// demo/visual stay teach-phase-only (they need the lesson's plan
|
||||
// context). Same gating as the teacher: toggle-filtered here,
|
||||
// research subject to the searchAvailable gate (honestly absent
|
||||
// when no engine is live).
|
||||
// context). Same user-toggle filtering as the teacher; research
|
||||
// remains available without a search-provider key.
|
||||
...(jobs.research ? { research: jobs.research } : {}),
|
||||
pdf: jobs.pdf!,
|
||||
// The map reader (wave 7): present only when the settings gate
|
||||
// opened — same registration-time drop as the search gate.
|
||||
// opened.
|
||||
...(ctx.graphStore
|
||||
? proberGraphTools({ chatStore: ctx.chatStore, graphStore: ctx.graphStore })
|
||||
: {}),
|
||||
@@ -400,9 +398,7 @@ export interface TeacherToolContext extends PlanToolContext {
|
||||
* rides along — the spawn fires mid-stream, before the tool part is
|
||||
* persisted to the transcript (a transcript scan would race it). */
|
||||
spawn: (job: JobRecord, input: ResearchInput | DemoInput | VisualInput | PdfInput) => void;
|
||||
/** Search availability (search port wave, 2026-09-20): the research
|
||||
* dispatch tool's honest-disable signal — no live engine = the tool
|
||||
* drops from this registry (JobToolContext.searchAvailable). */
|
||||
/** Retained search-status seam; research dispatch is available keylessly. */
|
||||
searchAvailable?: () => boolean;
|
||||
}
|
||||
|
||||
@@ -462,10 +458,6 @@ export function createTeacherTools(
|
||||
find: findTool,
|
||||
grep: grepTool,
|
||||
...planTools(ctx),
|
||||
// Fix wave (2026-09-20): searchAvailable was RECEIVED here but
|
||||
// DROPPED when building the inner JobToolContext — the drop-gate
|
||||
// always took its ?? true default. Threaded now: the live signal
|
||||
// reaches jobTools' research gate.
|
||||
...jobTools({ chatId: ctx.chatId, jobStore: ctx.jobStore, spawn: ctx.spawn, searchAvailable: ctx.searchAvailable }),
|
||||
},
|
||||
withoutMechanics(enabled),
|
||||
|
||||
+14
-21
@@ -23,11 +23,9 @@ export interface JobToolContext {
|
||||
* input rides along because the spawn happens MID-STREAM, before
|
||||
* onFinish persists the tool part — a transcript scan would race it. */
|
||||
spawn: (job: JobRecord, input: ResearchInput | DemoInput | VisualInput | PdfInput) => void;
|
||||
/** Search availability (search port wave, 2026-09-20): the honest
|
||||
* disable signal for the research dispatch tool — no live engine
|
||||
* means NO research tool in the registry (owner decision: registry-
|
||||
* level drop, no doomed dispatch, no silent fallback). Absent =
|
||||
* available (tests + any seam without search wiring). */
|
||||
/** Search-provider availability, retained for existing composition seams.
|
||||
* Research dispatch does not depend on it: web_fetch is keyless and
|
||||
* web_search reports a missing provider as a recoverable tool result. */
|
||||
searchAvailable?: () => boolean;
|
||||
}
|
||||
|
||||
@@ -127,25 +125,20 @@ const PDF_DESCRIPTION =
|
||||
* context (chatId + jobStore + the spawn seam), so concurrent chats never
|
||||
* share state. chat-handler wires it each turn; tests wire their own.
|
||||
*
|
||||
* The research tool drops from the registry when ctx.searchAvailable reports
|
||||
* no live engine (2026-09-20) — the teacher never sees it, so a keyless
|
||||
* server can't dispatch doomed research jobs. Jobs ALREADY in flight when
|
||||
* a key disappears still run to their honest failure (runner backstop).
|
||||
* Research remains available without a search-provider key; the role's
|
||||
* existing user-toggle filtering still applies outside this registry.
|
||||
*/
|
||||
export function jobTools(ctx: JobToolContext): Record<string, AgentTool<any>> {
|
||||
const searchOn = ctx.searchAvailable?.() ?? true;
|
||||
return {
|
||||
...(searchOn && {
|
||||
research: {
|
||||
name: "research",
|
||||
label: "research",
|
||||
description: RESEARCH_DESCRIPTION,
|
||||
parameters: researchInputSchema,
|
||||
execute: serverExecute("research", researchInputSchema, (input, { toolCallId }) =>
|
||||
dispatch(ctx, "research", toolCallId, RESEARCH_ACK, input),
|
||||
),
|
||||
},
|
||||
}),
|
||||
research: {
|
||||
name: "research",
|
||||
label: "research",
|
||||
description: RESEARCH_DESCRIPTION,
|
||||
parameters: researchInputSchema,
|
||||
execute: serverExecute("research", researchInputSchema, (input, { toolCallId }) =>
|
||||
dispatch(ctx, "research", toolCallId, RESEARCH_ACK, input),
|
||||
),
|
||||
},
|
||||
demo: {
|
||||
name: "demo",
|
||||
label: "demo",
|
||||
|
||||
@@ -40,8 +40,8 @@ export interface PracticeToolContext {
|
||||
graphNodes?: () => ReadonlyArray<Pick<import("../../core/contracts/graph.ts").GraphNode, "id" | "name">>;
|
||||
/** THE RARELY-NEEDED SET (owner round 10, 2026-10-05): the map readers
|
||||
* behind the SAME settings gate as the prober's (graphToolsEnabled at
|
||||
* the composition root); the research + pdf jobs with the honest
|
||||
* searchAvailable drop. Each absent (tests) = absent from the registry. */
|
||||
* the composition root); the research + pdf jobs (both keyless).
|
||||
* Each absent context (tests) = absent from the registry. */
|
||||
graph?: GraphToolContext;
|
||||
jobs?: JobToolContext;
|
||||
}
|
||||
@@ -225,8 +225,8 @@ export function practiceTools(ctx: PracticeToolContext): Record<string, AgentToo
|
||||
const gate = createPracticeGate(ctx);
|
||||
// THE RARELY-NEEDED SET (owner round 10): read/ls/find/grep over the
|
||||
// learner's files (always — the same read-only set the teacher and
|
||||
// prober carry), the research + pdf jobs (research honestly absent
|
||||
// when no search engine is live; demo/visual stay teach-only), and
|
||||
// prober carry), the research + pdf jobs (available without search
|
||||
// keys; demo/visual stay teach-only), and
|
||||
// the prober's map readers when the settings gate opened. None are
|
||||
// toggle-filtered: problem/conclude are the session; these are
|
||||
// exceptions the doctrine says to reach for rarely.
|
||||
|
||||
Reference in New Issue
Block a user