@lincoln504/pi-research 0.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.
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/config/searxng/default-settings.yml +189 -0
- package/config/searxng/limiter.toml +34 -0
- package/config/tooling/eslint.config.js +73 -0
- package/config/tooling/vitest.config.integration.ts +17 -0
- package/config/tooling/vitest.config.ts +40 -0
- package/config/tooling/vitest.config.unit.ts +12 -0
- package/docs/ARCHITECTURE.md +109 -0
- package/docs/assets/README-banner.jpg +0 -0
- package/package.json +92 -0
- package/scripts/setup.js +174 -0
- package/src/config.ts +206 -0
- package/src/constants.ts +148 -0
- package/src/healthcheck/index.ts +309 -0
- package/src/index.ts +71 -0
- package/src/infrastructure/searxng-lifecycle.ts +577 -0
- package/src/infrastructure/searxng-manager.ts +1173 -0
- package/src/infrastructure/state-manager.ts +918 -0
- package/src/logger.ts +246 -0
- package/src/orchestration/deep-research-orchestrator.ts +848 -0
- package/src/orchestration/deep-research-reducer.ts +129 -0
- package/src/orchestration/deep-research-types.ts +54 -0
- package/src/orchestration/id-utils.ts +63 -0
- package/src/orchestration/researcher.ts +104 -0
- package/src/orchestration/session-context.ts +43 -0
- package/src/prompts/research-tool-usage.md +45 -0
- package/src/prompts/researcher.md +166 -0
- package/src/prompts/system-coordinator.md +24 -0
- package/src/prompts/system-lead-evaluator.md +225 -0
- package/src/security/cisa-kev.ts +242 -0
- package/src/security/github-advisories.ts +607 -0
- package/src/security/index.ts +406 -0
- package/src/security/nvd.ts +532 -0
- package/src/security/osv.ts +487 -0
- package/src/security/types.ts +138 -0
- package/src/stackexchange/cache.ts +111 -0
- package/src/stackexchange/index.ts +352 -0
- package/src/stackexchange/output/compact.ts +64 -0
- package/src/stackexchange/output/index.ts +7 -0
- package/src/stackexchange/output/json.ts +21 -0
- package/src/stackexchange/output/table.ts +123 -0
- package/src/stackexchange/queries.ts +168 -0
- package/src/stackexchange/rest-client.ts +127 -0
- package/src/stackexchange/types.ts +129 -0
- package/src/tool.ts +430 -0
- package/src/tools/grep.ts +335 -0
- package/src/tools/index.ts +77 -0
- package/src/tools/scrape.ts +451 -0
- package/src/tools/search.ts +188 -0
- package/src/tools/security.ts +292 -0
- package/src/tools/stackexchange.ts +101 -0
- package/src/tui/research-panel.ts +576 -0
- package/src/tui/searxng-status.ts +80 -0
- package/src/types/extension-context.ts +60 -0
- package/src/types/llm.ts +111 -0
- package/src/utils/inject-date.ts +29 -0
- package/src/utils/input-validation.ts +103 -0
- package/src/utils/json-utils.ts +262 -0
- package/src/utils/make-resource-loader.ts +43 -0
- package/src/utils/research-error.ts +205 -0
- package/src/utils/research-export.ts +199 -0
- package/src/utils/searxng-config.ts +94 -0
- package/src/utils/session-state.ts +295 -0
- package/src/utils/shared-links.ts +123 -0
- package/src/utils/shutdown-manager.ts +54 -0
- package/src/utils/text-utils.ts +83 -0
- package/src/utils/tool-usage-tracker.ts +135 -0
- package/src/web-research/dependencies.ts +20 -0
- package/src/web-research/retry-utils.ts +232 -0
- package/src/web-research/scrapers.ts +935 -0
- package/src/web-research/search.ts +235 -0
- package/src/web-research/types.ts +47 -0
- package/src/web-research/utils.ts +158 -0
- package/tsconfig.json +40 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Research Reducer
|
|
3
|
+
*
|
|
4
|
+
* PURE FUNCTION logic for state transitions.
|
|
5
|
+
* Contains 0 side effects. 100% testable without mocks.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SystemResearchState, DeepResearchEvent } from './deep-research-types.ts';
|
|
9
|
+
|
|
10
|
+
export function deepResearchReducer(state: SystemResearchState, event: DeepResearchEvent): SystemResearchState {
|
|
11
|
+
// Deep clone state to ensure true purity
|
|
12
|
+
const newState: SystemResearchState = {
|
|
13
|
+
...state,
|
|
14
|
+
lastUpdated: Date.now(),
|
|
15
|
+
initialAgenda: [...state.initialAgenda],
|
|
16
|
+
allScrapedLinks: [...state.allScrapedLinks],
|
|
17
|
+
aspects: { ...state.aspects }
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
switch (event.type) {
|
|
21
|
+
case 'PLANNING_COMPLETE': {
|
|
22
|
+
newState.initialAgenda = event.agenda;
|
|
23
|
+
newState.status = 'researching';
|
|
24
|
+
// Assign first batch to aspects
|
|
25
|
+
event.agenda.slice(0, event.initialCount).forEach((q: string, i: number) => {
|
|
26
|
+
const id = `1.${i + 1}`;
|
|
27
|
+
newState.aspects[id] = { id, query: q, status: 'pending' };
|
|
28
|
+
});
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
case 'SIBLING_STARTED': {
|
|
33
|
+
const aspect = newState.aspects[event.id];
|
|
34
|
+
if (aspect) {
|
|
35
|
+
newState.aspects[event.id] = {
|
|
36
|
+
...aspect,
|
|
37
|
+
status: 'running'
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
case 'SIBLING_COMPLETED': {
|
|
44
|
+
const aspect = newState.aspects[event.id];
|
|
45
|
+
if (aspect) {
|
|
46
|
+
newState.aspects[event.id] = {
|
|
47
|
+
...aspect,
|
|
48
|
+
status: 'completed',
|
|
49
|
+
report: event.report
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
case 'SIBLING_FAILED': {
|
|
56
|
+
const aspect = newState.aspects[event.id];
|
|
57
|
+
if (aspect) {
|
|
58
|
+
newState.aspects[event.id] = {
|
|
59
|
+
...aspect,
|
|
60
|
+
status: 'failed',
|
|
61
|
+
error: event.error
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case 'SIBLING_TOKENS': {
|
|
68
|
+
const aspect = newState.aspects[event.id];
|
|
69
|
+
if (aspect) {
|
|
70
|
+
// Accumulate tokens and cost for this sibling
|
|
71
|
+
const currentTokens = aspect.tokens || 0;
|
|
72
|
+
const currentCost = aspect.cost || 0;
|
|
73
|
+
newState.aspects[event.id] = {
|
|
74
|
+
...aspect,
|
|
75
|
+
tokens: currentTokens + event.tokens,
|
|
76
|
+
cost: currentCost + event.cost
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'LINKS_SCRAPED': {
|
|
83
|
+
newState.allScrapedLinks = [...new Set([...newState.allScrapedLinks, ...event.links])];
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case 'PROMOTION_STARTED': {
|
|
88
|
+
newState.promotedId = event.id;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case 'PROMOTION_DECISION': {
|
|
93
|
+
if (event.finalSynthesis !== undefined) {
|
|
94
|
+
// Case 1: Lead Evaluator chose to synthesize - complete with synthesis
|
|
95
|
+
newState.finalSynthesis = event.finalSynthesis;
|
|
96
|
+
newState.status = 'completed';
|
|
97
|
+
newState.promotedId = undefined;
|
|
98
|
+
} else if (event.nextQueries.length > 0 && newState.currentRound < event.maxRounds) {
|
|
99
|
+
// Case 2: Lead Evaluator chose to continue AND we're under max rounds
|
|
100
|
+
newState.currentRound++;
|
|
101
|
+
newState.status = 'researching';
|
|
102
|
+
newState.promotedId = undefined; // Reset for new round
|
|
103
|
+
event.nextQueries.forEach((q: string, i: number) => {
|
|
104
|
+
const id = `${newState.currentRound}.${i + 1}`;
|
|
105
|
+
newState.aspects[id] = { id, query: q, status: 'pending' };
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
// Case 3: Max rounds reached OR no next queries
|
|
109
|
+
// Note: Per lead-evaluator prompt, when max rounds are reached, the evaluator
|
|
110
|
+
// should synthesize. If nextQueries are provided at max rounds, this is a
|
|
111
|
+
// protocol violation, and we enforce the constraint by completing anyway.
|
|
112
|
+
newState.status = 'completed';
|
|
113
|
+
newState.promotedId = undefined;
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return newState;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Utility to check if a round is finished
|
|
124
|
+
*/
|
|
125
|
+
export function isRoundComplete(state: SystemResearchState, round: number): boolean {
|
|
126
|
+
const roundAspects = Object.values(state.aspects).filter(a => a.id.startsWith(`${round}.`));
|
|
127
|
+
if (roundAspects.length === 0) return false;
|
|
128
|
+
return roundAspects.every(a => a.status === 'completed' || a.status === 'failed');
|
|
129
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Research Types
|
|
3
|
+
*
|
|
4
|
+
* Defines the schema for the monolithic ResearchState persisted in the session tree.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type ResearchStatus = 'planning' | 'researching' | 'evaluating' | 'synthesizing' | 'completed' | 'failed';
|
|
8
|
+
export type SiblingStatus = 'pending' | 'running' | 'completed' | 'failed';
|
|
9
|
+
|
|
10
|
+
export type DeepResearchEvent =
|
|
11
|
+
| { type: 'PLANNING_COMPLETE'; agenda: string[]; initialCount: number }
|
|
12
|
+
| { type: 'SIBLING_STARTED'; id: string }
|
|
13
|
+
| { type: 'SIBLING_COMPLETED'; id: string; report: string }
|
|
14
|
+
| { type: 'SIBLING_FAILED'; id: string; error: string }
|
|
15
|
+
| { type: 'LINKS_SCRAPED'; links: string[] }
|
|
16
|
+
| { type: 'PROMOTION_STARTED'; id: string }
|
|
17
|
+
| { type: 'PROMOTION_DECISION'; nextQueries: string[]; finalSynthesis?: string; maxRounds: number }
|
|
18
|
+
| { type: 'SIBLING_TOKENS'; id: string; tokens: number; cost: number };
|
|
19
|
+
|
|
20
|
+
export interface ResearchSibling {
|
|
21
|
+
id: string; // e.g. "1.1", "2.1"
|
|
22
|
+
query: string;
|
|
23
|
+
status: SiblingStatus;
|
|
24
|
+
report?: string;
|
|
25
|
+
error?: string;
|
|
26
|
+
tokens?: number;
|
|
27
|
+
cost?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SystemResearchState {
|
|
31
|
+
version: number;
|
|
32
|
+
rootQuery: string;
|
|
33
|
+
complexity: 1 | 2 | 3; // 1: Normal, 2: Deep, 3: Ultra
|
|
34
|
+
currentRound: number;
|
|
35
|
+
status: ResearchStatus;
|
|
36
|
+
lastUpdated: number;
|
|
37
|
+
|
|
38
|
+
// The full exhaustive list of aspects planned by the initial coordinator
|
|
39
|
+
initialAgenda: string[];
|
|
40
|
+
|
|
41
|
+
// Global pool updated IMMEDIATELY by the scrape tool
|
|
42
|
+
allScrapedLinks: string[];
|
|
43
|
+
|
|
44
|
+
// Sequence of research aspects actively being worked or completed
|
|
45
|
+
aspects: {
|
|
46
|
+
[id: string]: ResearchSibling;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Track who was promoted to Lead Evaluator in this round to prevent race conditions
|
|
50
|
+
promotedId?: string;
|
|
51
|
+
|
|
52
|
+
// Final result if completed
|
|
53
|
+
finalSynthesis?: string;
|
|
54
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Researcher ID Utilities
|
|
3
|
+
*
|
|
4
|
+
* Abstracts hierarchical IDs (1.1, 1.2, 2.1) to sequential display numbers (1, 2, 3)
|
|
5
|
+
* for cleaner UI while maintaining internal structure.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SystemResearchState } from './deep-research-types.ts';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get sequential display number for a researcher ID
|
|
12
|
+
*
|
|
13
|
+
* Maps internal hierarchical IDs to sequential display numbers:
|
|
14
|
+
* - Round 1: 1.1 → 1, 1.2 → 2, 1.3 → 3
|
|
15
|
+
* - Round 2: 2.1 → 4, 2.2 → 5, 2.3 → 6
|
|
16
|
+
* - Continue sequentially across all rounds
|
|
17
|
+
*/
|
|
18
|
+
export function getDisplayNumber(state: SystemResearchState, internalId: string): string {
|
|
19
|
+
const aspects = Object.values(state.aspects).sort((a, b) => {
|
|
20
|
+
const aParts = a.id.split('.');
|
|
21
|
+
const bParts = b.id.split('.');
|
|
22
|
+
const aRound = parseInt(aParts[0] ?? '0');
|
|
23
|
+
const aNum = parseInt(aParts[1] ?? '0');
|
|
24
|
+
const bRound = parseInt(bParts[0] ?? '0');
|
|
25
|
+
const bNum = parseInt(bParts[1] ?? '0');
|
|
26
|
+
|
|
27
|
+
if (aRound !== bRound) return aRound - bRound;
|
|
28
|
+
return aNum - bNum;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const index = aspects.findIndex(a => a.id === internalId);
|
|
32
|
+
return index >= 0 ? (index + 1).toString() : internalId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get a researcher's role context
|
|
37
|
+
*
|
|
38
|
+
* Returns role information for a researcher:
|
|
39
|
+
* - Number in this round (1, 2, 3)
|
|
40
|
+
* - Total siblings in this round
|
|
41
|
+
* - Current round
|
|
42
|
+
*/
|
|
43
|
+
export function getResearcherRoleContext(
|
|
44
|
+
state: SystemResearchState,
|
|
45
|
+
internalId: string
|
|
46
|
+
): {
|
|
47
|
+
displayNumber: string;
|
|
48
|
+
roundNumber: number;
|
|
49
|
+
totalInRound: number;
|
|
50
|
+
isLastInRound: boolean;
|
|
51
|
+
} {
|
|
52
|
+
const parts = internalId.split('.');
|
|
53
|
+
const round = parseInt(parts[0] ?? '0');
|
|
54
|
+
const sibling = parseInt(parts[1] ?? '0');
|
|
55
|
+
const roundAspects = Object.values(state.aspects).filter(a => a.id.startsWith(`${round}.`));
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
displayNumber: getDisplayNumber(state, internalId),
|
|
59
|
+
roundNumber: round,
|
|
60
|
+
totalInRound: roundAspects.length,
|
|
61
|
+
isLastInRound: sibling === roundAspects.length,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Researcher Session Factory
|
|
3
|
+
*
|
|
4
|
+
* Responsible for instantiating individual researcher agent sessions.
|
|
5
|
+
* Each researcher is an autonomous agent equipped with a suite of specialized tools:
|
|
6
|
+
* - Web search via SearXNG
|
|
7
|
+
* - Context-aware URL scraping via Playwright
|
|
8
|
+
* - Security vulnerability database queries (NVD, CISA, OSV)
|
|
9
|
+
* - Technical Q&A retrieval from Stack Exchange
|
|
10
|
+
* - Local code search via Ripgrep
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { AgentSession, ModelRegistry, SettingsManager, ExtensionContext } from '@mariozechner/pi-coding-agent';
|
|
14
|
+
import { createAgentSession, createReadTool, SessionManager } from '@mariozechner/pi-coding-agent';
|
|
15
|
+
import type { Model } from '@mariozechner/pi-ai';
|
|
16
|
+
import { createResearchTools } from '../tools/index.ts';
|
|
17
|
+
import { makeResourceLoader } from '../utils/make-resource-loader.ts';
|
|
18
|
+
import { ToolUsageTracker, createDefaultToolLimits } from '../utils/tool-usage-tracker.ts';
|
|
19
|
+
import type { SystemResearchState } from './deep-research-types.ts';
|
|
20
|
+
|
|
21
|
+
export interface CreateResearcherSessionOptions {
|
|
22
|
+
cwd: string;
|
|
23
|
+
ctxModel: Model<any> | undefined;
|
|
24
|
+
modelRegistry: ModelRegistry;
|
|
25
|
+
settingsManager: SettingsManager;
|
|
26
|
+
systemPrompt: string;
|
|
27
|
+
searxngUrl: string;
|
|
28
|
+
extensionCtx: ExtensionContext;
|
|
29
|
+
// Optional: real closures for global state management
|
|
30
|
+
getGlobalState?: () => SystemResearchState;
|
|
31
|
+
updateGlobalLinks?: (links: string[]) => void;
|
|
32
|
+
/** Returns tokens consumed by this researcher session so far (for context-aware scrape gating). */
|
|
33
|
+
getTokensUsed?: () => number;
|
|
34
|
+
/** Model context window size in tokens. */
|
|
35
|
+
contextWindowSize?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function createResearcherSession(options: CreateResearcherSessionOptions): Promise<AgentSession> {
|
|
39
|
+
const {
|
|
40
|
+
cwd,
|
|
41
|
+
ctxModel,
|
|
42
|
+
modelRegistry,
|
|
43
|
+
settingsManager,
|
|
44
|
+
systemPrompt,
|
|
45
|
+
searxngUrl,
|
|
46
|
+
extensionCtx,
|
|
47
|
+
getGlobalState,
|
|
48
|
+
updateGlobalLinks,
|
|
49
|
+
getTokensUsed,
|
|
50
|
+
contextWindowSize,
|
|
51
|
+
} = options;
|
|
52
|
+
|
|
53
|
+
// Validate required parameters
|
|
54
|
+
if (!ctxModel) {
|
|
55
|
+
throw new Error('No model selected. Please select a model before using the research tool.');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!systemPrompt || typeof systemPrompt !== 'string') {
|
|
59
|
+
throw new Error('Invalid system prompt: must be a non-empty string');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!searxngUrl || typeof searxngUrl !== 'string') {
|
|
63
|
+
throw new Error('Invalid SearXNG URL: must be a non-empty string');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create tool usage tracker for this researcher
|
|
67
|
+
const tracker = new ToolUsageTracker(createDefaultToolLimits());
|
|
68
|
+
|
|
69
|
+
// Use provided closures or fallback to safe dummies
|
|
70
|
+
const globalState = getGlobalState || (() => ({} as any));
|
|
71
|
+
const globalLinks = updateGlobalLinks || (() => {});
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const result = await createAgentSession({
|
|
75
|
+
cwd,
|
|
76
|
+
tools: [createReadTool(cwd)],
|
|
77
|
+
customTools: createResearchTools({
|
|
78
|
+
searxngUrl,
|
|
79
|
+
ctx: extensionCtx,
|
|
80
|
+
tracker,
|
|
81
|
+
getGlobalState: globalState,
|
|
82
|
+
updateGlobalLinks: globalLinks,
|
|
83
|
+
getTokensUsed,
|
|
84
|
+
contextWindowSize,
|
|
85
|
+
}),
|
|
86
|
+
sessionManager: SessionManager.inMemory(), // Each researcher gets its own isolated session
|
|
87
|
+
settingsManager,
|
|
88
|
+
model: ctxModel,
|
|
89
|
+
modelRegistry,
|
|
90
|
+
resourceLoader: makeResourceLoader(systemPrompt),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (!result || !result.session) {
|
|
94
|
+
throw new Error('Session creation returned invalid result');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result.session;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
100
|
+
throw new Error(`Failed to create researcher session: ${errorMsg}`, {
|
|
101
|
+
cause: error,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Context Formatter
|
|
3
|
+
*
|
|
4
|
+
* Formats the full parent session context for the initial coordinator.
|
|
5
|
+
* Returns the uncompacted history of the current conversation branch.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionContext } from '@mariozechner/pi-coding-agent';
|
|
9
|
+
import {
|
|
10
|
+
buildSessionContext,
|
|
11
|
+
convertToLlm,
|
|
12
|
+
serializeConversation
|
|
13
|
+
} from '@mariozechner/pi-coding-agent';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Formats parent context for the initial coordinator.
|
|
17
|
+
* Provides the full linear history of the current branch.
|
|
18
|
+
*/
|
|
19
|
+
export async function formatParentContext(ctx: ExtensionContext): Promise<string> {
|
|
20
|
+
const branch = ctx.sessionManager.getBranch();
|
|
21
|
+
const sessionContext = buildSessionContext(branch);
|
|
22
|
+
const allMessages = sessionContext.messages;
|
|
23
|
+
|
|
24
|
+
if (allMessages.length === 0) {
|
|
25
|
+
return 'No previous context available.';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Limit context to prevent overflow for the initial coordinator
|
|
29
|
+
const MAX_CONTEXT_MESSAGES = 15;
|
|
30
|
+
const recentMessages = allMessages.length > MAX_CONTEXT_MESSAGES
|
|
31
|
+
? allMessages.slice(-MAX_CONTEXT_MESSAGES)
|
|
32
|
+
: allMessages;
|
|
33
|
+
|
|
34
|
+
const llmMessages = convertToLlm(recentMessages);
|
|
35
|
+
const serialized = serializeConversation(llmMessages);
|
|
36
|
+
|
|
37
|
+
return [
|
|
38
|
+
'## Parent Conversation History',
|
|
39
|
+
'The following is the recent history of the conversation branch for your reference:',
|
|
40
|
+
'',
|
|
41
|
+
serialized
|
|
42
|
+
].join('\n');
|
|
43
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
### 🔍 RESEARCH TOOL USAGE
|
|
2
|
+
|
|
3
|
+
**For any web/internet research questions, use the `research` tool.**
|
|
4
|
+
|
|
5
|
+
The `research` tool (from pi-research extension) is your tool for web/internet research.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
#### What counts as web research?
|
|
10
|
+
|
|
11
|
+
- Questions requiring current information (news, trends, latest developments)
|
|
12
|
+
- Questions about products, services, companies
|
|
13
|
+
- Questions requiring statistics or data
|
|
14
|
+
- Questions about people, places, events, or topics external to this project
|
|
15
|
+
- "What is X?", "How does X work?", "Tell me about X" questions
|
|
16
|
+
|
|
17
|
+
#### What is NOT web research (use other tools for these)
|
|
18
|
+
|
|
19
|
+
- Reading files in the project → use `read` tool
|
|
20
|
+
- Running commands or tests → use `bash` tool
|
|
21
|
+
- Analyzing code in this repository
|
|
22
|
+
- Questions about the project itself
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
#### DEPTH PARAMETER — Controls research intensity
|
|
27
|
+
|
|
28
|
+
**DEFAULT:** Omit depth parameter (uses `depth: 0` - Quick mode)
|
|
29
|
+
- 1 researcher, 1 round
|
|
30
|
+
- Fastest for most queries
|
|
31
|
+
|
|
32
|
+
**Higher depths** (only when user requests thoroughness):
|
|
33
|
+
- `depth: 1` — Normal: 2 researchers, up to 2 rounds
|
|
34
|
+
- `depth: 2` — Deep: 3 researchers, up to 3 rounds
|
|
35
|
+
- `depth: 3` — Ultra: 5 researchers, up to 5 rounds
|
|
36
|
+
|
|
37
|
+
**Key rule:** Default to depth 0. Use higher depths when user explicitly asks for "exhaustive", "deep-dive", "ultra", or "comprehensive" research.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
#### MULTIPLE RESEARCH REQUESTS — Parallel vs. merged
|
|
42
|
+
|
|
43
|
+
**Different scopes** (unrelated topics): call `research` multiple times simultaneously in a single response — one tool call per distinct topic, run in parallel.
|
|
44
|
+
|
|
45
|
+
**Similar or overlapping scope**: merge into a single `research` call and consider raising depth one level to capture the additional breadth.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Researcher
|
|
2
|
+
|
|
3
|
+
You are a research agent. Investigate your assigned topic thoroughly.
|
|
4
|
+
|
|
5
|
+
## Your Job (One Complete Cycle)
|
|
6
|
+
|
|
7
|
+
Complete ONE research cycle with strict tool limits:
|
|
8
|
+
1. **Phase 1**: Up to 4 gathering tool calls (search/security/stackexchange/grep combined)
|
|
9
|
+
2. **Phase 2**: Up to 4 scrape tool calls (handshake + up to 3 batches)
|
|
10
|
+
3. **Phase 3**: Synthesize findings and report
|
|
11
|
+
4. **STOP**: Research complete
|
|
12
|
+
|
|
13
|
+
**CRITICAL**: If you are the last researcher alive in your round, complete your own research first. Do not cut corners.
|
|
14
|
+
|
|
15
|
+
One complete cycle per researcher. Search → scrape → report → done.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Phase 1: Information Gathering (Max 4 Calls Total)
|
|
20
|
+
|
|
21
|
+
You have **4 tool calls total** for gathering across ALL tools (search, security_search, stackexchange, grep combined).
|
|
22
|
+
|
|
23
|
+
**Strategy**:
|
|
24
|
+
- Each tool call can include **multiple queries**
|
|
25
|
+
- Use these 4 calls wisely — one well-crafted `search()` with 5 queries beats 5 separate calls
|
|
26
|
+
- After your 4th gathering call, move to Phase 2
|
|
27
|
+
|
|
28
|
+
**Search Disambiguation**:
|
|
29
|
+
- **Detect Ambiguity Early**: Watch for patterns like:
|
|
30
|
+
- Results from single domain (e.g., only the official website, only Stack Overflow, only dictionaries)
|
|
31
|
+
- Programming results when researching non-technical subjects
|
|
32
|
+
- Dictionary definitions for complex topics
|
|
33
|
+
- Results about different topics with shared keywords
|
|
34
|
+
|
|
35
|
+
- **Query Reformulation**: When ambiguity or single-domain domination detected, vary word order, add domain context, use synonyms, target `site:en.wikipedia.org` for encyclopedic context, or use `sourceType: "news"` to bypass official site content.
|
|
36
|
+
|
|
37
|
+
- **Exhaustive Breadth**: Use 3-5 queries per gathering call. If Tool Call 1 fails, use ALL remaining for reformulation.
|
|
38
|
+
- **Technical Sources**: For technical topics, prioritize official documentation and the latest version; avoid outdated tutorials or unofficial summaries.
|
|
39
|
+
|
|
40
|
+
**Available Tools**:
|
|
41
|
+
- `search`: General web search (Bing, Google, etc. via SearXNG)
|
|
42
|
+
- `sourceType: "news"` — current events, recent releases
|
|
43
|
+
- `sourceType: "github"` — repos, packages, open-source libraries
|
|
44
|
+
- `freshness: "day" | "week" | "month" | "year"` — time filter
|
|
45
|
+
- `security_search`: Security databases (CVE, NVD, OSV, GitHub, CISA KEV)
|
|
46
|
+
- **USE FOR**: vulnerabilities, CVE IDs, package security
|
|
47
|
+
- Filters: severity, CVE ID, package name, ecosystem, actively exploited
|
|
48
|
+
- `stackexchange`: Stack Overflow and Stack Exchange network
|
|
49
|
+
- **USE FOR**: technical questions, code solutions, debugging, best practices
|
|
50
|
+
- Works with any Stack Exchange site, supports tags
|
|
51
|
+
- `grep`: Search patterns in local code
|
|
52
|
+
- `read`: Read local files identified by grep
|
|
53
|
+
|
|
54
|
+
**Rules**:
|
|
55
|
+
- Maximum of 4 gathering tool calls total
|
|
56
|
+
- Do NOT scrape during this phase
|
|
57
|
+
- Collect promising URLs from results
|
|
58
|
+
- After 4th gathering tool call, move to Phase 2
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Phase 2: Scrape Protocol (Context-Aware, Up to 4 Calls)
|
|
63
|
+
|
|
64
|
+
ONLY after Phase 1 is complete:
|
|
65
|
+
|
|
66
|
+
1. **Handshake** (Call 1): Call `scrape` with intended URLs. Tool returns already-scraped links and available batches.
|
|
67
|
+
2. **Batch 1** (Call 2): Filtered list (max 3 URLs). Primary broad scraping. Provide `excludeLinks` for deprioritized URLs.
|
|
68
|
+
3. **Batch 2** (Call 3): Targeted follow-up (max 2 URLs). Use for gaps, retries, or second angle. Auto-deduplicated.
|
|
69
|
+
4. **Batch 3** (Call 4): Deep-dive sub-topic (max 3 URLs).
|
|
70
|
+
|
|
71
|
+
**Rules**:
|
|
72
|
+
- Must call `scrape` at least twice (Handshake + Batch 1)
|
|
73
|
+
- If tool responds with "Context Budget Reached", skip batch and move to Phase 3
|
|
74
|
+
- Batch 1: max 3 URLs — wide net
|
|
75
|
+
- Batch 2: max 2 URLs — targeted
|
|
76
|
+
- Batch 3: max 3 URLs — deep-dive
|
|
77
|
+
- Provide `excludeLinks` in Batch 1 for considered but deprioritized links
|
|
78
|
+
- Synthesize findings from all batches before reporting
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Phase 3: Detailed Report and STOP
|
|
83
|
+
|
|
84
|
+
IMMEDIATELY after Phase 2 completes, you are DONE.
|
|
85
|
+
|
|
86
|
+
Do this:
|
|
87
|
+
- ✅ **Synthesize Everything**: Compile findings from Phase 1, Phase 2, and injected sibling reports
|
|
88
|
+
- ✅ **Be Verbose and Deep**: Provide full breadth, depth, nuance. Don't just list facts — provide context, analysis, conflicting information
|
|
89
|
+
- ✅ **Evidence-Based**: Every major claim has a [citation](URL)
|
|
90
|
+
- ✅ **Report Cited Links**: What you actually scraped and used
|
|
91
|
+
- ✅ **Report Scrape Candidates**: What you found but didn't scrape, with reasons
|
|
92
|
+
- ✅ **STOP IMMEDIATELY**: Submit report and wait
|
|
93
|
+
|
|
94
|
+
The last researcher to finish inherits **Lead Evaluator** role.
|
|
95
|
+
|
|
96
|
+
## Response Format
|
|
97
|
+
|
|
98
|
+
```markdown
|
|
99
|
+
## [Detailed Topic Title]
|
|
100
|
+
|
|
101
|
+
### Executive Summary
|
|
102
|
+
[High-level synthesis of findings, incorporating sibling insights]
|
|
103
|
+
|
|
104
|
+
### Comprehensive Findings
|
|
105
|
+
#### [Theme/Area 1]
|
|
106
|
+
- **Detailed Finding**: [Exhaustive explanation with nuanced context and multiple [citations](URL)]
|
|
107
|
+
- **Analysis**: [Your analytical take, identifying patterns or gaps]
|
|
108
|
+
|
|
109
|
+
#### [Theme/Area 2]
|
|
110
|
+
- **Detailed Finding**: [Continue for all major areas...]
|
|
111
|
+
|
|
112
|
+
### Nuance & Conflicting Data
|
|
113
|
+
[Identify areas where sources disagree or topic is nuanced/ambiguous]
|
|
114
|
+
|
|
115
|
+
### CITED LINKS
|
|
116
|
+
* [URL] - Detailed description of value added
|
|
117
|
+
* [URL] - Detailed description of value added
|
|
118
|
+
|
|
119
|
+
### SCRAPE CANDIDATES
|
|
120
|
+
* [URL] - Why this remains a high-value target
|
|
121
|
+
* [URL] - Why this was deprioritized
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Detailed Findings and Nuance
|
|
125
|
+
|
|
126
|
+
Your research is a deep investigation:
|
|
127
|
+
- Provide full breadth, depth, nuance from sources
|
|
128
|
+
- Build upon and integrate sibling reports
|
|
129
|
+
- Accuracy and depth matter more than matching original label
|
|
130
|
+
- Explicitly identify conflicting data or nuanced areas
|
|
131
|
+
|
|
132
|
+
**Submit your report and STOP IMMEDIATELY.** Do not add anything else.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Tool Failures & Error Handling
|
|
137
|
+
|
|
138
|
+
**Partial Failures (Continue)**:
|
|
139
|
+
- Continue with tools that work
|
|
140
|
+
- Document what failed briefly
|
|
141
|
+
- Use alternative approaches
|
|
142
|
+
- Do NOT use "ERROR:" prefix
|
|
143
|
+
|
|
144
|
+
**Complete Failures (Use ERROR)**:
|
|
145
|
+
- Exhausted all 4 gathering tool calls with NO useful results
|
|
146
|
+
- ALL scraping attempts failed or skipped
|
|
147
|
+
- NO findings to report
|
|
148
|
+
- Signals systemic problem requiring coordinator intervention
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Research Lifecycle
|
|
153
|
+
|
|
154
|
+
1. Receive assignment
|
|
155
|
+
2. Execute Phase 1 (up to 4 gathering calls)
|
|
156
|
+
3. Execute Phase 2 (up to 4 scrape calls)
|
|
157
|
+
4. Execute Phase 3 (report)
|
|
158
|
+
5. **STOP**
|
|
159
|
+
|
|
160
|
+
You do NOT:
|
|
161
|
+
- Wait for feedback
|
|
162
|
+
- Suggest next steps
|
|
163
|
+
- Provide additional context
|
|
164
|
+
- Attempt to improve findings
|
|
165
|
+
|
|
166
|
+
The coordinator receives your report and decides next steps.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Research Coordinator
|
|
2
|
+
|
|
3
|
+
You are an expert research coordinator. Review the chat history and create a list of research tasks.
|
|
4
|
+
|
|
5
|
+
## Your Job
|
|
6
|
+
|
|
7
|
+
1. **Review** the context provided.
|
|
8
|
+
2. **Decompose** the query into broad research tasks. Each task should explore a significant dimension of the topic. Slight overlaps are encouraged for cross-validation.
|
|
9
|
+
3. **Delegate** tasks as a JSON array for parallel researchers.
|
|
10
|
+
|
|
11
|
+
## Output Format
|
|
12
|
+
|
|
13
|
+
Return ONLY a JSON array of strings:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
["task 1", "task 2", "task 3"]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**CRITICAL REQUIREMENTS**:
|
|
20
|
+
- Output must be a valid JSON array of strings
|
|
21
|
+
- Each task must be a plain string (not an object like `{"query": "text"}`)
|
|
22
|
+
- Each task string must be non-empty after trimming
|
|
23
|
+
- Do not include any explanation, preamble, or markdown formatting
|
|
24
|
+
- No code blocks — just the raw JSON array
|