@lincoln504/pi-research 0.1.12 → 1.0.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 (248) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +64 -115
  3. package/dist/cli.mjs +23211 -0
  4. package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
  5. package/dist/prompts/research-knowledge-search-extractor.md +41 -0
  6. package/dist/prompts/research-tool-usage.md +92 -0
  7. package/dist/prompts/researcher.md +89 -0
  8. package/dist/prompts/system-coordinator.md +75 -0
  9. package/dist/prompts/system-lead-evaluator.md +118 -0
  10. package/dist/thread-worker.mjs +2611 -0
  11. package/dist/webgpu-probe.mjs +80 -0
  12. package/docs/AGENT-SKILL.md +102 -0
  13. package/docs/ARCHITECTURE.md +306 -80
  14. package/docs/CONFIGURATION.md +249 -0
  15. package/docs/KNOWLEDGE-STORE.md +213 -0
  16. package/docs/PI-EXTENSION.md +91 -0
  17. package/docs/SDK.md +113 -0
  18. package/docs/THIRD-PARTY-NOTICES.md +64 -0
  19. package/package.json +102 -36
  20. package/scripts/build.cjs +140 -0
  21. package/scripts/cleanup.cjs +149 -0
  22. package/scripts/prepare.cjs +28 -0
  23. package/scripts/setup.cjs +134 -0
  24. package/skills/pi-research/README.md +90 -0
  25. package/skills/pi-research/SKILL.md +102 -0
  26. package/skills/pi-research/references/configuration.md +77 -0
  27. package/skills/pi-research/scripts/run.mjs +237 -0
  28. package/src/cleanup/research-cleanup.ts +140 -0
  29. package/src/cli.ts +1044 -0
  30. package/src/config.ts +1036 -147
  31. package/src/constants.ts +115 -84
  32. package/src/core/health-check-service.ts +265 -0
  33. package/src/core/interfaces/health-check-interfaces.ts +50 -0
  34. package/src/core/interfaces/knowledge-interfaces.ts +190 -0
  35. package/src/core/interfaces/observer-interfaces.ts +67 -0
  36. package/src/core/interfaces/orchestration-interfaces.ts +86 -0
  37. package/src/core/interfaces/planning-interfaces.ts +100 -0
  38. package/src/core/interfaces/process-interfaces.ts +37 -0
  39. package/src/core/interfaces/research-plan-types.ts +52 -0
  40. package/src/core/interfaces/scheduler-interfaces.ts +91 -0
  41. package/src/core/interfaces/service-names.ts +39 -0
  42. package/src/core/interfaces/state-manager-interfaces.ts +29 -0
  43. package/src/core/interfaces/steering-interfaces.ts +31 -0
  44. package/src/core/llm/agentic-repair.ts +169 -0
  45. package/src/core/llm/inject-date.ts +52 -0
  46. package/src/core/llm/llm-timeout.ts +64 -0
  47. package/src/core/llm/llm-utils.ts +86 -0
  48. package/src/core/llm/model-registry-factory.ts +287 -0
  49. package/src/core/llm/pi-ai-completion.ts +29 -0
  50. package/src/core/llm/prompts.ts +50 -0
  51. package/src/core/llm/research-model-resolver.ts +91 -0
  52. package/src/core/planning-constants.ts +13 -0
  53. package/src/core/planning-service.ts +649 -0
  54. package/src/core/planning-utils.ts +299 -0
  55. package/src/core/scheduler-factory.ts +48 -0
  56. package/src/core/scheduler-service.ts +123 -0
  57. package/src/core/service-initialization.ts +194 -0
  58. package/src/core/service-interfaces.ts +48 -0
  59. package/src/core/service-registry.ts +659 -0
  60. package/src/healthcheck/index.ts +271 -265
  61. package/src/healthcheck/registry.ts +130 -0
  62. package/src/index.ts +639 -50
  63. package/src/infrastructure/browser/browser-cleanup.ts +309 -0
  64. package/src/infrastructure/browser/browser-client.ts +199 -0
  65. package/src/infrastructure/browser/browser-error-utils.ts +160 -0
  66. package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
  67. package/src/infrastructure/browser/browser-server.ts +174 -0
  68. package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
  69. package/src/infrastructure/browser/cleanup-utils.ts +155 -0
  70. package/src/infrastructure/browser/client-agent.ts +31 -0
  71. package/src/infrastructure/browser/config.ts +288 -0
  72. package/src/infrastructure/browser/ensure-browser.ts +205 -0
  73. package/src/infrastructure/browser/index.ts +37 -0
  74. package/src/infrastructure/browser/priority-task-queue.ts +226 -0
  75. package/src/infrastructure/browser/scheduler-factory.ts +354 -0
  76. package/src/infrastructure/browser/task-execution-service.ts +209 -0
  77. package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
  78. package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
  79. package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
  80. package/src/infrastructure/browser/thread-worker-types.ts +25 -0
  81. package/src/infrastructure/browser/thread-worker.mjs +2611 -0
  82. package/src/infrastructure/browser/thread-worker.ts +168 -0
  83. package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
  84. package/src/infrastructure/embedding/embedding-client.ts +205 -0
  85. package/src/infrastructure/embedding/embedding-factory.ts +324 -0
  86. package/src/infrastructure/embedding/embedding-server.ts +535 -0
  87. package/src/infrastructure/file-lock-service.ts +592 -0
  88. package/src/infrastructure/gpu-resource-service.ts +191 -0
  89. package/src/infrastructure/knowledge-store-service.ts +475 -0
  90. package/src/infrastructure/metrics-service.ts +129 -0
  91. package/src/infrastructure/process-lifecycle-service.ts +253 -0
  92. package/src/infrastructure/scheduler-factory-service.ts +58 -0
  93. package/src/infrastructure/service-initialization.ts +292 -0
  94. package/src/infrastructure/state/state-backup-manager.ts +290 -0
  95. package/src/infrastructure/state/state-browser-api.ts +62 -0
  96. package/src/infrastructure/state/state-browser-manager.ts +70 -0
  97. package/src/infrastructure/state/state-manager-service.ts +264 -0
  98. package/src/infrastructure/state/state-manager.ts +547 -0
  99. package/src/infrastructure/state/state-metrics.ts +92 -0
  100. package/src/infrastructure/state/state-migration.ts +103 -0
  101. package/src/infrastructure/state/state-path-configuration.ts +119 -0
  102. package/src/infrastructure/state/state-session-api.ts +94 -0
  103. package/src/infrastructure/state/state-session-manager.ts +127 -0
  104. package/src/infrastructure/state/state-validator.ts +93 -0
  105. package/src/infrastructure/types/state-types.ts +85 -0
  106. package/src/knowledge/chunker.ts +157 -0
  107. package/src/knowledge/embedder-init.ts +413 -0
  108. package/src/knowledge/embedder-utils.ts +296 -0
  109. package/src/knowledge/embedder.ts +592 -0
  110. package/src/knowledge/index.ts +126 -0
  111. package/src/knowledge/lancedb-loader.ts +35 -0
  112. package/src/knowledge/migration.ts +17 -0
  113. package/src/knowledge/model-config.ts +132 -0
  114. package/src/knowledge/onnx-env.ts +48 -0
  115. package/src/knowledge/store-operations.ts +291 -0
  116. package/src/knowledge/store-schema.ts +79 -0
  117. package/src/knowledge/store.ts +1251 -0
  118. package/src/knowledge/transformers-loader.ts +31 -0
  119. package/src/knowledge/webgpu-probe.mjs +80 -0
  120. package/src/knowledge/webgpu-viability.ts +255 -0
  121. package/src/knowledge/writer-queue.ts +252 -0
  122. package/src/logger.ts +316 -122
  123. package/src/observers/research-observer-impl.ts +459 -0
  124. package/src/orchestration/deep-research-orchestrator.ts +524 -773
  125. package/src/orchestration/deep-research-types.ts +47 -35
  126. package/src/orchestration/headless-observer.ts +114 -0
  127. package/src/orchestration/orchestration-types.ts +33 -0
  128. package/src/orchestration/quick-research-orchestrator.ts +463 -0
  129. package/src/orchestration/research-orchestration-service.ts +549 -0
  130. package/src/orchestration/research-session-service.ts +178 -0
  131. package/src/orchestration/research-synthesis-service.ts +348 -0
  132. package/src/orchestration/researcher-executor.ts +394 -0
  133. package/src/orchestration/researcher.ts +115 -42
  134. package/src/orchestration/service-initialization.ts +109 -0
  135. package/src/orchestration/session-context.ts +7 -3
  136. package/src/orchestration/session-state.ts +672 -0
  137. package/src/prompts/research-knowledge-relevance-triage.md +35 -0
  138. package/src/prompts/research-knowledge-search-extractor.md +41 -0
  139. package/src/prompts/research-tool-usage.md +63 -16
  140. package/src/prompts/researcher.md +59 -136
  141. package/src/prompts/system-coordinator.md +64 -13
  142. package/src/prompts/system-lead-evaluator.md +82 -189
  143. package/src/research-config.ts +872 -0
  144. package/src/sdk.ts +847 -0
  145. package/src/security/cisa-kev.ts +72 -9
  146. package/src/security/cvss.ts +92 -0
  147. package/src/security/ecosystem.ts +45 -0
  148. package/src/security/github-advisories.ts +168 -428
  149. package/src/security/github-advisory-types.ts +282 -0
  150. package/src/security/index.ts +63 -177
  151. package/src/security/nvd-client.ts +550 -0
  152. package/src/security/nvd-types.ts +179 -0
  153. package/src/security/osv-client.ts +246 -0
  154. package/src/security/osv-types.ts +285 -0
  155. package/src/security/types.ts +67 -53
  156. package/src/skill-install/skill-installer.ts +519 -0
  157. package/src/stackexchange/index.ts +81 -27
  158. package/src/stackexchange/output/compact.ts +7 -6
  159. package/src/stackexchange/output/table.ts +11 -7
  160. package/src/stackexchange/queries.ts +0 -10
  161. package/src/stackexchange/rest-client.ts +141 -66
  162. package/src/stackexchange/types.ts +0 -17
  163. package/src/tool.ts +15 -423
  164. package/src/tools/grep.ts +32 -310
  165. package/src/tools/health-tool-definition.ts +101 -0
  166. package/src/tools/index.ts +38 -9
  167. package/src/tools/research-knowledge-search.ts +974 -0
  168. package/src/tools/research-knowledge-types.ts +77 -0
  169. package/src/tools/research-tool-definition.ts +494 -0
  170. package/src/tools/scrape.ts +286 -388
  171. package/src/tools/search.ts +98 -153
  172. package/src/tools/security.ts +107 -80
  173. package/src/tools/stackexchange.ts +58 -35
  174. package/src/tools/youtube-transcript.ts +163 -0
  175. package/src/tui/knowledge-search-panel.ts +86 -0
  176. package/src/tui/pulse.ts +86 -0
  177. package/src/tui/research-health.ts +124 -0
  178. package/src/tui/research-panel-color-utils.ts +340 -0
  179. package/src/tui/research-panel-format.ts +54 -0
  180. package/src/tui/research-panel-state.ts +404 -0
  181. package/src/tui/research-panel-wave.ts +280 -0
  182. package/src/tui/research-panel.ts +339 -435
  183. package/src/tui/research-tui-manager.ts +233 -0
  184. package/src/tui/tui-controller.ts +137 -0
  185. package/src/types/extension-context.ts +18 -49
  186. package/src/types/index.ts +61 -0
  187. package/src/types/llm.ts +68 -29
  188. package/src/types/research-panel-types.ts +61 -0
  189. package/src/utils/circuit-breaker.ts +146 -0
  190. package/src/utils/citation-utils.ts +97 -0
  191. package/src/utils/disk-space-checker.ts +55 -0
  192. package/src/utils/error-tracker.ts +265 -0
  193. package/src/utils/host-config.ts +45 -0
  194. package/src/utils/input-validation.ts +8 -10
  195. package/src/utils/json-utils.ts +150 -16
  196. package/src/utils/llm-usage.ts +54 -0
  197. package/src/utils/log-rotation.ts +125 -0
  198. package/src/utils/log-utils.ts +159 -0
  199. package/src/utils/make-resource-loader.ts +3 -1
  200. package/src/utils/metrics-summary.ts +459 -0
  201. package/src/utils/metrics.ts +302 -0
  202. package/src/utils/research-export.ts +28 -12
  203. package/src/utils/safe-unref.ts +31 -0
  204. package/src/utils/shared-links.ts +195 -90
  205. package/src/utils/shutdown-manager.ts +65 -0
  206. package/src/utils/stdio-capture.ts +407 -0
  207. package/src/utils/text-utils.ts +264 -7
  208. package/src/utils/tool-usage-tracker.ts +83 -16
  209. package/src/utils/url-utils.ts +255 -0
  210. package/src/utils/user-agent.ts +28 -0
  211. package/src/web-research/browser-search.ts +179 -0
  212. package/src/web-research/retry-utils.ts +173 -77
  213. package/src/web-research/scraper-types.ts +72 -0
  214. package/src/web-research/scraper-utils.ts +434 -0
  215. package/src/web-research/search.ts +78 -218
  216. package/src/web-research/types.ts +9 -24
  217. package/src/web-research/utils.ts +2 -143
  218. package/src/web-research/web-scraper.ts +477 -0
  219. package/src/youtube/index.ts +154 -0
  220. package/src/youtube/potoken.ts +259 -0
  221. package/src/youtube/transcript-client.ts +390 -0
  222. package/src/youtube/video-id.ts +118 -0
  223. package/config/searxng/default-settings.yml +0 -189
  224. package/config/searxng/limiter.toml +0 -34
  225. package/config/tooling/eslint.config.js +0 -73
  226. package/config/tooling/vitest.config.integration.ts +0 -17
  227. package/config/tooling/vitest.config.ts +0 -40
  228. package/config/tooling/vitest.config.unit.ts +0 -12
  229. package/docs/assets/README-banner.jpg +0 -0
  230. package/scripts/setup.js +0 -174
  231. package/src/infrastructure/searxng-lifecycle.ts +0 -577
  232. package/src/infrastructure/searxng-manager.ts +0 -1183
  233. package/src/infrastructure/state-manager.ts +0 -918
  234. package/src/orchestration/deep-research-reducer.ts +0 -129
  235. package/src/orchestration/id-utils.ts +0 -63
  236. package/src/security/nvd.ts +0 -532
  237. package/src/security/osv.ts +0 -487
  238. package/src/stackexchange/cache.ts +0 -111
  239. package/src/stackexchange/output/index.ts +0 -7
  240. package/src/stackexchange/output/json.ts +0 -21
  241. package/src/tui/searxng-status.ts +0 -80
  242. package/src/utils/inject-date.ts +0 -29
  243. package/src/utils/research-error.ts +0 -205
  244. package/src/utils/searxng-config.ts +0 -94
  245. package/src/utils/session-state.ts +0 -295
  246. package/src/web-research/dependencies.ts +0 -20
  247. package/src/web-research/scrapers.ts +0 -935
  248. package/tsconfig.json +0 -40
