diff --git a/.changeset/fix-connector-tool-mode-gating.md b/.changeset/fix-connector-tool-mode-gating.md new file mode 100644 index 00000000..979cfa4c --- /dev/null +++ b/.changeset/fix-connector-tool-mode-gating.md @@ -0,0 +1,5 @@ +--- +"openwiki": patch +--- + +Gate OpenWiki connector tools by run mode. `createOpenWikiConnectorTools` now takes an `outputMode` and returns no connector tools for `repository` (code) mode, so code-mode agents are no longer offered credentialed external ingestion tools (Gmail, Slack, X, ...) that throw on missing credentials. Personal/local-wiki runs are unchanged. Fixes #444. diff --git a/src/agent/index.ts b/src/agent/index.ts index dd028088..ec4a0f7b 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -366,7 +366,7 @@ function createOpenWikiAgentGraph( return createDeepAgent({ model: options.model, - tools: createOpenWikiConnectorTools(), + tools: createOpenWikiConnectorTools(options.outputMode), checkpointer: options.checkpointer, backend, middleware: diff --git a/src/connectors/tools.ts b/src/connectors/tools.ts index 822194fa..c9d5e5d4 100644 --- a/src/connectors/tools.ts +++ b/src/connectors/tools.ts @@ -5,6 +5,7 @@ import { import { constants as fsConstants } from "node:fs"; import { lstat, open, readdir, stat } from "node:fs/promises"; import path from "node:path"; +import type { OpenWikiOutputMode } from "../agent/types.js"; import { getConnectorConfigPath, getConnectorRawDir, @@ -20,7 +21,18 @@ import { } from "./mcp-runtime.js"; import type { ConnectorId, ConnectorIngestOptions } from "./types.js"; -export function createOpenWikiConnectorTools(): StructuredToolInterface[] { +export function createOpenWikiConnectorTools( + outputMode: OpenWikiOutputMode = "local-wiki", +): StructuredToolInterface[] { + // Connector tools perform credentialed external fetches (Gmail, Slack, X, ...) + // and write raw data under the OpenWiki home. They are a personal/local-wiki + // capability: a code-mode run documents a codebase and must never be handed + // connector ingestion, which otherwise throws on missing credentials and + // wastes tokens discovering sources it has no business touching. See #444. + if (outputMode === "repository") { + return []; + } + return [ new DynamicStructuredTool({ name: "openwiki_list_connectors", diff --git a/test/raw-connector-tools.test.ts b/test/raw-connector-tools.test.ts index 881f9236..dd8cdbe9 100644 --- a/test/raw-connector-tools.test.ts +++ b/test/raw-connector-tools.test.ts @@ -119,6 +119,37 @@ describe("raw connector tools", () => { }); }); +describe("connector tool run-mode gating (#444)", () => { + test("personal/local-wiki mode exposes connector ingest tools", async () => { + const home = await createTempHome(); + const tools = await loadConnectorToolsForMode(home, "local-wiki"); + + expect(tools.map((tool) => tool.name)).toContain( + "openwiki_ingest_connector", + ); + expect(tools.map((tool) => tool.name)).toContain( + "openwiki_ingest_all_connectors", + ); + }); + + test("code/repository mode is not offered any connector tools", async () => { + const home = await createTempHome(); + const tools = await loadConnectorToolsForMode(home, "repository"); + + expect(tools).toEqual([]); + }); + + test("defaulting without a mode behaves like local-wiki", async () => { + const home = await createTempHome(); + const tools = await loadConnectorTools(home); + + expect(tools.length).toBeGreaterThan(0); + expect(tools.map((tool) => tool.name)).toContain( + "openwiki_ingest_connector", + ); + }); +}); + interface RawItemsResult { files: string[]; latestFiles: string[]; @@ -134,13 +165,30 @@ interface RawReadResult { async function loadConnectorTools( home: string, ): Promise { + const { createOpenWikiConnectorTools } = + await loadConnectorToolsModule(home); + + return createOpenWikiConnectorTools(); +} + +async function loadConnectorToolsForMode( + home: string, + outputMode: "local-wiki" | "repository", +): Promise { + const { createOpenWikiConnectorTools } = + await loadConnectorToolsModule(home); + + return createOpenWikiConnectorTools(outputMode); +} + +async function loadConnectorToolsModule( + home: string, +): Promise { vi.resetModules(); process.env.HOME = home; process.env.USERPROFILE = home; - const { createOpenWikiConnectorTools } = - await import("../src/connectors/tools.ts"); - return createOpenWikiConnectorTools(); + return import("../src/connectors/tools.ts"); } function getTool(