@bolloon/bolloon-agent 0.1.34 → 0.1.35
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/.auto-evolve-calls +1 -0
- package/.last-auto-evolve-baseline +1 -0
- package/Bolloon.md +103 -0
- package/dist/agents/pi-sdk.js +264 -12
- package/dist/bootstrap/bootstrap.js +114 -0
- package/dist/bootstrap/context-collector.js +296 -0
- package/dist/bootstrap/lifecycle-hooks.js +109 -0
- package/dist/bootstrap/project-context.js +151 -0
- package/dist/index.js +11 -0
- package/dist/llm/pi-ai.js +31 -21
- package/dist/pi-ecosystem-judgment/adaptive-scan.js +231 -0
- package/dist/pi-ecosystem-judgment/causal-judge.js +449 -0
- package/dist/pi-ecosystem-judgment/detect-hook.js +168 -0
- package/dist/pi-ecosystem-judgment/distill-prompt.js +226 -0
- package/dist/pi-ecosystem-judgment/evolve-judgment.js +170 -0
- package/dist/pi-ecosystem-judgment/human-value-pipeline.js +21 -0
- package/dist/pi-ecosystem-judgment/human-value-store.js +283 -22
- package/dist/pi-ecosystem-judgment/injection-gate.js +166 -0
- package/dist/pi-ecosystem-judgment/monitor-gate.js +188 -0
- package/dist/security/builtin-guards.js +124 -0
- package/dist/security/context-router-tool.js +106 -0
- package/dist/security/react-harness.js +143 -0
- package/dist/security/tool-gate.js +235 -0
- package/dist/utils/auto-evolve-policy.js +117 -0
- package/dist/utils/clamp.js +7 -0
- package/dist/utils/double.js +6 -0
- package/dist/web/client.js +668 -204
- package/dist/web/index.html +24 -4
- package/dist/web/server.js +531 -10
- package/lefthook.yml +29 -0
- package/package.json +3 -2
- package/scripts/auto-evolve-loop.ts +376 -0
- package/scripts/auto-evolve-oneshot.sh +155 -0
- package/scripts/auto-evolve-snapshot.sh +136 -0
- package/scripts/detect-schema-changes.sh +48 -0
- package/scripts/diff-reviewer.ts +159 -0
- package/scripts/weekly-report.ts +364 -0
- package/src/agents/pi-sdk.ts +293 -15
- package/src/bootstrap/bootstrap.ts +132 -0
- package/src/bootstrap/context-collector.ts +342 -0
- package/src/bootstrap/lifecycle-hooks.ts +176 -0
- package/src/bootstrap/project-context.ts +163 -0
- package/src/index.ts +11 -0
- package/src/llm/pi-ai.ts +33 -22
- package/src/security/builtin-guards.ts +162 -0
- package/src/security/context-router-tool.ts +122 -0
- package/src/security/react-harness.ts +177 -0
- package/src/security/tool-gate.ts +294 -0
- package/src/utils/auto-evolve-policy.ts +138 -0
- package/src/utils/clamp.ts +5 -0
- package/src/web/client.js +668 -204
- package/src/web/index.html +24 -4
- package/src/web/server.ts +596 -10
- package/staging/auto-evolve/clean-001/.review-verdict +9 -0
- package/staging/auto-evolve/clean-001/clean-001.patch +14 -0
- package/staging/auto-evolve/e2e-001/.patch-id +1 -0
- package/staging/auto-evolve/e2e-001/.review-verdict +12 -0
- package/staging/auto-evolve/e2e-001/e2e-001.patch +11 -0
- package/staging/auto-evolve/test-bad/.review-verdict +12 -0
- package/staging/auto-evolve/test-bad/test-bad.patch +11 -0
package/src/web/server.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
import * as fs from 'fs/promises';
|
|
6
6
|
import * as fsSync from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
8
9
|
import {
|
|
9
10
|
HyperswarmCommunicator,
|
|
10
11
|
createHyperswarmCommunicator,
|
|
@@ -816,7 +817,13 @@ async function handleV3P2PMessage(parsed: any, conn: P2PConnection, comm: Hypers
|
|
|
816
817
|
let fullResponse = '';
|
|
817
818
|
// v3 新增: 流式 token 节流推给 B — 让 B 看到过程
|
|
818
819
|
let lastFlushAt = 0;
|
|
820
|
+
let usedJudgmentIds: string[] = [];
|
|
819
821
|
const streamCallback: any = (event: any) => {
|
|
822
|
+
// P0.5: 注入门回传
|
|
823
|
+
if (event?.type === 'used_judgments' && Array.isArray(event.usedIds)) {
|
|
824
|
+
usedJudgmentIds = event.usedIds;
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
820
827
|
if (event.type === 'token') {
|
|
821
828
|
fullResponse += event.content;
|
|
822
829
|
if (fullResponse.length - lastFlushAt >= 20) {
|
|
@@ -830,7 +837,7 @@ async function handleV3P2PMessage(parsed: any, conn: P2PConnection, comm: Hypers
|
|
|
830
837
|
}
|
|
831
838
|
};
|
|
832
839
|
const agent = await getAgentForChannel(channelId, ch.did || '', ch.name, ch.didDocRef);
|
|
833
|
-
fullResponse = await agent.promptStream(fullPrompt, streamCallback);
|
|
840
|
+
fullResponse = await agent.promptStream(fullPrompt, streamCallback, undefined, channelId);
|
|
834
841
|
|
|
835
842
|
// v3 新增: 存 A 的 assistant 消息到 session — B 拉历史时能看到完整对话
|
|
836
843
|
try {
|
|
@@ -842,6 +849,7 @@ async function handleV3P2PMessage(parsed: any, conn: P2PConnection, comm: Hypers
|
|
|
842
849
|
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
843
850
|
type: 'ai',
|
|
844
851
|
content: fullResponse,
|
|
852
|
+
...(usedJudgmentIds.length > 0 ? { metadata: { usedJudgmentIds } } : {}),
|
|
845
853
|
timestamp: new Date().toISOString()
|
|
846
854
|
});
|
|
847
855
|
session.lastUpdated = new Date().toISOString();
|
|
@@ -1170,6 +1178,18 @@ export async function createWebServer(port: number = 3000, options: CreateWebSer
|
|
|
1170
1178
|
console.error('[警告] 未处理的 Promise 拒绝:', reason);
|
|
1171
1179
|
});
|
|
1172
1180
|
|
|
1181
|
+
// Bolloon Bootstrap (幂等, 重复调不会重复挂定时器)
|
|
1182
|
+
// 这里独立调一次以保证 CLI-only 模式 (无 index.ts 引导) 也能 bootstrap
|
|
1183
|
+
try {
|
|
1184
|
+
const { bootstrapBolloon } = await import(
|
|
1185
|
+
'../pi-ecosystem-judgment/human-value-pipeline.js'
|
|
1186
|
+
);
|
|
1187
|
+
const bs = await bootstrapBolloon({ cwd: process.cwd() });
|
|
1188
|
+
console.log(`[createWebServer] bootstrap 完成 (${bs.durationMs}ms)`);
|
|
1189
|
+
} catch (err) {
|
|
1190
|
+
console.warn('[createWebServer] bootstrap 失败 (非致命):', err);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1173
1193
|
// 重置旧的 agent session,确保使用新的 LLM 配置
|
|
1174
1194
|
const { resetAgentSession } = await import('../agents/pi-sdk.js');
|
|
1175
1195
|
resetAgentSession();
|
|
@@ -1630,11 +1650,42 @@ export async function createWebServer(port: number = 3000, options: CreateWebSer
|
|
|
1630
1650
|
// 捕获外层 channel 到独立变量, 避免被 try 块内 (line 740+) 的 const channel 遮蔽
|
|
1631
1651
|
const channelForJudgment = channel;
|
|
1632
1652
|
|
|
1653
|
+
// per-channel queue 检查: 已在跑就入队, 等当前跑完自动接上
|
|
1654
|
+
const runState = getOrCreateRunState(channelId);
|
|
1655
|
+
if (runState.running) {
|
|
1656
|
+
runState.queue.push({ channelId, text, boundWalletAddress, autoToolsEnabled });
|
|
1657
|
+
broadcastQueueUpdate(channelId);
|
|
1658
|
+
console.log(`[queue] /message 入队 channel=${channelId}, queue len=${runState.queue.length}`);
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
runState.running = true;
|
|
1662
|
+
runState.abortController = new AbortController();
|
|
1663
|
+
broadcastQueueUpdate(channelId);
|
|
1664
|
+
|
|
1633
1665
|
try {
|
|
1634
1666
|
const agent = await getAgentForChannel(channelId, realChannelDid, realChannelName, realChannelDidDoc);
|
|
1635
1667
|
let fullResponse = '';
|
|
1668
|
+
// P0.5: 注入门回传的 usedIds, 落 session message metadata, UI 可查
|
|
1669
|
+
let usedJudgmentIds: string[] = [];
|
|
1636
1670
|
|
|
1637
1671
|
const streamCallback: StreamCallback = (event: StreamEvent) => {
|
|
1672
|
+
// P0.5: 捕获注入门回传
|
|
1673
|
+
if ((event as any).type === 'used_judgments' && Array.isArray((event as any).usedIds)) {
|
|
1674
|
+
usedJudgmentIds = (event as any).usedIds;
|
|
1675
|
+
// 同步推给前端 (用于 finalizeTimelineAsMessage 时给 addMessage 传 usedIds)
|
|
1676
|
+
broadcast({ type: 'used_judgments', usedIds: usedJudgmentIds }, channelId);
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
// 阶段事件 (注入门 / D 触发)
|
|
1680
|
+
if ((event as any).type === 'phase') {
|
|
1681
|
+
broadcast({
|
|
1682
|
+
type: 'phase',
|
|
1683
|
+
phase: (event as any).phase,
|
|
1684
|
+
detail: (event as any).detail,
|
|
1685
|
+
usedCount: (event as any).usedCount,
|
|
1686
|
+
}, channelId);
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1638
1689
|
// 同时发送给流式显示和工作流显示
|
|
1639
1690
|
if (event.type === 'token' || event.type === 'thinking') {
|
|
1640
1691
|
broadcast({ type: 'stream', streamType: event.type, content: event.content }, channelId);
|
|
@@ -1796,7 +1847,20 @@ export async function createWebServer(port: number = 3000, options: CreateWebSer
|
|
|
1796
1847
|
}
|
|
1797
1848
|
|
|
1798
1849
|
if (contextHint) contextHint += '\n';
|
|
1799
|
-
|
|
1850
|
+
try {
|
|
1851
|
+
fullResponse = await agent.promptStream(contextHint + text, streamCallback, runState.abortController?.signal, channelId);
|
|
1852
|
+
} catch (err: any) {
|
|
1853
|
+
// abort 抛错: 保留已输出的部分 (fullResponse 可能是空字符串)
|
|
1854
|
+
if (runState.abortController?.signal.aborted || err?.name === 'AbortError') {
|
|
1855
|
+
console.log(`[chat] aborted channel=${channelId}`);
|
|
1856
|
+
} else {
|
|
1857
|
+
throw err;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
// abort 模式: 给 partial 拼后缀
|
|
1861
|
+
if (runState.abortController?.signal.aborted && fullResponse.trim().length > 0) {
|
|
1862
|
+
fullResponse = fullResponse + '\n\n_[生成已中断]_';
|
|
1863
|
+
}
|
|
1800
1864
|
|
|
1801
1865
|
// v3 新增: 解析 LLM 回复里的 @-mentions, 转发到目标 channel
|
|
1802
1866
|
await routeMentionsInReply(channelId, fullResponse, localChannels, remoteChannels);
|
|
@@ -1808,7 +1872,15 @@ export async function createWebServer(port: number = 3000, options: CreateWebSer
|
|
|
1808
1872
|
session.sessionId = currentSessionId;
|
|
1809
1873
|
// v3: 加 source 标记 (local = 内部 owner, remote = 远端访客)
|
|
1810
1874
|
session.messages.push({ id: crypto.randomUUID(), type: 'user' as const, content: text, timestamp: new Date().toISOString(), source: 'local' as any });
|
|
1811
|
-
session.messages.push({
|
|
1875
|
+
session.messages.push({
|
|
1876
|
+
id: crypto.randomUUID(),
|
|
1877
|
+
type: 'ai' as const,
|
|
1878
|
+
content: fullResponse,
|
|
1879
|
+
timestamp: new Date().toISOString(),
|
|
1880
|
+
source: 'local' as any,
|
|
1881
|
+
// P0.5: 这条 AI 回复引用了哪些 judgment (注入门回传)
|
|
1882
|
+
...(usedJudgmentIds.length > 0 ? { metadata: { usedJudgmentIds } } : {}),
|
|
1883
|
+
});
|
|
1812
1884
|
session.lastUpdated = new Date().toISOString();
|
|
1813
1885
|
await saveSession(session);
|
|
1814
1886
|
|
|
@@ -1823,12 +1895,62 @@ export async function createWebServer(port: number = 3000, options: CreateWebSer
|
|
|
1823
1895
|
}
|
|
1824
1896
|
|
|
1825
1897
|
broadcast({ type: 'done' }, channelId);
|
|
1898
|
+
|
|
1899
|
+
// D 触发: AI 被动捕获判断力 (后台异步, 不阻塞主对话)
|
|
1900
|
+
setImmediate(() => {
|
|
1901
|
+
try {
|
|
1902
|
+
const lastTurns = session.messages.slice(-6).map((m) => ({
|
|
1903
|
+
role: (m.type === 'user' ? 'human' : 'agent') as 'human' | 'agent',
|
|
1904
|
+
content: m.content,
|
|
1905
|
+
}));
|
|
1906
|
+
if (lastTurns.length < 2) return;
|
|
1907
|
+
broadcast({ type: 'phase', phase: 'd_detect', detail: '监测对话...' }, channelId);
|
|
1908
|
+
import('../pi-ecosystem-judgment/human-value-pipeline.js')
|
|
1909
|
+
.then(async ({ detectAndDistillFromChannel, throttleDHook }) => {
|
|
1910
|
+
// channel 维度 5min 节流, 防对话卡顿时 LLM 反复触发
|
|
1911
|
+
if (!throttleDHook(channelId, 5 * 60_000)) {
|
|
1912
|
+
console.log(`[D-hook ${channelId}] throttled (within 5min)`);
|
|
1913
|
+
broadcast({ type: 'phase', phase: 'd_skip', detail: 'throttled' }, channelId);
|
|
1914
|
+
return null;
|
|
1915
|
+
}
|
|
1916
|
+
broadcast({ type: 'phase', phase: 'd_distill', detail: '蒸馏判断力...' }, channelId);
|
|
1917
|
+
return detectAndDistillFromChannel(lastTurns, { channelId });
|
|
1918
|
+
})
|
|
1919
|
+
.then((result) => {
|
|
1920
|
+
if (result && result.triggered) {
|
|
1921
|
+
console.log(
|
|
1922
|
+
`[D-hook ${channelId}] stored: ${result.reason}`,
|
|
1923
|
+
result.evolved
|
|
1924
|
+
);
|
|
1925
|
+
broadcast({ type: 'phase', phase: 'd_done', detail: result.reason }, channelId);
|
|
1926
|
+
} else if (result && result.reason) {
|
|
1927
|
+
console.log(`[D-hook ${channelId}] skipped: ${result.reason}`);
|
|
1928
|
+
broadcast({ type: 'phase', phase: 'd_skip', detail: result.reason }, channelId);
|
|
1929
|
+
}
|
|
1930
|
+
})
|
|
1931
|
+
.catch((err) => {
|
|
1932
|
+
console.warn(`[D-hook ${channelId}] failed:`, err);
|
|
1933
|
+
broadcast({ type: 'phase', phase: 'd_error', detail: String(err) }, channelId);
|
|
1934
|
+
});
|
|
1935
|
+
} catch (err) {
|
|
1936
|
+
console.warn(`[D-hook ${channelId}] sync error:`, err);
|
|
1937
|
+
}
|
|
1938
|
+
});
|
|
1939
|
+
|
|
1826
1940
|
// 2026-06-11: 202 已发的话, 不要重复 res.json (会抛 ERR_HTTP_HEADERS_SENT)
|
|
1827
1941
|
if (!res.headersSent) res.json({ ok: true });
|
|
1828
1942
|
} catch (err: any) {
|
|
1829
1943
|
broadcast({ type: 'error', content: err.message }, channelId);
|
|
1830
1944
|
broadcast({ type: 'done' }, channelId);
|
|
1831
1945
|
if (!res.headersSent) res.status(500).json({ error: err.message });
|
|
1946
|
+
} finally {
|
|
1947
|
+
// queue dequeue: 跑完或失败都要清状态
|
|
1948
|
+
// 当前实现: 自动接下一条需要把 ~200 行 try 块抽函数, 暂不抽.
|
|
1949
|
+
// 替代: 用户点 [队列 +N] 按钮时, 客户端发起一个特殊的 HTTP 请求触发下一条
|
|
1950
|
+
// (在 client.js 实现). 这里只清状态 + 广播.
|
|
1951
|
+
runState.running = false;
|
|
1952
|
+
runState.abortController = null;
|
|
1953
|
+
broadcastQueueUpdate(channelId);
|
|
1832
1954
|
}
|
|
1833
1955
|
});
|
|
1834
1956
|
|
|
@@ -1840,6 +1962,36 @@ export async function createWebServer(port: number = 3000, options: CreateWebSer
|
|
|
1840
1962
|
let didFixRunning = false;
|
|
1841
1963
|
let didFixTimer: NodeJS.Timeout | null = null;
|
|
1842
1964
|
|
|
1965
|
+
// ---------- per-channel 消息 queue + abort 状态 ----------
|
|
1966
|
+
// 同 channel 串行 (避免 LLM 调用互踩上下文), 跨 channel 互不干扰
|
|
1967
|
+
interface PendingMessage {
|
|
1968
|
+
channelId: string;
|
|
1969
|
+
text: string;
|
|
1970
|
+
boundWalletAddress?: string;
|
|
1971
|
+
autoToolsEnabled?: boolean;
|
|
1972
|
+
// (req, res 已经在 /message 里 res.status(202) 返回, 入队的只是要重跑的内容参数)
|
|
1973
|
+
}
|
|
1974
|
+
interface ChannelRunState {
|
|
1975
|
+
running: boolean;
|
|
1976
|
+
queue: PendingMessage[];
|
|
1977
|
+
abortController: AbortController | null;
|
|
1978
|
+
}
|
|
1979
|
+
const channelRunState: Map<string, ChannelRunState> = new Map();
|
|
1980
|
+
function getOrCreateRunState(channelId: string): ChannelRunState {
|
|
1981
|
+
let s = channelRunState.get(channelId);
|
|
1982
|
+
if (!s) {
|
|
1983
|
+
s = { running: false, queue: [], abortController: null };
|
|
1984
|
+
channelRunState.set(channelId, s);
|
|
1985
|
+
}
|
|
1986
|
+
return s;
|
|
1987
|
+
}
|
|
1988
|
+
function broadcastQueueUpdate(channelId: string): void {
|
|
1989
|
+
const s = channelRunState.get(channelId);
|
|
1990
|
+
const queueLength = s ? s.queue.length : 0;
|
|
1991
|
+
const running = s ? s.running : false;
|
|
1992
|
+
try { broadcast({ type: 'queue_update', channelId, queueLength, running }, channelId); } catch { /* */ }
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1843
1995
|
function scheduleDidFix(channelId: string) {
|
|
1844
1996
|
didFixQueue.add(channelId);
|
|
1845
1997
|
if (didFixTimer) return;
|
|
@@ -2555,8 +2707,14 @@ app.get('/channels', async (_req, res) => {
|
|
|
2555
2707
|
|
|
2556
2708
|
const agent = await getAgentForChannel(channelId, realChannelDid, realChannelName, realChannelDidDoc);
|
|
2557
2709
|
let fullResponse = '';
|
|
2710
|
+
let usedJudgmentIds: string[] = [];
|
|
2558
2711
|
|
|
2559
2712
|
const streamCallback: StreamCallback = (event: StreamEvent) => {
|
|
2713
|
+
// P0.5: 注入门回传
|
|
2714
|
+
if ((event as any).type === 'used_judgments' && Array.isArray((event as any).usedIds)) {
|
|
2715
|
+
usedJudgmentIds = (event as any).usedIds;
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2560
2718
|
if (event.type === 'token' || event.type === 'thinking') {
|
|
2561
2719
|
broadcast({ type: 'stream', streamType: event.type, content: event.content }, channelId);
|
|
2562
2720
|
} else if (event.type === 'status' || event.type === 'tool') {
|
|
@@ -2568,7 +2726,7 @@ app.get('/channels', async (_req, res) => {
|
|
|
2568
2726
|
|
|
2569
2727
|
// 重新生成时只发送用户消息 (v3: 同时注入 channel 绑定的判断力)
|
|
2570
2728
|
const regenHint = await buildJudgmentHint(channel, channelId);
|
|
2571
|
-
fullResponse = await agent.promptStream(regenHint + userMessage, streamCallback);
|
|
2729
|
+
fullResponse = await agent.promptStream(regenHint + userMessage, streamCallback, undefined, channelId);
|
|
2572
2730
|
|
|
2573
2731
|
broadcast({ type: 'ai', content: fullResponse }, channelId);
|
|
2574
2732
|
|
|
@@ -2584,7 +2742,8 @@ app.get('/channels', async (_req, res) => {
|
|
|
2584
2742
|
id: crypto.randomUUID(),
|
|
2585
2743
|
type: 'ai' as const,
|
|
2586
2744
|
content: fullResponse,
|
|
2587
|
-
timestamp: new Date().toISOString()
|
|
2745
|
+
timestamp: new Date().toISOString(),
|
|
2746
|
+
...(usedJudgmentIds.length > 0 ? { metadata: { usedJudgmentIds } } : {}),
|
|
2588
2747
|
});
|
|
2589
2748
|
existingSession.lastUpdated = new Date().toISOString();
|
|
2590
2749
|
await saveSession(existingSession);
|
|
@@ -4002,6 +4161,23 @@ app.get('/channels', async (_req, res) => {
|
|
|
4002
4161
|
}
|
|
4003
4162
|
});
|
|
4004
4163
|
|
|
4164
|
+
// 终止当前 channel 的 LLM 流 (UI 终止按钮)
|
|
4165
|
+
app.post('/api/chat/abort', async (req, res) => {
|
|
4166
|
+
try {
|
|
4167
|
+
const { channelId } = req.body as { channelId?: string };
|
|
4168
|
+
if (!channelId) return res.status(400).json({ error: 'channelId required' });
|
|
4169
|
+
const s = channelRunState.get(channelId);
|
|
4170
|
+
if (s?.abortController) {
|
|
4171
|
+
s.abortController.abort();
|
|
4172
|
+
console.log(`[abort] user aborted channel=${channelId}`);
|
|
4173
|
+
return res.json({ ok: true, aborted: true });
|
|
4174
|
+
}
|
|
4175
|
+
res.json({ ok: true, aborted: false });
|
|
4176
|
+
} catch (err: any) {
|
|
4177
|
+
res.status(500).json({ error: err.message });
|
|
4178
|
+
}
|
|
4179
|
+
});
|
|
4180
|
+
|
|
4005
4181
|
// 主人审阅: 批准 draft
|
|
4006
4182
|
app.post('/api/chat/approve', async (req, res) => {
|
|
4007
4183
|
try {
|
|
@@ -4417,22 +4593,432 @@ app.get('/channels', async (_req, res) => {
|
|
|
4417
4593
|
}
|
|
4418
4594
|
});
|
|
4419
4595
|
|
|
4420
|
-
app.get('/api/judgments', async (
|
|
4596
|
+
app.get('/api/judgments', async (req, res) => {
|
|
4421
4597
|
try {
|
|
4422
|
-
const {
|
|
4598
|
+
const { listJudgmentsByStatus, initializeValueStore } = await import(
|
|
4423
4599
|
'../pi-ecosystem-judgment/human-value-store.js'
|
|
4424
4600
|
);
|
|
4425
4601
|
await initializeValueStore();
|
|
4426
|
-
const
|
|
4427
|
-
|
|
4602
|
+
const status = (typeof req.query.status === 'string' ? req.query.status : 'all') as
|
|
4603
|
+
| 'active'
|
|
4604
|
+
| 'pending'
|
|
4605
|
+
| 'superseded'
|
|
4606
|
+
| 'rejected'
|
|
4607
|
+
| 'all';
|
|
4608
|
+
const all = await listJudgmentsByStatus(status);
|
|
4428
4609
|
all.sort((a, b) => (b.timestamp || '').localeCompare(a.timestamp || ''));
|
|
4429
|
-
res.json({ count: all.length, judgments: all });
|
|
4610
|
+
res.json({ count: all.length, status, judgments: all });
|
|
4430
4611
|
} catch (err: any) {
|
|
4431
4612
|
console.error('[judgments] GET failed:', err);
|
|
4432
4613
|
res.status(500).json({ error: err.message });
|
|
4433
4614
|
}
|
|
4434
4615
|
});
|
|
4435
4616
|
|
|
4617
|
+
// 蒸馏 B 触发 (人类点按钮) — 同步执行演化对齐
|
|
4618
|
+
app.post('/api/judgments/distill-from-conversation', async (req, res) => {
|
|
4619
|
+
try {
|
|
4620
|
+
const { channelId, messageId, recentTurns } = req.body as {
|
|
4621
|
+
channelId?: string;
|
|
4622
|
+
messageId?: string;
|
|
4623
|
+
recentTurns?: number;
|
|
4624
|
+
};
|
|
4625
|
+
if (!channelId) {
|
|
4626
|
+
return res.status(400).json({ error: 'channelId required' });
|
|
4627
|
+
}
|
|
4628
|
+
|
|
4629
|
+
// 取 channel 最近的对话
|
|
4630
|
+
const channels = await loadChannels();
|
|
4631
|
+
const channel = channels.find((c) => c.id === channelId);
|
|
4632
|
+
if (!channel) return res.status(404).json({ error: 'channel not found' });
|
|
4633
|
+
|
|
4634
|
+
const currentSessionId = channel.currentSessionId;
|
|
4635
|
+
if (!currentSessionId) {
|
|
4636
|
+
return res.status(400).json({ error: 'no active session in channel' });
|
|
4637
|
+
}
|
|
4638
|
+
const session = await loadSession(channelId, currentSessionId);
|
|
4639
|
+
if (!session) return res.status(404).json({ error: 'session not found' });
|
|
4640
|
+
|
|
4641
|
+
// 取最近 N 轮 (默认 10), 转成 DistillTurn 格式
|
|
4642
|
+
const limit = Math.min(Math.max(recentTurns ?? 10, 2), 30);
|
|
4643
|
+
const turns = session.messages.slice(-limit).map((m) => ({
|
|
4644
|
+
role: (m.type === 'user' ? 'human' : 'agent') as 'human' | 'agent',
|
|
4645
|
+
content: m.content,
|
|
4646
|
+
}));
|
|
4647
|
+
|
|
4648
|
+
const { distillAndStoreFromChannel } = await import(
|
|
4649
|
+
'../pi-ecosystem-judgment/human-value-pipeline.js'
|
|
4650
|
+
);
|
|
4651
|
+
const result = await distillAndStoreFromChannel(turns, { channelId });
|
|
4652
|
+
|
|
4653
|
+
res.json({
|
|
4654
|
+
ok: true,
|
|
4655
|
+
triggered: result.triggered,
|
|
4656
|
+
reason: result.reason,
|
|
4657
|
+
judgment: result.judgment,
|
|
4658
|
+
evolved: result.evolved,
|
|
4659
|
+
});
|
|
4660
|
+
} catch (err: any) {
|
|
4661
|
+
console.error('[judgments] distill-from-conversation failed:', err);
|
|
4662
|
+
res.status(500).json({ error: err.message });
|
|
4663
|
+
}
|
|
4664
|
+
});
|
|
4665
|
+
|
|
4666
|
+
// 蒸馏 D 触发 (AI 被动) — 后台异步,不阻塞 HTTP 响应
|
|
4667
|
+
app.post('/api/judgments/detect-and-distill', async (req, res) => {
|
|
4668
|
+
try {
|
|
4669
|
+
const { channelId, turns } = req.body as {
|
|
4670
|
+
channelId?: string;
|
|
4671
|
+
turns?: Array<{ role: 'human' | 'agent'; content: string }>;
|
|
4672
|
+
};
|
|
4673
|
+
|
|
4674
|
+
// 先立即返回 202, 不等 LLM
|
|
4675
|
+
res.status(202).json({ ok: true, queued: true });
|
|
4676
|
+
|
|
4677
|
+
if (!channelId || !Array.isArray(turns) || turns.length === 0) {
|
|
4678
|
+
return;
|
|
4679
|
+
}
|
|
4680
|
+
|
|
4681
|
+
// 异步处理 (不 await, 不阻塞响应)
|
|
4682
|
+
setImmediate(async () => {
|
|
4683
|
+
try {
|
|
4684
|
+
const { detectAndDistillFromChannel } = await import(
|
|
4685
|
+
'../pi-ecosystem-judgment/human-value-pipeline.js'
|
|
4686
|
+
);
|
|
4687
|
+
const result = await detectAndDistillFromChannel(turns, { channelId });
|
|
4688
|
+
if (result.triggered) {
|
|
4689
|
+
console.log(`[D-hook] ${channelId}: ${result.reason}`, result.evolved);
|
|
4690
|
+
}
|
|
4691
|
+
} catch (err) {
|
|
4692
|
+
console.warn('[D-hook] background failed:', err);
|
|
4693
|
+
}
|
|
4694
|
+
});
|
|
4695
|
+
} catch (err: any) {
|
|
4696
|
+
console.error('[judgments] detect-and-distill failed:', err);
|
|
4697
|
+
res.status(500).json({ error: err.message });
|
|
4698
|
+
}
|
|
4699
|
+
});
|
|
4700
|
+
|
|
4701
|
+
// 判断力使用回溯 (P0.5): 给定 judgmentIds, 反查对应的 decision 文本
|
|
4702
|
+
// 用途: UI 上"这条 AI 回复引用了哪些原则"
|
|
4703
|
+
app.post('/api/judgments/resolve-usage', async (req, res) => {
|
|
4704
|
+
try {
|
|
4705
|
+
const { ids } = req.body as { ids?: string[] };
|
|
4706
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
4707
|
+
return res.json({ items: [] });
|
|
4708
|
+
}
|
|
4709
|
+
const { loadAllJudgments } = await import(
|
|
4710
|
+
'../pi-ecosystem-judgment/human-value-store.js'
|
|
4711
|
+
);
|
|
4712
|
+
const all = await loadAllJudgments();
|
|
4713
|
+
const byId = new Map(all.map((j) => [j.id, j]));
|
|
4714
|
+
const items = ids
|
|
4715
|
+
.map((id) => byId.get(id))
|
|
4716
|
+
.filter((j): j is NonNullable<typeof j> => Boolean(j))
|
|
4717
|
+
.map((j) => ({
|
|
4718
|
+
id: j.id,
|
|
4719
|
+
decision: j.decision,
|
|
4720
|
+
status: j.status ?? 'active',
|
|
4721
|
+
timestamp: j.timestamp,
|
|
4722
|
+
}));
|
|
4723
|
+
res.json({ items });
|
|
4724
|
+
} catch (err: any) {
|
|
4725
|
+
console.error('[judgments] resolve-usage failed:', err);
|
|
4726
|
+
res.status(500).json({ error: err.message });
|
|
4727
|
+
}
|
|
4728
|
+
});
|
|
4729
|
+
|
|
4730
|
+
// 判断力违规日志 (P3 UI): 读 violations.jsonl
|
|
4731
|
+
app.get('/api/judgments/violations', async (req, res) => {
|
|
4732
|
+
try {
|
|
4733
|
+
const { getRecentViolations } = await import(
|
|
4734
|
+
'../pi-ecosystem-judgment/monitor-gate.js'
|
|
4735
|
+
);
|
|
4736
|
+
const limit = Math.min(Math.max(parseInt(String(req.query.limit ?? '20'), 10) || 20, 1), 200);
|
|
4737
|
+
const items = await getRecentViolations(limit);
|
|
4738
|
+
res.json({ count: items.length, items });
|
|
4739
|
+
} catch (err: any) {
|
|
4740
|
+
console.error('[judgments] violations failed:', err);
|
|
4741
|
+
res.status(500).json({ error: err.message });
|
|
4742
|
+
}
|
|
4743
|
+
});
|
|
4744
|
+
|
|
4745
|
+
// 类 B 自适应扫描: 读 judgments.json + usage.jsonl, 给出 stale/rising/unused 建议
|
|
4746
|
+
// ?force=1 跳过 24h 缓存
|
|
4747
|
+
app.get('/api/judgments/adaptive-suggestions', async (req, res) => {
|
|
4748
|
+
try {
|
|
4749
|
+
const { getCachedScan } = await import(
|
|
4750
|
+
'../pi-ecosystem-judgment/adaptive-scan.js'
|
|
4751
|
+
);
|
|
4752
|
+
const force = String(req.query.force ?? '') === '1';
|
|
4753
|
+
const result = await getCachedScan(force);
|
|
4754
|
+
res.json(result);
|
|
4755
|
+
} catch (err: any) {
|
|
4756
|
+
console.error('[judgments] adaptive-scan failed:', err);
|
|
4757
|
+
res.status(500).json({ error: err.message });
|
|
4758
|
+
}
|
|
4759
|
+
});
|
|
4760
|
+
|
|
4761
|
+
// Bootstrap Context 调试视图: 返出完整 BolloonContext
|
|
4762
|
+
app.get('/api/bolloon/context', async (req, res) => {
|
|
4763
|
+
try {
|
|
4764
|
+
const { getCachedBolloonContext } = await import(
|
|
4765
|
+
'../pi-ecosystem-judgment/human-value-pipeline.js'
|
|
4766
|
+
);
|
|
4767
|
+
const force = String(req.query.force ?? '') === '1';
|
|
4768
|
+
const ctx = await getCachedBolloonContext({ cwd: process.cwd() }, force);
|
|
4769
|
+
res.json(ctx);
|
|
4770
|
+
} catch (err: any) {
|
|
4771
|
+
console.error('[bolloon] context failed:', err);
|
|
4772
|
+
res.status(500).json({ error: err.message });
|
|
4773
|
+
}
|
|
4774
|
+
});
|
|
4775
|
+
|
|
4776
|
+
// 阶段 B: 周报 (weekly-report.ts 产物) — 仅 API 读取, 不做 UI tab
|
|
4777
|
+
// GET /api/reports → { files: ['2026-W24.md', ...] }
|
|
4778
|
+
// GET /api/reports/2026-W24 → { week, content }
|
|
4779
|
+
app.get('/api/reports', async (_req, res) => {
|
|
4780
|
+
try {
|
|
4781
|
+
const dir = path.join(os.homedir(), '.bolloon', 'reports');
|
|
4782
|
+
try {
|
|
4783
|
+
const entries = await fs.readdir(dir);
|
|
4784
|
+
const files = entries
|
|
4785
|
+
.filter((f) => f.endsWith('.md'))
|
|
4786
|
+
.sort()
|
|
4787
|
+
.reverse(); // 新的在前
|
|
4788
|
+
res.json({ dir, files });
|
|
4789
|
+
} catch {
|
|
4790
|
+
res.json({ dir, files: [] });
|
|
4791
|
+
}
|
|
4792
|
+
} catch (err: any) {
|
|
4793
|
+
res.status(500).json({ error: err.message });
|
|
4794
|
+
}
|
|
4795
|
+
});
|
|
4796
|
+
|
|
4797
|
+
app.get('/api/reports/:week', async (req, res) => {
|
|
4798
|
+
try {
|
|
4799
|
+
const week = req.params.week;
|
|
4800
|
+
// 严格校验, 防路径穿越
|
|
4801
|
+
if (!/^\d{4}-W\d{1,2}$/.test(week)) {
|
|
4802
|
+
return res.status(400).json({ error: 'week must match YYYY-Www' });
|
|
4803
|
+
}
|
|
4804
|
+
const file = path.join(os.homedir(), '.bolloon', 'reports', `${week}.md`);
|
|
4805
|
+
try {
|
|
4806
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
4807
|
+
res.json({ week, content, length: content.length });
|
|
4808
|
+
} catch {
|
|
4809
|
+
res.status(404).json({ error: 'not found', week });
|
|
4810
|
+
}
|
|
4811
|
+
} catch (err: any) {
|
|
4812
|
+
res.status(500).json({ error: err.message });
|
|
4813
|
+
}
|
|
4814
|
+
});
|
|
4815
|
+
|
|
4816
|
+
// 阶段 C 护栏 5: auto-evolve baseline 管理 (无 UI, 仅 API)
|
|
4817
|
+
// GET /api/auto-evolve/baselines → 列出所有 baseline tag
|
|
4818
|
+
// GET /api/auto-evolve/baselines/:tag/diff → 看某 baseline 的 diff 摘要
|
|
4819
|
+
// POST /api/auto-evolve/rollback {tag} → 回滚到指定 baseline
|
|
4820
|
+
app.get('/api/auto-evolve/baselines', async (_req, res) => {
|
|
4821
|
+
try {
|
|
4822
|
+
const { execFile } = await import('child_process');
|
|
4823
|
+
const { promisify } = await import('util');
|
|
4824
|
+
const pExec = promisify(execFile);
|
|
4825
|
+
const { stdout } = await pExec('git', [
|
|
4826
|
+
'tag', '-l', 'auto-evolve-baseline-*', '--format=%(refname:short)|%(contents)|%(objectname:short)|%(taggerdate:iso)',
|
|
4827
|
+
], { cwd: process.cwd() });
|
|
4828
|
+
const tags = stdout.trim().split('\n').filter(Boolean).map((line) => {
|
|
4829
|
+
const [tag, msg, sha, date] = line.split('|');
|
|
4830
|
+
return { tag, message: msg || '', sha, date };
|
|
4831
|
+
});
|
|
4832
|
+
res.json({ tags, count: tags.length });
|
|
4833
|
+
} catch (err: any) {
|
|
4834
|
+
res.status(500).json({ error: err.message });
|
|
4835
|
+
}
|
|
4836
|
+
});
|
|
4837
|
+
|
|
4838
|
+
app.get('/api/auto-evolve/baselines/:tag/diff', async (req, res) => {
|
|
4839
|
+
try {
|
|
4840
|
+
const { execFile } = await import('child_process');
|
|
4841
|
+
const { promisify } = await import('util');
|
|
4842
|
+
const pExec = promisify(execFile);
|
|
4843
|
+
const tag = req.params.tag;
|
|
4844
|
+
if (!/^auto-evolve-baseline-[\w-]+$/.test(tag)) {
|
|
4845
|
+
return res.status(400).json({ error: 'tag must match auto-evolve-baseline-*' });
|
|
4846
|
+
}
|
|
4847
|
+
const { stdout } = await pExec('git', ['show', '--stat', '--no-color', tag], { cwd: process.cwd() });
|
|
4848
|
+
res.json({ tag, diff: stdout.slice(0, 5000) }); // 限长 5KB
|
|
4849
|
+
} catch (err: any) {
|
|
4850
|
+
res.status(500).json({ error: err.message });
|
|
4851
|
+
}
|
|
4852
|
+
});
|
|
4853
|
+
|
|
4854
|
+
// Bootstrap Context → 拼好的 system prompt 片段 (供调试看注入效果)
|
|
4855
|
+
app.get('/api/bolloon/context/system-prompt', async (req, res) => {
|
|
4856
|
+
try {
|
|
4857
|
+
const { getCachedBolloonContext } = await import(
|
|
4858
|
+
'../pi-ecosystem-judgment/human-value-pipeline.js'
|
|
4859
|
+
);
|
|
4860
|
+
const { formatContextForSystemPrompt } = await import(
|
|
4861
|
+
'../bootstrap/project-context.js'
|
|
4862
|
+
);
|
|
4863
|
+
const ctx = await getCachedBolloonContext({ cwd: process.cwd() });
|
|
4864
|
+
const systemAddition = formatContextForSystemPrompt(ctx, {
|
|
4865
|
+
maxChars: parseInt(String(req.query.max ?? '4000'), 10) || 4000,
|
|
4866
|
+
});
|
|
4867
|
+
res.json({ systemAddition, length: systemAddition.length, truncated: systemAddition.includes('截断模式') });
|
|
4868
|
+
} catch (err: any) {
|
|
4869
|
+
console.error('[bolloon] context/system-prompt failed:', err);
|
|
4870
|
+
res.status(500).json({ error: err.message });
|
|
4871
|
+
}
|
|
4872
|
+
});
|
|
4873
|
+
|
|
4874
|
+
// 自适应接受/拒绝: 写 evolution.jsonl 留痕, 接受时同时 patch judgments.json
|
|
4875
|
+
// body: { action: 'accept'|'reject'|'revert', suggestion, appliedPatch? }
|
|
4876
|
+
// query: ?auto=1 → 类 B 自动路径, 受 auto-evolve-policy 网关保护
|
|
4877
|
+
// 缺省 → 用户在 UI 手动触发, 不查开关 (避免阻塞用户)
|
|
4878
|
+
app.post('/api/judgments/adaptive-apply', async (req, res) => {
|
|
4879
|
+
try {
|
|
4880
|
+
const isAuto = req.query.auto === '1' || req.query.auto === 'true';
|
|
4881
|
+
const { action, suggestion, appliedPatch } = req.body as {
|
|
4882
|
+
action: 'accept' | 'reject' | 'revert';
|
|
4883
|
+
suggestion: { judgmentId: string; kind: string; decision: string; reason: string; action: string; metrics: unknown; scannedAt: string; key: string };
|
|
4884
|
+
appliedPatch?: Record<string, unknown>;
|
|
4885
|
+
};
|
|
4886
|
+
if (!action || !suggestion?.judgmentId) {
|
|
4887
|
+
return res.status(400).json({ error: 'action and suggestion.judgmentId required' });
|
|
4888
|
+
}
|
|
4889
|
+
const { updateJudgmentStatus } = await import(
|
|
4890
|
+
'../pi-ecosystem-judgment/human-value-store.js'
|
|
4891
|
+
);
|
|
4892
|
+
const { logEvolution } = await import(
|
|
4893
|
+
'../pi-ecosystem-judgment/adaptive-scan.js'
|
|
4894
|
+
);
|
|
4895
|
+
// accept 时: 真正改库
|
|
4896
|
+
if (action === 'accept') {
|
|
4897
|
+
// 阶段 A: 自动路径需先过 auto-evolve-policy 网关
|
|
4898
|
+
if (isAuto) {
|
|
4899
|
+
const { requireDataLayerAutoEvolve } = await import(
|
|
4900
|
+
'../utils/auto-evolve-policy.js'
|
|
4901
|
+
);
|
|
4902
|
+
try {
|
|
4903
|
+
await requireDataLayerAutoEvolve('adaptive-apply.auto.deprecate');
|
|
4904
|
+
} catch (err: any) {
|
|
4905
|
+
return res.status(423).json({
|
|
4906
|
+
error: 'data-layer-auto-evolve-disabled',
|
|
4907
|
+
message: err.message,
|
|
4908
|
+
hint: '设 BOLLOON_AUTO_EVOLVE_DATA=1 或在 self-improve-policy.json 加 dataLayerAutoEvolve: true',
|
|
4909
|
+
});
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
if (suggestion.action === 'deprecate') {
|
|
4913
|
+
// 标记 superseded (语义: 不再用, 但保留可回滚)
|
|
4914
|
+
await updateJudgmentStatus(suggestion.judgmentId, 'superseded', {
|
|
4915
|
+
evolutionReason: 'merged', // 借 merged 字段表达"被自适应废弃"
|
|
4916
|
+
});
|
|
4917
|
+
} else if (suggestion.action === 'boost') {
|
|
4918
|
+
// boost: 用户手动接受后, 不改库本身 (weight 在 getRelevantValues 里动态算),
|
|
4919
|
+
// 但写 evolution 留痕, 未来可以基于此调整算法
|
|
4920
|
+
// 当前不直接改库, 仅留痕
|
|
4921
|
+
}
|
|
4922
|
+
// 'review' 类不需要自动改库, 仅 log 接受
|
|
4923
|
+
}
|
|
4924
|
+
await logEvolution({
|
|
4925
|
+
ts: new Date().toISOString(),
|
|
4926
|
+
action,
|
|
4927
|
+
suggestion: suggestion as any,
|
|
4928
|
+
appliedPatch,
|
|
4929
|
+
});
|
|
4930
|
+
res.json({ ok: true });
|
|
4931
|
+
} catch (err: any) {
|
|
4932
|
+
console.error('[judgments] adaptive-apply failed:', err);
|
|
4933
|
+
res.status(500).json({ error: err.message });
|
|
4934
|
+
}
|
|
4935
|
+
});
|
|
4936
|
+
|
|
4937
|
+
// 演化日志 (audit / 一键回滚源)
|
|
4938
|
+
app.get('/api/judgments/evolution-log', async (req, res) => {
|
|
4939
|
+
try {
|
|
4940
|
+
const { readEvolutionLog } = await import(
|
|
4941
|
+
'../pi-ecosystem-judgment/adaptive-scan.js'
|
|
4942
|
+
);
|
|
4943
|
+
const limit = Math.min(Math.max(parseInt(String(req.query.limit ?? '50'), 10) || 50, 1), 200);
|
|
4944
|
+
const items = await readEvolutionLog(limit);
|
|
4945
|
+
res.json({ count: items.length, items });
|
|
4946
|
+
} catch (err: any) {
|
|
4947
|
+
console.error('[judgments] evolution-log failed:', err);
|
|
4948
|
+
res.status(500).json({ error: err.message });
|
|
4949
|
+
}
|
|
4950
|
+
});
|
|
4951
|
+
|
|
4952
|
+
// 阶段 2: Causal-judge 4 个 endpoint
|
|
4953
|
+
app.get('/api/judgments/causal/correlation', async (req, res) => {
|
|
4954
|
+
try {
|
|
4955
|
+
const { runCorrelationAnalysis } = await import(
|
|
4956
|
+
'../pi-ecosystem-judgment/human-value-pipeline.js'
|
|
4957
|
+
);
|
|
4958
|
+
const topN = Math.min(Math.max(parseInt(String(req.query.topN ?? '5'), 10) || 5, 1), 50);
|
|
4959
|
+
const useLLM = String(req.query.useLLM ?? '1') !== '0';
|
|
4960
|
+
const items = await runCorrelationAnalysis({ topN, useLLM });
|
|
4961
|
+
res.json({ count: items.length, items });
|
|
4962
|
+
} catch (err: any) {
|
|
4963
|
+
console.error('[causal] correlation failed:', err);
|
|
4964
|
+
res.status(500).json({ error: err.message });
|
|
4965
|
+
}
|
|
4966
|
+
});
|
|
4967
|
+
|
|
4968
|
+
app.get('/api/judgments/causal/intervention', async (req, res) => {
|
|
4969
|
+
try {
|
|
4970
|
+
const { runIntervention } = await import(
|
|
4971
|
+
'../pi-ecosystem-judgment/human-value-pipeline.js'
|
|
4972
|
+
);
|
|
4973
|
+
const { judgmentId, scenario } = req.query as { judgmentId?: string; scenario?: string };
|
|
4974
|
+
if (!judgmentId) return res.status(400).json({ error: 'judgmentId required' });
|
|
4975
|
+
const result = await runIntervention(judgmentId, { scenarioContext: scenario });
|
|
4976
|
+
res.json(result);
|
|
4977
|
+
} catch (err: any) {
|
|
4978
|
+
console.error('[causal] intervention failed:', err);
|
|
4979
|
+
res.status(500).json({ error: err.message });
|
|
4980
|
+
}
|
|
4981
|
+
});
|
|
4982
|
+
|
|
4983
|
+
app.post('/api/judgments/causal/counterfactual', async (req, res) => {
|
|
4984
|
+
try {
|
|
4985
|
+
const { runCounterfactualAudit } = await import(
|
|
4986
|
+
'../pi-ecosystem-judgment/human-value-pipeline.js'
|
|
4987
|
+
);
|
|
4988
|
+
const { userInput, aiReply, violatedPrinciples } = req.body as {
|
|
4989
|
+
userInput?: string;
|
|
4990
|
+
aiReply?: string;
|
|
4991
|
+
violatedPrinciples?: Array<{ principle: string; reason: string }>;
|
|
4992
|
+
};
|
|
4993
|
+
if (!userInput || !aiReply) {
|
|
4994
|
+
return res.status(400).json({ error: 'userInput and aiReply required' });
|
|
4995
|
+
}
|
|
4996
|
+
const audit = await runCounterfactualAudit({
|
|
4997
|
+
userInput,
|
|
4998
|
+
aiReply,
|
|
4999
|
+
violatedPrinciples: violatedPrinciples ?? [],
|
|
5000
|
+
});
|
|
5001
|
+
res.json(audit);
|
|
5002
|
+
} catch (err: any) {
|
|
5003
|
+
console.error('[causal] counterfactual failed:', err);
|
|
5004
|
+
res.status(500).json({ error: err.message });
|
|
5005
|
+
}
|
|
5006
|
+
});
|
|
5007
|
+
|
|
5008
|
+
app.get('/api/judgments/causal/audit-log', async (req, res) => {
|
|
5009
|
+
try {
|
|
5010
|
+
const { readCounterfactualLog } = await import(
|
|
5011
|
+
'../pi-ecosystem-judgment/human-value-pipeline.js'
|
|
5012
|
+
);
|
|
5013
|
+
const limit = Math.min(Math.max(parseInt(String(req.query.limit ?? '20'), 10) || 20, 1), 200);
|
|
5014
|
+
const items = await readCounterfactualLog(limit);
|
|
5015
|
+
res.json({ count: items.length, items });
|
|
5016
|
+
} catch (err: any) {
|
|
5017
|
+
console.error('[causal] audit-log failed:', err);
|
|
5018
|
+
res.status(500).json({ error: err.message });
|
|
5019
|
+
}
|
|
5020
|
+
});
|
|
5021
|
+
|
|
4436
5022
|
// 导入判断: 接受 { filename, content (base64), context }.
|
|
4437
5023
|
// 支持 .json / .yaml / .yml / .md / .txt / .html. 完全离线解析, 不调 LLM.
|
|
4438
5024
|
// 解析规则:
|