package/src/tool.ts CHANGED
@@ -1,430 +1,22 @@
1
1
  /**
2
2
  * pi-research: Multi-Agent Research Orchestration Tool
3
3
  *
4
- * This is the primary entry point for the pi-research extension. It handles:
5
- * 1. Configuration validation and SearXNG infrastructure lifecycle.
6
- * 2. TUI (Terminal UI) initialization and real-time progress tracking.
7
- * 3. Branching between "Quick" (single-agent) and "Deep" (multi-agent/multi-round) research modes.
8
- * 4. Error handling, session cleanup, and result synthesis.
4
+ * This is the primary entry point for the pi-research extension. It provides:
5
+ * - Research tool: Web/internet research with multi-agent orchestration
6
+ * - Health tool: System health checks across all components
7
+ *
8
+ * Tool definitions are modularized into:
9
+ * - tools/research-tool-definition.ts - Main research tool logic
10
+ * - tools/health-tool-definition.ts - Health check tool logic
11
+ * - tui/research-tui-manager.ts - TUI coordination
12
+ * - observers/research-observer-impl.ts - Research lifecycle observer
13
+ * - cleanup/research-cleanup.ts - Session cleanup logic
14
+ * - tui/research-health.ts - Health check helpers
15
+ * - orchestration/research-session-service.ts - session tracking and metadata helpers
9
16
  */
