@lincoln504/pi-research 1.0.5 → 1.0.8
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/dist/cli.mjs +210 -40
- package/dist/prompts/research-tool-usage.md +1 -0
- package/dist/thread-worker.mjs +2 -0
- package/docs/ARCHITECTURE.md +23 -7
- package/docs/CHANGELOG.md +108 -0
- package/docs/CONFIGURATION.md +1 -0
- package/docs/SDK.md +4 -1
- package/package.json +6 -5
- package/skills/pi-research/SKILL.md +1 -1
- package/src/cli.ts +53 -4
- package/src/config.ts +4 -0
- package/src/index.ts +2 -0
- package/src/infrastructure/browser/task-execution-service.ts +15 -3
- package/src/infrastructure/browser/thread-worker.mjs +2 -0
- package/src/orchestration/headless-observer.ts +27 -9
- package/src/orchestration/research-orchestration-service.ts +6 -6
- package/src/orchestration/research-synthesis-service.ts +31 -20
- package/src/orchestration/researcher-executor.ts +51 -13
- package/src/orchestration/session-state.ts +27 -4
- package/src/prompts/research-tool-usage.md +1 -0
- package/src/sdk.ts +57 -4
- package/src/youtube/index.ts +5 -4
package/src/sdk.ts
CHANGED
|
@@ -22,11 +22,12 @@ import { getService, resetServiceContainer, getServiceContainer } from './core/s
|
|
|
22
22
|
import type { ServiceContainer } from './core/service-registry.ts';
|
|
23
23
|
import { ServiceNames } from './core/service-interfaces.ts';
|
|
24
24
|
import type { IKnowledgeStoreService } from './core/interfaces/knowledge-interfaces.ts';
|
|
25
|
-
import type {
|
|
26
|
-
IResearchOrchestration,
|
|
25
|
+
import type {
|
|
26
|
+
IResearchOrchestration,
|
|
27
27
|
IResearchSynthesisService,
|
|
28
|
-
ResearchOptions
|
|
28
|
+
ResearchOptions
|
|
29
29
|
} from './core/interfaces/orchestration-interfaces.ts';
|
|
30
|
+
import type { IPlanningService } from './core/interfaces/planning-interfaces.ts';
|
|
30
31
|
import type { Model } from '@earendil-works/pi-ai';
|
|
31
32
|
import { type ExtensionContext, ModelRegistry } from '@earendil-works/pi-coding-agent';
|
|
32
33
|
import { logger, createLogger, setLogger } from './logger.ts';
|
|
@@ -78,6 +79,22 @@ let _lastRunSummary: RunSummary | null = null;
|
|
|
78
79
|
// run end when errors occurred, and consumers can read the full report after the run.
|
|
79
80
|
let _lastErrorReport: ErrorReport | null = null;
|
|
80
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Researcher-outcome summary for the most recent runDeepResearch call: how many
|
|
84
|
+
* researchers were planned/launched vs. how many ultimately failed (never landed
|
|
85
|
+
* a report) vs. succeeded, plus the first recorded failure reason per failed
|
|
86
|
+
* researcher. Lets a caller distinguish "thin report, little material out there"
|
|
87
|
+
* from "thin report because most researchers died" without opening the log file.
|
|
88
|
+
*/
|
|
89
|
+
export interface ResearcherOutcome {
|
|
90
|
+
planned: number;
|
|
91
|
+
launched: number;
|
|
92
|
+
succeeded: number;
|
|
93
|
+
failed: number;
|
|
94
|
+
failureReasons: Record<string, string>;
|
|
95
|
+
}
|
|
96
|
+
let _lastResearcherOutcome: ResearcherOutcome | null = null;
|
|
97
|
+
|
|
81
98
|
// Signal handler state — registered once, removed on clean shutdown.
|
|
82
99
|
let _onSignal: ((signal: string) => void) | null = null;
|
|
83
100
|
let _sigintHandler: (() => void) | null = null;
|
|
@@ -512,6 +529,7 @@ export async function runDeepResearch(
|
|
|
512
529
|
metrics.recordRunSummary(_lastRunSummary);
|
|
513
530
|
_lastErrorReport = runTracker.getReport();
|
|
514
531
|
logRunErrorSummary(_lastErrorReport, depthLabel, 'success');
|
|
532
|
+
_lastResearcherOutcome = await captureResearcherOutcome(sessionId, researchId, _lastRunSummary.snapshot);
|
|
515
533
|
return result;
|
|
516
534
|
} catch (err) {
|
|
517
535
|
const completedAt = Date.now();
|
|
@@ -524,6 +542,7 @@ export async function runDeepResearch(
|
|
|
524
542
|
metrics.recordRunSummary(_lastRunSummary);
|
|
525
543
|
_lastErrorReport = runTracker.getReport();
|
|
526
544
|
logRunErrorSummary(_lastErrorReport, depthLabel, 'error');
|
|
545
|
+
_lastResearcherOutcome = await captureResearcherOutcome(sessionId, researchId, _lastRunSummary.snapshot);
|
|
527
546
|
throw err;
|
|
528
547
|
} finally {
|
|
529
548
|
// Free the per-run PiSessionState that shouldStopResearch()/getFailedResearchers()
|
|
@@ -564,6 +583,14 @@ export function getLastErrorReport(): ErrorReport | null {
|
|
|
564
583
|
return _lastErrorReport;
|
|
565
584
|
}
|
|
566
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Researcher-outcome summary (planned/launched/succeeded/failed + failure reasons)
|
|
588
|
+
* for the most recent run. Null until the first run completes; cleared on shutdown.
|
|
589
|
+
*/
|
|
590
|
+
export function getLastResearcherOutcome(): ResearcherOutcome | null {
|
|
591
|
+
return _lastResearcherOutcome;
|
|
592
|
+
}
|
|
593
|
+
|
|
567
594
|
/**
|
|
568
595
|
* Emit a single compact line summarizing a run's tracked errors, so they are visible in
|
|
569
596
|
* the operator's log instead of silently accumulating. No-op when the run was clean.
|
|
@@ -576,6 +603,31 @@ function logRunErrorSummary(report: ErrorReport | null, depthLabel: string, stat
|
|
|
576
603
|
logger.warn(`[SDK] run ${status} with ${report.totalErrors} tracked error(s) across ${report.uniquePatterns} pattern(s) [${depthLabel}]${top ? ` — top: ${top}` : ''}`);
|
|
577
604
|
}
|
|
578
605
|
|
|
606
|
+
/**
|
|
607
|
+
* Capture how many researchers were planned/launched/failed for this run, BEFORE
|
|
608
|
+
* the caller's `finally` block frees the PiSessionState (endResearchSession) that
|
|
609
|
+
* getFailedResearchers()/getResearcherFailureReasons() read from. Best-effort:
|
|
610
|
+
* this is instrumentation only and must never affect the run's own result.
|
|
611
|
+
*/
|
|
612
|
+
async function captureResearcherOutcome(sessionId: string, researchId: string, snapshot: IMetricsSnapshot): Promise<ResearcherOutcome | null> {
|
|
613
|
+
try {
|
|
614
|
+
const planningService = await getService<IPlanningService>(ServiceNames.PLANNING, undefined, globalContainer!);
|
|
615
|
+
const planned = planningService.getTotalResearchersPlanned(researchId);
|
|
616
|
+
const launched = extractRunStats(snapshot)?.researchersLaunched ?? 0;
|
|
617
|
+
const failed = getFailedResearchers(sessionId, researchId);
|
|
618
|
+
return {
|
|
619
|
+
planned,
|
|
620
|
+
launched,
|
|
621
|
+
succeeded: Math.max(0, launched - failed.length),
|
|
622
|
+
failed: failed.length,
|
|
623
|
+
failureReasons: getResearcherFailureReasons(sessionId, researchId),
|
|
624
|
+
};
|
|
625
|
+
} catch (err) {
|
|
626
|
+
logger.debug('[SDK] Failed to capture researcher outcome (non-fatal):', err);
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
579
631
|
/**
|
|
580
632
|
* Distilled, human-relevant stats from the most recent run (researchers launched,
|
|
581
633
|
* searches, URLs analyzed/failed, tokens, cost, per-tool usage). Null if no run
|
|
@@ -744,7 +796,7 @@ export async function searchKnowledge(
|
|
|
744
796
|
};
|
|
745
797
|
}
|
|
746
798
|
|
|
747
|
-
import { clearAllSessionState, endResearchSession } from './orchestration/session-state.ts';
|
|
799
|
+
import { clearAllSessionState, endResearchSession, getFailedResearchers, getResearcherFailureReasons } from './orchestration/session-state.ts';
|
|
748
800
|
import { shutdownManager } from './utils/shutdown-manager.ts';
|
|
749
801
|
|
|
750
802
|
/**
|
|
@@ -833,6 +885,7 @@ async function _doShutdown(): Promise<void> {
|
|
|
833
885
|
_lastResearchId = null;
|
|
834
886
|
_lastRunSummary = null;
|
|
835
887
|
_lastErrorReport = null;
|
|
888
|
+
_lastResearcherOutcome = null;
|
|
836
889
|
// The module-level config cache survives the globals above. Without this, a
|
|
837
890
|
// shutdown → env/config-file change → re-init cycle in one process silently
|
|
838
891
|
// reuses the first init's config. (The CLI/skill are unaffected — they pass
|
package/src/youtube/index.ts
CHANGED
|
@@ -126,10 +126,11 @@ function formatResults(results: VideoTranscript[], rejected: string[]): string {
|
|
|
126
126
|
// citation pipeline (parseCitations extracts URLs), so the transcript's
|
|
127
127
|
// provenance would silently drop out of the final report's CITED LINKS.
|
|
128
128
|
// The title/channel follows so the report names the real video, not a
|
|
129
|
-
// paraphrase (the watch URL alone is not human-identifiable).
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
// paraphrase (the watch URL alone is not human-identifiable). Emit this line
|
|
130
|
+
// unconditionally — an untitled video still needs a parseable "Cite as" the
|
|
131
|
+
// model can copy verbatim; omitting the line entirely for untitled videos
|
|
132
|
+
// left them without any prompt-visible citation string to reuse.
|
|
133
|
+
md += `**Cite as:** ${r.url}${r.title ? ` — '${r.title}'${r.author ? ` by ${r.author}` : ''}` : ''}\n`;
|
|
133
134
|
md += `\n${r.text}\n\n---\n\n`;
|
|
134
135
|
}
|
|
135
136
|
|