@bolloon/bolloon-agent 0.3.36 → 0.3.38
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 +107 -0
- package/dist/agents/pi-sdk.js +20 -2
- package/dist/bootstrap/context-collector.js +5 -1
- package/dist/cli/ink-app.js +46 -8
- package/dist/cli/loading-tui.js +7 -5
- package/dist/cli-entry.js +1 -1
- 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-build/electron/config.js +21 -0
- package/dist/electron-build/electron/config.js.map +1 -0
- package/dist/electron-build/electron/dialogs.js +108 -0
- package/dist/electron-build/electron/dialogs.js.map +1 -0
- package/dist/electron-build/electron/first-run.js +170 -0
- package/dist/electron-build/electron/first-run.js.map +1 -0
- package/dist/electron-build/electron/ipc.js +20 -0
- package/dist/electron-build/electron/ipc.js.map +1 -0
- package/dist/electron-build/electron/logger.js +114 -0
- package/dist/electron-build/electron/logger.js.map +1 -0
- package/dist/electron-build/electron/main.js +79 -0
- package/dist/electron-build/electron/main.js.map +1 -0
- package/dist/electron-build/electron/menu.js +145 -0
- package/dist/electron-build/electron/menu.js.map +1 -0
- package/dist/electron-build/electron/paths.js +75 -0
- package/dist/electron-build/electron/paths.js.map +1 -0
- package/dist/electron-build/electron/server.js +119 -0
- package/dist/electron-build/electron/server.js.map +1 -0
- package/dist/electron-build/electron/tray.js +111 -0
- package/dist/electron-build/electron/tray.js.map +1 -0
- package/dist/electron-build/electron/window.js +111 -0
- package/dist/electron-build/electron/window.js.map +1 -0
- package/dist/electron-build/electron-preload.js +32 -0
- package/dist/electron-build/electron-preload.js.map +1 -0
- package/dist/electron-build/electron.js +8 -0
- package/dist/electron-build/electron.js.map +1 -0
- package/dist/electron-build/utils/auto-update.js +606 -0
- package/dist/electron-build/utils/auto-update.js.map +1 -0
- package/dist/electron-preload.js +16 -19
- package/dist/electron.js +1 -4
- package/dist/index.js +134 -31
- package/dist/llm/config-store.js +18 -2
- package/dist/llm/pi-ai.js +29 -11
- package/dist/security/tool-gate.js +2 -0
- package/dist/utils/auto-update.js +19 -51
- package/package.json +6 -5
|
@@ -2118,6 +2118,113 @@ export function registerBuiltinTools(ctx) {
|
|
|
2118
2118
|
}
|
|
2119
2119
|
}
|
|
2120
2120
|
});
|
|
2121
|
+
// ============================================================
|
|
2122
|
+
// bolloon_config_get / bolloon_config_set (2026-08-07)
|
|
2123
|
+
// Bolloon 自己读写 ~/.bolloon/bolloon-config.json — 统一配置文件,
|
|
2124
|
+
// 让 agent 有修改自身配置的权限 (模型/供应商/温度等), 不再只能靠用户手改.
|
|
2125
|
+
// ============================================================
|
|
2126
|
+
ctx.tools.set('bolloon_config_get', {
|
|
2127
|
+
name: 'bolloon_config_get',
|
|
2128
|
+
description: '读取 Bolloon 自身配置 (~/.bolloon/bolloon-config.json): 当前激活供应商 + 各供应商 API 配置 (baseUrl/model/温度). 注意: 不输出 apiKey 明文 (脱敏), 只显示是否已配置.',
|
|
2129
|
+
parameters: {},
|
|
2130
|
+
execute: async () => {
|
|
2131
|
+
try {
|
|
2132
|
+
const { llmConfigStore } = await import('../llm/config-store.js');
|
|
2133
|
+
await llmConfigStore.initialize();
|
|
2134
|
+
const cfg = await llmConfigStore.getConfig();
|
|
2135
|
+
const active = cfg.activeProvider;
|
|
2136
|
+
const lines = [`📋 Bolloon 配置 (${active} 激活):`];
|
|
2137
|
+
for (const [name, p] of Object.entries(cfg.providers || {})) {
|
|
2138
|
+
const pc = p;
|
|
2139
|
+
if (!pc)
|
|
2140
|
+
continue;
|
|
2141
|
+
const mark = name === active ? '●' : '○';
|
|
2142
|
+
const key = pc.apiKey ? '🔑' : pc.requiresApiKey ? '✗ 无key' : '无key需求';
|
|
2143
|
+
lines.push(` ${mark} ${name}: ${key} · model=${pc.model || '?'} · baseUrl=${pc.baseUrl || '?'}${pc.temperature ? ` · temp=${pc.temperature}` : ''}`);
|
|
2144
|
+
}
|
|
2145
|
+
return { success: true, output: lines.join('\n') };
|
|
2146
|
+
}
|
|
2147
|
+
catch (e) {
|
|
2148
|
+
return { success: false, error: `bolloon_config_get 失败: ${String(e.message || e).slice(0, 200)}` };
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
});
|
|
2152
|
+
ctx.tools.set('bolloon_config_set', {
|
|
2153
|
+
name: 'bolloon_config_set',
|
|
2154
|
+
description: '修改 Bolloon 自身配置 (~/.bolloon/bolloon-config.json). 可切换激活供应商 (provider) 或改某供应商的 model/baseUrl/temperature/enabled. 修改立即生效 (下次 LLM 调用使用新配置). 例: provider=deepseek; provider=minimax, model=MiniMax-M3; deepseek.temperature=0.3',
|
|
2155
|
+
parameters: {
|
|
2156
|
+
provider: '可选: 切换激活供应商 (如 deepseek / minimax / openai / anthropic / ollama)',
|
|
2157
|
+
'provider.key': '可选: 修改指定供应商字段, 格式 <供应商>.<字段>=<值>, 如 minimax.model=MiniMax-M3',
|
|
2158
|
+
model: '可选: 同时设置激活供应商的 model',
|
|
2159
|
+
temperature: '可选: 同时设置激活供应商的 temperature (0-2)',
|
|
2160
|
+
},
|
|
2161
|
+
execute: async (args) => {
|
|
2162
|
+
try {
|
|
2163
|
+
const { llmConfigStore } = await import('../llm/config-store.js');
|
|
2164
|
+
await llmConfigStore.initialize();
|
|
2165
|
+
const changes = [];
|
|
2166
|
+
// 1. 切换激活供应商
|
|
2167
|
+
if (args.provider) {
|
|
2168
|
+
const name = String(args.provider).trim().toLowerCase();
|
|
2169
|
+
const known = ['openai', 'anthropic', 'ollama', 'openrouter', 'gemini', 'minimax', 'deepseek', 'kimi', 'glm', 'qwen', 'mimo', 'grok', 'local'];
|
|
2170
|
+
if (!known.includes(name))
|
|
2171
|
+
return { success: false, error: `未知供应商: ${name}. 可用: ${known.join(', ')}` };
|
|
2172
|
+
await llmConfigStore.setActiveProvider(name);
|
|
2173
|
+
changes.push(`activeProvider=${name}`);
|
|
2174
|
+
}
|
|
2175
|
+
// 2. 修改指定供应商字段 (provider.key=value)
|
|
2176
|
+
for (const [k, v] of Object.entries(args)) {
|
|
2177
|
+
if (k === 'provider' || k === 'model' || k === 'temperature')
|
|
2178
|
+
continue;
|
|
2179
|
+
if (!k.includes('.'))
|
|
2180
|
+
continue;
|
|
2181
|
+
const [prov, field] = k.split('.');
|
|
2182
|
+
const val = String(v);
|
|
2183
|
+
if (field === 'temperature' || field === 'maxTokens') {
|
|
2184
|
+
const num = Number(val);
|
|
2185
|
+
if (Number.isNaN(num))
|
|
2186
|
+
return { success: false, error: `${k}=${val} 不是数字` };
|
|
2187
|
+
await llmConfigStore.updateProvider(prov, { [field]: num });
|
|
2188
|
+
}
|
|
2189
|
+
else if (field === 'enabled') {
|
|
2190
|
+
await llmConfigStore.updateProvider(prov, { enabled: val === 'true' || val === '1' });
|
|
2191
|
+
}
|
|
2192
|
+
else if (field === 'apiKey') {
|
|
2193
|
+
await llmConfigStore.updateProvider(prov, { apiKey: val });
|
|
2194
|
+
}
|
|
2195
|
+
else {
|
|
2196
|
+
await llmConfigStore.updateProvider(prov, { [field]: val });
|
|
2197
|
+
}
|
|
2198
|
+
changes.push(`${prov}.${field}=${field === 'apiKey' ? '***' : val}`);
|
|
2199
|
+
}
|
|
2200
|
+
// 3. 激活供应商的 model / temperature
|
|
2201
|
+
if (args.model || args.temperature) {
|
|
2202
|
+
const active = await llmConfigStore.getActiveProvider();
|
|
2203
|
+
const patch = {};
|
|
2204
|
+
if (args.model)
|
|
2205
|
+
patch.model = String(args.model);
|
|
2206
|
+
if (args.temperature) {
|
|
2207
|
+
const t = Number(args.temperature);
|
|
2208
|
+
if (Number.isNaN(t))
|
|
2209
|
+
return { success: false, error: `temperature=${args.temperature} 不是数字` };
|
|
2210
|
+
patch.temperature = t;
|
|
2211
|
+
}
|
|
2212
|
+
await llmConfigStore.updateProvider(active, patch);
|
|
2213
|
+
if (args.model)
|
|
2214
|
+
changes.push(`${active}.model=${args.model}`);
|
|
2215
|
+
if (args.temperature)
|
|
2216
|
+
changes.push(`${active}.temperature=${args.temperature}`);
|
|
2217
|
+
}
|
|
2218
|
+
if (changes.length === 0)
|
|
2219
|
+
return { success: false, error: '没有要修改的配置项. 例: provider=deepseek 或 minimax.model=MiniMax-M3' };
|
|
2220
|
+
const cfg = await llmConfigStore.getConfig();
|
|
2221
|
+
return { success: true, output: `✅ 配置已更新: ${changes.join(', ')}\n 当前激活: ${cfg.activeProvider}` };
|
|
2222
|
+
}
|
|
2223
|
+
catch (e) {
|
|
2224
|
+
return { success: false, error: `bolloon_config_set 失败: ${String(e.message || e).slice(0, 200)}` };
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
});
|
|
2121
2228
|
}
|
|
2122
2229
|
// ─── IPFS/IPNS 通用 helper (2026-08-04) ─────────────────────────────────────
|
|
2123
2230
|
// 复用 publish_did 的 checkKuboSetup 自动安装/启动本地 Kubo (darwin-arm64 v0.28.0)
|
package/dist/agents/pi-sdk.js
CHANGED
|
@@ -13,6 +13,10 @@ import * as fsSync from 'fs';
|
|
|
13
13
|
import * as os from 'os';
|
|
14
14
|
import * as path from 'path';
|
|
15
15
|
import { getContextManager } from '../bootstrap/context-manager.js';
|
|
16
|
+
import { createRequire } from 'module';
|
|
17
|
+
// 2026-08-07: ESM 下裸 require 抛错被 catch 吞掉 → estimateHistoryTokens/maxContextTokens 静默失效
|
|
18
|
+
// (状态栏恒 0 的第二层根因). 统一用 createRequire 加载 CJS 模块.
|
|
19
|
+
const _piRequire = createRequire(import.meta.url);
|
|
16
20
|
import { documentReader } from '../documents/reader.js';
|
|
17
21
|
import { getMinimax } from '../constraints/index.js';
|
|
18
22
|
import { p2pNetwork } from '../network/p2p.js';
|
|
@@ -90,7 +94,7 @@ export class PiAgentSession {
|
|
|
90
94
|
/** 2026-08-06: 上下文窗口 (tokens) — 统一走 ContextManager 配置 (1M 默认). */
|
|
91
95
|
maxContextTokens() {
|
|
92
96
|
try {
|
|
93
|
-
const { getContextManager } =
|
|
97
|
+
const { getContextManager } = _piRequire('../bootstrap/context-manager.js');
|
|
94
98
|
const n = getContextManager().getConfig().maxTokens;
|
|
95
99
|
return Number.isFinite(n) && n > 0 ? n : this.MAX_OUTPUT_TOKEN_ESCALATION_THRESHOLD;
|
|
96
100
|
}
|
|
@@ -574,6 +578,14 @@ export class PiAgentSession {
|
|
|
574
578
|
return false;
|
|
575
579
|
}
|
|
576
580
|
}
|
|
581
|
+
/** 2026-08-07: prompt 出口统一上报 token 用量到 ContextManager (fallback/pivot/react 全路径覆盖) —
|
|
582
|
+
* 之前只有 runReActLoop 迭代内上报, chitchat/fallback/pivot 路径状态栏恒 0 */
|
|
583
|
+
reportUsageToContextManager() {
|
|
584
|
+
try {
|
|
585
|
+
getContextManager().updateUsage(this.estimateHistoryTokens());
|
|
586
|
+
}
|
|
587
|
+
catch { /* 非致命 */ }
|
|
588
|
+
}
|
|
577
589
|
async prompt(input, options) {
|
|
578
590
|
this.minimaxAvailable = this.checkMinimax();
|
|
579
591
|
this.currentChannelId = options?.channelId ?? this.currentChannelId;
|
|
@@ -584,6 +596,7 @@ export class PiAgentSession {
|
|
|
584
596
|
if (!this.minimaxAvailable) {
|
|
585
597
|
const response = await this.handleFallback(input);
|
|
586
598
|
this.messageHistory.push({ role: 'assistant', content: response });
|
|
599
|
+
this.reportUsageToContextManager();
|
|
587
600
|
return response;
|
|
588
601
|
}
|
|
589
602
|
// P0 注入门
|
|
@@ -624,6 +637,7 @@ export class PiAgentSession {
|
|
|
624
637
|
this.clearJudgmentGate();
|
|
625
638
|
this.currentSignal = null;
|
|
626
639
|
this.currentOnStream = null;
|
|
640
|
+
this.reportUsageToContextManager();
|
|
627
641
|
}
|
|
628
642
|
}
|
|
629
643
|
try {
|
|
@@ -641,6 +655,7 @@ export class PiAgentSession {
|
|
|
641
655
|
this.clearJudgmentGate();
|
|
642
656
|
this.currentSignal = null;
|
|
643
657
|
this.currentOnStream = null;
|
|
658
|
+
this.reportUsageToContextManager();
|
|
644
659
|
}
|
|
645
660
|
}
|
|
646
661
|
async promptStream(input, onStream, signal, channelId) {
|
|
@@ -667,6 +682,7 @@ export class PiAgentSession {
|
|
|
667
682
|
const response = await this.handleFallback(userText);
|
|
668
683
|
this.messageHistory.push({ role: 'assistant', content: response });
|
|
669
684
|
onStream({ type: 'done', content: '' });
|
|
685
|
+
this.reportUsageToContextManager();
|
|
670
686
|
return response;
|
|
671
687
|
}
|
|
672
688
|
// P0 注入门: 缓存 onStream + signal, computeJudgmentGate 用 currentOnStream 广播 phase
|
|
@@ -812,6 +828,7 @@ export class PiAgentSession {
|
|
|
812
828
|
this.bootstrapAddition = '';
|
|
813
829
|
this.contextHintAddition = '';
|
|
814
830
|
this.promptStartTime = 0;
|
|
831
|
+
this.reportUsageToContextManager();
|
|
815
832
|
}
|
|
816
833
|
return pivotResult;
|
|
817
834
|
}
|
|
@@ -898,6 +915,7 @@ export class PiAgentSession {
|
|
|
898
915
|
// 用完即清, 避免污染下一轮
|
|
899
916
|
this.clearJudgmentGate();
|
|
900
917
|
this.currentOnStream = null;
|
|
918
|
+
this.reportUsageToContextManager();
|
|
901
919
|
this.currentSignal = null;
|
|
902
920
|
this.bootstrapAddition = '';
|
|
903
921
|
this.promptStartTime = 0;
|
|
@@ -1823,7 +1841,7 @@ ${toolDefs}
|
|
|
1823
1841
|
*/
|
|
1824
1842
|
estimateHistoryTokens() {
|
|
1825
1843
|
try {
|
|
1826
|
-
const { estimateTokens } =
|
|
1844
|
+
const { estimateTokens } = _piRequire('../context-compaction/index.js');
|
|
1827
1845
|
return estimateTokens(this.messageHistory);
|
|
1828
1846
|
}
|
|
1829
1847
|
catch {
|
|
@@ -184,7 +184,11 @@ function collectEnv() {
|
|
|
184
184
|
let llmProvider = 'unknown';
|
|
185
185
|
try {
|
|
186
186
|
const home = process.env.HOME || os.homedir() || '/tmp';
|
|
187
|
-
|
|
187
|
+
// 2026-08-07: bolloon-config.json 优先, 旧 llm-config.json 兜底
|
|
188
|
+
let cfgPath = path.join(home, '.bolloon', 'bolloon-config.json');
|
|
189
|
+
if (!require('fs').existsSync(cfgPath))
|
|
190
|
+
cfgPath = path.join(home, '.bolloon', 'llm-config.json');
|
|
191
|
+
const cfg = require(cfgPath);
|
|
188
192
|
if (cfg && typeof cfg === 'object' && 'provider' in cfg) {
|
|
189
193
|
llmProvider = String(cfg.provider);
|
|
190
194
|
}
|
package/dist/cli/ink-app.js
CHANGED
|
@@ -41,6 +41,13 @@ const MentionPopup = ({ title, items, sel, width, loading }) => {
|
|
|
41
41
|
};
|
|
42
42
|
const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH }) => {
|
|
43
43
|
const [input, setInput] = useState('');
|
|
44
|
+
// 2026-08-07: inputRef 同步镜像 input — useInput 回调拿最新值 (闭包里的 input 是陈旧的)
|
|
45
|
+
const inputRef = useRef('');
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
inputRef.current = input;
|
|
48
|
+
}, [input]);
|
|
49
|
+
// 2026-08-07: 提交防重 (InkApp \n/\r 兜底 + TextInput 双触发场景)
|
|
50
|
+
const lastSubmitRef = useRef({ t: 0, v: '' });
|
|
44
51
|
const [msgs, setMsgs] = useState([]);
|
|
45
52
|
const [status, setStatus] = useState(initialStatus);
|
|
46
53
|
const { exit } = useApp();
|
|
@@ -285,6 +292,11 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
285
292
|
}, []);
|
|
286
293
|
const onSubmit = useCallback((value) => {
|
|
287
294
|
const trimmed = value.trim();
|
|
295
|
+
// 2026-08-07: 防重 — \n/\r 兜底分支与 TextInput 可能都触发提交, 1.5s 内同值只提交一次
|
|
296
|
+
const now = Date.now();
|
|
297
|
+
if (lastSubmitRef.current.v === trimmed && now - lastSubmitRef.current.t < 1500)
|
|
298
|
+
return;
|
|
299
|
+
lastSubmitRef.current = { t: now, v: trimmed };
|
|
288
300
|
if (!trimmed)
|
|
289
301
|
return;
|
|
290
302
|
// 入历史 (去重最近一条, 上限 100)
|
|
@@ -349,13 +361,13 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
349
361
|
setSel(s => Math.min(filtered.length - 1, s + 1));
|
|
350
362
|
return;
|
|
351
363
|
}
|
|
352
|
-
if ((key.tab || key.return) && filtered.length > 0) {
|
|
364
|
+
if ((key.tab || key.return || /[\n\r]/.test(_input)) && filtered.length > 0) {
|
|
353
365
|
const it = filtered[safeSel];
|
|
354
366
|
if (it)
|
|
355
367
|
acceptMention(it);
|
|
356
368
|
return;
|
|
357
369
|
}
|
|
358
|
-
if (key.return && filtered.length === 0) {
|
|
370
|
+
if ((key.return || /[\n\r]/.test(_input)) && filtered.length === 0) {
|
|
359
371
|
// 弹窗无匹配项: Enter = 提交当前输入 (否则 /channel 无参 + Enter 永远提交不了 — 2026-08-06)
|
|
360
372
|
const v = input.trim();
|
|
361
373
|
if (v)
|
|
@@ -409,7 +421,19 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
409
421
|
return; // 其余键忽略 (return/tab/esc 等由 TextInput 或上层处理)
|
|
410
422
|
}
|
|
411
423
|
// ── 正常模式 ──
|
|
412
|
-
//
|
|
424
|
+
// 2026-08-07: Enter 兜底 — pty/管道下 termios 可能把 \r 转 \n 且 node 把整 chunk
|
|
425
|
+
// 当一次 keypress (key.return=false), TextInput 的 onSubmit 永不触发 → 消息发不出去.
|
|
426
|
+
// 应用层把 \n/\r 一律视为提交 (兼容 raw/cooked 两种模式, 不依赖 termios).
|
|
427
|
+
if (/[\n\r]/.test(_input)) {
|
|
428
|
+
const before = String(_input).split(/[\n\r]/)[0];
|
|
429
|
+
const val = inputRef.current + before;
|
|
430
|
+
if (val.trim())
|
|
431
|
+
onSubmit(val);
|
|
432
|
+
else
|
|
433
|
+
setInput('');
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
// Tab 命令补齐 (匹配触发符后的 token 再补)
|
|
413
437
|
if (key.tab) {
|
|
414
438
|
doTabCompletion();
|
|
415
439
|
return;
|
|
@@ -468,14 +492,28 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
468
492
|
// TextInput handles actual input; useInput only for Ctrl+C / Esc
|
|
469
493
|
});
|
|
470
494
|
// 自动更新状态栏 (每秒)
|
|
495
|
+
// 2026-08-07 修复: 依赖必须为空 [] — [getStatusUpdate] 在渲染间引用变化 (Ink 内部元素重建),
|
|
496
|
+
// effect 每次渲染 cleanup+setup → setInterval 刚建立就被清除 → 永不 tick → 状态栏恒初始值.
|
|
497
|
+
// getStatusUpdate 是 startInk 传入的模块级函数 (引用稳定), 空依赖首渲染捕获即可, 内部实时读.
|
|
471
498
|
useEffect(() => {
|
|
499
|
+
// 挂载时立即同步刷新一次状态栏 (不等 1s 后第一个 tick)
|
|
500
|
+
try {
|
|
501
|
+
const s0 = getStatusUpdate();
|
|
502
|
+
if (s0)
|
|
503
|
+
setStatus(s0);
|
|
504
|
+
}
|
|
505
|
+
catch { /* 状态栏更新失败不致命 */ }
|
|
472
506
|
const timer = setInterval(() => {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
507
|
+
try {
|
|
508
|
+
const s = getStatusUpdate();
|
|
509
|
+
if (s)
|
|
510
|
+
setStatus(s);
|
|
511
|
+
}
|
|
512
|
+
catch { /* 状态栏更新失败不致命 */ }
|
|
476
513
|
}, 1000);
|
|
477
514
|
return () => clearInterval(timer);
|
|
478
|
-
|
|
515
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
516
|
+
}, []);
|
|
479
517
|
// 调试/测试钩子: 输入变化时通知外部 (pty 测试用)
|
|
480
518
|
useEffect(() => {
|
|
481
519
|
globalThis.__inkOnInput?.(input);
|
|
@@ -511,7 +549,7 @@ export function startInk(onPrompt, initialStatus, getStatusUpdate) {
|
|
|
511
549
|
stdout: process.stdout,
|
|
512
550
|
stdin: process.stdin,
|
|
513
551
|
exitOnCtrlC: false,
|
|
514
|
-
patchConsole: false, //
|
|
552
|
+
patchConsole: false, // 重要: 阻止 Ink 劫持 console.log
|
|
515
553
|
});
|
|
516
554
|
}
|
|
517
555
|
export function stopInk() {
|
package/dist/cli/loading-tui.js
CHANGED
|
@@ -23,12 +23,14 @@ function bg(r, g, b) { return `\x1b[48;2;${r};${g};${b}m`; }
|
|
|
23
23
|
const C_ACCENT = fg(0xc4, 0xd6, 0x40); // #c4d640
|
|
24
24
|
const C_ACCENT_BG = bg(0xc4, 0xd6, 0x40);
|
|
25
25
|
const C_TEXT = fg(0xd8, 0xd8, 0xc8); // #d8d8c8
|
|
26
|
+
const C_WHITE = fg(0xff, 0xff, 0xff); // #ffffff 纯白 (2026-08-07: 用户要求回复字体白色显眼)
|
|
26
27
|
const C_DIM = fg(0x90, 0x90, 0x88); // #909088
|
|
27
28
|
const C_MUTED = fg(0x60, 0x60, 0x58); // #606058
|
|
28
29
|
const C_OK = fg(0x22, 0xc5, 0x5e); // #22c55e
|
|
29
30
|
const C_ERROR = fg(0xef, 0x44, 0x44); // #ef4444
|
|
30
31
|
const C_WARN = fg(0xf5, 0x9e, 0x0b); // #f59e0b
|
|
31
|
-
const C_BORDER = fg(0x3a, 0x3a, 0x36); // #3a3a36
|
|
32
|
+
const C_BORDER = fg(0x3a, 0x3a, 0x36); // #3a3a36 (暗描边)
|
|
33
|
+
const C_BORDER_BRIGHT = fg(0x8a, 0x8a, 0x7e); // #8a8a7e (2026-08-07: 对话框边框提亮, 告别灰白)
|
|
32
34
|
const HIDE = '\x1b[?25l';
|
|
33
35
|
const SHOW = '\x1b[?25h';
|
|
34
36
|
// 版本信息 — 从 package.json 读取, 不再硬编码
|
|
@@ -287,11 +289,11 @@ export function renderMessageBox(opts) {
|
|
|
287
289
|
const inner = Math.max(20, dispWidth(title) + 4, maxLine);
|
|
288
290
|
const width = Math.min(termWidth() - 2, opts.width ?? inner + 4);
|
|
289
291
|
const lines = [];
|
|
290
|
-
// 2026-08-
|
|
291
|
-
lines.push(`${
|
|
292
|
+
// 2026-08-07: 边框提亮 (C_BORDER_BRIGHT), 正文纯白 (C_WHITE) — 告别灰白内容
|
|
293
|
+
lines.push(`${C_BORDER_BRIGHT}${boxTop(`${color}${title}${RESET}`, width, RD)}${RESET}`);
|
|
292
294
|
for (const l of wrapText(opts.body, width - 4))
|
|
293
|
-
lines.push(`${
|
|
294
|
-
lines.push(`${
|
|
295
|
+
lines.push(`${C_BORDER_BRIGHT}${boxRow(`${C_WHITE}${l}${RESET}`, width, 'left', RD)}${RESET}`);
|
|
296
|
+
lines.push(`${C_BORDER_BRIGHT}${boxBottom(width, RD)}${RESET}`);
|
|
295
297
|
return lines.join('\n');
|
|
296
298
|
}
|
|
297
299
|
/** 取首条非空行作为预览 (按可见宽度截断, 加省略号) */
|
package/dist/cli-entry.js
CHANGED
|
@@ -357,7 +357,7 @@ async function handleModelCommand(modelArgs) {
|
|
|
357
357
|
const info = PROVIDER_INFO[name] || {};
|
|
358
358
|
console.log(`${GREEN}✅ 已切换到 ${name}${RESET} (${info.name || ''})${modelNote}`);
|
|
359
359
|
console.log(` 当前模型: ${modelArgs[1] || provider.model || (info.models && info.models[0]) || '默认'}`);
|
|
360
|
-
console.log(` 配置已持久化: ~/.bolloon/
|
|
360
|
+
console.log(` 配置已持久化: ~/.bolloon/bolloon-config.json`);
|
|
361
361
|
}
|
|
362
362
|
/** 引擎子命令: list / run */
|
|
363
363
|
async function handleEngineCommand(engineArgs) {
|
package/dist/electron/config.js
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isDev = exports.MAIN_WINDOW_MIN = exports.MAIN_WINDOW_DEFAULT = exports.WEB_SERVER_STARTUP_TIMEOUT_MS = exports.DEFAULT_HOST = exports.DEFAULT_PORT = void 0;
|
|
4
|
-
exports.preferredPort = preferredPort;
|
|
5
1
|
/**
|
|
6
2
|
* 常量配置 (env 解析在这里集中, 不散在 main 流程)
|
|
7
3
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { app } from 'electron';
|
|
5
|
+
export const DEFAULT_PORT = 54188;
|
|
10
6
|
/** Hard-pin to loopback; LAN exposure must be explicit. */
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function preferredPort() {
|
|
7
|
+
export const DEFAULT_HOST = '127.0.0.1';
|
|
8
|
+
export const WEB_SERVER_STARTUP_TIMEOUT_MS = 15_000;
|
|
9
|
+
export const MAIN_WINDOW_DEFAULT = { width: 1200, height: 800 };
|
|
10
|
+
export const MAIN_WINDOW_MIN = { width: 800, height: 600 };
|
|
11
|
+
export function preferredPort() {
|
|
16
12
|
const raw = process.env.ELECTRON_PORT || process.env.PORT;
|
|
17
13
|
const n = parseInt(raw || '', 10);
|
|
18
|
-
return Number.isFinite(n) && n > 0 && n < 65536 ? n :
|
|
14
|
+
return Number.isFinite(n) && n > 0 && n < 65536 ? n : DEFAULT_PORT;
|
|
19
15
|
}
|
|
20
|
-
|
|
21
|
-
//# sourceMappingURL=config.js.map
|
|
16
|
+
export const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
|
package/dist/electron/dialogs.js
CHANGED
|
@@ -1,61 +1,25 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.registerDialogIpc = registerDialogIpc;
|
|
37
1
|
/**
|
|
38
2
|
* 文件 dialog 桥 (open / save / dir) + 安全的 fs 桥 (read / write / exists)
|
|
39
3
|
*
|
|
40
4
|
* 5MB read 上限保护 — 渲染进程直接 fs.readFile 没法做限制, 走主进程就有界
|
|
41
5
|
* 所有 handler 解析 event.sender 拿到 window, 让 dialog 模态在该窗口上
|
|
42
6
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
7
|
+
import { BrowserWindow, dialog, ipcMain } from 'electron';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { log } from './logger';
|
|
47
11
|
const MAX_READ_BYTES = 5 * 1024 * 1024; // 5MB
|
|
48
12
|
function windowFor(event) {
|
|
49
|
-
return
|
|
13
|
+
return BrowserWindow.fromWebContents(event.sender);
|
|
50
14
|
}
|
|
51
15
|
function resolveSafe(target) {
|
|
52
16
|
// 不去硬限制路径 — user 给 renderer 暴露 fs 已经信任了, 这里只 normalize
|
|
53
17
|
return path.resolve(target);
|
|
54
18
|
}
|
|
55
|
-
function registerDialogIpc() {
|
|
56
|
-
|
|
19
|
+
export function registerDialogIpc() {
|
|
20
|
+
ipcMain.handle('dialog:open-file', async (event, opts = {}) => {
|
|
57
21
|
const win = windowFor(event);
|
|
58
|
-
const result = await
|
|
22
|
+
const result = await dialog.showOpenDialog(win, {
|
|
59
23
|
title: opts.title,
|
|
60
24
|
defaultPath: opts.defaultPath,
|
|
61
25
|
filters: opts.filters,
|
|
@@ -63,25 +27,25 @@ function registerDialogIpc() {
|
|
|
63
27
|
});
|
|
64
28
|
return { canceled: result.canceled, filePaths: result.filePaths };
|
|
65
29
|
});
|
|
66
|
-
|
|
30
|
+
ipcMain.handle('dialog:save-file', async (event, opts = {}) => {
|
|
67
31
|
const win = windowFor(event);
|
|
68
|
-
const result = await
|
|
32
|
+
const result = await dialog.showSaveDialog(win, {
|
|
69
33
|
title: opts.title,
|
|
70
34
|
defaultPath: opts.defaultPath,
|
|
71
35
|
filters: opts.filters,
|
|
72
36
|
});
|
|
73
37
|
return { canceled: result.canceled, filePath: result.filePath };
|
|
74
38
|
});
|
|
75
|
-
|
|
39
|
+
ipcMain.handle('dialog:open-directory', async (event, opts = {}) => {
|
|
76
40
|
const win = windowFor(event);
|
|
77
|
-
const result = await
|
|
41
|
+
const result = await dialog.showOpenDialog(win, {
|
|
78
42
|
title: opts.title,
|
|
79
43
|
defaultPath: opts.defaultPath,
|
|
80
44
|
properties: ['openDirectory', 'createDirectory'],
|
|
81
45
|
});
|
|
82
46
|
return { canceled: result.canceled, filePaths: result.filePaths };
|
|
83
47
|
});
|
|
84
|
-
|
|
48
|
+
ipcMain.handle('fs:read-text-file', async (_event, opts) => {
|
|
85
49
|
const target = resolveSafe(opts.path);
|
|
86
50
|
const stat = fs.statSync(target);
|
|
87
51
|
if (stat.size > MAX_READ_BYTES) {
|
|
@@ -89,12 +53,12 @@ function registerDialogIpc() {
|
|
|
89
53
|
}
|
|
90
54
|
return fs.readFileSync(target, { encoding: opts.encoding ?? 'utf8' });
|
|
91
55
|
});
|
|
92
|
-
|
|
56
|
+
ipcMain.handle('fs:write-text-file', async (_event, opts) => {
|
|
93
57
|
const target = resolveSafe(opts.path);
|
|
94
58
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
95
59
|
fs.writeFileSync(target, opts.content, { encoding: opts.encoding ?? 'utf8' });
|
|
96
60
|
});
|
|
97
|
-
|
|
61
|
+
ipcMain.handle('fs:path-exists', async (_event, opts) => {
|
|
98
62
|
try {
|
|
99
63
|
fs.accessSync(resolveSafe(opts.path));
|
|
100
64
|
return true;
|
|
@@ -103,6 +67,5 @@ function registerDialogIpc() {
|
|
|
103
67
|
return false;
|
|
104
68
|
}
|
|
105
69
|
});
|
|
106
|
-
|
|
70
|
+
log('dialog + fs IPC handlers registered');
|
|
107
71
|
}
|
|
108
|
-
//# sourceMappingURL=dialogs.js.map
|
|
@@ -1,54 +1,14 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.hasSeenFirstRun = hasSeenFirstRun;
|
|
37
|
-
exports.markFirstRunSeen = markFirstRunSeen;
|
|
38
|
-
exports.showFirstRunOverlay = showFirstRunOverlay;
|
|
39
|
-
exports.registerFirstRunIpc = registerFirstRunIpc;
|
|
40
|
-
exports.maybeShowFirstRun = maybeShowFirstRun;
|
|
41
1
|
/**
|
|
42
2
|
* 首启检测 + 引导浮层
|
|
43
3
|
*
|
|
44
4
|
* 标记文件写在 userData (不是 ~/.bolloon/), 卸载 app 自然清掉
|
|
45
5
|
* 引导窗是父主窗的 modal, frame=false, 透明背景; 关闭时标记写入
|
|
46
6
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
7
|
+
import { BrowserWindow, app, ipcMain } from 'electron';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { firstRunFlagPath, dataDir, logsDir } from './paths';
|
|
11
|
+
import { log } from './logger';
|
|
52
12
|
const OVERLAY_HTML = `
|
|
53
13
|
<!DOCTYPE html>
|
|
54
14
|
<html>
|
|
@@ -104,26 +64,26 @@ const OVERLAY_HTML = `
|
|
|
104
64
|
</body>
|
|
105
65
|
</html>
|
|
106
66
|
`;
|
|
107
|
-
function hasSeenFirstRun() {
|
|
67
|
+
export function hasSeenFirstRun() {
|
|
108
68
|
try {
|
|
109
|
-
return fs.existsSync(
|
|
69
|
+
return fs.existsSync(firstRunFlagPath());
|
|
110
70
|
}
|
|
111
71
|
catch {
|
|
112
72
|
return false;
|
|
113
73
|
}
|
|
114
74
|
}
|
|
115
|
-
function markFirstRunSeen() {
|
|
75
|
+
export function markFirstRunSeen() {
|
|
116
76
|
try {
|
|
117
|
-
fs.mkdirSync(path.dirname(
|
|
118
|
-
fs.writeFileSync(
|
|
77
|
+
fs.mkdirSync(path.dirname(firstRunFlagPath()), { recursive: true });
|
|
78
|
+
fs.writeFileSync(firstRunFlagPath(), new Date().toISOString());
|
|
119
79
|
}
|
|
120
80
|
catch (err) {
|
|
121
|
-
|
|
81
|
+
log(`写入首启标记失败: ${err.message}`, 'warn');
|
|
122
82
|
}
|
|
123
83
|
}
|
|
124
|
-
function showFirstRunOverlay(parent) {
|
|
84
|
+
export function showFirstRunOverlay(parent) {
|
|
125
85
|
return new Promise((resolve) => {
|
|
126
|
-
const overlay = new
|
|
86
|
+
const overlay = new BrowserWindow({
|
|
127
87
|
parent,
|
|
128
88
|
modal: true,
|
|
129
89
|
frame: false,
|
|
@@ -143,28 +103,27 @@ function showFirstRunOverlay(parent) {
|
|
|
143
103
|
markFirstRunSeen();
|
|
144
104
|
overlay.close();
|
|
145
105
|
};
|
|
146
|
-
|
|
106
|
+
ipcMain.once('first-run:ack', handler);
|
|
147
107
|
overlay.on('closed', () => {
|
|
148
|
-
|
|
108
|
+
ipcMain.removeListener('first-run:ack', handler);
|
|
149
109
|
resolve();
|
|
150
110
|
});
|
|
151
111
|
});
|
|
152
112
|
}
|
|
153
113
|
/** 注册 IPC handlers (给 preload 桥用) */
|
|
154
|
-
function registerFirstRunIpc() {
|
|
155
|
-
|
|
156
|
-
|
|
114
|
+
export function registerFirstRunIpc() {
|
|
115
|
+
ipcMain.handle('first-run:seen', () => hasSeenFirstRun());
|
|
116
|
+
ipcMain.handle('first-run:mark-seen', () => { markFirstRunSeen(); });
|
|
157
117
|
// 同步值 (不用 ipcRenderer.invoke 的 await) — 用 exposeInMainWorld 的 sync getter 更顺
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
118
|
+
ipcMain.handle('first-run:data-dir', () => dataDir());
|
|
119
|
+
ipcMain.handle('first-run:logs-dir', () => logsDir());
|
|
120
|
+
log('first-run IPC handlers registered');
|
|
161
121
|
}
|
|
162
122
|
/** 包装 — 决定要不要弹 overlay */
|
|
163
|
-
async function maybeShowFirstRun(parent) {
|
|
123
|
+
export async function maybeShowFirstRun(parent) {
|
|
164
124
|
if (hasSeenFirstRun())
|
|
165
125
|
return;
|
|
166
|
-
|
|
126
|
+
log('首启 — 弹出引导');
|
|
167
127
|
await showFirstRunOverlay(parent);
|
|
168
|
-
|
|
128
|
+
app.addRecentDocument(firstRunFlagPath()); // 跟踪最近文档, 让 user 知道有这文件
|
|
169
129
|
}
|
|
170
|
-
//# sourceMappingURL=first-run.js.map
|