@lincoln504/pi-research 0.1.13 → 1.0.1
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/LICENSE +1 -1
- package/README.md +55 -138
- package/dist/cli.mjs +23375 -0
- package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
- package/dist/prompts/research-knowledge-search-extractor.md +41 -0
- package/dist/prompts/research-tool-usage.md +92 -0
- package/dist/prompts/researcher.md +89 -0
- package/dist/prompts/system-coordinator.md +75 -0
- package/dist/prompts/system-lead-evaluator.md +118 -0
- package/dist/thread-worker.mjs +2611 -0
- package/dist/webgpu-probe.mjs +80 -0
- package/docs/AGENT-SKILL.md +130 -0
- package/docs/ARCHITECTURE.md +306 -80
- package/docs/CONFIGURATION.md +249 -0
- package/docs/KNOWLEDGE-STORE.md +211 -0
- package/docs/PI-EXTENSION.md +91 -0
- package/docs/SDK.md +122 -0
- package/docs/THIRD-PARTY-NOTICES.md +66 -0
- package/package.json +102 -36
- package/scripts/build.cjs +140 -0
- package/scripts/cleanup.cjs +149 -0
- package/scripts/prepare.cjs +28 -0
- package/scripts/setup.cjs +134 -0
- package/skills/pi-research/README.md +90 -0
- package/skills/pi-research/SKILL.md +102 -0
- package/skills/pi-research/references/configuration.md +77 -0
- package/skills/pi-research/scripts/run.mjs +237 -0
- package/src/cleanup/research-cleanup.ts +140 -0
- package/src/cli.ts +1195 -0
- package/src/config.ts +1032 -155
- package/src/constants.ts +115 -84
- package/src/core/health-check-service.ts +265 -0
- package/src/core/interfaces/health-check-interfaces.ts +50 -0
- package/src/core/interfaces/knowledge-interfaces.ts +190 -0
- package/src/core/interfaces/observer-interfaces.ts +67 -0
- package/src/core/interfaces/orchestration-interfaces.ts +86 -0
- package/src/core/interfaces/planning-interfaces.ts +100 -0
- package/src/core/interfaces/process-interfaces.ts +37 -0
- package/src/core/interfaces/research-plan-types.ts +52 -0
- package/src/core/interfaces/scheduler-interfaces.ts +91 -0
- package/src/core/interfaces/service-names.ts +39 -0
- package/src/core/interfaces/state-manager-interfaces.ts +29 -0
- package/src/core/interfaces/steering-interfaces.ts +31 -0
- package/src/core/llm/agentic-repair.ts +169 -0
- package/src/core/llm/inject-date.ts +52 -0
- package/src/core/llm/llm-timeout.ts +64 -0
- package/src/core/llm/llm-utils.ts +86 -0
- package/src/core/llm/model-registry-factory.ts +287 -0
- package/src/core/llm/pi-ai-completion.ts +29 -0
- package/src/core/llm/prompts.ts +50 -0
- package/src/core/llm/research-model-resolver.ts +91 -0
- package/src/core/planning-constants.ts +13 -0
- package/src/core/planning-service.ts +649 -0
- package/src/core/planning-utils.ts +299 -0
- package/src/core/scheduler-factory.ts +48 -0
- package/src/core/scheduler-service.ts +123 -0
- package/src/core/service-initialization.ts +194 -0
- package/src/core/service-interfaces.ts +48 -0
- package/src/core/service-registry.ts +659 -0
- package/src/healthcheck/index.ts +271 -265
- package/src/healthcheck/registry.ts +130 -0
- package/src/index.ts +639 -50
- package/src/infrastructure/browser/browser-cleanup.ts +309 -0
- package/src/infrastructure/browser/browser-client.ts +199 -0
- package/src/infrastructure/browser/browser-error-utils.ts +160 -0
- package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
- package/src/infrastructure/browser/browser-server.ts +174 -0
- package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
- package/src/infrastructure/browser/cleanup-utils.ts +155 -0
- package/src/infrastructure/browser/client-agent.ts +31 -0
- package/src/infrastructure/browser/config.ts +288 -0
- package/src/infrastructure/browser/ensure-browser.ts +205 -0
- package/src/infrastructure/browser/index.ts +37 -0
- package/src/infrastructure/browser/priority-task-queue.ts +226 -0
- package/src/infrastructure/browser/scheduler-factory.ts +354 -0
- package/src/infrastructure/browser/task-execution-service.ts +209 -0
- package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
- package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
- package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
- package/src/infrastructure/browser/thread-worker-types.ts +25 -0
- package/src/infrastructure/browser/thread-worker.mjs +2611 -0
- package/src/infrastructure/browser/thread-worker.ts +168 -0
- package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
- package/src/infrastructure/embedding/embedding-client.ts +205 -0
- package/src/infrastructure/embedding/embedding-factory.ts +324 -0
- package/src/infrastructure/embedding/embedding-server.ts +535 -0
- package/src/infrastructure/file-lock-service.ts +592 -0
- package/src/infrastructure/gpu-resource-service.ts +191 -0
- package/src/infrastructure/knowledge-store-service.ts +475 -0
- package/src/infrastructure/metrics-service.ts +129 -0
- package/src/infrastructure/process-lifecycle-service.ts +253 -0
- package/src/infrastructure/scheduler-factory-service.ts +58 -0
- package/src/infrastructure/service-initialization.ts +292 -0
- package/src/infrastructure/state/state-backup-manager.ts +290 -0
- package/src/infrastructure/state/state-browser-api.ts +62 -0
- package/src/infrastructure/state/state-browser-manager.ts +70 -0
- package/src/infrastructure/state/state-manager-service.ts +264 -0
- package/src/infrastructure/state/state-manager.ts +547 -0
- package/src/infrastructure/state/state-metrics.ts +92 -0
- package/src/infrastructure/state/state-migration.ts +103 -0
- package/src/infrastructure/state/state-path-configuration.ts +119 -0
- package/src/infrastructure/state/state-session-api.ts +94 -0
- package/src/infrastructure/state/state-session-manager.ts +127 -0
- package/src/infrastructure/state/state-validator.ts +93 -0
- package/src/infrastructure/types/state-types.ts +85 -0
- package/src/knowledge/chunker.ts +157 -0
- package/src/knowledge/embedder-init.ts +413 -0
- package/src/knowledge/embedder-utils.ts +296 -0
- package/src/knowledge/embedder.ts +592 -0
- package/src/knowledge/index.ts +126 -0
- package/src/knowledge/lancedb-loader.ts +35 -0
- package/src/knowledge/migration.ts +17 -0
- package/src/knowledge/model-config.ts +132 -0
- package/src/knowledge/onnx-env.ts +48 -0
- package/src/knowledge/store-operations.ts +291 -0
- package/src/knowledge/store-schema.ts +79 -0
- package/src/knowledge/store.ts +1251 -0
- package/src/knowledge/transformers-loader.ts +31 -0
- package/src/knowledge/webgpu-probe.mjs +80 -0
- package/src/knowledge/webgpu-viability.ts +255 -0
- package/src/knowledge/writer-queue.ts +252 -0
- package/src/logger.ts +316 -122
- package/src/observers/research-observer-impl.ts +459 -0
- package/src/orchestration/deep-research-orchestrator.ts +524 -773
- package/src/orchestration/deep-research-types.ts +47 -35
- package/src/orchestration/headless-observer.ts +114 -0
- package/src/orchestration/orchestration-types.ts +33 -0
- package/src/orchestration/quick-research-orchestrator.ts +463 -0
- package/src/orchestration/research-orchestration-service.ts +549 -0
- package/src/orchestration/research-session-service.ts +178 -0
- package/src/orchestration/research-synthesis-service.ts +348 -0
- package/src/orchestration/researcher-executor.ts +394 -0
- package/src/orchestration/researcher.ts +115 -42
- package/src/orchestration/service-initialization.ts +109 -0
- package/src/orchestration/session-context.ts +7 -3
- package/src/orchestration/session-state.ts +672 -0
- package/src/prompts/research-knowledge-relevance-triage.md +35 -0
- package/src/prompts/research-knowledge-search-extractor.md +41 -0
- package/src/prompts/research-tool-usage.md +63 -16
- package/src/prompts/researcher.md +59 -136
- package/src/prompts/system-coordinator.md +64 -13
- package/src/prompts/system-lead-evaluator.md +82 -189
- package/src/research-config.ts +872 -0
- package/src/sdk.ts +847 -0
- package/src/security/cisa-kev.ts +72 -9
- package/src/security/cvss.ts +92 -0
- package/src/security/ecosystem.ts +45 -0
- package/src/security/github-advisories.ts +168 -428
- package/src/security/github-advisory-types.ts +282 -0
- package/src/security/index.ts +63 -177
- package/src/security/nvd-client.ts +550 -0
- package/src/security/nvd-types.ts +179 -0
- package/src/security/osv-client.ts +246 -0
- package/src/security/osv-types.ts +285 -0
- package/src/security/types.ts +67 -53
- package/src/skill-install/skill-installer.ts +519 -0
- package/src/stackexchange/index.ts +81 -27
- package/src/stackexchange/output/compact.ts +7 -6
- package/src/stackexchange/output/table.ts +11 -7
- package/src/stackexchange/queries.ts +0 -10
- package/src/stackexchange/rest-client.ts +141 -66
- package/src/stackexchange/types.ts +0 -17
- package/src/tool.ts +15 -423
- package/src/tools/grep.ts +32 -310
- package/src/tools/health-tool-definition.ts +101 -0
- package/src/tools/index.ts +38 -9
- package/src/tools/research-knowledge-search.ts +974 -0
- package/src/tools/research-knowledge-types.ts +77 -0
- package/src/tools/research-tool-definition.ts +494 -0
- package/src/tools/scrape.ts +286 -388
- package/src/tools/search.ts +98 -153
- package/src/tools/security.ts +107 -80
- package/src/tools/stackexchange.ts +58 -35
- package/src/tools/youtube-transcript.ts +163 -0
- package/src/tui/knowledge-search-panel.ts +86 -0
- package/src/tui/pulse.ts +86 -0
- package/src/tui/research-health.ts +124 -0
- package/src/tui/research-panel-color-utils.ts +340 -0
- package/src/tui/research-panel-format.ts +54 -0
- package/src/tui/research-panel-state.ts +404 -0
- package/src/tui/research-panel-wave.ts +280 -0
- package/src/tui/research-panel.ts +339 -435
- package/src/tui/research-tui-manager.ts +233 -0
- package/src/tui/tui-controller.ts +137 -0
- package/src/types/extension-context.ts +18 -49
- package/src/types/index.ts +61 -0
- package/src/types/llm.ts +68 -29
- package/src/types/research-panel-types.ts +61 -0
- package/src/utils/circuit-breaker.ts +146 -0
- package/src/utils/citation-utils.ts +97 -0
- package/src/utils/disk-space-checker.ts +55 -0
- package/src/utils/error-tracker.ts +265 -0
- package/src/utils/host-config.ts +45 -0
- package/src/utils/input-validation.ts +8 -10
- package/src/utils/json-utils.ts +150 -16
- package/src/utils/llm-usage.ts +54 -0
- package/src/utils/log-rotation.ts +125 -0
- package/src/utils/log-utils.ts +159 -0
- package/src/utils/make-resource-loader.ts +3 -1
- package/src/utils/metrics-summary.ts +459 -0
- package/src/utils/metrics.ts +302 -0
- package/src/utils/research-export.ts +28 -12
- package/src/utils/safe-unref.ts +31 -0
- package/src/utils/shared-links.ts +195 -90
- package/src/utils/shutdown-manager.ts +65 -0
- package/src/utils/stdio-capture.ts +407 -0
- package/src/utils/text-utils.ts +258 -11
- package/src/utils/tool-usage-tracker.ts +83 -16
- package/src/utils/url-utils.ts +255 -0
- package/src/utils/user-agent.ts +28 -0
- package/src/web-research/browser-search.ts +179 -0
- package/src/web-research/retry-utils.ts +173 -77
- package/src/web-research/scraper-types.ts +72 -0
- package/src/web-research/scraper-utils.ts +434 -0
- package/src/web-research/search.ts +78 -218
- package/src/web-research/types.ts +9 -24
- package/src/web-research/utils.ts +2 -143
- package/src/web-research/web-scraper.ts +477 -0
- package/src/youtube/index.ts +154 -0
- package/src/youtube/potoken.ts +259 -0
- package/src/youtube/transcript-client.ts +390 -0
- package/src/youtube/video-id.ts +118 -0
- package/config/searxng/default-settings.yml +0 -189
- package/config/searxng/limiter.toml +0 -34
- package/config/tooling/eslint.config.js +0 -73
- package/config/tooling/vitest.config.integration.ts +0 -17
- package/config/tooling/vitest.config.ts +0 -40
- package/config/tooling/vitest.config.unit.ts +0 -12
- package/docs/assets/README-banner.jpg +0 -0
- package/scripts/setup.js +0 -212
- package/src/infrastructure/searxng-lifecycle.ts +0 -644
- package/src/infrastructure/searxng-manager.ts +0 -1183
- package/src/infrastructure/state-manager.ts +0 -918
- package/src/orchestration/deep-research-reducer.ts +0 -129
- package/src/orchestration/id-utils.ts +0 -63
- package/src/security/nvd.ts +0 -532
- package/src/security/osv.ts +0 -487
- package/src/stackexchange/cache.ts +0 -111
- package/src/stackexchange/output/index.ts +0 -7
- package/src/stackexchange/output/json.ts +0 -21
- package/src/tui/searxng-status.ts +0 -80
- package/src/utils/inject-date.ts +0 -29
- package/src/utils/research-error.ts +0 -205
- package/src/utils/searxng-config.ts +0 -94
- package/src/utils/session-state.ts +0 -295
- package/src/web-research/dependencies.ts +0 -20
- package/src/web-research/scrapers.ts +0 -935
- package/tsconfig.json +0 -40
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared metrics summary formatting.
|
|
3
|
+
*
|
|
4
|
+
* Used by BOTH:
|
|
5
|
+
* - The final research result (appendResearchSummary in research-tool-definition.ts)
|
|
6
|
+
* - The session metrics view (showMetricsAction in research-config.ts)
|
|
7
|
+
*
|
|
8
|
+
* This ensures a consistent structure between the two views:
|
|
9
|
+
* - Plain lists, no tables
|
|
10
|
+
* - Counts only, no percentages
|
|
11
|
+
* - Human-friendly labels, never raw Prometheus-style metric names
|
|
12
|
+
* - Subtly impressive: emphasizes volume of work done (sources analyzed,
|
|
13
|
+
* links discovered, pages scraped, etc.)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { formatTimeAgo, formatDuration } from './text-utils.js';
|
|
17
|
+
import type { IMetricsSnapshot } from './metrics.js';
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Types
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
export interface ResearchStats {
|
|
24
|
+
/** Total research duration in milliseconds. */
|
|
25
|
+
durationMs: number;
|
|
26
|
+
/** Complexity level: 0 (quick), 1, 2, or 3. */
|
|
27
|
+
complexity: number;
|
|
28
|
+
/** Number of researchers that were launched. */
|
|
29
|
+
researchersLaunched: number;
|
|
30
|
+
/** Number of rounds completed. */
|
|
31
|
+
roundsCompleted: number;
|
|
32
|
+
/** Number of search queries submitted. */
|
|
33
|
+
searchQueries: number;
|
|
34
|
+
/** Number of unique URLs discovered via search. */
|
|
35
|
+
urlsDiscovered: number;
|
|
36
|
+
/** Number of URLs successfully scraped via lightweight fetch. */
|
|
37
|
+
fetchSuccess: number;
|
|
38
|
+
/** Number of URLs successfully scraped via browser (stealth fallback). */
|
|
39
|
+
browserSuccess: number;
|
|
40
|
+
/** Number of URLs that fell back from fetch to browser. */
|
|
41
|
+
browserFallbacks: number;
|
|
42
|
+
/** Number of URLs successfully scraped/analyzed (fetch + browser). */
|
|
43
|
+
urlsAnalyzed: number;
|
|
44
|
+
/** Number of URLs that failed to scrape (blocked, stub, HTTP error, timeout). */
|
|
45
|
+
urlsFailed: number;
|
|
46
|
+
/** Genuine engine faults (errored/timed-out researcher attempts). Excludes
|
|
47
|
+
* expected remote scrape unavailability, which is reported via urlsFailed. */
|
|
48
|
+
errors: number;
|
|
49
|
+
/** Total LLM tokens consumed. */
|
|
50
|
+
tokens: number;
|
|
51
|
+
/** Total cost in USD across all LLM calls. */
|
|
52
|
+
cost: number;
|
|
53
|
+
/** Tool usage counts. */
|
|
54
|
+
toolUsage: {
|
|
55
|
+
searches: number;
|
|
56
|
+
scrapes: number;
|
|
57
|
+
securitySearches: number;
|
|
58
|
+
stackexchangeQueries: number;
|
|
59
|
+
knowledgeLookups: number;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Internal helpers
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Extract a clean numeric value from a metrics snapshot counter map.
|
|
69
|
+
* Sums all label-variants of the given counter base name.
|
|
70
|
+
*
|
|
71
|
+
* Example: sumCounter(snapshot.counters, 'scrape_results_total')
|
|
72
|
+
* sums scrape_results_total{outcome="fetch_success"} + ...browser_success + ...total_failure
|
|
73
|
+
*/
|
|
74
|
+
export function sumCounter(
|
|
75
|
+
counters: Record<string, number>,
|
|
76
|
+
baseName: string
|
|
77
|
+
): number {
|
|
78
|
+
let total = 0;
|
|
79
|
+
for (const [key, value] of Object.entries(counters)) {
|
|
80
|
+
// Match exact name or name followed by `{labels}`
|
|
81
|
+
if (key === baseName || key.startsWith(baseName + '{')) {
|
|
82
|
+
total += value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return total;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Extract a specific labeled counter value.
|
|
90
|
+
*
|
|
91
|
+
* Example: getLabeledCounter(counters, 'scrape_results_total', { outcome: 'fetch_success' })
|
|
92
|
+
*/
|
|
93
|
+
function getLabeledCounter(
|
|
94
|
+
counters: Record<string, number>,
|
|
95
|
+
baseName: string,
|
|
96
|
+
labels: Record<string, string>
|
|
97
|
+
): number {
|
|
98
|
+
// Build the label suffix to match: {key1="val1",key2="val2"}
|
|
99
|
+
const labelParts = Object.entries(labels).map(([k, v]) => `${k}="${v}"`);
|
|
100
|
+
const suffix = `{${labelParts.join(',')}}`;
|
|
101
|
+
|
|
102
|
+
// Also try without the base name prefix repetition
|
|
103
|
+
for (const [key, value] of Object.entries(counters)) {
|
|
104
|
+
if (key === `${baseName}${suffix}`) {
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// formatDuration and formatTimeAgo are imported from text-utils.ts
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Format a token count with thousands separators (e.g., "12,345").
|
|
115
|
+
*/
|
|
116
|
+
export function formatTokens(tokens: number): string {
|
|
117
|
+
if (tokens < 1000) return String(tokens);
|
|
118
|
+
return tokens.toLocaleString('en-US');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Run stats extraction from snapshot
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Extract human-relevant research stats from a run metrics snapshot.
|
|
127
|
+
* Returns null if the snapshot has no meaningful data.
|
|
128
|
+
*/
|
|
129
|
+
export function extractRunStats(snapshot: IMetricsSnapshot): ResearchStats | null {
|
|
130
|
+
const counters = snapshot.counters || {};
|
|
131
|
+
const histograms = snapshot.histograms || {};
|
|
132
|
+
|
|
133
|
+
// Researchers launched
|
|
134
|
+
const researchersLaunched = sumCounter(counters, 'researchers_launched_total');
|
|
135
|
+
|
|
136
|
+
// Search queries — combine browser search and regular search
|
|
137
|
+
const browserSearchQueries = sumCounter(counters, 'browser_search_queries_total');
|
|
138
|
+
const searchQueries = sumCounter(counters, 'search_queries_total') + browserSearchQueries;
|
|
139
|
+
|
|
140
|
+
// URLs discovered via search
|
|
141
|
+
const urlsDiscovered = sumCounter(counters, 'browser_search_results_total') +
|
|
142
|
+
sumCounter(counters, 'browser_search_unique_urls_total');
|
|
143
|
+
|
|
144
|
+
// URLs scraped — outcomes from scrape_results_total
|
|
145
|
+
const fetchSuccess = getLabeledCounter(counters, 'scrape_results_total', { outcome: 'fetch_success' });
|
|
146
|
+
const browserSuccess = getLabeledCounter(counters, 'scrape_results_total', { outcome: 'browser_success' });
|
|
147
|
+
const totalFailure = getLabeledCounter(counters, 'scrape_results_total', { outcome: 'total_failure' });
|
|
148
|
+
const browserFallbacks = sumCounter(counters, 'scrape_layer_fallbacks_total');
|
|
149
|
+
const urlsAnalyzed = fetchSuccess + browserSuccess;
|
|
150
|
+
const urlsFailed = totalFailure;
|
|
151
|
+
|
|
152
|
+
// Tool usage
|
|
153
|
+
const toolUsage = {
|
|
154
|
+
searches: sumCounter(counters, 'tool_search_calls_total'),
|
|
155
|
+
scrapes: sumCounter(counters, 'tool_scrape_calls_total'),
|
|
156
|
+
securitySearches: sumCounter(counters, 'tool_security_search_calls_total'),
|
|
157
|
+
stackexchangeQueries: sumCounter(counters, 'stackexchange_requests_total'),
|
|
158
|
+
knowledgeLookups: sumCounter(counters, 'research_knowledge_search_total'),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Errors — genuine engine faults only (a researcher attempt that threw or timed
|
|
162
|
+
// out; llm_api_errors_total is summed for forward-compat but no path emits it yet).
|
|
163
|
+
// Deliberately EXCLUDES scrape_errors_total: a URL blocked by bot-protection,
|
|
164
|
+
// returning a stub, or answering 4xx/5xx is an EXPECTED outcome of scraping the
|
|
165
|
+
// open web — it is already surfaced to the user as "not scraped" (urlsFailed,
|
|
166
|
+
// from the once-per-URL total_failure outcome). Counting scrape_errors_total here
|
|
167
|
+
// both mislabels remote unavailability as an engine error AND over-counts, since
|
|
168
|
+
// it is incremented several times along one URL's fetch→browser fallback chain.
|
|
169
|
+
// A researcher whose sources were all blocked does NOT increment
|
|
170
|
+
// researcher_errors_total (it takes the ungrounded path), so blocked runs
|
|
171
|
+
// correctly show 0 errors here.
|
|
172
|
+
const errors = sumCounter(counters, 'researcher_errors_total') +
|
|
173
|
+
sumCounter(counters, 'llm_api_errors_total');
|
|
174
|
+
|
|
175
|
+
// Tokens — sum all label variants
|
|
176
|
+
const tokens = sumCounter(counters, 'llm_tokens_total');
|
|
177
|
+
|
|
178
|
+
// Cost — sum all label variants
|
|
179
|
+
const cost = sumCounter(counters, 'llm_cost_total');
|
|
180
|
+
|
|
181
|
+
// Duration from histogram
|
|
182
|
+
const durationMs = histograms['research_session_duration_ms{mode="deep",complexity="1",status="success"}']?.max ||
|
|
183
|
+
histograms['research_session_duration_ms{mode="deep",complexity="2",status="success"}']?.max ||
|
|
184
|
+
histograms['research_session_duration_ms{mode="deep",complexity="3",status="success"}']?.max ||
|
|
185
|
+
histograms['research_session_duration_ms{mode="quick",complexity="0",status="success"}']?.max ||
|
|
186
|
+
0;
|
|
187
|
+
|
|
188
|
+
// Rounds completed — evaluator runs give us the round count
|
|
189
|
+
const roundsCompleted = sumCounter(counters, 'evaluator_runs_total');
|
|
190
|
+
|
|
191
|
+
// Complexity
|
|
192
|
+
let complexity = 1;
|
|
193
|
+
for (const key of Object.keys(counters)) {
|
|
194
|
+
if (key.startsWith('research_sessions_total{')) {
|
|
195
|
+
const match = key.match(/complexity="(\d+)"/);
|
|
196
|
+
if (match?.[1]) complexity = parseInt(match[1], 10);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// If nothing meaningful happened, return null
|
|
202
|
+
if (researchersLaunched === 0 && searchQueries === 0 && urlsAnalyzed === 0 && tokens === 0) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
durationMs,
|
|
208
|
+
complexity,
|
|
209
|
+
researchersLaunched,
|
|
210
|
+
roundsCompleted,
|
|
211
|
+
searchQueries,
|
|
212
|
+
urlsDiscovered,
|
|
213
|
+
fetchSuccess,
|
|
214
|
+
browserSuccess,
|
|
215
|
+
browserFallbacks,
|
|
216
|
+
urlsAnalyzed,
|
|
217
|
+
urlsFailed,
|
|
218
|
+
errors,
|
|
219
|
+
tokens,
|
|
220
|
+
cost,
|
|
221
|
+
toolUsage,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Final research result summary
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Build a concise human-readable summary for the END of a research result.
|
|
231
|
+
*
|
|
232
|
+
* Design principles:
|
|
233
|
+
* - One line for work done, one line for cost/resources
|
|
234
|
+
* - Errors collapsed to a single sentence (details belong in the log)
|
|
235
|
+
* - No tables, percentages, scrape-internal breakdowns, or diagnostic dumps
|
|
236
|
+
* - Consistent single bolding on key numbers
|
|
237
|
+
*
|
|
238
|
+
* Returns empty string if no meaningful data.
|
|
239
|
+
*/
|
|
240
|
+
export function buildResearchSummary(stats: ResearchStats): string {
|
|
241
|
+
const lines: string[] = [];
|
|
242
|
+
|
|
243
|
+
lines.push('### Research Summary');
|
|
244
|
+
|
|
245
|
+
// --- Work line: researchers, rounds, searches, sources ---
|
|
246
|
+
const workParts: string[] = [];
|
|
247
|
+
|
|
248
|
+
if (stats.researchersLaunched > 0) {
|
|
249
|
+
workParts.push(`**${stats.researchersLaunched}** researcher${stats.researchersLaunched > 1 ? 's' : ''}`);
|
|
250
|
+
}
|
|
251
|
+
if (stats.roundsCompleted > 1) {
|
|
252
|
+
workParts.push(`**${stats.roundsCompleted}** rounds`);
|
|
253
|
+
}
|
|
254
|
+
if (stats.searchQueries > 0) {
|
|
255
|
+
workParts.push(`**${stats.searchQueries}** searches`);
|
|
256
|
+
}
|
|
257
|
+
if (stats.urlsAnalyzed > 0) {
|
|
258
|
+
let sourcesLabel: string;
|
|
259
|
+
if (stats.urlsDiscovered > stats.urlsAnalyzed) {
|
|
260
|
+
sourcesLabel = `**${stats.urlsAnalyzed}** of **${stats.urlsDiscovered}** sources analyzed`;
|
|
261
|
+
} else {
|
|
262
|
+
sourcesLabel = `**${stats.urlsAnalyzed}** sources analyzed`;
|
|
263
|
+
}
|
|
264
|
+
// Show per-tier breakdown when browser fallback was used or some URLs failed
|
|
265
|
+
if (stats.browserSuccess > 0 || stats.urlsFailed > 0) {
|
|
266
|
+
const tierParts: string[] = [];
|
|
267
|
+
if (stats.fetchSuccess > 0) tierParts.push(`${stats.fetchSuccess} fetch`);
|
|
268
|
+
if (stats.browserSuccess > 0) tierParts.push(`${stats.browserSuccess} browser`);
|
|
269
|
+
if (stats.urlsFailed > 0) tierParts.push(`${stats.urlsFailed} not scraped`);
|
|
270
|
+
sourcesLabel += ` (${tierParts.join(', ')})`;
|
|
271
|
+
}
|
|
272
|
+
workParts.push(sourcesLabel);
|
|
273
|
+
} else if (stats.urlsDiscovered > 0) {
|
|
274
|
+
workParts.push(`**${stats.urlsDiscovered}** sources discovered`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (workParts.length > 0) {
|
|
278
|
+
lines.push(workParts.join(' · '));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// --- Resource line: tokens, cost, duration ---
|
|
282
|
+
const resourceParts: string[] = [];
|
|
283
|
+
if (stats.tokens > 0) {
|
|
284
|
+
resourceParts.push(`**${formatTokens(stats.tokens)}** tokens`);
|
|
285
|
+
}
|
|
286
|
+
if (stats.cost > 0) {
|
|
287
|
+
const costStr = stats.cost < 0.01 ? '<$0.01' : `$${stats.cost.toFixed(2)}`;
|
|
288
|
+
resourceParts.push(`**${costStr}**`);
|
|
289
|
+
}
|
|
290
|
+
if (stats.durationMs > 0) {
|
|
291
|
+
resourceParts.push(`**${formatDuration(stats.durationMs)}**`);
|
|
292
|
+
}
|
|
293
|
+
if (resourceParts.length > 0) {
|
|
294
|
+
lines.push(resourceParts.join(' · '));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// --- Footnote: genuine engine errors only. Blocked/unavailable sources are NOT
|
|
298
|
+
// errors — they appear as "N not scraped" in the tier breakdown above. ---
|
|
299
|
+
if (stats.errors > 0) {
|
|
300
|
+
lines.push(`*${stats.errors} error${stats.errors > 1 ? 's' : ''} encountered.*`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return lines.join('\n');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
// Session metrics view (config TUI)
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
|
|
310
|
+
export interface SessionStats {
|
|
311
|
+
sessionStartedAt: number;
|
|
312
|
+
totalRuns: number;
|
|
313
|
+
successfulRuns: number;
|
|
314
|
+
failedRuns: number;
|
|
315
|
+
totalDurationMs: number;
|
|
316
|
+
totalSourcesAnalyzed: number;
|
|
317
|
+
totalUrlsDiscovered: number;
|
|
318
|
+
totalSearchQueries: number;
|
|
319
|
+
totalTokens: number;
|
|
320
|
+
totalToolUsage: {
|
|
321
|
+
searches: number;
|
|
322
|
+
scrapes: number;
|
|
323
|
+
securitySearches: number;
|
|
324
|
+
stackexchangeQueries: number;
|
|
325
|
+
knowledgeLookups: number;
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Aggregate stats across all runs in the session history.
|
|
331
|
+
*/
|
|
332
|
+
export function aggregateSessionStats(
|
|
333
|
+
runHistory: ReadonlyArray<{ durationMs: number; status: string; snapshot: IMetricsSnapshot }>
|
|
334
|
+
): SessionStats {
|
|
335
|
+
let successfulRuns = 0;
|
|
336
|
+
let failedRuns = 0;
|
|
337
|
+
let totalDurationMs = 0;
|
|
338
|
+
let totalSourcesAnalyzed = 0;
|
|
339
|
+
let totalUrlsDiscovered = 0;
|
|
340
|
+
let totalSearchQueries = 0;
|
|
341
|
+
let totalTokens = 0;
|
|
342
|
+
const totalToolUsage = {
|
|
343
|
+
searches: 0,
|
|
344
|
+
scrapes: 0,
|
|
345
|
+
securitySearches: 0,
|
|
346
|
+
stackexchangeQueries: 0,
|
|
347
|
+
knowledgeLookups: 0,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
for (const run of runHistory) {
|
|
351
|
+
if (run.status === 'success') successfulRuns++;
|
|
352
|
+
else if (run.status === 'error') failedRuns++;
|
|
353
|
+
|
|
354
|
+
totalDurationMs += run.durationMs || 0;
|
|
355
|
+
|
|
356
|
+
const stats = extractRunStats(run.snapshot);
|
|
357
|
+
if (stats) {
|
|
358
|
+
totalSourcesAnalyzed += stats.urlsAnalyzed;
|
|
359
|
+
totalUrlsDiscovered += stats.urlsDiscovered;
|
|
360
|
+
totalSearchQueries += stats.searchQueries;
|
|
361
|
+
totalTokens += stats.tokens;
|
|
362
|
+
totalToolUsage.searches += stats.toolUsage.searches;
|
|
363
|
+
totalToolUsage.scrapes += stats.toolUsage.scrapes;
|
|
364
|
+
totalToolUsage.securitySearches += stats.toolUsage.securitySearches;
|
|
365
|
+
totalToolUsage.stackexchangeQueries += stats.toolUsage.stackexchangeQueries;
|
|
366
|
+
totalToolUsage.knowledgeLookups += stats.toolUsage.knowledgeLookups;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
sessionStartedAt: 0, // Set by caller
|
|
372
|
+
totalRuns: runHistory.length,
|
|
373
|
+
successfulRuns,
|
|
374
|
+
failedRuns,
|
|
375
|
+
totalDurationMs,
|
|
376
|
+
totalSourcesAnalyzed,
|
|
377
|
+
totalUrlsDiscovered,
|
|
378
|
+
totalSearchQueries,
|
|
379
|
+
totalTokens,
|
|
380
|
+
totalToolUsage,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Build the session-level overview section.
|
|
386
|
+
*/
|
|
387
|
+
export function buildSessionOverview(stats: SessionStats): string {
|
|
388
|
+
const lines: string[] = [];
|
|
389
|
+
|
|
390
|
+
lines.push('### Session Overview');
|
|
391
|
+
|
|
392
|
+
const overviewParts: string[] = [];
|
|
393
|
+
overviewParts.push(`**${stats.totalRuns}** research run${stats.totalRuns !== 1 ? 's' : ''}`);
|
|
394
|
+
|
|
395
|
+
if (stats.totalSourcesAnalyzed > 0) {
|
|
396
|
+
overviewParts.push(`**${stats.totalSourcesAnalyzed}** sources analyzed`);
|
|
397
|
+
}
|
|
398
|
+
if (stats.totalUrlsDiscovered > 0) {
|
|
399
|
+
overviewParts.push(`**${stats.totalUrlsDiscovered}** URLs discovered`);
|
|
400
|
+
}
|
|
401
|
+
if (stats.totalSearchQueries > 0) {
|
|
402
|
+
overviewParts.push(`**${stats.totalSearchQueries}** search queries`);
|
|
403
|
+
}
|
|
404
|
+
if (stats.totalTokens > 0) {
|
|
405
|
+
overviewParts.push(`**${formatTokens(stats.totalTokens)}** tokens`);
|
|
406
|
+
}
|
|
407
|
+
if (stats.totalDurationMs > 0) {
|
|
408
|
+
overviewParts.push(`**${formatDuration(stats.totalDurationMs)}** total research time`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
lines.push(overviewParts.join(' · '));
|
|
412
|
+
|
|
413
|
+
// Tool usage line
|
|
414
|
+
const toolParts: string[] = [];
|
|
415
|
+
if (stats.totalToolUsage.searches > 0) toolParts.push(`${stats.totalToolUsage.searches} searches`);
|
|
416
|
+
if (stats.totalToolUsage.scrapes > 0) toolParts.push(`${stats.totalToolUsage.scrapes} scrapes`);
|
|
417
|
+
if (stats.totalToolUsage.securitySearches > 0) toolParts.push(`${stats.totalToolUsage.securitySearches} security lookups`);
|
|
418
|
+
if (stats.totalToolUsage.stackexchangeQueries > 0) toolParts.push(`${stats.totalToolUsage.stackexchangeQueries} StackExchange queries`);
|
|
419
|
+
if (stats.totalToolUsage.knowledgeLookups > 0) toolParts.push(`${stats.totalToolUsage.knowledgeLookups} knowledge store queries`);
|
|
420
|
+
if (toolParts.length > 0) {
|
|
421
|
+
lines.push(`Tools used: ${toolParts.join(' · ')}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return lines.join('\n');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Build a compact one-line summary for a prior run.
|
|
429
|
+
*/
|
|
430
|
+
export function buildRunCompactLine(run: {
|
|
431
|
+
runId: string;
|
|
432
|
+
durationMs: number;
|
|
433
|
+
status: string;
|
|
434
|
+
completedAt: number;
|
|
435
|
+
snapshot: IMetricsSnapshot;
|
|
436
|
+
}): string {
|
|
437
|
+
const icon = run.status === 'success' ? '[ok]'
|
|
438
|
+
: run.status === 'cancelled' ? '[cancelled]'
|
|
439
|
+
: '[failed]';
|
|
440
|
+
|
|
441
|
+
const stats = extractRunStats(run.snapshot);
|
|
442
|
+
const extraParts: string[] = [];
|
|
443
|
+
|
|
444
|
+
if (stats) {
|
|
445
|
+
if (stats.urlsAnalyzed > 0) {
|
|
446
|
+
extraParts.push(`${stats.urlsAnalyzed} source${stats.urlsAnalyzed !== 1 ? 's' : ''}`);
|
|
447
|
+
}
|
|
448
|
+
if (stats.researchersLaunched > 0) {
|
|
449
|
+
extraParts.push(`${stats.researchersLaunched} researcher${stats.researchersLaunched !== 1 ? 's' : ''}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const extra = extraParts.length > 0 ? ` · ${extraParts.join(', ')}` : '';
|
|
454
|
+
const ago = formatTimeAgo(new Date(run.completedAt).toISOString());
|
|
455
|
+
|
|
456
|
+
return `- ${icon} \`${run.runId.slice(0, 8)}\` ${formatDuration(run.durationMs)}${extra} — ${ago}`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export { formatDuration, formatTimeAgo };
|