10
17
 
11
- import { readFileSync } from 'node:fs';
12
- import { fileURLToPath } from 'node:url';
13
- import { dirname, join } from 'node:path';
14
- import type {
15
- ToolDefinition,
16
- AgentToolResult,
17
- ExtensionContext,
18
- } from '@mariozechner/pi-coding-agent';
19
- import type {
20
- ExtendedExtensionContext,
21
- SessionManager,
22
- ModelWithId,
23
- ExtendedAgentSessionEvent,
24
- } from './types/extension-context.ts';
25
- import type { SettingsManager } from '@mariozechner/pi-coding-agent';
26
- import type { Model } from '@mariozechner/pi-ai';
27
- import { Type } from '@sinclair/typebox';
28
- import { createResearcherSession } from "./orchestration/researcher.ts";
29
- import { validateConfig } from './config.ts';
30
- import {
31
- createMasterResearchPanel,
32
- addSlice,
33
- activateSlice,
34
- completeSlice,
35
- removeSlice,
36
- updateSliceTokens,
37
- createInitialPanelState,
38
- clearAllFlashTimeouts,
39
- flashSlice,
40
- } from './tui/research-panel.ts';
41
- import { ensureAssistantResponse } from './utils/text-utils.ts';
42
- import { isTextContentBlock, parseTokenUsage, calculateTotalTokens } from './types/llm.ts';
43
- import {
44
- initLifecycle,
45
- ensureRunning,
46
- getStatus,
47
- isFunctional,
48
- setFunctional,
49
- } from './infrastructure/searxng-lifecycle.ts';
50
- import { getManager } from './infrastructure/searxng-lifecycle.ts';
51
- import { DeepResearchOrchestrator } from './orchestration/deep-research-orchestrator.ts';
52
- import { createResearchRunId, logger, runWithLogContext } from './logger.ts';
53
- import { exportResearchReport, appendExportMessage } from './utils/research-export.ts';
54
- import { validateAndSanitizeQuery } from './utils/input-validation.ts';
55
- import { injectCurrentDate } from './utils/inject-date.ts';
56
- import {
57
- startResearchSession,
58
- endResearchSession,
59
- registerSessionPanel,
60
- registerMasterUpdate,
61
- refreshAllSessions,
62
- onSessionOrderChange,
63
- getPiActivePanels,
64
- } from './utils/session-state.ts';
65
- import { cleanupSharedLinks } from './utils/shared-links.ts';
66
-
67
- const __filename = fileURLToPath(import.meta.url);
68
- const __dirname = dirname(__filename);
69
-
70
18
  /**
71
- * Functional Health Check for Tool Start
72
- *
73
- * Ensures SearXNG is not just running, but actually returning results.
74
- * Shows status in TUI while running.
19
+ * Export individual tool creation functions for direct use.
75
20
  */
