@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.
Files changed (53) hide show
  1. package/dist/agents/pi-sdk-tools.js +107 -0
  2. package/dist/agents/pi-sdk.js +20 -2
  3. package/dist/bootstrap/context-collector.js +5 -1
  4. package/dist/cli/ink-app.js +46 -8
  5. package/dist/cli/loading-tui.js +7 -5
  6. package/dist/cli-entry.js +1 -1
  7. package/dist/electron/config.js +9 -14
  8. package/dist/electron/dialogs.js +16 -53
  9. package/dist/electron/first-run.js +24 -65
  10. package/dist/electron/ipc.js +10 -14
  11. package/dist/electron/logger.js +7 -44
  12. package/dist/electron/main.js +42 -45
  13. package/dist/electron/menu.js +13 -18
  14. package/dist/electron/paths.js +12 -54
  15. package/dist/electron/server.js +18 -57
  16. package/dist/electron/tray.js +15 -53
  17. package/dist/electron/window.js +22 -61
  18. package/dist/electron-build/electron/config.js +21 -0
  19. package/dist/electron-build/electron/config.js.map +1 -0
  20. package/dist/electron-build/electron/dialogs.js +108 -0
  21. package/dist/electron-build/electron/dialogs.js.map +1 -0
  22. package/dist/electron-build/electron/first-run.js +170 -0
  23. package/dist/electron-build/electron/first-run.js.map +1 -0
  24. package/dist/electron-build/electron/ipc.js +20 -0
  25. package/dist/electron-build/electron/ipc.js.map +1 -0
  26. package/dist/electron-build/electron/logger.js +114 -0
  27. package/dist/electron-build/electron/logger.js.map +1 -0
  28. package/dist/electron-build/electron/main.js +79 -0
  29. package/dist/electron-build/electron/main.js.map +1 -0
  30. package/dist/electron-build/electron/menu.js +145 -0
  31. package/dist/electron-build/electron/menu.js.map +1 -0
  32. package/dist/electron-build/electron/paths.js +75 -0
  33. package/dist/electron-build/electron/paths.js.map +1 -0
  34. package/dist/electron-build/electron/server.js +119 -0
  35. package/dist/electron-build/electron/server.js.map +1 -0
  36. package/dist/electron-build/electron/tray.js +111 -0
  37. package/dist/electron-build/electron/tray.js.map +1 -0
  38. package/dist/electron-build/electron/window.js +111 -0
  39. package/dist/electron-build/electron/window.js.map +1 -0
  40. package/dist/electron-build/electron-preload.js +32 -0
  41. package/dist/electron-build/electron-preload.js.map +1 -0
  42. package/dist/electron-build/electron.js +8 -0
  43. package/dist/electron-build/electron.js.map +1 -0
  44. package/dist/electron-build/utils/auto-update.js +606 -0
  45. package/dist/electron-build/utils/auto-update.js.map +1 -0
  46. package/dist/electron-preload.js +16 -19
  47. package/dist/electron.js +1 -4
  48. package/dist/index.js +134 -31
  49. package/dist/llm/config-store.js +18 -2
  50. package/dist/llm/pi-ai.js +29 -11
  51. package/dist/security/tool-gate.js +2 -0
  52. package/dist/utils/auto-update.js +19 -51
  53. 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)
@@ -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 } = require('../bootstrap/context-manager.js');
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 } = require('../context-compaction/index.js');
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
- const cfg = require(path.join(home, '.bolloon', 'llm-config.json'));
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
  }
@@ -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
- // Tab 命令补齐 (无触发符的普通 token 也补)
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
- const s = getStatusUpdate();
474
- if (s)
475
- setStatus(s);
507
+ try {
508
+ const s = getStatusUpdate();
509
+ if (s)
510
+ setStatus(s);
511
+ }
512
+ catch { /* 状态栏更新失败不致命 */ }
476
513
  }, 1000);
477
514
  return () => clearInterval(timer);
478
- }, [getStatusUpdate]);
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, // 关键: 阻止 Ink 劫持 console.log
552
+ patchConsole: false, // 重要: 阻止 Ink 劫持 console.log
515
553
  });
516
554
  }
