@bolloon/bolloon-agent 0.6.4 → 0.6.6
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/chain/chain-settlement.js +0 -1
- package/dist/agents/chat-segmenter.js +4 -16
- package/dist/agents/contacts/human-directory.js +155 -0
- package/dist/agents/contacts/tools.js +79 -0
- package/dist/agents/experience-review.js +0 -3
- package/dist/agents/goal-flywheel/goal-change.js +3 -4
- package/dist/agents/goal-resume.js +0 -4
- package/dist/agents/intent-classifier.js +1 -1
- package/dist/agents/iteration-budget.js +12 -0
- package/dist/agents/knowledge-organizer.js +0 -2
- package/dist/agents/lesson-to-skill.js +0 -1
- package/dist/agents/loop-review.js +2 -2
- package/dist/agents/memory-store.js +359 -0
- package/dist/agents/opportunity-inbox.js +147 -0
- package/dist/agents/opportunity-match.js +48 -7
- package/dist/agents/opportunity-web-search.js +222 -0
- package/dist/agents/p2p-chat-tools.js +1 -2
- package/dist/agents/parse-tool-call.js +9 -22
- package/dist/agents/pi-harness.js +1 -2
- package/dist/agents/pi-sdk-tools.js +180 -5
- package/dist/agents/pi-sdk.js +143 -9
- package/dist/agents/process-runner.js +28 -9
- package/dist/agents/react-loop.js +9 -17
- package/dist/agents/runner-resolver.js +4 -3
- package/dist/agents/skill-loader.js +22 -15
- package/dist/agents/skills-manager.js +1 -25
- package/dist/agents/workflow-pivot-loop.js +152 -61
- package/dist/agents/world-watcher.js +30 -1
- package/dist/bootstrap/context-collector.js +2 -4
- package/dist/bootstrap/context-hierarchy.js +13 -1
- package/dist/cli/ink-app.js +44 -17
- package/dist/cli/intent-command.js +22 -12
- package/dist/cli/loading-tui.js +8 -2
- package/dist/cli/log-gate.js +40 -8
- package/dist/cli/mention-data.js +1 -0
- package/dist/cli/reply-hygiene.js +22 -0
- package/dist/cli/trace-line.js +61 -2
- package/dist/electron-build/agents/agent-gateway.js.map +1 -1
- package/dist/electron-build/agents/chain/chain-indexer.js.map +1 -1
- package/dist/electron-build/agents/chain/chain-settlement.js +0 -1
- package/dist/electron-build/agents/chain/chain-settlement.js.map +1 -1
- package/dist/electron-build/agents/contacts/chain.js.map +1 -1
- package/dist/electron-build/agents/contacts/human-directory.js +204 -0
- package/dist/electron-build/agents/contacts/human-directory.js.map +1 -0
- package/dist/electron-build/agents/contacts/policy.js.map +1 -1
- package/dist/electron-build/agents/contacts/tools.js +112 -0
- package/dist/electron-build/agents/contacts/tools.js.map +1 -1
- package/dist/electron-build/agents/experience-review.js +0 -3
- package/dist/electron-build/agents/experience-review.js.map +1 -1
- package/dist/electron-build/agents/external-events.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel/goal-change.js +3 -4
- package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -1
- package/dist/electron-build/agents/goal-resume.js +0 -4
- package/dist/electron-build/agents/goal-resume.js.map +1 -1
- package/dist/electron-build/agents/intent-classifier.js +1 -1
- package/dist/electron-build/agents/lesson-to-skill.js +0 -1
- package/dist/electron-build/agents/lesson-to-skill.js.map +1 -1
- package/dist/electron-build/agents/loop-review.js +2 -2
- package/dist/electron-build/agents/loop-review.js.map +1 -1
- package/dist/electron-build/agents/memory-recall.js.map +1 -1
- package/dist/electron-build/agents/memory-store.js +404 -0
- package/dist/electron-build/agents/memory-store.js.map +1 -0
- package/dist/electron-build/agents/parse-tool-call.js +9 -22
- package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
- package/dist/electron-build/agents/pi-harness.js +1 -2
- package/dist/electron-build/agents/pi-harness.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +180 -5
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +143 -9
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/pre-tool-validator.js.map +1 -1
- package/dist/electron-build/agents/process-runner.js +28 -9
- package/dist/electron-build/agents/process-runner.js.map +1 -1
- package/dist/electron-build/agents/runner-resolver.js +4 -3
- package/dist/electron-build/agents/runner-resolver.js.map +1 -1
- package/dist/electron-build/agents/skill-loader.js +22 -16
- package/dist/electron-build/agents/skill-loader.js.map +1 -1
- package/dist/electron-build/agents/skill-supervisor-link.js.map +1 -1
- package/dist/electron-build/agents/skills-manager.js +0 -24
- package/dist/electron-build/agents/skills-manager.js.map +1 -1
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/workflow-engine.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +155 -62
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/agents/x402/goal-run-bridge.js.map +1 -1
- package/dist/electron-build/bollharness-integration/gate-state-machine.js.map +1 -1
- package/dist/electron-build/bootstrap/context-collector.js +2 -4
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
- package/dist/electron-build/bootstrap/context-hierarchy.js +13 -1
- package/dist/electron-build/bootstrap/context-hierarchy.js.map +1 -1
- package/dist/electron-build/electron-preload.js.map +1 -1
- package/dist/electron-build/external-engines/delegate.js.map +1 -1
- package/dist/electron-build/kernel/channel-actor.js +31 -2
- package/dist/electron-build/kernel/channel-actor.js.map +1 -1
- package/dist/electron-build/kernel/model-runtime.js +8 -1
- package/dist/electron-build/kernel/model-runtime.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +20 -0
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/embedding-adapter.js +75 -0
- package/dist/electron-build/llm/embedding-adapter.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +96 -35
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/network/agent-network.js.map +1 -1
- package/dist/electron-build/orbitdb/memory-sync.js +76 -0
- package/dist/electron-build/orbitdb/memory-sync.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/adaptive-scan.js.map +1 -1
- package/dist/electron-build/security/tool-gate.js +2 -0
- package/dist/electron-build/security/tool-gate.js.map +1 -1
- package/dist/electron-build/setup/setup-store.js +34 -6
- package/dist/electron-build/setup/setup-store.js.map +1 -1
- package/dist/electron-build/social/heartbeat.js +18 -5
- package/dist/electron-build/social/heartbeat.js.map +1 -1
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/index.js +250 -12
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/client.js +150 -113
- package/dist/ios/icons/icon-1400x1400.png +0 -0
- package/dist/ios/icons/icon-1400x1400.webp +0 -0
- package/dist/ios/index.html +106 -107
- package/dist/ios/mobile-agent.js +346 -45
- package/dist/ios/mobile-core.js +7303 -4590
- package/dist/ios/mobile-harness.js +1004 -0
- package/dist/ios/mobile-orbitdb.js +194 -0
- package/dist/ios/mobile-persona.js +80 -0
- package/dist/ios/mobile-privacy.js +2 -3
- package/dist/ios/mobile.css +286 -21
- package/dist/ios/mobile.html +106 -107
- package/dist/ios/mobile.js +300 -132
- package/dist/ios/routes-world.js +58 -1
- package/dist/ios/server.js +3 -1
- package/dist/ios/style.css +49 -2
- package/dist/ios/sw.js +1 -1
- package/dist/ios/ui/message-renderer.js +4 -18
- package/dist/ios/ui/step-timeline.js +14 -1
- package/dist/kernel/channel-actor.js +30 -2
- package/dist/kernel/gate-scan.js +1 -1
- package/dist/kernel/index.js +33 -0
- package/dist/kernel/model-runtime.js +8 -1
- package/dist/kernel/plan.js +3 -3
- package/dist/kernel/roster.js +3 -3
- package/dist/llm/config-store.js +20 -0
- package/dist/llm/embedding-adapter.js +69 -0
- package/dist/llm/pi-ai.js +94 -34
- package/dist/orbitdb/memory-sync.js +70 -0
- package/dist/pi-ecosystem-judgment/adaptive-scan.js +1 -1
- package/dist/security/tool-gate.js +2 -0
- package/dist/setup/setup-store.js +34 -6
- package/dist/social/heartbeat.js +18 -5
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +150 -113
- package/dist/web/icons/icon-1400x1400.png +0 -0
- package/dist/web/icons/icon-1400x1400.webp +0 -0
- package/dist/web/index.html +52 -59
- package/dist/web/mobile-agent.js +351 -45
- package/dist/web/mobile-contacts.js +0 -4
- package/dist/web/mobile-core.js +7430 -4590
- package/dist/web/mobile-harness.js +1004 -0
- package/dist/web/mobile-orbitdb.js +194 -0
- package/dist/web/mobile-persona.js +80 -0
- package/dist/web/mobile-privacy.js +2 -3
- package/dist/web/mobile.css +286 -21
- package/dist/web/mobile.html +121 -107
- package/dist/web/mobile.js +409 -136
- package/dist/web/routes-contacts.js +94 -1
- package/dist/web/routes-world.js +58 -1
- package/dist/web/server.js +4 -2
- package/dist/web/style.css +49 -2
- package/dist/web/sw.js +1 -1
- package/dist/web/ui/message-renderer.js +4 -18
- package/dist/web/ui/step-timeline.js +14 -1
- package/package.json +3 -2
- package/scripts/build-web.ts +16 -1
package/dist/ios/server.js
CHANGED
|
@@ -920,6 +920,7 @@ async function handleV3P2PMessage(parsed, conn, comm) {
|
|
|
920
920
|
output: event.output,
|
|
921
921
|
error: event.error,
|
|
922
922
|
args: event.args,
|
|
923
|
+
durationMs: event.durationMs, // 2026-10-12: 命令执行时间读秒
|
|
923
924
|
fromPublicKey: v3P2PRef?.getPublicKey() || ''
|
|
924
925
|
}
|
|
925
926
|
});
|
|
@@ -4941,7 +4942,7 @@ fetchState();
|
|
|
4941
4942
|
// (与邮箱串行一致), web 不再自己持 abortController。取到 agent 后立刻开户 (见下面 getAgentForChannel 之后)。
|
|
4942
4943
|
let abortSignal;
|
|
4943
4944
|
// 2026-07-04: pivot loop safety net — 防止 LLM hang (minimax M3
|
|
4944
|
-
// 偶尔反复 think
|
|
4945
|
+
// 偶尔反复 think 不输出结果, pivot 连 5 次无进展时会 hang 在
|
|
4945
4946
|
// quality 评估). setTimeout 让 LLM 客户端收 signal 主动 break.
|
|
4946
4947
|
// 2026-07-06: 30s 太短 — minimax M3 单次 LLM 约 10s, pivot 默认 moderate profile 30 iter
|
|
4947
4948
|
// 留 ~300s. 之前 30s 自动 abort 时, 一次"你好"调用就被截断, 用户看到 ❌ 循环异常 + 空内容.
|
|
@@ -6901,6 +6902,7 @@ fetchState();
|
|
|
6901
6902
|
output: event.output,
|
|
6902
6903
|
error: event.error,
|
|
6903
6904
|
args: event.args,
|
|
6905
|
+
durationMs: event.durationMs, // 2026-10-12: 命令执行时间读秒
|
|
6904
6906
|
}, channelId);
|
|
6905
6907
|
}
|
|
6906
6908
|
else if (event.type === 'error') {
|
package/dist/ios/style.css
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
box-sizing: border-box;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
/* 2026-10-05 (leo): hidden 属性必须真隐藏 —— 之前 .messages{display:flex} 覆盖了
|
|
8
|
+
UA 的 [hidden]{display:none}, 导致世界栏下面一直显示聊天记录 (污染)。 */
|
|
9
|
+
[hidden] {
|
|
10
|
+
display: none !important;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
:root {
|
|
8
14
|
--bg: #1a1a18;
|
|
9
15
|
--bg-sidebar: #222220;
|
|
@@ -120,13 +126,20 @@ body {
|
|
|
120
126
|
flex-shrink: 0;
|
|
121
127
|
transition: var(--transition);
|
|
122
128
|
transform: translateX(0);
|
|
123
|
-
|
|
129
|
+
/* 2026-10-06: 改为 fixed 覆盖 — sidebar 收起时绝对定位脱离流, relative 会被挤成 0 宽,
|
|
130
|
+
恢复条必须固定在屏幕左侧才能被看到/点到 */
|
|
131
|
+
position: fixed;
|
|
132
|
+
left: 0;
|
|
133
|
+
top: 0;
|
|
134
|
+
height: 100%;
|
|
124
135
|
z-index: 99;
|
|
125
136
|
}
|
|
126
137
|
|
|
127
138
|
.sidebar-collapsed.hidden {
|
|
128
139
|
transform: translateX(calc(-1 * var(--sidebar-collapsed)));
|
|
129
|
-
position:
|
|
140
|
+
position: fixed;
|
|
141
|
+
left: 0;
|
|
142
|
+
top: 0;
|
|
130
143
|
height: 100%;
|
|
131
144
|
}
|
|
132
145
|
|
|
@@ -1858,6 +1871,40 @@ body {
|
|
|
1858
1871
|
font-weight: normal;
|
|
1859
1872
|
color: var(--text-muted);
|
|
1860
1873
|
}
|
|
1874
|
+
|
|
1875
|
+
/* 2026-10-05 (leo): 世界/聊天切换放进原导航栏 (header) 中间 — 与钱包/配置同栏 */
|
|
1876
|
+
.header-tabs {
|
|
1877
|
+
display: flex;
|
|
1878
|
+
gap: 2px;
|
|
1879
|
+
margin-left: 18px;
|
|
1880
|
+
padding: 3px;
|
|
1881
|
+
background: var(--bg-hover);
|
|
1882
|
+
border-radius: 8px;
|
|
1883
|
+
flex-shrink: 0;
|
|
1884
|
+
}
|
|
1885
|
+
.header-tab {
|
|
1886
|
+
padding: 7px 22px;
|
|
1887
|
+
background: transparent;
|
|
1888
|
+
border: none;
|
|
1889
|
+
border-radius: 6px;
|
|
1890
|
+
color: var(--text-secondary);
|
|
1891
|
+
font-size: 13px;
|
|
1892
|
+
font-weight: 600;
|
|
1893
|
+
cursor: pointer;
|
|
1894
|
+
transition: var(--transition);
|
|
1895
|
+
display: flex;
|
|
1896
|
+
align-items: center;
|
|
1897
|
+
gap: 5px;
|
|
1898
|
+
white-space: nowrap;
|
|
1899
|
+
}
|
|
1900
|
+
.header-tab:hover {
|
|
1901
|
+
color: var(--text-primary);
|
|
1902
|
+
}
|
|
1903
|
+
.header-tab.active {
|
|
1904
|
+
background: var(--bg-main);
|
|
1905
|
+
color: var(--accent-hover);
|
|
1906
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
|
1907
|
+
}
|
|
1861
1908
|
.judgment-status-bar {
|
|
1862
1909
|
display: flex;
|
|
1863
1910
|
gap: 6px;
|
package/dist/ios/sw.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 现在: 代码类资源 (html/js/css/json) 一律 **network-first** (拿不到网才回退缓存),
|
|
7
7
|
* 缓存名带版本号, activate 时清掉所有旧缓存; 图标等静态资源仍 cache-first。
|
|
8
8
|
*/
|
|
9
|
-
const CACHE = 'bolloon-mobile-v0.6.
|
|
9
|
+
const CACHE = 'bolloon-mobile-v0.6.5';
|
|
10
10
|
const SHELL = [
|
|
11
11
|
'./mobile.html',
|
|
12
12
|
'./mobile.css',
|
|
@@ -261,21 +261,10 @@ function segmentChatReply(reply, ctx) {
|
|
|
261
261
|
if (remaining.trim()) {
|
|
262
262
|
remaining = filterFillerText(remaining);
|
|
263
263
|
}
|
|
264
|
-
const
|
|
265
|
-
let beforeFinalText = remaining;
|
|
266
|
-
if (finalIdx >= 0) {
|
|
267
|
-
beforeFinalText = remaining.substring(0, finalIdx);
|
|
268
|
-
}
|
|
269
|
-
const textContent = beforeFinalText.trim();
|
|
264
|
+
const textContent = remaining.trim();
|
|
270
265
|
if (textContent) {
|
|
271
266
|
segments.push({ type: "text", content: textContent });
|
|
272
267
|
}
|
|
273
|
-
if (finalIdx >= 0) {
|
|
274
|
-
const afterFinal = remaining.substring(finalIdx + "<final gen>".length).trim();
|
|
275
|
-
if (afterFinal) {
|
|
276
|
-
segments.push({ type: "final", content: afterFinal });
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
268
|
return segments;
|
|
280
269
|
}
|
|
281
270
|
function extractAndPush(type, text, out, re) {
|
|
@@ -530,11 +519,7 @@ function addMessage(content, type, save = true, container, usedJudgmentIds = [],
|
|
|
530
519
|
div.className = `message message-${type}`;
|
|
531
520
|
let cleanContent = content;
|
|
532
521
|
if (type === "ai") {
|
|
533
|
-
cleanContent = cleanContent.replace(
|
|
534
|
-
const finalGenIdx = cleanContent.indexOf("<final gen>");
|
|
535
|
-
if (finalGenIdx >= 0) {
|
|
536
|
-
cleanContent = cleanContent.substring(0, finalGenIdx).trim();
|
|
537
|
-
}
|
|
522
|
+
cleanContent = cleanContent.replace(/ thinking[\s\S]*?<\/think>/g, "");
|
|
538
523
|
}
|
|
539
524
|
const knownToolNames = ctx && ctx.knownToolNames || /* @__PURE__ */ new Set();
|
|
540
525
|
const segments = segmentChatReply(cleanContent, { knownToolNames });
|
|
@@ -858,7 +843,8 @@ function handleStepEvent(data, ctx = { messagesEl: null, messagesContainers: /*
|
|
|
858
843
|
args: data.args,
|
|
859
844
|
success: data.success,
|
|
860
845
|
output: data.output,
|
|
861
|
-
error: data.error
|
|
846
|
+
error: data.error,
|
|
847
|
+
durationMs: data.durationMs
|
|
862
848
|
});
|
|
863
849
|
}
|
|
864
850
|
function flushStepEventBuffer(channelId, ctx) {
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
// src/web/ui/step-timeline.ts
|
|
2
2
|
var MAX_VISIBLE_NODES = 20;
|
|
3
3
|
var STORAGE_PREFIX = "bolloon.stepTimeline.expanded.";
|
|
4
|
+
function formatDuration(ms) {
|
|
5
|
+
if (!Number.isFinite(ms) || ms < 0) return "(耗时未知)";
|
|
6
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
7
|
+
const s = ms / 1e3;
|
|
8
|
+
if (s < 60) return `${s.toFixed(1)}s`;
|
|
9
|
+
const m = Math.floor(s / 60);
|
|
10
|
+
const rem = Math.round(s % 60);
|
|
11
|
+
return `${m}m${rem}s`;
|
|
12
|
+
}
|
|
4
13
|
var stateMap = /* @__PURE__ */ new WeakMap();
|
|
5
14
|
function readExpanded(channelId) {
|
|
6
15
|
if (!channelId) return false;
|
|
@@ -121,7 +130,10 @@ function render(timelineEl) {
|
|
|
121
130
|
}
|
|
122
131
|
node.setAttribute("data-status", step.status);
|
|
123
132
|
const label = node.querySelector(".step-timeline-label");
|
|
124
|
-
if (label)
|
|
133
|
+
if (label) {
|
|
134
|
+
const dur = step.durationMs !== void 0 && step.status !== "active" ? ` · ${formatDuration(step.durationMs)}` : "";
|
|
135
|
+
label.textContent = step.name + dur;
|
|
136
|
+
}
|
|
125
137
|
const argsEl = node.querySelector(".step-timeline-args");
|
|
126
138
|
if (argsEl) {
|
|
127
139
|
const argStr = step.args && Object.keys(step.args).length > 0 ? JSON.stringify(step.args).slice(0, 60) : "";
|
|
@@ -265,6 +277,7 @@ function pushStepToTimeline(timelineEl, eventType, data) {
|
|
|
265
277
|
steps[targetIdx].status = data.success === false ? "error" : "done";
|
|
266
278
|
if (data.output) steps[targetIdx].output = data.output;
|
|
267
279
|
if (data.error) steps[targetIdx].error = data.error;
|
|
280
|
+
if (data.durationMs !== void 0) steps[targetIdx].durationMs = data.durationMs;
|
|
268
281
|
} else {
|
|
269
282
|
steps[targetIdx].status = "error";
|
|
270
283
|
if (data.error) steps[targetIdx].error = data.error;
|
|
@@ -23,6 +23,17 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
23
23
|
* ⇒ 用 ALS 记住"我在谁的上下文里", 同 actor 重入时**直跑**, 不同 actor 照常排队。
|
|
24
24
|
*/
|
|
25
25
|
const actorCtx = new AsyncLocalStorage();
|
|
26
|
+
/**
|
|
27
|
+
* 2026-10-13: 当前 mailbox 任务被注入的**取消信号**。
|
|
28
|
+
* `deliverThroughActor` 进入执行时调 `actor.beginCancellation()` 并把 signal 注入本 ALS,
|
|
29
|
+
* 业务侧 (CLI/Web) 在 run() 里经 `currentRequestSignal()` 取它传给模型/循环调用 ⇒
|
|
30
|
+
* Ctrl+C / 终止按钮走 `actor.abort()` / `abortActorsOfChannel()` 能真正打断回合。
|
|
31
|
+
* 零业务依赖: 只用 node:async_hooks, 不 import 任何业务模块。
|
|
32
|
+
*/
|
|
33
|
+
const requestSignalCtx = new AsyncLocalStorage();
|
|
34
|
+
export function currentRequestSignal() {
|
|
35
|
+
return requestSignalCtx.getStore() ?? undefined;
|
|
36
|
+
}
|
|
26
37
|
/** 当前是否正跑在某个 Actor 的 mailbox 任务内 (诊断用) */
|
|
27
38
|
export function currentActorContext() {
|
|
28
39
|
return actorCtx.getStore();
|
|
@@ -245,6 +256,18 @@ export function resetActors() {
|
|
|
245
256
|
channelQueues.clear();
|
|
246
257
|
privateActorsCreated = 0;
|
|
247
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* 2026-10-13: 在 mailbox 任务体内开新的取消位并把 signal 注入 ALS (见 deliverThroughActor)。
|
|
261
|
+
* 收尾时**只有本任务仍是取消位主人**才清掉 —— 嵌套/并行路径下防 abort 打到下一个任务。
|
|
262
|
+
*/
|
|
263
|
+
function runWithCancellation(actor, run) {
|
|
264
|
+
const ac = new AbortController();
|
|
265
|
+
actor.state.cancellation = ac;
|
|
266
|
+
return requestSignalCtx.run(ac.signal, () => Promise.resolve(run()).finally(() => {
|
|
267
|
+
if (actor.state.cancellation === ac)
|
|
268
|
+
actor.state.cancellation = null;
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
248
271
|
export async function deliverThroughActor(
|
|
249
272
|
// 形参故意放宽成 `unknown`: 调用点的 receiver 类型五花八门 (AgentSession / 结构子集 / any),
|
|
250
273
|
// 写成 `{ actor?: ChannelActor }` 会触发 TS 的弱类型检查 ("no properties in common") 而误报。
|
|
@@ -264,10 +287,15 @@ holder, run, opts = {}) {
|
|
|
264
287
|
const q = getChannelQueue(ch);
|
|
265
288
|
if (channelCtx.getStore() === q)
|
|
266
289
|
return run();
|
|
267
|
-
return q.submit(() => channelCtx.run(q, () => actor.submit(() => actorCtx.run(actor, () => run
|
|
290
|
+
return q.submit(() => channelCtx.run(q, () => actor.submit(() => actorCtx.run(actor, () => runWithCancellation(actor, run)))));
|
|
268
291
|
}
|
|
269
292
|
}
|
|
270
|
-
|
|
293
|
+
// 2026-10-13 (leo: 「Ctrl+C 中断的落点必须是 kernel 的取消通道」):
|
|
294
|
+
// mailbox 任务体内开一个新的取消位 (state.cancellation) 并把 signal 注入 ALS ——
|
|
295
|
+
// 业务侧 run() 经 `currentRequestSignal()` 取到它 ⇒ Ctrl+C abort 能打断回合。
|
|
296
|
+
// **重入保护里直跑的分支不重新开位**: 嵌套的 inner run 复用同一个外层的 signal,
|
|
297
|
+
// 不会被内层 abort 清掉 (abort() 把 state.cancellation 置 null, 而 signal 经 ALS 走闭包引用)。
|
|
298
|
+
return actor.submit(() => actorCtx.run(actor, () => runWithCancellation(actor, run)));
|
|
271
299
|
}
|
|
272
300
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
273
301
|
// channel 级串行锁 (K5 步骤⑤) —— **能力先落地, 默认不启用**
|
package/dist/kernel/gate-scan.js
CHANGED
|
@@ -108,7 +108,7 @@ export function scanProhibition(files, p) {
|
|
|
108
108
|
const out = [];
|
|
109
109
|
const targets = p.targets ?? [];
|
|
110
110
|
const calls = p.writeCalls ?? [];
|
|
111
|
-
const callRe = calls.length ? new RegExp(
|
|
111
|
+
const callRe = calls.length ? new RegExp(`(?<![\\w.])(?<!async\\s)(${calls.join('|')})\\s*\\(`) : null;
|
|
112
112
|
for (const f of files) {
|
|
113
113
|
if (p.mode === 'import-edge') {
|
|
114
114
|
for (const { line, spec } of importSpecifiers(f.text)) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bolloon/bolloon-kernel — Bolloon Native Macro-Kernel SDK 出口
|
|
3
|
+
*
|
|
4
|
+
* 进程内 Agent 内核机制层(统一调度 · 共享状态受控 · 端口注入 · 零业务依赖)。
|
|
5
|
+
*
|
|
6
|
+
* 设计页: docs/wiki/bolloon-native-macro-kernel.md
|
|
7
|
+
* 出口纪律:
|
|
8
|
+
* · 只导出「机制」—— 不导出任何业务判定(Kernel 判据: 不判"该不该继续"、不判"算不算完成")。
|
|
9
|
+
* · 只导出「运行时机制」—— gate-scan / roster / plan-* 是 Bolloon 仓库治理件(判据与台账),
|
|
10
|
+
* 面向本仓的门(kernel-boundary / kernel-authority / kernel-gate-scan 等测试)直接吃源码,
|
|
11
|
+
* 不进 SDK 出口。
|
|
12
|
+
* · 依赖面: 除 node: 内置外零依赖 —— 每个模块的 I/O 走端口注入,调用方给什么就用什么。
|
|
13
|
+
*
|
|
14
|
+
* 模块速览:
|
|
15
|
+
* channel-actor K5 ChannelActor + SerialMailbox(同 channel 串行、异 channel 并发、ALS 重入)
|
|
16
|
+
* model-runtime K6 ModelRuntime.acquire(snapshot) 只读租约(连接复用/超时/取消/退避/熔断)
|
|
17
|
+
* control 命令式控制面(pause / resume / abort / recover)
|
|
18
|
+
* run-lifecycle Run 生命周期写口(start-run / record-step / finish-run / save-checkpoint)
|
|
19
|
+
* session-lifecycle 会话规则(持久化映射 / 过滤水合 / 运行种子合并)
|
|
20
|
+
* run-resume 恢复编排(漂移上报 / 核对失败不阻塞 / 单次语义)
|
|
21
|
+
* turn-selfcheck 回合末自检编排(fail-closed: 被拒/门抛错 ⇒ 零执行且可见)
|
|
22
|
+
* code-write-gate 写代码工具判定(哪个写操作产出 TS · 收尾要不要跑 tsc)
|
|
23
|
+
* transport 通信端口(send / broadcast / peers,未注入即拒)
|
|
24
|
+
*/
|
|
25
|
+
export * from './channel-actor.js';
|
|
26
|
+
export * from './model-runtime.js';
|
|
27
|
+
export * from './control.js';
|
|
28
|
+
export * from './run-lifecycle.js';
|
|
29
|
+
export * from './session-lifecycle.js';
|
|
30
|
+
export * from './run-resume.js';
|
|
31
|
+
export * from './turn-selfcheck.js';
|
|
32
|
+
export * from './code-write-gate.js';
|
|
33
|
+
export * from './transport.js';
|
|
@@ -179,7 +179,14 @@ export class ModelRuntime {
|
|
|
179
179
|
call: async (req, opts) => {
|
|
180
180
|
if (released)
|
|
181
181
|
return { ok: false, error: '租约已归还' };
|
|
182
|
-
|
|
182
|
+
// 2026-10-13 (leo: 「多步执行的任务会返回空」根因链的②): 30s 硬超时与上下文长度脱钩 ——
|
|
183
|
+
// 多步任务工具多轮后 prompt 膨胀到 40KB+, 模型生成要更久, 30s 一到就被杀。
|
|
184
|
+
// 预算 = max(配置/默认, 上下文长度自适应): 每 1KB 上下文给 4s, 再加 10s 生成余量。
|
|
185
|
+
// 长上下文不被误杀; 短上下文仍是 30s 底线 (供应商真慢照样暴露, 不掩盖问题)。
|
|
186
|
+
const baseBudget = snapshotReadonly.timeoutMs ?? this.defaultTimeoutMs;
|
|
187
|
+
const ctxChars = String(req?.context ?? '').length + String(req?.systemPrompt ?? '').length;
|
|
188
|
+
const adaptive = Math.ceil(ctxChars / 1024) * 4_000 + 10_000;
|
|
189
|
+
const budget = Math.max(baseBudget, adaptive);
|
|
183
190
|
const external = opts?.signal;
|
|
184
191
|
if (external?.aborted) {
|
|
185
192
|
self.stats.aborts += 1;
|
package/dist/kernel/plan.js
CHANGED
|
@@ -144,6 +144,6 @@ export const DELETION_LEDGER = [
|
|
|
144
144
|
},
|
|
145
145
|
];
|
|
146
146
|
/** 第一批删除候选 = 产品码里 **0 入边引用** 且非入口形态 (机械派生, 不手写) */
|
|
147
|
-
export const DELETION_CANDIDATE_COUNT =
|
|
148
|
-
export const DELETION_CANDIDATE_SHA256 = '
|
|
149
|
-
export const DELETION_CANDIDATE_DIRS = { "agents": 5, "bollharness-integration": 1, "bollharness": 61, "cli": 1, "(根)": 1, "judgeness": 3, "llm": 1, "network": 3, "orbitdb": 2, "scripts": 1, "social": 2, "utils": 3, "web":
|
|
147
|
+
export const DELETION_CANDIDATE_COUNT = 95;
|
|
148
|
+
export const DELETION_CANDIDATE_SHA256 = 'f271d369c830b73833e195c62076a0869dc8c8ef7c9b3cffddd6ce5342f2cb3a';
|
|
149
|
+
export const DELETION_CANDIDATE_DIRS = { "agents": 5, "bollharness-integration": 1, "bollharness": 61, "cli": 1, "(根)": 1, "judgeness": 3, "llm": 1, "network": 3, "orbitdb": 2, "scripts": 1, "social": 2, "utils": 3, "web": 11 };
|
package/dist/kernel/roster.js
CHANGED
|
@@ -100,7 +100,7 @@ export const KERNEL_ALLOWED_IMPORT_PREFIXES = ['kernel/'];
|
|
|
100
100
|
* 判据是**双向相等**: 盘上多一个未登记文件 ⇒ 红; 名册有而盘上没有 ⇒ 红。
|
|
101
101
|
* (先例: SEAM_ROSTER 的「名册外无人越界」)
|
|
102
102
|
*/
|
|
103
|
-
export const KERNEL_FILES = ['kernel/channel-actor.ts', 'kernel/control.ts', 'kernel/run-lifecycle.ts', 'kernel/transport.ts', 'kernel/session-lifecycle.ts', 'kernel/code-write-gate.ts', 'kernel/turn-selfcheck.ts', 'kernel/run-resume.ts', 'kernel/gate-scan.ts', 'kernel/model-runtime.ts', 'kernel/plan-harness.ts', 'kernel/plan-modelruntime.ts', 'kernel/plan-constraint.ts', 'kernel/plan-channel-actor.ts', 'kernel/plan-deletion.ts', 'kernel/plan-runcontext.ts', 'kernel/plan-communication.ts', 'kernel/plan.ts', 'kernel/roster.ts'];
|
|
103
|
+
export const KERNEL_FILES = ['kernel/channel-actor.ts', 'kernel/control.ts', 'kernel/run-lifecycle.ts', 'kernel/transport.ts', 'kernel/session-lifecycle.ts', 'kernel/code-write-gate.ts', 'kernel/turn-selfcheck.ts', 'kernel/run-resume.ts', 'kernel/gate-scan.ts', 'kernel/model-runtime.ts', 'kernel/index.ts', 'kernel/plan-harness.ts', 'kernel/plan-modelruntime.ts', 'kernel/plan-constraint.ts', 'kernel/plan-channel-actor.ts', 'kernel/plan-deletion.ts', 'kernel/plan-runcontext.ts', 'kernel/plan-communication.ts', 'kernel/plan.ts', 'kernel/roster.ts'];
|
|
104
104
|
/** Goal 事实的写入口 (goal-store.ts 的导出中带副作用的那些) */
|
|
105
105
|
export const GOAL_WRITES = [
|
|
106
106
|
'createGoal',
|
|
@@ -230,9 +230,9 @@ export const AUTHORITY_DEBT_FROZEN_AT = 0; // 3 → 0 (K4 用内核控制面还
|
|
|
230
230
|
* 目的只有一个: **不许所有逻辑回流到 kernel.ts**。要加就得显式抬这个数字, 留下痕迹。
|
|
231
231
|
* 数值 = 当前 kernel 目录真实行数, 不留余量。
|
|
232
232
|
*/
|
|
233
|
-
export const KERNEL_LINE_BUDGET =
|
|
233
|
+
export const KERNEL_LINE_BUDGET = 3885; // 2026-10-12: **显式抬档 +33** (kernel SDK 独立 npm 包: 新增 `kernel/index.ts` 出口, 零业务 import 纯 `export *`) // 2026-10-02: 收尾自检规则 `kernel/code-write-gate.ts` 从 agents 侧整条搬入 (+49, 减法: agent 侧少一个模块) // 2026-10-02: 会话生命周期端口 `kernel/session-lifecycle.ts` (278 行) + `gate-scan` 播种锚点跟形状 (+4) —— **显式抬档 +282** (K10 余项; pi-sdk 本批 -18 行); // 2026-10-02 **显式抬档 +22** (K8 收尾: channel-actor 加 `abortActorsOfChannel` + 台账/门同步) // 2026-10-02 **显式抬档 +16** (K10 ④ 后半: transport.ts 加同步读入口 `transportPeersSync` + `peersSync` 端口) // 2026-10-02 **显式抬档 +145** (K10 ④: 新增内核模块 kernel/transport.ts —— 通信入口端口) // 2026-10-02 **显式抬档 +184** (K10 ①: 新增内核模块 kernel/run-lifecycle.ts —— 运行生命周期写口, 与 control.ts 的控制面分开; 预算 == 真实值) // 2026-10-02: +1 (K4-B: gate-scan 复位阈值注释) // 2026-10-02: +5 (K8 正刀: gate-scan 加"收口字段数 3→1"+ queue 反回归) // 2026-10-02: +54 (K8: gate-scan 加 K8 判据) // 2026-10-02: +2 (STAGE_STATUS 拆 K4-A/K4-B + K6/K7 如实修正)
|
|
234
234
|
/** 预算冻结值 (棘轮: 只许减; 想抬预算必须同时改上面那个数字 ⇒ 一次显式动作, diff 里看得见) */
|
|
235
|
-
export const KERNEL_LINE_BUDGET_FROZEN_AT =
|
|
235
|
+
export const KERNEL_LINE_BUDGET_FROZEN_AT = 3885; // 2026-10-12: 与 SDK index.ts 出口新增同步
|
|
236
236
|
/**
|
|
237
237
|
* K3b —— **台账数据**单独一档预算 (`src/kernel/plan.ts`)。
|
|
238
238
|
*
|
package/dist/llm/config-store.js
CHANGED
|
@@ -225,6 +225,26 @@ function getDefaultConfig() {
|
|
|
225
225
|
activeProvider = 'mimo';
|
|
226
226
|
else if (process.env.OLLAMA_BASE_URL)
|
|
227
227
|
activeProvider = 'ollama';
|
|
228
|
+
// ── 2026-10-07: 开箱即用 — 无任何私有 key 时落到内置公共网关 ──────────────
|
|
229
|
+
// 手机端/网页端下载安装后**不需要配置任何 API key** 就能直接对话:
|
|
230
|
+
// Cloudflare Worker 托管 (api.bolloon.cn) 转发到 GLM 上游, 公共通道每天 ≤500 次/人,
|
|
231
|
+
// 配了自己的 key 的机器不受限 (上面 env 检测先命中)。
|
|
232
|
+
const hasAnyKey = !!(process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY
|
|
233
|
+
|| process.env.OPENROUTER_API_KEY || process.env.GEMINI_API_KEY || process.env.MINIMAX_API_KEY
|
|
234
|
+
|| process.env.DEEPSEEK_API_KEY || process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY
|
|
235
|
+
|| process.env.GLM_API_KEY || process.env.ZHIPU_API_KEY || process.env.QWEN_API_KEY
|
|
236
|
+
|| process.env.DASHSCOPE_API_KEY || process.env.MIMO_API_KEY || process.env.OLLAMA_BASE_URL);
|
|
237
|
+
if (!hasAnyKey) {
|
|
238
|
+
envConfigs.glm = {
|
|
239
|
+
...DEFAULT_PROVIDER_CONFIGS.glm,
|
|
240
|
+
enabled: true,
|
|
241
|
+
baseUrl: 'https://api.bolloon.cn/v1', // CF Worker 公共网关 (免配置)
|
|
242
|
+
apiKey: 'bolloon-free', // 公共通道标识 (每天 ≤500 次/人)
|
|
243
|
+
model: 'glm-5.3',
|
|
244
|
+
maxTokens: 16384,
|
|
245
|
+
};
|
|
246
|
+
activeProvider = 'glm';
|
|
247
|
+
}
|
|
228
248
|
const providers = { ...DEFAULT_PROVIDER_CONFIGS };
|
|
229
249
|
for (const [provider, config] of Object.entries(envConfigs)) {
|
|
230
250
|
if (config) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** none 实现: 无向量能力, 调用即抛 (由调用方按 provider 判走关键词回退) */
|
|
2
|
+
const noneAdapter = {
|
|
3
|
+
provider: 'none',
|
|
4
|
+
dim: 0,
|
|
5
|
+
async embed() {
|
|
6
|
+
throw new Error('embedding adapter 未配置 (provider=none); 调用方应先查 provider !== "none"');
|
|
7
|
+
},
|
|
8
|
+
async embedBatch(texts) {
|
|
9
|
+
throw new Error('embedding adapter 未配置 (provider=none); 调用方应先查 provider !== "none"');
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* 工厂: 从配置构造 adapter。
|
|
14
|
+
* 规则:
|
|
15
|
+
* - 无 provider / provider='none' → none adapter (关键词回退)
|
|
16
|
+
* - provider 有值但没有实现 → none + 说明 (不假装能用)
|
|
17
|
+
* - 新增供应商实现在此登记 (每实现一个, 加一个 case)
|
|
18
|
+
*/
|
|
19
|
+
export function createEmbeddingAdapter(cfg = {}) {
|
|
20
|
+
const provider = String(cfg.provider || 'none').toLowerCase();
|
|
21
|
+
switch (provider) {
|
|
22
|
+
case 'none':
|
|
23
|
+
case '':
|
|
24
|
+
return noneAdapter;
|
|
25
|
+
// 预留登记位: 供应商 key 就绪后在此补实现 (见文件头诚实边界)
|
|
26
|
+
// case 'glm': return createGlmAdapter(cfg);
|
|
27
|
+
// case 'minimax': return createMiniMaxAdapter(cfg);
|
|
28
|
+
// case 'openai': return createOpenAIAdapter(cfg);
|
|
29
|
+
default:
|
|
30
|
+
return noneAdapter;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** 测试辅助: 造一个确定性伪 adapter (cosine 检索测试用, 不碰网络) */
|
|
34
|
+
export function createFakeAdapter(dim = 8, seedText = 'seed') {
|
|
35
|
+
// 确定性伪向量: 文本长度 + 字符和 → 稳定可复现 (仅测试用)
|
|
36
|
+
const vec = (t) => {
|
|
37
|
+
const out = [];
|
|
38
|
+
const s = String(t || '');
|
|
39
|
+
for (let i = 0; i < dim; i++) {
|
|
40
|
+
const v = (s.length * (i + 1) + s.charCodeAt(i % Math.max(1, s.length)) * 0.01) % 1;
|
|
41
|
+
out.push(Number(v.toFixed(6)));
|
|
42
|
+
}
|
|
43
|
+
// 归一化 (cosine 需要)
|
|
44
|
+
const norm = Math.sqrt(out.reduce((a, b) => a + b * b, 0)) || 1;
|
|
45
|
+
return out.map((x) => x / norm);
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
provider: 'fake',
|
|
49
|
+
dim,
|
|
50
|
+
embed: async (t) => vec(t),
|
|
51
|
+
embedBatch: async (ts) => ts.map(vec),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** cosine 相似度 (纯函数, 供检索用; 维度不一致 → 0) */
|
|
55
|
+
export function cosineSimilarity(a, b) {
|
|
56
|
+
if (!a || !b || a.length === 0 || a.length !== b.length)
|
|
57
|
+
return 0;
|
|
58
|
+
let dot = 0;
|
|
59
|
+
let na = 0;
|
|
60
|
+
let nb = 0;
|
|
61
|
+
for (let i = 0; i < a.length; i++) {
|
|
62
|
+
dot += a[i] * b[i];
|
|
63
|
+
na += a[i] * a[i];
|
|
64
|
+
nb += b[i] * b[i];
|
|
65
|
+
}
|
|
66
|
+
if (na === 0 || nb === 0)
|
|
67
|
+
return 0;
|
|
68
|
+
return dot / (Math.sqrt(na) * Math.sqrt(nb));
|
|
69
|
+
}
|
package/dist/llm/pi-ai.js
CHANGED
|
@@ -9,6 +9,42 @@ export const LLAMACPP_DEFAULT_BASE_URL = 'http://localhost:8080/v1';
|
|
|
9
9
|
export const LLAMACPP_DEFAULT_MODEL = 'local';
|
|
10
10
|
/** 唯一的重路径用途 */
|
|
11
11
|
export const MAIN_AGENT_PURPOSE = 'main-agent';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// 模型调用并发闸 (2026-10-15, leo: 「[AI 服务调用失败] ... 429 并发请求已达上限, 有错误也要修复」)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// 根因: CLI/Web/心跳/会话总结等多路会同时打同一个 provider, 上游按 API key 限并发
|
|
16
|
+
// (实测 zai/glm: `当前 API Key 的并发请求已达到上限` 429 rate_limit_error)。
|
|
17
|
+
// 修复: 在**全仓唯一模型调用咽喉** generateText 前按 `providerId|baseUrl` 分桶限流 —
|
|
18
|
+
// 同 key 同时最多 LLM_MAX_CONCURRENCY 个在途请求, 其余排队 (轮询, 可响应 abort)。
|
|
19
|
+
const llmConcurrencyBuckets = new Map();
|
|
20
|
+
export const LLM_MAX_CONCURRENCY = (() => {
|
|
21
|
+
const n = Number(process.env.BOLLOON_LLM_MAX_CONCURRENCY ?? '');
|
|
22
|
+
return Number.isFinite(n) && n >= 1 ? Math.min(n, 8) : 2;
|
|
23
|
+
})();
|
|
24
|
+
/** 包一个模型调用进并发闸: 同 key 超限则排队, abort 时尽快退出 (轮询 120ms, 最坏等一拍) */
|
|
25
|
+
export async function withLlmConcurrency(key, signal, fn) {
|
|
26
|
+
for (;;) {
|
|
27
|
+
if (signal?.aborted)
|
|
28
|
+
throw new Error(String(signal.reason || 'AbortError'));
|
|
29
|
+
const b = llmConcurrencyBuckets.get(key) ?? { active: 0, waiting: 0 };
|
|
30
|
+
if (b.active < LLM_MAX_CONCURRENCY) {
|
|
31
|
+
b.active++;
|
|
32
|
+
llmConcurrencyBuckets.set(key, b);
|
|
33
|
+
try {
|
|
34
|
+
return await fn();
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
b.active--;
|
|
38
|
+
if (b.active <= 0 && b.waiting <= 0)
|
|
39
|
+
llmConcurrencyBuckets.delete(key);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
b.waiting++;
|
|
43
|
+
llmConcurrencyBuckets.set(key, b);
|
|
44
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
45
|
+
b.waiting--;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
12
48
|
/** 轻量前缀的 system 上限 (字符). 只对**自动装配部分 + 调用方自带短提示**做兜底截断, 正常调用远低于它 */
|
|
13
49
|
export const LIGHTWEIGHT_SYSTEM_MAX_CHARS = 2000;
|
|
14
50
|
/**
|
|
@@ -577,7 +613,9 @@ export class PiAIModel {
|
|
|
577
613
|
error = retryErr; // 记录最后一次错误, 循环继续重试 (backoff 递增)
|
|
578
614
|
}
|
|
579
615
|
}
|
|
580
|
-
|
|
616
|
+
// 2026-10-15: 单行标签形 (`[pi-ai]` 是 log-gate INTERNAL_CHATTER_TAG, 只落盘不上屏;
|
|
617
|
+
// `分类` 在 for 循环块级作用域内, errMsg 在下面才解析 —— 这里自包含一行, 不引块内变量)
|
|
618
|
+
console.error(`[pi-ai] chat 失败: ${String(error?.message ?? error).replace(/\n/g, ' ').slice(0, 300)}`);
|
|
581
619
|
// 2026-06-15: 真实 error 信息 + 明确告诉 LLM "这是 API 错, 不要 retry"
|
|
582
620
|
// 旧版: "抱歉,AI服务暂时不可用。" → LLM 看到 isTooShort=false(< 50 但 > 0),
|
|
583
621
|
// needsMoreWork 不会触发, 但 hasError 模式 (含 "error" / "失败") 会判定要继续修
|
|
@@ -655,29 +693,36 @@ export class PiAIModel {
|
|
|
655
693
|
finalMessages = [{ role: 'system', content: `可用工具:\n${toolDescriptions}` }, ...messages];
|
|
656
694
|
}
|
|
657
695
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
696
|
+
// 2026-10-15 (leo: 429 并发已达上限, 有错误也要修复): 全仓唯一模型调用咽喉 —
|
|
697
|
+
// 所有入口 (CLI/Web/MCP/技能/子 Agent/长期任务) 都经 generateText 出网,
|
|
698
|
+
// 在这里按 `providerId|baseUrl` 分桶限流, 同 key 同时最多 LLM_MAX_CONCURRENCY 个在途,
|
|
699
|
+
// 排队等待 (轮询 120ms, abort 时尽快退出) — 不再把同一 API key 的并发打爆。
|
|
700
|
+
const bucketKey = `${this.config.providerId ?? this.provider}|${this.getBaseUrl()}`;
|
|
701
|
+
return withLlmConcurrency(bucketKey, signal, async () => {
|
|
702
|
+
switch (this.provider) {
|
|
703
|
+
case 'openai':
|
|
704
|
+
case 'minimax':
|
|
705
|
+
case 'deepseek':
|
|
706
|
+
case 'kimi':
|
|
707
|
+
case 'glm':
|
|
708
|
+
case 'qwen':
|
|
709
|
+
case 'mimo':
|
|
710
|
+
case 'grok':
|
|
711
|
+
return this.callOpenAI(finalMessages, temperature, maxTokens, signal, openaiTools, onToken, purpose, source);
|
|
712
|
+
case 'anthropic':
|
|
713
|
+
return this.callAnthropic(finalMessages, temperature, maxTokens, signal);
|
|
714
|
+
case 'ollama':
|
|
715
|
+
return this.callOllama(finalMessages, temperature, signal);
|
|
716
|
+
case 'openrouter':
|
|
717
|
+
return this.callOpenRouter(finalMessages, temperature, maxTokens, signal);
|
|
718
|
+
case 'gemini':
|
|
719
|
+
return this.callGemini(finalMessages, temperature, maxTokens, signal);
|
|
720
|
+
case 'local':
|
|
721
|
+
return this.callLocal(finalMessages, temperature, signal);
|
|
722
|
+
default:
|
|
723
|
+
throw new Error(`Unsupported provider: ${this.provider}`);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
681
726
|
}
|
|
682
727
|
getApiKey() {
|
|
683
728
|
// 2026-09-28: llama.cpp (本地 OpenAI 兼容服务) —— 声明 id 单独接 key, key 可空
|
|
@@ -1390,9 +1435,8 @@ export function initPiAI(config = {}) {
|
|
|
1390
1435
|
const isLlamacpp = declared === LLAMACPP_PROVIDER_ID;
|
|
1391
1436
|
const provider = isLlamacpp ? 'openai' : declared;
|
|
1392
1437
|
const providerId = config.providerId || (isLlamacpp ? LLAMACPP_PROVIDER_ID : undefined);
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
let baseUrl = config.baseUrl || (isLlamacpp ? (process.env.LLAMACPP_BASE_URL || LLAMACPP_DEFAULT_BASE_URL) : undefined);
|
|
1438
|
+
let model = config.model || undefined;
|
|
1439
|
+
let baseUrl = config.baseUrl || undefined;
|
|
1396
1440
|
console.log('[PiAIModel] Initializing with provider:', provider, 'model:', model);
|
|
1397
1441
|
// 如果没有提供 apiKey,从配置文件读取
|
|
1398
1442
|
let apiKey = config.apiKey;
|
|
@@ -1400,26 +1444,42 @@ export function initPiAI(config = {}) {
|
|
|
1400
1444
|
// 否则"换 key"这种真变化不会重建实例, 指纹就失去意义)
|
|
1401
1445
|
if (!apiKey && isLlamacpp)
|
|
1402
1446
|
apiKey = process.env.LLAMACPP_API_KEY || undefined;
|
|
1403
|
-
|
|
1447
|
+
// ── 2026-10-06 技术债重构: 配置文件对所有 provider 生效 (不只 llamacpp) ──
|
|
1448
|
+
// 用户是 200+ 模型 / 自适应 URL 的重度用户: 在配置里自定义 baseUrl/model 必须被尊重。
|
|
1449
|
+
// 之前只有 llamacpp 走这条, 其他 provider 一律被 getBaseUrl()/mapModel() 的
|
|
1450
|
+
// 内置默认 / env 覆盖掉 ⇒ 配置了自定义网关 URL 却打官方端点 (401 真实事故)。
|
|
1451
|
+
// 优先级: 显式 config > 配置文件 > env > 内置默认 (getBaseUrl/mapModel 天然如此,
|
|
1452
|
+
// 这里只把配置值塞进去, 让"配置文件"这一层对所有 provider 生效)。
|
|
1453
|
+
{
|
|
1404
1454
|
try {
|
|
1405
1455
|
const configPath = resolveConfigPath();
|
|
1406
1456
|
if (configPath) {
|
|
1407
1457
|
const configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1408
|
-
const providerConfig = configData.providers[declared];
|
|
1409
|
-
if (providerConfig?.apiKey) {
|
|
1458
|
+
const providerConfig = configData.providers?.[declared];
|
|
1459
|
+
if (!apiKey && providerConfig?.apiKey) {
|
|
1410
1460
|
apiKey = providerConfig.apiKey;
|
|
1411
1461
|
console.log('[PiAIModel] Loaded apiKey from config for', declared);
|
|
1412
1462
|
}
|
|
1413
|
-
// 2026-
|
|
1414
|
-
// (
|
|
1415
|
-
if (
|
|
1463
|
+
// 2026-10-06: baseUrl / model 不再只给 llamacpp —— 所有 provider 都尊重配置文件
|
|
1464
|
+
// (显式 config 优先; 配置里的值只作为"未显式指定"时的兜底)
|
|
1465
|
+
if (!baseUrl && providerConfig?.baseUrl)
|
|
1416
1466
|
baseUrl = providerConfig.baseUrl;
|
|
1467
|
+
if (!model && providerConfig?.model)
|
|
1468
|
+
model = providerConfig.model;
|
|
1469
|
+
if (providerConfig?.baseUrl || providerConfig?.model) {
|
|
1470
|
+
console.log('[PiAIModel] Loaded baseUrl/model from config for', declared, baseUrl ? 'baseUrl=' + baseUrl : '', model ? 'model=' + model : '');
|
|
1471
|
+
}
|
|
1417
1472
|
}
|
|
1418
1473
|
}
|
|
1419
1474
|
catch (e) {
|
|
1420
|
-
console.log('[PiAIModel] Error reading
|
|
1475
|
+
console.log('[PiAIModel] Error reading config:', e);
|
|
1421
1476
|
}
|
|
1422
1477
|
}
|
|
1478
|
+
// 兜底: 配置文件也没给 → 回落 llamacpp env / detectModel 内置默认 (老行为不变)
|
|
1479
|
+
if (!model)
|
|
1480
|
+
model = isLlamacpp ? (process.env.LLAMACPP_MODEL || LLAMACPP_DEFAULT_MODEL) : detectModel(provider);
|
|
1481
|
+
if (!baseUrl)
|
|
1482
|
+
baseUrl = isLlamacpp ? (process.env.LLAMACPP_BASE_URL || LLAMACPP_DEFAULT_BASE_URL) : undefined;
|
|
1423
1483
|
// ── 2026-09-28: 按指纹幂等 ──────────────────────────────────────────────
|
|
1424
1484
|
// 同一份配置重复 init (CLI 每轮 / web 每次请求 / 脚本多入口) **不再重建实例、不再清装配缓存**.
|
|
1425
1485
|
// 之前每次 init 都清缓存 → 每轮重装 system → 前缀抖动 → 服务端前缀 KV 永远用不上.
|