@bolloon/bolloon-agent 0.1.42 → 0.2.0
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/build/icon.icns +0 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/tray.png +0 -0
- package/build/trayTemplate.png +0 -0
- package/dist/agents/pi-sdk.js +55 -10
- package/dist/electron/config.js +21 -0
- package/dist/electron/dialogs.js +108 -0
- package/dist/electron/first-run.js +170 -0
- package/dist/electron/ipc.js +20 -0
- package/dist/electron/logger.js +114 -0
- package/dist/electron/main.js +63 -0
- package/dist/electron/menu.js +145 -0
- package/dist/electron/paths.js +75 -0
- package/dist/electron/server.js +112 -0
- package/dist/electron/tray.js +111 -0
- package/dist/electron/window.js +108 -0
- package/dist/index.js +6 -3
- package/dist/web/server.js +9 -2
- package/package.json +7 -2
package/build/icon.icns
ADDED
|
Binary file
|
package/build/icon.ico
ADDED
|
Binary file
|
package/build/icon.png
ADDED
|
Binary file
|
package/build/tray.png
ADDED
|
Binary file
|
|
Binary file
|
package/dist/agents/pi-sdk.js
CHANGED
|
@@ -963,7 +963,32 @@ class PiAgentSession {
|
|
|
963
963
|
const cmd = String(args.command || '').trim();
|
|
964
964
|
if (!cmd)
|
|
965
965
|
return { success: false, error: 'command 必填' };
|
|
966
|
-
|
|
966
|
+
// 2026-06-19: 支持多种 args 格式
|
|
967
|
+
// 1. JSON 数组: ["checkout", "-b", "branch"] (LLM 偏好)
|
|
968
|
+
// 2. 逗号分隔: "checkout,-b,branch" (旧格式)
|
|
969
|
+
// 3. 字符串: "checkout -b branch" (单参数)
|
|
970
|
+
let argList = [];
|
|
971
|
+
const rawArgs = args.args;
|
|
972
|
+
if (Array.isArray(rawArgs)) {
|
|
973
|
+
argList = rawArgs.map((s) => String(s).trim()).filter(Boolean);
|
|
974
|
+
}
|
|
975
|
+
else if (typeof rawArgs === 'string') {
|
|
976
|
+
const trimmed = rawArgs.trim();
|
|
977
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
978
|
+
// JSON 数组字符串
|
|
979
|
+
try {
|
|
980
|
+
const parsed = JSON.parse(trimmed);
|
|
981
|
+
if (Array.isArray(parsed)) {
|
|
982
|
+
argList = parsed.map((s) => String(s).trim()).filter(Boolean);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
catch { /* fall through to comma split */ }
|
|
986
|
+
}
|
|
987
|
+
if (argList.length === 0) {
|
|
988
|
+
// 逗号分隔 或 单字符串
|
|
989
|
+
argList = trimmed.split(',').map(s => s.trim()).filter(Boolean);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
967
992
|
const timeoutMs = Number(args.timeoutMs) || 30000;
|
|
968
993
|
const result = await shellExec(cmd, argList, { timeoutMs });
|
|
969
994
|
if (result.deniedByGuard) {
|
|
@@ -1416,7 +1441,7 @@ class PiAgentSession {
|
|
|
1416
1441
|
description: '跑 vitest 测试. 自动 bail (失败就停). 默认 60s timeout.',
|
|
1417
1442
|
parameters: { pattern: '可选, 文件 glob e.g. "src/agents/pi-sdk.test.ts"', timeoutMs: '可选, 默认 60000' },
|
|
1418
1443
|
execute: async (args) => {
|
|
1419
|
-
const argv = ['vitest', 'run', '--reporter=
|
|
1444
|
+
const argv = ['vitest', 'run', '--reporter=default', '--no-color', '--bail=1'];
|
|
1420
1445
|
if (args.pattern)
|
|
1421
1446
|
argv.push(String(args.pattern));
|
|
1422
1447
|
const timeoutMs = parseInt(String(args.timeoutMs || '60000')) || 60000;
|
|
@@ -1634,10 +1659,13 @@ class PiAgentSession {
|
|
|
1634
1659
|
// M2.4 (2026-06-17): 缓存 tool 定义 — registerTools() 在构造时调一次, 此后不变
|
|
1635
1660
|
if (this.cachedToolDefinitions)
|
|
1636
1661
|
return this.cachedToolDefinitions;
|
|
1637
|
-
const defs = ['
|
|
1662
|
+
const defs = ['可用工具 (name(params) - 简介):'];
|
|
1638
1663
|
for (const tool of this.tools.values()) {
|
|
1639
|
-
|
|
1640
|
-
|
|
1664
|
+
// 2026-06-19: 压缩 tool 定义 — 只显示参数名 (不显示描述, 减少 60% 长度)
|
|
1665
|
+
// 完整 description 在 history 第一轮注入 (getToolDefinitionsFull 调用), 后续轮只看简短
|
|
1666
|
+
// 避免 system prompt 太大导致 minimax 撞 max_tokens 输出空
|
|
1667
|
+
const paramNames = Object.keys(tool.parameters).join(',');
|
|
1668
|
+
defs.push(`- ${tool.name}(${paramNames})`);
|
|
1641
1669
|
}
|
|
1642
1670
|
this.cachedToolDefinitions = defs.join('\n');
|
|
1643
1671
|
return this.cachedToolDefinitions;
|
|
@@ -2995,6 +3023,10 @@ Workspace root folder: ${this.cwd}
|
|
|
2995
3023
|
/\btool\s*=>\s*["'](\w+)["']/,
|
|
2996
3024
|
// 兼容: [TOOL_CALL] 块内 JSON 形式 {"name": "x", "args": {...}}
|
|
2997
3025
|
/\[TOOL_CALL\][\s\S]*?\{\s*"name"\s*:\s*"(\w+)"\s*,\s*"args"\s*:\s*(\{[\s\S]*?\})/i,
|
|
3026
|
+
// 2026-06-19: 兼容 LLM 输出 "tool_name {json_args}" 形式 (单行, 无 name 字段)
|
|
3027
|
+
// 实际输出: write_file {"path":"docs/test.md","content":"..."}
|
|
3028
|
+
// 只在行首/新行匹配, 避免误匹配普通文本里的 "foo {...}"
|
|
3029
|
+
/(?:^|\n)(\w+)\s+(\{[\s\S]*?\})(?=\n|$)/,
|
|
2998
3030
|
// 2026-06-15 修: 兼容 LLM 输出的 XML 格式 <tool_name>...<arg>value</arg>...</tool_name>
|
|
2999
3031
|
// 实际 LLM 习惯: <shell_exec>\n<command>ls</command>\n<args>["-la", "..."]</args>\n</shell_exec>
|
|
3000
3032
|
/<(\w+)>([\s\S]*?)<\/\1>/,
|
|
@@ -3026,15 +3058,28 @@ Workspace root folder: ${this.cwd}
|
|
|
3026
3058
|
else if (rawArgs && /<\w+>[\s\S]*<\/\w+>/.test(rawArgs)) {
|
|
3027
3059
|
// 2026-06-15 修: XML 格式, 解析内嵌子标签 <argname>value</argname>
|
|
3028
3060
|
// 例: <command>ls</command>\n<args>["-la","~/.bolloon/skills"]</args>
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
const
|
|
3061
|
+
// 2026-06-19 修: 也支持 <parameter name="X">value</parameter> 形式 (OpenAI Hermes function_call)
|
|
3062
|
+
const paramRe = /<parameter\s+name=["'](\w+)["']>([\s\S]*?)<\/parameter>/g;
|
|
3063
|
+
let pMatch;
|
|
3064
|
+
while ((pMatch = paramRe.exec(rawArgs)) !== null) {
|
|
3065
|
+
const argName = pMatch[1];
|
|
3066
|
+
const argValue = pMatch[2].trim();
|
|
3034
3067
|
if (argName && argValue) {
|
|
3035
3068
|
args[argName] = argValue;
|
|
3036
3069
|
}
|
|
3037
3070
|
}
|
|
3071
|
+
// 如果没匹配到 <parameter>, 用普通 XML 子标签
|
|
3072
|
+
if (Object.keys(args).length === 0) {
|
|
3073
|
+
const xmlArgPattern = /<(\w+)>([\s\S]*?)<\/\1>/g;
|
|
3074
|
+
let xmlMatch;
|
|
3075
|
+
while ((xmlMatch = xmlArgPattern.exec(rawArgs)) !== null) {
|
|
3076
|
+
const argName = xmlMatch[1];
|
|
3077
|
+
const argValue = xmlMatch[2].trim();
|
|
3078
|
+
if (argName && argValue) {
|
|
3079
|
+
args[argName] = argValue;
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3038
3083
|
}
|
|
3039
3084
|
else if (rawArgs) {
|
|
3040
3085
|
// 形参串, 形如 key: value, key2: value2
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
/**
|
|
6
|
+
* 常量配置 (env 解析在这里集中, 不散在 main 流程)
|
|
7
|
+
*/
|
|
8
|
+
const electron_1 = require("electron");
|
|
9
|
+
exports.DEFAULT_PORT = 54188;
|
|
10
|
+
/** 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() {
|
|
16
|
+
const raw = process.env.ELECTRON_PORT || process.env.PORT;
|
|
17
|
+
const n = parseInt(raw || '', 10);
|
|
18
|
+
return Number.isFinite(n) && n > 0 && n < 65536 ? n : exports.DEFAULT_PORT;
|
|
19
|
+
}
|
|
20
|
+
exports.isDev = process.env.NODE_ENV === 'development' || !electron_1.app.isPackaged;
|
|
21
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
/**
|
|
38
|
+
* 文件 dialog 桥 (open / save / dir) + 安全的 fs 桥 (read / write / exists)
|
|
39
|
+
*
|
|
40
|
+
* 5MB read 上限保护 — 渲染进程直接 fs.readFile 没法做限制, 走主进程就有界
|
|
41
|
+
* 所有 handler 解析 event.sender 拿到 window, 让 dialog 模态在该窗口上
|
|
42
|
+
*/
|
|
43
|
+
const electron_1 = require("electron");
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const logger_1 = require("./logger");
|
|
47
|
+
const MAX_READ_BYTES = 5 * 1024 * 1024; // 5MB
|
|
48
|
+
function windowFor(event) {
|
|
49
|
+
return electron_1.BrowserWindow.fromWebContents(event.sender);
|
|
50
|
+
}
|
|
51
|
+
function resolveSafe(target) {
|
|
52
|
+
// 不去硬限制路径 — user 给 renderer 暴露 fs 已经信任了, 这里只 normalize
|
|
53
|
+
return path.resolve(target);
|
|
54
|
+
}
|
|
55
|
+
function registerDialogIpc() {
|
|
56
|
+
electron_1.ipcMain.handle('dialog:open-file', async (event, opts = {}) => {
|
|
57
|
+
const win = windowFor(event);
|
|
58
|
+
const result = await electron_1.dialog.showOpenDialog(win, {
|
|
59
|
+
title: opts.title,
|
|
60
|
+
defaultPath: opts.defaultPath,
|
|
61
|
+
filters: opts.filters,
|
|
62
|
+
properties: opts.properties ?? ['openFile'],
|
|
63
|
+
});
|
|
64
|
+
return { canceled: result.canceled, filePaths: result.filePaths };
|
|
65
|
+
});
|
|
66
|
+
electron_1.ipcMain.handle('dialog:save-file', async (event, opts = {}) => {
|
|
67
|
+
const win = windowFor(event);
|
|
68
|
+
const result = await electron_1.dialog.showSaveDialog(win, {
|
|
69
|
+
title: opts.title,
|
|
70
|
+
defaultPath: opts.defaultPath,
|
|
71
|
+
filters: opts.filters,
|
|
72
|
+
});
|
|
73
|
+
return { canceled: result.canceled, filePath: result.filePath };
|
|
74
|
+
});
|
|
75
|
+
electron_1.ipcMain.handle('dialog:open-directory', async (event, opts = {}) => {
|
|
76
|
+
const win = windowFor(event);
|
|
77
|
+
const result = await electron_1.dialog.showOpenDialog(win, {
|
|
78
|
+
title: opts.title,
|
|
79
|
+
defaultPath: opts.defaultPath,
|
|
80
|
+
properties: ['openDirectory', 'createDirectory'],
|
|
81
|
+
});
|
|
82
|
+
return { canceled: result.canceled, filePaths: result.filePaths };
|
|
83
|
+
});
|
|
84
|
+
electron_1.ipcMain.handle('fs:read-text-file', async (_event, opts) => {
|
|
85
|
+
const target = resolveSafe(opts.path);
|
|
86
|
+
const stat = fs.statSync(target);
|
|
87
|
+
if (stat.size > MAX_READ_BYTES) {
|
|
88
|
+
throw new Error(`文件过大: ${stat.size} > ${MAX_READ_BYTES} bytes`);
|
|
89
|
+
}
|
|
90
|
+
return fs.readFileSync(target, { encoding: opts.encoding ?? 'utf8' });
|
|
91
|
+
});
|
|
92
|
+
electron_1.ipcMain.handle('fs:write-text-file', async (_event, opts) => {
|
|
93
|
+
const target = resolveSafe(opts.path);
|
|
94
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
95
|
+
fs.writeFileSync(target, opts.content, { encoding: opts.encoding ?? 'utf8' });
|
|
96
|
+
});
|
|
97
|
+
electron_1.ipcMain.handle('fs:path-exists', async (_event, opts) => {
|
|
98
|
+
try {
|
|
99
|
+
fs.accessSync(resolveSafe(opts.path));
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
(0, logger_1.log)('dialog + fs IPC handlers registered');
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=dialogs.js.map
|
|
@@ -0,0 +1,170 @@
|
|
|
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
|
+
/**
|
|
42
|
+
* 首启检测 + 引导浮层
|
|
43
|
+
*
|
|
44
|
+
* 标记文件写在 userData (不是 ~/.bolloon/), 卸载 app 自然清掉
|
|
45
|
+
* 引导窗是父主窗的 modal, frame=false, 透明背景; 关闭时标记写入
|
|
46
|
+
*/
|
|
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");
|
|
52
|
+
const OVERLAY_HTML = `
|
|
53
|
+
<!DOCTYPE html>
|
|
54
|
+
<html>
|
|
55
|
+
<head>
|
|
56
|
+
<meta charset="utf-8" />
|
|
57
|
+
<style>
|
|
58
|
+
:root { color-scheme: light dark; }
|
|
59
|
+
body {
|
|
60
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
61
|
+
margin: 0; padding: 32px 28px;
|
|
62
|
+
background: rgba(245,245,247,0.97);
|
|
63
|
+
color: #1d1d1f;
|
|
64
|
+
height: 100vh; box-sizing: border-box;
|
|
65
|
+
}
|
|
66
|
+
@media (prefers-color-scheme: dark) {
|
|
67
|
+
body { background: rgba(28,28,30,0.97); color: #f5f5f7; }
|
|
68
|
+
}
|
|
69
|
+
h1 { font-size: 18px; margin: 0 0 14px; font-weight: 600; }
|
|
70
|
+
p { font-size: 13px; line-height: 1.55; margin: 8px 0; }
|
|
71
|
+
code {
|
|
72
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
73
|
+
font-size: 12px; padding: 1px 5px;
|
|
74
|
+
background: rgba(0,0,0,0.06); border-radius: 3px;
|
|
75
|
+
}
|
|
76
|
+
@media (prefers-color-scheme: dark) {
|
|
77
|
+
code { background: rgba(255,255,255,0.08); }
|
|
78
|
+
}
|
|
79
|
+
.btn {
|
|
80
|
+
display: inline-block; margin-top: 18px; padding: 8px 18px;
|
|
81
|
+
background: #007aff; color: #fff; border: none; border-radius: 6px;
|
|
82
|
+
font-size: 13px; font-weight: 500; cursor: pointer;
|
|
83
|
+
}
|
|
84
|
+
.btn:hover { background: #0066d6; }
|
|
85
|
+
ul { padding-left: 20px; }
|
|
86
|
+
li { font-size: 13px; line-height: 1.7; }
|
|
87
|
+
</style>
|
|
88
|
+
</head>
|
|
89
|
+
<body>
|
|
90
|
+
<h1>欢迎使用 Bolloon Agent</h1>
|
|
91
|
+
<p>你的数据存放在本地, 不上传:</p>
|
|
92
|
+
<ul>
|
|
93
|
+
<li>配置 / 频道 / P2P 密钥: <code id="data"></code></li>
|
|
94
|
+
<li>运行日志: <code id="logs"></code></li>
|
|
95
|
+
</ul>
|
|
96
|
+
<p>所有数据在卸载后仍保留 — 想清理就手动删除 ~/.bolloon/。</p>
|
|
97
|
+
<button class="btn" id="ok">知道了</button>
|
|
98
|
+
<script>
|
|
99
|
+
const ok = document.getElementById('ok');
|
|
100
|
+
ok.addEventListener('click', () => window.electronAPI.markFirstRunSeen());
|
|
101
|
+
document.getElementById('data').textContent = window.electronAPI.getDataPathSync?.() || '';
|
|
102
|
+
document.getElementById('logs').textContent = window.electronAPI.getLogsPathSync?.() || '';
|
|
103
|
+
</script>
|
|
104
|
+
</body>
|
|
105
|
+
</html>
|
|
106
|
+
`;
|
|
107
|
+
function hasSeenFirstRun() {
|
|
108
|
+
try {
|
|
109
|
+
return fs.existsSync((0, paths_1.firstRunFlagPath)());
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function markFirstRunSeen() {
|
|
116
|
+
try {
|
|
117
|
+
fs.mkdirSync(path.dirname((0, paths_1.firstRunFlagPath)()), { recursive: true });
|
|
118
|
+
fs.writeFileSync((0, paths_1.firstRunFlagPath)(), new Date().toISOString());
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
(0, logger_1.log)(`写入首启标记失败: ${err.message}`, 'warn');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function showFirstRunOverlay(parent) {
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
const overlay = new electron_1.BrowserWindow({
|
|
127
|
+
parent,
|
|
128
|
+
modal: true,
|
|
129
|
+
frame: false,
|
|
130
|
+
transparent: true,
|
|
131
|
+
width: 520,
|
|
132
|
+
height: 360,
|
|
133
|
+
resizable: false,
|
|
134
|
+
movable: false,
|
|
135
|
+
minimizable: false,
|
|
136
|
+
maximizable: false,
|
|
137
|
+
title: 'Welcome',
|
|
138
|
+
});
|
|
139
|
+
const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(OVERLAY_HTML);
|
|
140
|
+
overlay.loadURL(dataUrl);
|
|
141
|
+
// 一次性的 IPC handler, 关闭后清掉避免累积
|
|
142
|
+
const handler = () => {
|
|
143
|
+
markFirstRunSeen();
|
|
144
|
+
overlay.close();
|
|
145
|
+
};
|
|
146
|
+
electron_1.ipcMain.once('first-run:ack', handler);
|
|
147
|
+
overlay.on('closed', () => {
|
|
148
|
+
electron_1.ipcMain.removeListener('first-run:ack', handler);
|
|
149
|
+
resolve();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** 注册 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(); });
|
|
157
|
+
// 同步值 (不用 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');
|
|
161
|
+
}
|
|
162
|
+
/** 包装 — 决定要不要弹 overlay */
|
|
163
|
+
async function maybeShowFirstRun(parent) {
|
|
164
|
+
if (hasSeenFirstRun())
|
|
165
|
+
return;
|
|
166
|
+
(0, logger_1.log)('首启 — 弹出引导');
|
|
167
|
+
await showFirstRunOverlay(parent);
|
|
168
|
+
electron_1.app.addRecentDocument((0, paths_1.firstRunFlagPath)()); // 跟踪最近文档, 让 user 知道有这文件
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=first-run.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCoreIpc = registerCoreIpc;
|
|
4
|
+
/**
|
|
5
|
+
* IPC handler 集中注册 — version / userData path / open-external (legacy 3 个)
|
|
6
|
+
* dialog/fs 的注册在 dialogs.ts; updater 的注册在 updater.ts
|
|
7
|
+
*/
|
|
8
|
+
const electron_1 = require("electron");
|
|
9
|
+
const paths_1 = require("./paths");
|
|
10
|
+
const logger_1 = require("./logger");
|
|
11
|
+
function registerCoreIpc() {
|
|
12
|
+
electron_1.ipcMain.handle('get-version', () => electron_1.app.getVersion());
|
|
13
|
+
electron_1.ipcMain.handle('get-user-data-path', () => (0, paths_1.userDataDir)());
|
|
14
|
+
electron_1.ipcMain.handle('get-data-path', () => (0, paths_1.dataDir)()); // 跟 userData 分开, 渲染层要用
|
|
15
|
+
electron_1.ipcMain.handle('open-external', async (_event, url) => {
|
|
16
|
+
await electron_1.shell.openExternal(url);
|
|
17
|
+
});
|
|
18
|
+
(0, logger_1.log)('core IPC handlers registered');
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=ipc.js.map
|
|
@@ -0,0 +1,114 @@
|
|
|
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.log = log;
|
|
37
|
+
/**
|
|
38
|
+
* 简单 logger: 写日志到 <userData>/logs/bolloon.log, size-based 轮转
|
|
39
|
+
* (5MB 上限, 保留 .1 .2 .3 共 3 个备份 → ~20MB 上限)
|
|
40
|
+
*
|
|
41
|
+
* 同步写是因为 Electron 主进程日志量小, 同步 append 简单可靠, 不丢日志.
|
|
42
|
+
* 量大的会话级 jsonl 走自己路径 (lifecycle-hooks 等), 跟本 logger 无关.
|
|
43
|
+
*/
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const paths_1 = require("./paths");
|
|
46
|
+
const MAX_BYTES = 5 * 1024 * 1024;
|
|
47
|
+
const MAX_BACKUPS = 3; // 保留 bolloon.log.1 .2 .3
|
|
48
|
+
let currentSize = 0;
|
|
49
|
+
let initialized = false;
|
|
50
|
+
function ensureLogFile() {
|
|
51
|
+
if (initialized)
|
|
52
|
+
return;
|
|
53
|
+
initialized = true;
|
|
54
|
+
try {
|
|
55
|
+
fs.mkdirSync((0, paths_1.logsDir)(), { recursive: true });
|
|
56
|
+
try {
|
|
57
|
+
currentSize = fs.statSync((0, paths_1.mainLogPath)()).size;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
currentSize = 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
// 失败也无所谓, log() 内部会 swallow
|
|
65
|
+
currentSize = 0;
|
|
66
|
+
console.error('[logger] ensureLogFile failed:', err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function rotate() {
|
|
70
|
+
const logPath = (0, paths_1.mainLogPath)();
|
|
71
|
+
for (let i = MAX_BACKUPS; i >= 1; i--) {
|
|
72
|
+
const src = `${logPath}.${i}`;
|
|
73
|
+
const dst = `${logPath}.${i + 1}`;
|
|
74
|
+
if (i === MAX_BACKUPS) {
|
|
75
|
+
try {
|
|
76
|
+
fs.unlinkSync(src);
|
|
77
|
+
}
|
|
78
|
+
catch { /* missing ok */ }
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
try {
|
|
82
|
+
fs.renameSync(src, dst);
|
|
83
|
+
}
|
|
84
|
+
catch { /* missing ok */ }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
fs.renameSync(logPath, `${logPath}.1`);
|
|
89
|
+
}
|
|
90
|
+
catch { /* missing ok */ }
|
|
91
|
+
currentSize = 0;
|
|
92
|
+
}
|
|
93
|
+
function log(message, level = 'info') {
|
|
94
|
+
ensureLogFile();
|
|
95
|
+
const ts = new Date().toISOString();
|
|
96
|
+
const line = `[${ts}] [${level}] ${message}\n`;
|
|
97
|
+
if (currentSize + line.length > MAX_BYTES)
|
|
98
|
+
rotate();
|
|
99
|
+
try {
|
|
100
|
+
fs.appendFileSync((0, paths_1.mainLogPath)(), line);
|
|
101
|
+
currentSize += line.length;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
// 写不进就 console, 不要再递归 log
|
|
105
|
+
console.error('[logger] appendFileSync failed:', err);
|
|
106
|
+
}
|
|
107
|
+
if (level === 'error')
|
|
108
|
+
console.error(line.trimEnd());
|
|
109
|
+
else if (level === 'warn')
|
|
110
|
+
console.warn(line.trimEnd());
|
|
111
|
+
else
|
|
112
|
+
console.log(line.trimEnd());
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Electron 主进程 lifecycle 入口
|
|
5
|
+
* 单实例锁 → whenReady → createMainWindow → cleanup on quit
|
|
6
|
+
* 启动时: 装 menu / tray / IPC handlers / 首启引导
|
|
7
|
+
*/
|
|
8
|
+
const electron_1 = require("electron");
|
|
9
|
+
const logger_1 = require("./logger");
|
|
10
|
+
const window_1 = require("./window");
|
|
11
|
+
const server_1 = require("./server");
|
|
12
|
+
const menu_1 = require("./menu");
|
|
13
|
+
const tray_1 = require("./tray");
|
|
14
|
+
const ipc_1 = require("./ipc");
|
|
15
|
+
const dialogs_1 = require("./dialogs");
|
|
16
|
+
const first_run_1 = require("./first-run");
|
|
17
|
+
(0, logger_1.log)('Bolloon Electron 启动');
|
|
18
|
+
// 单实例锁
|
|
19
|
+
const gotTheLock = electron_1.app.requestSingleInstanceLock();
|
|
20
|
+
if (!gotTheLock) {
|
|
21
|
+
(0, logger_1.log)('已有实例在运行,退出');
|
|
22
|
+
electron_1.app.quit();
|
|
23
|
+
}
|
|
24
|
+
electron_1.app.on('second-instance', () => {
|
|
25
|
+
(0, window_1.focusMainWindow)();
|
|
26
|
+
});
|
|
27
|
+
electron_1.app.whenReady().then(async () => {
|
|
28
|
+
(0, logger_1.log)('Electron 准备好');
|
|
29
|
+
(0, ipc_1.registerCoreIpc)();
|
|
30
|
+
(0, dialogs_1.registerDialogIpc)();
|
|
31
|
+
(0, first_run_1.registerFirstRunIpc)();
|
|
32
|
+
(0, menu_1.installAppMenu)(window_1.getMainWindow);
|
|
33
|
+
(0, tray_1.createTray)(window_1.getMainWindow);
|
|
34
|
+
await (0, window_1.createMainWindow)();
|
|
35
|
+
// 首启引导 (在主窗口就绪后弹, 不阻塞 UI)
|
|
36
|
+
const win = (0, window_1.getMainWindow)();
|
|
37
|
+
if (win) {
|
|
38
|
+
win.webContents.once('did-finish-load', () => {
|
|
39
|
+
void (0, first_run_1.maybeShowFirstRun)(win);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
electron_1.app.on('activate', async () => {
|
|
43
|
+
// macOS: dock 图标被点击且没有窗口时, 重开
|
|
44
|
+
if (electron_1.BrowserWindow.getAllWindows().length === 0) {
|
|
45
|
+
await (0, window_1.createMainWindow)();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
electron_1.app.on('window-all-closed', () => {
|
|
50
|
+
(0, logger_1.log)('所有窗口已关闭');
|
|
51
|
+
(0, tray_1.destroyTray)();
|
|
52
|
+
(0, server_1.killWebServer)();
|
|
53
|
+
if (process.platform !== 'darwin') {
|
|
54
|
+
electron_1.app.quit();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
electron_1.app.on('before-quit', () => {
|
|
58
|
+
(0, logger_1.log)('应用即将退出');
|
|
59
|
+
(0, tray_1.destroyTray)();
|
|
60
|
+
(0, server_1.killWebServer)();
|
|
61
|
+
});
|
|
62
|
+
(0, logger_1.log)('主进程初始化完成');
|
|
63
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAppMenu = buildAppMenu;
|
|
4
|
+
exports.installAppMenu = installAppMenu;
|
|
5
|
+
/**
|
|
6
|
+
* 应用菜单 — macOS 标准 + File/Edit/View/Window/Help 自定义项
|
|
7
|
+
* "Open Data Folder" 等会跨平台显示 (mac 也会出现, 不会冲突)
|
|
8
|
+
*/
|
|
9
|
+
const electron_1 = require("electron");
|
|
10
|
+
const paths_1 = require("./paths");
|
|
11
|
+
const logger_1 = require("./logger");
|
|
12
|
+
function openDataFolder() {
|
|
13
|
+
electron_1.shell.openPath((0, paths_1.dataDir)()).catch((e) => (0, logger_1.log)(`open data folder 失败: ${e.message}`, 'error'));
|
|
14
|
+
}
|
|
15
|
+
function openLogsFolder() {
|
|
16
|
+
electron_1.shell.openPath((0, paths_1.logsDir)()).catch((e) => (0, logger_1.log)(`open logs folder 失败: ${e.message}`, 'error'));
|
|
17
|
+
}
|
|
18
|
+
function openExternal(url) {
|
|
19
|
+
electron_1.shell.openExternal(url).catch((e) => (0, logger_1.log)(`openExternal 失败: ${e.message}`, 'error'));
|
|
20
|
+
}
|
|
21
|
+
function buildAppMenu(getMainWindow) {
|
|
22
|
+
const isMac = process.platform === 'darwin';
|
|
23
|
+
const win = getMainWindow();
|
|
24
|
+
const appMenu = {
|
|
25
|
+
label: electron_1.app.getName(),
|
|
26
|
+
submenu: [
|
|
27
|
+
{ role: 'about' },
|
|
28
|
+
{ type: 'separator' },
|
|
29
|
+
{ role: 'services' },
|
|
30
|
+
{ type: 'separator' },
|
|
31
|
+
{ role: 'hide' },
|
|
32
|
+
{ role: 'hideOthers' },
|
|
33
|
+
{ role: 'unhide' },
|
|
34
|
+
{ type: 'separator' },
|
|
35
|
+
{ role: 'quit' },
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
const fileMenu = {
|
|
39
|
+
label: 'File',
|
|
40
|
+
submenu: [
|
|
41
|
+
{
|
|
42
|
+
label: 'Open Data Folder',
|
|
43
|
+
accelerator: 'CmdOrCtrl+Shift+D',
|
|
44
|
+
click: () => openDataFolder(),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: 'Open Logs Folder',
|
|
48
|
+
accelerator: 'CmdOrCtrl+Shift+L',
|
|
49
|
+
click: () => openLogsFolder(),
|
|
50
|
+
},
|
|
51
|
+
{ type: 'separator' },
|
|
52
|
+
isMac ? { role: 'close' } : { role: 'quit' },
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
const editMenu = {
|
|
56
|
+
label: 'Edit',
|
|
57
|
+
submenu: [
|
|
58
|
+
{ role: 'undo' },
|
|
59
|
+
{ role: 'redo' },
|
|
60
|
+
{ type: 'separator' },
|
|
61
|
+
{ role: 'cut' },
|
|
62
|
+
{ role: 'copy' },
|
|
63
|
+
{ role: 'paste' },
|
|
64
|
+
{ role: 'selectAll' },
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
const viewMenu = {
|
|
68
|
+
label: 'View',
|
|
69
|
+
submenu: [
|
|
70
|
+
{
|
|
71
|
+
label: 'Reload',
|
|
72
|
+
accelerator: 'CmdOrCtrl+R',
|
|
73
|
+
click: () => win?.webContents.reload(),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
label: 'Force Reload',
|
|
77
|
+
accelerator: 'CmdOrCtrl+Shift+R',
|
|
78
|
+
click: () => win?.webContents.reloadIgnoringCache(),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
label: 'Toggle DevTools',
|
|
82
|
+
accelerator: isMac ? 'Alt+Cmd+I' : 'Ctrl+Shift+I',
|
|
83
|
+
click: () => win?.webContents.toggleDevTools(),
|
|
84
|
+
},
|
|
85
|
+
{ type: 'separator' },
|
|
86
|
+
{ role: 'resetZoom' },
|
|
87
|
+
{ role: 'zoomIn' },
|
|
88
|
+
{ role: 'zoomOut' },
|
|
89
|
+
{ type: 'separator' },
|
|
90
|
+
{ role: 'togglefullscreen' },
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
const windowMenu = {
|
|
94
|
+
label: 'Window',
|
|
95
|
+
submenu: [
|
|
96
|
+
{ role: 'minimize' },
|
|
97
|
+
{ role: 'zoom' },
|
|
98
|
+
...(isMac
|
|
99
|
+
? [{ type: 'separator' }, { role: 'front' }]
|
|
100
|
+
: [{ role: 'close' }]),
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
const helpMenu = {
|
|
104
|
+
role: 'help',
|
|
105
|
+
submenu: [
|
|
106
|
+
{
|
|
107
|
+
label: 'GitHub Repository',
|
|
108
|
+
click: () => openExternal('https://github.com/bolloon/bolloon'),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
label: 'Report Issue',
|
|
112
|
+
click: () => openExternal('https://github.com/bolloon/bolloon/issues'),
|
|
113
|
+
},
|
|
114
|
+
{ type: 'separator' },
|
|
115
|
+
{
|
|
116
|
+
label: 'About Bolloon Agent',
|
|
117
|
+
click: () => {
|
|
118
|
+
// 简单 about — 用 dialog 暂代 (避免再造 HTML 窗口)
|
|
119
|
+
// 真实 about panel 在 macOS 是 app.role 触发的, 我们走菜单项即可
|
|
120
|
+
const { dialog } = require('electron');
|
|
121
|
+
dialog.showMessageBox({
|
|
122
|
+
type: 'info',
|
|
123
|
+
title: 'About Bolloon Agent',
|
|
124
|
+
message: `Bolloon Agent v${electron_1.app.getVersion()}`,
|
|
125
|
+
detail: `P2P AI Document Agent\nData: ${(0, paths_1.dataDir)()}\nLogs: ${(0, paths_1.logsDir)()}`,
|
|
126
|
+
buttons: ['OK'],
|
|
127
|
+
}).catch(() => { });
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
const template = [
|
|
133
|
+
...(isMac ? [appMenu] : []),
|
|
134
|
+
fileMenu,
|
|
135
|
+
editMenu,
|
|
136
|
+
viewMenu,
|
|
137
|
+
windowMenu,
|
|
138
|
+
helpMenu,
|
|
139
|
+
];
|
|
140
|
+
return electron_1.Menu.buildFromTemplate(template);
|
|
141
|
+
}
|
|
142
|
+
function installAppMenu(getMainWindow) {
|
|
143
|
+
electron_1.Menu.setApplicationMenu(buildAppMenu(getMainWindow));
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=menu.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
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.dataDir = dataDir;
|
|
37
|
+
exports.userDataDir = userDataDir;
|
|
38
|
+
exports.logsDir = logsDir;
|
|
39
|
+
exports.mainLogPath = mainLogPath;
|
|
40
|
+
exports.firstRunFlagPath = firstRunFlagPath;
|
|
41
|
+
exports.buildResourcesDir = buildResourcesDir;
|
|
42
|
+
/**
|
|
43
|
+
* 路径常量 — 单一来源, 避免散落各处
|
|
44
|
+
*
|
|
45
|
+
* dataDir: 用户数据 (LLM config / P2P secret / 频道 JSON 等) — 故意保持在 ~/.bolloon/
|
|
46
|
+
* 与 CLI 共用, 卸载 app 不删数据. 不要改这个路径, 不要做 userData 重定向.
|
|
47
|
+
* userDataDir: Electron 的 app.getPath('userData'), 用来放日志 / 临时 / 锁文件
|
|
48
|
+
* logsDir: userData 下的 logs/ 子目录
|
|
49
|
+
*/
|
|
50
|
+
const electron_1 = require("electron");
|
|
51
|
+
const os = __importStar(require("os"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
function dataDir() {
|
|
54
|
+
return path.join(os.homedir(), '.bolloon');
|
|
55
|
+
}
|
|
56
|
+
function userDataDir() {
|
|
57
|
+
return electron_1.app.getPath('userData');
|
|
58
|
+
}
|
|
59
|
+
function logsDir() {
|
|
60
|
+
return path.join(userDataDir(), 'logs');
|
|
61
|
+
}
|
|
62
|
+
function mainLogPath() {
|
|
63
|
+
return path.join(logsDir(), 'bolloon.log');
|
|
64
|
+
}
|
|
65
|
+
function firstRunFlagPath() {
|
|
66
|
+
return path.join(userDataDir(), '.bolloon-first-run-seen');
|
|
67
|
+
}
|
|
68
|
+
/** 资源目录: packaged 下是 process.resourcesPath/build, dev 下是 repo/build/ */
|
|
69
|
+
function buildResourcesDir() {
|
|
70
|
+
if (electron_1.app.isPackaged) {
|
|
71
|
+
return path.join(process.resourcesPath, 'build');
|
|
72
|
+
}
|
|
73
|
+
return path.join(electron_1.app.getAppPath(), 'build');
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
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.getWebServerProcess = getWebServerProcess;
|
|
37
|
+
exports.killWebServer = killWebServer;
|
|
38
|
+
exports.startWebServer = startWebServer;
|
|
39
|
+
/**
|
|
40
|
+
* Spawns dist/web/server.js as a child Node process (via Electron's
|
|
41
|
+
* ELECTRON_RUN_AS_NODE=1) and parses BOLLOON_PORT=NNNN to learn the
|
|
42
|
+
* actually-bound port (which may have drifted from the preferred port
|
|
43
|
+
* due to EADDRINUSE auto-bump).
|
|
44
|
+
*/
|
|
45
|
+
const child_process_1 = require("child_process");
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const logger_1 = require("./logger");
|
|
48
|
+
const config_1 = require("./config");
|
|
49
|
+
let webServerProcess = null;
|
|
50
|
+
function getWebServerProcess() {
|
|
51
|
+
return webServerProcess;
|
|
52
|
+
}
|
|
53
|
+
function killWebServer() {
|
|
54
|
+
if (webServerProcess && !webServerProcess.killed) {
|
|
55
|
+
(0, logger_1.log)('终止 Web 服务器进程');
|
|
56
|
+
webServerProcess.kill();
|
|
57
|
+
}
|
|
58
|
+
webServerProcess = null;
|
|
59
|
+
}
|
|
60
|
+
function startWebServer(preferredPort) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const serverScript = path.join(__dirname, '..', 'web', 'server.js');
|
|
63
|
+
(0, logger_1.log)(`启动 Web 服务器进程: ${serverScript}`);
|
|
64
|
+
// ELECTRON_RUN_AS_NODE=1 — packaged 时 process.execPath 是 Electron 二进制,
|
|
65
|
+
// 没这行 require() 会失败. Dev 模式 (electron dist/electron.js) 同样情况.
|
|
66
|
+
webServerProcess = (0, child_process_1.spawn)(process.execPath, [serverScript], {
|
|
67
|
+
env: {
|
|
68
|
+
...process.env,
|
|
69
|
+
ELECTRON_RUN_AS_NODE: '1',
|
|
70
|
+
NODE_ENV: 'production',
|
|
71
|
+
PORT: String(preferredPort),
|
|
72
|
+
ELECTRON_PORT: String(preferredPort),
|
|
73
|
+
BOLLOON_HOST: config_1.DEFAULT_HOST,
|
|
74
|
+
},
|
|
75
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
76
|
+
});
|
|
77
|
+
let stdoutBuf = '';
|
|
78
|
+
let stderrData = '';
|
|
79
|
+
webServerProcess.stdout?.on('data', (data) => {
|
|
80
|
+
stdoutBuf += data.toString();
|
|
81
|
+
process.stdout.write(`[WebServer] ${data}`);
|
|
82
|
+
const m = stdoutBuf.match(/^BOLLOON_PORT=(\d+)/m);
|
|
83
|
+
if (m) {
|
|
84
|
+
const actualPort = parseInt(m[1], 10);
|
|
85
|
+
(0, logger_1.log)(`Web 服务器就绪: 实际端口=${actualPort} (preferred=${preferredPort})`);
|
|
86
|
+
resolve({ port: actualPort });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
webServerProcess.stderr?.on('data', (data) => {
|
|
90
|
+
stderrData += data.toString();
|
|
91
|
+
process.stderr.write(`[WebServer ERR] ${data}`);
|
|
92
|
+
});
|
|
93
|
+
webServerProcess.on('error', (err) => {
|
|
94
|
+
(0, logger_1.log)(`Web 服务器启动失败: ${err.message}`, 'error');
|
|
95
|
+
reject(err);
|
|
96
|
+
});
|
|
97
|
+
webServerProcess.on('exit', (code, signal) => {
|
|
98
|
+
(0, logger_1.log)(`Web 服务器退出: code=${code} signal=${signal}`);
|
|
99
|
+
if (code !== 0 && code !== null) {
|
|
100
|
+
(0, logger_1.log)(`Web 服务器 stderr: ${stderrData}`, 'error');
|
|
101
|
+
}
|
|
102
|
+
webServerProcess = null;
|
|
103
|
+
});
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
if (webServerProcess && !webServerProcess.killed) {
|
|
106
|
+
(0, logger_1.log)(`Web 服务器启动超时 (${config_1.WEB_SERVER_STARTUP_TIMEOUT_MS}ms)`, 'error');
|
|
107
|
+
reject(new Error('Web server startup timeout'));
|
|
108
|
+
}
|
|
109
|
+
}, config_1.WEB_SERVER_STARTUP_TIMEOUT_MS);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
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.createTray = createTray;
|
|
37
|
+
exports.destroyTray = destroyTray;
|
|
38
|
+
/**
|
|
39
|
+
* 系统托盘 — Show / Hide / Quit
|
|
40
|
+
*
|
|
41
|
+
* 图标资源要求 (需用户自备):
|
|
42
|
+
* build/trayTemplate.png (mac, 16x16 + 32x32 @2x, 单色 alpha, 自动暗/亮)
|
|
43
|
+
* build/tray.png (win/linux, 16x16 + 32x32)
|
|
44
|
+
*
|
|
45
|
+
* 若 build 目录没有 tray 图标, 降级为不创建托盘 (主菜单和窗口关闭行为不变)
|
|
46
|
+
*/
|
|
47
|
+
const electron_1 = require("electron");
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const paths_1 = require("./paths");
|
|
51
|
+
const logger_1 = require("./logger");
|
|
52
|
+
let trayInstance = null;
|
|
53
|
+
function loadTrayIcon() {
|
|
54
|
+
const isMac = process.platform === 'darwin';
|
|
55
|
+
const file = isMac ? 'trayTemplate.png' : 'tray.png';
|
|
56
|
+
const iconPath = path.join((0, paths_1.buildResourcesDir)(), file);
|
|
57
|
+
if (!fs.existsSync(iconPath)) {
|
|
58
|
+
(0, logger_1.log)(`托盘图标缺失: ${iconPath} (跳过托盘创建)`, 'warn');
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const img = electron_1.nativeImage.createFromPath(iconPath);
|
|
62
|
+
if (isMac)
|
|
63
|
+
img.setTemplateImage(true);
|
|
64
|
+
return img;
|
|
65
|
+
}
|
|
66
|
+
function createTray(getMainWindow) {
|
|
67
|
+
if (trayInstance)
|
|
68
|
+
return;
|
|
69
|
+
const icon = loadTrayIcon();
|
|
70
|
+
if (!icon)
|
|
71
|
+
return;
|
|
72
|
+
trayInstance = new electron_1.Tray(icon);
|
|
73
|
+
trayInstance.setToolTip(`Bolloon Agent v${electron_1.app.getVersion()}`);
|
|
74
|
+
const toggle = () => {
|
|
75
|
+
const w = getMainWindow();
|
|
76
|
+
if (!w)
|
|
77
|
+
return;
|
|
78
|
+
if (w.isVisible() && w.isFocused()) {
|
|
79
|
+
w.hide();
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
if (w.isMinimized())
|
|
83
|
+
w.restore();
|
|
84
|
+
w.show();
|
|
85
|
+
w.focus();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const menu = electron_1.Menu.buildFromTemplate([
|
|
89
|
+
{ label: 'Show / Hide', click: () => toggle() },
|
|
90
|
+
{ type: 'separator' },
|
|
91
|
+
{
|
|
92
|
+
label: 'Quit',
|
|
93
|
+
click: () => {
|
|
94
|
+
(0, logger_1.log)('托盘菜单 Quit');
|
|
95
|
+
electron_1.app.quit();
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
trayInstance.setContextMenu(menu);
|
|
100
|
+
// mac 默认 click 弹菜单, win/linux 默认 click 触发自定义
|
|
101
|
+
if (process.platform !== 'darwin') {
|
|
102
|
+
trayInstance.on('click', () => toggle());
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function destroyTray() {
|
|
106
|
+
if (trayInstance) {
|
|
107
|
+
trayInstance.destroy();
|
|
108
|
+
trayInstance = null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=tray.js.map
|
|
@@ -0,0 +1,108 @@
|
|
|
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.getMainWindow = getMainWindow;
|
|
37
|
+
exports.createMainWindow = createMainWindow;
|
|
38
|
+
exports.focusMainWindow = focusMainWindow;
|
|
39
|
+
/**
|
|
40
|
+
* 主窗口工厂 — preload 路径解析 + dev/prod loadURL + 外部链接走系统浏览器
|
|
41
|
+
*/
|
|
42
|
+
const electron_1 = require("electron");
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const logger_1 = require("./logger");
|
|
45
|
+
const config_1 = require("./config");
|
|
46
|
+
const server_1 = require("./server");
|
|
47
|
+
let mainWindow = null;
|
|
48
|
+
function getMainWindow() {
|
|
49
|
+
return mainWindow;
|
|
50
|
+
}
|
|
51
|
+
async function createMainWindow() {
|
|
52
|
+
(0, logger_1.log)('创建主窗口...');
|
|
53
|
+
mainWindow = new electron_1.BrowserWindow({
|
|
54
|
+
width: config_1.MAIN_WINDOW_DEFAULT.width,
|
|
55
|
+
height: config_1.MAIN_WINDOW_DEFAULT.height,
|
|
56
|
+
minWidth: config_1.MAIN_WINDOW_MIN.width,
|
|
57
|
+
minHeight: config_1.MAIN_WINDOW_MIN.height,
|
|
58
|
+
title: 'Bolloon Agent',
|
|
59
|
+
webPreferences: {
|
|
60
|
+
nodeIntegration: false,
|
|
61
|
+
contextIsolation: true,
|
|
62
|
+
sandbox: false, // MCP/spawn 等需要 fs; 真正的隔离靠 preload 边界
|
|
63
|
+
preload: path.join(__dirname, '..', 'electron-preload.js'),
|
|
64
|
+
},
|
|
65
|
+
show: false,
|
|
66
|
+
});
|
|
67
|
+
if (config_1.isDev) {
|
|
68
|
+
// dev:web 用 tsx 起 server, 端口固定 preferredPort, 不会 EADDRINUSE 自增
|
|
69
|
+
const port = (0, config_1.preferredPort)();
|
|
70
|
+
mainWindow.loadURL(`http://localhost:${port}`);
|
|
71
|
+
mainWindow.webContents.openDevTools();
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
try {
|
|
75
|
+
(0, logger_1.log)('启动内置 Web 服务器...');
|
|
76
|
+
const { port: actualPort } = await (0, server_1.startWebServer)((0, config_1.preferredPort)());
|
|
77
|
+
mainWindow.loadURL(`http://localhost:${actualPort}`);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
(0, logger_1.log)(`启动服务器失败: ${err.message}`, 'error');
|
|
81
|
+
console.error('启动服务器失败:', err);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
mainWindow.once('ready-to-show', () => {
|
|
85
|
+
mainWindow?.show();
|
|
86
|
+
(0, logger_1.log)('窗口已显示');
|
|
87
|
+
});
|
|
88
|
+
// 外部链接走系统浏览器, 不在 app 内开新窗口
|
|
89
|
+
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
90
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
91
|
+
electron_1.shell.openExternal(url);
|
|
92
|
+
}
|
|
93
|
+
return { action: 'deny' };
|
|
94
|
+
});
|
|
95
|
+
mainWindow.on('closed', () => {
|
|
96
|
+
mainWindow = null;
|
|
97
|
+
});
|
|
98
|
+
(0, logger_1.log)('窗口创建完成');
|
|
99
|
+
}
|
|
100
|
+
function focusMainWindow() {
|
|
101
|
+
if (!mainWindow)
|
|
102
|
+
return;
|
|
103
|
+
if (mainWindow.isMinimized())
|
|
104
|
+
mainWindow.restore();
|
|
105
|
+
mainWindow.show();
|
|
106
|
+
mainWindow.focus();
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=window.js.map
|
package/dist/index.js
CHANGED
|
@@ -1928,9 +1928,12 @@ async function main() {
|
|
|
1928
1928
|
}
|
|
1929
1929
|
const { createWebServer, openBrowser } = await import('./web/server.js');
|
|
1930
1930
|
s.info(`启动 Web 服务端口 ${port}...`);
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1931
|
+
// 2026-06-24: CLI 默认 loopback bind (安全), LAN 访问需 BOLLOON_HOST=0.0.0.0
|
|
1932
|
+
const bindHost = process.env.BOLLOON_HOST;
|
|
1933
|
+
const { port: actualPort } = await createWebServer(port, { selfImprove, ...(bindHost ? { host: bindHost } : {}) });
|
|
1934
|
+
const displayHost = bindHost ?? '127.0.0.1';
|
|
1935
|
+
s.success(`浏览器已打开 → http://${displayHost}:${actualPort}`);
|
|
1936
|
+
openBrowser(`http://${displayHost}:${actualPort}`);
|
|
1934
1937
|
}
|
|
1935
1938
|
else if (isNonInteractive) {
|
|
1936
1939
|
console.log = originalLog;
|
package/dist/web/server.js
CHANGED
|
@@ -5209,16 +5209,23 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
5209
5209
|
const startPort = port;
|
|
5210
5210
|
let currentPort = startPort;
|
|
5211
5211
|
let attempt = 0;
|
|
5212
|
+
// 2026-06-24: 默认 loopback bind,避免在 LAN 上暴露 (CORS 已经 * 了)。
|
|
5213
|
+
// Electron 包装里强制 127.0.0.1; CLI 用户想 LAN 访问可显式 BOLLOON_HOST=0.0.0.0
|
|
5214
|
+
const bindHost = options.host ?? process.env.BOLLOON_HOST ?? '127.0.0.1';
|
|
5212
5215
|
// 局部可变 server 引用 — listen 失败后必须重新 createServer 再 listen
|
|
5213
5216
|
let currentServer = server;
|
|
5214
5217
|
const tryListen = () => {
|
|
5215
5218
|
currentServer.removeAllListeners('error');
|
|
5216
5219
|
currentServer.once('error', onError);
|
|
5217
|
-
currentServer.listen(currentPort, () => {
|
|
5220
|
+
currentServer.listen(currentPort, bindHost, () => {
|
|
5218
5221
|
if (currentPort !== startPort) {
|
|
5219
5222
|
console.warn(`⚠ 端口 ${startPort} 被占用,已自动切换到 ${currentPort}`);
|
|
5220
5223
|
}
|
|
5221
|
-
|
|
5224
|
+
// 2026-06-24: BOLLOON_PORT=NNNN 是 Electron 主进程解析的契约行 (parseable),
|
|
5225
|
+
// 旧 marker ('服务器已监听') 也保留给日志/调试看。
|
|
5226
|
+
console.log(`BOLLOON_PORT=${currentPort}`);
|
|
5227
|
+
console.log(`BOLLOON_HOST=${bindHost}`);
|
|
5228
|
+
console.log(`Web 服务器启动完成: http://${bindHost}:${currentPort}`);
|
|
5222
5229
|
console.log('服务器已监听');
|
|
5223
5230
|
// 安装 chat bus -> SSE 桥 (供前端 inbox UI 实时刷新)
|
|
5224
5231
|
void installChatBusHook();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bolloon/bolloon-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -87,6 +87,11 @@
|
|
|
87
87
|
"bin/**/*",
|
|
88
88
|
"package.json"
|
|
89
89
|
],
|
|
90
|
+
"npmRebuild": false,
|
|
91
|
+
"buildDependenciesFromSource": false,
|
|
92
|
+
"extraResources": [
|
|
93
|
+
{ "from": "build", "to": "build", "filter": ["**/*"] }
|
|
94
|
+
],
|
|
90
95
|
"extraMetadata": {
|
|
91
96
|
"main": "dist/electron.js",
|
|
92
97
|
"type": "commonjs"
|
|
@@ -121,7 +126,7 @@
|
|
|
121
126
|
"createDesktopShortcut": true,
|
|
122
127
|
"createStartMenuShortcut": true
|
|
123
128
|
},
|
|
124
|
-
"asar":
|
|
129
|
+
"asar": true,
|
|
125
130
|
"asarUnpack": [
|
|
126
131
|
"node_modules/@diap/**/*",
|
|
127
132
|
"node_modules/@rayhanadev/**/*",
|