@bolloon/bolloon-agent 0.3.8 → 0.3.10
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/dist/agents/pi-sdk-tools.js +90 -9
- package/dist/agents/pi-sdk.js +26 -5
- package/dist/bootstrap/exhaust-scrubber.js +233 -0
- package/dist/bootstrap/memory-compressor.js +11 -0
- package/dist/constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js +19 -4
- package/dist/constraint-runtime/src/tools/PolymarketSDK/clobShared.js +104 -0
- package/dist/constraint-runtime/src/tools/PolymarketSDK/createOrder.js +34 -4
- package/dist/constraint-runtime/src/tools/PolymarketSDK/getOrders.js +17 -4
- package/dist/external-engines/delegate.js +148 -0
- package/dist/external-engines/discovery.js +394 -0
- package/dist/external-engines/index.js +9 -0
- package/dist/external-engines/types.js +11 -0
- package/dist/pi-ecosystem-judgment/injection-gate.js +85 -1
- package/dist/web/api-config.html +250 -1
- package/dist/web/client.js +32 -10
- package/dist/web/index.html +47 -23
- package/dist/web/routes-external-engines.js +111 -0
- package/dist/web/routes-judgments.js +8 -2
- package/dist/web/server.js +15 -0
- package/dist/web/style.css +61 -0
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import { checkWritePath } from './shell-guard.js';
|
|
|
8
8
|
import { p2pDocumentTools } from './p2p-document-tools.js';
|
|
9
9
|
import { runSelfImproveLoop } from './pi-sdk-session-factory.js';
|
|
10
10
|
import { getMinimax } from '../constraints/index.js';
|
|
11
|
+
import { delegateToEngine } from '../external-engines/delegate.js';
|
|
11
12
|
/**
|
|
12
13
|
* Tools 模块 — 从 pi-sdk.ts 抽出的 registerTools() / _registerWalletTools() / _setupInboxListener()
|
|
13
14
|
* / wrapToolsWithIdempotency() (2026-07-06).
|
|
@@ -267,6 +268,37 @@ export function registerBuiltinTools(ctx) {
|
|
|
267
268
|
return { success: true, output: `📞 RPC 任务已发送给 ${peerId.substring(0, 16)}...\n requestId=${requestId}` };
|
|
268
269
|
}
|
|
269
270
|
});
|
|
271
|
+
// delegate_to_engine — 把编码任务委派给本机已安装的其他 AI 编码智能体 CLI
|
|
272
|
+
// (codex / claude-code / opencode / openclaw / hermes). 它们必须已安装且可达 PATH.
|
|
273
|
+
// 实验 API 引擎 (experiment:xxx) 是供应商不是 CLI, 不支持委派, 工具会提示改用 import.
|
|
274
|
+
ctx.tools.set('delegate_to_engine', {
|
|
275
|
+
name: 'delegate_to_engine',
|
|
276
|
+
description: '把编码任务委派给本机已安装的其他 AI 编码智能体 (子智能体) 执行: codex / claude-code / opencode / openclaw / hermes. 引擎需已安装且在 PATH 上. 返回其执行输出. 注意: 各工具 CLI 的非交互参数随版本变化, 若报错请检查该工具版本对应 flag.',
|
|
277
|
+
parameters: {
|
|
278
|
+
engine: "引擎 id: codex / claude-code / opencode / openclaw / hermes (实验 API 不支持委派)",
|
|
279
|
+
prompt: '派发的任务描述 (作为单参数传给该引擎 CLI)',
|
|
280
|
+
model: '可选, 强制指定模型 (如 deepseek/deepseek-v4-flash), 需引擎支持',
|
|
281
|
+
cwd: '可选, 工作目录, 默认当前目录'
|
|
282
|
+
},
|
|
283
|
+
execute: async (args) => {
|
|
284
|
+
const engine = String(args.engine || '').trim();
|
|
285
|
+
const prompt = String(args.prompt || '').trim();
|
|
286
|
+
if (!engine)
|
|
287
|
+
return { success: false, error: 'engine 必填' };
|
|
288
|
+
if (!prompt)
|
|
289
|
+
return { success: false, error: 'prompt 必填' };
|
|
290
|
+
const cwd = args.cwd ? String(args.cwd).trim() : undefined;
|
|
291
|
+
const model = args.model ? String(args.model).trim() : undefined;
|
|
292
|
+
const result = await delegateToEngine(engine, prompt, { cwd: cwd || undefined, ...(model ? { model } : {}) });
|
|
293
|
+
if (!result.success) {
|
|
294
|
+
return { success: false, error: result.error, output: result.output };
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
success: true,
|
|
298
|
+
output: `🤖 ${engine} 执行结果 (exitCode=${result.exitCode}):\n${result.output || '(无输出)'}`
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
});
|
|
270
302
|
ctx.tools.set('get_identity', {
|
|
271
303
|
name: 'get_identity',
|
|
272
304
|
description: '获取当前智能体身份信息',
|
|
@@ -1217,12 +1249,26 @@ export function registerWalletTools(ctx) {
|
|
|
1217
1249
|
});
|
|
1218
1250
|
ctx.tools.set('polymarket_get_orders', {
|
|
1219
1251
|
name: 'polymarket_get_orders',
|
|
1220
|
-
description: '查询 Polymarket
|
|
1221
|
-
parameters: {
|
|
1252
|
+
description: '查询 Polymarket 开放订单. 需要钱包私钥 privateKey 做 API key 鉴权.',
|
|
1253
|
+
parameters: {
|
|
1254
|
+
privateKey: '钱包私钥 0x... (必填)',
|
|
1255
|
+
marketId: '可选 按市场 ID 过滤',
|
|
1256
|
+
apiKey: '可选, 已存在的 API key',
|
|
1257
|
+
apiSecret: '可选, API secret',
|
|
1258
|
+
apiPassphrase: '可选, API passphrase',
|
|
1259
|
+
funder: '可选, 资金地址',
|
|
1260
|
+
},
|
|
1222
1261
|
execute: async (args) => {
|
|
1223
1262
|
try {
|
|
1224
1263
|
const { getOrders } = await import('../constraint-runtime/dist/tools/PolymarketSDK/getOrders.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/getOrders.js'));
|
|
1225
|
-
const orders = await getOrders(
|
|
1264
|
+
const orders = await getOrders({
|
|
1265
|
+
privateKey: String(args.privateKey),
|
|
1266
|
+
marketId: args.marketId ? String(args.marketId) : undefined,
|
|
1267
|
+
apiKey: args.apiKey ? String(args.apiKey) : undefined,
|
|
1268
|
+
apiSecret: args.apiSecret ? String(args.apiSecret) : undefined,
|
|
1269
|
+
apiPassphrase: args.apiPassphrase ? String(args.apiPassphrase) : undefined,
|
|
1270
|
+
funder: args.funder ? String(args.funder) : undefined,
|
|
1271
|
+
});
|
|
1226
1272
|
return { success: true, output: `📋 订单列表: ${JSON.stringify(orders, null, 2).substring(0, 1500)}` };
|
|
1227
1273
|
}
|
|
1228
1274
|
catch (e) {
|
|
@@ -1232,19 +1278,40 @@ export function registerWalletTools(ctx) {
|
|
|
1232
1278
|
});
|
|
1233
1279
|
ctx.tools.set('polymarket_create_order', {
|
|
1234
1280
|
name: 'polymarket_create_order',
|
|
1235
|
-
description: '在 Polymarket 下单 (BUY/SELL).',
|
|
1236
|
-
parameters: {
|
|
1281
|
+
description: '在 Polymarket 下单 (BUY/SELL). 需要钱包私钥 privateKey 做 EIP-712 订单签名与 API key 派生.',
|
|
1282
|
+
parameters: {
|
|
1283
|
+
privateKey: '下单钱包私钥 0x... (必填)',
|
|
1284
|
+
marketId: '市场 ID (必填)',
|
|
1285
|
+
side: 'BUY 或 SELL (必填)',
|
|
1286
|
+
price: '价格 0-1 (必填, 需符合 tickSize)',
|
|
1287
|
+
size: '数量 USDC (必填)',
|
|
1288
|
+
outcome: '可选, Yes/No 或索引 0/1, 默认第一个 (通常 Yes)',
|
|
1289
|
+
tokenId: '可选, 显式条件代币 tokenID (优先于 outcome)',
|
|
1290
|
+
orderType: '可选, GTC (默认) 或 GTD',
|
|
1291
|
+
apiKey: '可选, 已存在的 API key (需配合 apiSecret/apiPassphrase)',
|
|
1292
|
+
apiSecret: '可选, API secret',
|
|
1293
|
+
apiPassphrase: '可选, API passphrase',
|
|
1294
|
+
funder: '可选, 资金地址 (默认=私钥地址)',
|
|
1295
|
+
},
|
|
1237
1296
|
execute: async (args) => {
|
|
1238
1297
|
try {
|
|
1239
1298
|
const { createOrder } = await import('../constraint-runtime/dist/tools/PolymarketSDK/createOrder.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/createOrder.js'));
|
|
1240
1299
|
const r = await createOrder({
|
|
1300
|
+
privateKey: String(args.privateKey),
|
|
1241
1301
|
marketId: String(args.marketId),
|
|
1242
1302
|
side: String(args.side).toUpperCase() === 'SELL' ? 'SELL' : 'BUY',
|
|
1243
1303
|
price: Number(args.price),
|
|
1244
1304
|
size: Number(args.size),
|
|
1305
|
+
outcome: args.outcome !== undefined ? args.outcome : undefined,
|
|
1306
|
+
tokenId: args.tokenId ? String(args.tokenId) : undefined,
|
|
1307
|
+
orderType: args.orderType ? String(args.orderType) : undefined,
|
|
1308
|
+
apiKey: args.apiKey ? String(args.apiKey) : undefined,
|
|
1309
|
+
apiSecret: args.apiSecret ? String(args.apiSecret) : undefined,
|
|
1310
|
+
apiPassphrase: args.apiPassphrase ? String(args.apiPassphrase) : undefined,
|
|
1311
|
+
funder: args.funder ? String(args.funder) : undefined,
|
|
1245
1312
|
});
|
|
1246
1313
|
if (r.success)
|
|
1247
|
-
return { success: true, output: `✅
|
|
1314
|
+
return { success: true, output: `✅ 订单已提交: orderId=${r.orderId} status=${r.status}` };
|
|
1248
1315
|
return { success: false, error: r.message, output: r.message };
|
|
1249
1316
|
}
|
|
1250
1317
|
catch (e) {
|
|
@@ -1254,12 +1321,26 @@ export function registerWalletTools(ctx) {
|
|
|
1254
1321
|
});
|
|
1255
1322
|
ctx.tools.set('polymarket_cancel_order', {
|
|
1256
1323
|
name: 'polymarket_cancel_order',
|
|
1257
|
-
description: '取消 Polymarket 订单.',
|
|
1258
|
-
parameters: {
|
|
1324
|
+
description: '取消 Polymarket 订单. 需要钱包私钥 privateKey 做 API key 鉴权.',
|
|
1325
|
+
parameters: {
|
|
1326
|
+
privateKey: '钱包私钥 0x... (必填)',
|
|
1327
|
+
orderId: '订单 ID (必填)',
|
|
1328
|
+
apiKey: '可选, 已存在的 API key',
|
|
1329
|
+
apiSecret: '可选, API secret',
|
|
1330
|
+
apiPassphrase: '可选, API passphrase',
|
|
1331
|
+
funder: '可选, 资金地址',
|
|
1332
|
+
},
|
|
1259
1333
|
execute: async (args) => {
|
|
1260
1334
|
try {
|
|
1261
1335
|
const { cancelOrder } = await import('../constraint-runtime/dist/tools/PolymarketSDK/cancelOrder.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js'));
|
|
1262
|
-
const r = await cancelOrder({
|
|
1336
|
+
const r = await cancelOrder({
|
|
1337
|
+
privateKey: String(args.privateKey),
|
|
1338
|
+
orderId: String(args.orderId),
|
|
1339
|
+
apiKey: args.apiKey ? String(args.apiKey) : undefined,
|
|
1340
|
+
apiSecret: args.apiSecret ? String(args.apiSecret) : undefined,
|
|
1341
|
+
apiPassphrase: args.apiPassphrase ? String(args.apiPassphrase) : undefined,
|
|
1342
|
+
funder: args.funder ? String(args.funder) : undefined,
|
|
1343
|
+
});
|
|
1263
1344
|
if (r.success)
|
|
1264
1345
|
return { success: true, output: `✅ 取消订单: ${r.message}` };
|
|
1265
1346
|
return { success: false, error: r.message, output: r.message };
|
package/dist/agents/pi-sdk.js
CHANGED
|
@@ -31,7 +31,8 @@ import { registerBuiltinTools, registerWalletTools, setupInboxListener, Idempote
|
|
|
31
31
|
export { createAgentSession, getAgentSession, resetAgentSession, runSelfImproveLoop, } from './pi-sdk-session-factory.js';
|
|
32
32
|
// Judgment 注入门 (P0): 在主对话 LLM 调起前自动拼入 Top 3 判断力
|
|
33
33
|
// 失败静默, 不阻塞主对话
|
|
34
|
-
import { injectJudgmentGate, recordJudgmentUsage } from '../pi-ecosystem-judgment/injection-gate.js';
|
|
34
|
+
import { injectJudgmentGate, injectNegativeGuard, recordJudgmentUsage } from '../pi-ecosystem-judgment/injection-gate.js';
|
|
35
|
+
import { getInjectionMaxChars } from '../bootstrap/exhaust-scrubber.js';
|
|
35
36
|
// 持续监控门 (P3): AI 回复后审计是否违反原则
|
|
36
37
|
import { monitorAfterReply } from '../pi-ecosystem-judgment/monitor-gate.js';
|
|
37
38
|
// Bootstrap 生命周期 hook (SessionStart / Stop / PreToolUse)
|
|
@@ -109,6 +110,8 @@ export class PiAgentSession {
|
|
|
109
110
|
*/
|
|
110
111
|
judgmentGateAddition = '';
|
|
111
112
|
judgmentGateUsedIds = [];
|
|
113
|
+
/** 2026-07-22 设计 B: 负向判断力 (避免清单) 注入用到的 judgment id */
|
|
114
|
+
judgmentGateNegativeUsedIds = [];
|
|
112
115
|
// 2026-06-18: 来自 web server markedPrompt 外的 contextHint (channel/judgment/distill/remote channels),
|
|
113
116
|
// 拼到 systemPrompt 末尾, 别再混进 user message
|
|
114
117
|
contextHintAddition = '';
|
|
@@ -147,11 +150,25 @@ export class PiAgentSession {
|
|
|
147
150
|
try {
|
|
148
151
|
// P-Action 4 (2026-06-15) 路径 1 整合: 透传 maxChars=1500 (≈ 375 tokens 硬上限)
|
|
149
152
|
// 路径 2/3 检测由 injection-gate 内部 alreadyInjectedSources 处理 (目前 assembleSystemPrompt 还没注入 value-store 标记, 所以这里不传)
|
|
150
|
-
|
|
153
|
+
// 2026-07-22 设计 C: maxChars 读背压动态值 (涡轮增压进气调参)
|
|
154
|
+
// 上下文紧张 (high) → 收紧 800; 宽裕 (idle/low) → 放宽 1800; 默认 medium 1500
|
|
155
|
+
const gate = await injectJudgmentGate(input, {}, { maxChars: getInjectionMaxChars() });
|
|
151
156
|
this.judgmentGateAddition = gate.systemAddition;
|
|
152
157
|
this.judgmentGateUsedIds = gate.usedIds;
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
// 2026-07-22 设计 B: 负向判断力回收 — "避免清单"注入 (显式, 进 prompt)
|
|
159
|
+
// 判断力负向是"判断力"非"废气", 可进 prompt 作为约束 (精准 = 正向指引 + 负向避免)
|
|
160
|
+
try {
|
|
161
|
+
const neg = await injectNegativeGuard(input, {}, { maxChars: 300 });
|
|
162
|
+
if (neg.didInject && neg.systemAddition) {
|
|
163
|
+
this.judgmentGateAddition += '\n' + neg.systemAddition;
|
|
164
|
+
this.judgmentGateNegativeUsedIds = neg.usedIds;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (negErr) {
|
|
168
|
+
console.warn('[PiAgent] negative guard failed (non-fatal):', negErr);
|
|
169
|
+
}
|
|
170
|
+
if (this.judgmentGateUsedIds.length > 0 || this.judgmentGateNegativeUsedIds.length > 0) {
|
|
171
|
+
safePhase('gate_done', { usedCount: this.judgmentGateUsedIds.length, negativeCount: this.judgmentGateNegativeUsedIds.length, didInject: gate.didInject, skipReason: gate.skipReason });
|
|
155
172
|
}
|
|
156
173
|
}
|
|
157
174
|
catch (err) {
|
|
@@ -163,6 +180,7 @@ export class PiAgentSession {
|
|
|
163
180
|
clearJudgmentGate() {
|
|
164
181
|
this.judgmentGateAddition = '';
|
|
165
182
|
this.judgmentGateUsedIds = [];
|
|
183
|
+
this.judgmentGateNegativeUsedIds = [];
|
|
166
184
|
}
|
|
167
185
|
constructor(config) {
|
|
168
186
|
this.cwd = config.cwd;
|
|
@@ -491,7 +509,10 @@ export class PiAgentSession {
|
|
|
491
509
|
}
|
|
492
510
|
finally {
|
|
493
511
|
if (this.judgmentGateUsedIds.length > 0) {
|
|
494
|
-
recordJudgmentUsage(this.judgmentGateUsedIds, { userInput: input }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage failed:', err));
|
|
512
|
+
recordJudgmentUsage(this.judgmentGateUsedIds, { userInput: input, polarity: 'positive' }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage failed:', err));
|
|
513
|
+
}
|
|
514
|
+
if (this.judgmentGateNegativeUsedIds.length > 0) {
|
|
515
|
+
recordJudgmentUsage(this.judgmentGateNegativeUsedIds, { userInput: input, polarity: 'negative' }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage (negative) failed:', err));
|
|
495
516
|
}
|
|
496
517
|
this.clearJudgmentGate();
|
|
497
518
|
this.currentSignal = null;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* exhaust-scrubber.ts — 上下文废气涡轮 (2026-07-22 设计 C)
|
|
3
|
+
*
|
|
4
|
+
* 思想锚点: 涡轮增压 (Turbocharger)
|
|
5
|
+
* 排气 (废气) = session-window dropped / memory-compressor skipped / compaction stage drops / truncation
|
|
6
|
+
* 涡轮 (本模块) = 采样丢弃事件, 聚合成"背压"指标
|
|
7
|
+
* 进气增压 = 背压反向调进气侧参数 (压缩阈值 / 检索 top-k / judgment 注入 maxChars)
|
|
8
|
+
* 燃烧室 (prompt) = 废气**不进**这里 (保持精准), 只让压力调参
|
|
9
|
+
*
|
|
10
|
+
* 拍板: 上下文废气 → 不进 prompt, 只调参, 进 log/memory, 隐式
|
|
11
|
+
*
|
|
12
|
+
* 三个职责:
|
|
13
|
+
* 1. recordExhaust(event): 采样丢弃事件 → 环形缓冲 + 落盘 ~/.bolloon/engine/backpressure.jsonl (log)
|
|
14
|
+
* 2. getBackpressure(): 算背压等级 (idle/low/medium/high) — 进气侧读它调参
|
|
15
|
+
* 3. getInjectionMaxChars(level): 背压 → judgment 注入 maxChars 映射 (进气增压)
|
|
16
|
+
* 4. maybeWriteExhaustMemory(): 背压高峰持续 → 模板摘要写 memory (月度滚动, 不调 LLM)
|
|
17
|
+
*
|
|
18
|
+
* 设计原则:
|
|
19
|
+
* - 零新数据源: 只订阅已有丢弃事件
|
|
20
|
+
* - 隐式: 用户看不到废气内容, 只看到压力等级 (可选背压表)
|
|
21
|
+
* - 静默: 任何失败 console.warn 不阻塞主流程
|
|
22
|
+
* - 不存原文: 只存 source + reason + 估算 token 数 (防隐私/膨胀)
|
|
23
|
+
*/
|
|
24
|
+
import * as fs from 'fs/promises';
|
|
25
|
+
import * as os from 'os';
|
|
26
|
+
import * as path from 'path';
|
|
27
|
+
// ============== 路径 ==============
|
|
28
|
+
function getEngineDir(home) {
|
|
29
|
+
return path.join(home || os.homedir(), '.bolloon', 'engine');
|
|
30
|
+
}
|
|
31
|
+
function getBackpressureLogPath(home) {
|
|
32
|
+
return path.join(getEngineDir(home), 'backpressure.jsonl');
|
|
33
|
+
}
|
|
34
|
+
function getMemoryEngineDir(agentId, home) {
|
|
35
|
+
// 跟 memory-compressor.ts 一致: ~/.bolloon/memory/<agentId>/engine/
|
|
36
|
+
const safe = agentId.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
|
|
37
|
+
return path.join(home || os.homedir(), '.bolloon', 'memory', safe, 'engine');
|
|
38
|
+
}
|
|
39
|
+
// ============== 状态 (模块级单例, 跟 chat-archiver 同模式) ==============
|
|
40
|
+
const RING_CAPACITY = 100;
|
|
41
|
+
const ringBuffer = [];
|
|
42
|
+
let droppedTokensTotal = 0;
|
|
43
|
+
const bySource = {};
|
|
44
|
+
let monthlyHighCount = 0; // 当月 high 事件累计 (触发 memory 摘要用)
|
|
45
|
+
// ============== 采样 (涡轮入口) ==============
|
|
46
|
+
/**
|
|
47
|
+
* 记录一个废气事件. 推入环形缓冲 + 落盘 jsonl (log). 静默失败.
|
|
48
|
+
*
|
|
49
|
+
* @param event 废气事件 (source + reason + 可选 droppedTokens; ts 不传则自动填)
|
|
50
|
+
* @param home 可选 home 目录 (测试注入)
|
|
51
|
+
*/
|
|
52
|
+
export async function recordExhaust(event, home) {
|
|
53
|
+
try {
|
|
54
|
+
const full = {
|
|
55
|
+
ts: event.ts || new Date().toISOString(),
|
|
56
|
+
source: event.source,
|
|
57
|
+
reason: event.reason,
|
|
58
|
+
droppedTokens: event.droppedTokens,
|
|
59
|
+
};
|
|
60
|
+
// 环形缓冲
|
|
61
|
+
ringBuffer.push(full);
|
|
62
|
+
if (ringBuffer.length > RING_CAPACITY) {
|
|
63
|
+
ringBuffer.shift();
|
|
64
|
+
}
|
|
65
|
+
// 计数
|
|
66
|
+
if (full.droppedTokens && full.droppedTokens > 0) {
|
|
67
|
+
droppedTokensTotal += full.droppedTokens;
|
|
68
|
+
}
|
|
69
|
+
bySource[full.source] = (bySource[full.source] || 0) + 1;
|
|
70
|
+
// 落盘 jsonl (log) — append 模式, 跟 chat-archiver 同
|
|
71
|
+
const logPath = getBackpressureLogPath(home);
|
|
72
|
+
await fs.mkdir(path.dirname(logPath), { recursive: true });
|
|
73
|
+
await fs.appendFile(logPath, JSON.stringify(full) + '\n', 'utf-8');
|
|
74
|
+
// high 事件累计 → 触发 memory 摘要 (火忘)
|
|
75
|
+
const snap = getBackpressure(home);
|
|
76
|
+
if (snap.level === 'high') {
|
|
77
|
+
monthlyHighCount++;
|
|
78
|
+
// 每 10 次 high 触发一次 memory 摘要 (节流, 防频繁写盘)
|
|
79
|
+
if (monthlyHighCount % 10 === 0) {
|
|
80
|
+
maybeWriteExhaustMemorySummary('default', home).catch(() => { });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.warn('[exhaust-scrubber] recordExhaust failed (non-fatal):', err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 同步版本 (供不方便 await 的调用方; 只更新内存环形缓冲, 不落盘).
|
|
90
|
+
* 落盘由下次 async recordExhaust 或显式 flush 触发.
|
|
91
|
+
*/
|
|
92
|
+
export function recordExhaustSync(event) {
|
|
93
|
+
try {
|
|
94
|
+
const full = {
|
|
95
|
+
ts: event.ts || new Date().toISOString(),
|
|
96
|
+
source: event.source,
|
|
97
|
+
reason: event.reason,
|
|
98
|
+
droppedTokens: event.droppedTokens,
|
|
99
|
+
};
|
|
100
|
+
ringBuffer.push(full);
|
|
101
|
+
if (ringBuffer.length > RING_CAPACITY)
|
|
102
|
+
ringBuffer.shift();
|
|
103
|
+
if (full.droppedTokens && full.droppedTokens > 0)
|
|
104
|
+
droppedTokensTotal += full.droppedTokens;
|
|
105
|
+
bySource[full.source] = (bySource[full.source] || 0) + 1;
|
|
106
|
+
}
|
|
107
|
+
catch { /* 静默 */ }
|
|
108
|
+
}
|
|
109
|
+
// ============== 聚合 (背压等级) ==============
|
|
110
|
+
/**
|
|
111
|
+
* 算当前背压快照. 基于环形缓冲 + 最近 60s 事件速率.
|
|
112
|
+
*
|
|
113
|
+
* 等级映射 (dropRatePerMin = 最近 60s 内事件数 / 60 * 60... 简化为最近 60s 事件数本身):
|
|
114
|
+
* 0 → idle
|
|
115
|
+
* 1-2 → low
|
|
116
|
+
* 3-10 → medium
|
|
117
|
+
* > 10 → high
|
|
118
|
+
*/
|
|
119
|
+
export function getBackpressure(_home) {
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
const recent = ringBuffer.filter((e) => {
|
|
122
|
+
const t = Date.parse(e.ts);
|
|
123
|
+
return Number.isFinite(t) && now - t < 60_000; // 最近 60s
|
|
124
|
+
});
|
|
125
|
+
const dropRatePerMin = recent.length;
|
|
126
|
+
let level;
|
|
127
|
+
if (dropRatePerMin === 0)
|
|
128
|
+
level = 'idle';
|
|
129
|
+
else if (dropRatePerMin <= 2)
|
|
130
|
+
level = 'low';
|
|
131
|
+
else if (dropRatePerMin <= 10)
|
|
132
|
+
level = 'medium';
|
|
133
|
+
else
|
|
134
|
+
level = 'high';
|
|
135
|
+
const srcCount = {};
|
|
136
|
+
for (const e of ringBuffer) {
|
|
137
|
+
srcCount[e.source] = (srcCount[e.source] || 0) + 1;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
level,
|
|
141
|
+
dropCount: ringBuffer.length,
|
|
142
|
+
droppedTokensTotal,
|
|
143
|
+
dropRatePerMin,
|
|
144
|
+
lastTs: ringBuffer.length > 0 ? ringBuffer[ringBuffer.length - 1].ts : '',
|
|
145
|
+
bySource: srcCount,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export function getPressureLevel(home) {
|
|
149
|
+
return getBackpressure(home).level;
|
|
150
|
+
}
|
|
151
|
+
// ============== 进气增压 (背压 → 调参) ==============
|
|
152
|
+
/**
|
|
153
|
+
* 背压 → judgment 注入 maxChars 映射 (进气增压核心).
|
|
154
|
+
*
|
|
155
|
+
* idle/low → 1800 (放宽注入, 上下文宽裕)
|
|
156
|
+
* medium → 1500 (默认, 现状)
|
|
157
|
+
* high → 800 (收紧注入, 上下文紧张, 留空间给主任务)
|
|
158
|
+
*
|
|
159
|
+
* 调用方: pi-sdk.ts computeJudgmentGate 的 maxChars 从固定 1500 改为读这个.
|
|
160
|
+
*/
|
|
161
|
+
export function getInjectionMaxChars(level, home) {
|
|
162
|
+
const lvl = level ?? getPressureLevel(home);
|
|
163
|
+
switch (lvl) {
|
|
164
|
+
case 'idle':
|
|
165
|
+
case 'low':
|
|
166
|
+
return 1800;
|
|
167
|
+
case 'medium':
|
|
168
|
+
return 1500;
|
|
169
|
+
case 'high':
|
|
170
|
+
return 800;
|
|
171
|
+
default:
|
|
172
|
+
return 1500;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 背压 → 检索 top-k 映射 (进气增压, 检索侧).
|
|
177
|
+
* idle/low → 8, medium → 5, high → 3
|
|
178
|
+
*/
|
|
179
|
+
export function getRetrievalTopK(level, home) {
|
|
180
|
+
const lvl = level ?? getPressureLevel(home);
|
|
181
|
+
switch (lvl) {
|
|
182
|
+
case 'idle':
|
|
183
|
+
case 'low':
|
|
184
|
+
return 8;
|
|
185
|
+
case 'medium':
|
|
186
|
+
return 5;
|
|
187
|
+
case 'high':
|
|
188
|
+
return 3;
|
|
189
|
+
default:
|
|
190
|
+
return 5;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// ============== memory 落地 (月度摘要, 拍板要求"进 memory") ==============
|
|
194
|
+
/**
|
|
195
|
+
* 背压高峰持续 → 模板摘要写 memory (月度滚动, 不调 LLM).
|
|
196
|
+
* ~/.bolloon/memory/<agentId>/engine/exhaust-<YYYY-MM>.summary.md (append)
|
|
197
|
+
*
|
|
198
|
+
* 让"为什么这段时间上下文一直紧张"沉淀进 memory 供 agent 回看.
|
|
199
|
+
* 节流: 每 10 次 high 触发一次 (recordExhaust 内控制).
|
|
200
|
+
*/
|
|
201
|
+
export async function maybeWriteExhaustMemorySummary(agentId, home) {
|
|
202
|
+
try {
|
|
203
|
+
const snap = getBackpressure(home);
|
|
204
|
+
if (snap.level !== 'high')
|
|
205
|
+
return { written: false };
|
|
206
|
+
const now = new Date();
|
|
207
|
+
const yearMonth = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, '0')}`;
|
|
208
|
+
const dir = getMemoryEngineDir(agentId, home);
|
|
209
|
+
const file = path.join(dir, `exhaust-${yearMonth}.summary.md`);
|
|
210
|
+
await fs.mkdir(dir, { recursive: true });
|
|
211
|
+
const block = `\n\n---\n\n## 引擎背压高峰 @ ${now.toISOString()} (level=high)\n\n` +
|
|
212
|
+
`- 最近 60s 丢弃事件: ${snap.dropRatePerMin} 次/min\n` +
|
|
213
|
+
`- 环形缓冲事件总数: ${snap.dropCount}\n` +
|
|
214
|
+
`- 估算丢弃 token 累计: ${snap.droppedTokensTotal}\n` +
|
|
215
|
+
`- 按 source 分布: ${Object.entries(snap.bySource).map(([k, v]) => `${k}=${v}`).join(', ') || '(无)'}\n` +
|
|
216
|
+
`- 含义: 上下文持续紧张, 进气侧已自动收紧 (judgment 注入 maxChars=800, 检索 top-k=3)\n`;
|
|
217
|
+
await fs.appendFile(file, block, 'utf-8');
|
|
218
|
+
return { written: true, path: file };
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
console.warn('[exhaust-scrubber] maybeWriteExhaustMemorySummary failed (non-fatal):', err);
|
|
222
|
+
return { written: false };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ============== 测试辅助 ==============
|
|
226
|
+
/** 重置模块状态 (仅测试用) */
|
|
227
|
+
export function __resetForTest() {
|
|
228
|
+
ringBuffer.length = 0;
|
|
229
|
+
droppedTokensTotal = 0;
|
|
230
|
+
for (const k of Object.keys(bySource))
|
|
231
|
+
delete bySource[k];
|
|
232
|
+
monthlyHighCount = 0;
|
|
233
|
+
}
|
|
@@ -161,6 +161,17 @@ export async function compressSessionToMemory(opts) {
|
|
|
161
161
|
await fs.mkdir(path.dirname(summaryPath), { recursive: true });
|
|
162
162
|
await fs.appendFile(summaryPath, block, 'utf-8');
|
|
163
163
|
await writeCursor(cursorPath, allMessages.length);
|
|
164
|
+
// 2026-07-22 设计 C: 废气采样 — 压缩成功 = 上下文需要压缩的信号, 记入涡轮 (隐式)
|
|
165
|
+
// 废气不进 prompt, 只调参 (背压高 → judgment 注入收紧). 落 log/memory.
|
|
166
|
+
try {
|
|
167
|
+
const { recordExhaust } = await import('./exhaust-scrubber.js');
|
|
168
|
+
recordExhaust({
|
|
169
|
+
source: 'memory-compressor',
|
|
170
|
+
reason: 'compress-summary-written',
|
|
171
|
+
droppedTokens: newMessages.length * 200, // 粗估 200 tokens/msg
|
|
172
|
+
}, opts.home).catch(() => { });
|
|
173
|
+
}
|
|
174
|
+
catch { /* 静默 */ }
|
|
164
175
|
return {
|
|
165
176
|
summaryPath,
|
|
166
177
|
cursorPath,
|
|
@@ -1,6 +1,21 @@
|
|
|
1
|
+
import { buildClobClient } from './clobShared';
|
|
1
2
|
export async function cancelOrder(params) {
|
|
2
|
-
|
|
3
|
-
success: false,
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
if (!params.privateKey) {
|
|
4
|
+
return { success: false, message: '取消订单需要提供钱包私钥 privateKey' };
|
|
5
|
+
}
|
|
6
|
+
if (!params.orderId) {
|
|
7
|
+
return { success: false, message: 'orderId 必填' };
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
const { client } = await buildClobClient({
|
|
11
|
+
privateKey: params.privateKey,
|
|
12
|
+
creds: { apiKey: params.apiKey, apiSecret: params.apiSecret, apiPassphrase: params.apiPassphrase },
|
|
13
|
+
funder: params.funder,
|
|
14
|
+
});
|
|
15
|
+
const resp = await client.cancelOrder({ orderID: params.orderId });
|
|
16
|
+
return { success: true, message: '已提交取消', raw: resp };
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
return { success: false, message: `取消失败: ${e?.message || e}` };
|
|
20
|
+
}
|
|
6
21
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polymarket CLOB 下单/查单/撤单 共享依赖
|
|
3
|
+
*
|
|
4
|
+
* 真实实现基于 @polymarket/clob-client (ClobClient):
|
|
5
|
+
* - 签名用 viem WalletClient (privateKeyToAccount + polygon + http transport)
|
|
6
|
+
* - 下单前需 ApiKeyCreds (key/secret/passphrase), 由 createOrDeriveApiKey() 从签名派生
|
|
7
|
+
* - tokenID / tickSize / negRisk 来自 Gamma 市场元数据 (https://gamma-api.polymarket.com/markets/:id)
|
|
8
|
+
*
|
|
9
|
+
* chainId = 137 (Polygon), host = https://clob.polymarket.com
|
|
10
|
+
*/
|
|
11
|
+
import { ClobClient, OrderType, Side } from '@polymarket/clob-client';
|
|
12
|
+
import { createWalletClient, http } from 'viem';
|
|
13
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
14
|
+
import { polygon } from 'viem/chains';
|
|
15
|
+
export const CLOB_HOST = 'https://clob.polymarket.com';
|
|
16
|
+
export const CHAIN_ID = 137;
|
|
17
|
+
export function normalizePrivateKey(pk) {
|
|
18
|
+
const s = pk.startsWith('0x') ? pk : `0x${pk}`;
|
|
19
|
+
return s;
|
|
20
|
+
}
|
|
21
|
+
function safeParseArray(v) {
|
|
22
|
+
if (Array.isArray(v))
|
|
23
|
+
return v;
|
|
24
|
+
if (typeof v === 'string') {
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(v);
|
|
27
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 从 Gamma 取市场元数据: clobTokenIds / outcomes / tickSize / negRisk.
|
|
37
|
+
* 优先用 Gamma 路径端点 (带 tickSize/negRisk), 失败回退 polymarket-sdk listMarkets({id}).
|
|
38
|
+
*/
|
|
39
|
+
export async function fetchMarketMeta(marketId) {
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch(`https://gamma-api.polymarket.com/markets/${encodeURIComponent(marketId)}`);
|
|
42
|
+
if (res.ok) {
|
|
43
|
+
const m = await res.json();
|
|
44
|
+
return {
|
|
45
|
+
clobTokenIds: safeParseArray(m.clobTokenIds),
|
|
46
|
+
outcomes: safeParseArray(m.outcomes),
|
|
47
|
+
tickSize: typeof m.tickSize === 'string' ? m.tickSize : '0.01',
|
|
48
|
+
negRisk: !!m.negRisk,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// fall through to sdk
|
|
54
|
+
}
|
|
55
|
+
const { listMarkets } = await import('polymarket-sdk').catch(() => ({ listMarkets: async () => [] }));
|
|
56
|
+
const ms = await listMarkets({ id: marketId });
|
|
57
|
+
const m = ms?.[0];
|
|
58
|
+
if (!m)
|
|
59
|
+
throw new Error(`未找到市场: ${marketId}`);
|
|
60
|
+
return {
|
|
61
|
+
clobTokenIds: safeParseArray(m.clobTokenIds),
|
|
62
|
+
outcomes: safeParseArray(m.outcomes),
|
|
63
|
+
tickSize: '0.01',
|
|
64
|
+
negRisk: false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 由 outcome (如 "Yes"/"No" 或索引 0/1) 或显式 tokenId 解析出要交易的 tokenID.
|
|
69
|
+
* 都不给 → 默认取第一个 clobTokenId (通常是 "Yes").
|
|
70
|
+
*/
|
|
71
|
+
export function resolveTokenId(meta, tokenId, outcome) {
|
|
72
|
+
if (tokenId)
|
|
73
|
+
return tokenId;
|
|
74
|
+
if (!meta.clobTokenIds || meta.clobTokenIds.length === 0)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (outcome === undefined || outcome === null)
|
|
77
|
+
return meta.clobTokenIds[0];
|
|
78
|
+
if (typeof outcome === 'number')
|
|
79
|
+
return meta.clobTokenIds[outcome];
|
|
80
|
+
const idx = meta.outcomes.findIndex((o) => String(o).toLowerCase() === String(outcome).toLowerCase());
|
|
81
|
+
return idx >= 0 ? meta.clobTokenIds[idx] : undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 构造已鉴权的 ClobClient.
|
|
85
|
+
* - 若提供完整 apiKey/apiSecret/apiPassphrase → 直接用
|
|
86
|
+
* - 否则用签名派生 ApiKey (createOrDeriveApiKey, 需联网)
|
|
87
|
+
* signatureType = 0 (EOA / 浏览器钱包, 对应原始私钥签名)
|
|
88
|
+
*/
|
|
89
|
+
export async function buildClobClient(params) {
|
|
90
|
+
const account = privateKeyToAccount(normalizePrivateKey(params.privateKey));
|
|
91
|
+
const signer = createWalletClient({ account, chain: polygon, transport: http() });
|
|
92
|
+
const address = account.address;
|
|
93
|
+
let creds;
|
|
94
|
+
if (params.creds?.apiKey && params.creds?.apiSecret && params.creds?.apiPassphrase) {
|
|
95
|
+
creds = { key: params.creds.apiKey, secret: params.creds.apiSecret, passphrase: params.creds.apiPassphrase };
|
|
96
|
+
}
|
|
97
|
+
if (!creds) {
|
|
98
|
+
const tmp = new ClobClient(CLOB_HOST, CHAIN_ID, signer);
|
|
99
|
+
creds = await tmp.createOrDeriveApiKey();
|
|
100
|
+
}
|
|
101
|
+
const client = new ClobClient(CLOB_HOST, CHAIN_ID, signer, creds, 0, params.funder ?? address);
|
|
102
|
+
return { client, address };
|
|
103
|
+
}
|
|
104
|
+
export { OrderType, Side };
|
|
@@ -1,6 +1,36 @@
|
|
|
1
|
+
import { buildClobClient, fetchMarketMeta, resolveTokenId } from './clobShared';
|
|
1
2
|
export async function createOrder(params) {
|
|
2
|
-
|
|
3
|
-
success: false,
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
if (!params.privateKey) {
|
|
4
|
+
return { success: false, message: '下单需要提供钱包私钥 privateKey (用于 EIP-712 订单签名)' };
|
|
5
|
+
}
|
|
6
|
+
if (!params.marketId) {
|
|
7
|
+
return { success: false, message: 'marketId 必填' };
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
const meta = await fetchMarketMeta(params.marketId);
|
|
11
|
+
const tokenID = resolveTokenId(meta, params.tokenId, params.outcome);
|
|
12
|
+
if (!tokenID) {
|
|
13
|
+
return { success: false, message: '无法解析 tokenID (outcome 不匹配或市场无 clobTokenIds)' };
|
|
14
|
+
}
|
|
15
|
+
const { client } = await buildClobClient({
|
|
16
|
+
privateKey: params.privateKey,
|
|
17
|
+
creds: { apiKey: params.apiKey, apiSecret: params.apiSecret, apiPassphrase: params.apiPassphrase },
|
|
18
|
+
funder: params.funder,
|
|
19
|
+
});
|
|
20
|
+
const resp = await client.createAndPostOrder({
|
|
21
|
+
tokenID,
|
|
22
|
+
price: Number(params.price),
|
|
23
|
+
size: Number(params.size),
|
|
24
|
+
side: params.side,
|
|
25
|
+
}, { tickSize: meta.tickSize, negRisk: meta.negRisk }, (params.orderType ?? 'GTC'));
|
|
26
|
+
return {
|
|
27
|
+
success: true,
|
|
28
|
+
orderId: resp?.orderID ?? resp?.id,
|
|
29
|
+
status: resp?.status,
|
|
30
|
+
raw: resp,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return { success: false, message: `下单失败: ${e?.message || e}` };
|
|
35
|
+
}
|
|
6
36
|
}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
import { buildClobClient } from './clobShared';
|
|
1
2
|
export async function getOrders(params = {}) {
|
|
2
|
-
|
|
3
|
-
orders: [],
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
if (!params.privateKey) {
|
|
4
|
+
return { orders: [], message: '查询订单需要提供钱包私钥 privateKey' };
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
const { client } = await buildClobClient({
|
|
8
|
+
privateKey: params.privateKey,
|
|
9
|
+
creds: { apiKey: params.apiKey, apiSecret: params.apiSecret, apiPassphrase: params.apiPassphrase },
|
|
10
|
+
funder: params.funder,
|
|
11
|
+
});
|
|
12
|
+
const resp = await client.getOpenOrders(params.marketId ? { market: params.marketId } : {});
|
|
13
|
+
const orders = Array.isArray(resp) ? resp : resp?.orders ?? [];
|
|
14
|
+
return { orders, message: 'OK' };
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
return { orders: [], message: `查询失败: ${e?.message || e}` };
|
|
18
|
+
}
|
|
6
19
|
}
|