@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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -0
  3. package/config/searxng/default-settings.yml +189 -0
  4. package/config/searxng/limiter.toml +34 -0
  5. package/config/tooling/eslint.config.js +73 -0
  6. package/config/tooling/vitest.config.integration.ts +17 -0
  7. package/config/tooling/vitest.config.ts +40 -0
  8. package/config/tooling/vitest.config.unit.ts +12 -0
  9. package/docs/ARCHITECTURE.md +109 -0
  10. package/docs/assets/README-banner.jpg +0 -0
  11. package/package.json +92 -0
  12. package/scripts/setup.js +174 -0
  13. package/src/config.ts +206 -0
  14. package/src/constants.ts +148 -0
  15. package/src/healthcheck/index.ts +309 -0
  16. package/src/index.ts +71 -0
  17. package/src/infrastructure/searxng-lifecycle.ts +577 -0
  18. package/src/infrastructure/searxng-manager.ts +1173 -0
  19. package/src/infrastructure/state-manager.ts +918 -0
  20. package/src/logger.ts +246 -0
  21. package/src/orchestration/deep-research-orchestrator.ts +848 -0
  22. package/src/orchestration/deep-research-reducer.ts +129 -0
  23. package/src/orchestration/deep-research-types.ts +54 -0
  24. package/src/orchestration/id-utils.ts +63 -0
  25. package/src/orchestration/researcher.ts +104 -0
  26. package/src/orchestration/session-context.ts +43 -0
  27. package/src/prompts/research-tool-usage.md +45 -0
  28. package/src/prompts/researcher.md +166 -0
  29. package/src/prompts/system-coordinator.md +24 -0
  30. package/src/prompts/system-lead-evaluator.md +225 -0
  31. package/src/security/cisa-kev.ts +242 -0
  32. package/src/security/github-advisories.ts +607 -0
  33. package/src/security/index.ts +406 -0
  34. package/src/security/nvd.ts +532 -0
  35. package/src/security/osv.ts +487 -0
  36. package/src/security/types.ts +138 -0
  37. package/src/stackexchange/cache.ts +111 -0
  38. package/src/stackexchange/index.ts +352 -0
  39. package/src/stackexchange/output/compact.ts +64 -0
  40. package/src/stackexchange/output/index.ts +7 -0
  41. package/src/stackexchange/output/json.ts +21 -0
  42. package/src/stackexchange/output/table.ts +123 -0
  43. package/src/stackexchange/queries.ts +168 -0
  44. package/src/stackexchange/rest-client.ts +127 -0
  45. package/src/stackexchange/types.ts +129 -0
  46. package/src/tool.ts +430 -0
  47. package/src/tools/grep.ts +335 -0
  48. package/src/tools/index.ts +77 -0
  49. package/src/tools/scrape.ts +451 -0
  50. package/src/tools/search.ts +188 -0
  51. package/src/tools/security.ts +292 -0
  52. package/src/tools/stackexchange.ts +101 -0
  53. package/src/tui/research-panel.ts +576 -0
  54. package/src/tui/searxng-status.ts +80 -0
  55. package/src/types/extension-context.ts +60 -0
  56. package/src/types/llm.ts +111 -0
  57. package/src/utils/inject-date.ts +29 -0
  58. package/src/utils/input-validation.ts +103 -0
  59. package/src/utils/json-utils.ts +262 -0
  60. package/src/utils/make-resource-loader.ts +43 -0
  61. package/src/utils/research-error.ts +205 -0
  62. package/src/utils/research-export.ts +199 -0
  63. package/src/utils/searxng-config.ts +94 -0
  64. package/src/utils/session-state.ts +295 -0
  65. package/src/utils/shared-links.ts +123 -0
  66. package/src/utils/shutdown-manager.ts +54 -0
  67. package/src/utils/text-utils.ts +83 -0
  68. package/src/utils/tool-usage-tracker.ts +135 -0
  69. package/src/web-research/dependencies.ts +20 -0
  70. package/src/web-research/retry-utils.ts +232 -0
  71. package/src/web-research/scrapers.ts +935 -0
  72. package/src/web-research/search.ts +235 -0
  73. package/src/web-research/types.ts +47 -0
  74. package/src/web-research/utils.ts +158 -0
  75. package/tsconfig.json +40 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Extended Type Definitions for pi Extension Context
