diff --git a/.project-docs/30-worklog/tasks/20260901-web-search-tool-routing-fix-6f9d3b82.md b/.project-docs/30-worklog/tasks/20260901-web-search-tool-routing-fix-6f9d3b82.md new file mode 100644 index 00000000..891117a6 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260901-web-search-tool-routing-fix-6f9d3b82.md @@ -0,0 +1,84 @@ +# Task: Diagnose and fix Web Search tool routing from current main + +## Identity + +- Task ID: 20260901-web-search-tool-routing-fix-6f9d3b82 +- Mode: Feature +- Branch: codex/20260901-web-search-tool-routing-fix-6f9d3b82-web-search-tool-routing-fix +- Worktree: D:\Datas\OthersProjects\makelore-web-search-tool-routing-fix-6f9d3b82 +- Base commit: 08099ea1c2ea24efc10c20f878fc25a557ea9d90 +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Reproduce the reported installed-client behavior where an explicit request to use + Web Search invokes `agent_browser` instead of `makelore_web_search`. +- Trace the exact current package version, project Plugin selection, Agent Skill + assignment, backend admission, effective frozen worker snapshot, and Pi CLI tool + catalog without changing server policy or production data. +- If a client defect is confirmed, add a regression at the narrow owning seam, + implement the smallest fix, and verify the affected packaged/runtime path. + +## Intent And Constraints + +- Concurrent Task Gate: Passed in the isolated worktree from exact local + `main` base `08099ea1c2ea24efc10c20f878fc25a557ea9d90`. The two unrelated + untracked packaging records in the user root remain untouched. +- Planning Gate: Passed after loading the project memory entry points, current + architecture/data-flow/domain rules, ADR-006, evidence/commitments, the prior + Web Search coordinator scope, and the two local 1.2.2/1.2.3 package records. +- Confirmed product contract: official Web Search is bundled but optional; Account + acquisition, project enablement, Agent assignment, current Admission, and the + frozen parent worker snapshot are separate gates. Child workers receive no hosted + tool. +- Diagnosis must begin with a deterministic loop that observes the actual worker + tool catalog. A prompt-only change is not acceptable unless the tool is proven + present and the routing failure remains. +- Preserve the current project, conversations, user data, credentials, unrelated + task records, and production server state. No push, deploy, publication, paid + Provider call, or destructive cleanup is in scope. + +## Outcome + +- Confirmed the installed client is MakeLore 1.2.3 and the affected project has + `makelore.web-search` enabled, but Agent `cc` is assigned only `agent-browser`. + The latest persisted Pi session consequently exposes only `agent_browser`; this + is not a model-choice conflict between two available tools. +- Reproduced the client bootstrap defect at the Product Host seam: the unscoped + Agent assignment catalog called the effective resolver with an empty assignment, + then treated `effectiveSkillIds=[]` as the plugin availability set. An enabled + Web Search Skill therefore disappeared until it had already been assigned. +- Changed the unscoped catalog to use project-enabled plugin IDs directly and to + avoid runtime/admission resolution. Agent-scoped projections still use the + resolver-approved effective subset, so the fix does not materialize Web Search + into a worker before explicit Agent assignment. +- Added a regression proving an enabled/unassigned Web Search Skill is available + in the assignment catalog, while the same Skill remains unavailable in Agent + `builder`'s scoped projection until assigned. +- Applied the requested operational correction to the affected project only: + Agent `cc` now has `agent-browser` and `makelore-web-search`; the existing + `makelore.web-search` project enablement remains unchanged. + +## Verification + +- TDD RED: `pnpm exec vitest run tests/unit/coding-product-services.test.ts` = + 1 failed / 10 passed; the enabled Web Search Skill was absent. +- Focused GREEN: Product Host, effective resolver, capability registry, Pi resource + loader, and composition = 5 files / 45 tests passed. +- Full client suite on the final implementation = 215 files / 1785 passed / 2 + skipped, plus pressure 1/1 passed. +- `pnpm run typecheck` passed. Scoped ESLint passed with zero findings. +- The affected project JSON parses successfully and reports Agent `cc` Skills + `agent-browser,makelore-web-search` with Web Search project enablement `true`. + +## Follow-ups + +- The currently running 1.2.3 process does not contain the assignment-catalog UI + fix. The affected Agent assignment is already corrected on disk; start a new + conversation so a new frozen parent worker reads it and includes + `makelore_web_search`. Existing workers intentionally remain frozen. + +## Promotion Candidates + +- None recorded. diff --git a/electron/api/coding-product-services.ts b/electron/api/coding-product-services.ts index 55f50d8a..44d36568 100644 --- a/electron/api/coding-product-services.ts +++ b/electron/api/coding-product-services.ts @@ -674,7 +674,7 @@ export function createCodingProductHost(options: CodingProductHostOptions): Codi async listSkills(agentId) { const project = await activeProject(); const assignedSkillIds = await selectedSkillIds(project.path, agentId); - const effective = options.effectiveResolver + const effective = agentId && options.effectiveResolver ? await options.effectiveResolver.resolve({ projectId: project.id, projectPath: project.path, @@ -687,11 +687,17 @@ export function createCodingProductHost(options: CodingProductHostOptions): Codi // still supplies the availability set; only the worker opener receives // the effective subset. const skillIds = assignedSkillIds; - const enabledPluginIds = effective - ? effective.effectiveSkillIds - : options.getEnabledPluginIds - ? await options.getEnabledPluginIds(project.path) - : []; + // The unscoped list is the Agent assignment catalog. It must expose + // every project-enabled plugin Skill before that Skill has been assigned; + // the resolver's effective subset is only authoritative once an Agent is + // selected (and for worker-facing projections). + const enabledPluginIds = !agentId && options.getEnabledPluginIds + ? await options.getEnabledPluginIds(project.path) + : effective + ? effective.effectiveSkillIds + : options.getEnabledPluginIds + ? await options.getEnabledPluginIds(project.path) + : []; return await options.productTools.listSkills( skillIds, enabledPluginIds, diff --git a/tests/unit/coding-product-services.test.ts b/tests/unit/coding-product-services.test.ts index 9a5aa28e..c62abc0c 100644 --- a/tests/unit/coding-product-services.test.ts +++ b/tests/unit/coding-product-services.test.ts @@ -218,6 +218,56 @@ describe('PI-105 product Host composition', () => { })); }); + it('offers an enabled Web Search Skill before its first Agent assignment', async () => { + const root = await configuredProject(['agent-browser']); + const tools = new PiProductTools({ + browser: {} as AgentBrowserModule, + attachments: new CodingAttachmentStore(path.join(root, 'attachments')), + bundledSkillsDir: path.resolve('resources/coding-skills'), + pluginSkillSources: [{ + id: 'makelore-web-search', + pluginId: 'makelore.web-search', + directory: path.resolve( + 'resources/coding-plugins/web-search/skills/makelore-web-search', + ), + }], + }); + const effectiveResolver = { + resolve: vi.fn(async () => ({ + accountSessionId: 'account-a\u00001', + projectId: 'project-a', + pluginReleaseIds: [], + effectiveSkillIds: [], + skillEntries: [], + toolDefinitions: [], + runtimePolicies: [], + unavailableReasons: [{ + pluginId: 'makelore.web-search', + code: 'skill_unassigned' as const, + message: 'Plugin Skill is not assigned', + }], + })), + getSkillSources: vi.fn(async () => []), + getPolicyState: vi.fn(() => ({ + status: 'current' as const, catalog: null, revision: 1, lastVerifiedAt: 1, + })), + }; + const host = createCodingProductHost({ + projects: projectService(root), + productTools: tools, + effectiveResolver, + getEnabledPluginIds: async () => ['makelore.web-search'], + }); + + await expect(host.listSkills()).resolves.toContainEqual(expect.objectContaining({ + id: 'makelore-web-search', selected: false, available: true, effective: false, + })); + expect(effectiveResolver.resolve).not.toHaveBeenCalled(); + await expect(host.listSkills('builder')).resolves.not.toContainEqual( + expect.objectContaining({ id: 'makelore-web-search', available: true }), + ); + }); + it('reads exact-run changes from the same PiProductTools tracker instance', async () => { const root = await configuredProject(); await writeFile(path.join(root, 'notes.txt'), 'baseline\n', 'utf8');