pi-workflow-engine 0.12.0 → 1.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/.pi/extensions/pi-workflow-engine/index.ts +106 -391
- package/.pi/extensions/pi-workflow-engine/src/advisory-challenge.ts +75 -0
- package/.pi/extensions/pi-workflow-engine/src/advisory-evidence.ts +90 -0
- package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +20 -2
- package/.pi/extensions/pi-workflow-engine/src/agent-attempt.ts +48 -62
- package/.pi/extensions/pi-workflow-engine/src/agent-replay.ts +46 -67
- package/.pi/extensions/pi-workflow-engine/src/agent-retry.ts +14 -37
- package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +5 -11
- package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +15 -21
- package/.pi/extensions/pi-workflow-engine/src/agent-session-identity.ts +13 -43
- package/.pi/extensions/pi-workflow-engine/src/agent-session-providers.ts +17 -5
- package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +93 -100
- package/.pi/extensions/pi-workflow-engine/src/agent-skills.ts +11 -43
- package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +18 -18
- package/.pi/extensions/pi-workflow-engine/src/background-workflows.ts +33 -86
- package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +1 -5
- package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +37 -86
- package/.pi/extensions/pi-workflow-engine/src/discovery.ts +16 -23
- package/.pi/extensions/pi-workflow-engine/src/dynamax-shortcuts.ts +1 -4
- package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +39 -98
- package/.pi/extensions/pi-workflow-engine/src/engine.ts +37 -80
- package/.pi/extensions/pi-workflow-engine/src/finalizers.ts +2 -10
- package/.pi/extensions/pi-workflow-engine/src/git.ts +55 -0
- package/.pi/extensions/pi-workflow-engine/src/guards.ts +8 -0
- package/.pi/extensions/pi-workflow-engine/src/identity-canonicalization.ts +7 -19
- package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +171 -106
- package/.pi/extensions/pi-workflow-engine/src/journal.ts +1 -12
- package/.pi/extensions/pi-workflow-engine/src/model-profile-command.ts +37 -8
- package/.pi/extensions/pi-workflow-engine/src/model-profiles.ts +15 -66
- package/.pi/extensions/pi-workflow-engine/src/options.ts +97 -16
- package/.pi/extensions/pi-workflow-engine/src/perf.ts +43 -72
- package/.pi/extensions/pi-workflow-engine/src/process-runner.ts +14 -4
- package/.pi/extensions/pi-workflow-engine/src/progress-types.ts +2 -0
- package/.pi/extensions/pi-workflow-engine/src/progress.ts +79 -80
- package/.pi/extensions/pi-workflow-engine/src/provider-usage-limit.ts +28 -20
- package/.pi/extensions/pi-workflow-engine/src/replay-path-identity.ts +2 -7
- package/.pi/extensions/pi-workflow-engine/src/research-contract.ts +0 -1
- package/.pi/extensions/pi-workflow-engine/src/research-evidence.ts +27 -31
- package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +128 -142
- package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +14 -31
- package/.pi/extensions/pi-workflow-engine/src/review/patch-validation.ts +119 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/review/review-diff-lines.ts +54 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +41 -7
- package/.pi/extensions/pi-workflow-engine/src/review/review-format.ts +51 -116
- package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +9 -14
- package/.pi/extensions/pi-workflow-engine/src/review/review-report.ts +3 -7
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +158 -241
- package/.pi/extensions/pi-workflow-engine/src/review/review-session-coordinator.ts +28 -38
- package/.pi/extensions/pi-workflow-engine/src/review/review-snapshot.ts +35 -44
- package/.pi/extensions/pi-workflow-engine/src/review-diff-target.ts +13 -14
- package/.pi/extensions/pi-workflow-engine/src/text.ts +29 -0
- package/.pi/extensions/pi-workflow-engine/src/tool-capabilities.ts +2 -1
- package/.pi/extensions/pi-workflow-engine/src/tool-source-identity.ts +16 -28
- package/.pi/extensions/pi-workflow-engine/src/tree-fingerprint.ts +34 -14
- package/.pi/extensions/pi-workflow-engine/src/types.ts +9 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/kit.ts +193 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/research-result-renderer.ts +177 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/result-layout.ts +209 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-format.ts +17 -44
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +703 -235
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-picker.ts +48 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +239 -126
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-runs-browser.ts +461 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-tool-renderer.ts +91 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-widget.ts +316 -98
- package/.pi/extensions/pi-workflow-engine/src/usage.ts +21 -32
- package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +178 -188
- package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +19 -24
- package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +17 -37
- package/.pi/extensions/pi-workflow-engine/src/workflow-result-context.ts +79 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-background.ts +4 -14
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-controller.ts +77 -63
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-history.ts +42 -29
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-record.ts +15 -14
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-store.ts +27 -31
- package/.pi/extensions/pi-workflow-engine/src/workflow-usage-limit-scheduler.ts +1 -5
- package/.pi/extensions/pi-workflow-engine/src/workflows.ts +3 -6
- package/.pi/extensions/pi-workflow-engine/src/worktree.ts +55 -110
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +101 -203
- package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +51 -187
- package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +33 -109
- package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +36 -106
- package/.pi/extensions/pi-workflow-engine/workflows/research.ts +74 -66
- package/USAGE.md +90 -17
- package/package.json +1 -2
- package/skills/workflow-code-review-actions/SKILL.md +12 -23
- package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +0 -81
- package/.pi/extensions/pi-workflow-engine/src/review/code-review-orchestration.ts +0 -49
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +0 -52
- package/.pi/settings.json +0 -3
|
@@ -1,25 +1,24 @@
|
|
|
1
|
+
import { challengeFindings, parseChallengeArgs } from "../src/advisory-challenge.ts";
|
|
1
2
|
import { Type } from "typebox";
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
backfillAdvisoryFindings,
|
|
4
|
+
type AdvisoryVerified,
|
|
5
|
+
type AdvisoryLens,
|
|
6
|
+
concludeLensReview,
|
|
7
|
+
finishAdvisoryReport,
|
|
9
8
|
emptyAdvisoryReport,
|
|
10
|
-
|
|
9
|
+
EMPTY_LENS_REVIEW_STATS,
|
|
11
10
|
formatLocation,
|
|
12
|
-
|
|
11
|
+
formatRankedFinding,
|
|
13
12
|
runLensVerificationPipeline,
|
|
14
13
|
DEFAULT_ADVISORY_TOOL_HINTS,
|
|
15
14
|
DEFAULT_ADVISORY_TOOLS,
|
|
16
15
|
} from "../src/workflow-advisory-utils.ts";
|
|
17
|
-
import type { WorkflowApi, WorkflowMeta
|
|
16
|
+
import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
|
|
18
17
|
|
|
19
18
|
export const meta: WorkflowMeta = {
|
|
20
19
|
name: "refactor-scout",
|
|
21
20
|
description: "Advisory-only refactor scout: scope → per-lens find → independent verify → synthesize safe refactor opportunities.",
|
|
22
|
-
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Synthesize" }],
|
|
21
|
+
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
|
|
23
22
|
};
|
|
24
23
|
|
|
25
24
|
const ScopeSchema = Type.Object({
|
|
@@ -29,22 +28,7 @@ const ScopeSchema = Type.Object({
|
|
|
29
28
|
conventions: Type.Optional(Type.String({ description: "Relevant project conventions from AGENTS.md / docs." })),
|
|
30
29
|
});
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
label: string;
|
|
34
|
-
category: string;
|
|
35
|
-
text: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
type Candidate = AdvisoryCandidate;
|
|
39
|
-
|
|
40
|
-
interface Verified extends Candidate {
|
|
41
|
-
verdict: AdvisoryVerdict["verdict"];
|
|
42
|
-
evidence: string[];
|
|
43
|
-
confidence?: AdvisoryVerdict["confidence"];
|
|
44
|
-
lens: RefactorLens;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const REFACTOR_LENSES: RefactorLens[] = [
|
|
31
|
+
const REFACTOR_LENSES: AdvisoryLens[] = [
|
|
48
32
|
{ label: "duplication", category: "duplication", text: "Repeated logic, copy-pasted structures, or near-duplicate flows that could share one clearer implementation." },
|
|
49
33
|
{ label: "complexity", category: "complexity", text: "Oversized functions, tangled control flow, or abstractions that make local reasoning harder than necessary." },
|
|
50
34
|
{ label: "type-safety", category: "type-safety", text: "Weak typing, avoidable casts, unchecked shapes, or places stronger types would prevent mistakes." },
|
|
@@ -53,25 +37,12 @@ const REFACTOR_LENSES: RefactorLens[] = [
|
|
|
53
37
|
{ label: "conventions", category: "conventions", text: "Departures from project conventions, naming, dependency rules, or local idioms." },
|
|
54
38
|
];
|
|
55
39
|
|
|
56
|
-
const TOOLS = DEFAULT_ADVISORY_TOOLS;
|
|
57
|
-
const TOOL_HINTS = DEFAULT_ADVISORY_TOOL_HINTS;
|
|
58
40
|
const PER_LENS = 5;
|
|
59
41
|
|
|
60
42
|
export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
61
|
-
const { agent,
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
let rawCandidateCount = 0;
|
|
65
|
-
let droppedCandidateCount = 0;
|
|
66
|
-
let refutedCandidateCount = 0;
|
|
67
|
-
const makeStats = (verified: number, kept: number): WorkflowRunStats => ({
|
|
68
|
-
files: fileCount,
|
|
69
|
-
candidates: rawCandidateCount,
|
|
70
|
-
verified,
|
|
71
|
-
kept,
|
|
72
|
-
dropped: droppedCandidateCount,
|
|
73
|
-
refuted: refutedCandidateCount,
|
|
74
|
-
});
|
|
43
|
+
const { agent, phase, log, progress, args } = api;
|
|
44
|
+
const challengeConfig = parseChallengeArgs(args);
|
|
45
|
+
const target = challengeConfig.args || ".";
|
|
75
46
|
|
|
76
47
|
phase("Scope");
|
|
77
48
|
const scope = await agent(
|
|
@@ -80,35 +51,30 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
80
51
|
"Inspect repository structure, the target path or module, and relevant AGENTS.md / project docs conventions. " +
|
|
81
52
|
"Return the concrete files that should be considered, a short summary, and any conventions that affect refactor advice. " +
|
|
82
53
|
`This workflow will fan out across ${REFACTOR_LENSES.length} lenses with up to ${PER_LENS} candidates per lens. Structured output only.`,
|
|
83
|
-
{ phase: "Scope", label: "scope", tools:
|
|
54
|
+
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
84
55
|
);
|
|
85
56
|
|
|
86
|
-
if (
|
|
87
|
-
return emptyAdvisoryReport(
|
|
57
|
+
if (scope.files.length === 0) {
|
|
58
|
+
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
88
59
|
"No files were identified for refactor scouting.",
|
|
89
60
|
["Provide a target path, module, or subsystem to scout for refactor opportunities."],
|
|
90
|
-
|
|
91
|
-
);
|
|
61
|
+
EMPTY_LENS_REVIEW_STATS,
|
|
62
|
+
), []);
|
|
92
63
|
}
|
|
93
64
|
|
|
94
|
-
|
|
95
|
-
progress({ type: "counter", key: "files", label: "files", value: fileCount });
|
|
65
|
+
progress({ type: "counter", key: "files", label: "files", value: scope.files.length });
|
|
96
66
|
progress({ type: "summary", key: "files", value: scope.files.join(", ") });
|
|
97
67
|
log(`${scope.files.length} files scoped for refactor scouting`);
|
|
98
68
|
|
|
99
69
|
const scopeBlock =
|
|
100
70
|
`## Target\n${scope.target}\n\n## Files in scope\n${scope.files.map((file) => `- ${file}`).join("\n")}\n\n` +
|
|
101
71
|
`## Summary\n${scope.summary}\n\n## Conventions\n${scope.conventions ?? "(none noted)"}\n` +
|
|
102
|
-
(args
|
|
72
|
+
(challengeConfig.args ? `\n## User instructions (verbatim)\n${challengeConfig.args}\n` : "");
|
|
103
73
|
|
|
104
|
-
phase("Find");
|
|
105
74
|
const pipelineResult = await runLensVerificationPipeline({
|
|
106
|
-
api
|
|
75
|
+
api,
|
|
107
76
|
lenses: REFACTOR_LENSES,
|
|
108
|
-
schedulingMode: "finder-barrier",
|
|
109
77
|
perLens: PER_LENS,
|
|
110
|
-
tools: TOOLS,
|
|
111
|
-
toolHints: TOOL_HINTS,
|
|
112
78
|
finderPrompt: (lens) =>
|
|
113
79
|
`## Refactor-scout finder — ${lens.label}\n\n${scopeBlock}\n` +
|
|
114
80
|
"This workflow is advisory-only: do not edit files and do not propose broad rewrites.\n" +
|
|
@@ -120,65 +86,29 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
120
86
|
`## Refactor-scout verifier\n\n${scopeBlock}\n## Candidate\n` +
|
|
121
87
|
`Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
|
|
122
88
|
`Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
|
|
123
|
-
"Read the relevant files and return CONFIRMED, PLAUSIBLE, or REFUTED. " +
|
|
89
|
+
"Read the relevant files and return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED. " +
|
|
124
90
|
"Default toward REFUTED if the opportunity is generic, too broad, not evidenced by code, or lacks a safe first step. " +
|
|
125
91
|
"Evidence must quote or cite code. Structured output only.",
|
|
126
|
-
makeVerified: (candidate, lens, judged): Verified => ({
|
|
127
|
-
...candidate,
|
|
128
|
-
verdict: judged.verdict,
|
|
129
|
-
evidence: judged.evidence,
|
|
130
|
-
confidence: judged.confidence,
|
|
131
|
-
lens,
|
|
132
|
-
}),
|
|
133
92
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
phase("Synthesize");
|
|
147
|
-
const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
|
|
148
|
-
const block = ranked
|
|
149
|
-
.map(
|
|
150
|
-
(finding, index) =>
|
|
151
|
-
`### [${index}] ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
|
|
152
|
-
`${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nSafe first step: ${finding.recommendation ?? "(none supplied)"}`,
|
|
153
|
-
)
|
|
154
|
-
.join("\n\n");
|
|
155
|
-
|
|
156
|
-
const report = await agent(
|
|
157
|
-
`## Synthesis: final refactor-scout report\n\n${ranked.length} opportunities survived independent verification.\n\n${block}\n\n` +
|
|
93
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, pipelineResult.coverage);
|
|
94
|
+
return concludeLensReview(api, pipelineResult, verified, {
|
|
95
|
+
files: scope.files.length,
|
|
96
|
+
rank,
|
|
97
|
+
formatFinding: (finding, index) => formatRankedFinding(finding, index, "Safe first step"),
|
|
98
|
+
empty: {
|
|
99
|
+
summary: "No refactor opportunities survived verification.",
|
|
100
|
+
nextSteps: ["Leave the scoped code unchanged unless a human reviewer has additional context."],
|
|
101
|
+
},
|
|
102
|
+
synthesisPrompt: (block, ranked) =>
|
|
103
|
+
`## Synthesis: final refactor-scout report\n\n${ranked.length} opportunities survived independent verification.\n\n${block}\n\n` +
|
|
158
104
|
"Merge findings with the same root cause and rank highest leverage / lowest risk first. " +
|
|
159
|
-
"
|
|
160
|
-
"Severity is maintenance or future-correctness impact.
|
|
105
|
+
"Select findings by ID. " +
|
|
106
|
+
"Severity is maintenance or future-correctness impact. " +
|
|
161
107
|
"Recommendations must be safe first refactor steps, not rewrites. Include concrete nextSteps for the host developer. Structured output only.",
|
|
162
|
-
{
|
|
163
|
-
phase: "Synthesize",
|
|
164
|
-
label: "synthesize",
|
|
165
|
-
tools: [],
|
|
166
|
-
profile: "medium",
|
|
167
|
-
resume: "read-only",
|
|
168
|
-
schema: AdvisoryReportSchema,
|
|
169
|
-
},
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
if (!report) return emptyAdvisoryReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun the workflow with a narrower target."], stats);
|
|
173
|
-
|
|
174
|
-
const findings = backfillAdvisoryFindings(report.findings, ranked, {
|
|
175
|
-
impact: "Impact not restated by synthesis.",
|
|
176
|
-
recommendation: "Choose a small, behavior-preserving refactor first step.",
|
|
177
108
|
});
|
|
178
|
-
return { ...report, findings, stats };
|
|
179
109
|
}
|
|
180
110
|
|
|
181
|
-
function rank(finding:
|
|
111
|
+
function rank(finding: AdvisoryVerified): number {
|
|
182
112
|
const verdictRank = finding.verdict === "CONFIRMED" ? 0 : 1;
|
|
183
113
|
const categoryRank = finding.category === "dead-code" || finding.category === "conventions" ? 2 : 0;
|
|
184
114
|
return verdictRank + categoryRank;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { compactResults } from "../src/concurrency.ts";
|
|
2
1
|
import {
|
|
3
2
|
MAX_RESEARCH_LANES,
|
|
4
3
|
ResearchLaneResultSchema,
|
|
@@ -7,18 +6,17 @@ import {
|
|
|
7
6
|
ResearchVerificationSchema,
|
|
8
7
|
type ResearchClaimCandidate,
|
|
9
8
|
type ResearchReport,
|
|
10
|
-
type ResearchVerification,
|
|
11
9
|
} from "../src/research-contract.ts";
|
|
12
10
|
import {
|
|
13
11
|
buildClaimCandidates,
|
|
14
12
|
fallbackResearchReport,
|
|
15
13
|
normalizeResearchLanes,
|
|
16
|
-
sanitizeLaneResults,
|
|
17
14
|
sanitizeResearchReport,
|
|
18
15
|
sanitizeVerification,
|
|
19
16
|
unavailableResearchReport,
|
|
20
17
|
unavailableVerification,
|
|
21
18
|
} from "../src/research-evidence.ts";
|
|
19
|
+
import { isFatalWorkflowError } from "../src/cancellation.ts";
|
|
22
20
|
import { WorkflowToolHintUnavailableError } from "../src/tool-capabilities.ts";
|
|
23
21
|
import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
|
|
24
22
|
|
|
@@ -32,7 +30,7 @@ const EXTERNAL_TOOLS: string[] = [];
|
|
|
32
30
|
const EXTERNAL_TOOL_HINTS = ["external-search"] as const;
|
|
33
31
|
|
|
34
32
|
export default async function run(api: WorkflowApi): Promise<ResearchReport> {
|
|
35
|
-
const { agent, parallel, phase, log, progress, args } = api;
|
|
33
|
+
const { agent, parallel, phase, log, progress, args, signal } = api;
|
|
36
34
|
const question = args.trim();
|
|
37
35
|
if (!question) return unavailableResearchReport("empty-question");
|
|
38
36
|
|
|
@@ -61,54 +59,54 @@ export default async function run(api: WorkflowApi): Promise<ResearchReport> {
|
|
|
61
59
|
throw error;
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
if (!plan) return unavailableResearchReport("no-evidence");
|
|
65
62
|
const lanes = normalizeResearchLanes(plan);
|
|
66
63
|
if (lanes.length === 0) return unavailableResearchReport("no-evidence");
|
|
67
64
|
progress({ type: "counter", key: "research.lanes", label: "research lanes", value: lanes.length });
|
|
68
65
|
progress({ type: "summary", key: "research.question", value: question });
|
|
69
66
|
|
|
70
67
|
phase("Gather");
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
68
|
+
const gatherResults = await parallel(
|
|
69
|
+
lanes.map((lane) => async () => {
|
|
70
|
+
const result = await agent(
|
|
71
|
+
`Research one bounded lane using only installed external web-search, browsing, or URL-extraction tools.\n\n` +
|
|
72
|
+
`Question: ${question}\n` +
|
|
73
|
+
`Scope constraints: ${plan.scopeConstraints.join("; ") || "(none)"}\n` +
|
|
74
|
+
`Lane: ${lane.title}\nObjective: ${lane.objective}\n` +
|
|
75
|
+
`Queries:\n${lane.queries.map((query) => `- ${query}`).join("\n")}\n\n` +
|
|
76
|
+
"Open the supporting pages instead of citing a search-results page. Prefer primary and authoritative sources; use independent sources when useful. " +
|
|
77
|
+
"Return concrete claims with importance, whether each page supports or conflicts with the claim, a short passage or precise paraphrase, and the exact page title and URL. " +
|
|
78
|
+
"State evidence gaps. Never invent a URL or claim that the opened page does not support. Structured output only.",
|
|
79
|
+
{
|
|
80
|
+
phase: "Gather",
|
|
81
|
+
label: `gather:${lane.id}`,
|
|
82
|
+
tools: EXTERNAL_TOOLS,
|
|
83
|
+
toolHints: EXTERNAL_TOOL_HINTS,
|
|
84
|
+
requireToolHints: true,
|
|
85
|
+
// The prompt embeds lane text planned by a web-enabled agent; never infer skills (and the read tool) from it.
|
|
86
|
+
skills: [],
|
|
87
|
+
profile: "small",
|
|
88
|
+
resume: "off",
|
|
89
|
+
schema: ResearchLaneResultSchema,
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
progress({ type: "counter_delta", key: "research.evidence", label: "evidence items", delta: result.evidence.length });
|
|
93
|
+
progress({
|
|
94
|
+
type: "lane_item",
|
|
95
|
+
lane: "Research lanes",
|
|
96
|
+
title: lane.title,
|
|
97
|
+
subtitle: `${result.evidence.length} evidence item(s)`,
|
|
98
|
+
status: result.evidence.length > 0 ? "success" : "warning",
|
|
99
|
+
details: result.gaps.join("; ") || lane.objective,
|
|
100
|
+
});
|
|
101
|
+
return result;
|
|
102
|
+
}),
|
|
103
|
+
{ settled: true },
|
|
107
104
|
);
|
|
105
|
+
const gathered = gatherResults.flatMap((result) => (result.ok ? [result.value] : []));
|
|
106
|
+
const failedLanes = lanes.filter((_, index) => !gatherResults[index]!.ok);
|
|
108
107
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
if (candidates.length === 0) return unavailableResearchReport("no-evidence");
|
|
108
|
+
const candidates = buildClaimCandidates(gathered);
|
|
109
|
+
if (candidates.length === 0) return unavailableResearchReport(failedLanes.length > 0 ? "lanes-failed" : "no-evidence");
|
|
112
110
|
progress({ type: "counter", key: "research.claims", label: "claims to verify", value: candidates.length });
|
|
113
111
|
log(`${candidates.length} bounded claim(s) selected for independent verification`);
|
|
114
112
|
|
|
@@ -121,11 +119,13 @@ export default async function run(api: WorkflowApi): Promise<ResearchReport> {
|
|
|
121
119
|
tools: EXTERNAL_TOOLS,
|
|
122
120
|
toolHints: EXTERNAL_TOOL_HINTS,
|
|
123
121
|
requireToolHints: true,
|
|
122
|
+
// The prompt embeds web-derived evidence; never infer skills (and the read tool) from it.
|
|
123
|
+
skills: [],
|
|
124
124
|
profile: "medium",
|
|
125
125
|
resume: "off",
|
|
126
126
|
schema: ResearchVerificationSchema,
|
|
127
127
|
});
|
|
128
|
-
return
|
|
128
|
+
return sanitizeVerification(result, candidate);
|
|
129
129
|
}),
|
|
130
130
|
);
|
|
131
131
|
const verifications = verificationResults.map((result, index) => result ?? unavailableVerification(candidates[index]!));
|
|
@@ -135,28 +135,36 @@ export default async function run(api: WorkflowApi): Promise<ResearchReport> {
|
|
|
135
135
|
const synthesisInputs = verifications.filter((verification) => verification.verdict !== "REJECTED");
|
|
136
136
|
|
|
137
137
|
phase("Synthesize");
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
`
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
138
|
+
let synthesis: ResearchReport;
|
|
139
|
+
try {
|
|
140
|
+
synthesis = await agent(
|
|
141
|
+
`Answer the research question using only the independently verified handoff below.\n\n` +
|
|
142
|
+
`Question: ${question}\nScope constraints: ${plan.scopeConstraints.join("; ") || "(none)"}\n\n` +
|
|
143
|
+
`Verified claims JSON:\n${JSON.stringify(synthesisInputs)}\n\n` +
|
|
144
|
+
"Keep SUPPORTED claims, CONFLICTED evidence, UNCERTAIN claims, and model INFERENCE in their separate fields. Exclude REJECTED claims. " +
|
|
145
|
+
"Copy each verified claim string exactly so its citations remain bound to that claim during validation. " +
|
|
146
|
+
"Every supported or conflicting claim must cite exact title/URL objects from its verification; do not add URLs, cite search-results pages, or turn inference into fact. " +
|
|
147
|
+
"Answer concisely, disclose limited coverage, and provide useful next steps. Structured output only.",
|
|
148
|
+
{
|
|
149
|
+
phase: "Synthesize",
|
|
150
|
+
label: "synthesize",
|
|
151
|
+
tools: [],
|
|
152
|
+
// The prompt embeds web-derived claims; never infer skills (and the read tool) from them.
|
|
153
|
+
skills: [],
|
|
154
|
+
profile: "medium",
|
|
155
|
+
resume: "off",
|
|
156
|
+
schema: ResearchReportSchema,
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (isFatalWorkflowError(error, signal)) throw error;
|
|
161
|
+
synthesis = fallbackResearchReport(synthesisInputs);
|
|
162
|
+
}
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
164
|
+
const report = sanitizeResearchReport(synthesis, synthesisInputs);
|
|
165
|
+
if (failedLanes.length === 0) return report;
|
|
166
|
+
const laneGap = `${failedLanes.length} of ${lanes.length} research lane(s) failed (${failedLanes.map((lane) => lane.title).join(", ")}); coverage is incomplete.`;
|
|
167
|
+
return { ...report, limitations: [...report.limitations, laneGap] };
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
function verificationPrompt(question: string, candidate: ResearchClaimCandidate): string {
|