@dreb/coding-agent 2.34.2 → 2.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +3 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +30 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +172 -130
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/buddy/buddy-controller.d.ts +35 -1
- package/dist/core/buddy/buddy-controller.d.ts.map +1 -1
- package/dist/core/buddy/buddy-controller.js +65 -4
- package/dist/core/buddy/buddy-controller.js.map +1 -1
- package/dist/core/buddy/buddy-manager.d.ts +6 -0
- package/dist/core/buddy/buddy-manager.d.ts.map +1 -1
- package/dist/core/buddy/buddy-manager.js +25 -2
- package/dist/core/buddy/buddy-manager.js.map +1 -1
- package/dist/core/export-html/template.js +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +5 -102
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/git-branch.d.ts +19 -0
- package/dist/core/git-branch.d.ts.map +1 -0
- package/dist/core/git-branch.js +109 -0
- package/dist/core/git-branch.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +21 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +117 -42
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/{modes/interactive → core}/tab-title.d.ts +12 -3
- package/dist/core/tab-title.d.ts.map +1 -0
- package/dist/{modes/interactive → core}/tab-title.js +58 -16
- package/dist/core/tab-title.js.map +1 -0
- package/dist/core/tools/search.d.ts.map +1 -1
- package/dist/core/tools/search.js +4 -1
- package/dist/core/tools/search.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +63 -2
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +459 -73
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/core/tools/web.d.ts +1 -1
- package/dist/core/tools/web.d.ts.map +1 -1
- package/dist/core/tools/web.js +60 -16
- package/dist/core/tools/web.js.map +1 -1
- package/dist/main.d.ts +10 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +54 -4
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +2 -2
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +4 -40
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +20 -3
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/index.d.ts +2 -2
- package/dist/modes/rpc/index.d.ts.map +1 -1
- package/dist/modes/rpc/index.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +114 -6
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +140 -2
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +97 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +656 -27
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +312 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/buddy.md +9 -1
- package/docs/dashboard.md +152 -0
- package/docs/development.md +8 -5
- package/docs/rpc.md +652 -5
- package/examples/sdk/12-full-control.ts +1 -1
- package/package.json +1 -1
- package/dist/modes/interactive/tab-title.d.ts.map +0 -1
- package/dist/modes/interactive/tab-title.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/core/tools/search.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAUtF,QAAA,MAAM,YAAY;;;;;;EAgBhB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAM1D,MAAM,WAAW,iBAAiB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAMD,sCAAsC;AACtC,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EACnH,KAAK,EAAE,cAAc,wCAAwC,EAAE,KAAK,GAClE,MAAM,CAkBR;AAED,sCAAsC;AACtC,wBAAgB,kBAAkB,CACjC,MAAM,EAAE;IACP,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC5B,EACD,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,cAAc,wCAAwC,EAAE,KAAK,GAClE,MAAM,CAoBR;AAMD,oEAAoE;AACpE,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAmBD,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAAC,OAAO,YAAY,EAAE,iBAAiB,CAAC,CAiH9G;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,OAAO,YAAY,CAAC,CAE5E;AAED,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB;;;;;;2BAA4C,CAAC;AAC9E,eAAO,MAAM,UAAU;;;;;;QAAkC,CAAC","sourcesContent":["/**\n * Semantic codebase search tool.\n *\n * Uses embeddings + FTS5 to support natural language queries over the codebase.\n * Feature-gated on `node:sqlite` availability (Node 22+).\n */\n\nimport { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { formatResults, SearchEngine } from \"@dreb/semantic-search\";\nimport { Text } from \"@dreb/tui\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { getDrebToolVisibleDirs } from \"./dreb-paths.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\n// ============================================================================\n// Schema\n// ============================================================================\n\nconst searchSchema = Type.Object({\n\tquery: Type.String({ description: \"The search query (natural language, identifier, or path)\" }),\n\trestrictToDir: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Filter results to files under this path (relative to searchDir or cwd). Does not affect indexing — the entire searchDir is still indexed.\",\n\t\t}),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results to return (default: 20)\" })),\n\tsearchDir: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Directory to index and search instead of cwd (useful when cwd is ~/). The entire contents of this directory are scanned and indexed.\",\n\t\t}),\n\t),\n\trebuild: Type.Optional(Type.Boolean({ description: \"Force a clean rebuild of the search index (default: false)\" })),\n});\n\nexport type SearchToolInput = Static<typeof searchSchema>;\n\n// ============================================================================\n// Details\n// ============================================================================\n\nexport interface SearchToolDetails {\n\tresultCount: number;\n\tindexBuilt: boolean;\n\tindexStats?: { files: number; chunks: number };\n}\n\n// ============================================================================\n// Rendering\n// ============================================================================\n\n/** @internal Exported for testing. */\nexport function formatSearchCall(\n\targs: { query?: string; restrictToDir?: string; limit?: number; searchDir?: string; rebuild?: boolean } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst query = str(args?.query);\n\tconst restrictToDir = str(args?.restrictToDir);\n\tconst searchDir = str(args?.searchDir);\n\tlet text = `${theme.fg(\"toolTitle\", theme.bold(\"search\"))} ${theme.fg(\"accent\", `\"${query ?? \"\"}\"`)}`;\n\tif (searchDir) {\n\t\ttext += theme.fg(\"toolOutput\", ` project ${shortenPath(searchDir)}`);\n\t}\n\tif (restrictToDir) {\n\t\ttext += theme.fg(\"toolOutput\", ` in ${shortenPath(restrictToDir)}`);\n\t}\n\tif (args?.rebuild) {\n\t\ttext += theme.fg(\"toolOutput\", \" [rebuild]\");\n\t}\n\tif (args?.limit !== undefined) {\n\t\ttext += theme.fg(\"toolOutput\", ` limit ${args.limit}`);\n\t}\n\treturn text;\n}\n\n/** @internal Exported for testing. */\nexport function formatSearchResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string }>;\n\t\tdetails?: SearchToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst output = result.content[0]?.text?.trim() ?? \"\";\n\tif (!output) return \"\";\n\n\tconst lines = output.split(\"\\n\");\n\tconst maxLines = options.expanded ? lines.length : 20;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\n\tlet text = `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `\\n${theme.fg(\"muted\", `... (${remaining} more lines)`)}`;\n\t}\n\n\tif (result.details?.indexStats) {\n\t\tconst { files, chunks } = result.details.indexStats;\n\t\ttext += `\\n${theme.fg(\"muted\", `[Index: ${files} files, ${chunks} chunks]`)}`;\n\t}\n\n\treturn text;\n}\n\n// ============================================================================\n// Tool Definition\n// ============================================================================\n\n/** Check if the search tool is available (requires node:sqlite). */\nexport function isSearchAvailable(): boolean {\n\treturn SearchEngine.isAvailable();\n}\n\n// Cache search engines per project root to reuse index across calls within a session\nconst engineCache = new Map<string, SearchEngine>();\n\nfunction getSearchEngine(projectRoot: string): SearchEngine {\n\tlet engine = engineCache.get(projectRoot);\n\tif (!engine) {\n\t\tengine = new SearchEngine(projectRoot, {\n\t\t\tindexDir: path.join(projectRoot, \".dreb\", \"index\"),\n\t\t\tglobalMemoryDir: path.join(homedir(), \".dreb\", \"memory\"),\n\t\t\tmodelCacheDir: path.join(homedir(), \".dreb\", \"agent\", \"models\"),\n\t\t\tvisibleDirs: getDrebToolVisibleDirs,\n\t\t});\n\t\tengineCache.set(projectRoot, engine);\n\t}\n\treturn engine;\n}\n\nexport function createSearchToolDefinition(cwd: string): ToolDefinition<typeof searchSchema, SearchToolDetails> {\n\treturn {\n\t\tname: \"search\",\n\t\tlabel: \"search\",\n\t\tdescription:\n\t\t\t\"Search the codebase using natural language queries. Returns ranked code/doc results using semantic similarity and keyword matching. First query builds the index (may take a moment); subsequent queries are fast. Supports identifier queries (e.g. 'AuthMiddleware'), natural language (e.g. 'where is rate limiting handled'), and path queries (e.g. 'src/auth/').\",\n\t\tpromptSnippet: \"Semantic codebase search — natural language queries over code and docs\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `search` as your default exploration tool — for understanding code, finding where things are, and answering questions about the codebase. Use `grep` when you already know the exact text or pattern you're looking for.\",\n\t\t\t\"The first search query builds an index (may take 10-60s). Subsequent queries are fast.\",\n\t\t],\n\t\tparameters: searchSchema,\n\n\t\tasync execute(_toolCallId, params, signal, onUpdate, _ctx) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\t\tif (!isSearchAvailable()) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: \"Semantic search requires Node.js 22+ (for built-in SQLite). Current Node.js version does not support node:sqlite.\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst { query, restrictToDir, limit, searchDir, rebuild } = params;\n\n\t\t\tif (!query || query.trim().length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Search query cannot be empty.\" }],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst resolvedSearchDir = searchDir ? resolveToCwd(searchDir, cwd) : cwd;\n\n\t\t\tif (searchDir && (!existsSync(resolvedSearchDir) || !statSync(resolvedSearchDir).isDirectory())) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `searchDir does not exist or is not a directory: ${resolvedSearchDir}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst engine = getSearchEngine(resolvedSearchDir);\n\n\t\t\tif (rebuild) {\n\t\t\t\tawait engine.resetIndex();\n\t\t\t}\n\n\t\t\tlet indexBuilt = false;\n\t\t\tconst results = await engine.search(query, {\n\t\t\t\tlimit: typeof limit === \"number\" && limit > 0 ? Math.floor(limit) : 20,\n\t\t\t\tpathFilter: restrictToDir,\n\t\t\t\tonProgress: (phase, current, total) => {\n\t\t\t\t\tif (phase === \"indexing\" || phase === \"scanning\" || phase === \"loading model\" || phase === \"embedding\") {\n\t\t\t\t\t\tindexBuilt = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (onUpdate) {\n\t\t\t\t\t\tonUpdate({\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `${phase}: ${current}/${total}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: true } as SearchToolDetails,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tif (results.length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"No results found.\" }],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst text = formatResults(results);\n\n\t\t\t// Get index stats from the existing engine (no new connection)\n\t\t\tconst stats = engine.getStats();\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\tdetails: {\n\t\t\t\t\tresultCount: results.length,\n\t\t\t\t\tindexBuilt,\n\t\t\t\t\tindexStats: stats ?? undefined,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatSearchCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatSearchResult(result as any, options, theme));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createSearchTool(cwd: string): AgentTool<typeof searchSchema> {\n\treturn wrapToolDefinition(createSearchToolDefinition(cwd));\n}\n\n/** Default search tool using process.cwd() for backwards compatibility. */\nexport const searchToolDefinition = createSearchToolDefinition(process.cwd());\nexport const searchTool = createSearchTool(process.cwd());\n"]}
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/core/tools/search.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAUtF,QAAA,MAAM,YAAY;;;;;;EAgBhB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAM1D,MAAM,WAAW,iBAAiB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAUD,sCAAsC;AACtC,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EACnH,KAAK,EAAE,cAAc,wCAAwC,EAAE,KAAK,GAClE,MAAM,CAkBR;AAED,sCAAsC;AACtC,wBAAgB,kBAAkB,CACjC,MAAM,EAAE;IACP,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC5B,EACD,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,cAAc,wCAAwC,EAAE,KAAK,GAClE,MAAM,CAoBR;AAMD,oEAAoE;AACpE,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAmBD,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAAC,OAAO,YAAY,EAAE,iBAAiB,CAAC,CAiH9G;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,OAAO,YAAY,CAAC,CAE5E;AAED,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB;;;;;;2BAA4C,CAAC;AAC9E,eAAO,MAAM,UAAU;;;;;;QAAkC,CAAC","sourcesContent":["/**\n * Semantic codebase search tool.\n *\n * Uses embeddings + FTS5 to support natural language queries over the codebase.\n * Feature-gated on `node:sqlite` availability (Node 22+).\n */\n\nimport { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { formatResults, SearchEngine } from \"@dreb/semantic-search\";\nimport { Text } from \"@dreb/tui\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { getDrebToolVisibleDirs } from \"./dreb-paths.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\n// ============================================================================\n// Schema\n// ============================================================================\n\nconst searchSchema = Type.Object({\n\tquery: Type.String({ description: \"The search query (natural language, identifier, or path)\" }),\n\trestrictToDir: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Filter results to files under this path (relative to searchDir or cwd). Does not affect indexing — the entire searchDir is still indexed.\",\n\t\t}),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results to return (default: 20)\" })),\n\tsearchDir: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Directory to index and search instead of cwd (useful when cwd is ~/). The entire contents of this directory are scanned and indexed.\",\n\t\t}),\n\t),\n\trebuild: Type.Optional(Type.Boolean({ description: \"Force a clean rebuild of the search index (default: false)\" })),\n});\n\nexport type SearchToolInput = Static<typeof searchSchema>;\n\n// ============================================================================\n// Details\n// ============================================================================\n\nexport interface SearchToolDetails {\n\tresultCount: number;\n\tindexBuilt: boolean;\n\tindexStats?: { files: number; chunks: number };\n}\n\nfunction normalizeLineEndings(text: string): string {\n\treturn text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n}\n\n// ============================================================================\n// Rendering\n// ============================================================================\n\n/** @internal Exported for testing. */\nexport function formatSearchCall(\n\targs: { query?: string; restrictToDir?: string; limit?: number; searchDir?: string; rebuild?: boolean } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst query = str(args?.query);\n\tconst restrictToDir = str(args?.restrictToDir);\n\tconst searchDir = str(args?.searchDir);\n\tlet text = `${theme.fg(\"toolTitle\", theme.bold(\"search\"))} ${theme.fg(\"accent\", `\"${query ?? \"\"}\"`)}`;\n\tif (searchDir) {\n\t\ttext += theme.fg(\"toolOutput\", ` project ${shortenPath(searchDir)}`);\n\t}\n\tif (restrictToDir) {\n\t\ttext += theme.fg(\"toolOutput\", ` in ${shortenPath(restrictToDir)}`);\n\t}\n\tif (args?.rebuild) {\n\t\ttext += theme.fg(\"toolOutput\", \" [rebuild]\");\n\t}\n\tif (args?.limit !== undefined) {\n\t\ttext += theme.fg(\"toolOutput\", ` limit ${args.limit}`);\n\t}\n\treturn text;\n}\n\n/** @internal Exported for testing. */\nexport function formatSearchResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string }>;\n\t\tdetails?: SearchToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst output = result.content[0]?.text ? normalizeLineEndings(result.content[0].text).trim() : \"\";\n\tif (!output) return \"\";\n\n\tconst lines = output.split(\"\\n\");\n\tconst maxLines = options.expanded ? lines.length : 20;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\n\tlet text = `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `\\n${theme.fg(\"muted\", `... (${remaining} more lines)`)}`;\n\t}\n\n\tif (result.details?.indexStats) {\n\t\tconst { files, chunks } = result.details.indexStats;\n\t\ttext += `\\n${theme.fg(\"muted\", `[Index: ${files} files, ${chunks} chunks]`)}`;\n\t}\n\n\treturn text;\n}\n\n// ============================================================================\n// Tool Definition\n// ============================================================================\n\n/** Check if the search tool is available (requires node:sqlite). */\nexport function isSearchAvailable(): boolean {\n\treturn SearchEngine.isAvailable();\n}\n\n// Cache search engines per project root to reuse index across calls within a session\nconst engineCache = new Map<string, SearchEngine>();\n\nfunction getSearchEngine(projectRoot: string): SearchEngine {\n\tlet engine = engineCache.get(projectRoot);\n\tif (!engine) {\n\t\tengine = new SearchEngine(projectRoot, {\n\t\t\tindexDir: path.join(projectRoot, \".dreb\", \"index\"),\n\t\t\tglobalMemoryDir: path.join(homedir(), \".dreb\", \"memory\"),\n\t\t\tmodelCacheDir: path.join(homedir(), \".dreb\", \"agent\", \"models\"),\n\t\t\tvisibleDirs: getDrebToolVisibleDirs,\n\t\t});\n\t\tengineCache.set(projectRoot, engine);\n\t}\n\treturn engine;\n}\n\nexport function createSearchToolDefinition(cwd: string): ToolDefinition<typeof searchSchema, SearchToolDetails> {\n\treturn {\n\t\tname: \"search\",\n\t\tlabel: \"search\",\n\t\tdescription:\n\t\t\t\"Search the codebase using natural language queries. Returns ranked code/doc results using semantic similarity and keyword matching. First query builds the index (may take a moment); subsequent queries are fast. Supports identifier queries (e.g. 'AuthMiddleware'), natural language (e.g. 'where is rate limiting handled'), and path queries (e.g. 'src/auth/').\",\n\t\tpromptSnippet: \"Semantic codebase search — natural language queries over code and docs\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `search` as your default exploration tool — for understanding code, finding where things are, and answering questions about the codebase. Use `grep` when you already know the exact text or pattern you're looking for.\",\n\t\t\t\"The first search query builds an index (may take 10-60s). Subsequent queries are fast.\",\n\t\t],\n\t\tparameters: searchSchema,\n\n\t\tasync execute(_toolCallId, params, signal, onUpdate, _ctx) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\t\tif (!isSearchAvailable()) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: \"Semantic search requires Node.js 22+ (for built-in SQLite). Current Node.js version does not support node:sqlite.\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst { query, restrictToDir, limit, searchDir, rebuild } = params;\n\n\t\t\tif (!query || query.trim().length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Search query cannot be empty.\" }],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst resolvedSearchDir = searchDir ? resolveToCwd(searchDir, cwd) : cwd;\n\n\t\t\tif (searchDir && (!existsSync(resolvedSearchDir) || !statSync(resolvedSearchDir).isDirectory())) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `searchDir does not exist or is not a directory: ${resolvedSearchDir}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst engine = getSearchEngine(resolvedSearchDir);\n\n\t\t\tif (rebuild) {\n\t\t\t\tawait engine.resetIndex();\n\t\t\t}\n\n\t\t\tlet indexBuilt = false;\n\t\t\tconst results = await engine.search(query, {\n\t\t\t\tlimit: typeof limit === \"number\" && limit > 0 ? Math.floor(limit) : 20,\n\t\t\t\tpathFilter: restrictToDir,\n\t\t\t\tonProgress: (phase, current, total) => {\n\t\t\t\t\tif (phase === \"indexing\" || phase === \"scanning\" || phase === \"loading model\" || phase === \"embedding\") {\n\t\t\t\t\t\tindexBuilt = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (onUpdate) {\n\t\t\t\t\t\tonUpdate({\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `${phase}: ${current}/${total}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: true } as SearchToolDetails,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tif (results.length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"No results found.\" }],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst text = formatResults(results);\n\n\t\t\t// Get index stats from the existing engine (no new connection)\n\t\t\tconst stats = engine.getStats();\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\tdetails: {\n\t\t\t\t\tresultCount: results.length,\n\t\t\t\t\tindexBuilt,\n\t\t\t\t\tindexStats: stats ?? undefined,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatSearchCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatSearchResult(result as any, options, theme));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createSearchTool(cwd: string): AgentTool<typeof searchSchema> {\n\treturn wrapToolDefinition(createSearchToolDefinition(cwd));\n}\n\n/** Default search tool using process.cwd() for backwards compatibility. */\nexport const searchToolDefinition = createSearchToolDefinition(process.cwd());\nexport const searchTool = createSearchTool(process.cwd());\n"]}
|
|
@@ -28,6 +28,9 @@ const searchSchema = Type.Object({
|
|
|
28
28
|
})),
|
|
29
29
|
rebuild: Type.Optional(Type.Boolean({ description: "Force a clean rebuild of the search index (default: false)" })),
|
|
30
30
|
});
|
|
31
|
+
function normalizeLineEndings(text) {
|
|
32
|
+
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
33
|
+
}
|
|
31
34
|
// ============================================================================
|
|
32
35
|
// Rendering
|
|
33
36
|
// ============================================================================
|
|
@@ -53,7 +56,7 @@ export function formatSearchCall(args, theme) {
|
|
|
53
56
|
}
|
|
54
57
|
/** @internal Exported for testing. */
|
|
55
58
|
export function formatSearchResult(result, options, theme) {
|
|
56
|
-
const output = result.content[0]?.text
|
|
59
|
+
const output = result.content[0]?.text ? normalizeLineEndings(result.content[0].text).trim() : "";
|
|
57
60
|
if (!output)
|
|
58
61
|
return "";
|
|
59
62
|
const lines = output.split("\n");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../../src/core/tools/search.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Semantic codebase search tool.\n *\n * Uses embeddings + FTS5 to support natural language queries over the codebase.\n * Feature-gated on `node:sqlite` availability (Node 22+).\n */\n\nimport { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { formatResults, SearchEngine } from \"@dreb/semantic-search\";\nimport { Text } from \"@dreb/tui\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { getDrebToolVisibleDirs } from \"./dreb-paths.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\n// ============================================================================\n// Schema\n// ============================================================================\n\nconst searchSchema = Type.Object({\n\tquery: Type.String({ description: \"The search query (natural language, identifier, or path)\" }),\n\trestrictToDir: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Filter results to files under this path (relative to searchDir or cwd). Does not affect indexing — the entire searchDir is still indexed.\",\n\t\t}),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results to return (default: 20)\" })),\n\tsearchDir: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Directory to index and search instead of cwd (useful when cwd is ~/). The entire contents of this directory are scanned and indexed.\",\n\t\t}),\n\t),\n\trebuild: Type.Optional(Type.Boolean({ description: \"Force a clean rebuild of the search index (default: false)\" })),\n});\n\nexport type SearchToolInput = Static<typeof searchSchema>;\n\n// ============================================================================\n// Details\n// ============================================================================\n\nexport interface SearchToolDetails {\n\tresultCount: number;\n\tindexBuilt: boolean;\n\tindexStats?: { files: number; chunks: number };\n}\n\n// ============================================================================\n// Rendering\n// ============================================================================\n\n/** @internal Exported for testing. */\nexport function formatSearchCall(\n\targs: { query?: string; restrictToDir?: string; limit?: number; searchDir?: string; rebuild?: boolean } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst query = str(args?.query);\n\tconst restrictToDir = str(args?.restrictToDir);\n\tconst searchDir = str(args?.searchDir);\n\tlet text = `${theme.fg(\"toolTitle\", theme.bold(\"search\"))} ${theme.fg(\"accent\", `\"${query ?? \"\"}\"`)}`;\n\tif (searchDir) {\n\t\ttext += theme.fg(\"toolOutput\", ` project ${shortenPath(searchDir)}`);\n\t}\n\tif (restrictToDir) {\n\t\ttext += theme.fg(\"toolOutput\", ` in ${shortenPath(restrictToDir)}`);\n\t}\n\tif (args?.rebuild) {\n\t\ttext += theme.fg(\"toolOutput\", \" [rebuild]\");\n\t}\n\tif (args?.limit !== undefined) {\n\t\ttext += theme.fg(\"toolOutput\", ` limit ${args.limit}`);\n\t}\n\treturn text;\n}\n\n/** @internal Exported for testing. */\nexport function formatSearchResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string }>;\n\t\tdetails?: SearchToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst output = result.content[0]?.text?.trim() ?? \"\";\n\tif (!output) return \"\";\n\n\tconst lines = output.split(\"\\n\");\n\tconst maxLines = options.expanded ? lines.length : 20;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\n\tlet text = `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `\\n${theme.fg(\"muted\", `... (${remaining} more lines)`)}`;\n\t}\n\n\tif (result.details?.indexStats) {\n\t\tconst { files, chunks } = result.details.indexStats;\n\t\ttext += `\\n${theme.fg(\"muted\", `[Index: ${files} files, ${chunks} chunks]`)}`;\n\t}\n\n\treturn text;\n}\n\n// ============================================================================\n// Tool Definition\n// ============================================================================\n\n/** Check if the search tool is available (requires node:sqlite). */\nexport function isSearchAvailable(): boolean {\n\treturn SearchEngine.isAvailable();\n}\n\n// Cache search engines per project root to reuse index across calls within a session\nconst engineCache = new Map<string, SearchEngine>();\n\nfunction getSearchEngine(projectRoot: string): SearchEngine {\n\tlet engine = engineCache.get(projectRoot);\n\tif (!engine) {\n\t\tengine = new SearchEngine(projectRoot, {\n\t\t\tindexDir: path.join(projectRoot, \".dreb\", \"index\"),\n\t\t\tglobalMemoryDir: path.join(homedir(), \".dreb\", \"memory\"),\n\t\t\tmodelCacheDir: path.join(homedir(), \".dreb\", \"agent\", \"models\"),\n\t\t\tvisibleDirs: getDrebToolVisibleDirs,\n\t\t});\n\t\tengineCache.set(projectRoot, engine);\n\t}\n\treturn engine;\n}\n\nexport function createSearchToolDefinition(cwd: string): ToolDefinition<typeof searchSchema, SearchToolDetails> {\n\treturn {\n\t\tname: \"search\",\n\t\tlabel: \"search\",\n\t\tdescription:\n\t\t\t\"Search the codebase using natural language queries. Returns ranked code/doc results using semantic similarity and keyword matching. First query builds the index (may take a moment); subsequent queries are fast. Supports identifier queries (e.g. 'AuthMiddleware'), natural language (e.g. 'where is rate limiting handled'), and path queries (e.g. 'src/auth/').\",\n\t\tpromptSnippet: \"Semantic codebase search — natural language queries over code and docs\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `search` as your default exploration tool — for understanding code, finding where things are, and answering questions about the codebase. Use `grep` when you already know the exact text or pattern you're looking for.\",\n\t\t\t\"The first search query builds an index (may take 10-60s). Subsequent queries are fast.\",\n\t\t],\n\t\tparameters: searchSchema,\n\n\t\tasync execute(_toolCallId, params, signal, onUpdate, _ctx) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\t\tif (!isSearchAvailable()) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: \"Semantic search requires Node.js 22+ (for built-in SQLite). Current Node.js version does not support node:sqlite.\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst { query, restrictToDir, limit, searchDir, rebuild } = params;\n\n\t\t\tif (!query || query.trim().length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Search query cannot be empty.\" }],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst resolvedSearchDir = searchDir ? resolveToCwd(searchDir, cwd) : cwd;\n\n\t\t\tif (searchDir && (!existsSync(resolvedSearchDir) || !statSync(resolvedSearchDir).isDirectory())) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `searchDir does not exist or is not a directory: ${resolvedSearchDir}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst engine = getSearchEngine(resolvedSearchDir);\n\n\t\t\tif (rebuild) {\n\t\t\t\tawait engine.resetIndex();\n\t\t\t}\n\n\t\t\tlet indexBuilt = false;\n\t\t\tconst results = await engine.search(query, {\n\t\t\t\tlimit: typeof limit === \"number\" && limit > 0 ? Math.floor(limit) : 20,\n\t\t\t\tpathFilter: restrictToDir,\n\t\t\t\tonProgress: (phase, current, total) => {\n\t\t\t\t\tif (phase === \"indexing\" || phase === \"scanning\" || phase === \"loading model\" || phase === \"embedding\") {\n\t\t\t\t\t\tindexBuilt = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (onUpdate) {\n\t\t\t\t\t\tonUpdate({\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `${phase}: ${current}/${total}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: true } as SearchToolDetails,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tif (results.length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"No results found.\" }],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst text = formatResults(results);\n\n\t\t\t// Get index stats from the existing engine (no new connection)\n\t\t\tconst stats = engine.getStats();\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\tdetails: {\n\t\t\t\t\tresultCount: results.length,\n\t\t\t\t\tindexBuilt,\n\t\t\t\t\tindexStats: stats ?? undefined,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatSearchCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatSearchResult(result as any, options, theme));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createSearchTool(cwd: string): AgentTool<typeof searchSchema> {\n\treturn wrapToolDefinition(createSearchToolDefinition(cwd));\n}\n\n/** Default search tool using process.cwd() for backwards compatibility. */\nexport const searchToolDefinition = createSearchToolDefinition(process.cwd());\nexport const searchTool = createSearchTool(process.cwd());\n"]}
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../../src/core/tools/search.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Semantic codebase search tool.\n *\n * Uses embeddings + FTS5 to support natural language queries over the codebase.\n * Feature-gated on `node:sqlite` availability (Node 22+).\n */\n\nimport { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { formatResults, SearchEngine } from \"@dreb/semantic-search\";\nimport { Text } from \"@dreb/tui\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { getDrebToolVisibleDirs } from \"./dreb-paths.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\n// ============================================================================\n// Schema\n// ============================================================================\n\nconst searchSchema = Type.Object({\n\tquery: Type.String({ description: \"The search query (natural language, identifier, or path)\" }),\n\trestrictToDir: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Filter results to files under this path (relative to searchDir or cwd). Does not affect indexing — the entire searchDir is still indexed.\",\n\t\t}),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results to return (default: 20)\" })),\n\tsearchDir: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Directory to index and search instead of cwd (useful when cwd is ~/). The entire contents of this directory are scanned and indexed.\",\n\t\t}),\n\t),\n\trebuild: Type.Optional(Type.Boolean({ description: \"Force a clean rebuild of the search index (default: false)\" })),\n});\n\nexport type SearchToolInput = Static<typeof searchSchema>;\n\n// ============================================================================\n// Details\n// ============================================================================\n\nexport interface SearchToolDetails {\n\tresultCount: number;\n\tindexBuilt: boolean;\n\tindexStats?: { files: number; chunks: number };\n}\n\nfunction normalizeLineEndings(text: string): string {\n\treturn text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n}\n\n// ============================================================================\n// Rendering\n// ============================================================================\n\n/** @internal Exported for testing. */\nexport function formatSearchCall(\n\targs: { query?: string; restrictToDir?: string; limit?: number; searchDir?: string; rebuild?: boolean } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst query = str(args?.query);\n\tconst restrictToDir = str(args?.restrictToDir);\n\tconst searchDir = str(args?.searchDir);\n\tlet text = `${theme.fg(\"toolTitle\", theme.bold(\"search\"))} ${theme.fg(\"accent\", `\"${query ?? \"\"}\"`)}`;\n\tif (searchDir) {\n\t\ttext += theme.fg(\"toolOutput\", ` project ${shortenPath(searchDir)}`);\n\t}\n\tif (restrictToDir) {\n\t\ttext += theme.fg(\"toolOutput\", ` in ${shortenPath(restrictToDir)}`);\n\t}\n\tif (args?.rebuild) {\n\t\ttext += theme.fg(\"toolOutput\", \" [rebuild]\");\n\t}\n\tif (args?.limit !== undefined) {\n\t\ttext += theme.fg(\"toolOutput\", ` limit ${args.limit}`);\n\t}\n\treturn text;\n}\n\n/** @internal Exported for testing. */\nexport function formatSearchResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string }>;\n\t\tdetails?: SearchToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst output = result.content[0]?.text ? normalizeLineEndings(result.content[0].text).trim() : \"\";\n\tif (!output) return \"\";\n\n\tconst lines = output.split(\"\\n\");\n\tconst maxLines = options.expanded ? lines.length : 20;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\n\tlet text = `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `\\n${theme.fg(\"muted\", `... (${remaining} more lines)`)}`;\n\t}\n\n\tif (result.details?.indexStats) {\n\t\tconst { files, chunks } = result.details.indexStats;\n\t\ttext += `\\n${theme.fg(\"muted\", `[Index: ${files} files, ${chunks} chunks]`)}`;\n\t}\n\n\treturn text;\n}\n\n// ============================================================================\n// Tool Definition\n// ============================================================================\n\n/** Check if the search tool is available (requires node:sqlite). */\nexport function isSearchAvailable(): boolean {\n\treturn SearchEngine.isAvailable();\n}\n\n// Cache search engines per project root to reuse index across calls within a session\nconst engineCache = new Map<string, SearchEngine>();\n\nfunction getSearchEngine(projectRoot: string): SearchEngine {\n\tlet engine = engineCache.get(projectRoot);\n\tif (!engine) {\n\t\tengine = new SearchEngine(projectRoot, {\n\t\t\tindexDir: path.join(projectRoot, \".dreb\", \"index\"),\n\t\t\tglobalMemoryDir: path.join(homedir(), \".dreb\", \"memory\"),\n\t\t\tmodelCacheDir: path.join(homedir(), \".dreb\", \"agent\", \"models\"),\n\t\t\tvisibleDirs: getDrebToolVisibleDirs,\n\t\t});\n\t\tengineCache.set(projectRoot, engine);\n\t}\n\treturn engine;\n}\n\nexport function createSearchToolDefinition(cwd: string): ToolDefinition<typeof searchSchema, SearchToolDetails> {\n\treturn {\n\t\tname: \"search\",\n\t\tlabel: \"search\",\n\t\tdescription:\n\t\t\t\"Search the codebase using natural language queries. Returns ranked code/doc results using semantic similarity and keyword matching. First query builds the index (may take a moment); subsequent queries are fast. Supports identifier queries (e.g. 'AuthMiddleware'), natural language (e.g. 'where is rate limiting handled'), and path queries (e.g. 'src/auth/').\",\n\t\tpromptSnippet: \"Semantic codebase search — natural language queries over code and docs\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `search` as your default exploration tool — for understanding code, finding where things are, and answering questions about the codebase. Use `grep` when you already know the exact text or pattern you're looking for.\",\n\t\t\t\"The first search query builds an index (may take 10-60s). Subsequent queries are fast.\",\n\t\t],\n\t\tparameters: searchSchema,\n\n\t\tasync execute(_toolCallId, params, signal, onUpdate, _ctx) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\t\tif (!isSearchAvailable()) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: \"Semantic search requires Node.js 22+ (for built-in SQLite). Current Node.js version does not support node:sqlite.\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst { query, restrictToDir, limit, searchDir, rebuild } = params;\n\n\t\t\tif (!query || query.trim().length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Search query cannot be empty.\" }],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst resolvedSearchDir = searchDir ? resolveToCwd(searchDir, cwd) : cwd;\n\n\t\t\tif (searchDir && (!existsSync(resolvedSearchDir) || !statSync(resolvedSearchDir).isDirectory())) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `searchDir does not exist or is not a directory: ${resolvedSearchDir}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: false },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst engine = getSearchEngine(resolvedSearchDir);\n\n\t\t\tif (rebuild) {\n\t\t\t\tawait engine.resetIndex();\n\t\t\t}\n\n\t\t\tlet indexBuilt = false;\n\t\t\tconst results = await engine.search(query, {\n\t\t\t\tlimit: typeof limit === \"number\" && limit > 0 ? Math.floor(limit) : 20,\n\t\t\t\tpathFilter: restrictToDir,\n\t\t\t\tonProgress: (phase, current, total) => {\n\t\t\t\t\tif (phase === \"indexing\" || phase === \"scanning\" || phase === \"loading model\" || phase === \"embedding\") {\n\t\t\t\t\t\tindexBuilt = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (onUpdate) {\n\t\t\t\t\t\tonUpdate({\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `${phase}: ${current}/${total}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { resultCount: 0, indexBuilt: true } as SearchToolDetails,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tif (results.length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"No results found.\" }],\n\t\t\t\t\tdetails: { resultCount: 0, indexBuilt },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst text = formatResults(results);\n\n\t\t\t// Get index stats from the existing engine (no new connection)\n\t\t\tconst stats = engine.getStats();\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\tdetails: {\n\t\t\t\t\tresultCount: results.length,\n\t\t\t\t\tindexBuilt,\n\t\t\t\t\tindexStats: stats ?? undefined,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatSearchCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatSearchResult(result as any, options, theme));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createSearchTool(cwd: string): AgentTool<typeof searchSchema> {\n\treturn wrapToolDefinition(createSearchToolDefinition(cwd));\n}\n\n/** Default search tool using process.cwd() for backwards compatibility. */\nexport const searchToolDefinition = createSearchToolDefinition(process.cwd());\nexport const searchTool = createSearchTool(process.cwd());\n"]}
|
|
@@ -38,6 +38,45 @@ export interface SubagentResult {
|
|
|
38
38
|
* back to SUBAGENT_DEFAULT_TOOLS if all specified tools were excluded).
|
|
39
39
|
*/
|
|
40
40
|
export declare function filterSubagentTools(tools: string | undefined): string;
|
|
41
|
+
/**
|
|
42
|
+
* Sinks for the child process's JSONL stdout stream. Extracted so the line
|
|
43
|
+
* handling is unit-testable without spawning a real child process.
|
|
44
|
+
*/
|
|
45
|
+
export interface ChildLineSinks {
|
|
46
|
+
/** Called with every successfully parsed JSONL event (including the session header). */
|
|
47
|
+
onEvent?: (event: Record<string, unknown>) => void;
|
|
48
|
+
/** Called with each complete assistant message (`message_end`). */
|
|
49
|
+
onAssistantMessage: (message: {
|
|
50
|
+
role: string;
|
|
51
|
+
content: any[];
|
|
52
|
+
}) => void;
|
|
53
|
+
/** Called with human-readable progress lines (tool start/end). */
|
|
54
|
+
onProgress?: (text: string) => void;
|
|
55
|
+
/** Called when the child reports its resolved model (`agent_start`). */
|
|
56
|
+
onModel: (modelId: string) => void;
|
|
57
|
+
/** Called with lines that failed to parse as JSON (often real startup errors). */
|
|
58
|
+
onPlainLine: (line: string) => void;
|
|
59
|
+
/** Mutable holder for the last tool name, shared across lines for progress text. */
|
|
60
|
+
toolNameRef: {
|
|
61
|
+
current: string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Handle one line of a subagent child's JSONL stdout. Parses the line and
|
|
66
|
+
* dispatches to the sinks: full-event relay, assistant-message collection,
|
|
67
|
+
* tool progress, resolved model, and non-JSON passthrough.
|
|
68
|
+
*/
|
|
69
|
+
export declare function handleChildJsonlLine(line: string, sinks: ChildLineSinks): void;
|
|
70
|
+
/**
|
|
71
|
+
* Find session JSONL files for a subagent session directory.
|
|
72
|
+
*
|
|
73
|
+
* Normal subagents write one .jsonl directly under sessionDir, so this returns
|
|
74
|
+
* the most recently modified flat file. Chain-mode subagents register the chain
|
|
75
|
+
* root as sessionDir but write each step under step-N/, so when no flat file is
|
|
76
|
+
* present this recurses one level into step-* directories and returns one file
|
|
77
|
+
* per step in numeric step order.
|
|
78
|
+
*/
|
|
79
|
+
export declare function discoverSessionFiles(sessionDir: string, agentName: string): string[];
|
|
41
80
|
/**
|
|
42
81
|
* Find the most recently modified .jsonl file in a session directory.
|
|
43
82
|
* Returns the full path, or undefined if no session file was written
|
|
@@ -103,14 +142,30 @@ export declare function resolveModelForSubagentSpawn(models: string | string[],
|
|
|
103
142
|
logPrefix?: string): Promise<SubagentModelResolution>;
|
|
104
143
|
export declare function formatModelFallbackSummary(skippedModels: SkippedFallbackModel[], selectedModel: string | undefined): string | undefined;
|
|
105
144
|
export declare function prependModelFallbackSummary(output: string, skippedModels: SkippedFallbackModel[], selectedModel: string | undefined): string;
|
|
106
|
-
export declare function executeSingle(agents: Map<string, AgentTypeConfig>, agentName: string | undefined, task: string, cwd: string, signal?: AbortSignal, onProgress?: (event: string) => void, modelOverride?: string, parentProvider?: string, registry?: ModelRegistry, sessionDir?: string, parentModel?: string, agentModels?: string[], parentSessionFile?: string): Promise<SubagentResult>;
|
|
145
|
+
export declare function executeSingle(agents: Map<string, AgentTypeConfig>, agentName: string | undefined, task: string, cwd: string, signal?: AbortSignal, onProgress?: (event: string) => void, modelOverride?: string, parentProvider?: string, registry?: ModelRegistry, sessionDir?: string, parentModel?: string, agentModels?: string[], parentSessionFile?: string, onChildEvent?: (event: Record<string, unknown>) => void): Promise<SubagentResult>;
|
|
107
146
|
export interface BackgroundAgentInfo {
|
|
108
147
|
agentId: string;
|
|
109
148
|
agentType: string;
|
|
110
149
|
taskSummary: string;
|
|
111
150
|
startedAt: number;
|
|
112
151
|
status: "running" | "completed" | "failed";
|
|
152
|
+
/** Directory containing the agent's session JSONL file (known at spawn time). */
|
|
153
|
+
sessionDir?: string;
|
|
154
|
+
/** Path to the agent's session JSONL file (discovered when the child exits). */
|
|
155
|
+
sessionFile?: string;
|
|
156
|
+
/** Working directory the agent runs in. */
|
|
157
|
+
cwd?: string;
|
|
113
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Best-effort recovery for completed background subagents after a dashboard/RPC
|
|
161
|
+
* process resumes an existing parent session. Live background-agent state is an
|
|
162
|
+
* in-memory registry, while child sessions are durable JSONL files under the
|
|
163
|
+
* subagent sessions directory.
|
|
164
|
+
*
|
|
165
|
+
* Returns the number of newly registered agents. Expected filesystem and JSONL
|
|
166
|
+
* parse failures are skipped; unexpected programming errors are allowed to throw.
|
|
167
|
+
*/
|
|
168
|
+
export declare function rehydrateBackgroundAgentsFromDisk(parentSessionFile: string | undefined, subagentSessionsBase?: string): number;
|
|
114
169
|
/** Get a snapshot of all tracked background agents (running and recently completed). Returns readonly clones. */
|
|
115
170
|
export declare function getBackgroundAgents(): readonly Readonly<BackgroundAgentInfo>[];
|
|
116
171
|
/** Get only currently running background agents. Returns readonly clones. */
|
|
@@ -121,9 +176,15 @@ export declare function abortBackgroundAgents(): void;
|
|
|
121
176
|
export declare function pruneBackgroundAgents(maxAgeMs?: number): void;
|
|
122
177
|
export interface SubagentToolOptions {
|
|
123
178
|
/** Called when a background subagent starts. Used by TUI to show status indicators. */
|
|
124
|
-
onBackgroundStart?: (agentId: string, agentType: string, taskSummary: string) => void;
|
|
179
|
+
onBackgroundStart?: (agentId: string, agentType: string, taskSummary: string, sessionDir?: string) => void;
|
|
125
180
|
/** Called when a background subagent completes with its result. `cancelled` is true if the user aborted it. */
|
|
126
181
|
onBackgroundComplete?: (agentId: string, result: SubagentResult, cancelled: boolean) => void;
|
|
182
|
+
/**
|
|
183
|
+
* Called with every JSONL event a background child process emits, tagged with the
|
|
184
|
+
* child's agentId. Enables live observability relays (e.g. the dashboard) without
|
|
185
|
+
* tailing session files. Chain steps share the chain's agentId.
|
|
186
|
+
*/
|
|
187
|
+
onBackgroundEvent?: (agentId: string, event: Record<string, unknown>) => void;
|
|
127
188
|
/** Parent session's current provider (e.g. "anthropic"). Called at each invocation to get the live value after mid-session model switches. */
|
|
128
189
|
parentProvider?: () => string | undefined;
|
|
129
190
|
/** Parent session's current model ID. Used as a final fallback when all subagent-configured models fail to resolve. Called at each invocation for fresh value. */
|