@bolloon/bolloon-agent 0.1.41 → 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/.comm/README.md +21 -0
- package/.comm/default/2026-06-17T08-23-00-017Z-8a735de8.md +7 -0
- package/CLAUDE.md +3 -0
- 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/intent-classifier.js +99 -0
- package/dist/agents/pi-sdk.js +1418 -67
- package/dist/agents/pre-tool-validator.js +2 -1
- package/dist/agents/shell-guard.js +9 -3
- package/dist/agents/shell-tool.js +28 -13
- package/dist/agents/task-state.js +224 -0
- package/dist/agents/workflow-pivot-loop.js +30 -0
- package/dist/bollharness-integration/gate-state-machine.js +13 -0
- package/dist/bollharness-integration/integration.js +71 -0
- package/dist/bollharness-integration/skill-adapter.js +52 -127
- package/dist/bootstrap/context-hierarchy.js +6 -4
- package/dist/cli/interface.js +28 -0
- package/dist/documents/reader.js +14 -0
- 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/git-transport/chat-render.js +155 -0
- package/dist/git-transport/chat-repo.js +370 -0
- package/dist/git-transport/chat-types.js +23 -0
- package/dist/git-transport/chat-watch.js +102 -0
- package/dist/index.js +477 -28
- package/dist/llm/pi-ai.js +103 -27
- package/dist/network/local-inbox-bus.js +73 -0
- package/dist/network/p2p-direct.js +3 -4
- package/dist/pi-ecosystem-judgment/adaptive-scan.js +6 -2
- package/dist/pi-ecosystem-judgment/causal-judge.js +3 -3
- package/dist/pi-ecosystem-judgment/index.js +1 -1
- package/dist/security/tool-gate.js +11 -0
- package/dist/web/server.js +68 -4
- package/package.json +8 -3
- package/scripts/lefthook-helper.sh +69 -0
|
@@ -88,6 +88,10 @@ function loadAllHarnessSkills() {
|
|
|
88
88
|
* Base skill class for bollharness-compatible skills
|
|
89
89
|
*/
|
|
90
90
|
export class BaseSkill {
|
|
91
|
+
static llm = null;
|
|
92
|
+
static setLlm(instance) {
|
|
93
|
+
BaseSkill.llm = instance;
|
|
94
|
+
}
|
|
91
95
|
log(message, level = 'info') {
|
|
92
96
|
const prefix = level === 'error' ? '❌' : level === 'warn' ? '⚠️' : 'ℹ️';
|
|
93
97
|
console.log(`${prefix} [${this.name}] ${message}`);
|
|
@@ -95,6 +99,33 @@ export class BaseSkill {
|
|
|
95
99
|
formatOutput(output) {
|
|
96
100
|
return JSON.stringify(output, null, 2);
|
|
97
101
|
}
|
|
102
|
+
async callLm(system, user) {
|
|
103
|
+
let llm = BaseSkill.llm;
|
|
104
|
+
if (!llm) {
|
|
105
|
+
try {
|
|
106
|
+
const { getMinimax } = await import('../llm/pi-ai.js');
|
|
107
|
+
const m = getMinimax();
|
|
108
|
+
if (m && typeof m.chat === 'function') {
|
|
109
|
+
BaseSkill.setLlm(m);
|
|
110
|
+
llm = m;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return this.formatOutput({ warning: 'LLM 未注入', name: this.name });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!llm) {
|
|
118
|
+
return this.formatOutput({ warning: 'LLM 未注入', name: this.name });
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const res = await llm.chat([{ role: 'user', content: user }], system);
|
|
122
|
+
return res.reply || '(空回复)';
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
this.log(`LLM 调用失败: ${err.message}`, 'error');
|
|
126
|
+
return this.formatOutput({ error: `LLM 调用失败: ${err.message}` });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
98
129
|
}
|
|
99
130
|
/**
|
|
100
131
|
* Architecture Skill - Project architect for architecture decisions
|
|
@@ -103,58 +134,12 @@ export class ArchSkill extends BaseSkill {
|
|
|
103
134
|
name = 'arch';
|
|
104
135
|
description = 'Project architect. Responsible for architecture decisions, scheme comparison, and boundary freezing.';
|
|
105
136
|
async execute(params) {
|
|
106
|
-
const task = params.task || params.description;
|
|
137
|
+
const task = (params.task || params.description || params.action || '');
|
|
107
138
|
this.log('Analyzing architecture task');
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// Compare alternatives
|
|
113
|
-
const alternatives = this.compareAlternatives(task);
|
|
114
|
-
// Identify boundaries to freeze
|
|
115
|
-
const boundaries = this.identifyBoundaries(task);
|
|
116
|
-
return this.formatOutput({
|
|
117
|
-
essence,
|
|
118
|
-
tensions,
|
|
119
|
-
alternatives,
|
|
120
|
-
boundaries,
|
|
121
|
-
recommendation: this.makeRecommendation(task, alternatives),
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
extractEssence(task) {
|
|
125
|
-
// Simplified essence extraction
|
|
126
|
-
return `The core challenge is: ${task}`;
|
|
127
|
-
}
|
|
128
|
-
identifyTensions(task) {
|
|
129
|
-
return [
|
|
130
|
-
'Simplicity vs Flexibility',
|
|
131
|
-
'Performance vs Maintainability',
|
|
132
|
-
'Coupling vs Cohesion',
|
|
133
|
-
];
|
|
134
|
-
}
|
|
135
|
-
compareAlternatives(task) {
|
|
136
|
-
return [
|
|
137
|
-
{
|
|
138
|
-
name: 'Option A: Direct Implementation',
|
|
139
|
-
tradeoffs: ['Fast to implement', 'May not scale'],
|
|
140
|
-
recommendation: 'Suitable for MVP',
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'Option B: Abstraction Layer',
|
|
144
|
-
tradeoffs: ['More upfront work', 'Better for extension'],
|
|
145
|
-
recommendation: 'Suitable for long-term',
|
|
146
|
-
},
|
|
147
|
-
];
|
|
148
|
-
}
|
|
149
|
-
identifyBoundaries(task) {
|
|
150
|
-
return [
|
|
151
|
-
'API contract boundaries',
|
|
152
|
-
'Data format boundaries',
|
|
153
|
-
'Capability boundaries',
|
|
154
|
-
];
|
|
155
|
-
}
|
|
156
|
-
makeRecommendation(task, alternatives) {
|
|
157
|
-
return alternatives[1]?.recommendation || 'Consider abstraction layer for long-term maintainability';
|
|
139
|
+
if (!task) {
|
|
140
|
+
return this.formatOutput({ warning: '未提供任务', usage: '--harness-skill arch "你的架构任务"' });
|
|
141
|
+
}
|
|
142
|
+
return this.callLm('You are a senior software architect. Analyze the task and output structured JSON with fields: essence, tensions, alternatives, boundaries, recommendation.', `Architecture task: ${task}`);
|
|
158
143
|
}
|
|
159
144
|
}
|
|
160
145
|
/**
|
|
@@ -221,18 +206,9 @@ export class LeadSkill extends BaseSkill {
|
|
|
221
206
|
gate_pack: this.getGatePack(),
|
|
222
207
|
});
|
|
223
208
|
}
|
|
224
|
-
classifyChange(params) {
|
|
209
|
+
async classifyChange(params) {
|
|
225
210
|
const description = params.description || '';
|
|
226
|
-
|
|
227
|
-
const isPolicy = description.includes('policy') || description.includes('boundary');
|
|
228
|
-
const isContract = description.includes('API') || description.includes('contract') || description.includes('schema');
|
|
229
|
-
const isImplementation = !isPolicy && !isContract;
|
|
230
|
-
return this.formatOutput({
|
|
231
|
-
classification: isPolicy ? 'policy' : isContract ? 'contract' : 'implementation',
|
|
232
|
-
description,
|
|
233
|
-
minimum_gate_path: isPolicy ? '0→8 (full)' : isContract ? '0→8 (full + consumers)' : '0→7 (fast track eligible)',
|
|
234
|
-
fast_track_eligible: isImplementation,
|
|
235
|
-
});
|
|
211
|
+
return this.callLm('You are a change classifier. Given a description, classify the change as policy, contract, or implementation. Output JSON: { classification, description, minimum_gate_path, fast_track_eligible }.', `Classify this change: ${description}`);
|
|
236
212
|
}
|
|
237
213
|
}
|
|
238
214
|
/**
|
|
@@ -242,44 +218,12 @@ export class TaskArchSkill extends BaseSkill {
|
|
|
242
218
|
name = 'task-arch';
|
|
243
219
|
description = 'Task decomposition. Breaks down PLAN into parallelizable work packages (WP).';
|
|
244
220
|
async execute(params) {
|
|
245
|
-
const plan = params.plan;
|
|
221
|
+
const plan = (params.plan || params.task || params.description || params.action || '');
|
|
246
222
|
this.log('Decomposing plan into work packages');
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// Identify seams
|
|
250
|
-
const seams = this.identifySeams(workPackages);
|
|
251
|
-
// Identify integration points
|
|
252
|
-
const integration = this.identifyIntegration(workPackages);
|
|
253
|
-
return this.formatOutput({
|
|
254
|
-
work_packages: workPackages,
|
|
255
|
-
seams,
|
|
256
|
-
integration,
|
|
257
|
-
seam_owners: this.assignSeamOwners(seams),
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
extractWorkPackages(plan) {
|
|
261
|
-
// Simplified WP extraction
|
|
262
|
-
return [
|
|
263
|
-
{ id: 'WP-1', description: 'Core implementation', files: ['src/agents/*.ts'] },
|
|
264
|
-
{ id: 'WP-2', description: 'Network layer', files: ['src/network/*.ts'] },
|
|
265
|
-
{ id: 'WP-3', description: 'Testing', files: ['src/test/*.ts'] },
|
|
266
|
-
];
|
|
267
|
-
}
|
|
268
|
-
identifySeams(packages) {
|
|
269
|
-
return [
|
|
270
|
-
{ from: 'WP-1', to: 'WP-2', interface: 'P2PNetwork interface' },
|
|
271
|
-
{ from: 'WP-1', to: 'WP-3', interface: 'Test fixtures' },
|
|
272
|
-
];
|
|
273
|
-
}
|
|
274
|
-
identifyIntegration(packages) {
|
|
275
|
-
return ['Integration test at WP-1/WP-2 boundary'];
|
|
276
|
-
}
|
|
277
|
-
assignSeamOwners(seams) {
|
|
278
|
-
const owners = {};
|
|
279
|
-
for (const seam of seams) {
|
|
280
|
-
owners[`${seam.from}/${seam.to}`] = 'integration-owner';
|
|
223
|
+
if (!plan) {
|
|
224
|
+
return this.formatOutput({ warning: '未提供 plan', usage: '--harness-skill task-arch "你的开发计划"' });
|
|
281
225
|
}
|
|
282
|
-
return
|
|
226
|
+
return this.callLm('You are a task decomposition specialist. Given a plan, break it down into work packages (WP) with seams and integration points. Output JSON: { work_packages, seams, integration }.', `Plan: ${plan}`);
|
|
283
227
|
}
|
|
284
228
|
}
|
|
285
229
|
/**
|
|
@@ -398,35 +342,12 @@ export class CrystalLearnSkill extends BaseSkill {
|
|
|
398
342
|
name = 'crystal-learn';
|
|
399
343
|
description = 'Extracts failure patterns and maintains invariants.';
|
|
400
344
|
async execute(params) {
|
|
401
|
-
const task = params.task;
|
|
402
|
-
this.log('Extracting failure patterns');
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
// Generate invariants
|
|
408
|
-
const invariants = this.generateInvariants(patterns);
|
|
409
|
-
return this.formatOutput({
|
|
410
|
-
failure_modes: failures,
|
|
411
|
-
patterns,
|
|
412
|
-
invariants,
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
identifyFailures(task) {
|
|
416
|
-
return [
|
|
417
|
-
'Truth source split',
|
|
418
|
-
'Verification decay',
|
|
419
|
-
'Orphaned seams',
|
|
420
|
-
];
|
|
421
|
-
}
|
|
422
|
-
extractPatterns(failures) {
|
|
423
|
-
return failures.map(f => ({
|
|
424
|
-
pattern: f,
|
|
425
|
-
prevention: `Implement guard to prevent ${f}`,
|
|
426
|
-
}));
|
|
427
|
-
}
|
|
428
|
-
generateInvariants(patterns) {
|
|
429
|
-
return patterns.map(p => `INV: ${p.pattern} must not occur`);
|
|
345
|
+
const task = (params.task || params.description || params.action || '');
|
|
346
|
+
this.log('Extracting failure patterns from: ' + (task || '(空)'));
|
|
347
|
+
if (!task) {
|
|
348
|
+
return this.formatOutput({ warning: '未提供任务', usage: '--harness-skill crystal-learn "你的任务描述"' });
|
|
349
|
+
}
|
|
350
|
+
return this.callLm('You are a failure pattern analyst. Extract failure modes, patterns, and invariants from the task description. Output structured JSON with failure_modes, patterns, invariants.', `Task: ${task}`);
|
|
430
351
|
}
|
|
431
352
|
}
|
|
432
353
|
/**
|
|
@@ -482,6 +403,10 @@ export class SkillAdapter {
|
|
|
482
403
|
getSkill(name) {
|
|
483
404
|
return this.registry.get(name);
|
|
484
405
|
}
|
|
406
|
+
injectLlm(llm) {
|
|
407
|
+
BaseSkill.setLlm(llm);
|
|
408
|
+
this.log('LLM 已注入到所有 BaseSkill');
|
|
409
|
+
}
|
|
485
410
|
listSkills() {
|
|
486
411
|
return this.registry.list();
|
|
487
412
|
}
|
|
@@ -43,13 +43,15 @@ export const DEFAULT_MAX_CHARS = {
|
|
|
43
43
|
// ============================================================
|
|
44
44
|
export function resolveUserPath(home) {
|
|
45
45
|
const h = home ?? process.env.HOME ?? os.homedir() ?? '/tmp';
|
|
46
|
-
|
|
46
|
+
// 用 path.posix.join — 这些是配置/注入路径,跨平台语义稳定, 便于测试断言 + 注入 system prompt
|
|
47
|
+
// (Windows 文件系统也接受 '/' 分隔符, 所以 fs.readFile 不受影响)
|
|
48
|
+
return path.posix.join(h, '.bolloon', 'Bolloon.md');
|
|
47
49
|
}
|
|
48
50
|
export function resolveProjectPaths(cwd) {
|
|
49
51
|
return {
|
|
50
|
-
project: path.join(cwd, 'Bolloon.md'),
|
|
51
|
-
projectRulesDir: path.join(cwd, '.claude', 'rules'),
|
|
52
|
-
local: path.join(cwd, 'CLAUDE.local.md'),
|
|
52
|
+
project: path.posix.join(cwd, 'Bolloon.md'),
|
|
53
|
+
projectRulesDir: path.posix.join(cwd, '.claude', 'rules'),
|
|
54
|
+
local: path.posix.join(cwd, 'CLAUDE.local.md'),
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
// ============================================================
|
package/dist/cli/interface.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as readline from 'readline';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
2
3
|
import { documentReader } from '../documents/reader.js';
|
|
3
4
|
import { getMinimax } from '../constraints/index.js';
|
|
4
5
|
import { AgentProtocol } from '../agents/protocol.js';
|
|
@@ -63,6 +64,7 @@ export class CLIInterface {
|
|
|
63
64
|
console.log(' send <peer> - 向指定节点发送消息');
|
|
64
65
|
console.log(' broadcast - 广播任务到所有节点');
|
|
65
66
|
console.log(' summary <text> - 总结文本');
|
|
67
|
+
console.log(' chat <消息正文> - 跨机聊天 (commits as messages, wrapper)');
|
|
66
68
|
console.log(' help - 显示帮助');
|
|
67
69
|
console.log(' exit - 退出\n');
|
|
68
70
|
}
|
|
@@ -89,6 +91,9 @@ export class CLIInterface {
|
|
|
89
91
|
case 'summary':
|
|
90
92
|
await this.handleSummary(args.join(' '));
|
|
91
93
|
break;
|
|
94
|
+
case 'chat':
|
|
95
|
+
await this.handleChat(args.join(' '));
|
|
96
|
+
break;
|
|
92
97
|
case 'help':
|
|
93
98
|
this.showHelp();
|
|
94
99
|
break;
|
|
@@ -172,6 +177,29 @@ export class CLIInterface {
|
|
|
172
177
|
console.log(result.summary);
|
|
173
178
|
console.log(`\n质量评分: ${(result.qualityScore * 10).toFixed(1)}/10`);
|
|
174
179
|
}
|
|
180
|
+
handleChat(text) {
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
if (!text) {
|
|
183
|
+
console.log('请提供聊天内容: chat <消息正文>');
|
|
184
|
+
resolve();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// 委派给 bolloon --chat-send, spawn 子进程, 同步输出
|
|
188
|
+
const child = spawn('npx', ['tsx', 'src/index.ts', '--chat-send', text], {
|
|
189
|
+
cwd: process.cwd(),
|
|
190
|
+
stdio: 'inherit',
|
|
191
|
+
});
|
|
192
|
+
child.on('close', (code) => {
|
|
193
|
+
if (code !== 0)
|
|
194
|
+
console.error(`chat 子进程退出码 ${code}`);
|
|
195
|
+
resolve();
|
|
196
|
+
});
|
|
197
|
+
child.on('error', (err) => {
|
|
198
|
+
console.error('chat 子进程启动失败:', err.message);
|
|
199
|
+
resolve();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
175
203
|
async shutdown() {
|
|
176
204
|
console.log('\n正在关闭...');
|
|
177
205
|
await p2pNetwork.shutdown();
|
package/dist/documents/reader.js
CHANGED
|
@@ -16,6 +16,20 @@ export class DocumentReader {
|
|
|
16
16
|
case '.yaml':
|
|
17
17
|
case '.yml':
|
|
18
18
|
case '.json':
|
|
19
|
+
// M3.5 (2026-06-17): agent 自读源码需要 — 之前 .ts 不支持, LLM 拿到空内容
|
|
20
|
+
// .ts/.tsx/.js/.jsx 都是纯文本, 直接 readFile
|
|
21
|
+
case '.ts':
|
|
22
|
+
case '.tsx':
|
|
23
|
+
case '.js':
|
|
24
|
+
case '.jsx':
|
|
25
|
+
case '.mjs':
|
|
26
|
+
case '.cjs':
|
|
27
|
+
case '.py':
|
|
28
|
+
case '.go':
|
|
29
|
+
case '.rs':
|
|
30
|
+
case '.java':
|
|
31
|
+
case '.sh':
|
|
32
|
+
case '.bash':
|
|
19
33
|
text = await fs.readFile(filePath, 'utf-8');
|
|
20
34
|
break;
|
|
21
35
|
case '.pdf':
|
|
@@ -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
|