@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
package/src/index.ts
CHANGED
|
@@ -1,74 +1,663 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Manages SearXNG as singleton (shared across all agents).
|
|
6
|
-
* SearXNG is initialized on first research() call, NOT on session_start.
|
|
7
|
-
* Extension-owned cleanup is delegated to pi's session lifecycle.
|
|
8
|
-
*/
|
|
1
|
+
// Set ONNX Runtime logging level early (before library load) to prevent
|
|
2
|
+
// "Attempt to use DefaultLogger but none has been registered" crash on exit.
|
|
3
|
+
// Level 3 = Error, 4 = Fatal.
|
|
4
|
+
process.env['ORT_LOGGING_LEVEL'] = '3';
|
|
9
5
|
|
|
10
|
-
import type { ExtensionAPI } from '@
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
6
|
+
import type { ExtensionAPI, ToolDefinition, AgentToolResult, ExtensionContext, SessionShutdownEvent, SessionBeforeCompactEvent, SessionCompactEvent } from '@earendil-works/pi-coding-agent';
|
|
7
|
+
import type { ExtendedExtensionContext } from './types/extension-context.ts';
|
|
8
|
+
import { VERSION as PI_VERSION } from '@earendil-works/pi-coding-agent';
|
|
9
|
+
import { Key } from '@earendil-works/pi-tui';
|
|
10
|
+
import type { ResearchResultDetails } from './types/index.ts';
|
|
11
|
+
import { createResearchTool, createHealthTool } from './tool.ts';
|
|
12
|
+
import { createResearchKnowledgeSearchTool } from './tools/research-knowledge-search.ts';
|
|
13
|
+
import { logger } from './logger.ts';
|
|
14
|
+
import { randomUUID } from 'node:crypto';
|
|
14
15
|
import { shutdownManager } from './utils/shutdown-manager.ts';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { healthRegistry } from './healthcheck/index.ts';
|
|
17
|
+
import { registerBeforeExitSafetyNet } from './knowledge/embedder-utils.ts';
|
|
18
|
+
import { getConfig, validateConfig } from './config.ts';
|
|
19
|
+
import { metrics } from './utils/metrics.ts';
|
|
20
|
+
import { handleResearchConfigCommand } from './research-config.ts';
|
|
21
|
+
import { loadPrompt } from './core/llm/prompts.ts';
|
|
22
|
+
import { clearAllSessionState, addSteeringMessage, getSteeringMessages, normalizeSessionId, getActiveSessionCount, popQueuedMessages, requeuePoppedMessage, getAllTrackedSessions, getPiActiveSessionOrder, getPiActivePanels } from './orchestration/session-state.ts';
|
|
23
|
+
import { initGlobalTuiController, disposeGlobalTuiController } from './tui/tui-controller.ts';
|
|
24
|
+
import { registerCoreServices, initializeCoreServices, disposeCoreServices } from './core/service-initialization.ts';
|
|
25
|
+
import { getServiceContainer, resetServiceContainer } from './core/service-registry.ts';
|
|
26
|
+
import { registerInfrastructureServices } from './infrastructure/service-initialization.ts';
|
|
27
|
+
import { registerOrchestrationServices } from './orchestration/service-initialization.ts';
|
|
28
|
+
|
|
29
|
+
// Modular Orchestration Exports
|
|
30
|
+
export { ServiceNames } from './core/interfaces/service-names.ts';
|
|
31
|
+
export type { IResearchOrchestration, ResearchOptions } from './core/interfaces/orchestration-interfaces.ts';
|
|
32
|
+
export { DeepResearchOrchestrator, type DeepResearchOrchestratorOptions } from './orchestration/deep-research-orchestrator.ts';
|
|
33
|
+
export { QuickResearchOrchestrator, type QuickResearchOrchestratorOptions } from './orchestration/quick-research-orchestrator.ts';
|
|
34
|
+
export { shutdownManager } from './utils/shutdown-manager.ts';
|
|
35
|
+
export type { ResearchObserver } from './core/interfaces/observer-interfaces.ts';
|
|
36
|
+
export { normalizeUrl } from './utils/shared-links.ts';
|
|
37
|
+
export { resetConfig, getConfig, setConfig, validateConfig } from './config.ts';
|
|
38
|
+
|
|
39
|
+
// Programmatic SDK Exports. Explicit list (not `export *`) so getSDKContainer —
|
|
40
|
+
// @internal, test-only in src/sdk.ts, imported only by shutdown-perf.test.ts via a
|
|
41
|
+
// direct relative path — is not re-published from the package's primary entry point.
|
|
42
|
+
// (It remains reachable via the separate, intentional `@lincoln504/pi-research/sdk`
|
|
43
|
+
// raw-module subpath in package.json exports, which this does not touch.)
|
|
44
|
+
export {
|
|
45
|
+
initResearchSDK,
|
|
46
|
+
type ResearchSDKOptions,
|
|
47
|
+
repairJson,
|
|
48
|
+
exportKnowledge,
|
|
49
|
+
verifyUrl,
|
|
50
|
+
scrapeUrl,
|
|
51
|
+
runDeepResearch,
|
|
52
|
+
getLastRunMetrics,
|
|
53
|
+
getLastRunSummary,
|
|
54
|
+
getLastErrorReport,
|
|
55
|
+
getLastRunStats,
|
|
56
|
+
getSessionMetrics,
|
|
57
|
+
getResearchHealth,
|
|
58
|
+
runQuickResearch,
|
|
59
|
+
getResearchReports,
|
|
60
|
+
type ResearchRunResult,
|
|
61
|
+
runResearchDetailed,
|
|
62
|
+
type KnowledgeSearchResult,
|
|
63
|
+
searchKnowledge,
|
|
64
|
+
shutdownResearchSDK,
|
|
65
|
+
} from './sdk.ts';
|
|
66
|
+
export { HeadlessObserver, type HeadlessObserverOptions } from './orchestration/headless-observer.ts';
|
|
67
|
+
|
|
68
|
+
// Audit / metrics types for SDK consumers (getLastRunMetrics/getLastRunStats etc.)
|
|
69
|
+
export type { IMetricsSnapshot, IMetricHistogram, RunSummary } from './utils/metrics.ts';
|
|
70
|
+
export type { ResearchStats } from './utils/metrics-summary.ts';
|
|
71
|
+
export { extractRunStats } from './utils/metrics-summary.ts';
|
|
72
|
+
|
|
73
|
+
import {
|
|
74
|
+
MAX_TEAM_SIZE_LEVEL_1,
|
|
75
|
+
MAX_TEAM_SIZE_LEVEL_2,
|
|
76
|
+
MAX_TEAM_SIZE_LEVEL_3,
|
|
77
|
+
} from './constants.ts';
|
|
18
78
|
|
|
19
|
-
|
|
20
|
-
|
|
79
|
+
// Extract the text content from a research tool result
|
|
80
|
+
function extractResultText(result: AgentToolResult<unknown>): string {
|
|
81
|
+
const textBlock = result.content?.find(
|
|
82
|
+
(c): c is { type: 'text'; text: string } => c.type === 'text',
|
|
83
|
+
);
|
|
84
|
+
return textBlock?.text || 'Research completed, but no text content was generated.';
|
|
85
|
+
}
|
|
21
86
|
|
|
22
87
|
/**
|
|
23
|
-
*
|
|
88
|
+
* Pi Research Extension
|
|
24
89
|
*/
|
|
25
|
-
function
|
|
26
|
-
|
|
90
|
+
export default async function (pi: ExtensionAPI) {
|
|
91
|
+
// Runtime version check — must match the @earendil-works/* dependency minimum (>=0.80.2).
|
|
92
|
+
// 0.80.2 is the floor because the AuthStorage credential discriminator pi-research
|
|
93
|
+
// passes (`type: 'api_key'`, model-registry-factory.ts) only became correct in 0.80.2
|
|
94
|
+
// (it was `api-key` before); the pi-ai `/compat` entrypoint also exists from 0.80.0.
|
|
95
|
+
const versionParts = PI_VERSION.split('.').map(Number);
|
|
96
|
+
const major = versionParts[0] ?? 0;
|
|
97
|
+
const minor = versionParts[1] ?? 0;
|
|
98
|
+
const patch = versionParts[2] ?? 0;
|
|
99
|
+
if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`[pi-research] Cannot parse pi-coding-agent version "${PI_VERSION}". ` +
|
|
102
|
+
`Please ensure pi-coding-agent is installed correctly.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const minMajor = 0, minMinor = 80, minPatch = 2;
|
|
106
|
+
const tooOld = major < minMajor
|
|
107
|
+
|| (major === minMajor && minor < minMinor)
|
|
108
|
+
|| (major === minMajor && minor === minMinor && patch < minPatch);
|
|
109
|
+
if (tooOld) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`[pi-research] pi-coding-agent v${PI_VERSION} is too old. ` +
|
|
112
|
+
`Requires v${minMajor}.${minMinor}.${minPatch}+. Please update pi-coding-agent.`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Re-register the beforeExit safety net (deactivate() strips event listeners during reload).
|
|
117
|
+
// This ensures the ONNX pipeline is disposed even after extension reloads.
|
|
118
|
+
registerBeforeExitSafetyNet();
|
|
119
|
+
|
|
120
|
+
// 1. REGISTER CRITICAL EVENT LISTENERS IMMEDIATELY
|
|
121
|
+
// This ensures we capture steering even if initialization takes time.
|
|
122
|
+
pi.on('input', async (event: any, ctx: ExtensionContext) => {
|
|
123
|
+
// Only intercept genuine interactive keystrokes. Programmatic sends originate
|
|
124
|
+
// from this extension (source 'extension') — e.g. the Alt+P pop handler forwards
|
|
125
|
+
// a popped message as 'steer'. Without this guard that forward would be captured
|
|
126
|
+
// straight back into the research queue, so the pop could never reach pi.
|
|
127
|
+
if (event.source === 'extension') return undefined;
|
|
128
|
+
if (event.streamingBehavior === 'steer' && event.text) {
|
|
129
|
+
try {
|
|
130
|
+
const eCtx = ctx as ExtendedExtensionContext;
|
|
131
|
+
// eslint-disable-next-line no-control-regex
|
|
132
|
+
const sanitized = event.text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '').trim();
|
|
133
|
+
if (!sanitized) return undefined;
|
|
134
|
+
|
|
135
|
+
const activeCount = getActiveSessionCount();
|
|
136
|
+
if (activeCount === 0) return undefined;
|
|
137
|
+
|
|
138
|
+
logger.debug(`[pi-research] Captured steering input. sessionId=${eCtx.sessionId}`);
|
|
139
|
+
|
|
140
|
+
// Best-effort UI feedback. Without it, a steering message that is forwarded to
|
|
141
|
+
// pi's follow-up queue (because the current research step can't be interrupted)
|
|
142
|
+
// is delivered but invisible — it looks like nothing happened.
|
|
143
|
+
const notify = (msg: string, level: 'info' | 'warning' = 'info') => {
|
|
144
|
+
try { if (ctx.hasUI) ctx.ui.notify(msg, level); } catch { /* notify is best-effort */ }
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
let sessionIds: string[] = [];
|
|
148
|
+
if (eCtx.sessionId) {
|
|
149
|
+
sessionIds = [eCtx.sessionId];
|
|
150
|
+
} else {
|
|
151
|
+
const activeSessions = getAllTrackedSessions().filter(sid => getPiActiveSessionOrder(sid).length > 0);
|
|
152
|
+
if (activeSessions.length === 1) {
|
|
153
|
+
sessionIds = [activeSessions[0]!];
|
|
154
|
+
} else if (activeSessions.length > 1) {
|
|
155
|
+
// Ambiguous — we cannot attribute the message to one research session.
|
|
156
|
+
// Do NOT swallow it: fall through so pi applies its own native steering.
|
|
157
|
+
notify('Multiple active research sessions — steering left to pi.', 'warning');
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (sessionIds.length === 0) {
|
|
163
|
+
// No research session we can route to — let pi handle the input natively
|
|
164
|
+
// rather than silently dropping it (returning 'handled' would eat it).
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let queued = 0;
|
|
169
|
+
let forwarded = 0;
|
|
170
|
+
for (const sid of sessionIds) {
|
|
171
|
+
// Check if any active research panel in this session can accept steering.
|
|
172
|
+
// During eval/coordinator LLM calls, steering is forwarded to follow-up
|
|
173
|
+
// instead of being queued, since the running LLM call cannot be interrupted
|
|
174
|
+
// and the message might not be consumed before the research ends.
|
|
175
|
+
const activePanels = getPiActivePanels(sid);
|
|
176
|
+
const steeringAcceptable = activePanels.some(p => p.steeringAcceptable === true);
|
|
177
|
+
|
|
178
|
+
if (steeringAcceptable) {
|
|
179
|
+
addSteeringMessage(sid, sanitized);
|
|
180
|
+
queued++;
|
|
181
|
+
} else {
|
|
182
|
+
// Steering not acceptable right now (e.g. an eval/coordinator LLM call is in
|
|
183
|
+
// flight). Forward to pi as a STEER, not a follow-up: steer is injected at the
|
|
184
|
+
// next agent step — right after the running research tool returns its report —
|
|
185
|
+
// so it lands as the next message instead of sitting until the agent stops
|
|
186
|
+
// (which, in a long autonomous turn, may never happen). The source-'extension'
|
|
187
|
+
// guard above prevents this from being re-captured back into the queue.
|
|
188
|
+
logger.debug(`[pi-research] Steering not acceptable for session ${sid}, forwarding as steer: ${sanitized}`);
|
|
189
|
+
try {
|
|
190
|
+
await pi.sendUserMessage(sanitized, { deliverAs: 'steer' });
|
|
191
|
+
forwarded++;
|
|
192
|
+
} catch (err) {
|
|
193
|
+
logger.warn('[pi-research] Failed to forward steering to pi:', err);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Surface the outcome so the action is never invisible.
|
|
199
|
+
if (queued > 0 && forwarded === 0) {
|
|
200
|
+
notify('Queued — will steer the next research round.', 'info');
|
|
201
|
+
} else if (forwarded > 0 && queued === 0) {
|
|
202
|
+
notify('Sent to pi to steer the agent.', 'info');
|
|
203
|
+
} else if (queued > 0 && forwarded > 0) {
|
|
204
|
+
notify('Steering received (queued for next round + steer to pi).', 'info');
|
|
205
|
+
} else {
|
|
206
|
+
// Nothing was delivered (e.g. the steer send threw) — let pi handle it
|
|
207
|
+
// natively rather than eating the message.
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Return handled to indicate we have fully processed this input
|
|
212
|
+
// and Pi should not echo it into the main chat log.
|
|
213
|
+
return { action: 'handled' };
|
|
214
|
+
} catch (err) {
|
|
215
|
+
logger.debug('[pi-research] Input handler error:', err);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return undefined;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// 2. REGISTER SHUTDOWN TASKS
|
|
223
|
+
shutdownManager.register(async () => {
|
|
224
|
+
try {
|
|
225
|
+
// Dispose native resources (ONNX pipeline) while C++ statics are still alive.
|
|
226
|
+
await disposeCoreServices();
|
|
227
|
+
logger.log('[pi-research] All services disposed');
|
|
228
|
+
// Clear service REGISTRATIONS too (not just instances). disposeCoreServices
|
|
229
|
+
// nulls instances but leaves the factory map intact, so without this a
|
|
230
|
+
// same-process re-activate() (pi reload / new session) would re-run
|
|
231
|
+
// registerCoreServices against an already-populated container and throw on
|
|
232
|
+
// the first allowOverwrite:false register — leaving isReady=true over stale
|
|
233
|
+
// services. Matches the SDK and CLI teardown (both reset after dispose).
|
|
234
|
+
await resetServiceContainer(getServiceContainer());
|
|
235
|
+
// Clear in-memory state after disposal.
|
|
236
|
+
disposeGlobalTuiController();
|
|
237
|
+
clearAllSessionState();
|
|
238
|
+
metrics.clearSession();
|
|
239
|
+
logger.info('[pi-research] All session state cleared');
|
|
240
|
+
} catch (err) {
|
|
241
|
+
logger.error('[pi-research] Shutdown task failed:', err);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// 3. SERVICE INITIALIZATION
|
|
246
|
+
logger.log(`[pi-research] Activating extension (pi v${PI_VERSION})...`);
|
|
247
|
+
|
|
248
|
+
// Register and initialize services into the global container
|
|
27
249
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
250
|
+
const container = getServiceContainer();
|
|
251
|
+
registerCoreServices(container);
|
|
252
|
+
registerInfrastructureServices(container);
|
|
253
|
+
registerOrchestrationServices(container);
|
|
254
|
+
logger.log('[pi-research] Services registered');
|
|
255
|
+
|
|
256
|
+
// Pass pi as context — includes cwd for proper config loading
|
|
257
|
+
const result = await initializeCoreServices(pi, container);
|
|
258
|
+
if (result.failed.length > 0) {
|
|
259
|
+
logger.error(`[pi-research] WARNING: Service initialization incomplete: ${result.failed.join(', ')}`);
|
|
260
|
+
} else {
|
|
261
|
+
logger.log('[pi-research] All critical services initialized and ready');
|
|
262
|
+
container.isReady = true;
|
|
263
|
+
}
|
|
264
|
+
} catch (err) {
|
|
265
|
+
logger.error('[pi-research] Critical failure during service setup:', err);
|
|
33
266
|
}
|
|
34
|
-
}
|
|
35
267
|
|
|
36
|
-
export default function (pi: ExtensionAPI) {
|
|
37
|
-
logger.log('[pi-research] Extension loading...');
|
|
38
268
|
|
|
39
|
-
//
|
|
40
|
-
|
|
269
|
+
// Validate config at startup (using pi.cwd)
|
|
270
|
+
try {
|
|
271
|
+
const config = getConfig((pi as any).cwd, 'pi');
|
|
272
|
+
validateConfig(config);
|
|
273
|
+
logger.debug('[pi-research] Config validated');
|
|
274
|
+
} catch (err) {
|
|
275
|
+
logger.error(`[pi-research] WARNING: Config validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Self-heal cross-harness skill installs: re-point links left stale by an
|
|
279
|
+
// update and prune ones left dangling by a prior removal. Best-effort — a
|
|
280
|
+
// filesystem hiccup here must never block activation. (npm does not run our
|
|
281
|
+
// preuninstall on uninstall, so this startup pass is the reliable GC.)
|
|
282
|
+
try {
|
|
283
|
+
const { reconcileSkillInstalls } = await import('./skill-install/skill-installer.ts');
|
|
284
|
+
const r = reconcileSkillInstalls();
|
|
285
|
+
if (r.repointed.length || r.pruned.length) {
|
|
286
|
+
logger.info(`[pi-research] Skill links reconciled: ${r.repointed.length} re-pointed, ${r.pruned.length} pruned`);
|
|
287
|
+
}
|
|
288
|
+
} catch (err) {
|
|
289
|
+
logger.debug('[pi-research] Skill reconcile skipped:', err);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Primary cleanup path for pi -p (print mode) and normal session end.
|
|
293
|
+
// Only arm force-exit and mark PI_PROCESS_EXITING on a genuine quit — not on
|
|
294
|
+
// reload/new/resume/fork where the process continues under a rebuilt extension.
|
|
295
|
+
pi.on('session_shutdown', async (event: SessionShutdownEvent) => {
|
|
296
|
+
try {
|
|
297
|
+
if (event.reason === 'quit') {
|
|
298
|
+
process.env['PI_PROCESS_EXITING'] = '1';
|
|
299
|
+
// Watchdog: browser pool + ONNX teardown can take up to ~30s in worst case.
|
|
300
|
+
shutdownManager.forceExitAfter(35000);
|
|
301
|
+
}
|
|
302
|
+
await shutdownManager.runCleanup(`session_shutdown:${event.reason}`);
|
|
303
|
+
} catch (_err) {
|
|
304
|
+
logger.error('[pi-research] session_shutdown cleanup failed:', _err);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
pi.on('session_before_compact', (event: SessionBeforeCompactEvent) => {
|
|
309
|
+
logger.debug(`[pi-research] compaction starting: reason=${event.reason} willRetry=${event.willRetry}`);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
pi.on('session_compact', (event: SessionCompactEvent) => {
|
|
313
|
+
logger.debug(`[pi-research] compaction complete: reason=${event.reason} willRetry=${event.willRetry}`);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Create and register the research tool
|
|
317
|
+
const researchTool: ToolDefinition = createResearchTool('pi');
|
|
318
|
+
pi.registerTool(researchTool);
|
|
319
|
+
|
|
320
|
+
// Create and register the health check tool
|
|
321
|
+
const healthTool: ToolDefinition = createHealthTool();
|
|
322
|
+
pi.registerTool(healthTool);
|
|
323
|
+
|
|
324
|
+
// Create and register the Research Knowledge Search tool. It is native-free at
|
|
325
|
+
// construction (the vector/ML stack loads lazily on first execute) and self-guards when
|
|
326
|
+
// Knowledge Mode is 'none' (its execute returns a "store disabled" miss). Registering it
|
|
327
|
+
// unconditionally lets a Knowledge Mode change via /research-config take effect without a
|
|
328
|
+
// Pi restart — availability to the agent and the /knowledge-store command below both key
|
|
329
|
+
// off live config, not this startup binding. The pi API has no unregisterTool, so a
|
|
330
|
+
// startup-gated registration could never be added back after enabling mid-session anyway.
|
|
331
|
+
const researchKnowledgeSearchTool: ToolDefinition = createResearchKnowledgeSearchTool('pi');
|
|
332
|
+
pi.registerTool(researchKnowledgeSearchTool);
|
|
41
333
|
|
|
42
|
-
|
|
43
|
-
|
|
334
|
+
// Alt+P — Pop queued steering messages out of the research queue and steer pi with them.
|
|
335
|
+
pi.registerShortcut(Key.alt('p'), {
|
|
336
|
+
description: 'Pop queued research steering messages back to chat',
|
|
337
|
+
handler: async (ctx: ExtensionContext) => {
|
|
338
|
+
const eCtx = ctx as ExtendedExtensionContext;
|
|
339
|
+
let piSessionId = eCtx.sessionId || eCtx.sessionManager?.getSessionId();
|
|
340
|
+
|
|
341
|
+
if (!piSessionId) {
|
|
342
|
+
const activeSessions = getAllTrackedSessions().filter(sid => getPiActiveSessionOrder(sid).length > 0);
|
|
343
|
+
if (activeSessions.length === 1) {
|
|
344
|
+
piSessionId = activeSessions[0]!;
|
|
345
|
+
} else if (activeSessions.length > 1) {
|
|
346
|
+
if (ctx.hasUI) ctx.ui.notify('Ambiguous sessions — cannot pop steering.', 'warning');
|
|
347
|
+
return;
|
|
348
|
+
} else {
|
|
349
|
+
piSessionId = 'default';
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const activeCount = getActiveSessionCount();
|
|
354
|
+
if (activeCount === 0) return;
|
|
355
|
+
|
|
356
|
+
const queuedBefore = getSteeringMessages(piSessionId).filter(m => m.status === 'queued');
|
|
357
|
+
const popped = popQueuedMessages(piSessionId);
|
|
358
|
+
|
|
359
|
+
if (popped.length === 0) {
|
|
360
|
+
if (ctx.hasUI) {
|
|
361
|
+
// queuedBefore>0 but nothing popped means the orchestrator consumed the
|
|
362
|
+
// message between the panel render and this keypress — tell the user
|
|
363
|
+
// rather than appearing to do nothing.
|
|
364
|
+
ctx.ui.notify(
|
|
365
|
+
queuedBefore.length === 0
|
|
366
|
+
? 'No steering messages found.'
|
|
367
|
+
: 'Steering already consumed by research.',
|
|
368
|
+
'info',
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
// Forward as 'steer', NOT 'followUp'. A popped message means "redirect the agent
|
|
374
|
+
// now". A follow-up is only consumed once the agent would otherwise stop, so during
|
|
375
|
+
// a long autonomous turn — e.g. a multi-round research the model keeps extending —
|
|
376
|
+
// it never drains and the user sees their instruction ignored. 'steer' is injected
|
|
377
|
+
// at the next agent step: right after the running research tool returns its report
|
|
378
|
+
// into chat, the agent acts on it. (The input handler ignores source 'extension',
|
|
379
|
+
// so this is not re-captured into the research queue.)
|
|
380
|
+
let sent = 0;
|
|
381
|
+
for (const msg of popped) {
|
|
382
|
+
try {
|
|
383
|
+
await pi.sendUserMessage(msg.text, { deliverAs: 'steer' });
|
|
384
|
+
sent++;
|
|
385
|
+
} catch (err) {
|
|
386
|
+
// Never lose the user's words: restore the message to queued (still poppable).
|
|
387
|
+
logger.warn('[pi-research] Failed to forward popped steering to pi:', err);
|
|
388
|
+
requeuePoppedMessage(piSessionId, msg.id);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (ctx.hasUI) {
|
|
393
|
+
if (sent > 0) {
|
|
394
|
+
ctx.ui.notify(`Sent ${sent} steering message(s) to pi.`, 'info');
|
|
395
|
+
} else {
|
|
396
|
+
ctx.ui.notify('Could not send steering to pi — left it queued.', 'warning');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// /research <query> — direct quick research, no LLM turn.
|
|
403
|
+
pi.registerCommand('research', {
|
|
404
|
+
description: 'Web research a query',
|
|
405
|
+
handler: async (args, ctx) => {
|
|
406
|
+
const text = args.trim();
|
|
407
|
+
if (!text) return;
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
const config = getConfig(ctx.cwd, 'pi');
|
|
411
|
+
|
|
412
|
+
const result = await researchTool.execute(
|
|
413
|
+
randomUUID(),
|
|
414
|
+
{ query: text, depth: config.DEFAULT_RESEARCH_DEPTH },
|
|
415
|
+
ctx.signal,
|
|
416
|
+
undefined,
|
|
417
|
+
ctx,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
const output = extractResultText(result);
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
pi.sendMessage({
|
|
424
|
+
customType: 'research-result',
|
|
425
|
+
content: output,
|
|
426
|
+
display: true,
|
|
427
|
+
details: {
|
|
428
|
+
totalTokens: (result.details as ResearchResultDetails)?.totalTokens ?? 0,
|
|
429
|
+
researchId: (result.details as any)?.researchId
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
if (ctx.hasUI) {
|
|
434
|
+
ctx.ui.notify('Research finished.', 'info');
|
|
435
|
+
}
|
|
436
|
+
} catch (deliverErr) {
|
|
437
|
+
// ctx went stale (the session was closed mid-run) — there's no live session to
|
|
438
|
+
// deliver the result to. Not a failure of the research itself.
|
|
439
|
+
logger.debug('[pi-research] could not deliver research result (session closed):', deliverErr);
|
|
440
|
+
}
|
|
441
|
+
} catch (error: unknown) {
|
|
442
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
443
|
+
// A quit / session replacement mid-run aborts ctx.signal and invalidates ctx; the run
|
|
444
|
+
// was cancelled, not broken, and any sendMessage/ui call below would itself throw
|
|
445
|
+
// "ctx is stale after session replacement or reload". There is no live session to
|
|
446
|
+
// report to, so exit quietly instead of logging a failure and re-throwing.
|
|
447
|
+
if (ctx.signal?.aborted || /ctx is stale after session replacement/i.test(message)) {
|
|
448
|
+
logger.debug('[pi-research] /research command ended early (session closed mid-run); skipping result delivery');
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
logger.error('[pi-research] /research command failed:', error);
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
pi.sendMessage({
|
|
455
|
+
customType: 'research-result',
|
|
456
|
+
content: `**Research failed**\n\n${message}`,
|
|
457
|
+
display: true,
|
|
458
|
+
details: { error: message },
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
if (ctx.hasUI) {
|
|
462
|
+
ctx.ui.notify(`Research failed: ${message}`, 'error');
|
|
463
|
+
}
|
|
464
|
+
} catch (deliverErr) {
|
|
465
|
+
logger.debug('[pi-research] could not deliver failure notice (session closed):', deliverErr);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
},
|
|
44
469
|
});
|
|
45
470
|
|
|
46
|
-
//
|
|
47
|
-
pi.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
471
|
+
// /research-config — consolidated research configuration and management command
|
|
472
|
+
pi.registerCommand('research-config', {
|
|
473
|
+
description: 'Research configuration and management (health, errors, knowledge, settings, metrics)',
|
|
474
|
+
handler: async (args, ctx: ExtensionContext) => {
|
|
475
|
+
await handleResearchConfigCommand(args, ctx, pi);
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// /knowledge-store <query> — search the local knowledge store for previously
|
|
480
|
+
// researched findings (synthesised answer). Query-only: the store auto-manages its
|
|
481
|
+
// own compaction/pruning after runs, so there is no manual maintenance subcommand.
|
|
482
|
+
pi.registerCommand('knowledge-store', {
|
|
483
|
+
description: 'Search the local research knowledge store for a query',
|
|
484
|
+
handler: async (args, ctx: ExtensionContext) => {
|
|
485
|
+
const text = args.trim();
|
|
486
|
+
if (!text) return;
|
|
487
|
+
|
|
488
|
+
// With KNOWLEDGE_STORE_MODE='none' the store is disabled and a search can only miss.
|
|
489
|
+
// Point the user at the toggle instead. Read LIVE config so a /research-config change
|
|
490
|
+
// applies without a Pi restart (getConfig is re-read after research-config's resetConfig).
|
|
491
|
+
// Key the gate off the SAME cwd the tool below resolves its config from (ctx.cwd, not
|
|
492
|
+
// the activation pi.cwd) so the "disabled?" check and the tool never disagree on the
|
|
493
|
+
// mode when the session cwd differs from the activation directory.
|
|
494
|
+
if (getConfig(ctx.cwd ?? (pi as any).cwd, 'pi').KNOWLEDGE_STORE_MODE === 'none') {
|
|
495
|
+
const msg = 'The knowledge store is disabled (Knowledge Mode = none). Enable it via /research-config.';
|
|
496
|
+
if (ctx.hasUI) ctx.ui.notify(msg, 'warning');
|
|
497
|
+
else pi.sendMessage({ customType: 'knowledge-store', content: msg, display: true });
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
try {
|
|
502
|
+
const result = await researchKnowledgeSearchTool.execute(
|
|
503
|
+
randomUUID(),
|
|
504
|
+
{ queries: [text] },
|
|
505
|
+
ctx.signal,
|
|
506
|
+
undefined,
|
|
507
|
+
ctx,
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
const output = extractResultText(result);
|
|
511
|
+
try {
|
|
512
|
+
pi.sendMessage({
|
|
513
|
+
customType: 'knowledge-store',
|
|
514
|
+
content: output,
|
|
515
|
+
display: true,
|
|
516
|
+
details: { researchId: (result.details as any)?.researchId },
|
|
517
|
+
});
|
|
518
|
+
if (ctx.hasUI) ctx.ui.notify('Knowledge search finished.', 'info');
|
|
519
|
+
} catch (deliverErr) {
|
|
520
|
+
logger.debug('[pi-research] could not deliver knowledge-store result (session closed):', deliverErr);
|
|
521
|
+
}
|
|
522
|
+
} catch (error: unknown) {
|
|
523
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
524
|
+
// A quit/session replacement mid-run aborts ctx.signal and invalidates ctx —
|
|
525
|
+
// the search was cancelled, not broken. Exit quietly (mirrors /research).
|
|
526
|
+
if (ctx.signal?.aborted || /ctx is stale after session replacement/i.test(message)) {
|
|
527
|
+
logger.debug('[pi-research] /knowledge-store command ended early (session closed mid-run)');
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
logger.error('[pi-research] /knowledge-store command failed:', error);
|
|
531
|
+
try {
|
|
532
|
+
pi.sendMessage({
|
|
533
|
+
customType: 'knowledge-store',
|
|
534
|
+
content: `**Knowledge store search failed**\n\n${message}`,
|
|
535
|
+
display: true,
|
|
536
|
+
details: { error: message },
|
|
537
|
+
});
|
|
538
|
+
if (ctx.hasUI) ctx.ui.notify(`Knowledge store search failed: ${message}`, 'error');
|
|
539
|
+
} catch (deliverErr) {
|
|
540
|
+
logger.debug('[pi-research] could not deliver knowledge-store failure notice (session closed):', deliverErr);
|
|
541
|
+
}
|
|
54
542
|
}
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// JIT Prompt Injection (Simplified & Non-Intrusive)
|
|
547
|
+
pi.on('before_agent_start', async (event: any, ctx: ExtensionContext) => {
|
|
548
|
+
if (event.streamingBehavior === 'steer') {
|
|
549
|
+
return { systemPrompt: event.systemPrompt };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (ctx.mode === 'tui' && ctx.hasUI) {
|
|
553
|
+
initGlobalTuiController(ctx.ui, (ctx as ExtendedExtensionContext).sessionId);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Researcher sub-agent sessions already carry the authoritative steering copy:
|
|
557
|
+
// the executor (deep) and quick orchestrator inject "ADDITIONAL USER GUIDANCE"/
|
|
558
|
+
// "ADDITIONAL CONSIDERATIONS" into the researcher system prompt themselves.
|
|
559
|
+
// Appending the same messages again here delivered every steering message
|
|
560
|
+
// TWICE to every researcher — return the prompt untouched. (The coordinator/
|
|
561
|
+
// evaluator never pass through this hook at all: they run via completeSimple,
|
|
562
|
+
// not an agent session, and get steering from planning-service directly.)
|
|
563
|
+
const isResearcher = event.systemPrompt?.includes('RESEARCHER_AGENT_MARKER');
|
|
564
|
+
if (isResearcher) {
|
|
565
|
+
return { systemPrompt: event.systemPrompt };
|
|
55
566
|
}
|
|
56
567
|
|
|
57
|
-
|
|
58
|
-
|
|
568
|
+
const eCtx = ctx as ExtendedExtensionContext;
|
|
569
|
+
const normalizedSid = normalizeSessionId(eCtx.sessionId);
|
|
570
|
+
const steeringMessages = getSteeringMessages(normalizedSid);
|
|
571
|
+
let injectedSystemPrompt = event.systemPrompt;
|
|
572
|
+
|
|
573
|
+
const activeCount = getActiveSessionCount();
|
|
574
|
+
const hasSteeringMessages = steeringMessages.length > 0;
|
|
575
|
+
const shouldInjectSteering = hasSteeringMessages && activeCount > 0;
|
|
576
|
+
|
|
577
|
+
if (shouldInjectSteering) {
|
|
578
|
+
injectedSystemPrompt += '\n\n### ADDITIONAL CONSIDERATIONS\n' +
|
|
579
|
+
'The user has provided the following additional considerations for this task:\n' +
|
|
580
|
+
steeringMessages.map(m => `- ${m.text}`).join('\n');
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const isResearchToolAvailable = !event.systemPromptOptions || event.systemPromptOptions.selectedTools?.includes(researchTool.name);
|
|
584
|
+
// Gate the KNOWLEDGE SEARCH prompt block on LIVE Knowledge Mode (not the startup tool
|
|
585
|
+
// binding, which is now always registered) so enabling/disabling via /research-config
|
|
586
|
+
// applies without a Pi restart. The tool must also be selected for this turn.
|
|
587
|
+
// Key the gate off the SAME cwd the tool and /knowledge-store command resolve config
|
|
588
|
+
// from (ctx.cwd, not the activation pi.cwd) — KNOWLEDGE_STORE_MODE is per-directory, so
|
|
589
|
+
// when the session cwd differs from the activation dir the prompt block and the tool
|
|
590
|
+
// must not disagree on whether the store is enabled.
|
|
591
|
+
const knowledgeModeEnabled = getConfig(ctx.cwd ?? (pi as any).cwd, 'pi').KNOWLEDGE_STORE_MODE !== 'none';
|
|
592
|
+
const isKnowledgeSearchAvailable = knowledgeModeEnabled &&
|
|
593
|
+
(!event.systemPromptOptions || event.systemPromptOptions.selectedTools?.includes(researchKnowledgeSearchTool.name));
|
|
594
|
+
|
|
595
|
+
if (isResearchToolAvailable || isKnowledgeSearchAvailable) {
|
|
596
|
+
let researchPrompt = loadPrompt('research-tool-usage')
|
|
597
|
+
.replace('{{max_team_size_l1}}', MAX_TEAM_SIZE_LEVEL_1.toString())
|
|
598
|
+
.replace('{{max_team_size_l2}}', MAX_TEAM_SIZE_LEVEL_2.toString())
|
|
599
|
+
.replace('{{max_team_size_l3}}', MAX_TEAM_SIZE_LEVEL_3.toString());
|
|
600
|
+
|
|
601
|
+
if (!isKnowledgeSearchAvailable) {
|
|
602
|
+
// Strip the whole KNOWLEDGE SEARCH block when no store is enabled. Match from
|
|
603
|
+
// the `**KNOWLEDGE SEARCH` header (the title may carry a suffix, e.g.
|
|
604
|
+
// "— MANDATORY FIRST STEP") through its terminating `---` rule.
|
|
605
|
+
researchPrompt = researchPrompt.replace(/\n\*\*KNOWLEDGE SEARCH[\s\S]*?\n---\n/m, '\n---\n');
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return {
|
|
609
|
+
systemPrompt: injectedSystemPrompt + '\n\n' + researchPrompt
|
|
610
|
+
};
|
|
59
611
|
}
|
|
612
|
+
|
|
613
|
+
return { systemPrompt: injectedSystemPrompt };
|
|
60
614
|
});
|
|
61
615
|
|
|
62
|
-
//
|
|
63
|
-
pi.
|
|
616
|
+
// Monitor provider responses for diagnostics
|
|
617
|
+
pi.on('after_provider_response', async (event: any, ctx: any) => {
|
|
618
|
+
const { status, headers } = event;
|
|
64
619
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
620
|
+
if (status >= 500) {
|
|
621
|
+
logger.warn(`[pi-research] Provider server error: ${status}`, { headers });
|
|
622
|
+
} else if (status === 429) {
|
|
623
|
+
const retryAfter = headers?.['retry-after'];
|
|
624
|
+
logger.warn(`[pi-research] Rate limited by provider`, { retryAfter });
|
|
625
|
+
if (retryAfter && ctx.hasUI) {
|
|
626
|
+
ctx.ui.notify(`Rate limited. Retry in ${retryAfter}s.`, 'warning');
|
|
627
|
+
}
|
|
628
|
+
} else if (status >= 400) {
|
|
629
|
+
logger.warn(`[pi-research] Provider error: ${status}`, { headers });
|
|
630
|
+
}
|
|
71
631
|
});
|
|
72
632
|
|
|
633
|
+
// Log health status at startup (non-blocking). Track + unref the handle: unref() so a
|
|
634
|
+
// short `pi -p` run isn't held open up to 2s waiting on it, and clear it on shutdown so a
|
|
635
|
+
// fast reload/deactivate doesn't leave it pending to fire against disposed services.
|
|
636
|
+
const healthTimer = setTimeout(async () => {
|
|
637
|
+
try {
|
|
638
|
+
const health = await healthRegistry.runAll();
|
|
639
|
+
const statusIcon = health.status === 'healthy' ? '[OK]' :
|
|
640
|
+
health.status === 'degraded' ? '[WARN]' : '[ERROR]';
|
|
641
|
+
const failedComponents = health.components.filter((c: any) => !c.healthy).map((c: any) => c.component).join(', ');
|
|
642
|
+
|
|
643
|
+
if (health.status === 'healthy') {
|
|
644
|
+
logger.log(`[pi-research] ${statusIcon} System health check passed. All components operational.`);
|
|
645
|
+
} else {
|
|
646
|
+
logger.warn(`[pi-research] ${statusIcon} System health check: ${health.status}. Failed: ${failedComponents || 'none'}`);
|
|
647
|
+
}
|
|
648
|
+
} catch (error) {
|
|
649
|
+
logger.warn('[pi-research] Startup health check failed (non-fatal):', error);
|
|
650
|
+
}
|
|
651
|
+
}, 2000);
|
|
652
|
+
if (healthTimer.unref) healthTimer.unref();
|
|
653
|
+
shutdownManager.register(async () => clearTimeout(healthTimer));
|
|
654
|
+
|
|
73
655
|
logger.log('[pi-research] Extension loaded');
|
|
74
656
|
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Extension Deactivation
|
|
660
|
+
*/
|
|
661
|
+
export async function deactivate(): Promise<void> {
|
|
662
|
+
await shutdownManager.runCleanup('extension-deactivate');
|
|
663
|
+
}
|