76
- async function ensureFunctionalHealth(
77
- panelState: any,
78
- onUpdate: () => void
79
- ): Promise<void> {
80
- if (isFunctional()) return;
81
-
82
- const sliceLabel = 'health check ...';
83
- addSlice(panelState, sliceLabel, sliceLabel, false);
84
- activateSlice(panelState, sliceLabel);
85
- onUpdate();
86
-
87
- try {
88
- const { runHealthCheck } = await import('./healthcheck/index.ts');
89
- const health = await runHealthCheck();
90
-
91
- if (!health.success) {
92
- throw new Error(`Functional health check failed: ${health.error || 'Unknown error'}. Your network or search engines may be blocked.`);
93
- }
94
-
95
- setFunctional(true);
96
- } finally {
97
- removeSlice(panelState, sliceLabel);
98
- onUpdate();
99
- }
100
- }
101
-
102
- function getPiSessionMetadata(ctx: ExtensionContext) {
103
- const extendedCtx = ctx as ExtendedExtensionContext;
104
- const sessionManager: SessionManager | undefined = extendedCtx.sessionManager;
105
- const piSessionId = typeof sessionManager?.getSessionId === 'function' ? String(sessionManager.getSessionId()) : 'default';
106
- return {
107
- piSessionId,
108
- sessionFile: typeof sessionManager?.getSessionFile === 'function' ? String(sessionManager.getSessionFile()) : undefined,
109
- cwd: ctx.cwd,
110
- };
111
- }
112
-
113
- export function createResearchTool(): ToolDefinition {
114
- return {
115
- name: 'research',
116
- label: 'Research',
117
- description:
118
- 'Perform deep web/internet research using a coordinated team of agents. Synthesizes findings from web search, scraping, security databases, and Stack Exchange.',
119
- promptSnippet: 'Conduct multi-agent web/internet research',
120
- promptGuidelines: [
121
- 'Specifically for web research, not local project exploration.',
122
- 'Research is organized into Rounds. Each round contains multiple parallel siblings.',
123
- 'SCRAPE PROTOCOL: Call the `scrape` tool up to FOUR times per agent: (1) handshake returns globally scraped links, (2) Batch 1 broad scrape ≤3 URLs, (3) Batch 2 targeted ≤2 URLs, (4) Batch 3 deep-dive ≤3 URLs. Batches skip automatically when context > 55%.',
124
- 'The last sibling in each round evaluates progress and decides whether to continue or synthesize.',
125
- 'Use `security_search` for vulnerabilities, CVE IDs, package security, or actively exploited vulnerabilities.',
126
- 'Use `stackexchange` for technical questions, code solutions, debugging help, and best practices.',
127
- ],
128
- parameters: Type.Object({
129
- query: Type.String({
130
- description: 'Research query or topic to investigate',
131
- }),
132
- depth: Type.Optional(Type.Integer({
133
- minimum: 0,
134
- maximum: 3,
135
- description: [
136
- 'Research complexity level (0-3).',
137
- '0: Quick/Brief — 1 researcher, 1 round. Direct session; no coordinator/orchestrator.',
138
- '1: Normal — 2 researchers, 2 rounds. AI-orchestrated.',
139
- '2: Deep — 3 researchers, 3 rounds. AI-orchestrated.',
140
- '3: Ultra — 5 researchers, 5 rounds (Exhaustive). AI-orchestrated.',
141
- 'Omit to use default complexity 0 (Quick mode).',
142
- ].join(' '),
143
- })),
144
- model: Type.Optional(Type.String({
145
- description: 'Model ID to use for all research agents (defaults to current active model)',
146
- })),
147
- }),
148
- async execute(
149
- _toolCallId: string,
150
- params: unknown,
151
- signal: AbortSignal | undefined,
152
- _onUpdate: unknown,
153
- ctx: ExtensionContext,
154
- ): Promise<AgentToolResult<unknown>> {
155
- const { query, model: modelId, depth } = params as {
156
- query: string;
157
- depth?: number;
158
- model?: string;
159
- };
160
-
161
- if (!query || !ctx.model) {
162
- return { content: [{ type: 'text', text: 'Error: query and model are required' }], details: {} };
163
- }
164
-
165
- // Validate and sanitize query
166
- const sanitizedQuery = validateAndSanitizeQuery(query);
167
-
168
- const baseModel = ctx.model;
169
- const researchRunId = createResearchRunId();
170
- const metadata = getPiSessionMetadata(ctx);
171
- const piSessionId = metadata.piSessionId;
172
-
173
- return runWithLogContext({
174
- ...metadata,
175
- researchRunId,
176
- toolName: 'research',
177
- }, async () => {
178
- let aborted = false;
179
- let cleanup: (() => void) | null = null;
180
- let unsubOrder: (() => void) | null = null;
181
-
182
- try {
183
- validateConfig();
184
- await initLifecycle(ctx);
185
- const searxngUrl = await ensureRunning();
186
- const manager = getManager();
187
- if (manager) {
188
- const { setSearxngManager } = await import('./web-research/utils.ts');
189
- setSearxngManager(manager);
190
- }
191
-
192
- let selectedModel = baseModel;
193
- if (modelId) {
194
- selectedModel = ctx.modelRegistry.getAll().find(m => m.id === modelId) || baseModel;
195
- }
196
-
197
- const typedModel = selectedModel as ModelWithId;
198
- const modelIdStr = typedModel?.id || 'unknown';
199
-
200
- const researchId = startResearchSession(piSessionId);
201
- const masterWidgetId = `pi-research-master-${piSessionId}`;
202
-
203
- cleanup = () => {
204
- if (unsubOrder) unsubOrder();
205
- endResearchSession(piSessionId, researchId);
206
- cleanupSharedLinks(researchId);
207
- // Clear all flash timeouts for this specific run
208
- clearAllFlashTimeouts(researchId);
209
-
210
- // If no more research runs are active in this Pi session, remove the master widget
211
- const activePanels = getPiActivePanels(piSessionId);
212
- if (activePanels.length === 0) {
213
- ctx.ui.setWidget(masterWidgetId, undefined);
214
- } else {
215
- // Otherwise, just refresh to remove this run's block
216
- refreshAllSessions(piSessionId);
217
- }
218
-
219
- logger.info('[research] cleanup completed', { piSessionId, researchId });
220
- };
221
-
222
- const panelState = createInitialPanelState(researchId, sanitizedQuery, getStatus(), modelIdStr);
223
-
224
- // Register this research run's state and the master update function
225
- registerSessionPanel(piSessionId, researchId, panelState);
226
- const updateMasterWidget = () => {
227
- const masterPanelCreator = createMasterResearchPanel(piSessionId);
228
- ctx.ui.setWidget(masterWidgetId, masterPanelCreator, { placement: 'aboveEditor' });
229
- };
230
- registerMasterUpdate(piSessionId, updateMasterWidget);
231
-
232
- // Subscribe to order changes to ensure refresh when ANY run starts/ends
233
- unsubOrder = onSessionOrderChange(piSessionId, () => refreshAllSessions(piSessionId));
234
-
235
- // Robust functional health check before research starts
236
- await ensureFunctionalHealth(panelState, () => refreshAllSessions(piSessionId));
237
-
238
- const onTokens = (n: number) => {
239
- panelState.totalTokens += n;
240
- refreshAllSessions(piSessionId);
241
- };
242
-
243
- // Default complexity to 0 (quick mode) if not provided
244
- const researchComplexity = depth ?? 0;
245
- const isQuick = researchComplexity === 0;
246
-
247
- // For deep mode, complexity (1-3) is passed directly to orchestrator
248
- // complexity 1 → Normal (2 researchers, 2 rounds)
249
- // complexity 2 → Deep (3 researchers, 3 rounds)
250
- // complexity 3 → Ultra (5 researchers, 5 rounds)
251
- const orchestratorComplexity = researchComplexity as 1 | 2 | 3;
252
-
253
- logger.info('[research] run initialized', {
254
- mode: isQuick ? 'quick' : 'deep',
255
- complexity: researchComplexity,
256
- piSessionId,
257
- researchId,
258
- selectedModelId: modelIdStr,
259
- });
260
-
261
- signal?.addEventListener('abort', () => {
262
- aborted = true;
263
- logger.warn('[research] run aborted', { piSessionId, researchId });
264
- cleanup?.();
265
- }, { once: true });
266
-
267
- if (isQuick) {
268
- const truncatedQuery = sanitizedQuery.length > 20 ? sanitizedQuery.slice(0, 20) + '...' : sanitizedQuery;
269
- const sliceLabel = `researching: ${truncatedQuery}`;
270
- addSlice(panelState, sliceLabel, sliceLabel, false);
271
- activateSlice(panelState, sliceLabel);
272
- // Quick mode: deterministic tool limits
273
- // - 4 gathering calls (search/security/stackexchange/grep combined)
274
- // - 4 scrape calls (handshake + 3 batches)
275
- // Total: 8 tool calls, then STOP
276
- panelState.progress = { expected: 8, made: 0, extended: false };
277
- logger.info('[research] quick mode started', { query: sanitizedQuery });
278
- refreshAllSessions(piSessionId);
279
- const researcherPromptRaw = readFileSync(join(__dirname, 'prompts', 'researcher.md'), 'utf-8');
280
- let researcherPrompt = injectCurrentDate(researcherPromptRaw, 'researcher');
281
-
282
- // QUICK MODE EFFICIENCY: Encourage early exit ONLY for definitive fact-finding.
283
- researcherPrompt += '\n\n## Quick Mode Efficiency\n' +
284
- 'For specific fact-seeking queries: if you find a definitive, up-to-date answer from a highly reliable source ' +
285
- 'that fully resolves the request, stop gathering/scraping immediately and provide a concise, high-signal report.';
286
-
287
- const extendedCtx = ctx as ExtendedExtensionContext;
288
-
289
- // Per-session token accumulator for context-aware scrape gating
290
- let quickSessionTokens = 0;
291
- const quickContextWindowSize = (selectedModel as any)?.contextWindow ?? 200000;
292
-
293
- const session = await createResearcherSession({
294
- cwd: ctx.cwd,
295
- ctxModel: selectedModel,
296
- modelRegistry: ctx.modelRegistry,
297
- settingsManager: extendedCtx.settingsManager || (ctx as unknown as { settingsManager: SettingsManager }).settingsManager!,
298
- systemPrompt: researcherPrompt,
299
- searxngUrl,
300
- extensionCtx: ctx,
301
- getTokensUsed: () => quickSessionTokens,
302
- contextWindowSize: quickContextWindowSize,
303
- });
304
-
305
- const calculateUsageCost = (usage: Partial<import('./types/llm').TokenUsage>): number => {
306
- if (!usage || !selectedModel?.cost) return 0;
307
- const modelCost = selectedModel.cost;
308
- const inputCost = (modelCost.input / 1_000_000) * (usage.input ?? 0);
309
- const outputCost = (modelCost.output / 1_000_000) * (usage.output ?? 0);
310
- const cacheReadCost = (modelCost.cacheRead / 1_000_000) * (usage.cacheRead ?? 0);
311
- const cacheWriteCost = (modelCost.cacheWrite / 1_000_000) * (usage.cacheWrite ?? 0);
312
- return inputCost + outputCost + cacheReadCost + cacheWriteCost;
313
- };
314
-
315
- const subscription = session.subscribe((event: ExtendedAgentSessionEvent) => {
316
- if (event.type === 'message_end' && event.message?.role === 'assistant') {
317
- const usage = parseTokenUsage(event.message?.usage);
318
- const tokens = calculateTotalTokens(usage);
319
- const cost = calculateUsageCost(usage);
320
- if (tokens > 0) {
321
- quickSessionTokens += tokens; // drives context-aware scrape gating
322
- onTokens(tokens);
323
- updateSliceTokens(panelState, sliceLabel, tokens, cost);
324
- refreshAllSessions(piSessionId);
325
- }
326
- } else if (event.type === 'message_update') {
327
- // Estimate context growth from streaming text during model generation.
328
- // Cost is left unchanged (only updated with exact values on message_end).
329
- const updateMsg = event.message;
330
- // Content may be in the message object but not typed in ExtendedAgentSessionEvent
331
- const content = updateMsg?.role === 'assistant' ? (updateMsg as any).content : undefined;
332
- if (Array.isArray(content)) {
333
- const textLen = content
334
- .filter(isTextContentBlock)
335
- .reduce((sum, b) => sum + b.text.length, 0);
336
- if (textLen > 200) {
337
- const estimated = quickSessionTokens + Math.ceil(textLen / 4);
338
- updateSliceTokens(panelState, sliceLabel, estimated, 0);
339
- refreshAllSessions(piSessionId);
340
- }
341
- }
342
- } else if (event.type === 'tool_execution_end') {
343
- const isError = event.isError ?? false;
344
- const isBlocked = event.details?.blocked === true;
345
- const color = isError || isBlocked ? 'red' : 'green';
346
- const duration = isError || isBlocked ? 1000 : 500;
347
- flashSlice(panelState, sliceLabel, color, duration, () => refreshAllSessions(piSessionId));
348
- // Advance progress bar ONLY for non-blocked tool calls
349
- if (panelState.progress && !isBlocked) {
350
- panelState.progress.made += 1;
351
- // Snap to 100% when all tool calls are used
352
- if (panelState.progress.made >= panelState.progress.expected) {
353
- panelState.progress.made = panelState.progress.expected;
354
- }
355
- refreshAllSessions(piSessionId);
356
- }
357
- }
358
- });
359
-
360
- // Race the session prompt against the abort signal so the tool returns
361
- // immediately on Ctrl+C rather than waiting for the session to finish.
362
- // The session itself continues running in the background (pi SDK limitation),
363
- // but the tool call resolves and the user gets a responsive cancel.
364
- if (signal) {
365
- await Promise.race([
366
- session.prompt(sanitizedQuery),
367
- new Promise<never>((_, reject) => {
368
- if (signal.aborted) { reject(new Error('Research aborted')); return; }
369
- signal.addEventListener('abort', () => reject(new Error('Research aborted')), { once: true });
370
- }),
371
- ]);
372
- } else {
373
- await session.prompt(sanitizedQuery);
374
- }
375
- const result = ensureAssistantResponse(session, 'Quick');
376
-
377
- // Snap to 100%
378
- if (panelState.progress) {
379
- panelState.progress.made = panelState.progress.expected;
380
- refreshAllSessions(piSessionId);
381
- }
382
-
383
- // Export research report
384
- const exportPath = await exportResearchReport(sanitizedQuery, result, 'quick', ctx.cwd);
385
- const finalResult = exportPath ? appendExportMessage(result, exportPath) : result;
386
-
387
- // Cleanup subscription
388
- if (typeof subscription === 'function') {
389
- subscription();
390
- }
391
-
392
- completeSlice(panelState, sliceLabel);
393
- cleanup();
394
- return { content: [{ type: 'text', text: finalResult }], details: { totalTokens: panelState.totalTokens } };
395
- } else {
396
- // DEEP MODE: orchestrator complexity has already been determined above.
397
- const orchestrator = new DeepResearchOrchestrator({
398
- ctx,
399
- model: selectedModel as Model<any>,
400
- query: sanitizedQuery,
401
- complexity: orchestratorComplexity,
402
- onTokens,
403
- onUpdate: () => refreshAllSessions(piSessionId),
404
- searxngUrl,
405
- panelState,
406
- });
407
-
408
- const result = await orchestrator.run(signal);
409
-
410
- // Export research report
411
- const exportPath = await exportResearchReport(sanitizedQuery, result, 'deep', ctx.cwd);
412
- const finalResult = exportPath ? appendExportMessage(result, exportPath) : result;
413
-
414
- cleanup();
415
- return { content: [{ type: 'text', text: finalResult }], details: { totalTokens: panelState.totalTokens } };
416
- }
417
- } catch (error) {
418
- // If aborted, don't treat as error - just return gracefully
419
- if (aborted) {
420
- return { content: [{ type: 'text', text: 'Research cancelled.' }], details: {} };
421
- }
422
- cleanup?.();
423
- const errorMsg = error instanceof Error ? error.message : String(error);
424
- logger.error('[research] run failed', error);
425
- return { content: [{ type: 'text', text: `Research failed: ${errorMsg}` }], details: {} };
426
- }
427
- });
428
- },
429
- };
430
- }
21
+ export { createResearchTool } from './tools/research-tool-definition.ts';
22
+ export { createHealthTool } from './tools/health-tool-definition.ts';