alphacouncil-agent 0.9.1 → 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 +90 -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/evaluation/persona-v3-release-evidence.md +2 -2
- package/docs/plans/0.9.0-personapack-v3.md +3 -3
- package/docs/releases/v0.9.2.md +62 -0
- 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/abort.mjs +0 -1
- 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 -266
- 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/abort.mjs
CHANGED
|
@@ -7,7 +7,6 @@ export function linkedAbort(timeoutMs, upstream) {
|
|
|
7
7
|
const timer = Number.isFinite(timeoutMs) && timeoutMs >= 0
|
|
8
8
|
? setTimeout(() => controller.abort(new Error(`operation timed out after ${timeoutMs}ms`)), timeoutMs)
|
|
9
9
|
: null;
|
|
10
|
-
if (typeof timer?.unref === "function") timer.unref();
|
|
11
10
|
return {
|
|
12
11
|
signal: controller.signal,
|
|
13
12
|
cleanup() {
|
package/mcp/lib/codex.mjs
CHANGED
|
@@ -51,9 +51,9 @@ export function stopChild(child, force = false) {
|
|
|
51
51
|
* second Codex-backed search bridge, creating recursive workers and multi-minute nested
|
|
52
52
|
* timeouts. Authentication still comes from CODEX_HOME according to the Codex CLI contract.
|
|
53
53
|
*/
|
|
54
|
-
export function codexWorkerArgs(outFile, dataDir = DATA_DIR) {
|
|
54
|
+
export function codexWorkerArgs(outFile, dataDir = DATA_DIR, { search = true } = {}) {
|
|
55
55
|
return [
|
|
56
|
-
"--search",
|
|
56
|
+
...(search ? ["--search"] : []),
|
|
57
57
|
"-s",
|
|
58
58
|
"read-only",
|
|
59
59
|
"-a",
|
|
@@ -73,7 +73,7 @@ export function runCodex(prompt, timeoutMs, onStart = () => {}, onHeartbeat = ()
|
|
|
73
73
|
return new Promise((resolvePromise) => {
|
|
74
74
|
mkdirSync(DATA_DIR, { recursive: true });
|
|
75
75
|
const outFile = join(DATA_DIR, `codex-${Date.now()}-${Math.random().toString(16).slice(2)}.txt`);
|
|
76
|
-
const args = codexWorkerArgs(outFile);
|
|
76
|
+
const args = codexWorkerArgs(outFile, DATA_DIR, { search: runtime.search !== false });
|
|
77
77
|
const invocation = codexInvocation(args);
|
|
78
78
|
const spawnWorker = runtime.spawn || spawn;
|
|
79
79
|
const stopWorker = runtime.stopChild || stopChild;
|
package/mcp/lib/constants.mjs
CHANGED
|
@@ -59,37 +59,37 @@ export const OUTPUT_MODES = [
|
|
|
59
59
|
* several legitimately terse sections ("## Confidence\nmedium").
|
|
60
60
|
*/
|
|
61
61
|
export const REPORT_SECTIONS = [
|
|
62
|
-
{ id: "conclusion", aliases: ["结论", "conclusion"], min_body: 6 },
|
|
63
|
-
{ id: "analyst_work_log", aliases: ["分析师工作记录", "analyst work log"], min_body: 12, per_task: true },
|
|
64
|
-
{ id: "debate_record", aliases: ["多空辩论记录", "bull bear debate", "debate record"], min_body: 20 },
|
|
62
|
+
{ id: "conclusion", aliases: ["结论", "conclusion", "結論", "결론"], min_body: 6 },
|
|
63
|
+
{ id: "analyst_work_log", aliases: ["分析师工作记录", "analyst work log", "アナリスト作業記録", "分析担当作業記録", "분석가 작업 기록"], min_body: 12, per_task: true },
|
|
64
|
+
{ id: "debate_record", aliases: ["多空辩论记录", "bull bear debate", "debate record", "強気弱気討論記録", "多空討論記録", "롱 숏 토론 기록", "강세 약세 토론 기록"], min_body: 20 },
|
|
65
65
|
// Required only when a bench actually ran. A report that spends ten master seats and
|
|
66
66
|
// then mentions none of them is the defect this entry exists to catch; a screen-only
|
|
67
67
|
// run that never selected one must not be failed for omitting it.
|
|
68
|
-
{ id: "master_bench", aliases: ["大师席", "master席位", "master bench", "master lens"], min_body: 20, when_masters: true },
|
|
69
|
-
{ id: "market_expectations", aliases: ["市场预期", "market expectations"], min_body: 8 },
|
|
70
|
-
{ id: "analyst_rating", aliases: ["分析师评级", "analyst rating", "target price"], min_body: 8 },
|
|
71
|
-
{ id: "earnings_call", aliases: ["电话会", "earnings call"], min_body: 8 },
|
|
72
|
-
{ id: "quant", aliases: ["量化", "quant"], min_body: 8 },
|
|
73
|
-
{ id: "news", aliases: ["新闻", "news"], min_body: 8 },
|
|
74
|
-
{ id: "short_interest", aliases: ["short interest", "borrow"], min_body: 8 },
|
|
75
|
-
{ id: "strategic_transaction", aliases: ["战略交易", "strategic transaction", "banking event"], min_body: 8 },
|
|
76
|
-
{ id: "valuation", aliases: ["估值", "valuation"], min_body: 8 },
|
|
68
|
+
{ id: "master_bench", aliases: ["大师席", "master席位", "master bench", "master lens", "マスターベンチ", "マスター・ベンチ", "メソッド席", "마스터 벤치", "방법론 좌석"], min_body: 20, when_masters: true },
|
|
69
|
+
{ id: "market_expectations", aliases: ["市场预期", "market expectations", "市場予想", "市場期待", "시장 기대"], min_body: 8 },
|
|
70
|
+
{ id: "analyst_rating", aliases: ["分析师评级", "analyst rating", "target price", "アナリスト評価", "目標株価", "애널리스트 등급", "목표가"], min_body: 8 },
|
|
71
|
+
{ id: "earnings_call", aliases: ["电话会", "earnings call", "決算説明会", "실적 발표 콜"], min_body: 8 },
|
|
72
|
+
{ id: "quant", aliases: ["量化", "quant", "クオンツ", "定量", "퀀트", "정량"], min_body: 8 },
|
|
73
|
+
{ id: "news", aliases: ["新闻", "news", "ニュース", "뉴스"], min_body: 8 },
|
|
74
|
+
{ id: "short_interest", aliases: ["short interest", "borrow", "空売り", "貸株", "공매도", "대차"], min_body: 8 },
|
|
75
|
+
{ id: "strategic_transaction", aliases: ["战略交易", "strategic transaction", "banking event", "戦略取引", "銀行イベント", "전략적 거래", "금융 이벤트"], min_body: 8 },
|
|
76
|
+
{ id: "valuation", aliases: ["估值", "valuation", "バリュエーション", "企業価値評価", "가치평가"], min_body: 8 },
|
|
77
77
|
// Required, and deliberately not called "target price". A single number pretends to a
|
|
78
78
|
// precision nobody has; what a reader needs is the price at which the case changes.
|
|
79
79
|
// Three master lenses already demand this -- Marks asks at what price it stops being
|
|
80
80
|
// "leave it", Graham asks for a calculable floor, Thorp asks for a size -- and the PM
|
|
81
81
|
// used to be able to skip it with "the cycle position is undetermined".
|
|
82
|
-
{ id: "price_levels", aliases: ["价位", "入场价位", "price levels", "entry level", "价格条件"], min_body: 30 },
|
|
83
|
-
{ id: "catalysts", aliases: ["催化剂", "catalyst", "catalysts"], min_body: 8 },
|
|
84
|
-
{ id: "risks", aliases: ["风险", "risk", "risks"], min_body: 8 },
|
|
85
|
-
{ id: "position", aliases: ["仓位", "position"], min_body: 8 },
|
|
86
|
-
{ id: "short_term", aliases: ["短线", "short term"], min_body: 6 },
|
|
87
|
-
{ id: "medium_term", aliases: ["中期", "medium term"], min_body: 6 },
|
|
88
|
-
{ id: "long_term", aliases: ["长期", "long term"], min_body: 6 },
|
|
89
|
-
{ id: "data_gaps", aliases: ["数据缺口", "data gaps", "unavailable data"], min_body: 8 },
|
|
90
|
-
{ id: "invalidation", aliases: ["反证", "invalidation"], min_body: 8 },
|
|
91
|
-
{ id: "confidence", aliases: ["置信", "confidence"], min_body: 3 },
|
|
92
|
-
{ id: "source_table", aliases: ["来源表", "source table"], min_body: 6 },
|
|
82
|
+
{ id: "price_levels", aliases: ["价位", "入场价位", "price levels", "entry level", "价格条件", "価格条件", "エントリー価格", "가격 조건", "진입 가격"], min_body: 30 },
|
|
83
|
+
{ id: "catalysts", aliases: ["催化剂", "catalyst", "catalysts", "カタリスト", "材料", "촉매"], min_body: 8 },
|
|
84
|
+
{ id: "risks", aliases: ["风险", "risk", "risks", "リスク", "위험"], min_body: 8 },
|
|
85
|
+
{ id: "position", aliases: ["仓位", "position", "ポジション", "配分", "포지션"], min_body: 8 },
|
|
86
|
+
{ id: "short_term", aliases: ["短线", "short term", "短期", "단기"], min_body: 6 },
|
|
87
|
+
{ id: "medium_term", aliases: ["中期", "medium term", "중기"], min_body: 6 },
|
|
88
|
+
{ id: "long_term", aliases: ["长期", "long term", "長期", "장기"], min_body: 6 },
|
|
89
|
+
{ id: "data_gaps", aliases: ["数据缺口", "data gaps", "unavailable data", "データ欠落", "利用不可データ", "데이터 공백", "사용 불가 데이터"], min_body: 8 },
|
|
90
|
+
{ id: "invalidation", aliases: ["反证", "invalidation", "無効化条件", "反証条件", "무효화 조건", "반증 조건"], min_body: 8 },
|
|
91
|
+
{ id: "confidence", aliases: ["置信", "confidence", "信頼度", "신뢰도"], min_body: 3 },
|
|
92
|
+
{ id: "source_table", aliases: ["来源表", "source table", "出典表", "ソース表", "출처 표"], min_body: 6 },
|
|
93
93
|
];
|
|
94
94
|
|
|
95
95
|
// Quick reports are deliberately a different publication scope. Requiring the full
|
|
@@ -159,6 +159,42 @@ export const LIMITS = Object.freeze({
|
|
|
159
159
|
SIGKILL_GRACE_MS: 5000,
|
|
160
160
|
/** Default per-subagent Codex timeout. */
|
|
161
161
|
CODEX_TIMEOUT_MS: Number(process.env.ALPHACOUNCIL_AGENT_TIMEOUT_MS) || 600000,
|
|
162
|
+
/** Non-overridable public ceiling for a plugin-managed full council. */
|
|
163
|
+
FULL_HARD_MAX_MS: 30 * 60 * 1000,
|
|
164
|
+
/** Default full-council queue-to-persistence budget. Operators may only lower it. */
|
|
165
|
+
FULL_TOTAL_MS: Math.max(1_000, Math.min(
|
|
166
|
+
Number(process.env.ALPHACOUNCIL_FULL_TOTAL_MS) || 30 * 60 * 1000,
|
|
167
|
+
30 * 60 * 1000,
|
|
168
|
+
)),
|
|
169
|
+
/** Deterministic grounding is useful, but it may not hold the whole run hostage. */
|
|
170
|
+
FULL_GROUNDING_MS: 30 * 1000,
|
|
171
|
+
/** All eight full evidence seats launch in one wave under this per-seat cap. */
|
|
172
|
+
FULL_EVIDENCE_MS: Math.max(1_000, Math.min(
|
|
173
|
+
Number(process.env.ALPHACOUNCIL_FULL_EVIDENCE_MS) || 6 * 60 * 1000,
|
|
174
|
+
6 * 60 * 1000,
|
|
175
|
+
)),
|
|
176
|
+
/** Every selected method gets one isolated explanation worker after its decision freezes. */
|
|
177
|
+
FULL_MASTER_MS: Math.max(1_000, Math.min(
|
|
178
|
+
Number(process.env.ALPHACOUNCIL_FULL_MASTER_MS) || 2 * 60 * 1000,
|
|
179
|
+
2 * 60 * 1000,
|
|
180
|
+
)),
|
|
181
|
+
/** Bull and bear run together inside each of the three full-council rounds. */
|
|
182
|
+
FULL_DEBATE_MS: Math.max(1_000, Math.min(
|
|
183
|
+
Number(process.env.ALPHACOUNCIL_FULL_DEBATE_MS) || 150 * 1000,
|
|
184
|
+
150 * 1000,
|
|
185
|
+
)),
|
|
186
|
+
/** The PM is the final model call and receives its own bounded slice. */
|
|
187
|
+
FULL_PM_MS: Math.max(1_000, Math.min(
|
|
188
|
+
Number(process.env.ALPHACOUNCIL_FULL_PM_MS) || 180 * 1000,
|
|
189
|
+
180 * 1000,
|
|
190
|
+
)),
|
|
191
|
+
/** Reserved for forced child settlement, deterministic assembly and atomic persistence. */
|
|
192
|
+
FULL_FINALIZE_RESERVE_MS: 45 * 1000,
|
|
193
|
+
/** A malformed response gets one short, no-search transport-only repair. */
|
|
194
|
+
PARSE_REPAIR_MS: 30 * 1000,
|
|
195
|
+
PARSE_REPAIR_INPUT_CHARS: 80 * 1000,
|
|
196
|
+
FULL_EVIDENCE_CONCURRENCY: 8,
|
|
197
|
+
FULL_MASTER_CONCURRENCY: 13,
|
|
162
198
|
/** Non-overridable public ceiling for a quick council, including retries and synthesis. */
|
|
163
199
|
QUICK_HARD_MAX_MS: 10 * 60 * 1000,
|
|
164
200
|
/** Default quick budget. Operators may lower it for stricter environments, never raise it. */
|
|
@@ -191,8 +227,8 @@ export const LIMITS = Object.freeze({
|
|
|
191
227
|
Math.trunc(Number(process.env.ALPHACOUNCIL_QUICK_MIN_SUCCESSFUL_TASKS) || 2),
|
|
192
228
|
)),
|
|
193
229
|
CONCURRENCY_MIN: 1,
|
|
194
|
-
CONCURRENCY_MAX:
|
|
195
|
-
CONCURRENCY_DEFAULT: Number(process.env.ALPHACOUNCIL_AGENT_CONCURRENCY) ||
|
|
230
|
+
CONCURRENCY_MAX: 26,
|
|
231
|
+
CONCURRENCY_DEFAULT: Number(process.env.ALPHACOUNCIL_AGENT_CONCURRENCY) || 8,
|
|
196
232
|
/** A selection is deliberately short-lived and may create exactly one council run. */
|
|
197
233
|
SELECTION_TTL_MS: Number(process.env.ALPHACOUNCIL_SELECTION_TTL_MS) || 60 * 60 * 1000,
|
|
198
234
|
/** Selection lock metadata advertises this lease, but a live owner is never pre-empted. */
|
|
@@ -4,6 +4,7 @@ import { loadPacks } from "./personas-v2/loader.mjs";
|
|
|
4
4
|
import { compiledPersonaPacks } from "./personas-v3/registry.mjs";
|
|
5
5
|
import { sha256 } from "./personas-v3/canonical.mjs";
|
|
6
6
|
import { selectorCard } from "./master-catalog.mjs";
|
|
7
|
+
import { languageKey, localized } from "./lang.mjs";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* The menu a host shows before a run starts.
|
|
@@ -42,7 +43,8 @@ const estimateSelectionRange = ({ analysts = 0, allMasters = 1, verifiers = 0, d
|
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
export function councilOptions({ language = "English" } = {}) {
|
|
45
|
-
const
|
|
46
|
+
const locale = languageKey(language);
|
|
47
|
+
const copy = (messages) => localized(language, messages);
|
|
46
48
|
const reg = registry();
|
|
47
49
|
const packs = loadPacks();
|
|
48
50
|
const v3Packs = compiledPersonaPacks();
|
|
@@ -59,15 +61,6 @@ export function councilOptions({ language = "English" } = {}) {
|
|
|
59
61
|
covers: (p.tags || []).join(", "),
|
|
60
62
|
}));
|
|
61
63
|
|
|
62
|
-
const rosterChoices = masterRosters.map((roster) => {
|
|
63
|
-
const members = selectRoster(reg, { kind: "master", roster });
|
|
64
|
-
return {
|
|
65
|
-
roster,
|
|
66
|
-
count: members.length,
|
|
67
|
-
members: members.map((m) => ({ id: m.id, title: personaTitle(m, language) })),
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
|
|
71
64
|
// A flattened, stable-order catalog is the selection source of truth. Rosters remain
|
|
72
65
|
// useful shortcuts, but a user may choose any 1..N individual methods across schools.
|
|
73
66
|
const masterChoices = reg.ids("master").map((id, offset) => {
|
|
@@ -77,7 +70,13 @@ export function councilOptions({ language = "English" } = {}) {
|
|
|
77
70
|
const v3Selection = v3?.manifest?.selection;
|
|
78
71
|
const v3Label = v3?.admitted_label;
|
|
79
72
|
const provisionalV3 = v3?.build_profile === "solo_test";
|
|
80
|
-
const
|
|
73
|
+
const field = (value, label) => {
|
|
74
|
+
const selected = value?.[locale];
|
|
75
|
+
if (typeof selected !== "string" || !selected.trim()) {
|
|
76
|
+
throw new Error(`${id}: missing ${locale} selector ${label}`);
|
|
77
|
+
}
|
|
78
|
+
return selected;
|
|
79
|
+
};
|
|
81
80
|
// Until a physical v3 pack exists, bind the receipt to the exact v2 pack or prompt
|
|
82
81
|
// persona rather than publishing a null hash. A prompt edit must invalidate a catalog
|
|
83
82
|
// the user saw before that edit just as a v3 policy edit does.
|
|
@@ -94,13 +93,19 @@ export function councilOptions({ language = "English" } = {}) {
|
|
|
94
93
|
return {
|
|
95
94
|
index: offset + 1,
|
|
96
95
|
id,
|
|
97
|
-
title:
|
|
96
|
+
title: v3Label ? field(v3Label, "title") : personaTitle(persona, language),
|
|
98
97
|
...(v3Selection ? {
|
|
99
|
-
identity:
|
|
100
|
-
method:
|
|
101
|
-
best_for:
|
|
98
|
+
identity: field(v3Selection.identity, "identity"),
|
|
99
|
+
method: field(v3Selection.method, "method"),
|
|
100
|
+
best_for: field(v3Selection.best_for, "best_for"),
|
|
102
101
|
} : selectorCard(persona, language)),
|
|
103
102
|
maturity: v3?.maturity || pack?.kind || "prompt_lens",
|
|
103
|
+
maturity_label: copy({
|
|
104
|
+
en: v3?.maturity === "method_model" ? "Validated method model" : v3?.maturity === "candidate" ? "Candidate method" : "Provisional operator lens",
|
|
105
|
+
zh: v3?.maturity === "method_model" ? "已验证方法模型" : v3?.maturity === "candidate" ? "候选方法" : "临时操作视角",
|
|
106
|
+
ja: v3?.maturity === "method_model" ? "検証済みメソッドモデル" : v3?.maturity === "candidate" ? "候補メソッド" : "暫定オペレーター・レンズ",
|
|
107
|
+
ko: v3?.maturity === "method_model" ? "검증된 방법론 모델" : v3?.maturity === "candidate" ? "후보 방법론" : "임시 오퍼레이터 렌즈",
|
|
108
|
+
}),
|
|
104
109
|
runtime_level: v3?.admission?.level || (pack ? "v2_operator" : "v1_prompt"),
|
|
105
110
|
admission_level: v3?.admission?.level || (pack ? "operator_lens" : "prompt_lens"),
|
|
106
111
|
pack_format: v3 ? provisionalV3 ? "schema_v3_solo_test" : "schema_v3_physical" : pack ? "schema_v2_legacy" : "prompt_v1_legacy",
|
|
@@ -119,15 +124,31 @@ export function councilOptions({ language = "English" } = {}) {
|
|
|
119
124
|
};
|
|
120
125
|
});
|
|
121
126
|
|
|
127
|
+
const masterById = new Map(masterChoices.map((master) => [master.id, master]));
|
|
128
|
+
const rosterChoices = masterRosters.map((roster) => {
|
|
129
|
+
const members = selectRoster(reg, { kind: "master", roster });
|
|
130
|
+
return {
|
|
131
|
+
roster,
|
|
132
|
+
count: members.length,
|
|
133
|
+
members: members.map((member) => ({
|
|
134
|
+
id: member.id,
|
|
135
|
+
title: masterById.get(member.id)?.title || personaTitle(member, language),
|
|
136
|
+
})),
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
|
|
122
140
|
const allMasters = masterChoices.length;
|
|
123
141
|
const verifiers = reg.ids("verifier");
|
|
124
142
|
|
|
125
143
|
const presets = [
|
|
126
144
|
{
|
|
127
145
|
id: "quick",
|
|
128
|
-
label:
|
|
129
|
-
|
|
130
|
-
: "
|
|
146
|
+
label: copy({
|
|
147
|
+
en: "Quick: 4 core analysts in parallel + up to 4 masters + one parallel bull/bear round + short synthesis",
|
|
148
|
+
zh: "快速:4 位核心分析师并行 + 最多 4 位大师 + 单轮并行多空 + 短综合",
|
|
149
|
+
ja: "クイック:中核アナリスト4席を並列実行 + マスター最大4席 + 1回の並列Bull/Bear + 短い総合判断",
|
|
150
|
+
ko: "퀵: 핵심 분석가 4개 좌석 병렬 + 마스터 최대 4개 좌석 + 1회 병렬 Bull/Bear + 짧은 종합 판단",
|
|
151
|
+
}),
|
|
131
152
|
analysts: QUICK_TASKS,
|
|
132
153
|
master_selection: "required_1_to_N",
|
|
133
154
|
master_selection_maximum: 4,
|
|
@@ -139,33 +160,52 @@ export function councilOptions({ language = "English" } = {}) {
|
|
|
139
160
|
debate_rounds: 1,
|
|
140
161
|
report_contract: "quick_v1",
|
|
141
162
|
full_council_equivalent: false,
|
|
142
|
-
good_for:
|
|
143
|
-
|
|
144
|
-
: "
|
|
163
|
+
good_for: copy({
|
|
164
|
+
en: "A bounded directional read retaining masters, core analysts and recent company/industry news; no three-round cross-exam or adversarial verification.",
|
|
165
|
+
zh: "十分钟级方向性初读:保留大师、核心分析师和近期公司/行业新闻;不做三轮交叉问答或对抗核验",
|
|
166
|
+
ja: "短時間の方向性確認。マスター、中核アナリスト、直近の企業・業界ニュースを含むが、3ラウンドの反対尋問や対抗検証は行わない。",
|
|
167
|
+
ko: "짧은 방향성 검토. 마스터, 핵심 분석가, 최근 기업·산업 뉴스를 포함하지만 3라운드 교차 질문이나 적대적 검증은 수행하지 않는다.",
|
|
168
|
+
}),
|
|
145
169
|
},
|
|
146
170
|
{
|
|
147
171
|
id: "standard",
|
|
148
|
-
label:
|
|
172
|
+
label: copy({
|
|
173
|
+
en: "Standard: 8 analysts + selected masters + debate",
|
|
174
|
+
zh: "标准:8 位分析师 + 所选大师 + 辩论",
|
|
175
|
+
ja: "標準:アナリスト8席 + 選択したマスター + 討論",
|
|
176
|
+
ko: "표준: 분석가 8개 좌석 + 선택한 마스터 + 토론",
|
|
177
|
+
}),
|
|
149
178
|
analysts: DEFAULT_TASKS,
|
|
150
179
|
master_selection: "required_1_to_N",
|
|
151
180
|
suggested_masters_roster: "masters-core",
|
|
152
181
|
verify: false,
|
|
153
182
|
...estimateSelectionRange({ analysts: DEFAULT_TASKS.length, allMasters }),
|
|
154
|
-
good_for:
|
|
155
|
-
|
|
156
|
-
: "
|
|
183
|
+
good_for: copy({
|
|
184
|
+
en: "The recommended default: full evidence coverage plus this run's confirmed methods.",
|
|
185
|
+
zh: "默认推荐。覆盖完整证据面,并运行用户本次确认的方法席",
|
|
186
|
+
ja: "推奨される標準設定。証拠範囲を完全にカバーし、この実行で確定したメソッド席を動かす。",
|
|
187
|
+
ko: "권장 기본값. 전체 증거 범위를 다루고 이번 실행에서 확정한 방법론 좌석을 실행한다.",
|
|
188
|
+
}),
|
|
157
189
|
},
|
|
158
190
|
{
|
|
159
191
|
id: "deep",
|
|
160
|
-
label:
|
|
192
|
+
label: copy({
|
|
193
|
+
en: "Deep: every analyst + selected masters + verification + debate",
|
|
194
|
+
zh: "深度:全部分析师 + 所选大师 + 交叉验证 + 辩论",
|
|
195
|
+
ja: "詳細:全アナリスト + 選択したマスター + 交差検証 + 討論",
|
|
196
|
+
ko: "심층: 전체 분석가 + 선택한 마스터 + 교차 검증 + 토론",
|
|
197
|
+
}),
|
|
161
198
|
analysts: allAnalysts.map((p) => p.id),
|
|
162
199
|
master_selection: "required_1_to_N",
|
|
163
200
|
suggested_masters_roster: "masters-core",
|
|
164
201
|
verify: true,
|
|
165
202
|
...estimateSelectionRange({ analysts: allAnalysts.length, allMasters, verifiers: verifiers.length * 3 }),
|
|
166
|
-
good_for:
|
|
167
|
-
|
|
168
|
-
: "
|
|
203
|
+
good_for: copy({
|
|
204
|
+
en: "For a decision with real money behind it. Every load-bearing claim is re-sourced, re-derived and attacked.",
|
|
205
|
+
zh: "要下真钱的时候用。每条承重论断都会被回源、独立重算和反面检索",
|
|
206
|
+
ja: "実資金を伴う判断向け。重要な主張をすべて原典確認、独立再計算、反証検索にかける。",
|
|
207
|
+
ko: "실제 자금이 걸린 판단용. 핵심 주장을 모두 원출처 확인, 독립 재계산, 반증 검색에 부친다.",
|
|
208
|
+
}),
|
|
169
209
|
},
|
|
170
210
|
];
|
|
171
211
|
|
|
@@ -179,16 +219,31 @@ export function councilOptions({ language = "English" } = {}) {
|
|
|
179
219
|
all_master_ids: masterChoices.map((m) => m.id),
|
|
180
220
|
all_masters_count: allMasters,
|
|
181
221
|
verifiers: verifiers.map((id) => ({ id, title: personaTitle(reg.get(id), language) })),
|
|
182
|
-
how_to_ask:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
222
|
+
how_to_ask: copy({
|
|
223
|
+
en: [
|
|
224
|
+
"Before every new council run, show the individual catalog and require at least one master selection. The user may pick one, any combination, or all.",
|
|
225
|
+
"If the request already names masters, preselect them, but still show the catalog and require a submission for this run. Never reuse an old run's selection silently.",
|
|
226
|
+
"Prefer a host-native multi-select. If it cannot hold the full catalog, show the stable numbered table and accept numbers, stable IDs, or all.",
|
|
227
|
+
"Submitting the selection is the confirmation. Do not add another confirmation question, but do not start research without a selection receipt.",
|
|
228
|
+
],
|
|
229
|
+
zh: [
|
|
230
|
+
"每次新的委员会运行都先展示逐席名单,并要求用户选择至少 1 位大师;可以单选、任意多选或全选。",
|
|
231
|
+
"用户已经点名时,把这些席位预选出来,但仍要展示名单并让用户提交本次选择。旧运行的选择不能自动复用。",
|
|
232
|
+
"优先使用宿主原生多选;容纳不了完整名单时,展示固定编号表,并接受编号、稳定 ID 或 all。",
|
|
233
|
+
"选择提交本身就是确认。不要再追加第二个确认问题,但没有 selection receipt 就不能开始研究。",
|
|
234
|
+
],
|
|
235
|
+
ja: [
|
|
236
|
+
"新しい委員会実行の前に必ず席ごとの一覧を表示し、少なくとも1つのマスター席を選んでもらう。単独、複数、全選択が可能。",
|
|
237
|
+
"依頼文でマスターが指定済みでも、事前選択として表示したうえで今回の選択送信を求める。以前の実行の選択を黙って再利用しない。",
|
|
238
|
+
"ホストの複数選択を優先し、一覧を収められない場合は固定番号表を示して番号、stable ID、allを受け付ける。",
|
|
239
|
+
"選択の送信自体を確定とする。二重確認は追加しないが、selection receiptなしで調査を開始しない。",
|
|
240
|
+
],
|
|
241
|
+
ko: [
|
|
242
|
+
"새 위원회 실행 전에는 좌석별 목록을 모두 보여 주고 최소 1개 마스터 좌석을 선택받는다. 단일, 복수, 전체 선택이 가능하다.",
|
|
243
|
+
"요청에 마스터가 이미 지정되어 있어도 사전 선택으로 표시한 뒤 이번 실행의 선택 제출을 받아야 한다. 이전 실행의 선택을 조용히 재사용하지 않는다.",
|
|
244
|
+
"호스트의 다중 선택 기능을 우선하고, 전체 목록을 담을 수 없으면 고정 번호표를 보여 준 뒤 번호, stable ID 또는 all을 받는다.",
|
|
245
|
+
"선택 제출 자체가 확정이다. 두 번째 확인 질문은 추가하지 않지만 selection receipt 없이는 조사를 시작하지 않는다.",
|
|
246
|
+
],
|
|
247
|
+
}),
|
|
193
248
|
};
|
|
194
249
|
}
|
|
@@ -5,6 +5,7 @@ import { COUNCIL_MODES, LIMITS, SELECTIONS_DIR } from "./constants.mjs";
|
|
|
5
5
|
import { councilOptions } from "./council-options.mjs";
|
|
6
6
|
import { invalidParams } from "./errors.mjs";
|
|
7
7
|
import { readJson, writeJson } from "./fsutil.mjs";
|
|
8
|
+
import { resolveLanguage } from "./lang.mjs";
|
|
8
9
|
import { safeSymbol } from "./run-store.mjs";
|
|
9
10
|
import { cleanupSelectionStore } from "./selection-cleanup.mjs";
|
|
10
11
|
import { ensureSelectionLockStore, withSelectionLock } from "./selection-locks.mjs";
|
|
@@ -12,11 +13,34 @@ import { ensureSelectionLockStore, withSelectionLock } from "./selection-locks.m
|
|
|
12
13
|
const SELECTION_ID = /^SEL-[0-9a-f-]{36}$/i;
|
|
13
14
|
const RECEIPT_ID = /^RCP-[0-9a-f-]{36}$/i;
|
|
14
15
|
const QUICK_MASTER_MAX = 4;
|
|
16
|
+
const SUPPORTED_SELECTION_LANGUAGES = Object.freeze(["中文", "English", "日本語", "한국어"]);
|
|
15
17
|
|
|
16
18
|
function digest(value) {
|
|
17
19
|
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
function sameJson(left, right) {
|
|
23
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function receiptSelectionHash(receipt) {
|
|
27
|
+
return digest({
|
|
28
|
+
schema_version: receipt.schema_version,
|
|
29
|
+
selection_receipt: receipt.selection_receipt,
|
|
30
|
+
selection_id: receipt.selection_id,
|
|
31
|
+
symbol: receipt.symbol,
|
|
32
|
+
council_mode: receipt.council_mode,
|
|
33
|
+
catalog_hash: receipt.catalog_hash,
|
|
34
|
+
request_hash: receipt.request_hash,
|
|
35
|
+
intent_hash: receipt.intent_hash,
|
|
36
|
+
selected_master_ids: receipt.selected_master_ids,
|
|
37
|
+
selected_master_pack_hashes: receipt.selected_master_pack_hashes,
|
|
38
|
+
selection_mode: receipt.selection_mode,
|
|
39
|
+
created_at: receipt.created_at,
|
|
40
|
+
expires_at: receipt.expires_at,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
20
44
|
function councilMode(value) {
|
|
21
45
|
const mode = value === undefined ? "full" : String(value);
|
|
22
46
|
if (!COUNCIL_MODES.includes(mode)) {
|
|
@@ -27,6 +51,21 @@ function councilMode(value) {
|
|
|
27
51
|
return mode;
|
|
28
52
|
}
|
|
29
53
|
|
|
54
|
+
function selectionLanguage(args = {}) {
|
|
55
|
+
const language = resolveLanguage({ language: args.language, prompt: args.prompt });
|
|
56
|
+
if (!SUPPORTED_SELECTION_LANGUAGES.includes(language)) {
|
|
57
|
+
throw invalidParams(
|
|
58
|
+
`Selection language ${JSON.stringify(language)} is unsupported. Use zh-CN, en-US, ja-JP or ko-KR.`,
|
|
59
|
+
{
|
|
60
|
+
reason: "UNSUPPORTED_SELECTION_LANGUAGE",
|
|
61
|
+
requested_language: args.language ?? null,
|
|
62
|
+
supported_languages: ["zh-CN", "en-US", "ja-JP", "ko-KR"],
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return language;
|
|
67
|
+
}
|
|
68
|
+
|
|
30
69
|
function ensureStore() {
|
|
31
70
|
ensureSelectionLockStore(SELECTIONS_DIR);
|
|
32
71
|
}
|
|
@@ -76,7 +115,8 @@ function markExpired(record) {
|
|
|
76
115
|
}
|
|
77
116
|
|
|
78
117
|
export function catalogSnapshot(language = "English") {
|
|
79
|
-
const
|
|
118
|
+
const effectiveLanguage = selectionLanguage({ language });
|
|
119
|
+
const options = councilOptions({ language: effectiveLanguage });
|
|
80
120
|
const catalog = {
|
|
81
121
|
schema_version: 1,
|
|
82
122
|
language: options.language,
|
|
@@ -94,8 +134,8 @@ export function beginCouncilSelection(args = {}, { now = Date.now() } = {}) {
|
|
|
94
134
|
// performed before the new record exists and never follows symlinks or unknown names.
|
|
95
135
|
cleanupSelectionStore({ selectionsDir: SELECTIONS_DIR, now });
|
|
96
136
|
const symbol = safeSymbol(args.symbol);
|
|
97
|
-
const language = String(args.language || "English");
|
|
98
137
|
const prompt = typeof args.prompt === "string" ? args.prompt : "";
|
|
138
|
+
const language = selectionLanguage({ language: args.language, prompt });
|
|
99
139
|
const mode = councilMode(args.council_mode);
|
|
100
140
|
const catalog = catalogSnapshot(language);
|
|
101
141
|
const preselected = args.preselected_master_ids === undefined
|
|
@@ -349,12 +389,6 @@ function confirmCouncilSelectionUnlocked(args = {}, { now = Date.now() } = {}) {
|
|
|
349
389
|
catalog_hash: record.catalog_hash,
|
|
350
390
|
request_hash: record.request_hash,
|
|
351
391
|
intent_hash: record.intent_hash,
|
|
352
|
-
selection_hash: digest({
|
|
353
|
-
selection_id: record.selection_id,
|
|
354
|
-
catalog_hash: record.catalog_hash,
|
|
355
|
-
council_mode: record.council_mode || "full",
|
|
356
|
-
selected_master_ids: record.selected_master_ids,
|
|
357
|
-
}),
|
|
358
392
|
selected_master_ids: record.selected_master_ids,
|
|
359
393
|
selected_master_pack_hashes: selectedPackHashes,
|
|
360
394
|
selection_mode: record.selection_mode,
|
|
@@ -363,6 +397,7 @@ function confirmCouncilSelectionUnlocked(args = {}, { now = Date.now() } = {}) {
|
|
|
363
397
|
consumed_at: null,
|
|
364
398
|
consumed_by_run_id: null,
|
|
365
399
|
};
|
|
400
|
+
receiptRecord.selection_hash = receiptSelectionHash(receiptRecord);
|
|
366
401
|
writeJson(selectionPath(record.selection_id), record);
|
|
367
402
|
writeJson(receiptPath(receipt), receiptRecord);
|
|
368
403
|
return confirmationResult(record);
|
|
@@ -388,6 +423,7 @@ function confirmationResult(record) {
|
|
|
388
423
|
selection_receipt: record.selection_receipt,
|
|
389
424
|
status: record.status,
|
|
390
425
|
symbol: record.symbol,
|
|
426
|
+
language: record.language,
|
|
391
427
|
council_mode: record.council_mode || "full",
|
|
392
428
|
catalog_hash: record.catalog_hash,
|
|
393
429
|
intent_hash: record.intent_hash,
|
|
@@ -406,7 +442,50 @@ function confirmationResult(record) {
|
|
|
406
442
|
function consumeCouncilSelectionUnlocked(args = {}, { now = Date.now() } = {}) {
|
|
407
443
|
ensureStore();
|
|
408
444
|
const receipt = readReceipt(args.selection_receipt);
|
|
445
|
+
if (receipt.selection_receipt !== args.selection_receipt) {
|
|
446
|
+
throw invalidParams("The selection receipt file does not match its requested receipt id.", {
|
|
447
|
+
reason: "MASTER_SELECTION_RECORD_MISMATCH",
|
|
448
|
+
mismatched_fields: ["selection_receipt"],
|
|
449
|
+
});
|
|
450
|
+
}
|
|
409
451
|
const selection = readSelection(receipt.selection_id);
|
|
452
|
+
const expectedPackHashes = Object.fromEntries(
|
|
453
|
+
selection.catalog.masters
|
|
454
|
+
.filter((master) => selection.selected_master_ids.includes(master.id))
|
|
455
|
+
.map((master) => [master.id, master.pack_hash]),
|
|
456
|
+
);
|
|
457
|
+
const recordBindings = [
|
|
458
|
+
["schema_version", receipt.schema_version, selection.schema_version],
|
|
459
|
+
["selection_id", receipt.selection_id, selection.selection_id],
|
|
460
|
+
["selection_receipt", receipt.selection_receipt, selection.selection_receipt],
|
|
461
|
+
["symbol", receipt.symbol, selection.symbol],
|
|
462
|
+
["council_mode", receipt.council_mode || "full", selection.council_mode || "full"],
|
|
463
|
+
["catalog_hash", receipt.catalog_hash, selection.catalog_hash],
|
|
464
|
+
["request_hash", receipt.request_hash, selection.request_hash],
|
|
465
|
+
["intent_hash", receipt.intent_hash, selection.intent_hash],
|
|
466
|
+
["selected_master_ids", receipt.selected_master_ids, selection.selected_master_ids],
|
|
467
|
+
["selection_mode", receipt.selection_mode, selection.selection_mode],
|
|
468
|
+
["created_at", receipt.created_at, selection.confirmed_at],
|
|
469
|
+
["expires_at", receipt.expires_at, selection.expires_at],
|
|
470
|
+
];
|
|
471
|
+
const mismatchedBindings = recordBindings
|
|
472
|
+
.filter(([, receiptValue, selectionValue]) => !sameJson(receiptValue, selectionValue))
|
|
473
|
+
.map(([field]) => field);
|
|
474
|
+
if (mismatchedBindings.length) {
|
|
475
|
+
throw invalidParams("The selection receipt does not match its confirmed selection record.", {
|
|
476
|
+
reason: "MASTER_SELECTION_RECORD_MISMATCH",
|
|
477
|
+
mismatched_fields: mismatchedBindings,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (!sameJson(receipt.selected_master_pack_hashes, expectedPackHashes)) {
|
|
481
|
+
throw invalidParams("The selected persona pack hashes do not match the confirmed selection record.", {
|
|
482
|
+
reason: "MASTER_SELECTION_PACK_HASH_MISMATCH",
|
|
483
|
+
mismatched_master_ids: [...new Set([
|
|
484
|
+
...selection.selected_master_ids,
|
|
485
|
+
...Object.keys(receipt.selected_master_pack_hashes || {}),
|
|
486
|
+
])].filter((id) => receipt.selected_master_pack_hashes?.[id] !== expectedPackHashes[id]),
|
|
487
|
+
});
|
|
488
|
+
}
|
|
410
489
|
const symbol = safeSymbol(args.symbol);
|
|
411
490
|
if (receipt.symbol !== symbol || selection.symbol !== symbol) {
|
|
412
491
|
throw invalidParams(`Selection receipt is for ${receipt.symbol}, not ${symbol}.`, {
|
|
@@ -434,8 +513,15 @@ function consumeCouncilSelectionUnlocked(args = {}, { now = Date.now() } = {}) {
|
|
|
434
513
|
mismatched_master_ids: mismatchedPackHashes,
|
|
435
514
|
});
|
|
436
515
|
}
|
|
437
|
-
const
|
|
516
|
+
const expectedSelectionHash = receiptSelectionHash(receipt);
|
|
517
|
+
if (receipt.selection_hash !== expectedSelectionHash) {
|
|
518
|
+
throw invalidParams("The selection receipt hash is invalid.", {
|
|
519
|
+
reason: "MASTER_SELECTION_HASH_MISMATCH",
|
|
520
|
+
expected_selection_hash: expectedSelectionHash,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
438
523
|
const prompt = typeof args.prompt === "string" ? args.prompt : "";
|
|
524
|
+
const language = selectionLanguage({ language: args.language, prompt });
|
|
439
525
|
const mode = councilMode(args.council_mode);
|
|
440
526
|
if ((receipt.council_mode || "full") !== mode || (selection.council_mode || "full") !== mode) {
|
|
441
527
|
throw invalidParams("The selection receipt belongs to a different council mode.", {
|