alphacouncil-agent 0.9.2 → 0.9.4
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/.claude/commands/alpha.md +22 -3
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.grok/commands/alpha.md +22 -3
- package/.opencode/command/alpha.md +22 -3
- package/AGENTS.md +27 -4
- package/CHANGELOG.md +67 -0
- package/CLAUDE.md +41 -7
- package/README.ja.md +21 -11
- package/README.md +35 -11
- package/README.zh-CN.md +31 -11
- package/commands/alpha.md +22 -3
- package/data/build-profile.v1.json +1 -1
- package/data/master-selector-method-locales.v1.mjs +286 -0
- package/docs/INSTALL.md +43 -6
- package/docs/releases/v0.9.3.md +86 -0
- package/docs/releases/v0.9.4.md +79 -0
- package/docs/report-contract.md +50 -1
- package/knowledge/ai-assisted-solo/experiments/runs/a.json +4 -4
- package/knowledge/ai-assisted-solo/experiments/runs/b.json +2 -2
- package/knowledge/ai-assisted-solo/experiments/runs/c.json +2 -2
- package/knowledge/ai-assisted-solo/experiments/runs/d13.json +16 -16
- package/knowledge/ai-assisted-solo/experiments/runs/d26.json +29 -29
- package/knowledge/ai-assisted-solo/experiments/runs/e-d13.json +19 -19
- package/knowledge/ai-assisted-solo/experiments/runs/e-d26.json +32 -32
- package/knowledge/ai-assisted-solo/experiments/runs/h_ai_reference.json +33 -33
- package/knowledge/ai-assisted-solo/experiments/simulation-input.json +79 -79
- package/knowledge/ai-assisted-solo/experiments/simulation-manifest.json +19 -19
- package/knowledge/solo-test/masters/master_ackman/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_aschenbrenner/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_asness/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_buffett/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_burry/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_cathie_wood/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_dalio/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_damodaran/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_druckenmiller/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_duan_yongping/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_fisher/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_forensic_short/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_graham/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_jhunjhunwala/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_klarman/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_li_lu/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_lynch/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_marks/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_munger/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_natenberg/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_pabrai/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_simons/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_sinclair/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_soros/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_taleb/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_thorp/manifest.json +14 -4
- package/mcp/lib/codex.mjs +3 -3
- package/mcp/lib/constants.mjs +61 -25
- package/mcp/lib/council-options.mjs +95 -40
- package/mcp/lib/council-selection.mjs +95 -9
- package/mcp/lib/gates.mjs +78 -11
- package/mcp/lib/lang.mjs +109 -2
- package/mcp/lib/markdown.mjs +444 -135
- package/mcp/lib/master-catalog.mjs +28 -3
- package/mcp/lib/orchestrator.mjs +952 -265
- package/mcp/lib/packets.mjs +173 -20
- package/mcp/lib/personas/engine.mjs +30 -30
- package/mcp/lib/personas-v3/compiler.mjs +2 -0
- package/mcp/lib/personas-v3/loader.mjs +10 -7
- package/mcp/lib/personas-v3/source-acquisition.mjs +10 -3
- package/mcp/lib/prompts.mjs +33 -0
- package/mcp/lib/rpc.mjs +75 -33
- package/mcp/lib/run-store.mjs +23 -0
- package/package.json +2 -2
- package/schemas/persona-v3.schema.json +37 -2
- package/scripts/lib/packaged-host-parity.mjs +54 -5
- package/scripts/lib/persona-v3-solo-test-packs.mjs +31 -5
- package/scripts/package-smoke.mjs +31 -12
- package/scripts/run-tests.mjs +23 -2
- package/skills/alphacouncil-agent/SKILL.md +50 -7
package/mcp/lib/packets.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { LIMITS, MASTER_STANCES, RATINGS } from "./constants.mjs";
|
|
3
3
|
import { internalError } from "./errors.mjs";
|
|
4
|
-
import { isChineseLanguage } from "./lang.mjs";
|
|
4
|
+
import { isChineseLanguage, languageKey, localized } from "./lang.mjs";
|
|
5
5
|
import { cleanLog, clip } from "./text.mjs";
|
|
6
6
|
import { scopedSourceId } from "./gates.mjs";
|
|
7
7
|
import { runPath } from "./run-store.mjs";
|
|
@@ -85,6 +85,7 @@ export function normalizeDebate(packet, role, run, raw = "") {
|
|
|
85
85
|
questions_answered: Array.isArray(packet?.questions_answered) ? packet.questions_answered : [],
|
|
86
86
|
debate_rounds: Array.isArray(packet?.debate_rounds) ? packet.debate_rounds : [],
|
|
87
87
|
report_markdown: typeof packet?.report_markdown === "string" ? packet.report_markdown : "",
|
|
88
|
+
failure_kind: typeof packet?.failure_kind === "string" ? packet.failure_kind : undefined,
|
|
88
89
|
thread_id: typeof packet?.thread_id === "string" ? packet.thread_id : undefined,
|
|
89
90
|
thread_title: typeof packet?.thread_title === "string" ? packet.thread_title : undefined,
|
|
90
91
|
execution_mode: typeof packet?.execution_mode === "string" ? packet.execution_mode : undefined,
|
|
@@ -92,6 +93,57 @@ export function normalizeDebate(packet, role, run, raw = "") {
|
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
export function debateFailurePacket(role, run, failureKind) {
|
|
97
|
+
const kind = ["global_deadline", "timeout", "exit", "parse_failed", "reader_language_mismatch", "unexpected_error"]
|
|
98
|
+
.includes(failureKind)
|
|
99
|
+
? failureKind
|
|
100
|
+
: "unexpected_error";
|
|
101
|
+
const copy = localized(run.language, {
|
|
102
|
+
en: {
|
|
103
|
+
global_deadline: `${role} did not complete before the council's global deadline.`,
|
|
104
|
+
timeout: `${role} timed out and produced no usable debate statement.`,
|
|
105
|
+
exit: `${role} exited unsuccessfully and produced no usable debate statement.`,
|
|
106
|
+
parse_failed: `${role} returned output that violated the debate JSON contract.`,
|
|
107
|
+
reader_language_mismatch: `${role} returned reader-facing content in the wrong language.`,
|
|
108
|
+
unexpected_error: `${role} failed unexpectedly and produced no usable debate statement.`,
|
|
109
|
+
},
|
|
110
|
+
zh: {
|
|
111
|
+
global_deadline: `${role} 未能在委员会全局截止时间前完成。`,
|
|
112
|
+
timeout: `${role} 执行超时,未生成可用的辩论发言。`,
|
|
113
|
+
exit: `${role} 异常退出,未生成可用的辩论发言。`,
|
|
114
|
+
parse_failed: `${role} 的输出违反辩论 JSON 契约。`,
|
|
115
|
+
reader_language_mismatch: `${role} 返回了错误语言的读者内容。`,
|
|
116
|
+
unexpected_error: `${role} 意外失败,未生成可用的辩论发言。`,
|
|
117
|
+
},
|
|
118
|
+
ja: {
|
|
119
|
+
global_deadline: `${role} は委員会全体の期限までに完了しませんでした。`,
|
|
120
|
+
timeout: `${role} はタイムアウトし、利用可能な討論発言を生成しませんでした。`,
|
|
121
|
+
exit: `${role} は異常終了し、利用可能な討論発言を生成しませんでした。`,
|
|
122
|
+
parse_failed: `${role} の出力は討論 JSON 契約に違反しています。`,
|
|
123
|
+
reader_language_mismatch: `${role} は指定と異なる言語の読者向け内容を返しました。`,
|
|
124
|
+
unexpected_error: `${role} は予期せず失敗し、利用可能な討論発言を生成しませんでした。`,
|
|
125
|
+
},
|
|
126
|
+
ko: {
|
|
127
|
+
global_deadline: `${role}이 위원회 전체 마감 시간 전에 완료되지 않았습니다.`,
|
|
128
|
+
timeout: `${role}이 시간 초과되어 사용할 수 있는 토론 발언을 생성하지 못했습니다.`,
|
|
129
|
+
exit: `${role}이 비정상 종료되어 사용할 수 있는 토론 발언을 생성하지 못했습니다.`,
|
|
130
|
+
parse_failed: `${role}의 출력이 토론 JSON 계약을 위반했습니다.`,
|
|
131
|
+
reader_language_mismatch: `${role}이 지정과 다른 언어의 독자용 내용을 반환했습니다.`,
|
|
132
|
+
unexpected_error: `${role}이 예기치 않게 실패해 사용할 수 있는 토론 발언을 생성하지 못했습니다.`,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
return normalizeDebate({
|
|
136
|
+
verdict: "FAILED",
|
|
137
|
+
decision_available: false,
|
|
138
|
+
rating: null,
|
|
139
|
+
winner: "unknown",
|
|
140
|
+
summary: copy[kind],
|
|
141
|
+
confidence: "low",
|
|
142
|
+
report_markdown: "",
|
|
143
|
+
failure_kind: kind,
|
|
144
|
+
}, role, run, "");
|
|
145
|
+
}
|
|
146
|
+
|
|
95
147
|
export function dryPacket(task, symbol, asOfDate, prompt, language = "English") {
|
|
96
148
|
const chinese = isChineseLanguage(language);
|
|
97
149
|
return normalizePacket({
|
|
@@ -124,15 +176,41 @@ export function compactEvidence(run) {
|
|
|
124
176
|
run_id: run.run_id,
|
|
125
177
|
symbol: run.symbol,
|
|
126
178
|
as_of: run.as_of,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
179
|
+
context_contract: "bounded_full_v1",
|
|
180
|
+
packets: (run.packets || []).map((packet) => {
|
|
181
|
+
const claims = (packet.claims || []).slice(0, 8);
|
|
182
|
+
const sourceById = new Map((packet.sources || []).map((source) => [source?.id, source]));
|
|
183
|
+
const referenced = [...new Set(claims.flatMap((claim) => claim?.source_ids || []))];
|
|
184
|
+
const selectedIds = referenced.filter((id) => sourceById.has(id)).slice(0, 12);
|
|
185
|
+
for (const source of packet.sources || []) {
|
|
186
|
+
if (selectedIds.length >= 12) break;
|
|
187
|
+
if (source?.id && !selectedIds.includes(source.id)) selectedIds.push(source.id);
|
|
188
|
+
}
|
|
189
|
+
const included = new Set(selectedIds);
|
|
190
|
+
return {
|
|
191
|
+
task: packet.task,
|
|
192
|
+
summary: clip(packet.summary || "", 1_800),
|
|
193
|
+
claims: claims.map((claim) => ({
|
|
194
|
+
claim: clip(claim?.claim || "", 700),
|
|
195
|
+
evidence: clip(claim?.evidence || "", 700),
|
|
196
|
+
confidence: claim?.confidence || "low",
|
|
197
|
+
source_ids: (claim?.source_ids || []).filter((id) => included.has(id)),
|
|
198
|
+
})),
|
|
199
|
+
metrics: compactValue(packet.metrics || {}),
|
|
200
|
+
sources: selectedIds.map((id) => sourceById.get(id)).filter(Boolean).map((source) => ({
|
|
201
|
+
id: source?.id,
|
|
202
|
+
title: clip(source?.title || "", 260),
|
|
203
|
+
url: source?.url || "",
|
|
204
|
+
published_at: source?.published_at || "unknown",
|
|
205
|
+
retrieved_at: source?.retrieved_at || run.as_of,
|
|
206
|
+
})),
|
|
207
|
+
omitted_claim_count: Math.max(0, (packet.claims || []).length - claims.length),
|
|
208
|
+
omitted_source_count: Math.max(0, (packet.sources || []).length - selectedIds.length),
|
|
209
|
+
open_questions: (packet.open_questions || []).slice(0, 8).map((item) => clip(item, 650)),
|
|
210
|
+
confidence: packet.confidence,
|
|
211
|
+
information_richness: packet.information_richness,
|
|
212
|
+
};
|
|
213
|
+
}),
|
|
136
214
|
};
|
|
137
215
|
}
|
|
138
216
|
|
|
@@ -239,21 +317,53 @@ export function compactDebateContext(packet) {
|
|
|
239
317
|
}
|
|
240
318
|
|
|
241
319
|
export function debateFromCodex(result, role, run, fallbackPrompt) {
|
|
242
|
-
if (!result.ok)
|
|
320
|
+
if (!result.ok) {
|
|
321
|
+
const failureKind = result.deadline_exhausted
|
|
322
|
+
? "global_deadline"
|
|
323
|
+
: result.timedOut
|
|
324
|
+
? "timeout"
|
|
325
|
+
: Number.isInteger(result.code)
|
|
326
|
+
? "exit"
|
|
327
|
+
: "unexpected_error";
|
|
328
|
+
return debateFailurePacket(role, run, failureKind);
|
|
329
|
+
}
|
|
243
330
|
try {
|
|
244
331
|
return normalizeDebate(extractJson(result.text), role, run, result.text);
|
|
245
|
-
} catch {
|
|
246
|
-
return
|
|
247
|
-
verdict: "PARSE_FAILED",
|
|
248
|
-
rating: "Hold",
|
|
249
|
-
winner: "unknown",
|
|
250
|
-
summary: `${role} returned non-JSON output.`,
|
|
251
|
-
confidence: "low",
|
|
252
|
-
report_markdown: cleanLog(result.text),
|
|
253
|
-
}, role, run, cleanLog(result.text));
|
|
332
|
+
} catch (error) {
|
|
333
|
+
return debateFailurePacket(role, run, "parse_failed");
|
|
254
334
|
}
|
|
255
335
|
}
|
|
256
336
|
|
|
337
|
+
function asianManagerFallback(run, summary) {
|
|
338
|
+
const key = languageKey(run.language);
|
|
339
|
+
if (!new Set(["ja", "ko"]).has(key)) return null;
|
|
340
|
+
const c = localized(run.language, {
|
|
341
|
+
ja: {
|
|
342
|
+
title: "投資委員会ドラフト", conclusion: "結論", analyst: "アナリスト作業記録", debate: "強気・弱気討論記録", masters: "メソッド席", long: "強気論点", short: "弱気論点", market: "市場期待と織り込み条件", rating: "アナリスト評価と目標株価の変更", call: "決算説明会の経営シグナル", quant: "定量・ファクター視点", news: "ニュースと企業・業界シグナル", borrow: "空売り・貸株・オプション情報", transaction: "戦略取引・銀行イベント", valuation: "企業価値評価レンジ", price: "価格条件", catalysts: "主要カタリスト", risks: "主要リスク", position: "ポジション提案", shortTerm: "短期1–4週間の見通し", mediumTerm: "中期3–6か月の見通し", longTerm: "長期12か月の見通し", gaps: "データ欠落・利用不可データ", invalidation: "無効化条件", confidence: "信頼度", sources: "出典表",
|
|
343
|
+
unavailable: "今回の実行では同じ言語で確認できる情報を取得できませんでした。", managerMissing: "portfolio_manager の統合が完了していないため、正式な投資判断はありません。", draftOnly: "この文書はドラフトであり、正式なポジションを示しません。", noPackets: "証拠パケットは生成されませんでした。", noMaster: "完了したメソッド席はありません。", keyFindings: "主要所見", dataGaps: "データ欠落", packetSummary: "要約", packetConfidence: "信頼度", sourceCount: "出典数",
|
|
344
|
+
},
|
|
345
|
+
ko: {
|
|
346
|
+
title: "투자위원회 초안", conclusion: "결론", analyst: "분석가 작업 기록", debate: "강세·약세 토론 기록", masters: "방법론 좌석", long: "강세 논거", short: "약세 논거", market: "시장 기대와 내재 조건", rating: "애널리스트 등급 및 목표가 변경", call: "실적 발표 콜 경영진 신호", quant: "정량·팩터 관점", news: "뉴스 및 기업·산업 신호", borrow: "공매도·대차·옵션 정보", transaction: "전략적 거래·금융 이벤트", valuation: "가치평가 범위", price: "가격 조건", catalysts: "핵심 촉매", risks: "주요 위험", position: "포지션 제안", shortTerm: "단기 1–4주 전망", mediumTerm: "중기 3–6개월 전망", longTerm: "장기 12개월 전망", gaps: "데이터 공백·사용 불가 데이터", invalidation: "무효화 조건", confidence: "신뢰도", sources: "출처 표",
|
|
347
|
+
unavailable: "이번 실행에서는 같은 언어로 확인 가능한 정보를 확보하지 못했습니다.", managerMissing: "portfolio_manager 종합이 완료되지 않아 공식 투자 판단을 제공할 수 없습니다.", draftOnly: "이 문서는 초안이며 공식 포지션을 제시하지 않습니다.", noPackets: "증거 패킷이 생성되지 않았습니다.", noMaster: "완료된 방법론 좌석이 없습니다.", keyFindings: "핵심 발견", dataGaps: "데이터 공백", packetSummary: "요약", packetConfidence: "신뢰도", sourceCount: "출처 수",
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
const analystLog = run.packets.length
|
|
351
|
+
? run.packets.map((packet) => {
|
|
352
|
+
const claims = (packet.claims || []).slice(0, 5).map((claim) => ` - ${claim.claim}`).join("\n");
|
|
353
|
+
const gaps = (packet.open_questions || []).slice(0, 3).map((item) => ` - ${item}`).join("\n");
|
|
354
|
+
return `### ${packet.task}\n- ${c.packetConfidence}: ${packet.confidence || "unknown"}\n- ${c.packetSummary}: ${packet.summary || c.unavailable}\n${claims ? `- ${c.keyFindings}:\n${claims}\n` : ""}${gaps ? `- ${c.dataGaps}:\n${gaps}\n` : ""}`;
|
|
355
|
+
}).join("\n\n")
|
|
356
|
+
: c.noPackets;
|
|
357
|
+
const masterLog = (run.master_opinions || []).length
|
|
358
|
+
? run.master_opinions.map((opinion) => `- ${opinion.master}: ${opinion.stance} - ${opinion.summary || opinion.verdict || c.unavailable}`).join("\n")
|
|
359
|
+
: `- ${c.noMaster}`;
|
|
360
|
+
const long = summary.thesis.filter((claim) => claim.confidence !== "low").slice(0, 6).map((claim) => `- ${claim.claim}`).join("\n") || `- ${c.unavailable}`;
|
|
361
|
+
const short = summary.open_questions.slice(0, 6).map((item) => `- ${item}`).join("\n") || `- ${c.unavailable}`;
|
|
362
|
+
const gaps = summary.open_questions.length ? summary.open_questions.map((item) => `- ${item}`).join("\n") : `- ${c.unavailable}`;
|
|
363
|
+
const report = `# ${run.symbol} ${c.title}\n\n## ${c.conclusion}\n${summary.final_decision} — ${c.managerMissing}\n\n## ${c.analyst}\n${analystLog}\n\n## ${c.debate}\n${c.managerMissing}\n\n## ${c.masters}\n${masterLog}\n\n## ${c.long}\n${long}\n\n## ${c.short}\n${short}\n\n## ${c.market}\n${c.unavailable}\n\n## ${c.rating}\n${c.unavailable}\n\n## ${c.call}\n${c.unavailable}\n\n## ${c.quant}\n${c.unavailable}\n\n## ${c.news}\n${c.unavailable}\n\n## ${c.borrow}\n${c.unavailable}\n\n## ${c.transaction}\n${c.unavailable}\n\n## ${c.valuation}\n${c.unavailable}\n\n## ${c.price}\n${c.managerMissing} ${c.unavailable}\n\n## ${c.catalysts}\n${c.managerMissing}\n\n## ${c.risks}\n${short}\n\n## ${c.position}\n${c.draftOnly}\n\n## ${c.shortTerm}\n${c.managerMissing}\n\n## ${c.mediumTerm}\n${c.managerMissing}\n\n## ${c.longTerm}\n${c.managerMissing}\n\n## ${c.gaps}\n${gaps}\n\n## ${c.invalidation}\n${c.managerMissing} ${c.draftOnly}\n\n## ${c.confidence}\n${summary.confidence}\n\n## ${c.sources}\n- ${c.sourceCount}: ${summary.source_count}\n`;
|
|
364
|
+
return { copy: c, analystLog, masterLog, report };
|
|
365
|
+
}
|
|
366
|
+
|
|
257
367
|
export function mergeDebateRounds(rounds) {
|
|
258
368
|
const list = (rounds || []).filter(Boolean);
|
|
259
369
|
if (list.length === 0) return null;
|
|
@@ -347,6 +457,20 @@ export function summarizeRun(run, userPrompt = "") {
|
|
|
347
457
|
|
|
348
458
|
export function managerFallback(run, userPrompt = "") {
|
|
349
459
|
const summary = summarizeRun(run, userPrompt);
|
|
460
|
+
const asian = asianManagerFallback(run, summary);
|
|
461
|
+
if (asian) {
|
|
462
|
+
return normalizeDebate({
|
|
463
|
+
verdict: summary.final_decision,
|
|
464
|
+
decision_available: false,
|
|
465
|
+
rating: null,
|
|
466
|
+
winner: "unknown",
|
|
467
|
+
summary: asian.copy.managerMissing,
|
|
468
|
+
long_thesis: summary.thesis.filter((claim) => claim.confidence !== "low").slice(0, 6).map((claim) => claim.claim),
|
|
469
|
+
short_thesis: summary.open_questions.slice(0, 6),
|
|
470
|
+
confidence: summary.confidence,
|
|
471
|
+
report_markdown: asian.report,
|
|
472
|
+
}, "portfolio_manager", run);
|
|
473
|
+
}
|
|
350
474
|
const chinese = isChineseLanguage(run.language);
|
|
351
475
|
const analystLog = run.packets.length
|
|
352
476
|
? run.packets.map((packet) => {
|
|
@@ -442,6 +566,35 @@ export function normalizeMasterOpinion(packet, masterId, run, raw = "") {
|
|
|
442
566
|
};
|
|
443
567
|
}
|
|
444
568
|
|
|
569
|
+
/**
|
|
570
|
+
* A dedicated v3 method worker explains an already frozen decision; it never gets to
|
|
571
|
+
* choose or rewrite the stance. Keeping this packet separate from normalizeMasterOpinion
|
|
572
|
+
* makes a persuasive narrative incapable of overriding deterministic policy output.
|
|
573
|
+
*/
|
|
574
|
+
export function normalizeMasterVoice(packet, masterId, run, frozenOpinion, raw = "") {
|
|
575
|
+
const list = (value) => (Array.isArray(value) ? value.filter((x) => typeof x === "string" && x.trim()) : []);
|
|
576
|
+
if (packet?.master !== masterId) throw internalError(`dedicated method worker returned the wrong master id for ${masterId}`);
|
|
577
|
+
if (packet?.acknowledged_stance !== frozenOpinion?.stance) {
|
|
578
|
+
throw internalError(`dedicated method worker attempted to change frozen stance for ${masterId}`);
|
|
579
|
+
}
|
|
580
|
+
const statement = typeof packet?.statement === "string" ? packet.statement.trim() : "";
|
|
581
|
+
if (!statement) throw internalError(`dedicated method worker returned no statement for ${masterId}`);
|
|
582
|
+
return {
|
|
583
|
+
master: masterId,
|
|
584
|
+
symbol: run.symbol,
|
|
585
|
+
as_of: run.as_of,
|
|
586
|
+
acknowledged_stance: frozenOpinion.stance,
|
|
587
|
+
statement,
|
|
588
|
+
key_findings: list(packet?.key_findings),
|
|
589
|
+
disagreements: list(packet?.disagreements),
|
|
590
|
+
what_would_change_my_mind: list(packet?.what_would_change_my_mind),
|
|
591
|
+
source_ids: list(packet?.source_ids),
|
|
592
|
+
confidence: ["high", "medium", "low"].includes(packet?.confidence) ? packet.confidence : frozenOpinion.confidence || "low",
|
|
593
|
+
language: run.language,
|
|
594
|
+
raw_text: raw,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
445
598
|
/** Compact master opinions for injection into the debate prompt. */
|
|
446
599
|
export function compactMasterOpinions(run) {
|
|
447
600
|
return (run.master_opinions || []).map((opinion) => ({
|
|
@@ -12,7 +12,7 @@ import { buildFactPack } from "../personas-v3/typed-facts.mjs";
|
|
|
12
12
|
import { typedFactPackFromGrounding } from "../personas-v3/grounding-adapter.mjs";
|
|
13
13
|
import { buildAnonymousPreDecision, freezeAnonymousDecision } from "../personas-v3/runtime.mjs";
|
|
14
14
|
import { executeDeterministicPersonaPolicy } from "../personas-v3/deterministic-executor.mjs";
|
|
15
|
-
import {
|
|
15
|
+
import { languageKey, localized } from "../lang.mjs";
|
|
16
16
|
import {
|
|
17
17
|
declinedOpinion as v2DeclinedOpinion,
|
|
18
18
|
planMasters as planLegacyMasters,
|
|
@@ -190,9 +190,15 @@ export function completedMasterOpinion(run, item) {
|
|
|
190
190
|
throw new Error("completedMasterOpinion requires a completed v3 deterministic seat");
|
|
191
191
|
}
|
|
192
192
|
const result = item.frozenDecision.structured_decision.result;
|
|
193
|
-
const
|
|
194
|
-
const label = item.pack.admitted_label?.[
|
|
193
|
+
const locale = languageKey(run.language);
|
|
194
|
+
const label = item.pack.admitted_label?.[locale]
|
|
195
195
|
|| item.pack.admitted_label?.en || item.id;
|
|
196
|
+
const copy = localized(run.language, {
|
|
197
|
+
en: { withheld: (pct) => `${pct}% coverage; score withheld`, unscored: "not scored", verdict: (stance, reason) => `${label} frozen decision: ${stance} (${reason})`, summary: (score) => `The PersonaPack v3 deterministic policy executed with score ${score}. Typed facts, hard vetoes, and score bands produced this stance; no language model selected it.`, hit: (id) => `score hit: ${id}`, miss: (id) => `score miss: ${id}`, eligibility: (id) => `eligibility condition ${id} becomes satisfied`, veto: (id) => `hard veto ${id} no longer triggers`, score: (id) => `score condition ${id} becomes satisfied` },
|
|
198
|
+
zh: { withheld: (pct) => `覆盖率 ${pct}%,分数被保留不发布`, unscored: "未评分", verdict: (stance, reason) => `${label}的冻结结论:${stance}(${reason})`, summary: (score) => `PersonaPack v3 确定性政策已执行;得分 ${score}。该立场由结构化事实、硬否决和评分带产生,没有让语言模型选择立场。`, hit: (id) => `评分命中:${id}`, miss: (id) => `评分未命中:${id}`, eligibility: (id) => `资格条件 ${id} 变为满足`, veto: (id) => `硬否决 ${id} 不再触发`, score: (id) => `评分条件 ${id} 变为满足` },
|
|
199
|
+
ja: { withheld: (pct) => `カバレッジ ${pct}% のためスコアは非公開`, unscored: "未採点", verdict: (stance, reason) => `${label}の凍結済み判断:${stance}(${reason})`, summary: (score) => `PersonaPack v3 の決定論的ポリシーを実行し、スコアは ${score}。構造化事実、ハード拒否条件、スコア帯がこの立場を生成しており、言語モデルは立場を選択していない。`, hit: (id) => `採点条件を満たす:${id}`, miss: (id) => `採点条件を満たさない:${id}`, eligibility: (id) => `適格条件 ${id} が満たされる`, veto: (id) => `ハード拒否条件 ${id} が解除される`, score: (id) => `採点条件 ${id} が満たされる` },
|
|
200
|
+
ko: { withheld: (pct) => `커버리지 ${pct}%로 점수를 공개하지 않음`, unscored: "미채점", verdict: (stance, reason) => `${label}의 동결된 판단: ${stance}(${reason})`, summary: (score) => `PersonaPack v3 결정론적 정책을 실행했으며 점수는 ${score}입니다. 구조화된 사실, 하드 거부 조건, 점수 구간이 이 입장을 만들었고 언어 모델은 입장을 선택하지 않았습니다.`, hit: (id) => `점수 조건 충족: ${id}`, miss: (id) => `점수 조건 미충족: ${id}`, eligibility: (id) => `적격 조건 ${id} 충족`, veto: (id) => `하드 거부 조건 ${id} 해제`, score: (id) => `점수 조건 ${id} 충족` },
|
|
201
|
+
});
|
|
196
202
|
const vetoIds = (result.vetoes_triggered || []).map((veto) => veto.veto_id);
|
|
197
203
|
const unmet = result.eligibility?.unmet_condition_ids || [];
|
|
198
204
|
const hitIds = (result.score?.hits || []).map((rule) => rule.rule_id);
|
|
@@ -204,33 +210,27 @@ export function completedMasterOpinion(run, item) {
|
|
|
204
210
|
...(result.score?.misses || []).flatMap((rule) => rule.source_ids || []),
|
|
205
211
|
]);
|
|
206
212
|
const scoreText = result.score?.status === "insufficient_coverage"
|
|
207
|
-
?
|
|
208
|
-
? `覆盖率 ${Math.round(result.score.coverage * 100)}%,分数被保留不发布`
|
|
209
|
-
: `${Math.round(result.score.coverage * 100)}% coverage; score withheld`
|
|
213
|
+
? copy.withheld(Math.round(result.score.coverage * 100))
|
|
210
214
|
: result.score
|
|
211
215
|
? `${result.score.score}/${result.score.max_possible} (${Math.round((result.ratio || 0) * 100)}%)`
|
|
212
|
-
:
|
|
216
|
+
: copy.unscored;
|
|
213
217
|
return {
|
|
214
218
|
master: item.id,
|
|
215
219
|
symbol: run.symbol,
|
|
216
220
|
as_of: run.as_of,
|
|
217
221
|
stance: result.stance,
|
|
218
|
-
verdict:
|
|
219
|
-
|
|
220
|
-
: `${label} frozen decision: ${result.stance} (${result.reason})`,
|
|
221
|
-
summary: zh
|
|
222
|
-
? `PersonaPack v3 确定性政策已执行;得分 ${scoreText}。该立场由结构化事实、硬否决和评分带产生,没有让语言模型选择立场。`
|
|
223
|
-
: `The PersonaPack v3 deterministic policy executed with score ${scoreText}. Typed facts, hard vetoes, and score bands produced this stance; no language model selected it.`,
|
|
222
|
+
verdict: copy.verdict(result.stance, result.reason),
|
|
223
|
+
summary: copy.summary(scoreText),
|
|
224
224
|
key_findings: uniqueStrings([
|
|
225
|
-
...hitIds.map(
|
|
226
|
-
...missIds.map(
|
|
225
|
+
...hitIds.map(copy.hit),
|
|
226
|
+
...missIds.map(copy.miss),
|
|
227
227
|
]),
|
|
228
228
|
disagreements: [],
|
|
229
229
|
disqualifiers_triggered: uniqueStrings([...unmet, ...vetoIds]),
|
|
230
230
|
what_would_change_my_mind: uniqueStrings([
|
|
231
|
-
...unmet.map(
|
|
232
|
-
...vetoIds.map(
|
|
233
|
-
...missIds.map(
|
|
231
|
+
...unmet.map(copy.eligibility),
|
|
232
|
+
...vetoIds.map(copy.veto),
|
|
233
|
+
...missIds.map(copy.score),
|
|
234
234
|
]),
|
|
235
235
|
source_ids: sourceIds,
|
|
236
236
|
confidence: result.common_projection?.confidence || "low",
|
|
@@ -257,27 +257,27 @@ export function completedMasterOpinion(run, item) {
|
|
|
257
257
|
export function declinedMasterOpinion(run, item) {
|
|
258
258
|
if (item?.engine !== "v3_method_runtime") return v2DeclinedOpinion(run, item.id, item.decision);
|
|
259
259
|
const eligibility = item.preDecision.eligibility;
|
|
260
|
-
const
|
|
261
|
-
const label = item.pack.admitted_label?.[
|
|
260
|
+
const locale = languageKey(run.language);
|
|
261
|
+
const label = item.pack.admitted_label?.[locale]
|
|
262
262
|
|| item.pack.admitted_label?.en || item.id;
|
|
263
|
-
const
|
|
263
|
+
const copy = localized(run.language, {
|
|
264
|
+
en: { none: "none", verdict: `${label} cannot evaluate ${run.symbol}: ${eligibility.reason}`, summary: (missing) => `The v3 typed-fact gate returned ${eligibility.status}; missing: ${missing}. No legacy prompt or narrative decision layer was called.`, available: (id) => `${id} becomes available from a point-in-time source` },
|
|
265
|
+
zh: { none: "无", verdict: `${label}无法评估 ${run.symbol}:${eligibility.reason}`, summary: (missing) => `v3 typed-fact 闸门返回 ${eligibility.status};缺失:${missing}。系统未调用旧提示词或叙述决策层。`, available: (id) => `${id} 可从时点一致的来源取得` },
|
|
266
|
+
ja: { none: "なし", verdict: `${label}は ${run.symbol} を評価できません:${eligibility.reason}`, summary: (missing) => `v3 typed-fact ゲートは ${eligibility.status} を返しました。欠落:${missing}。旧プロンプトや叙述型の判断層は呼び出していません。`, available: (id) => `${id} が時点整合した出典から利用可能になる` },
|
|
267
|
+
ko: { none: "없음", verdict: `${label}은 ${run.symbol}을 평가할 수 없습니다: ${eligibility.reason}`, summary: (missing) => `v3 typed-fact 게이트가 ${eligibility.status}을 반환했습니다. 누락: ${missing}. 기존 프롬프트나 서술형 판단 계층은 호출하지 않았습니다.`, available: (id) => `${id}을 시점 일치 출처에서 확보할 수 있게 됨` },
|
|
268
|
+
});
|
|
269
|
+
const missing = eligibility.missing_required_fact_types.join(", ") || copy.none;
|
|
264
270
|
return {
|
|
265
271
|
master: item.id,
|
|
266
272
|
symbol: run.symbol,
|
|
267
273
|
as_of: run.as_of,
|
|
268
274
|
stance: "out_of_scope",
|
|
269
|
-
verdict:
|
|
270
|
-
|
|
271
|
-
: `${label} cannot evaluate ${run.symbol}: ${eligibility.reason}`,
|
|
272
|
-
summary: zh
|
|
273
|
-
? `v3 typed-fact 闸门返回 ${eligibility.status};缺失:${missing}。系统未调用旧提示词或叙述决策层。`
|
|
274
|
-
: `The v3 typed-fact gate returned ${eligibility.status}; missing: ${missing}. No legacy prompt or narrative decision layer was called.`,
|
|
275
|
+
verdict: copy.verdict,
|
|
276
|
+
summary: copy.summary(missing),
|
|
275
277
|
key_findings: [],
|
|
276
278
|
disagreements: [],
|
|
277
279
|
disqualifiers_triggered: eligibility.missing_required_fact_types,
|
|
278
|
-
what_would_change_my_mind: eligibility.missing_required_fact_types.map(
|
|
279
|
-
zh ? `${id} 可从时点一致的来源取得` : `${id} becomes available from a point-in-time source`
|
|
280
|
-
)),
|
|
280
|
+
what_would_change_my_mind: eligibility.missing_required_fact_types.map(copy.available),
|
|
281
281
|
source_ids: [],
|
|
282
282
|
// The refusal itself is deterministic, but the seat has insufficient evidence for an
|
|
283
283
|
// investment judgment. Keep the reader-facing evidence confidence low so a mechanically
|
|
@@ -18,6 +18,8 @@ function candidateLabel(label) {
|
|
|
18
18
|
return {
|
|
19
19
|
en: /candidate/i.test(label.en) ? label.en : `${label.en} candidate`,
|
|
20
20
|
zh: /候选/.test(label.zh) ? label.zh : `${label.zh}候选`,
|
|
21
|
+
...(label.ja ? { ja: /候補/.test(label.ja) ? label.ja : `${label.ja}候補` } : {}),
|
|
22
|
+
...(label.ko ? { ko: /후보/.test(label.ko) ? label.ko : `${label.ko} 후보` } : {}),
|
|
21
23
|
};
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -113,8 +113,10 @@ function physicalComponent(packDir, relativePath, label) {
|
|
|
113
113
|
return file;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
function localized(value, label, errors) {
|
|
117
|
-
if (!isObject(value) ||
|
|
116
|
+
function localized(value, label, errors, required = ["en", "zh"]) {
|
|
117
|
+
if (!isObject(value) || required.some((key) => !value[key]?.trim())) {
|
|
118
|
+
errors.push(`${label} must contain non-empty ${required.join(", ")} strings`);
|
|
119
|
+
}
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
function validateManifest(manifest, packDir) {
|
|
@@ -128,15 +130,16 @@ function validateManifest(manifest, packDir) {
|
|
|
128
130
|
errors.push("pack_version must be a semantic version, optionally with a prerelease suffix");
|
|
129
131
|
}
|
|
130
132
|
const identity = manifest.identity;
|
|
133
|
+
const selectorLocales = manifest.build_profile === "solo_test" ? ["en", "zh", "ja", "ko"] : ["en", "zh"];
|
|
131
134
|
if (!isObject(identity) || !ID.test(identity.persona_id || "")) errors.push("identity.persona_id is invalid");
|
|
132
135
|
else if (basename(resolve(packDir)) !== identity.persona_id) errors.push(`pack directory must be named ${identity.persona_id}`);
|
|
133
|
-
localized(identity?.public_label, "identity.public_label", errors);
|
|
134
|
-
localized(identity?.operator_label, "identity.operator_label", errors);
|
|
136
|
+
localized(identity?.public_label, "identity.public_label", errors, selectorLocales);
|
|
137
|
+
localized(identity?.operator_label, "identity.operator_label", errors, selectorLocales);
|
|
135
138
|
if (!MATURITIES.has(identity?.maturity)) errors.push("identity.maturity is invalid (and remains advisory even when valid)");
|
|
136
139
|
if (!Number.isFinite(inclusiveCutoffTime(identity?.source_cutoff))) errors.push("identity.source_cutoff is invalid");
|
|
137
|
-
localized(manifest.selection?.identity, "selection.identity", errors);
|
|
138
|
-
localized(manifest.selection?.method, "selection.method", errors);
|
|
139
|
-
localized(manifest.selection?.best_for, "selection.best_for", errors);
|
|
140
|
+
localized(manifest.selection?.identity, "selection.identity", errors, selectorLocales);
|
|
141
|
+
localized(manifest.selection?.method, "selection.method", errors, selectorLocales);
|
|
142
|
+
localized(manifest.selection?.best_for, "selection.best_for", errors, selectorLocales);
|
|
140
143
|
if (!Array.isArray(manifest.capability?.domains) || !manifest.capability.domains.length) errors.push("capability.domains is required");
|
|
141
144
|
const requiredFacts = manifest.capability?.required_fact_types;
|
|
142
145
|
const optionalFacts = manifest.capability?.optional_fact_types;
|
|
@@ -353,7 +353,13 @@ function headerValue(value) {
|
|
|
353
353
|
return value === undefined ? null : String(value);
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
function requestOnce(url, {
|
|
356
|
+
function requestOnce(url, {
|
|
357
|
+
timeoutMs,
|
|
358
|
+
configuredTimeoutMs = timeoutMs,
|
|
359
|
+
maxBytes,
|
|
360
|
+
requestImpl,
|
|
361
|
+
destination,
|
|
362
|
+
}) {
|
|
357
363
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
358
364
|
let settled = false;
|
|
359
365
|
let timer = null;
|
|
@@ -367,8 +373,8 @@ function requestOnce(url, { timeoutMs, maxBytes, requestImpl, destination }) {
|
|
|
367
373
|
const transport = requestImpl || (new URL(url).protocol === "https:" ? httpsRequest : httpRequest);
|
|
368
374
|
let request;
|
|
369
375
|
timer = setTimeout(() => {
|
|
370
|
-
request?.destroy(new Error(`source acquisition timed out after ${
|
|
371
|
-
reject(new PersonaSourceAcquisitionError(`source acquisition timed out after ${
|
|
376
|
+
request?.destroy(new Error(`source acquisition timed out after ${configuredTimeoutMs}ms`));
|
|
377
|
+
reject(new PersonaSourceAcquisitionError(`source acquisition timed out after ${configuredTimeoutMs}ms`));
|
|
372
378
|
}, timeoutMs);
|
|
373
379
|
try {
|
|
374
380
|
request = transport(url, {
|
|
@@ -465,6 +471,7 @@ export async function retrieveExplicitHttpBytes(url, {
|
|
|
465
471
|
networkTrace.push(destination);
|
|
466
472
|
const result = await requestOnce(current, {
|
|
467
473
|
timeoutMs: remaining,
|
|
474
|
+
configuredTimeoutMs: normalizedLimits.timeout_ms,
|
|
468
475
|
maxBytes: normalizedLimits.max_bytes,
|
|
469
476
|
requestImpl,
|
|
470
477
|
destination,
|
package/mcp/lib/prompts.mjs
CHANGED
|
@@ -156,6 +156,39 @@ export function masterPrompt(masterId, run) {
|
|
|
156
156
|
].filter(Boolean).join("\n\n");
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* One isolated worker per selected physical v3 method, after its structured decision is
|
|
161
|
+
* frozen. The worker may explain and challenge the evidence, but it cannot vote again.
|
|
162
|
+
*/
|
|
163
|
+
export function masterVoicePrompt(masterId, run, frozenOpinion) {
|
|
164
|
+
const reg = registry();
|
|
165
|
+
const persona = reg.get(masterId);
|
|
166
|
+
if (!persona || persona.kind !== "master") throw new Error(`unknown master persona: ${masterId}`);
|
|
167
|
+
const language = run.language || "English";
|
|
168
|
+
const values = { symbol: run.symbol, as_of: run.as_of, language };
|
|
169
|
+
const evidence = JSON.stringify(run.council_mode === "quick" ? compactQuickEvidence(run) : compactEvidence(run));
|
|
170
|
+
return [
|
|
171
|
+
`You are the dedicated, isolated method-seat explanation worker for ${personaTitle(persona, language)} (${masterId}) in the ${run.symbol} council.`,
|
|
172
|
+
`Write every reader-facing field in ${language}. Keep stable IDs, tickers and source IDs unchanged.`,
|
|
173
|
+
"This is a project-derived provisional method lens, not the named person's current statement, endorsement, or quotation.",
|
|
174
|
+
"The structured method decision below is already frozen. You MUST NOT change, soften, strengthen or reinterpret its stance. Explain why that frozen result follows, identify the highest-information facts or missing facts, state any disagreement with analyst interpretation, and say what evidence would change the method result. Do not browse or add facts.",
|
|
175
|
+
`Frozen method result JSON: ${JSON.stringify({
|
|
176
|
+
master: masterId,
|
|
177
|
+
stance: frozenOpinion?.stance,
|
|
178
|
+
verdict: frozenOpinion?.verdict,
|
|
179
|
+
summary: frozenOpinion?.summary,
|
|
180
|
+
disqualifiers_triggered: frozenOpinion?.disqualifiers_triggered || [],
|
|
181
|
+
what_would_change_my_mind: frozenOpinion?.what_would_change_my_mind || [],
|
|
182
|
+
source_ids: frozenOpinion?.source_ids || [],
|
|
183
|
+
deterministic_core_hash: frozenOpinion?.deterministic_core_hash || null,
|
|
184
|
+
frozen_decision_hash: frozenOpinion?.frozen_decision_hash || null,
|
|
185
|
+
})}`,
|
|
186
|
+
`Method instructions (for explanation only):\n${render(personaPrompt(persona, language), values)}`,
|
|
187
|
+
"Return ONLY one valid JSON object, no Markdown fence. Schema: {\"master\":\"stable id\",\"acknowledged_stance\":\"constructive|cautious|opposed|out_of_scope\",\"statement\":\"reader-facing explanation\",\"key_findings\":[\"string\"],\"disagreements\":[\"string\"],\"what_would_change_my_mind\":[\"string\"],\"source_ids\":[\"task:S1\"],\"confidence\":\"high|medium|low\"}.",
|
|
188
|
+
`Bounded shared evidence JSON: ${evidence}`,
|
|
189
|
+
].join("\n\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
159
192
|
/** The master ids a run has selected, from an explicit list or a roster name. */
|
|
160
193
|
export function selectedMasters(run) {
|
|
161
194
|
const reg = registry();
|