@bolloon/bolloon-agent 0.3.15 → 0.3.17
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/agent-identity.js +138 -0
- package/dist/agents/error-classifier.js +118 -0
- package/dist/agents/parse-tool-call.js +196 -1
- package/dist/agents/pi-sdk-tools.js +13 -0
- package/dist/agents/pi-sdk.js +195 -262
- package/dist/cli/loading-tui.js +43 -36
- package/dist/cli-entry.js +15 -8
- package/dist/electron/config.js +9 -14
- package/dist/electron/dialogs.js +16 -53
- package/dist/electron/first-run.js +24 -65
- package/dist/electron/ipc.js +10 -14
- package/dist/electron/logger.js +7 -44
- package/dist/electron/main.js +42 -45
- package/dist/electron/menu.js +13 -18
- package/dist/electron/paths.js +12 -54
- package/dist/electron/server.js +18 -57
- package/dist/electron/tray.js +15 -53
- package/dist/electron/window.js +22 -61
- package/dist/electron-preload.js +16 -19
- package/dist/electron.js +1 -4
- package/dist/external-engines/discovery.js +11 -0
- package/dist/index.js +172 -15
- package/dist/lsp/lsp-manager.js +281 -0
- package/dist/lsp/lsp-tools.js +222 -0
- package/dist/network/auto-peer-discovery.js +77 -0
- package/dist/network/did-agent-resolver.js +206 -0
- package/dist/network/p2p-direct.js +1 -1
- package/dist/network/p2p-outbox.js +2 -2
- package/dist/utils/auto-update.js +12 -51
- package/dist/web/client.js +4323 -4812
- package/dist/web/components/p2p/P2PModal.js +188 -0
- package/dist/web/components/p2p/index.js +276 -234
- package/dist/web/components/p2p/p2p-modal.js +664 -0
- package/dist/web/components/p2p/p2p-tools.js +248 -0
- package/dist/web/index.html +7 -0
- package/dist/web/server.js +164 -41
- package/dist/web/style.css +58 -3
- package/dist/web/ui/message-renderer.js +535 -396
- package/dist/web/ui/step-timeline.js +371 -272
- package/package.json +3 -3
package/dist/agents/pi-sdk.js
CHANGED
|
@@ -41,7 +41,8 @@ import { onPostToolUse } from '../bootstrap/lifecycle-hooks.js';
|
|
|
41
41
|
import { budgetReduce, snip, microcompact } from '../context-compaction/index.js';
|
|
42
42
|
// React Harness: 8-gate + 4-guard (防越权 / 防 prompt 注入)
|
|
43
43
|
import { ReactHarness } from '../security/react-harness.js';
|
|
44
|
-
import { parseToolCall as parseToolCallImpl, isFinalResponse as isFinalResponseImpl, extractFinalAnswer as extractFinalAnswerImpl } from './parse-tool-call.js';
|
|
44
|
+
import { parseToolCall as parseToolCallImpl, parseAllToolCalls, isFinalResponse as isFinalResponseImpl, extractFinalAnswer as extractFinalAnswerImpl } from './parse-tool-call.js';
|
|
45
|
+
import { buildObservation, buildReflection, formatObservationWithReflection } from './error-classifier.js';
|
|
45
46
|
import { sessionStore as defaultSessionStore } from './session-store.js';
|
|
46
47
|
import { ToolRegistry } from './tool-registry.js';
|
|
47
48
|
import { decideMaxIterations, decideContextOverflow, shouldCompactBeforeIteration } from './react-loop.js';
|
|
@@ -1127,303 +1128,235 @@ ${toolDefs}
|
|
|
1127
1128
|
// Bug 5 (2026-07-17): 优先用 LLM 的 native tool_calls (response.toolCalls), 再回退到文本解析
|
|
1128
1129
|
// deepseek-v4-flash 用 OpenAI 协议 tools 时, 会真返回结构化 tool_calls 数组
|
|
1129
1130
|
// 之前 nativeToolCalls 被读了不用, 只查 reply 文本, 导致 LLM 明明选了工具但代码找不到
|
|
1130
|
-
|
|
1131
|
+
// 2026-07-28: 修复多工具调用 — 收集 ALL tool calls, 顺序执行后一次性返回
|
|
1132
|
+
let toolCalls = [];
|
|
1133
|
+
// 路径 A: native OpenAI 协议 tool_calls (可能多个)
|
|
1131
1134
|
if (nativeToolCalls && nativeToolCalls.length > 0) {
|
|
1132
|
-
const nc
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
catch (err) {
|
|
1147
|
-
console.warn(`[PiAgent] 解析 native tool_call 失败, 回退到文本解析: ${err.message?.slice(0, 100)}`);
|
|
1148
|
-
toolCall = null;
|
|
1135
|
+
for (const nc of nativeToolCalls) {
|
|
1136
|
+
try {
|
|
1137
|
+
const args = typeof nc.function?.arguments === 'string'
|
|
1138
|
+
? JSON.parse(nc.function.arguments)
|
|
1139
|
+
: (nc.function?.arguments || {});
|
|
1140
|
+
toolCalls.push({
|
|
1141
|
+
name: nc.function?.name,
|
|
1142
|
+
args,
|
|
1143
|
+
id: nc.id || `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
catch (err) {
|
|
1147
|
+
console.warn(`[PiAgent] 解析 native tool_call 失败: ${err.message?.slice(0, 100)}`);
|
|
1148
|
+
}
|
|
1149
1149
|
}
|
|
1150
1150
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1151
|
+
// 路径 B: 文本解析 (parseAllToolCalls 收集全部)
|
|
1152
|
+
if (toolCalls.length === 0) {
|
|
1153
|
+
const knownTools = new Set(Array.from(this.tools.keys()));
|
|
1154
|
+
toolCalls = parseAllToolCalls(reply, { tools: knownTools });
|
|
1155
|
+
}
|
|
1156
|
+
// 回退路径 C: 原生 parseToolCall (单个)
|
|
1157
|
+
if (toolCalls.length === 0) {
|
|
1158
|
+
const single = this.parseToolCall(reply);
|
|
1159
|
+
if (single)
|
|
1160
|
+
toolCalls.push(single);
|
|
1161
|
+
}
|
|
1162
|
+
// 给每个 toolCall 分配稳定 id
|
|
1163
|
+
for (const tc of toolCalls) {
|
|
1164
|
+
if (!tc.id) {
|
|
1165
|
+
tc.id = `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1166
|
+
}
|
|
1158
1167
|
}
|
|
1159
|
-
if (
|
|
1168
|
+
if (toolCalls.length > 0) {
|
|
1169
|
+
// 把原始 LLM 回复 push 进 history (仅一次)
|
|
1160
1170
|
this.messageHistory.push({
|
|
1161
1171
|
role: 'assistant',
|
|
1162
1172
|
content: reply,
|
|
1163
|
-
|
|
1173
|
+
toolCalls: toolCalls.length > 1 ? toolCalls : [toolCalls[0]],
|
|
1164
1174
|
});
|
|
1165
|
-
//
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
content: `调用 ${toolCall.name}`,
|
|
1175
|
-
tool: toolCall.name,
|
|
1176
|
-
args: toolCall.args || {},
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
const tool = this.tools.get(toolCall.name);
|
|
1180
|
-
if (!tool) {
|
|
1181
|
-
consecutiveErrors++;
|
|
1182
|
-
// 2026-06-16 新增: 未知工具也要累计 (LLM 幻觉高频场景)
|
|
1183
|
-
totalErrors++;
|
|
1184
|
-
const errorResult = { success: false, error: `未知工具: ${toolCall.name}` };
|
|
1185
|
-
this.messageHistory.push({ role: 'tool', content: JSON.stringify(errorResult), toolResult: errorResult });
|
|
1186
|
-
this.logToHarness(toolCall.name, toolCall.args, errorResult);
|
|
1187
|
-
console.warn(`[PiAgent] 未知工具: ${toolCall.name} (累计 ${totalErrors}/${this.MAX_TOTAL_ERRORS}),跳过并继续`);
|
|
1188
|
-
continue;
|
|
1189
|
-
}
|
|
1190
|
-
// Bootstrap PreToolUse hook: 调工具前校验 (危险命令拦截)
|
|
1191
|
-
// 失败静默 — hook 自身挂掉 = 放行
|
|
1192
|
-
// P2: 透传 permissionMode (从 BootstrapOptions / env BOLLOON_PERM_MODE 解析)
|
|
1193
|
-
let toolToExecute = tool;
|
|
1194
|
-
try {
|
|
1195
|
-
const pre = await onPreToolUse({
|
|
1196
|
-
tool: toolCall.name,
|
|
1197
|
-
args: toolCall.args || {},
|
|
1198
|
-
permissionMode: this.currentPermissionMode,
|
|
1199
|
-
});
|
|
1200
|
-
if (!pre.allowed) {
|
|
1201
|
-
const deniedResult = {
|
|
1202
|
-
success: false,
|
|
1203
|
-
error: `PreToolUse 拒绝: ${pre.reason || '未通过安全校验'}`,
|
|
1204
|
-
};
|
|
1205
|
-
this.messageHistory.push({
|
|
1206
|
-
role: 'tool',
|
|
1207
|
-
content: JSON.stringify(deniedResult),
|
|
1208
|
-
toolResult: deniedResult,
|
|
1209
|
-
});
|
|
1210
|
-
this.logToHarness(toolCall.name, toolCall.args, deniedResult);
|
|
1211
|
-
if (onStream) {
|
|
1212
|
-
onStream({
|
|
1213
|
-
type: 'error',
|
|
1214
|
-
content: `🛡️ PreToolUse 拒绝 ${toolCall.name}: ${pre.reason || '安全校验失败'}`,
|
|
1215
|
-
tool: toolCall.name,
|
|
1216
|
-
});
|
|
1217
|
-
// 2026-06-15: step-timeline — 拦在 PreToolUse, 标 step_error
|
|
1218
|
-
onStream({
|
|
1219
|
-
type: 'step_error',
|
|
1220
|
-
content: `PreToolUse 拒绝 ${toolCall.name}`,
|
|
1221
|
-
tool: toolCall.name,
|
|
1222
|
-
error: pre.reason || '安全校验失败',
|
|
1223
|
-
});
|
|
1175
|
+
// 顺序执行每个工具
|
|
1176
|
+
for (let ti = 0; ti < toolCalls.length; ti++) {
|
|
1177
|
+
const toolCall = toolCalls[ti];
|
|
1178
|
+
const isMulti = toolCalls.length > 1;
|
|
1179
|
+
// 通知前端
|
|
1180
|
+
if (onStream) {
|
|
1181
|
+
onStream({ type: 'tool', content: `🔧 调用工具 (${ti + 1}/${toolCalls.length}): ${toolCall.name}`, tool: toolCall.name });
|
|
1182
|
+
if (toolCall.args && Object.keys(toolCall.args).length > 0) {
|
|
1183
|
+
onStream({ type: 'status', content: `📋 参数: ${JSON.stringify(toolCall.args)}`, tool: toolCall.name });
|
|
1224
1184
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
catch (err) {
|
|
1231
|
-
console.warn('[PiAgent] onPreToolUse failed (non-fatal, allowing):', err);
|
|
1232
|
-
}
|
|
1233
|
-
// React Harness: 8-gate + builtin-guards 校验 (在 PreToolUse 之后, 串接双层)
|
|
1234
|
-
// 失败静默, 拒绝时不调 tool.execute
|
|
1235
|
-
try {
|
|
1236
|
-
const pre = await this.reactHarness.preToolCall(toolCall.name, toolCall.args || {}, this.currentChannelId || undefined);
|
|
1237
|
-
if (!pre.allowed) {
|
|
1238
|
-
const deniedResult = {
|
|
1239
|
-
success: false,
|
|
1240
|
-
error: `Harness gate 拒绝 (${pre.details.rejectedBy}): ${pre.reason || '未通过安全校验'}`,
|
|
1241
|
-
};
|
|
1242
|
-
this.messageHistory.push({
|
|
1243
|
-
role: 'tool',
|
|
1244
|
-
content: JSON.stringify(deniedResult),
|
|
1245
|
-
toolResult: deniedResult,
|
|
1185
|
+
onStream({
|
|
1186
|
+
type: 'step_start',
|
|
1187
|
+
content: `调用 ${toolCall.name}${isMulti ? ` (${ti + 1}/${toolCalls.length})` : ''}`,
|
|
1188
|
+
tool: toolCall.name,
|
|
1189
|
+
args: toolCall.args || {},
|
|
1246
1190
|
});
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
console.warn(`[PiAgent] Harness denied ${toolCall.name} (${pre.details.rejectedBy}): ${pre.reason}`);
|
|
1191
|
+
}
|
|
1192
|
+
const tool = this.tools.get(toolCall.name);
|
|
1193
|
+
if (!tool) {
|
|
1194
|
+
consecutiveErrors++;
|
|
1195
|
+
totalErrors++;
|
|
1196
|
+
const errorResult = { success: false, error: `未知工具: ${toolCall.name}` };
|
|
1197
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(errorResult), toolResult: errorResult });
|
|
1198
|
+
this.logToHarness(toolCall.name, toolCall.args, errorResult);
|
|
1199
|
+
// 2026-07-28: 注入 Reflection 帮助 LLM 理解错误
|
|
1200
|
+
const obs = buildObservation(toolCall.name, toolCall.args, errorResult);
|
|
1201
|
+
const ref = buildReflection(toolCall.name, errorResult.error, totalErrors, lastFailedToolCount);
|
|
1202
|
+
this.messageHistory.push({ role: 'system', content: formatObservationWithReflection(obs, ref) });
|
|
1203
|
+
if (onStream)
|
|
1204
|
+
onStream({ type: 'status', content: `💡 Reflection: ${obs.summary}`, tool: 'system' });
|
|
1205
|
+
console.warn(`[PiAgent] 未知工具: ${toolCall.name} (累计 ${totalErrors}/${this.MAX_TOTAL_ERRORS}),跳过并继续`);
|
|
1263
1206
|
continue;
|
|
1264
1207
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
try {
|
|
1270
|
-
const toolStart = Date.now();
|
|
1271
|
-
let result = await tool.execute(toolCall.args);
|
|
1272
|
-
const toolDurationMs = Date.now() - toolStart;
|
|
1273
|
-
console.log(`[PiAgent] 工具 ${toolCall.name} 执行完成: success=${result.success} (${toolDurationMs}ms)`);
|
|
1274
|
-
// PostToolUse 审计 hook: 写 audit log, 默认 continue
|
|
1208
|
+
// Bootstrap PreToolUse hook: 调工具前校验 (危险命令拦截)
|
|
1209
|
+
// 失败静默 — hook 自身挂掉 = 放行
|
|
1210
|
+
// P2: 透传 permissionMode (从 BootstrapOptions / env BOLLOON_PERM_MODE 解析)
|
|
1211
|
+
let toolToExecute = tool;
|
|
1275
1212
|
try {
|
|
1276
|
-
await
|
|
1213
|
+
const pre = await onPreToolUse({
|
|
1277
1214
|
tool: toolCall.name,
|
|
1278
1215
|
args: toolCall.args || {},
|
|
1279
|
-
|
|
1280
|
-
success: result.success,
|
|
1281
|
-
output: result.output?.substring(0, 500),
|
|
1282
|
-
error: result.error,
|
|
1283
|
-
},
|
|
1284
|
-
durationMs: toolDurationMs,
|
|
1216
|
+
permissionMode: this.currentPermissionMode,
|
|
1285
1217
|
});
|
|
1218
|
+
if (!pre.allowed) {
|
|
1219
|
+
const deniedResult = {
|
|
1220
|
+
success: false,
|
|
1221
|
+
error: `PreToolUse 拒绝: ${pre.reason || '未通过安全校验'}`,
|
|
1222
|
+
};
|
|
1223
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(deniedResult), toolResult: deniedResult });
|
|
1224
|
+
this.logToHarness(toolCall.name, toolCall.args, deniedResult);
|
|
1225
|
+
if (onStream) {
|
|
1226
|
+
onStream({ type: 'error', content: `🛡️ PreToolUse 拒绝 ${toolCall.name}: ${pre.reason || '安全校验失败'}`, tool: toolCall.name });
|
|
1227
|
+
onStream({ type: 'step_error', content: `PreToolUse 拒绝 ${toolCall.name}`, tool: toolCall.name, error: pre.reason || '安全校验失败' });
|
|
1228
|
+
}
|
|
1229
|
+
console.warn(`[PiAgent] PreToolUse denied ${toolCall.name}: ${pre.reason}`);
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1286
1232
|
}
|
|
1287
|
-
catch (
|
|
1288
|
-
console.warn('[PiAgent]
|
|
1289
|
-
}
|
|
1290
|
-
// Context router: 拿最近一次 preToolCall 算的 hint, 拼到 tool result messageHistory
|
|
1291
|
-
// (LLM 下次看到 tool result 时, 能"记得"这次调用的安全约束)
|
|
1292
|
-
const routeHint = this.reactHarness.getLastRouteHint();
|
|
1293
|
-
if (routeHint && routeHint.systemAddition) {
|
|
1294
|
-
this.messageHistory.push({
|
|
1295
|
-
role: 'system',
|
|
1296
|
-
content: `[Harness Router Hint: ${routeHint.reason}]\n${routeHint.systemAddition}`,
|
|
1297
|
-
});
|
|
1298
|
-
this.reactHarness.clearRouteHint();
|
|
1233
|
+
catch (err) {
|
|
1234
|
+
console.warn('[PiAgent] onPreToolUse failed (non-fatal, allowing):', err);
|
|
1299
1235
|
}
|
|
1300
|
-
// React Harness:
|
|
1301
|
-
// 拒绝时 result.output 含敏感 → 替换为 generic message, 不污染 messageHistory
|
|
1236
|
+
// React Harness: 8-gate + builtin-guards 校验 (串接双层)
|
|
1302
1237
|
try {
|
|
1303
|
-
const
|
|
1304
|
-
if (!
|
|
1238
|
+
const pre = await this.reactHarness.preToolCall(toolCall.name, toolCall.args || {}, this.currentChannelId || undefined);
|
|
1239
|
+
if (!pre.allowed) {
|
|
1240
|
+
const deniedResult = { success: false, error: `Harness gate 拒绝 (${pre.details.rejectedBy}): ${pre.reason || '未通过安全校验'}` };
|
|
1241
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(deniedResult), toolResult: deniedResult });
|
|
1242
|
+
this.logToHarness(toolCall.name, toolCall.args, deniedResult);
|
|
1305
1243
|
if (onStream) {
|
|
1306
|
-
onStream({
|
|
1307
|
-
|
|
1308
|
-
content: `🛡️ Harness output 拒绝 ${toolCall.name}: ${post.reason || '输出含敏感信息'}`,
|
|
1309
|
-
tool: toolCall.name,
|
|
1310
|
-
});
|
|
1244
|
+
onStream({ type: 'error', content: `🛡️ Harness ${pre.details.rejectedBy} 拒绝 ${toolCall.name}: ${pre.reason || '安全校验失败'}`, tool: toolCall.name });
|
|
1245
|
+
onStream({ type: 'step_error', content: `Harness 拒绝 ${toolCall.name}`, tool: toolCall.name, error: pre.reason || '安全校验失败' });
|
|
1311
1246
|
}
|
|
1312
|
-
console.warn(`[PiAgent] Harness
|
|
1313
|
-
|
|
1314
|
-
// 这样 LLM 下轮看 output 不会拿到秘密, 但 success 标志让它知道 "工具执行了"
|
|
1315
|
-
result = {
|
|
1316
|
-
...result,
|
|
1317
|
-
output: `[harness output gate: output 含敏感内容, 已屏蔽. 原因: ${post.reason || 'unknown'}]`,
|
|
1318
|
-
_harnessDenied: true,
|
|
1319
|
-
};
|
|
1247
|
+
console.warn(`[PiAgent] Harness denied ${toolCall.name} (${pre.details.rejectedBy}): ${pre.reason}`);
|
|
1248
|
+
continue;
|
|
1320
1249
|
}
|
|
1321
1250
|
}
|
|
1322
1251
|
catch (err) {
|
|
1323
|
-
console.warn('[PiAgent] reactHarness.
|
|
1252
|
+
console.warn('[PiAgent] reactHarness.preToolCall failed (non-fatal, allowing):', err);
|
|
1324
1253
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
const outputPreview = result.output.substring(0, 200);
|
|
1333
|
-
onStream({ type: 'tool', content: `📤 结果: ${outputPreview}${result.output.length > 200 ? '...' : ''}`, tool: toolCall.name });
|
|
1334
|
-
}
|
|
1335
|
-
// 2026-06-15: step-timeline 状态机 — 关闭当前节点 (成功)
|
|
1336
|
-
onStream({
|
|
1337
|
-
type: 'step_done',
|
|
1338
|
-
content: `${toolCall.name} 执行成功`,
|
|
1339
|
-
tool: toolCall.name,
|
|
1340
|
-
success: true,
|
|
1341
|
-
output: result.output,
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
1344
|
-
else {
|
|
1345
|
-
onStream({ type: 'error', content: `❌ ${toolCall.name} 执行失败: ${result.error}`, tool: toolCall.name });
|
|
1346
|
-
// 2026-06-15: step-timeline 状态机 — 关闭当前节点 (失败)
|
|
1347
|
-
onStream({
|
|
1348
|
-
type: 'step_error',
|
|
1349
|
-
content: `${toolCall.name} 执行失败`,
|
|
1350
|
-
tool: toolCall.name,
|
|
1351
|
-
error: result.error,
|
|
1352
|
-
});
|
|
1254
|
+
try {
|
|
1255
|
+
const toolStart = Date.now();
|
|
1256
|
+
let result = await tool.execute(toolCall.args);
|
|
1257
|
+
const toolDurationMs = Date.now() - toolStart;
|
|
1258
|
+
console.log(`[PiAgent] 工具 ${toolCall.name} 执行完成: success=${result.success} (${toolDurationMs}ms)`);
|
|
1259
|
+
try {
|
|
1260
|
+
await onPostToolUse({ tool: toolCall.name, args: toolCall.args || {}, result: { success: result.success, output: result.output?.substring(0, 500), error: result.error }, durationMs: toolDurationMs });
|
|
1353
1261
|
}
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
consecutiveErrors = 0; // 重置连续错误计数
|
|
1357
|
-
// 2026-06-19: 记录成功结果, 用于 LLM 失败退出时汇总给用户
|
|
1358
|
-
if (result.output) {
|
|
1359
|
-
this.successfulToolResults.push({
|
|
1360
|
-
tool: toolCall.name,
|
|
1361
|
-
outputPreview: result.output.substring(0, 200) + (result.output.length > 200 ? '...' : '')
|
|
1362
|
-
});
|
|
1262
|
+
catch (postErr) {
|
|
1263
|
+
console.warn('[PiAgent] onPostToolUse failed (non-fatal):', postErr);
|
|
1363
1264
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1265
|
+
const routeHint = this.reactHarness.getLastRouteHint();
|
|
1266
|
+
if (routeHint && routeHint.systemAddition) {
|
|
1267
|
+
this.messageHistory.push({ role: 'system', content: `[Harness Router Hint: ${routeHint.reason}]\n${routeHint.systemAddition}` });
|
|
1268
|
+
this.reactHarness.clearRouteHint();
|
|
1366
1269
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1270
|
+
try {
|
|
1271
|
+
const post = await this.reactHarness.postToolCall(toolCall.name, String(result.output || ''), this.currentChannelId || undefined);
|
|
1272
|
+
if (!post.allowed) {
|
|
1273
|
+
if (onStream) {
|
|
1274
|
+
onStream({ type: 'error', content: `🛡️ Harness output 拒绝 ${toolCall.name}: ${post.reason || '输出含敏感信息'}`, tool: toolCall.name });
|
|
1275
|
+
}
|
|
1276
|
+
console.warn(`[PiAgent] Harness output denied ${toolCall.name}: ${post.reason}`);
|
|
1277
|
+
result = { ...result, output: `[harness output gate: 输出含敏感内容, 已屏蔽. 原因: ${post.reason || 'unknown'}]`, _harnessDenied: true };
|
|
1278
|
+
}
|
|
1372
1279
|
}
|
|
1373
|
-
|
|
1374
|
-
console.
|
|
1280
|
+
catch (err) {
|
|
1281
|
+
console.warn('[PiAgent] reactHarness.postToolCall failed (non-fatal, allowing):', err);
|
|
1375
1282
|
}
|
|
1376
|
-
|
|
1283
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(result), toolResult: result, toolCallId: toolCall.id || `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` });
|
|
1284
|
+
this.logToHarness(toolCall.name, toolCall.args, result);
|
|
1377
1285
|
if (onStream) {
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
}
|
|
1390
|
-
else {
|
|
1391
|
-
lastFailedTool = toolCall.name;
|
|
1392
|
-
lastFailedToolCount = 1;
|
|
1286
|
+
if (result.success) {
|
|
1287
|
+
onStream({ type: 'status', content: `✅ ${toolCall.name} 执行成功`, tool: toolCall.name });
|
|
1288
|
+
if (result.output) {
|
|
1289
|
+
onStream({ type: 'tool', content: `📤 结果: ${result.output.substring(0, 200)}${result.output.length > 200 ? '...' : ''}`, tool: toolCall.name });
|
|
1290
|
+
}
|
|
1291
|
+
onStream({ type: 'step_done', content: `${toolCall.name} 执行成功`, tool: toolCall.name, success: true, output: result.output });
|
|
1292
|
+
}
|
|
1293
|
+
else {
|
|
1294
|
+
onStream({ type: 'error', content: `❌ ${toolCall.name} 执行失败: ${result.error}`, tool: toolCall.name });
|
|
1295
|
+
onStream({ type: 'step_error', content: `${toolCall.name} 执行失败`, tool: toolCall.name, error: result.error });
|
|
1296
|
+
}
|
|
1393
1297
|
}
|
|
1394
|
-
|
|
1395
|
-
// 同一工具连续失败达到上限, 不再重试, 强制 LLM 给出最终答案
|
|
1396
|
-
if (lastFailedToolCount >= MAX_SAME_TOOL_FAILURES) {
|
|
1397
|
-
console.log(`[PiAgent] 工具 ${toolCall.name} 连续 ${MAX_SAME_TOOL_FAILURES} 次失败, 放弃并要求直接回答`);
|
|
1398
|
-
this.messageHistory.push({
|
|
1399
|
-
role: 'system',
|
|
1400
|
-
content: `[注意] 工具 ${toolCall.name} 在这个上下文中不可用 (连续 ${MAX_SAME_TOOL_FAILURES} 次失败: ${result.error}). 请不要再次调用它, 直接用你已知的信息回答用户, 并在回答开头标记 <final gen>.`
|
|
1401
|
-
});
|
|
1402
|
-
lastFailedTool = '';
|
|
1403
|
-
lastFailedToolCount = 0;
|
|
1298
|
+
if (result.success) {
|
|
1404
1299
|
consecutiveErrors = 0;
|
|
1405
|
-
|
|
1300
|
+
if (result.output) {
|
|
1301
|
+
this.successfulToolResults.push({ tool: toolCall.name, outputPreview: result.output.substring(0, 200) + (result.output.length > 200 ? '...' : '') });
|
|
1302
|
+
}
|
|
1303
|
+
else {
|
|
1304
|
+
this.successfulToolResults.push({ tool: toolCall.name, outputPreview: '(无输出)' });
|
|
1305
|
+
}
|
|
1306
|
+
lastQualityScore = this.estimateToolResultQuality(result);
|
|
1307
|
+
if (lastQualityScore < this.QUALITY_THRESHOLD && refineAttempts < this.MAX_REFINE_ATTEMPTS) {
|
|
1308
|
+
refineAttempts++;
|
|
1309
|
+
}
|
|
1310
|
+
if (onStream) {
|
|
1311
|
+
onStream({ type: 'status', content: `🔄 工具执行完成,继续循环...`, tool: 'loop' });
|
|
1312
|
+
}
|
|
1406
1313
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1314
|
+
else {
|
|
1315
|
+
consecutiveErrors++;
|
|
1316
|
+
totalErrors++;
|
|
1317
|
+
if (toolCall.name === lastFailedTool) {
|
|
1318
|
+
lastFailedToolCount++;
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
lastFailedTool = toolCall.name;
|
|
1322
|
+
lastFailedToolCount = 1;
|
|
1323
|
+
}
|
|
1324
|
+
console.warn(`[PiAgent] 工具 ${toolCall.name} 执行失败 (${lastFailedToolCount}/${MAX_SAME_TOOL_FAILURES}, 累计 ${totalErrors}/${this.MAX_TOTAL_ERRORS}): ${result.error}`);
|
|
1325
|
+
// 2026-07-28: 注入 Observation + Reflection 替代旧 hardcode 提示
|
|
1326
|
+
const obs = buildObservation(toolCall.name, toolCall.args, { success: false, error: result.error });
|
|
1327
|
+
const ref = buildReflection(toolCall.name, result.error, totalErrors, lastFailedToolCount);
|
|
1328
|
+
this.messageHistory.push({ role: 'system', content: formatObservationWithReflection(obs, ref) });
|
|
1329
|
+
if (onStream)
|
|
1330
|
+
onStream({ type: 'status', content: `💡 Reflection: ${obs.summary} → ${ref[0]?.action || '放弃'}`, tool: 'system' });
|
|
1331
|
+
if (lastFailedToolCount >= MAX_SAME_TOOL_FAILURES) {
|
|
1332
|
+
this.messageHistory.push({ role: 'system', content: `[注意] 工具 ${toolCall.name} 在这个上下文中不可用 (连续 ${MAX_SAME_TOOL_FAILURES} 次失败: ${result.error}). 请不要再次调用它, 直接用你已知的信息回答用户, 并在回答开头标记 <final gen>.` });
|
|
1333
|
+
lastFailedTool = '';
|
|
1334
|
+
lastFailedToolCount = 0;
|
|
1335
|
+
consecutiveErrors = 0;
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
1339
|
+
this.messageHistory.push({ role: 'system', content: `[注意] 前面的工具调用连续失败。请尝试其他工具或换一种方式完成用户请求, 或用 <final gen> 给出最终回答.` });
|
|
1340
|
+
consecutiveErrors = 0;
|
|
1341
|
+
}
|
|
1415
1342
|
}
|
|
1416
1343
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1344
|
+
catch (execError) {
|
|
1345
|
+
consecutiveErrors++;
|
|
1346
|
+
totalErrors++;
|
|
1347
|
+
const errorResult = { success: false, error: String(execError) };
|
|
1348
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(errorResult), toolResult: errorResult });
|
|
1349
|
+
this.logToHarness(toolCall.name, toolCall.args, errorResult);
|
|
1350
|
+
const obs = buildObservation(toolCall.name, toolCall.args, errorResult);
|
|
1351
|
+
const ref = buildReflection(toolCall.name, errorResult.error, totalErrors, lastFailedToolCount);
|
|
1352
|
+
this.messageHistory.push({ role: 'system', content: formatObservationWithReflection(obs, ref) });
|
|
1353
|
+
if (onStream)
|
|
1354
|
+
onStream({ type: 'status', content: `💡 Reflection: ${obs.summary}`, tool: 'system' });
|
|
1355
|
+
console.error(`[PiAgent] 工具执行异常 (累计 ${totalErrors}/${this.MAX_TOTAL_ERRORS}): ${execError}`);
|
|
1356
|
+
}
|
|
1357
|
+
} // end for (ti)
|
|
1358
|
+
// 所有工具执行完毕后, continue while 循环, 让 LLM 看到结果
|
|
1359
|
+
continue;
|
|
1427
1360
|
}
|
|
1428
1361
|
else {
|
|
1429
1362
|
// LLM 返回的不是 tool call 格式
|