517
555
  export function stopInk() {
@@ -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-06: 边框改 bolloon 色系 (C_BORDER #3a3a36 暗色描边, 标题色不变)
291
- lines.push(`${C_BORDER}${boxTop(`${color}${title}${RESET}`, width, RD)}${RESET}`);
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(`${C_BORDER}${boxRow(l, width, 'left', RD)}${RESET}`);
294
- lines.push(`${C_BORDER}${boxBottom(width, RD)}${RESET}`);
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/llm-config.json`);
360
+ console.log(` 配置已持久化: ~/.bolloon/bolloon-config.json`);
361
361
  }
362
362
  /** 引擎子命令: list / run */
363
363
  async function handleEngineCommand(engineArgs) {
@@ -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
- const electron_1 = require("electron");
9
- exports.DEFAULT_PORT = 54188;
4
+ import { app } from 'electron';
5
+ export const DEFAULT_PORT = 54188;
10
6
  /** Hard-pin to loopback; LAN exposure must be explicit. */
11
- exports.DEFAULT_HOST = '127.0.0.1';
12
- exports.WEB_SERVER_STARTUP_TIMEOUT_MS = 15_000;
13
- exports.MAIN_WINDOW_DEFAULT = { width: 1200, height: 800 };
14
- exports.MAIN_WINDOW_MIN = { width: 800, height: 600 };
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 : exports.DEFAULT_PORT;
14
+ return Number.isFinite(n) && n > 0 && n < 65536 ? n : DEFAULT_PORT;
19
15
  }
20
- exports.isDev = process.env.NODE_ENV === 'development' || !electron_1.app.isPackaged;
21
- //# sourceMappingURL=config.js.map
16
+ export const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
@@ -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
- const electron_1 = require("electron");
44
- const fs = __importStar(require("fs"));
45
- const path = __importStar(require("path"));
46
- const logger_1 = require("./logger");
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 electron_1.BrowserWindow.fromWebContents(event.sender);
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
- electron_1.ipcMain.handle('dialog:open-file', async (event, opts = {}) => {
19
+ export function registerDialogIpc() {
20
+ ipcMain.handle('dialog:open-file', async (event, opts = {}) => {
57
21
  const win = windowFor(event);
58
- const result = await electron_1.dialog.showOpenDialog(win, {
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
- electron_1.ipcMain.handle('dialog:save-file', async (event, opts = {}) => {
30
+ ipcMain.handle('dialog:save-file', async (event, opts = {}) => {
67
31
  const win = windowFor(event);
68
- const result = await electron_1.dialog.showSaveDialog(win, {
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
- electron_1.ipcMain.handle('dialog:open-directory', async (event, opts = {}) => {
39
+ ipcMain.handle('dialog:open-directory', async (event, opts = {}) => {
76
40
  const win = windowFor(event);
77
- const result = await electron_1.dialog.showOpenDialog(win, {
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
- electron_1.ipcMain.handle('fs:read-text-file', async (_event, opts) => {
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
- electron_1.ipcMain.handle('fs:write-text-file', async (_event, opts) => {
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
- electron_1.ipcMain.handle('fs:path-exists', async (_event, opts) => {
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
- (0, logger_1.log)('dialog + fs IPC handlers registered');
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
- const electron_1 = require("electron");
48
- const fs = __importStar(require("fs"));
49
- const path = __importStar(require("path"));
50
- const paths_1 = require("./paths");
51
- const logger_1 = require("./logger");
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((0, paths_1.firstRunFlagPath)());
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((0, paths_1.firstRunFlagPath)()), { recursive: true });
118
- fs.writeFileSync((0, paths_1.firstRunFlagPath)(), new Date().toISOString());
77
+ fs.mkdirSync(path.dirname(firstRunFlagPath()), { recursive: true });
78
+ fs.writeFileSync(firstRunFlagPath(), new Date().toISOString());
119
79
  }
120
80
  catch (err) {
121
- (0, logger_1.log)(`写入首启标记失败: ${err.message}`, 'warn');
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 electron_1.BrowserWindow({
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
- electron_1.ipcMain.once('first-run:ack', handler);
106
+ ipcMain.once('first-run:ack', handler);
147
107
  overlay.on('closed', () => {
148
- electron_1.ipcMain.removeListener('first-run:ack', handler);
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
- electron_1.ipcMain.handle('first-run:seen', () => hasSeenFirstRun());
156
- electron_1.ipcMain.handle('first-run:mark-seen', () => { markFirstRunSeen(); });
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
- electron_1.ipcMain.handle('first-run:data-dir', () => (0, paths_1.dataDir)());
159
- electron_1.ipcMain.handle('first-run:logs-dir', () => (0, paths_1.logsDir)());
160
- (0, logger_1.log)('first-run IPC handlers registered');
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
- (0, logger_1.log)('首启 — 弹出引导');
126
+ log('首启 — 弹出引导');
167
127
  await showFirstRunOverlay(parent);
168
- electron_1.app.addRecentDocument((0, paths_1.firstRunFlagPath)()); // 跟踪最近文档, 让 user 知道有这文件
128
+ app.addRecentDocument(firstRunFlagPath()); // 跟踪最近文档, 让 user 知道有这文件
169
129
  }
170
- //# sourceMappingURL=first-run.js.map