3
+ *
4
+ * These types extend the base ExtensionContext from @mariozechner/pi-coding-agent
5
+ * to provide type safety for additional properties that are accessed at runtime.
6
+ */
7
+
8
+ import type { SettingsManager } from '@mariozechner/pi-coding-agent';
9
+
10
+ /**
11
+ * Session manager interface for accessing session metadata
12
+ */
13
+ export interface SessionManager {
14
+ getSessionId(): string;
15
+ getSessionFile(): string;
16
+ }
17
+
18
+ /**
19
+ * Extended ExtensionContext with additional properties
20
+ * Note: We don't extend ExtensionContext to avoid type conflicts with
21
+ * ReadonlySessionManager. This is used as a cast target only.
22
+ */
23
+ export interface ExtendedExtensionContext {
24
+ sessionManager?: SessionManager;
25
+ settingsManager?: SettingsManager;
26
+ }
27
+
28
+ /**
29
+ * Model with ID property and optional cost information
30
+ */
31
+ export interface ModelWithId {
32
+ id: string;
33
+ cost?: {
34
+ input: number;
35
+ output: number;
36
+ cacheRead?: number;
37
+ cacheWrite?: number;
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Agent session event with usage and error information
43
+ */
44
+ export interface ExtendedAgentSessionEvent {
45
+ type: string;
46
+ message?: {
47
+ role?: string;
48
+ usage?: {
49
+ totalTokens?: number;
50
+ input?: number;
51
+ output?: number;
52
+ cacheRead?: number;
53
+ cacheWrite?: number;
54
+ };
55
+ };
56
+ isError?: boolean;
57
+ details?: {
58
+ blocked?: boolean;
59
+ };
60
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * LLM-Related Type Definitions
3
+ *
4
+ * Shared type definitions for LLM responses, usage, and message content.
5
+ * These types complement the types from @mariozechner/pi-ai and provide
6
+ * more specific typing for our use cases.
7
+ */
8
+
9
+ /**
10
+ * Token usage information from LLM responses
11
+ */
12
+ export interface TokenUsage {
13
+ /** Input tokens consumed */
14
+ input?: number;
15
+ /** Output tokens generated */
16
+ output?: number;
17
+ /** Cache read tokens (for prompt caching) */
18
+ cacheRead?: number;
19
+ /** Cache write tokens (for prompt caching) */
20
+ cacheWrite?: number;
21
+ /** Total tokens (may be provided by some providers) */
22
+ totalTokens?: number;
23
+ }
24
+
25
+ /**
26
+ * Text content block in a message
27
+ */
28
+ export interface TextContentBlock {
29
+ type: 'text';
30
+ text: string;
31
+ }
32
+
33
+ /**
34
+ * Message content (can be a string or array of blocks)
35
+ */
36
+ export type MessageContent = string | TextContentBlock[];
37
+
38
+ /**
39
+ * Filter for text content blocks from a message content array
40
+ */
41
+ export function isTextContentBlock(block: unknown): block is TextContentBlock {
42
+ return (
43
+ block !== null &&
44
+ typeof block === 'object' &&
45
+ (block as Record<string, unknown>)['type'] === 'text' &&
46
+ typeof (block as Record<string, unknown>)['text'] === 'string'
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Extract text from a message content block or string
52
+ */
53
+ export function extractTextFromContent(content: MessageContent): string {
54
+ if (typeof content === 'string') {
55
+ return content;
56
+ }
57
+ if (Array.isArray(content)) {
58
+ return content
59
+ .filter(isTextContentBlock)
60
+ .map(block => block.text)
61
+ .join('\n');
62
+ }
63
+ return '';
64
+ }
65
+
66
+ /**
67
+ * Parse token usage from an unknown object
68
+ * Returns a partial usage object with any fields that were present
69
+ */
70
+ export function parseTokenUsage(usage: unknown): Partial<TokenUsage> {
71
+ if (!usage || typeof usage !== 'object') {
72
+ return {};
73
+ }
74
+
75
+ const obj = usage as Record<string, unknown>;
76
+ const result: Partial<TokenUsage> = {};
77
+
78
+ if (typeof obj['input'] === 'number') {
79
+ result.input = obj['input'];
80
+ }
81
+ if (typeof obj['output'] === 'number') {
82
+ result.output = obj['output'];
83
+ }
84
+ if (typeof obj['cacheRead'] === 'number') {
85
+ result.cacheRead = obj['cacheRead'];
86
+ }
87
+ if (typeof obj['cacheWrite'] === 'number') {
88
+ result.cacheWrite = obj['cacheWrite'];
89
+ }
90
+ if (typeof obj['totalTokens'] === 'number') {
91
+ result.totalTokens = obj['totalTokens'];
92
+ }
93
+
94
+ return result;
95
+ }
96
+
97
+ /**
98
+ * Calculate total tokens from usage object
99
+ * Falls back to sum of individual components if totalTokens not provided
100
+ */
101
+ export function calculateTotalTokens(usage: Partial<TokenUsage>): number {
102
+ if (usage.totalTokens !== undefined) {
103
+ return usage.totalTokens;
104
+ }
105
+ return (
106
+ (usage.input ?? 0) +
107
+ (usage.output ?? 0) +
108
+ (usage.cacheRead ?? 0) +
109
+ (usage.cacheWrite ?? 0)
110
+ );
111
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Date Injection Utility
3
+ *
4
+ * Injects the current date into agent prompts.
5
+ */
6
+
7
+ /**
8
+ * Format current date as a readable string
9
+ */
10
+ function getCurrentDateString(): string {
11
+ const now = new Date();
12
+ const options: Intl.DateTimeFormatOptions = {
13
+ weekday: 'long',
14
+ year: 'numeric',
15
+ month: 'long',
16
+ day: 'numeric',
17
+ };
18
+ return now.toLocaleDateString('en-US', options);
19
+ }
20
+
21
+ /**
22
+ * Inject current date into a prompt
23
+ * Prepends a date context line to the prompt
24
+ */
25
+ export function injectCurrentDate(prompt: string, _agentType: 'coordinator' | 'researcher'): string {
26
+ const dateString = getCurrentDateString();
27
+ const dateContext = `**Current Date:** ${dateString}\n\n`;
28
+ return dateContext + prompt;
29
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Input Validation Utilities
3
+ *
4
+ * Basic validation for research queries and parameters.
5
+ */
6
+
7
+ import { logger } from '../logger.ts';
8
+
9
+ /** Maximum query length */
10
+ export const MAX_QUERY_LENGTH = 500;
11
+
12
+ /** Minimum query length */
13
+ export const MIN_QUERY_LENGTH = 3;
14
+
15
+ /**
16
+ * Validate research query
17
+ *
18
+ * @param query - The research query to validate
19
+ * @returns Object with isValid flag and optional error message
20
+ */
21
+ export function validateQuery(query: string): { isValid: boolean; error?: string } {
22
+ // Check if query is provided
23
+ if (!query || typeof query !== 'string') {
24
+ return { isValid: false, error: 'Query must be a non-empty string' };
25
+ }
26
+
27
+ const trimmed = query.trim();
28
+
29
+ // Check minimum length
30
+ if (trimmed.length < MIN_QUERY_LENGTH) {
31
+ return { isValid: false, error: `Query must be at least ${MIN_QUERY_LENGTH} characters` };
32
+ }
33
+
34
+ // Check maximum length
35
+ if (trimmed.length > MAX_QUERY_LENGTH) {
36
+ return { isValid: false, error: `Query must be at most ${MAX_QUERY_LENGTH} characters` };
37
+ }
38
+
39
+ // Check for empty or whitespace-only queries
40
+ if (trimmed === '') {
41
+ return { isValid: false, error: 'Query cannot be empty or whitespace-only' };
42
+ }
43
+
44
+ // Check for potentially dangerous patterns
45
+ const dangerousPatterns = [
46
+ /<script/i,
47
+ /javascript:/i,
48
+ /on\w+\s*=/i, // Event handlers like onclick=
49
+ /<iframe/i,
50
+ /<object/i,
51
+ /<embed/i,
52
+ ];
53
+
54
+ for (const pattern of dangerousPatterns) {
55
+ if (pattern.test(trimmed)) {
56
+ return { isValid: false, error: 'Query contains potentially dangerous content' };
57
+ }
58
+ }
59
+
60
+ return { isValid: true };
61
+ }
62
+
63
+ /**
64
+ * Sanitize research query for safe use
65
+ *
66
+ * @param query - The research query to sanitize
67
+ * @returns Sanitized query
68
+ */
69
+ export function sanitizeQuery(query: string): string {
70
+ return query
71
+ .trim()
72
+ .replace(/\s+/g, ' ') // Normalize whitespace
73
+ .replace(/[\x00-\x1F\x7F]/g, ''); // eslint-disable-line no-control-regex -- Remove control characters
74
+ }
75
+
76
+ /**
77
+ * Validate and sanitize research query in one step
78
+ *
79
+ * @param query - The research query to validate and sanitize
80
+ * @returns Sanitized query or throws error if invalid
81
+ * @throws Error if query is invalid
82
+ */
83
+ export function validateAndSanitizeQuery(query: string): string {
84
+ const validation = validateQuery(query);
85
+
86
+ if (!validation.isValid) {
87
+ const error = validation.error || 'Invalid query';
88
+ logger.warn(`[input-validation] ${error}`);
89
+ throw new Error(error);
90
+ }
91
+
92
+ return sanitizeQuery(query);
93
+ }
94
+
95
+ /**
96
+ * Validate complexity level
97
+ *
98
+ * @param complexity - The complexity level to validate
99
+ * @returns True if valid, false otherwise
100
+ */
101
+ export function validateComplexity(complexity: number): boolean {
102
+ return complexity === 0 || complexity === 1 || complexity === 2 || complexity === 3;
103
+ }
@@ -0,0 +1,262 @@
1
+ /**
2
+ * JSON Parsing Utilities
3
+ *
4
+ * Shared utilities for parsing JSON from LLM responses.
5
+ * Handles various formats including code blocks, raw JSON, and malformed input.
6
+ */
7
+
8
+ import { logger } from '../logger.ts';
9
+
10
+ /**
11
+ * Result of a JSON extraction attempt
12
+ */
13
+ export interface JsonExtractionResult<T = unknown> {
14
+ /** Whether extraction was successful */
15
+ success: boolean;
16
+ /** The parsed value if successful, undefined otherwise */
17
+ value: T | undefined;
18
+ /** Error message if unsuccessful */
19
+ error?: string;
20
+ /** The method that succeeded (for logging) */
21
+ method?: 'code-block' | 'raw-object' | 'raw-array';
22
+ }
23
+
24
+ /**
25
+ * Extract JSON from markdown code blocks
26
+ *
27
+ * Looks for ```json or ```javascript code blocks and parses the content.
28
+ * Returns the first successfully parsed block.
29
+ *
30
+ * @param text - Text to search for JSON code blocks
31
+ * @returns Extraction result with parsed value or error
32
+ */
33
+ export function extractJsonFromCodeBlocks<T = unknown>(
34
+ text: string
35
+ ): JsonExtractionResult<T> {
36
+ const codeBlockRegex = /```(?:json|javascript)?\s*([\s\S]*?)```/gi;
37
+ let match: RegExpExecArray | null;
38
+
39
+ while ((match = codeBlockRegex.exec(text)) !== null) {
40
+ const codeContent = match[1] ?? '';
41
+ try {
42
+ const parsed = JSON.parse(codeContent.trim());
43
+ return { success: true, value: parsed as T, method: 'code-block' };
44
+ } catch {
45
+ // Try next code block
46
+ continue;
47
+ }
48
+ }
49
+
50
+ return {
51
+ success: false,
52
+ value: undefined,
53
+ error: 'No valid JSON found in code blocks',
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Extract JSON object from raw text
59
+ *
60
+ * Looks for a JSON object spanning from the first `{` to the last `}`.
61
+ * Useful when JSON is embedded in prose without code blocks.
62
+ *
63
+ * @param text - Text to search for JSON object
64
+ * @returns Extraction result with parsed value or error
65
+ */
66
+ export function extractJsonObject<T = unknown>(
67
+ text: string
68
+ ): JsonExtractionResult<T> {
69
+ const objStart = text.indexOf('{');
70
+ const objEnd = text.lastIndexOf('}');
71
+
72
+ if (objStart === -1 || objEnd <= objStart) {
73
+ return {
74
+ success: false,
75
+ value: undefined,
76
+ error: 'No JSON object boundaries found',
77
+ };
78
+ }
79
+
80
+ try {
81
+ const jsonStr = text.slice(objStart, objEnd + 1);
82
+ const parsed = JSON.parse(jsonStr);
83
+ return { success: true, value: parsed as T, method: 'raw-object' };
84
+ } catch (err) {
85
+ const errorMsg = err instanceof Error ? err.message : String(err);
86
+ return {
87
+ success: false,
88
+ value: undefined,
89
+ error: `Failed to parse JSON object: ${errorMsg}`,
90
+ };
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Extract JSON array from raw text
96
+ *
97
+ * Looks for a JSON array spanning from the first `[` to the last `]`.
98
+ * Useful for extracting arrays of strings or objects.
99
+ *
100
+ * @param text - Text to search for JSON array
101
+ * @returns Extraction result with parsed value or error
102
+ */
103
+ export function extractJsonArray<T = unknown>(
104
+ text: string
105
+ ): JsonExtractionResult<T[]> {
106
+ const stripped = text.trim();
107
+
108
+ // Quick check if it's a JSON array
109
+ if (!stripped.startsWith('[') || !stripped.endsWith(']')) {
110
+ return {
111
+ success: false,
112
+ value: undefined,
113
+ error: 'Text is not a JSON array',
114
+ };
115
+ }
116
+
117
+ try {
118
+ const parsed = JSON.parse(stripped);
119
+ if (!Array.isArray(parsed)) {
120
+ return {
121
+ success: false,
122
+ value: undefined,
123
+ error: 'Parsed value is not an array',
124
+ };
125
+ }
126
+ return { success: true, value: parsed as T[], method: 'raw-array' };
127
+ } catch (err) {
128
+ const errorMsg = err instanceof Error ? err.message : String(err);
129
+ return {
130
+ success: false,
131
+ value: undefined,
132
+ error: `Failed to parse JSON array: ${errorMsg}`,
133
+ };
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Extract JSON with fallback chain
139
+ *
140
+ * Tries multiple extraction methods in order:
141
+ * 1. Code blocks (most reliable)
142
+ * 2. Raw JSON object (for objects)
143
+ * 3. Raw JSON array (for arrays)
144
+ *
145
+ * @param text - Text to extract JSON from
146
+ * @param targetType - Whether to expect 'object', 'array', or 'any'
147
+ * @returns Extraction result with parsed value or error
148
+ */
149
+ export function extractJson<T = unknown>(
150
+ text: string,
151
+ targetType: 'object' | 'array' | 'any' = 'any'
152
+ ): JsonExtractionResult<T> {
153
+ // Try code blocks first (most reliable)
154
+ const codeBlockResult = extractJsonFromCodeBlocks<T>(text);
155
+ if (codeBlockResult.success) {
156
+ logger.debug('[json-utils] Extracted JSON from code block');
157
+ return codeBlockResult;
158
+ }
159
+
160
+ // Try raw object (if expecting object or any)
161
+ if (targetType === 'object' || targetType === 'any') {
162
+ const objectResult = extractJsonObject<T>(text);
163
+ if (objectResult.success) {
164
+ logger.debug('[json-utils] Extracted JSON object from raw text');
165
+ return objectResult;
166
+ }
167
+ }
168
+
169
+ // Try raw array (if expecting array or any)
170
+ if (targetType === 'array' || targetType === 'any') {
171
+ const arrayResult = extractJsonArray<T>(text);
172
+ if (arrayResult.success) {
173
+ logger.debug('[json-utils] Extracted JSON array from raw text');
174
+ return arrayResult as JsonExtractionResult<T>;
175
+ }
176
+ }
177
+
178
+ // All methods failed
179
+ return {
180
+ success: false,
181
+ value: undefined,
182
+ error: 'No valid JSON found using any extraction method',
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Result of string array normalization
188
+ */
189
+ export interface NormalizeArrayResult {
190
+ /** Normalized strings */
191
+ strings: string[];
192
+ /** Count of items that were skipped (empty after normalization) */
193
+ skippedCount: number;
194
+ /** Count of items that were objects and needed extraction */
195
+ extractedCount: number;
196
+ /** Warnings about any issues */
197
+ warnings: string[];
198
+ }
199
+
200
+ /**
201
+ * Normalize string array with detailed feedback
202
+ *
203
+ * Normalizes an array of values to strings with detailed feedback
204
+ * about what was normalized and any issues.
205
+ *
206
+ * @param arr - Array of values to normalize
207
+ * @returns Detailed normalization result
208
+ */
209
+ export function normalizeStringArrayDetailed(
210
+ arr: unknown[]
211
+ ): NormalizeArrayResult {
212
+ const strings: string[] = [];
213
+ const warnings: string[] = [];
214
+ let skippedCount = 0;
215
+ let extractedCount = 0;
216
+
217
+ arr.forEach((item, index) => {
218
+ if (typeof item === 'string') {
219
+ const trimmed = item.trim();
220
+ if (trimmed.length > 0) {
221
+ strings.push(trimmed);
222
+ } else {
223
+ skippedCount++;
224
+ warnings.push(`Item ${index}: empty string`);
225
+ }
226
+ } else if (item !== null && typeof item === 'object') {
227
+ extractedCount++;
228
+ const obj = item as Record<string, unknown>;
229
+ const candidate = obj['query'] ?? obj['topic'] ?? obj['text'] ?? obj['task'];
230
+
231
+ if (typeof candidate === 'string') {
232
+ const trimmed = candidate.trim();
233
+ if (trimmed.length > 0) {
234
+ strings.push(trimmed);
235
+ } else {
236
+ skippedCount++;
237
+ warnings.push(`Item ${index}: object extracted empty string`);
238
+ }
239
+ } else {
240
+ // No recognized key, stringify the whole object
241
+ const stringified = JSON.stringify(item);
242
+ if (stringified.length > 0) {
243
+ strings.push(stringified);
244
+ warnings.push(`Item ${index}: no recognized key, stringified object`);
245
+ } else {
246
+ skippedCount++;
247
+ warnings.push(`Item ${index}: object stringified to empty`);
248
+ }
249
+ }
250
+ } else {
251
+ const stringified = String(item).trim();
252
+ if (stringified.length > 0) {
253
+ strings.push(stringified);
254
+ } else {
255
+ skippedCount++;
256
+ warnings.push(`Item ${index}: ${typeof item} converted to empty string`);
257
+ }
258
+ }
259
+ });
260
+
261
+ return { strings, skippedCount, extractedCount, warnings };
262
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Shared ResourceLoader Factory
3
+ *
4
+ * Extracted from coordinator.ts and researcher.ts to avoid duplication.
5
+ * Both files use identical ResourceLoader implementations.
6
+ */
7
+
8
+ import type { ResourceLoader, ExtensionRuntime } from '@mariozechner/pi-coding-agent';
9
+
10
+ export function makeResourceLoader(systemPromptText: string): ResourceLoader {
11
+ const mockRuntime: ExtensionRuntime = {
12
+ flagValues: new Map(),
13
+ pendingProviderRegistrations: [],
14
+ registerProvider: () => {},
15
+ unregisterProvider: () => {},
16
+ sendMessage: async () => {},
17
+ sendUserMessage: async () => {},
18
+ appendEntry: async () => {},
19
+ setSessionName: () => undefined,
20
+ getSessionName: () => undefined,
21
+ setLabel: async () => {},
22
+ getActiveTools: () => [],
23
+ getAllTools: () => [],
24
+ setActiveTools: () => {},
25
+ refreshTools: () => {},
26
+ getCommands: () => [],
27
+ setModel: async () => true,
28
+ getThinkingLevel: () => 'off',
29
+ setThinkingLevel: () => {},
30
+ };
31
+
32
+ return {
33
+ getExtensions: () => ({ extensions: [], errors: [], runtime: mockRuntime }),
34
+ getSkills: () => ({ skills: [], diagnostics: [] }),
35
+ getPrompts: () => ({ prompts: [], diagnostics: [] }),
36
+ getThemes: () => ({ themes: [], diagnostics: [] }),
37
+ getAgentsFiles: () => ({ agentsFiles: [] }),
38
+ getSystemPrompt: () => systemPromptText,
39
+ getAppendSystemPrompt: () => [],
40
+ extendResources: () => {},
41
+ reload: async () => {},
42
+ };
43
+ }