@bolloon/bolloon-agent 0.3.9 → 0.3.11

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.
@@ -31,7 +31,8 @@ import { registerBuiltinTools, registerWalletTools, setupInboxListener, Idempote
31
31
  export { createAgentSession, getAgentSession, resetAgentSession, runSelfImproveLoop, } from './pi-sdk-session-factory.js';
32
32
  // Judgment 注入门 (P0): 在主对话 LLM 调起前自动拼入 Top 3 判断力
33
33
  // 失败静默, 不阻塞主对话
34
- import { injectJudgmentGate, recordJudgmentUsage } from '../pi-ecosystem-judgment/injection-gate.js';
34
+ import { injectJudgmentGate, injectNegativeGuard, recordJudgmentUsage } from '../pi-ecosystem-judgment/injection-gate.js';
35
+ import { getInjectionMaxChars } from '../bootstrap/exhaust-scrubber.js';
35
36
  // 持续监控门 (P3): AI 回复后审计是否违反原则
36
37
  import { monitorAfterReply } from '../pi-ecosystem-judgment/monitor-gate.js';
37
38
  // Bootstrap 生命周期 hook (SessionStart / Stop / PreToolUse)
@@ -109,6 +110,8 @@ export class PiAgentSession {
109
110
  */
110
111
  judgmentGateAddition = '';
111
112
  judgmentGateUsedIds = [];
113
+ /** 2026-07-22 设计 B: 负向判断力 (避免清单) 注入用到的 judgment id */
114
+ judgmentGateNegativeUsedIds = [];
112
115
  // 2026-06-18: 来自 web server markedPrompt 外的 contextHint (channel/judgment/distill/remote channels),
113
116
  // 拼到 systemPrompt 末尾, 别再混进 user message
114
117
  contextHintAddition = '';
@@ -147,11 +150,25 @@ export class PiAgentSession {
147
150
  try {
148
151
  // P-Action 4 (2026-06-15) 路径 1 整合: 透传 maxChars=1500 (≈ 375 tokens 硬上限)
149
152
  // 路径 2/3 检测由 injection-gate 内部 alreadyInjectedSources 处理 (目前 assembleSystemPrompt 还没注入 value-store 标记, 所以这里不传)
150
- const gate = await injectJudgmentGate(input, {}, { maxChars: 1500 });
153
+ // 2026-07-22 设计 C: maxChars 读背压动态值 (涡轮增压进气调参)
154
+ // 上下文紧张 (high) → 收紧 800; 宽裕 (idle/low) → 放宽 1800; 默认 medium 1500
155
+ const gate = await injectJudgmentGate(input, {}, { maxChars: getInjectionMaxChars() });
151
156
  this.judgmentGateAddition = gate.systemAddition;
152
157
  this.judgmentGateUsedIds = gate.usedIds;
153
- if (gate.usedIds.length > 0) {
154
- safePhase('gate_done', { usedCount: gate.usedIds.length, didInject: gate.didInject, skipReason: gate.skipReason });
158
+ // 2026-07-22 设计 B: 负向判断力回收 — "避免清单"注入 (显式, prompt)
159
+ // 判断力负向是"判断力"非"废气", 可进 prompt 作为约束 (精准 = 正向指引 + 负向避免)
160
+ try {
161
+ const neg = await injectNegativeGuard(input, {}, { maxChars: 300 });
162
+ if (neg.didInject && neg.systemAddition) {
163
+ this.judgmentGateAddition += '\n' + neg.systemAddition;
164
+ this.judgmentGateNegativeUsedIds = neg.usedIds;
165
+ }
166
+ }
167
+ catch (negErr) {
168
+ console.warn('[PiAgent] negative guard failed (non-fatal):', negErr);
169
+ }
170
+ if (this.judgmentGateUsedIds.length > 0 || this.judgmentGateNegativeUsedIds.length > 0) {
171
+ safePhase('gate_done', { usedCount: this.judgmentGateUsedIds.length, negativeCount: this.judgmentGateNegativeUsedIds.length, didInject: gate.didInject, skipReason: gate.skipReason });
155
172
  }
156
173
  }
157
174
  catch (err) {
@@ -163,6 +180,7 @@ export class PiAgentSession {
163
180
  clearJudgmentGate() {
164
181
  this.judgmentGateAddition = '';
165
182
  this.judgmentGateUsedIds = [];
183
+ this.judgmentGateNegativeUsedIds = [];
166
184
  }
167
185
  constructor(config) {
168
186
  this.cwd = config.cwd;
@@ -491,7 +509,10 @@ export class PiAgentSession {
491
509
  }
492
510
  finally {
493
511
  if (this.judgmentGateUsedIds.length > 0) {
494
- recordJudgmentUsage(this.judgmentGateUsedIds, { userInput: input }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage failed:', err));
512
+ recordJudgmentUsage(this.judgmentGateUsedIds, { userInput: input, polarity: 'positive' }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage failed:', err));
513
+ }
514
+ if (this.judgmentGateNegativeUsedIds.length > 0) {
515
+ recordJudgmentUsage(this.judgmentGateNegativeUsedIds, { userInput: input, polarity: 'negative' }).catch((err) => console.warn('[PiAgent] recordJudgmentUsage (negative) failed:', err));
495
516
  }
496
517
  this.clearJudgmentGate();
497
518
  this.currentSignal = null;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * exhaust-scrubber.ts — 上下文废气涡轮 (2026-07-22 设计 C)
3
+ *
4
+ * 思想锚点: 涡轮增压 (Turbocharger)
5
+ * 排气 (废气) = session-window dropped / memory-compressor skipped / compaction stage drops / truncation
6
+ * 涡轮 (本模块) = 采样丢弃事件, 聚合成"背压"指标
7
+ * 进气增压 = 背压反向调进气侧参数 (压缩阈值 / 检索 top-k / judgment 注入 maxChars)
8
+ * 燃烧室 (prompt) = 废气**不进**这里 (保持精准), 只让压力调参
9
+ *
10
+ * 拍板: 上下文废气 → 不进 prompt, 只调参, 进 log/memory, 隐式
11
+ *
12
+ * 三个职责:
13
+ * 1. recordExhaust(event): 采样丢弃事件 → 环形缓冲 + 落盘 ~/.bolloon/engine/backpressure.jsonl (log)
14
+ * 2. getBackpressure(): 算背压等级 (idle/low/medium/high) — 进气侧读它调参
15
+ * 3. getInjectionMaxChars(level): 背压 → judgment 注入 maxChars 映射 (进气增压)
16
+ * 4. maybeWriteExhaustMemory(): 背压高峰持续 → 模板摘要写 memory (月度滚动, 不调 LLM)
17
+ *
18
+ * 设计原则:
19
+ * - 零新数据源: 只订阅已有丢弃事件
20
+ * - 隐式: 用户看不到废气内容, 只看到压力等级 (可选背压表)
21
+ * - 静默: 任何失败 console.warn 不阻塞主流程
22
+ * - 不存原文: 只存 source + reason + 估算 token 数 (防隐私/膨胀)
23
+ */
24
+ import * as fs from 'fs/promises';
25
+ import * as os from 'os';
26
+ import * as path from 'path';
27
+ // ============== 路径 ==============
28
+ function getEngineDir(home) {
29
+ return path.join(home || os.homedir(), '.bolloon', 'engine');
30
+ }
31
+ function getBackpressureLogPath(home) {
32
+ return path.join(getEngineDir(home), 'backpressure.jsonl');
33
+ }
34
+ function getMemoryEngineDir(agentId, home) {
35
+ // 跟 memory-compressor.ts 一致: ~/.bolloon/memory/<agentId>/engine/
36
+ const safe = agentId.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
37
+ return path.join(home || os.homedir(), '.bolloon', 'memory', safe, 'engine');
38
+ }
39
+ // ============== 状态 (模块级单例, 跟 chat-archiver 同模式) ==============
40
+ const RING_CAPACITY = 100;
41
+ const ringBuffer = [];
42
+ let droppedTokensTotal = 0;
43
+ const bySource = {};
44
+ let monthlyHighCount = 0; // 当月 high 事件累计 (触发 memory 摘要用)
45
+ // ============== 采样 (涡轮入口) ==============
46
+ /**
47
+ * 记录一个废气事件. 推入环形缓冲 + 落盘 jsonl (log). 静默失败.
48
+ *
49
+ * @param event 废气事件 (source + reason + 可选 droppedTokens; ts 不传则自动填)
50
+ * @param home 可选 home 目录 (测试注入)
51
+ */
52
+ export async function recordExhaust(event, home) {
53
+ try {
54
+ const full = {
55
+ ts: event.ts || new Date().toISOString(),
56
+ source: event.source,
57
+ reason: event.reason,
58
+ droppedTokens: event.droppedTokens,
59
+ };
60
+ // 环形缓冲
61
+ ringBuffer.push(full);
62
+ if (ringBuffer.length > RING_CAPACITY) {
63
+ ringBuffer.shift();
64
+ }
65
+ // 计数
66
+ if (full.droppedTokens && full.droppedTokens > 0) {
67
+ droppedTokensTotal += full.droppedTokens;
68
+ }
69
+ bySource[full.source] = (bySource[full.source] || 0) + 1;
70
+ // 落盘 jsonl (log) — append 模式, 跟 chat-archiver 同
71
+ const logPath = getBackpressureLogPath(home);
72
+ await fs.mkdir(path.dirname(logPath), { recursive: true });
73
+ await fs.appendFile(logPath, JSON.stringify(full) + '\n', 'utf-8');
74
+ // high 事件累计 → 触发 memory 摘要 (火忘)
75
+ const snap = getBackpressure(home);
76
+ if (snap.level === 'high') {
77
+ monthlyHighCount++;
78
+ // 每 10 次 high 触发一次 memory 摘要 (节流, 防频繁写盘)
79
+ if (monthlyHighCount % 10 === 0) {
80
+ maybeWriteExhaustMemorySummary('default', home).catch(() => { });
81
+ }
82
+ }
83
+ }
84
+ catch (err) {
85
+ console.warn('[exhaust-scrubber] recordExhaust failed (non-fatal):', err);
86
+ }
87
+ }
88
+ /**
89
+ * 同步版本 (供不方便 await 的调用方; 只更新内存环形缓冲, 不落盘).
90
+ * 落盘由下次 async recordExhaust 或显式 flush 触发.
91
+ */
92
+ export function recordExhaustSync(event) {
93
+ try {
94
+ const full = {
95
+ ts: event.ts || new Date().toISOString(),
96
+ source: event.source,
97
+ reason: event.reason,
98
+ droppedTokens: event.droppedTokens,
99
+ };
100
+ ringBuffer.push(full);
101
+ if (ringBuffer.length > RING_CAPACITY)
102
+ ringBuffer.shift();
103
+ if (full.droppedTokens && full.droppedTokens > 0)
104
+ droppedTokensTotal += full.droppedTokens;
105
+ bySource[full.source] = (bySource[full.source] || 0) + 1;
106
+ }
107
+ catch { /* 静默 */ }
108
+ }
109
+ // ============== 聚合 (背压等级) ==============
110
+ /**
111
+ * 算当前背压快照. 基于环形缓冲 + 最近 60s 事件速率.
112
+ *
113
+ * 等级映射 (dropRatePerMin = 最近 60s 内事件数 / 60 * 60... 简化为最近 60s 事件数本身):
114
+ * 0 → idle
115
+ * 1-2 → low
116
+ * 3-10 → medium
117
+ * > 10 → high
118
+ */
119
+ export function getBackpressure(_home) {
120
+ const now = Date.now();
121
+ const recent = ringBuffer.filter((e) => {
122
+ const t = Date.parse(e.ts);
123
+ return Number.isFinite(t) && now - t < 60_000; // 最近 60s
124
+ });
125
+ const dropRatePerMin = recent.length;
126
+ let level;
127
+ if (dropRatePerMin === 0)
128
+ level = 'idle';
129
+ else if (dropRatePerMin <= 2)
130
+ level = 'low';
131
+ else if (dropRatePerMin <= 10)
132
+ level = 'medium';
133
+ else
134
+ level = 'high';
135
+ const srcCount = {};
136
+ for (const e of ringBuffer) {
137
+ srcCount[e.source] = (srcCount[e.source] || 0) + 1;
138
+ }
139
+ return {
140
+ level,
141
+ dropCount: ringBuffer.length,
142
+ droppedTokensTotal,
143
+ dropRatePerMin,
144
+ lastTs: ringBuffer.length > 0 ? ringBuffer[ringBuffer.length - 1].ts : '',
145
+ bySource: srcCount,
146
+ };
147
+ }
148
+ export function getPressureLevel(home) {
149
+ return getBackpressure(home).level;
150
+ }
151
+ // ============== 进气增压 (背压 → 调参) ==============
152
+ /**
153
+ * 背压 → judgment 注入 maxChars 映射 (进气增压核心).
154
+ *
155
+ * idle/low → 1800 (放宽注入, 上下文宽裕)
156
+ * medium → 1500 (默认, 现状)
157
+ * high → 800 (收紧注入, 上下文紧张, 留空间给主任务)
158
+ *
159
+ * 调用方: pi-sdk.ts computeJudgmentGate 的 maxChars 从固定 1500 改为读这个.
160
+ */
161
+ export function getInjectionMaxChars(level, home) {
162
+ const lvl = level ?? getPressureLevel(home);
163
+ switch (lvl) {
164
+ case 'idle':
165
+ case 'low':
166
+ return 1800;
167
+ case 'medium':
168
+ return 1500;
169
+ case 'high':
170
+ return 800;
171
+ default:
172
+ return 1500;
173
+ }
174
+ }
175
+ /**
176
+ * 背压 → 检索 top-k 映射 (进气增压, 检索侧).
177
+ * idle/low → 8, medium → 5, high → 3
178
+ */
179
+ export function getRetrievalTopK(level, home) {
180
+ const lvl = level ?? getPressureLevel(home);
181
+ switch (lvl) {
182
+ case 'idle':
183
+ case 'low':
184
+ return 8;
185
+ case 'medium':
186
+ return 5;
187
+ case 'high':
188
+ return 3;
189
+ default:
190
+ return 5;
191
+ }
192
+ }
193
+ // ============== memory 落地 (月度摘要, 拍板要求"进 memory") ==============
194
+ /**
195
+ * 背压高峰持续 → 模板摘要写 memory (月度滚动, 不调 LLM).
196
+ * ~/.bolloon/memory/<agentId>/engine/exhaust-<YYYY-MM>.summary.md (append)
197
+ *
198
+ * 让"为什么这段时间上下文一直紧张"沉淀进 memory 供 agent 回看.
199
+ * 节流: 每 10 次 high 触发一次 (recordExhaust 内控制).
200
+ */
201
+ export async function maybeWriteExhaustMemorySummary(agentId, home) {
202
+ try {
203
+ const snap = getBackpressure(home);
204
+ if (snap.level !== 'high')
205
+ return { written: false };
206
+ const now = new Date();
207
+ const yearMonth = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, '0')}`;
208
+ const dir = getMemoryEngineDir(agentId, home);
209
+ const file = path.join(dir, `exhaust-${yearMonth}.summary.md`);
210
+ await fs.mkdir(dir, { recursive: true });
211
+ const block = `\n\n---\n\n## 引擎背压高峰 @ ${now.toISOString()} (level=high)\n\n` +
212
+ `- 最近 60s 丢弃事件: ${snap.dropRatePerMin} 次/min\n` +
213
+ `- 环形缓冲事件总数: ${snap.dropCount}\n` +
214
+ `- 估算丢弃 token 累计: ${snap.droppedTokensTotal}\n` +
215
+ `- 按 source 分布: ${Object.entries(snap.bySource).map(([k, v]) => `${k}=${v}`).join(', ') || '(无)'}\n` +
216
+ `- 含义: 上下文持续紧张, 进气侧已自动收紧 (judgment 注入 maxChars=800, 检索 top-k=3)\n`;
217
+ await fs.appendFile(file, block, 'utf-8');
218
+ return { written: true, path: file };
219
+ }
220
+ catch (err) {
221
+ console.warn('[exhaust-scrubber] maybeWriteExhaustMemorySummary failed (non-fatal):', err);
222
+ return { written: false };
223
+ }
224
+ }
225
+ // ============== 测试辅助 ==============
226
+ /** 重置模块状态 (仅测试用) */
227
+ export function __resetForTest() {
228
+ ringBuffer.length = 0;
229
+ droppedTokensTotal = 0;
230
+ for (const k of Object.keys(bySource))
231
+ delete bySource[k];
232
+ monthlyHighCount = 0;
233
+ }
@@ -161,6 +161,17 @@ export async function compressSessionToMemory(opts) {
161
161
  await fs.mkdir(path.dirname(summaryPath), { recursive: true });
162
162
  await fs.appendFile(summaryPath, block, 'utf-8');
163
163
  await writeCursor(cursorPath, allMessages.length);
164
+ // 2026-07-22 设计 C: 废气采样 — 压缩成功 = 上下文需要压缩的信号, 记入涡轮 (隐式)
165
+ // 废气不进 prompt, 只调参 (背压高 → judgment 注入收紧). 落 log/memory.
166
+ try {
167
+ const { recordExhaust } = await import('./exhaust-scrubber.js');
168
+ recordExhaust({
169
+ source: 'memory-compressor',
170
+ reason: 'compress-summary-written',
171
+ droppedTokens: newMessages.length * 200, // 粗估 200 tokens/msg
172
+ }, opts.home).catch(() => { });
173
+ }
174
+ catch { /* 静默 */ }
164
175
  return {
165
176
  summaryPath,
166
177
  cursorPath,
package/dist/cli-entry.js CHANGED
@@ -9,12 +9,15 @@
9
9
  * bolloon --summarize <file> # 总结文档
10
10
  * bolloon --web # 启动 Web UI
11
11
  * bolloon --version # 显示版本
12
+ * bolloon engine list # 列出外部编码智能体
13
+ * bolloon engine run <prompt> --engine opencode --model opencode/deepseek-v4-flash-free # 委派任务
12
14
  */
13
15
  import { spawn } from 'child_process';
14
16
  import * as path from 'path';
15
17
  import * as fs from 'fs';
16
18
  import { fileURLToPath } from 'url';
17
19
  import { printBanner } from './cli/loading-tui.js';
20
+ import { discoverEngines, delegateToEngine } from './external-engines/index.js';
18
21
  const isWindows = process.platform === 'win32';
19
22
  // ANSI 颜色
20
23
  const RESET = '\x1b[0m';
@@ -121,11 +124,99 @@ function parseArgs() {
121
124
  case '-c':
122
125
  case '--cli':
123
126
  return { mode: 'cli', args: args.slice(1) };
127
+ case 'engine':
128
+ return { mode: 'engine', args: args.slice(1) };
124
129
  default:
125
130
  // 传递所有参数给主程序
126
131
  return { mode: 'passthrough', args };
127
132
  }
128
133
  }
134
+ /** 引擎子命令: list / run */
135
+ async function handleEngineCommand(engineArgs) {
136
+ if (engineArgs.length === 0) {
137
+ console.log(`${BOLD}用法:${RESET}`);
138
+ console.log(' bolloon engine list # 列出外部编码智能体');
139
+ console.log(' bolloon engine run <prompt> [options] # 委派任务给智能体');
140
+ console.log('');
141
+ console.log(`${BOLD}选项 (run):${RESET}`);
142
+ console.log(' --engine <id> 引擎 id (默认 opencode)');
143
+ console.log(' --model <name> 指定模型 (如 opencode/deepseek-v4-flash-free)');
144
+ return;
145
+ }
146
+ const sub = engineArgs[0];
147
+ if (sub === 'list') {
148
+ const engines = await discoverEngines();
149
+ console.log(`\n${BOLD}外部编码智能体:${RESET}`);
150
+ console.log('─'.repeat(60));
151
+ for (const e of engines) {
152
+ const status = e.available ? `${GREEN}✅ 可用${RESET}` : e.configured ? `${YELLOW}⚠ 已配置${RESET}` : e.installed ? `${YELLOW}⚠ 未配置${RESET}` : `${MAGENTA}✗ 未安装${RESET}`;
153
+ console.log(` ${status} ${e.displayName}`);
154
+ console.log(` ID: ${e.id}`);
155
+ console.log(` CLI: ${e.cliPath || '(未安装)'}`);
156
+ console.log(` Provider: ${e.provider || '(未知)'}`);
157
+ if (e.model)
158
+ console.log(` 模型: ${e.model}`);
159
+ if (e.apiKey)
160
+ console.log(` API Key: ***${e.apiKey.slice(-4)}`);
161
+ if (e.notes)
162
+ console.log(` ${e.notes}`);
163
+ console.log('');
164
+ }
165
+ return;
166
+ }
167
+ if (sub === 'run') {
168
+ // 解析子参数: bolloon engine run <prompt> --engine <id> --model <name>
169
+ const rest = engineArgs.slice(1);
170
+ const promptParts = [];
171
+ let engineId = 'opencode';
172
+ let modelName;
173
+ let cwd;
174
+ for (let i = 0; i < rest.length; i++) {
175
+ const arg = rest[i];
176
+ if (arg === '--engine' && i + 1 < rest.length) {
177
+ engineId = rest[++i];
178
+ }
179
+ else if (arg === '--model' && i + 1 < rest.length) {
180
+ modelName = rest[++i];
181
+ }
182
+ else if (arg === '--cwd' && i + 1 < rest.length) {
183
+ cwd = rest[++i];
184
+ }
185
+ else {
186
+ promptParts.push(arg);
187
+ }
188
+ }
189
+ const prompt = promptParts.join(' ');
190
+ if (!prompt) {
191
+ console.error('错误: 请提供 prompt');
192
+ process.exit(1);
193
+ }
194
+ console.log(`${CYAN}🚀 委派给 ${engineId}${RESET}${modelName ? ` (模型: ${modelName})` : ''}`);
195
+ console.log(` Prompt: ${prompt.slice(0, 120)}${prompt.length > 120 ? '...' : ''}`);
196
+ console.log('');
197
+ const result = await delegateToEngine(engineId, prompt, {
198
+ model: modelName,
199
+ cwd: cwd || process.cwd(),
200
+ });
201
+ if (result.success) {
202
+ console.log(`${GREEN}✅ 执行成功 (exit: ${result.exitCode})${RESET}`);
203
+ console.log('─'.repeat(40));
204
+ console.log(result.output || '(无输出)');
205
+ }
206
+ else {
207
+ console.log(`${MAGENTA}❌ 执行失败${RESET}`);
208
+ console.log('─'.repeat(40));
209
+ if (result.error)
210
+ console.error(` Error: ${result.error}`);
211
+ if (result.output)
212
+ console.log(result.output.slice(0, 2000));
213
+ process.exit(result.exitCode ?? 1);
214
+ }
215
+ return;
216
+ }
217
+ console.error(`未知引擎子命令: ${sub}`);
218
+ process.exit(1);
219
+ }
129
220
  // 执行 Node.js 脚本
130
221
  async function runNodeScript(scriptPath, additionalArgs) {
131
222
  return new Promise((resolve, reject) => {
@@ -237,6 +328,9 @@ async function main() {
237
328
  case 'cli':
238
329
  await startCLI(args);
239
330
  break;
331
+ case 'engine':
332
+ await handleEngineCommand(args);
333
+ break;
240
334
  case 'passthrough':
241
335
  // 传递所有参数给主程序
242
336
  try {
@@ -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