@cjhyy/code-shell-arena 0.8.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/README.md +46 -0
- package/dist/arena.d.ts +45 -0
- package/dist/arena.js +362 -0
- package/dist/capability.d.ts +43 -0
- package/dist/capability.js +453 -0
- package/dist/context/context-tools.d.ts +16 -0
- package/dist/context/context-tools.js +272 -0
- package/dist/context/within-root.d.ts +7 -0
- package/dist/context/within-root.js +15 -0
- package/dist/detect-mode.d.ts +20 -0
- package/dist/detect-mode.js +78 -0
- package/dist/digest-builder.d.ts +25 -0
- package/dist/digest-builder.js +120 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +23 -0
- package/dist/index.runtime.d.ts +13 -0
- package/dist/index.runtime.js +11 -0
- package/dist/iterate/convergence.d.ts +25 -0
- package/dist/iterate/convergence.js +103 -0
- package/dist/iterate/formats/index.d.ts +22 -0
- package/dist/iterate/formats/index.js +283 -0
- package/dist/iterate/index.d.ts +11 -0
- package/dist/iterate/index.js +9 -0
- package/dist/iterate/iterative-arena.d.ts +31 -0
- package/dist/iterate/iterative-arena.js +252 -0
- package/dist/iterate/parse.d.ts +42 -0
- package/dist/iterate/parse.js +123 -0
- package/dist/iterate/phases/argue.d.ts +22 -0
- package/dist/iterate/phases/argue.js +165 -0
- package/dist/iterate/phases/revise.d.ts +16 -0
- package/dist/iterate/phases/revise.js +62 -0
- package/dist/iterate/phases/tournament.d.ts +34 -0
- package/dist/iterate/phases/tournament.js +113 -0
- package/dist/iterate/tools/web-tools.d.ts +13 -0
- package/dist/iterate/tools/web-tools.js +54 -0
- package/dist/iterate/types.d.ts +152 -0
- package/dist/iterate/types.js +8 -0
- package/dist/ledger.d.ts +47 -0
- package/dist/ledger.js +159 -0
- package/dist/lenses/architecture.d.ts +5 -0
- package/dist/lenses/architecture.js +22 -0
- package/dist/lenses/engineering.d.ts +5 -0
- package/dist/lenses/engineering.js +22 -0
- package/dist/lenses/general.d.ts +5 -0
- package/dist/lenses/general.js +20 -0
- package/dist/lenses/index.d.ts +16 -0
- package/dist/lenses/index.js +47 -0
- package/dist/lenses/product.d.ts +5 -0
- package/dist/lenses/product.js +22 -0
- package/dist/model-presets.d.ts +23 -0
- package/dist/model-presets.js +44 -0
- package/dist/phases/adjudication.d.ts +25 -0
- package/dist/phases/adjudication.js +142 -0
- package/dist/phases/build-consensus.d.ts +30 -0
- package/dist/phases/build-consensus.js +85 -0
- package/dist/phases/claim-registry.d.ts +26 -0
- package/dist/phases/claim-registry.js +60 -0
- package/dist/phases/cross-review.d.ts +47 -0
- package/dist/phases/cross-review.js +224 -0
- package/dist/phases/debate-rounds.d.ts +28 -0
- package/dist/phases/debate-rounds.js +161 -0
- package/dist/phases/participant-research.d.ts +39 -0
- package/dist/phases/participant-research.js +322 -0
- package/dist/phases/planning-detail-expansion.d.ts +39 -0
- package/dist/phases/planning-detail-expansion.js +120 -0
- package/dist/planner.d.ts +28 -0
- package/dist/planner.js +312 -0
- package/dist/providers/docs.d.ts +7 -0
- package/dist/providers/docs.js +111 -0
- package/dist/providers/git.d.ts +8 -0
- package/dist/providers/git.js +174 -0
- package/dist/providers/index.d.ts +32 -0
- package/dist/providers/index.js +132 -0
- package/dist/providers/none.d.ts +7 -0
- package/dist/providers/none.js +11 -0
- package/dist/providers/repo.d.ts +7 -0
- package/dist/providers/repo.js +258 -0
- package/dist/render/session.d.ts +17 -0
- package/dist/render/session.js +190 -0
- package/dist/render/terminal.d.ts +34 -0
- package/dist/render/terminal.js +286 -0
- package/dist/strategies/discussion.d.ts +25 -0
- package/dist/strategies/discussion.js +143 -0
- package/dist/strategies/index.d.ts +15 -0
- package/dist/strategies/index.js +28 -0
- package/dist/strategies/language-wrapper.d.ts +17 -0
- package/dist/strategies/language-wrapper.js +102 -0
- package/dist/strategies/lens-wrapper.d.ts +16 -0
- package/dist/strategies/lens-wrapper.js +236 -0
- package/dist/strategies/planning.d.ts +30 -0
- package/dist/strategies/planning.js +225 -0
- package/dist/strategies/review.d.ts +26 -0
- package/dist/strategies/review.js +168 -0
- package/dist/strategies/utils.d.ts +36 -0
- package/dist/strategies/utils.js +603 -0
- package/dist/tools/selector.d.ts +17 -0
- package/dist/tools/selector.js +61 -0
- package/dist/transitions.d.ts +53 -0
- package/dist/transitions.js +97 -0
- package/dist/types.d.ts +518 -0
- package/dist/types.js +27 -0
- package/package.json +45 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CrossReview / VerificationReview — participants review each other's findings.
|
|
3
|
+
*
|
|
4
|
+
* V1 (CrossReview): Each participant sees the other participants' structured findings
|
|
5
|
+
* and provides per-finding verdicts: agree, refine, disagree, needs_evidence.
|
|
6
|
+
*
|
|
7
|
+
* V2 (VerificationReview): Reviewers get claim + evidence packet + digest data,
|
|
8
|
+
* and produce ClaimChallenge records that feed into the claim state machine.
|
|
9
|
+
*/
|
|
10
|
+
import { createLLMClient } from "@cjhyy/code-shell-core/extension";
|
|
11
|
+
import { isStrategyV2, isStrategyPlanning } from "../types.js";
|
|
12
|
+
import { selectClaimsForReview } from "./claim-registry.js";
|
|
13
|
+
import { buildDigest } from "../digest-builder.js";
|
|
14
|
+
import { markUnderReview, applyReviewResult } from "../transitions.js";
|
|
15
|
+
import { logger } from "@cjhyy/code-shell-core/extension";
|
|
16
|
+
/**
|
|
17
|
+
* Run cross-review phase (V1). Each participant reviews the others' findings.
|
|
18
|
+
*/
|
|
19
|
+
export async function runCrossReview(options) {
|
|
20
|
+
const { participants, strategy, topic, reports, signal, onProgress, onUsage } = options;
|
|
21
|
+
onProgress?.({ type: "cross_review_start", round: 1 });
|
|
22
|
+
const allReviews = [];
|
|
23
|
+
const tasks = participants.map(async (p) => {
|
|
24
|
+
const myReport = reports.find((r) => r.participant === p.name);
|
|
25
|
+
const otherReports = reports.filter((r) => r.participant !== p.name);
|
|
26
|
+
// Skip if no other reports to review
|
|
27
|
+
if (otherReports.length === 0)
|
|
28
|
+
return [];
|
|
29
|
+
if (!myReport)
|
|
30
|
+
return [];
|
|
31
|
+
const client = await createLLMClient(p.llm, p.clientDefaults);
|
|
32
|
+
const systemPrompt = strategy.crossReviewSystemPrompt(p.name);
|
|
33
|
+
const userContent = strategy.crossReviewUserPrompt(topic, myReport, otherReports);
|
|
34
|
+
let response = await client.createMessage({
|
|
35
|
+
systemPrompt,
|
|
36
|
+
messages: [{ role: "user", content: userContent }],
|
|
37
|
+
signal,
|
|
38
|
+
});
|
|
39
|
+
onUsage?.(response.usage);
|
|
40
|
+
logger.info("arena.cross_review_raw_response", {
|
|
41
|
+
participant: p.name,
|
|
42
|
+
text: response.text,
|
|
43
|
+
stopReason: response.stopReason,
|
|
44
|
+
});
|
|
45
|
+
// Retry if truncated
|
|
46
|
+
if (response.stopReason === "length") {
|
|
47
|
+
logger.warn("arena.cross_review_truncated", { participant: p.name });
|
|
48
|
+
const retryResponse = await client.createMessage({
|
|
49
|
+
systemPrompt,
|
|
50
|
+
messages: [
|
|
51
|
+
{ role: "user", content: userContent },
|
|
52
|
+
{ role: "assistant", content: response.text },
|
|
53
|
+
{
|
|
54
|
+
role: "user",
|
|
55
|
+
content: "Your previous response was truncated. Please output the COMPLETE review JSON, using shorter comments. Respond ONLY with JSON.",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
signal,
|
|
59
|
+
});
|
|
60
|
+
onUsage?.(retryResponse.usage);
|
|
61
|
+
logger.info("arena.cross_review_retry", {
|
|
62
|
+
participant: p.name,
|
|
63
|
+
stopReason: retryResponse.stopReason,
|
|
64
|
+
});
|
|
65
|
+
response = retryResponse;
|
|
66
|
+
}
|
|
67
|
+
return strategy.parseCrossReviewResponse(p.name, response.text);
|
|
68
|
+
});
|
|
69
|
+
const results = await Promise.all(tasks);
|
|
70
|
+
for (const reviews of results) {
|
|
71
|
+
allReviews.push(...reviews);
|
|
72
|
+
}
|
|
73
|
+
onProgress?.({ type: "cross_review_done", reviews: allReviews });
|
|
74
|
+
return allReviews;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Run verification-review phase. Reviewers get claim + evidence + digest data.
|
|
78
|
+
* Falls back to V1 cross-review if strategy doesn't implement V2.
|
|
79
|
+
*/
|
|
80
|
+
export async function runVerificationReview(options) {
|
|
81
|
+
const { participants, strategy, topic, reports, ledger, limits, mode, signal, onProgress, onUsage } = options;
|
|
82
|
+
// Select claims to review, prioritized by severity + confidence
|
|
83
|
+
const allClaims = ledger.getAllClaims();
|
|
84
|
+
const claimsToReview = selectClaimsForReview(allClaims, limits.maxClaimsForReview);
|
|
85
|
+
// Transition selected claims to under_review
|
|
86
|
+
for (const claim of claimsToReview) {
|
|
87
|
+
markUnderReview(claim);
|
|
88
|
+
}
|
|
89
|
+
// Build digest for reviewer context
|
|
90
|
+
const relevantClaimIds = claimsToReview.map((c) => c.claimId);
|
|
91
|
+
const digest = buildDigest(ledger, { round: 1, relevantClaimIds });
|
|
92
|
+
const isPlanningMerge = mode === "planning" && isStrategyPlanning(strategy);
|
|
93
|
+
if (isPlanningMerge) {
|
|
94
|
+
onProgress?.({ type: "planning_merge_review_start" });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
onProgress?.({ type: "verification_start" });
|
|
98
|
+
}
|
|
99
|
+
const v2 = isStrategyV2(strategy);
|
|
100
|
+
const allReviews = [];
|
|
101
|
+
const allChallenges = [];
|
|
102
|
+
// Assign claims to reviewers: each claim reviewed by non-owners, capped
|
|
103
|
+
const tasks = participants.map(async (p) => {
|
|
104
|
+
const myReport = reports.find((r) => r.participant === p.name);
|
|
105
|
+
const otherReports = reports.filter((r) => r.participant !== p.name);
|
|
106
|
+
if (!myReport || otherReports.length === 0)
|
|
107
|
+
return;
|
|
108
|
+
// Claims this participant should review (not their own)
|
|
109
|
+
const myClaims = claimsToReview.filter((c) => c.owner !== p.name);
|
|
110
|
+
if (myClaims.length === 0)
|
|
111
|
+
return;
|
|
112
|
+
const client = await createLLMClient(p.llm, p.clientDefaults);
|
|
113
|
+
const systemPrompt = strategy.crossReviewSystemPrompt(p.name);
|
|
114
|
+
if (v2) {
|
|
115
|
+
// V2 path: claim-aware verification review (or merge review for planning)
|
|
116
|
+
const userContent = isPlanningMerge
|
|
117
|
+
? strategy.mergeReviewUserPrompt(topic, myReport, myClaims, digest)
|
|
118
|
+
: strategy
|
|
119
|
+
.verificationReviewUserPrompt(topic, myReport, myClaims, digest);
|
|
120
|
+
let response = await client.createMessage({
|
|
121
|
+
systemPrompt,
|
|
122
|
+
messages: [{ role: "user", content: userContent }],
|
|
123
|
+
signal,
|
|
124
|
+
});
|
|
125
|
+
onUsage?.(response.usage);
|
|
126
|
+
logger.info("arena.verification_review_raw", {
|
|
127
|
+
participant: p.name,
|
|
128
|
+
stopReason: response.stopReason,
|
|
129
|
+
});
|
|
130
|
+
if (response.stopReason === "length") {
|
|
131
|
+
const retryResponse = await client.createMessage({
|
|
132
|
+
systemPrompt,
|
|
133
|
+
messages: [
|
|
134
|
+
{ role: "user", content: userContent },
|
|
135
|
+
{ role: "assistant", content: response.text },
|
|
136
|
+
{ role: "user", content: "Truncated. Output the COMPLETE JSON array, shorter comments. JSON only." },
|
|
137
|
+
],
|
|
138
|
+
signal,
|
|
139
|
+
});
|
|
140
|
+
onUsage?.(retryResponse.usage);
|
|
141
|
+
response = retryResponse;
|
|
142
|
+
}
|
|
143
|
+
const challenges = isPlanningMerge
|
|
144
|
+
? strategy.parseMergeReviewResponse(p.name, response.text)
|
|
145
|
+
: strategy
|
|
146
|
+
.parseVerificationReviewResponse(p.name, response.text);
|
|
147
|
+
for (const challenge of challenges) {
|
|
148
|
+
allChallenges.push(challenge);
|
|
149
|
+
ledger.appendChallenge(challenge);
|
|
150
|
+
}
|
|
151
|
+
// Also produce FindingReview for backward compat
|
|
152
|
+
for (const ch of challenges) {
|
|
153
|
+
allReviews.push({
|
|
154
|
+
reviewer: ch.reviewer,
|
|
155
|
+
findingId: ch.claimId,
|
|
156
|
+
verdict: ch.verdict,
|
|
157
|
+
reason: ch.reason,
|
|
158
|
+
extraEvidence: ch.supportingEvidenceRefs,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// V1 fallback: standard cross-review, then map to challenges
|
|
164
|
+
const userContent = strategy.crossReviewUserPrompt(topic, myReport, otherReports);
|
|
165
|
+
let response = await client.createMessage({
|
|
166
|
+
systemPrompt,
|
|
167
|
+
messages: [{ role: "user", content: userContent }],
|
|
168
|
+
signal,
|
|
169
|
+
});
|
|
170
|
+
onUsage?.(response.usage);
|
|
171
|
+
if (response.stopReason === "length") {
|
|
172
|
+
const retryResponse = await client.createMessage({
|
|
173
|
+
systemPrompt,
|
|
174
|
+
messages: [
|
|
175
|
+
{ role: "user", content: userContent },
|
|
176
|
+
{ role: "assistant", content: response.text },
|
|
177
|
+
{ role: "user", content: "Truncated. Output COMPLETE review JSON. Shorter comments. JSON only." },
|
|
178
|
+
],
|
|
179
|
+
signal,
|
|
180
|
+
});
|
|
181
|
+
onUsage?.(retryResponse.usage);
|
|
182
|
+
response = retryResponse;
|
|
183
|
+
}
|
|
184
|
+
const reviews = strategy.parseCrossReviewResponse(p.name, response.text);
|
|
185
|
+
allReviews.push(...reviews);
|
|
186
|
+
// Map FindingReview → ClaimChallenge for claim state updates
|
|
187
|
+
for (const review of reviews) {
|
|
188
|
+
// Find the claim that matches this finding
|
|
189
|
+
const claim = claimsToReview.find((c) => c.finding.id === review.findingId || c.claimId === review.findingId);
|
|
190
|
+
if (claim) {
|
|
191
|
+
const challenge = {
|
|
192
|
+
reviewer: review.reviewer,
|
|
193
|
+
claimId: claim.claimId,
|
|
194
|
+
verdict: review.verdict,
|
|
195
|
+
reason: review.reason,
|
|
196
|
+
supportingEvidenceRefs: review.extraEvidence,
|
|
197
|
+
};
|
|
198
|
+
allChallenges.push(challenge);
|
|
199
|
+
ledger.appendChallenge(challenge);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
await Promise.all(tasks);
|
|
205
|
+
// Apply review results to claim status transitions
|
|
206
|
+
for (const claim of claimsToReview) {
|
|
207
|
+
const claimChallenges = ledger.getChallengesForClaim(claim.claimId);
|
|
208
|
+
const hasPendingChecks = ledger.getPendingChecksForClaim(claim.claimId).length > 0;
|
|
209
|
+
applyReviewResult(claim, claimChallenges, hasPendingChecks);
|
|
210
|
+
}
|
|
211
|
+
logger.info("arena.verification_done", {
|
|
212
|
+
reviewCount: allReviews.length,
|
|
213
|
+
challengeCount: allChallenges.length,
|
|
214
|
+
claimStatuses: Object.fromEntries(claimsToReview.map((c) => [c.claimId, c.status])),
|
|
215
|
+
});
|
|
216
|
+
if (isPlanningMerge) {
|
|
217
|
+
onProgress?.({ type: "planning_merge_review_done", mergeCount: allChallenges.length });
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
onProgress?.({ type: "verification_done", challengeCount: allChallenges.length });
|
|
221
|
+
}
|
|
222
|
+
onProgress?.({ type: "cross_review_done", reviews: allReviews });
|
|
223
|
+
return { reviews: allReviews, challenges: allChallenges };
|
|
224
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DebateRounds — contested claims enter structured debate between
|
|
3
|
+
* the claim owner and the primary challenger.
|
|
4
|
+
*
|
|
5
|
+
* Each debate round:
|
|
6
|
+
* 1. Both debaters produce a DebateTurn (stance + argument)
|
|
7
|
+
* 2. Convergence check: all stances are "support" or "narrow" → resolved
|
|
8
|
+
* 3. If max rounds exhausted → stays contested for adjudication
|
|
9
|
+
*/
|
|
10
|
+
import type { ArenaParticipant, ArenaStrategy, DebateRound, ArenaExecutionLimits, ArenaProgressEvent, ArenaUsageRecorder } from "../types.js";
|
|
11
|
+
import type { ArenaLedger } from "../ledger.js";
|
|
12
|
+
interface DebateOptions {
|
|
13
|
+
participants: ArenaParticipant[];
|
|
14
|
+
strategy: ArenaStrategy;
|
|
15
|
+
topic: string;
|
|
16
|
+
ledger: ArenaLedger;
|
|
17
|
+
limits: ArenaExecutionLimits;
|
|
18
|
+
maxRounds: number;
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
onProgress?: (event: ArenaProgressEvent) => void;
|
|
21
|
+
onUsage?: ArenaUsageRecorder;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run debate rounds for all contested claims.
|
|
25
|
+
* Returns empty array if no contested claims exist.
|
|
26
|
+
*/
|
|
27
|
+
export declare function runDebateRounds(options: DebateOptions): Promise<DebateRound[]>;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DebateRounds — contested claims enter structured debate between
|
|
3
|
+
* the claim owner and the primary challenger.
|
|
4
|
+
*
|
|
5
|
+
* Each debate round:
|
|
6
|
+
* 1. Both debaters produce a DebateTurn (stance + argument)
|
|
7
|
+
* 2. Convergence check: all stances are "support" or "narrow" → resolved
|
|
8
|
+
* 3. If max rounds exhausted → stays contested for adjudication
|
|
9
|
+
*/
|
|
10
|
+
import { createLLMClient } from "@cjhyy/code-shell-core/extension";
|
|
11
|
+
import { isStrategyV2 } from "../types.js";
|
|
12
|
+
import { buildDigest } from "../digest-builder.js";
|
|
13
|
+
import { parseDebateTurn as parseDebateTurnUtil } from "../strategies/utils.js";
|
|
14
|
+
import { logger } from "@cjhyy/code-shell-core/extension";
|
|
15
|
+
/**
|
|
16
|
+
* Run debate rounds for all contested claims.
|
|
17
|
+
* Returns empty array if no contested claims exist.
|
|
18
|
+
*/
|
|
19
|
+
export async function runDebateRounds(options) {
|
|
20
|
+
const { participants, strategy, topic, ledger, limits, maxRounds, signal, onProgress, onUsage } = options;
|
|
21
|
+
// Select contested claims, capped
|
|
22
|
+
const contested = ledger.getClaimsByStatus("contested")
|
|
23
|
+
.slice(0, limits.maxContestedClaimsForDebate);
|
|
24
|
+
if (contested.length === 0) {
|
|
25
|
+
logger.info("arena.debate_skip", { reason: "no contested claims" });
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
logger.info("arena.debate_start", {
|
|
29
|
+
contestedCount: contested.length,
|
|
30
|
+
maxRounds,
|
|
31
|
+
});
|
|
32
|
+
const allDebateRounds = [];
|
|
33
|
+
const v2 = isStrategyV2(strategy);
|
|
34
|
+
// Debate each claim sequentially (to avoid token explosion from parallelism)
|
|
35
|
+
for (const claim of contested) {
|
|
36
|
+
signal?.throwIfAborted();
|
|
37
|
+
const claimDebateRounds = await debateClaim({
|
|
38
|
+
claim,
|
|
39
|
+
participants,
|
|
40
|
+
strategy,
|
|
41
|
+
v2,
|
|
42
|
+
topic,
|
|
43
|
+
ledger,
|
|
44
|
+
maxRounds,
|
|
45
|
+
signal,
|
|
46
|
+
onProgress,
|
|
47
|
+
onUsage,
|
|
48
|
+
});
|
|
49
|
+
allDebateRounds.push(...claimDebateRounds);
|
|
50
|
+
// Attach debate rounds to claim record
|
|
51
|
+
claim.debateRounds.push(...claimDebateRounds);
|
|
52
|
+
}
|
|
53
|
+
return allDebateRounds;
|
|
54
|
+
}
|
|
55
|
+
async function debateClaim(options) {
|
|
56
|
+
const { claim, participants, strategy, v2, topic, ledger, maxRounds, signal, onProgress, onUsage } = options;
|
|
57
|
+
// Identify debaters: claim owner + primary challenger
|
|
58
|
+
const owner = participants.find((p) => p.name === claim.owner);
|
|
59
|
+
const primaryChallenger = findPrimaryChallenger(claim, participants);
|
|
60
|
+
if (!owner || !primaryChallenger) {
|
|
61
|
+
logger.warn("arena.debate_skip_claim", {
|
|
62
|
+
claimId: claim.claimId,
|
|
63
|
+
reason: "cannot identify debaters",
|
|
64
|
+
});
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const debaters = [owner, primaryChallenger];
|
|
68
|
+
const rounds = [];
|
|
69
|
+
const allTurns = [];
|
|
70
|
+
for (let round = 1; round <= maxRounds; round++) {
|
|
71
|
+
signal?.throwIfAborted();
|
|
72
|
+
onProgress?.({ type: "debate_round_start", round, claims: [claim.claimId] });
|
|
73
|
+
const digest = buildDigest(ledger, {
|
|
74
|
+
round,
|
|
75
|
+
relevantClaimIds: [claim.claimId],
|
|
76
|
+
});
|
|
77
|
+
// Each debater produces a turn
|
|
78
|
+
const turnPromises = debaters.map(async (p) => {
|
|
79
|
+
const client = await createLLMClient(p.llm, p.clientDefaults);
|
|
80
|
+
const systemPrompt = strategy.crossReviewSystemPrompt(p.name);
|
|
81
|
+
let userContent;
|
|
82
|
+
if (v2) {
|
|
83
|
+
userContent = strategy
|
|
84
|
+
.debateTurnUserPrompt(topic, claim, allTurns, digest);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Fallback prompt for non-V2 strategies
|
|
88
|
+
userContent = buildFallbackDebatePrompt(topic, claim, allTurns);
|
|
89
|
+
}
|
|
90
|
+
const response = await client.createMessage({
|
|
91
|
+
systemPrompt,
|
|
92
|
+
messages: [{ role: "user", content: userContent }],
|
|
93
|
+
signal,
|
|
94
|
+
});
|
|
95
|
+
onUsage?.(response.usage);
|
|
96
|
+
logger.info("arena.debate_turn", {
|
|
97
|
+
participant: p.name,
|
|
98
|
+
claimId: claim.claimId,
|
|
99
|
+
round,
|
|
100
|
+
stopReason: response.stopReason,
|
|
101
|
+
});
|
|
102
|
+
if (v2) {
|
|
103
|
+
return strategy.parseDebateTurnResponse(p.name, response.text);
|
|
104
|
+
}
|
|
105
|
+
return parseDebateTurnUtil(p.name, response.text);
|
|
106
|
+
});
|
|
107
|
+
const turns = await Promise.all(turnPromises);
|
|
108
|
+
allTurns.push(...turns);
|
|
109
|
+
// Check convergence
|
|
110
|
+
const resolved = turns.every((t) => t.stance === "support" || t.stance === "narrow");
|
|
111
|
+
const resolutionNote = resolved
|
|
112
|
+
? `Converged in round ${round}: ${turns.map((t) => `${t.participant}=${t.stance}`).join(", ")}`
|
|
113
|
+
: undefined;
|
|
114
|
+
const debateRound = {
|
|
115
|
+
round,
|
|
116
|
+
claimId: claim.claimId,
|
|
117
|
+
participants: turns,
|
|
118
|
+
resolved,
|
|
119
|
+
resolutionNote,
|
|
120
|
+
};
|
|
121
|
+
rounds.push(debateRound);
|
|
122
|
+
onProgress?.({ type: "debate_round_done", round, resolved: resolved ? 1 : 0 });
|
|
123
|
+
if (resolved) {
|
|
124
|
+
logger.info("arena.debate_converged", {
|
|
125
|
+
claimId: claim.claimId,
|
|
126
|
+
round,
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return rounds;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Find the primary challenger — the participant who disagreed or requested evidence.
|
|
135
|
+
*/
|
|
136
|
+
function findPrimaryChallenger(claim, participants) {
|
|
137
|
+
// Prefer disagree over needs_evidence
|
|
138
|
+
const disagreeChallenge = claim.challenges.find((c) => c.verdict === "disagree");
|
|
139
|
+
const needsEvidenceChallenge = claim.challenges.find((c) => c.verdict === "needs_evidence");
|
|
140
|
+
const challenge = disagreeChallenge ?? needsEvidenceChallenge ?? claim.challenges[0];
|
|
141
|
+
if (!challenge)
|
|
142
|
+
return undefined;
|
|
143
|
+
return participants.find((p) => p.name === challenge.reviewer);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Build a fallback debate prompt for non-V2 strategies.
|
|
147
|
+
*/
|
|
148
|
+
function buildFallbackDebatePrompt(topic, claim, priorTurns) {
|
|
149
|
+
const turnsText = priorTurns.length > 0
|
|
150
|
+
? priorTurns.map((t) => `[${t.participant}] ${t.stance}: ${t.summary}`).join("\n")
|
|
151
|
+
: "No prior turns.";
|
|
152
|
+
return (`## Topic: ${topic}\n\n` +
|
|
153
|
+
`## Contested Claim\n` +
|
|
154
|
+
`[${claim.claimId}] ${claim.finding.title}\n${claim.finding.summary}\n\n` +
|
|
155
|
+
`## Prior Debate\n${turnsText}\n\n` +
|
|
156
|
+
`State your position on this claim. Build on prior turns: cite specific evidence, ` +
|
|
157
|
+
`acknowledge counter-arguments, and explain your reasoning in 150-300 words. ` +
|
|
158
|
+
`Brevity here means missed nuance — depth wins.\n` +
|
|
159
|
+
`Respond ONLY with JSON:\n` +
|
|
160
|
+
`{"stance": "support|oppose|narrow|uncertain", "summary": "your argument", "newEvidenceRefs": ["optional"]}`);
|
|
161
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ParticipantResearch — each participant independently investigates
|
|
3
|
+
* the shared base context and produces structured findings.
|
|
4
|
+
*
|
|
5
|
+
* Phase 3 upgrade: now captures ToolTrace and builds EvidencePackets,
|
|
6
|
+
* outputting a full ResearchDossier alongside the ParticipantReport.
|
|
7
|
+
*/
|
|
8
|
+
import type { ArenaBaseContext, ArenaParticipant, ArenaStrategy, ParticipantReport, ResearchDossier, ArenaProgressEvent, ArenaUsageRecorder } from "../types.js";
|
|
9
|
+
import type { ToolDefinition } from "@cjhyy/code-shell-core/extension";
|
|
10
|
+
/** Result of a single participant's research — includes both report and dossier */
|
|
11
|
+
export interface ResearchResult {
|
|
12
|
+
report: ParticipantReport;
|
|
13
|
+
dossier: ResearchDossier;
|
|
14
|
+
}
|
|
15
|
+
interface ResearchOptions {
|
|
16
|
+
participants: ArenaParticipant[];
|
|
17
|
+
strategy: ArenaStrategy;
|
|
18
|
+
topic: string;
|
|
19
|
+
baseContext: ArenaBaseContext;
|
|
20
|
+
enableContextTools?: boolean;
|
|
21
|
+
/** Plan-selected tools override. When provided, these are used instead of all CONTEXT_TOOLS. */
|
|
22
|
+
contextTools?: ToolDefinition[];
|
|
23
|
+
/** AbortSignal — cancels in-flight LLM calls */
|
|
24
|
+
signal?: AbortSignal;
|
|
25
|
+
onProgress?: (event: ArenaProgressEvent) => void;
|
|
26
|
+
onUsage?: ArenaUsageRecorder;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Run participant research phase in parallel.
|
|
30
|
+
* Each participant reads the shared context, optionally requests more,
|
|
31
|
+
* then produces structured findings with evidence trails.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runParticipantResearch(options: ResearchOptions): Promise<ParticipantReport[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Run participant research phase with full dossier output.
|
|
36
|
+
* Returns both reports (backward compat) and dossiers (evidence trail).
|
|
37
|
+
*/
|
|
38
|
+
export declare function runParticipantResearchWithDossiers(options: ResearchOptions): Promise<ResearchResult[]>;
|
|
39
|
+
export {};
|