@dreb/coding-agent 2.34.3 → 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.
Files changed (80) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/dist/cli/args.d.ts +3 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +3 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +30 -2
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +172 -130
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/export-html/template.js +1 -1
  12. package/dist/core/footer-data-provider.d.ts.map +1 -1
  13. package/dist/core/footer-data-provider.js +5 -102
  14. package/dist/core/footer-data-provider.js.map +1 -1
  15. package/dist/core/git-branch.d.ts +19 -0
  16. package/dist/core/git-branch.d.ts.map +1 -0
  17. package/dist/core/git-branch.js +109 -0
  18. package/dist/core/git-branch.js.map +1 -0
  19. package/dist/core/sdk.d.ts.map +1 -1
  20. package/dist/core/sdk.js +1 -1
  21. package/dist/core/sdk.js.map +1 -1
  22. package/dist/core/session-manager.d.ts +21 -0
  23. package/dist/core/session-manager.d.ts.map +1 -1
  24. package/dist/core/session-manager.js +117 -42
  25. package/dist/core/session-manager.js.map +1 -1
  26. package/dist/core/settings-manager.d.ts +6 -0
  27. package/dist/core/settings-manager.d.ts.map +1 -1
  28. package/dist/core/settings-manager.js +8 -0
  29. package/dist/core/settings-manager.js.map +1 -1
  30. package/dist/{modes/interactive → core}/tab-title.d.ts +12 -3
  31. package/dist/core/tab-title.d.ts.map +1 -0
  32. package/dist/{modes/interactive → core}/tab-title.js +58 -16
  33. package/dist/core/tab-title.js.map +1 -0
  34. package/dist/core/tools/search.d.ts.map +1 -1
  35. package/dist/core/tools/search.js +4 -1
  36. package/dist/core/tools/search.js.map +1 -1
  37. package/dist/core/tools/subagent.d.ts +63 -2
  38. package/dist/core/tools/subagent.d.ts.map +1 -1
  39. package/dist/core/tools/subagent.js +459 -73
  40. package/dist/core/tools/subagent.js.map +1 -1
  41. package/dist/core/tools/web.d.ts +1 -1
  42. package/dist/core/tools/web.d.ts.map +1 -1
  43. package/dist/core/tools/web.js +60 -16
  44. package/dist/core/tools/web.js.map +1 -1
  45. package/dist/main.d.ts +10 -0
  46. package/dist/main.d.ts.map +1 -1
  47. package/dist/main.js +54 -4
  48. package/dist/main.js.map +1 -1
  49. package/dist/modes/index.d.ts +2 -2
  50. package/dist/modes/index.d.ts.map +1 -1
  51. package/dist/modes/index.js +1 -1
  52. package/dist/modes/index.js.map +1 -1
  53. package/dist/modes/interactive/components/session-selector.d.ts +1 -1
  54. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  55. package/dist/modes/interactive/components/session-selector.js +4 -40
  56. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  57. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  58. package/dist/modes/interactive/interactive-mode.js +2 -2
  59. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  60. package/dist/modes/rpc/index.d.ts +2 -2
  61. package/dist/modes/rpc/index.d.ts.map +1 -1
  62. package/dist/modes/rpc/index.js.map +1 -1
  63. package/dist/modes/rpc/rpc-client.d.ts +114 -6
  64. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  65. package/dist/modes/rpc/rpc-client.js +140 -2
  66. package/dist/modes/rpc/rpc-client.js.map +1 -1
  67. package/dist/modes/rpc/rpc-mode.d.ts +97 -0
  68. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  69. package/dist/modes/rpc/rpc-mode.js +656 -27
  70. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  71. package/dist/modes/rpc/rpc-types.d.ts +312 -1
  72. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  73. package/dist/modes/rpc/rpc-types.js.map +1 -1
  74. package/docs/dashboard.md +152 -0
  75. package/docs/development.md +8 -5
  76. package/docs/rpc.md +652 -5
  77. package/examples/sdk/12-full-control.ts +1 -1
  78. package/package.json +1 -1
  79. package/dist/modes/interactive/tab-title.d.ts.map +0 -1
  80. 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?.trim() ?? "";
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. */