@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/dist/web/server.js
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 { createHyperswarmCommunicator, createTopic, KeyManager, AgentAuthManager, } from '@diap/sdk';
|
|
9
10
|
import { documentReader } from '../documents/reader.js';
|
|
10
11
|
import { initMinimax, getMinimax } from '../constraints/index.js';
|
|
@@ -657,7 +658,13 @@ async function handleV3P2PMessage(parsed, conn, comm) {
|
|
|
657
658
|
let fullResponse = '';
|
|
658
659
|
// v3 新增: 流式 token 节流推给 B — 让 B 看到过程
|
|
659
660
|
let lastFlushAt = 0;
|
|
661
|
+
let usedJudgmentIds = [];
|
|
660
662
|
const streamCallback = (event) => {
|
|
663
|
+
// P0.5: 注入门回传
|
|
664
|
+
if (event?.type === 'used_judgments' && Array.isArray(event.usedIds)) {
|
|
665
|
+
usedJudgmentIds = event.usedIds;
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
661
668
|
if (event.type === 'token') {
|
|
662
669
|
fullResponse += event.content;
|
|
663
670
|
if (fullResponse.length - lastFlushAt >= 20) {
|
|
@@ -671,7 +678,7 @@ async function handleV3P2PMessage(parsed, conn, comm) {
|
|
|
671
678
|
}
|
|
672
679
|
};
|
|
673
680
|
const agent = await getAgentForChannel(channelId, ch.did || '', ch.name, ch.didDocRef);
|
|
674
|
-
fullResponse = await agent.promptStream(fullPrompt, streamCallback);
|
|
681
|
+
fullResponse = await agent.promptStream(fullPrompt, streamCallback, undefined, channelId);
|
|
675
682
|
// v3 新增: 存 A 的 assistant 消息到 session — B 拉历史时能看到完整对话
|
|
676
683
|
try {
|
|
677
684
|
const existing = await loadSession(channelId, 'default');
|
|
@@ -682,6 +689,7 @@ async function handleV3P2PMessage(parsed, conn, comm) {
|
|
|
682
689
|
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
683
690
|
type: 'ai',
|
|
684
691
|
content: fullResponse,
|
|
692
|
+
...(usedJudgmentIds.length > 0 ? { metadata: { usedJudgmentIds } } : {}),
|
|
685
693
|
timestamp: new Date().toISOString()
|
|
686
694
|
});
|
|
687
695
|
session.lastUpdated = new Date().toISOString();
|
|
@@ -967,6 +975,16 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
967
975
|
process.on('unhandledRejection', (reason, promise) => {
|
|
968
976
|
console.error('[警告] 未处理的 Promise 拒绝:', reason);
|
|
969
977
|
});
|
|
978
|
+
// Bolloon Bootstrap (幂等, 重复调不会重复挂定时器)
|
|
979
|
+
// 这里独立调一次以保证 CLI-only 模式 (无 index.ts 引导) 也能 bootstrap
|
|
980
|
+
try {
|
|
981
|
+
const { bootstrapBolloon } = await import('../pi-ecosystem-judgment/human-value-pipeline.js');
|
|
982
|
+
const bs = await bootstrapBolloon({ cwd: process.cwd() });
|
|
983
|
+
console.log(`[createWebServer] bootstrap 完成 (${bs.durationMs}ms)`);
|
|
984
|
+
}
|
|
985
|
+
catch (err) {
|
|
986
|
+
console.warn('[createWebServer] bootstrap 失败 (非致命):', err);
|
|
987
|
+
}
|
|
970
988
|
// 重置旧的 agent session,确保使用新的 LLM 配置
|
|
971
989
|
const { resetAgentSession } = await import('../agents/pi-sdk.js');
|
|
972
990
|
resetAgentSession();
|
|
@@ -1410,10 +1428,40 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
1410
1428
|
const autoToolsEnabled = channel?.autoInvokeTools !== false; // 默认开启
|
|
1411
1429
|
// 捕获外层 channel 到独立变量, 避免被 try 块内 (line 740+) 的 const channel 遮蔽
|
|
1412
1430
|
const channelForJudgment = channel;
|
|
1431
|
+
// per-channel queue 检查: 已在跑就入队, 等当前跑完自动接上
|
|
1432
|
+
const runState = getOrCreateRunState(channelId);
|
|
1433
|
+
if (runState.running) {
|
|
1434
|
+
runState.queue.push({ channelId, text, boundWalletAddress, autoToolsEnabled });
|
|
1435
|
+
broadcastQueueUpdate(channelId);
|
|
1436
|
+
console.log(`[queue] /message 入队 channel=${channelId}, queue len=${runState.queue.length}`);
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
runState.running = true;
|
|
1440
|
+
runState.abortController = new AbortController();
|
|
1441
|
+
broadcastQueueUpdate(channelId);
|
|
1413
1442
|
try {
|
|
1414
1443
|
const agent = await getAgentForChannel(channelId, realChannelDid, realChannelName, realChannelDidDoc);
|
|
1415
1444
|
let fullResponse = '';
|
|
1445
|
+
// P0.5: 注入门回传的 usedIds, 落 session message metadata, UI 可查
|
|
1446
|
+
let usedJudgmentIds = [];
|
|
1416
1447
|
const streamCallback = (event) => {
|
|
1448
|
+
// P0.5: 捕获注入门回传
|
|
1449
|
+
if (event.type === 'used_judgments' && Array.isArray(event.usedIds)) {
|
|
1450
|
+
usedJudgmentIds = event.usedIds;
|
|
1451
|
+
// 同步推给前端 (用于 finalizeTimelineAsMessage 时给 addMessage 传 usedIds)
|
|
1452
|
+
broadcast({ type: 'used_judgments', usedIds: usedJudgmentIds }, channelId);
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
// 阶段事件 (注入门 / D 触发)
|
|
1456
|
+
if (event.type === 'phase') {
|
|
1457
|
+
broadcast({
|
|
1458
|
+
type: 'phase',
|
|
1459
|
+
phase: event.phase,
|
|
1460
|
+
detail: event.detail,
|
|
1461
|
+
usedCount: event.usedCount,
|
|
1462
|
+
}, channelId);
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1417
1465
|
// 同时发送给流式显示和工作流显示
|
|
1418
1466
|
if (event.type === 'token' || event.type === 'thinking') {
|
|
1419
1467
|
broadcast({ type: 'stream', streamType: event.type, content: event.content }, channelId);
|
|
@@ -1583,7 +1631,22 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
1583
1631
|
}
|
|
1584
1632
|
if (contextHint)
|
|
1585
1633
|
contextHint += '\n';
|
|
1586
|
-
|
|
1634
|
+
try {
|
|
1635
|
+
fullResponse = await agent.promptStream(contextHint + text, streamCallback, runState.abortController?.signal, channelId);
|
|
1636
|
+
}
|
|
1637
|
+
catch (err) {
|
|
1638
|
+
// abort 抛错: 保留已输出的部分 (fullResponse 可能是空字符串)
|
|
1639
|
+
if (runState.abortController?.signal.aborted || err?.name === 'AbortError') {
|
|
1640
|
+
console.log(`[chat] aborted channel=${channelId}`);
|
|
1641
|
+
}
|
|
1642
|
+
else {
|
|
1643
|
+
throw err;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
// abort 模式: 给 partial 拼后缀
|
|
1647
|
+
if (runState.abortController?.signal.aborted && fullResponse.trim().length > 0) {
|
|
1648
|
+
fullResponse = fullResponse + '\n\n_[生成已中断]_';
|
|
1649
|
+
}
|
|
1587
1650
|
// v3 新增: 解析 LLM 回复里的 @-mentions, 转发到目标 channel
|
|
1588
1651
|
await routeMentionsInReply(channelId, fullResponse, localChannels, remoteChannels);
|
|
1589
1652
|
broadcast({ type: 'ai', content: fullResponse }, channelId);
|
|
@@ -1592,7 +1655,15 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
1592
1655
|
session.sessionId = currentSessionId;
|
|
1593
1656
|
// v3: 加 source 标记 (local = 内部 owner, remote = 远端访客)
|
|
1594
1657
|
session.messages.push({ id: crypto.randomUUID(), type: 'user', content: text, timestamp: new Date().toISOString(), source: 'local' });
|
|
1595
|
-
session.messages.push({
|
|
1658
|
+
session.messages.push({
|
|
1659
|
+
id: crypto.randomUUID(),
|
|
1660
|
+
type: 'ai',
|
|
1661
|
+
content: fullResponse,
|
|
1662
|
+
timestamp: new Date().toISOString(),
|
|
1663
|
+
source: 'local',
|
|
1664
|
+
// P0.5: 这条 AI 回复引用了哪些 judgment (注入门回传)
|
|
1665
|
+
...(usedJudgmentIds.length > 0 ? { metadata: { usedJudgmentIds } } : {}),
|
|
1666
|
+
});
|
|
1596
1667
|
session.lastUpdated = new Date().toISOString();
|
|
1597
1668
|
await saveSession(session);
|
|
1598
1669
|
const channels = await loadChannels();
|
|
@@ -1605,6 +1676,46 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
1605
1676
|
await saveChannels(channels);
|
|
1606
1677
|
}
|
|
1607
1678
|
broadcast({ type: 'done' }, channelId);
|
|
1679
|
+
// D 触发: AI 被动捕获判断力 (后台异步, 不阻塞主对话)
|
|
1680
|
+
setImmediate(() => {
|
|
1681
|
+
try {
|
|
1682
|
+
const lastTurns = session.messages.slice(-6).map((m) => ({
|
|
1683
|
+
role: (m.type === 'user' ? 'human' : 'agent'),
|
|
1684
|
+
content: m.content,
|
|
1685
|
+
}));
|
|
1686
|
+
if (lastTurns.length < 2)
|
|
1687
|
+
return;
|
|
1688
|
+
broadcast({ type: 'phase', phase: 'd_detect', detail: '监测对话...' }, channelId);
|
|
1689
|
+
import('../pi-ecosystem-judgment/human-value-pipeline.js')
|
|
1690
|
+
.then(async ({ detectAndDistillFromChannel, throttleDHook }) => {
|
|
1691
|
+
// channel 维度 5min 节流, 防对话卡顿时 LLM 反复触发
|
|
1692
|
+
if (!throttleDHook(channelId, 5 * 60_000)) {
|
|
1693
|
+
console.log(`[D-hook ${channelId}] throttled (within 5min)`);
|
|
1694
|
+
broadcast({ type: 'phase', phase: 'd_skip', detail: 'throttled' }, channelId);
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
broadcast({ type: 'phase', phase: 'd_distill', detail: '蒸馏判断力...' }, channelId);
|
|
1698
|
+
return detectAndDistillFromChannel(lastTurns, { channelId });
|
|
1699
|
+
})
|
|
1700
|
+
.then((result) => {
|
|
1701
|
+
if (result && result.triggered) {
|
|
1702
|
+
console.log(`[D-hook ${channelId}] stored: ${result.reason}`, result.evolved);
|
|
1703
|
+
broadcast({ type: 'phase', phase: 'd_done', detail: result.reason }, channelId);
|
|
1704
|
+
}
|
|
1705
|
+
else if (result && result.reason) {
|
|
1706
|
+
console.log(`[D-hook ${channelId}] skipped: ${result.reason}`);
|
|
1707
|
+
broadcast({ type: 'phase', phase: 'd_skip', detail: result.reason }, channelId);
|
|
1708
|
+
}
|
|
1709
|
+
})
|
|
1710
|
+
.catch((err) => {
|
|
1711
|
+
console.warn(`[D-hook ${channelId}] failed:`, err);
|
|
1712
|
+
broadcast({ type: 'phase', phase: 'd_error', detail: String(err) }, channelId);
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
catch (err) {
|
|
1716
|
+
console.warn(`[D-hook ${channelId}] sync error:`, err);
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1608
1719
|
// 2026-06-11: 202 已发的话, 不要重复 res.json (会抛 ERR_HTTP_HEADERS_SENT)
|
|
1609
1720
|
if (!res.headersSent)
|
|
1610
1721
|
res.json({ ok: true });
|
|
@@ -1615,6 +1726,15 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
1615
1726
|
if (!res.headersSent)
|
|
1616
1727
|
res.status(500).json({ error: err.message });
|
|
1617
1728
|
}
|
|
1729
|
+
finally {
|
|
1730
|
+
// queue dequeue: 跑完或失败都要清状态
|
|
1731
|
+
// 当前实现: 自动接下一条需要把 ~200 行 try 块抽函数, 暂不抽.
|
|
1732
|
+
// 替代: 用户点 [队列 +N] 按钮时, 客户端发起一个特殊的 HTTP 请求触发下一条
|
|
1733
|
+
// (在 client.js 实现). 这里只清状态 + 广播.
|
|
1734
|
+
runState.running = false;
|
|
1735
|
+
runState.abortController = null;
|
|
1736
|
+
broadcastQueueUpdate(channelId);
|
|
1737
|
+
}
|
|
1618
1738
|
});
|
|
1619
1739
|
// ---------- 频道元数据后台修复队列 ----------
|
|
1620
1740
|
// 关键点: 旧实现会在每次 GET /channels 时同步执行 KeyManager.generate() + IPFS POST,
|
|
@@ -1623,6 +1743,24 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
1623
1743
|
const didFixQueue = new Set(); // 待修复的 channelId
|
|
1624
1744
|
let didFixRunning = false;
|
|
1625
1745
|
let didFixTimer = null;
|
|
1746
|
+
const channelRunState = new Map();
|
|
1747
|
+
function getOrCreateRunState(channelId) {
|
|
1748
|
+
let s = channelRunState.get(channelId);
|
|
1749
|
+
if (!s) {
|
|
1750
|
+
s = { running: false, queue: [], abortController: null };
|
|
1751
|
+
channelRunState.set(channelId, s);
|
|
1752
|
+
}
|
|
1753
|
+
return s;
|
|
1754
|
+
}
|
|
1755
|
+
function broadcastQueueUpdate(channelId) {
|
|
1756
|
+
const s = channelRunState.get(channelId);
|
|
1757
|
+
const queueLength = s ? s.queue.length : 0;
|
|
1758
|
+
const running = s ? s.running : false;
|
|
1759
|
+
try {
|
|
1760
|
+
broadcast({ type: 'queue_update', channelId, queueLength, running }, channelId);
|
|
1761
|
+
}
|
|
1762
|
+
catch { /* */ }
|
|
1763
|
+
}
|
|
1626
1764
|
function scheduleDidFix(channelId) {
|
|
1627
1765
|
didFixQueue.add(channelId);
|
|
1628
1766
|
if (didFixTimer)
|
|
@@ -2312,7 +2450,13 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
2312
2450
|
broadcast({ type: 'regenerating', channelId }, channelId);
|
|
2313
2451
|
const agent = await getAgentForChannel(channelId, realChannelDid, realChannelName, realChannelDidDoc);
|
|
2314
2452
|
let fullResponse = '';
|
|
2453
|
+
let usedJudgmentIds = [];
|
|
2315
2454
|
const streamCallback = (event) => {
|
|
2455
|
+
// P0.5: 注入门回传
|
|
2456
|
+
if (event.type === 'used_judgments' && Array.isArray(event.usedIds)) {
|
|
2457
|
+
usedJudgmentIds = event.usedIds;
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2316
2460
|
if (event.type === 'token' || event.type === 'thinking') {
|
|
2317
2461
|
broadcast({ type: 'stream', streamType: event.type, content: event.content }, channelId);
|
|
2318
2462
|
}
|
|
@@ -2325,7 +2469,7 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
2325
2469
|
};
|
|
2326
2470
|
// 重新生成时只发送用户消息 (v3: 同时注入 channel 绑定的判断力)
|
|
2327
2471
|
const regenHint = await buildJudgmentHint(channel, channelId);
|
|
2328
|
-
fullResponse = await agent.promptStream(regenHint + userMessage, streamCallback);
|
|
2472
|
+
fullResponse = await agent.promptStream(regenHint + userMessage, streamCallback, undefined, channelId);
|
|
2329
2473
|
broadcast({ type: 'ai', content: fullResponse }, channelId);
|
|
2330
2474
|
// 更新 session
|
|
2331
2475
|
const existingSession = await loadSession(channelId, currentSessionId);
|
|
@@ -2339,7 +2483,8 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
2339
2483
|
id: crypto.randomUUID(),
|
|
2340
2484
|
type: 'ai',
|
|
2341
2485
|
content: fullResponse,
|
|
2342
|
-
timestamp: new Date().toISOString()
|
|
2486
|
+
timestamp: new Date().toISOString(),
|
|
2487
|
+
...(usedJudgmentIds.length > 0 ? { metadata: { usedJudgmentIds } } : {}),
|
|
2343
2488
|
});
|
|
2344
2489
|
existingSession.lastUpdated = new Date().toISOString();
|
|
2345
2490
|
await saveSession(existingSession);
|
|
@@ -3664,6 +3809,24 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
3664
3809
|
res.status(500).json({ error: err.message });
|
|
3665
3810
|
}
|
|
3666
3811
|
});
|
|
3812
|
+
// 终止当前 channel 的 LLM 流 (UI 终止按钮)
|
|
3813
|
+
app.post('/api/chat/abort', async (req, res) => {
|
|
3814
|
+
try {
|
|
3815
|
+
const { channelId } = req.body;
|
|
3816
|
+
if (!channelId)
|
|
3817
|
+
return res.status(400).json({ error: 'channelId required' });
|
|
3818
|
+
const s = channelRunState.get(channelId);
|
|
3819
|
+
if (s?.abortController) {
|
|
3820
|
+
s.abortController.abort();
|
|
3821
|
+
console.log(`[abort] user aborted channel=${channelId}`);
|
|
3822
|
+
return res.json({ ok: true, aborted: true });
|
|
3823
|
+
}
|
|
3824
|
+
res.json({ ok: true, aborted: false });
|
|
3825
|
+
}
|
|
3826
|
+
catch (err) {
|
|
3827
|
+
res.status(500).json({ error: err.message });
|
|
3828
|
+
}
|
|
3829
|
+
});
|
|
3667
3830
|
// 主人审阅: 批准 draft
|
|
3668
3831
|
app.post('/api/chat/approve', async (req, res) => {
|
|
3669
3832
|
try {
|
|
@@ -4046,20 +4209,378 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
4046
4209
|
res.status(500).json({ error: err.message });
|
|
4047
4210
|
}
|
|
4048
4211
|
});
|
|
4049
|
-
app.get('/api/judgments', async (
|
|
4212
|
+
app.get('/api/judgments', async (req, res) => {
|
|
4050
4213
|
try {
|
|
4051
|
-
const {
|
|
4214
|
+
const { listJudgmentsByStatus, initializeValueStore } = await import('../pi-ecosystem-judgment/human-value-store.js');
|
|
4052
4215
|
await initializeValueStore();
|
|
4053
|
-
const
|
|
4054
|
-
|
|
4216
|
+
const status = (typeof req.query.status === 'string' ? req.query.status : 'all');
|
|
4217
|
+
const all = await listJudgmentsByStatus(status);
|
|
4055
4218
|
all.sort((a, b) => (b.timestamp || '').localeCompare(a.timestamp || ''));
|
|
4056
|
-
res.json({ count: all.length, judgments: all });
|
|
4219
|
+
res.json({ count: all.length, status, judgments: all });
|
|
4057
4220
|
}
|
|
4058
4221
|
catch (err) {
|
|
4059
4222
|
console.error('[judgments] GET failed:', err);
|
|
4060
4223
|
res.status(500).json({ error: err.message });
|
|
4061
4224
|
}
|
|
4062
4225
|
});
|
|
4226
|
+
// 蒸馏 B 触发 (人类点按钮) — 同步执行演化对齐
|
|
4227
|
+
app.post('/api/judgments/distill-from-conversation', async (req, res) => {
|
|
4228
|
+
try {
|
|
4229
|
+
const { channelId, messageId, recentTurns } = req.body;
|
|
4230
|
+
if (!channelId) {
|
|
4231
|
+
return res.status(400).json({ error: 'channelId required' });
|
|
4232
|
+
}
|
|
4233
|
+
// 取 channel 最近的对话
|
|
4234
|
+
const channels = await loadChannels();
|
|
4235
|
+
const channel = channels.find((c) => c.id === channelId);
|
|
4236
|
+
if (!channel)
|
|
4237
|
+
return res.status(404).json({ error: 'channel not found' });
|
|
4238
|
+
const currentSessionId = channel.currentSessionId;
|
|
4239
|
+
if (!currentSessionId) {
|
|
4240
|
+
return res.status(400).json({ error: 'no active session in channel' });
|
|
4241
|
+
}
|
|
4242
|
+
const session = await loadSession(channelId, currentSessionId);
|
|
4243
|
+
if (!session)
|
|
4244
|
+
return res.status(404).json({ error: 'session not found' });
|
|
4245
|
+
// 取最近 N 轮 (默认 10), 转成 DistillTurn 格式
|
|
4246
|
+
const limit = Math.min(Math.max(recentTurns ?? 10, 2), 30);
|
|
4247
|
+
const turns = session.messages.slice(-limit).map((m) => ({
|
|
4248
|
+
role: (m.type === 'user' ? 'human' : 'agent'),
|
|
4249
|
+
content: m.content,
|
|
4250
|
+
}));
|
|
4251
|
+
const { distillAndStoreFromChannel } = await import('../pi-ecosystem-judgment/human-value-pipeline.js');
|
|
4252
|
+
const result = await distillAndStoreFromChannel(turns, { channelId });
|
|
4253
|
+
res.json({
|
|
4254
|
+
ok: true,
|
|
4255
|
+
triggered: result.triggered,
|
|
4256
|
+
reason: result.reason,
|
|
4257
|
+
judgment: result.judgment,
|
|
4258
|
+
evolved: result.evolved,
|
|
4259
|
+
});
|
|
4260
|
+
}
|
|
4261
|
+
catch (err) {
|
|
4262
|
+
console.error('[judgments] distill-from-conversation failed:', err);
|
|
4263
|
+
res.status(500).json({ error: err.message });
|
|
4264
|
+
}
|
|
4265
|
+
});
|
|
4266
|
+
// 蒸馏 D 触发 (AI 被动) — 后台异步,不阻塞 HTTP 响应
|
|
4267
|
+
app.post('/api/judgments/detect-and-distill', async (req, res) => {
|
|
4268
|
+
try {
|
|
4269
|
+
const { channelId, turns } = req.body;
|
|
4270
|
+
// 先立即返回 202, 不等 LLM
|
|
4271
|
+
res.status(202).json({ ok: true, queued: true });
|
|
4272
|
+
if (!channelId || !Array.isArray(turns) || turns.length === 0) {
|
|
4273
|
+
return;
|
|
4274
|
+
}
|
|
4275
|
+
// 异步处理 (不 await, 不阻塞响应)
|
|
4276
|
+
setImmediate(async () => {
|
|
4277
|
+
try {
|
|
4278
|
+
const { detectAndDistillFromChannel } = await import('../pi-ecosystem-judgment/human-value-pipeline.js');
|
|
4279
|
+
const result = await detectAndDistillFromChannel(turns, { channelId });
|
|
4280
|
+
if (result.triggered) {
|
|
4281
|
+
console.log(`[D-hook] ${channelId}: ${result.reason}`, result.evolved);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
catch (err) {
|
|
4285
|
+
console.warn('[D-hook] background failed:', err);
|
|
4286
|
+
}
|
|
4287
|
+
});
|
|
4288
|
+
}
|
|
4289
|
+
catch (err) {
|
|
4290
|
+
console.error('[judgments] detect-and-distill failed:', err);
|
|
4291
|
+
res.status(500).json({ error: err.message });
|
|
4292
|
+
}
|
|
4293
|
+
});
|
|
4294
|
+
// 判断力使用回溯 (P0.5): 给定 judgmentIds, 反查对应的 decision 文本
|
|
4295
|
+
// 用途: UI 上"这条 AI 回复引用了哪些原则"
|
|
4296
|
+
app.post('/api/judgments/resolve-usage', async (req, res) => {
|
|
4297
|
+
try {
|
|
4298
|
+
const { ids } = req.body;
|
|
4299
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
4300
|
+
return res.json({ items: [] });
|
|
4301
|
+
}
|
|
4302
|
+
const { loadAllJudgments } = await import('../pi-ecosystem-judgment/human-value-store.js');
|
|
4303
|
+
const all = await loadAllJudgments();
|
|
4304
|
+
const byId = new Map(all.map((j) => [j.id, j]));
|
|
4305
|
+
const items = ids
|
|
4306
|
+
.map((id) => byId.get(id))
|
|
4307
|
+
.filter((j) => Boolean(j))
|
|
4308
|
+
.map((j) => ({
|
|
4309
|
+
id: j.id,
|
|
4310
|
+
decision: j.decision,
|
|
4311
|
+
status: j.status ?? 'active',
|
|
4312
|
+
timestamp: j.timestamp,
|
|
4313
|
+
}));
|
|
4314
|
+
res.json({ items });
|
|
4315
|
+
}
|
|
4316
|
+
catch (err) {
|
|
4317
|
+
console.error('[judgments] resolve-usage failed:', err);
|
|
4318
|
+
res.status(500).json({ error: err.message });
|
|
4319
|
+
}
|
|
4320
|
+
});
|
|
4321
|
+
// 判断力违规日志 (P3 UI): 读 violations.jsonl
|
|
4322
|
+
app.get('/api/judgments/violations', async (req, res) => {
|
|
4323
|
+
try {
|
|
4324
|
+
const { getRecentViolations } = await import('../pi-ecosystem-judgment/monitor-gate.js');
|
|
4325
|
+
const limit = Math.min(Math.max(parseInt(String(req.query.limit ?? '20'), 10) || 20, 1), 200);
|
|
4326
|
+
const items = await getRecentViolations(limit);
|
|
4327
|
+
res.json({ count: items.length, items });
|
|
4328
|
+
}
|
|
4329
|
+
catch (err) {
|
|
4330
|
+
console.error('[judgments] violations failed:', err);
|
|
4331
|
+
res.status(500).json({ error: err.message });
|
|
4332
|
+
}
|
|
4333
|
+
});
|
|
4334
|
+
// 类 B 自适应扫描: 读 judgments.json + usage.jsonl, 给出 stale/rising/unused 建议
|
|
4335
|
+
// ?force=1 跳过 24h 缓存
|
|
4336
|
+
app.get('/api/judgments/adaptive-suggestions', async (req, res) => {
|
|
4337
|
+
try {
|
|
4338
|
+
const { getCachedScan } = await import('../pi-ecosystem-judgment/adaptive-scan.js');
|
|
4339
|
+
const force = String(req.query.force ?? '') === '1';
|
|
4340
|
+
const result = await getCachedScan(force);
|
|
4341
|
+
res.json(result);
|
|
4342
|
+
}
|
|
4343
|
+
catch (err) {
|
|
4344
|
+
console.error('[judgments] adaptive-scan failed:', err);
|
|
4345
|
+
res.status(500).json({ error: err.message });
|
|
4346
|
+
}
|
|
4347
|
+
});
|
|
4348
|
+
// Bootstrap Context 调试视图: 返出完整 BolloonContext
|
|
4349
|
+
app.get('/api/bolloon/context', async (req, res) => {
|
|
4350
|
+
try {
|
|
4351
|
+
const { getCachedBolloonContext } = await import('../pi-ecosystem-judgment/human-value-pipeline.js');
|
|
4352
|
+
const force = String(req.query.force ?? '') === '1';
|
|
4353
|
+
const ctx = await getCachedBolloonContext({ cwd: process.cwd() }, force);
|
|
4354
|
+
res.json(ctx);
|
|
4355
|
+
}
|
|
4356
|
+
catch (err) {
|
|
4357
|
+
console.error('[bolloon] context failed:', err);
|
|
4358
|
+
res.status(500).json({ error: err.message });
|
|
4359
|
+
}
|
|
4360
|
+
});
|
|
4361
|
+
// 阶段 B: 周报 (weekly-report.ts 产物) — 仅 API 读取, 不做 UI tab
|
|
4362
|
+
// GET /api/reports → { files: ['2026-W24.md', ...] }
|
|
4363
|
+
// GET /api/reports/2026-W24 → { week, content }
|
|
4364
|
+
app.get('/api/reports', async (_req, res) => {
|
|
4365
|
+
try {
|
|
4366
|
+
const dir = path.join(os.homedir(), '.bolloon', 'reports');
|
|
4367
|
+
try {
|
|
4368
|
+
const entries = await fs.readdir(dir);
|
|
4369
|
+
const files = entries
|
|
4370
|
+
.filter((f) => f.endsWith('.md'))
|
|
4371
|
+
.sort()
|
|
4372
|
+
.reverse(); // 新的在前
|
|
4373
|
+
res.json({ dir, files });
|
|
4374
|
+
}
|
|
4375
|
+
catch {
|
|
4376
|
+
res.json({ dir, files: [] });
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
catch (err) {
|
|
4380
|
+
res.status(500).json({ error: err.message });
|
|
4381
|
+
}
|
|
4382
|
+
});
|
|
4383
|
+
app.get('/api/reports/:week', async (req, res) => {
|
|
4384
|
+
try {
|
|
4385
|
+
const week = req.params.week;
|
|
4386
|
+
// 严格校验, 防路径穿越
|
|
4387
|
+
if (!/^\d{4}-W\d{1,2}$/.test(week)) {
|
|
4388
|
+
return res.status(400).json({ error: 'week must match YYYY-Www' });
|
|
4389
|
+
}
|
|
4390
|
+
const file = path.join(os.homedir(), '.bolloon', 'reports', `${week}.md`);
|
|
4391
|
+
try {
|
|
4392
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
4393
|
+
res.json({ week, content, length: content.length });
|
|
4394
|
+
}
|
|
4395
|
+
catch {
|
|
4396
|
+
res.status(404).json({ error: 'not found', week });
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
catch (err) {
|
|
4400
|
+
res.status(500).json({ error: err.message });
|
|
4401
|
+
}
|
|
4402
|
+
});
|
|
4403
|
+
// 阶段 C 护栏 5: auto-evolve baseline 管理 (无 UI, 仅 API)
|
|
4404
|
+
// GET /api/auto-evolve/baselines → 列出所有 baseline tag
|
|
4405
|
+
// GET /api/auto-evolve/baselines/:tag/diff → 看某 baseline 的 diff 摘要
|
|
4406
|
+
// POST /api/auto-evolve/rollback {tag} → 回滚到指定 baseline
|
|
4407
|
+
app.get('/api/auto-evolve/baselines', async (_req, res) => {
|
|
4408
|
+
try {
|
|
4409
|
+
const { execFile } = await import('child_process');
|
|
4410
|
+
const { promisify } = await import('util');
|
|
4411
|
+
const pExec = promisify(execFile);
|
|
4412
|
+
const { stdout } = await pExec('git', [
|
|
4413
|
+
'tag', '-l', 'auto-evolve-baseline-*', '--format=%(refname:short)|%(contents)|%(objectname:short)|%(taggerdate:iso)',
|
|
4414
|
+
], { cwd: process.cwd() });
|
|
4415
|
+
const tags = stdout.trim().split('\n').filter(Boolean).map((line) => {
|
|
4416
|
+
const [tag, msg, sha, date] = line.split('|');
|
|
4417
|
+
return { tag, message: msg || '', sha, date };
|
|
4418
|
+
});
|
|
4419
|
+
res.json({ tags, count: tags.length });
|
|
4420
|
+
}
|
|
4421
|
+
catch (err) {
|
|
4422
|
+
res.status(500).json({ error: err.message });
|
|
4423
|
+
}
|
|
4424
|
+
});
|
|
4425
|
+
app.get('/api/auto-evolve/baselines/:tag/diff', async (req, res) => {
|
|
4426
|
+
try {
|
|
4427
|
+
const { execFile } = await import('child_process');
|
|
4428
|
+
const { promisify } = await import('util');
|
|
4429
|
+
const pExec = promisify(execFile);
|
|
4430
|
+
const tag = req.params.tag;
|
|
4431
|
+
if (!/^auto-evolve-baseline-[\w-]+$/.test(tag)) {
|
|
4432
|
+
return res.status(400).json({ error: 'tag must match auto-evolve-baseline-*' });
|
|
4433
|
+
}
|
|
4434
|
+
const { stdout } = await pExec('git', ['show', '--stat', '--no-color', tag], { cwd: process.cwd() });
|
|
4435
|
+
res.json({ tag, diff: stdout.slice(0, 5000) }); // 限长 5KB
|
|
4436
|
+
}
|
|
4437
|
+
catch (err) {
|
|
4438
|
+
res.status(500).json({ error: err.message });
|
|
4439
|
+
}
|
|
4440
|
+
});
|
|
4441
|
+
// Bootstrap Context → 拼好的 system prompt 片段 (供调试看注入效果)
|
|
4442
|
+
app.get('/api/bolloon/context/system-prompt', async (req, res) => {
|
|
4443
|
+
try {
|
|
4444
|
+
const { getCachedBolloonContext } = await import('../pi-ecosystem-judgment/human-value-pipeline.js');
|
|
4445
|
+
const { formatContextForSystemPrompt } = await import('../bootstrap/project-context.js');
|
|
4446
|
+
const ctx = await getCachedBolloonContext({ cwd: process.cwd() });
|
|
4447
|
+
const systemAddition = formatContextForSystemPrompt(ctx, {
|
|
4448
|
+
maxChars: parseInt(String(req.query.max ?? '4000'), 10) || 4000,
|
|
4449
|
+
});
|
|
4450
|
+
res.json({ systemAddition, length: systemAddition.length, truncated: systemAddition.includes('截断模式') });
|
|
4451
|
+
}
|
|
4452
|
+
catch (err) {
|
|
4453
|
+
console.error('[bolloon] context/system-prompt failed:', err);
|
|
4454
|
+
res.status(500).json({ error: err.message });
|
|
4455
|
+
}
|
|
4456
|
+
});
|
|
4457
|
+
// 自适应接受/拒绝: 写 evolution.jsonl 留痕, 接受时同时 patch judgments.json
|
|
4458
|
+
// body: { action: 'accept'|'reject'|'revert', suggestion, appliedPatch? }
|
|
4459
|
+
// query: ?auto=1 → 类 B 自动路径, 受 auto-evolve-policy 网关保护
|
|
4460
|
+
// 缺省 → 用户在 UI 手动触发, 不查开关 (避免阻塞用户)
|
|
4461
|
+
app.post('/api/judgments/adaptive-apply', async (req, res) => {
|
|
4462
|
+
try {
|
|
4463
|
+
const isAuto = req.query.auto === '1' || req.query.auto === 'true';
|
|
4464
|
+
const { action, suggestion, appliedPatch } = req.body;
|
|
4465
|
+
if (!action || !suggestion?.judgmentId) {
|
|
4466
|
+
return res.status(400).json({ error: 'action and suggestion.judgmentId required' });
|
|
4467
|
+
}
|
|
4468
|
+
const { updateJudgmentStatus } = await import('../pi-ecosystem-judgment/human-value-store.js');
|
|
4469
|
+
const { logEvolution } = await import('../pi-ecosystem-judgment/adaptive-scan.js');
|
|
4470
|
+
// accept 时: 真正改库
|
|
4471
|
+
if (action === 'accept') {
|
|
4472
|
+
// 阶段 A: 自动路径需先过 auto-evolve-policy 网关
|
|
4473
|
+
if (isAuto) {
|
|
4474
|
+
const { requireDataLayerAutoEvolve } = await import('../utils/auto-evolve-policy.js');
|
|
4475
|
+
try {
|
|
4476
|
+
await requireDataLayerAutoEvolve('adaptive-apply.auto.deprecate');
|
|
4477
|
+
}
|
|
4478
|
+
catch (err) {
|
|
4479
|
+
return res.status(423).json({
|
|
4480
|
+
error: 'data-layer-auto-evolve-disabled',
|
|
4481
|
+
message: err.message,
|
|
4482
|
+
hint: '设 BOLLOON_AUTO_EVOLVE_DATA=1 或在 self-improve-policy.json 加 dataLayerAutoEvolve: true',
|
|
4483
|
+
});
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
if (suggestion.action === 'deprecate') {
|
|
4487
|
+
// 标记 superseded (语义: 不再用, 但保留可回滚)
|
|
4488
|
+
await updateJudgmentStatus(suggestion.judgmentId, 'superseded', {
|
|
4489
|
+
evolutionReason: 'merged', // 借 merged 字段表达"被自适应废弃"
|
|
4490
|
+
});
|
|
4491
|
+
}
|
|
4492
|
+
else if (suggestion.action === 'boost') {
|
|
4493
|
+
// boost: 用户手动接受后, 不改库本身 (weight 在 getRelevantValues 里动态算),
|
|
4494
|
+
// 但写 evolution 留痕, 未来可以基于此调整算法
|
|
4495
|
+
// 当前不直接改库, 仅留痕
|
|
4496
|
+
}
|
|
4497
|
+
// 'review' 类不需要自动改库, 仅 log 接受
|
|
4498
|
+
}
|
|
4499
|
+
await logEvolution({
|
|
4500
|
+
ts: new Date().toISOString(),
|
|
4501
|
+
action,
|
|
4502
|
+
suggestion: suggestion,
|
|
4503
|
+
appliedPatch,
|
|
4504
|
+
});
|
|
4505
|
+
res.json({ ok: true });
|
|
4506
|
+
}
|
|
4507
|
+
catch (err) {
|
|
4508
|
+
console.error('[judgments] adaptive-apply failed:', err);
|
|
4509
|
+
res.status(500).json({ error: err.message });
|
|
4510
|
+
}
|
|
4511
|
+
});
|
|
4512
|
+
// 演化日志 (audit / 一键回滚源)
|
|
4513
|
+
app.get('/api/judgments/evolution-log', async (req, res) => {
|
|
4514
|
+
try {
|
|
4515
|
+
const { readEvolutionLog } = await import('../pi-ecosystem-judgment/adaptive-scan.js');
|
|
4516
|
+
const limit = Math.min(Math.max(parseInt(String(req.query.limit ?? '50'), 10) || 50, 1), 200);
|
|
4517
|
+
const items = await readEvolutionLog(limit);
|
|
4518
|
+
res.json({ count: items.length, items });
|
|
4519
|
+
}
|
|
4520
|
+
catch (err) {
|
|
4521
|
+
console.error('[judgments] evolution-log failed:', err);
|
|
4522
|
+
res.status(500).json({ error: err.message });
|
|
4523
|
+
}
|
|
4524
|
+
});
|
|
4525
|
+
// 阶段 2: Causal-judge 4 个 endpoint
|
|
4526
|
+
app.get('/api/judgments/causal/correlation', async (req, res) => {
|
|
4527
|
+
try {
|
|
4528
|
+
const { runCorrelationAnalysis } = await import('../pi-ecosystem-judgment/human-value-pipeline.js');
|
|
4529
|
+
const topN = Math.min(Math.max(parseInt(String(req.query.topN ?? '5'), 10) || 5, 1), 50);
|
|
4530
|
+
const useLLM = String(req.query.useLLM ?? '1') !== '0';
|
|
4531
|
+
const items = await runCorrelationAnalysis({ topN, useLLM });
|
|
4532
|
+
res.json({ count: items.length, items });
|
|
4533
|
+
}
|
|
4534
|
+
catch (err) {
|
|
4535
|
+
console.error('[causal] correlation failed:', err);
|
|
4536
|
+
res.status(500).json({ error: err.message });
|
|
4537
|
+
}
|
|
4538
|
+
});
|
|
4539
|
+
app.get('/api/judgments/causal/intervention', async (req, res) => {
|
|
4540
|
+
try {
|
|
4541
|
+
const { runIntervention } = await import('../pi-ecosystem-judgment/human-value-pipeline.js');
|
|
4542
|
+
const { judgmentId, scenario } = req.query;
|
|
4543
|
+
if (!judgmentId)
|
|
4544
|
+
return res.status(400).json({ error: 'judgmentId required' });
|
|
4545
|
+
const result = await runIntervention(judgmentId, { scenarioContext: scenario });
|
|
4546
|
+
res.json(result);
|
|
4547
|
+
}
|
|
4548
|
+
catch (err) {
|
|
4549
|
+
console.error('[causal] intervention failed:', err);
|
|
4550
|
+
res.status(500).json({ error: err.message });
|
|
4551
|
+
}
|
|
4552
|
+
});
|
|
4553
|
+
app.post('/api/judgments/causal/counterfactual', async (req, res) => {
|
|
4554
|
+
try {
|
|
4555
|
+
const { runCounterfactualAudit } = await import('../pi-ecosystem-judgment/human-value-pipeline.js');
|
|
4556
|
+
const { userInput, aiReply, violatedPrinciples } = req.body;
|
|
4557
|
+
if (!userInput || !aiReply) {
|
|
4558
|
+
return res.status(400).json({ error: 'userInput and aiReply required' });
|
|
4559
|
+
}
|
|
4560
|
+
const audit = await runCounterfactualAudit({
|
|
4561
|
+
userInput,
|
|
4562
|
+
aiReply,
|
|
4563
|
+
violatedPrinciples: violatedPrinciples ?? [],
|
|
4564
|
+
});
|
|
4565
|
+
res.json(audit);
|
|
4566
|
+
}
|
|
4567
|
+
catch (err) {
|
|
4568
|
+
console.error('[causal] counterfactual failed:', err);
|
|
4569
|
+
res.status(500).json({ error: err.message });
|
|
4570
|
+
}
|
|
4571
|
+
});
|
|
4572
|
+
app.get('/api/judgments/causal/audit-log', async (req, res) => {
|
|
4573
|
+
try {
|
|
4574
|
+
const { readCounterfactualLog } = await import('../pi-ecosystem-judgment/human-value-pipeline.js');
|
|
4575
|
+
const limit = Math.min(Math.max(parseInt(String(req.query.limit ?? '20'), 10) || 20, 1), 200);
|
|
4576
|
+
const items = await readCounterfactualLog(limit);
|
|
4577
|
+
res.json({ count: items.length, items });
|
|
4578
|
+
}
|
|
4579
|
+
catch (err) {
|
|
4580
|
+
console.error('[causal] audit-log failed:', err);
|
|
4581
|
+
res.status(500).json({ error: err.message });
|
|
4582
|
+
}
|
|
4583
|
+
});
|
|
4063
4584
|
// 导入判断: 接受 { filename, content (base64), context }.
|
|
4064
4585
|
// 支持 .json / .yaml / .yml / .md / .txt / .html. 完全离线解析, 不调 LLM.
|
|
4065
4586
|
// 解析规则:
|