@bolloon/bolloon-agent 0.3.21 → 0.3.23

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.
@@ -1,25 +1,63 @@
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;
1
39
  /**
2
40
  * 主窗口工厂 — preload 路径解析 + dev/prod loadURL + 外部链接走系统浏览器
3
41
  */
4
- import { BrowserWindow, shell } from 'electron';
5
- import * as path from 'path';
6
- import { log } from './logger';
7
- import { isDev, MAIN_WINDOW_DEFAULT, MAIN_WINDOW_MIN, preferredPort } from './config';
8
- import { startWebServer } from './server';
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");
9
47
  const g = globalThis;
10
48
  const __basedir = g.__dirname
11
49
  ?? (g.__filename ? path.dirname(g.__filename) : process.cwd());
12
50
  let mainWindow = null;
13
- export function getMainWindow() {
51
+ function getMainWindow() {
14
52
  return mainWindow;
15
53
  }
16
- export async function createMainWindow() {
17
- log('创建主窗口...');
18
- mainWindow = new BrowserWindow({
19
- width: MAIN_WINDOW_DEFAULT.width,
20
- height: MAIN_WINDOW_DEFAULT.height,
21
- minWidth: MAIN_WINDOW_MIN.width,
22
- minHeight: MAIN_WINDOW_MIN.height,
54
+ async function createMainWindow() {
55
+ (0, logger_1.log)('创建主窗口...');
56
+ mainWindow = new electron_1.BrowserWindow({
57
+ width: config_1.MAIN_WINDOW_DEFAULT.width,
58
+ height: config_1.MAIN_WINDOW_DEFAULT.height,
59
+ minWidth: config_1.MAIN_WINDOW_MIN.width,
60
+ minHeight: config_1.MAIN_WINDOW_MIN.height,
23
61
  title: 'Bolloon Agent',
24
62
  webPreferences: {
25
63
  nodeIntegration: false,
@@ -29,40 +67,40 @@ export async function createMainWindow() {
29
67
  },
30
68
  show: false,
31
69
  });
32
- if (isDev) {
70
+ if (config_1.isDev) {
33
71
  // dev:web 用 tsx 起 server, 端口固定 preferredPort, 不会 EADDRINUSE 自增
34
- const port = preferredPort();
72
+ const port = (0, config_1.preferredPort)();
35
73
  mainWindow.loadURL(`http://localhost:${port}`);
36
74
  mainWindow.webContents.openDevTools();
37
75
  }
38
76
  else {
39
77
  try {
40
- log('启动内置 Web 服务器...');
41
- const { port: actualPort } = await startWebServer(preferredPort());
78
+ (0, logger_1.log)('启动内置 Web 服务器...');
79
+ const { port: actualPort } = await (0, server_1.startWebServer)((0, config_1.preferredPort)());
42
80
  mainWindow.loadURL(`http://localhost:${actualPort}`);
43
81
  }
44
82
  catch (err) {
45
- log(`启动服务器失败: ${err.message}`, 'error');
83
+ (0, logger_1.log)(`启动服务器失败: ${err.message}`, 'error');
46
84
  console.error('启动服务器失败:', err);
47
85
  }
48
86
  }
49
87
  mainWindow.once('ready-to-show', () => {
50
88
  mainWindow?.show();
51
- log('窗口已显示');
89
+ (0, logger_1.log)('窗口已显示');
52
90
  });
53
91
  // 外部链接走系统浏览器, 不在 app 内开新窗口
54
92
  mainWindow.webContents.setWindowOpenHandler(({ url }) => {
55
93
  if (url.startsWith('http://') || url.startsWith('https://')) {
56
- shell.openExternal(url);
94
+ electron_1.shell.openExternal(url);
57
95
  }
58
96
  return { action: 'deny' };
59
97
  });
60
98
  mainWindow.on('closed', () => {
61
99
  mainWindow = null;
62
100
  });
63
- log('窗口创建完成');
101
+ (0, logger_1.log)('窗口创建完成');
64
102
  }
65
- export function focusMainWindow() {
103
+ function focusMainWindow() {
66
104
  if (!mainWindow)
67
105
  return;
68
106
  if (mainWindow.isMinimized())
@@ -70,3 +108,4 @@ export function focusMainWindow() {
70
108
  mainWindow.show();
71
109
  mainWindow.focus();
72
110
  }
111
+ //# sourceMappingURL=window.js.map
@@ -1,29 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
1
3
  /**
2
4
  * Electron Preload 脚本
3
5
  * 在渲染进程和主进程之间建立安全的通信桥梁
4
6
  * contextIsolation: true, nodeIntegration: false — 只能通过这里暴露的 API 触达主进程
5
7
  */
6
- import { contextBridge, ipcRenderer } from 'electron';
7
- contextBridge.exposeInMainWorld('electronAPI', {
8
+ const electron_1 = require("electron");
9
+ electron_1.contextBridge.exposeInMainWorld('electronAPI', {
8
10
  // === 原有 (保留) ===
9
- getVersion: () => ipcRenderer.invoke('get-version'),
10
- getUserDataPath: () => ipcRenderer.invoke('get-user-data-path'),
11
- openExternal: (url) => ipcRenderer.invoke('open-external', url),
11
+ getVersion: () => electron_1.ipcRenderer.invoke('get-version'),
12
+ getUserDataPath: () => electron_1.ipcRenderer.invoke('get-user-data-path'),
13
+ openExternal: (url) => electron_1.ipcRenderer.invoke('open-external', url),
12
14
  // === 新增: 数据目录 ===
13
- getDataPath: () => ipcRenderer.invoke('get-data-path'),
15
+ getDataPath: () => electron_1.ipcRenderer.invoke('get-data-path'),
14
16
  // === 新增: 文件 dialog ===
15
- openFile: (opts) => ipcRenderer.invoke('dialog:open-file', opts),
16
- saveFile: (opts) => ipcRenderer.invoke('dialog:save-file', opts),
17
- openDirectory: (opts) => ipcRenderer.invoke('dialog:open-directory', opts),
17
+ openFile: (opts) => electron_1.ipcRenderer.invoke('dialog:open-file', opts),
18
+ saveFile: (opts) => electron_1.ipcRenderer.invoke('dialog:save-file', opts),
19
+ openDirectory: (opts) => electron_1.ipcRenderer.invoke('dialog:open-directory', opts),
18
20
  // === 新增: 文件系统 (限大小, 主进程守卫) ===
19
- readTextFile: (opts) => ipcRenderer.invoke('fs:read-text-file', opts),
20
- writeTextFile: (opts) => ipcRenderer.invoke('fs:write-text-file', opts),
21
- pathExists: (opts) => ipcRenderer.invoke('fs:path-exists', opts),
21
+ readTextFile: (opts) => electron_1.ipcRenderer.invoke('fs:read-text-file', opts),
22
+ writeTextFile: (opts) => electron_1.ipcRenderer.invoke('fs:write-text-file', opts),
23
+ pathExists: (opts) => electron_1.ipcRenderer.invoke('fs:path-exists', opts),
22
24
  // === 新增: 首启引导 ===
23
- getFirstRunSeen: () => ipcRenderer.invoke('first-run:seen'),
24
- markFirstRunSeen: () => ipcRenderer.invoke('first-run:mark-seen'),
25
- getDataPathSync: () => ipcRenderer.invoke('first-run:data-dir'),
26
- getLogsPathSync: () => ipcRenderer.invoke('first-run:logs-dir'),
25
+ getFirstRunSeen: () => electron_1.ipcRenderer.invoke('first-run:seen'),
26
+ markFirstRunSeen: () => electron_1.ipcRenderer.invoke('first-run:mark-seen'),
27
+ getDataPathSync: () => electron_1.ipcRenderer.invoke('first-run:data-dir'),
28
+ getLogsPathSync: () => electron_1.ipcRenderer.invoke('first-run:logs-dir'),
27
29
  // === 元数据 ===
28
30
  platform: process.platform,
29
31
  });
32
+ //# sourceMappingURL=electron-preload.js.map
package/dist/electron.js CHANGED
@@ -1,5 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
1
3
  /**
2
4
  * Electron 入口 shim — 真正逻辑在 src/electron/main.ts
3
5
  * (保留 src/electron.ts 平铺入口, 不动 package.json 的 dist/electron.js 解析)
4
6
  */
5
- import './electron/main';
7
+ require("./electron/main");
8
+ //# sourceMappingURL=electron.js.map
@@ -10,6 +10,9 @@
10
10
  * - experiment 引擎是 API 供应商不是 CLI, 不支持委派 (提示改用 import)
11
11
  */
12
12
  import { spawn } from 'child_process';
13
+ import * as fs from 'fs/promises';
14
+ import * as path from 'path';
15
+ import * as os from 'os';
13
16
  import { discoverEngines, buildDelegateArgs } from './discovery.js';
14
17
  function delegateTimeoutMs() {
15
18
  const env = Number(process.env.BOLLOON_ENGINE_DELEGATE_TIMEOUT_MS);
@@ -133,6 +136,22 @@ export async function delegateToEngine(id, prompt, opts = {}) {
133
136
  catch { /* noop */ }
134
137
  killTree();
135
138
  const combined = (stdout + (stderr ? `\n[stderr]\n${stderr}` : '')).trim();
139
+ // 2026-07-29: Sidechain transcript — 保存委派完整记录
140
+ try {
141
+ const sidechainDir = path.join(os.homedir(), '.bolloon', 'sidechains');
142
+ fs.mkdir(sidechainDir, { recursive: true });
143
+ const ts = Date.now();
144
+ const filePath = path.join(sidechainDir, `${ts}-${trimmedId}.jsonl`);
145
+ const entry = JSON.stringify({
146
+ ts, engineId: trimmedId, prompt: trimmedPrompt,
147
+ stdout: stdout.slice(0, 100_000),
148
+ stderr: stderr.slice(0, 10_000),
149
+ exitCode: code, duration: Date.now() - ts, model: opts.model || null,
150
+ }) + '\n';
151
+ // fire-and-forget, 不阻塞主流程
152
+ fs.appendFile(filePath, entry, 'utf-8').catch(() => { });
153
+ }
154
+ catch { /* sidechain 写入失败静默 */ }
136
155
  if (code === 0) {
137
156
  resolve({ success: true, output: combined || '(无输出)', exitCode: code });
138
157
  }
@@ -0,0 +1,329 @@
1
+ /**
2
+ * hooks-engine.ts — Phase 4: Hook 机制 (2026-07-29)
3
+ *
4
+ * Claude Code 式 hook 系统: 在 agent 循环的关键点触发外部回调.
5
+ *
6
+ * 设计决策:
7
+ * - 事件驱动 (EventEmitter): 注册 → 触发 → 执行回调
8
+ * - 2 种执行模式: shell (同步/异步 command) + llm (LLM 评估)
9
+ * - 回调可以拒绝/修改/记录/中断流程
10
+ * - 配置从 ~/.bolloon/hooks.yaml 加载
11
+ * - Hook 是零 context 成本 — 不在 prompt 内, 不消耗 token
12
+ */
13
+ import * as fs from 'fs/promises';
14
+ import * as path from 'path';
15
+ import * as os from 'os';
16
+ import { spawn } from 'child_process';
17
+ import { getMinimax } from '../constraints/index.js';
18
+ // ============== 类型 ==============
19
+ /** 支持的 hook 事件类型 */
20
+ export const HOOK_EVENT_TYPES = [
21
+ 'preToolUse', // 工具调用前 — 可返回 {deny, reason} 拒绝
22
+ 'postToolUse', // 工具调用后 — 可注入系统提示
23
+ 'onMessage', // 用户/助手消息后
24
+ 'onSessionStart', // 会话开始
25
+ 'onSessionEnd', // 会话结束
26
+ 'onLoopStart', // ReAct 循环开始
27
+ 'onLoopEnd', // ReAct 循环结束
28
+ 'onError', // 发生 API/工具错误
29
+ ];
30
+ // ============== Hook 引擎 ==============
31
+ export class HooksEngine {
32
+ hooks = new Map();
33
+ configPath;
34
+ constructor(home) {
35
+ this.configPath = path.join(home || os.homedir(), '.bolloon', 'hooks.yaml');
36
+ }
37
+ /** 注册一个 hook */
38
+ register(config) {
39
+ if (!config.id)
40
+ throw new Error('Hook id 必填');
41
+ if (!HOOK_EVENT_TYPES.includes(config.event)) {
42
+ throw new Error(`未知 hook 事件: ${config.event}, 可用: ${HOOK_EVENT_TYPES.join(', ')}`);
43
+ }
44
+ this.hooks.set(config.id, { ...config, enabled: config.enabled ?? true });
45
+ }
46
+ /** 批量注册 */
47
+ registerMany(configs) {
48
+ for (const c of configs)
49
+ this.register(c);
50
+ }
51
+ /** 注销 hook */
52
+ unregister(id) {
53
+ return this.hooks.delete(id);
54
+ }
55
+ /** 获取指定事件的所有启用的 hook */
56
+ getHooks(event) {
57
+ return Array.from(this.hooks.values()).filter(h => h.enabled !== false && h.event === event);
58
+ }
59
+ /** 获取所有 hook */
60
+ listAll() {
61
+ return Array.from(this.hooks.values());
62
+ }
63
+ /** 启用/禁用 */
64
+ setEnabled(id, enabled) {
65
+ const h = this.hooks.get(id);
66
+ if (!h)
67
+ return false;
68
+ h.enabled = enabled;
69
+ return true;
70
+ }
71
+ /**
72
+ * 触发一个事件, 执行所有匹配的 hook.
73
+ * 返回 HookResult 数组.
74
+ *
75
+ * preToolUse 的 deny 结果: 如果任意 hook 返回 deny=true, 该工具被拒绝.
76
+ */
77
+ async fire(event, ctx) {
78
+ const matched = this.getHooks(event);
79
+ if (matched.length === 0)
80
+ return [];
81
+ const results = [];
82
+ for (const hook of matched) {
83
+ // preToolUse 工具过滤
84
+ if (event === 'preToolUse' && hook.toolFilter && ctx.toolName) {
85
+ if (!hook.toolFilter.includes(ctx.toolName))
86
+ continue;
87
+ }
88
+ try {
89
+ const result = await this.executeHook(hook, ctx);
90
+ results.push(result);
91
+ }
92
+ catch (e) {
93
+ results.push({
94
+ id: hook.id,
95
+ error: String(e),
96
+ elapsedMs: 0,
97
+ });
98
+ }
99
+ }
100
+ return results;
101
+ }
102
+ /**
103
+ * 检查 preToolUse hook 是否拒绝当前工具.
104
+ * 返回第一个 deny 的 HookResult, 或 null.
105
+ */
106
+ async checkToolUse(toolName, args) {
107
+ const results = await this.fire('preToolUse', {
108
+ event: 'preToolUse',
109
+ toolName,
110
+ toolArgs: args,
111
+ });
112
+ for (const r of results) {
113
+ if (r.deny)
114
+ return r;
115
+ }
116
+ return null;
117
+ }
118
+ /**
119
+ * 执行单个 hook.
120
+ */
121
+ async executeHook(hook, ctx) {
122
+ const start = Date.now();
123
+ const timeoutMs = hook.timeoutMs ?? 5000;
124
+ if (hook.mode === 'shell' && hook.command) {
125
+ return this.execShell(hook, ctx, start, timeoutMs);
126
+ }
127
+ if (hook.mode === 'llm' && hook.prompt) {
128
+ return this.execLlm(hook, ctx, start, timeoutMs);
129
+ }
130
+ return {
131
+ id: hook.id,
132
+ error: `不支持的执行模式: ${hook.mode}`,
133
+ elapsedMs: Date.now() - start,
134
+ };
135
+ }
136
+ /**
137
+ * shell 模式: spawn 子进程执行命令.
138
+ * 环境变量 HOOK_EVENT / HOOK_TOOL / HOOK_ARGS / HOOK_RESULT 传递上下文.
139
+ */
140
+ async execShell(hook, ctx, start, timeoutMs) {
141
+ return new Promise((resolve) => {
142
+ const env = {
143
+ ...process.env,
144
+ HOOK_EVENT: ctx.event,
145
+ HOOK_TOOL: ctx.toolName || '',
146
+ HOOK_ARGS: ctx.toolArgs ? JSON.stringify(ctx.toolArgs) : '',
147
+ HOOK_RESULT: ctx.toolResult ? JSON.stringify(ctx.toolResult).slice(0, 2000) : '',
148
+ HOOK_MESSAGE: ctx.messageContent || '',
149
+ HOOK_CHANNEL: ctx.channelId || '',
150
+ HOOK_SESSION: ctx.sessionId || '',
151
+ HOOK_ERROR: ctx.error || '',
152
+ };
153
+ const proc = spawn('sh', ['-c', hook.command], {
154
+ env,
155
+ stdio: ['ignore', 'pipe', 'pipe'],
156
+ timeout: timeoutMs,
157
+ });
158
+ let stdout = '';
159
+ let stderr = '';
160
+ proc.stdout.on('data', (d) => { stdout += d.toString(); });
161
+ proc.stderr.on('data', (d) => { stderr += d.toString(); });
162
+ const timer = setTimeout(() => {
163
+ proc.kill('SIGTERM');
164
+ resolve({
165
+ id: hook.id,
166
+ rawOutput: stdout.slice(0, 1000),
167
+ timedOut: true,
168
+ elapsedMs: Date.now() - start,
169
+ });
170
+ }, timeoutMs);
171
+ proc.on('close', (code) => {
172
+ clearTimeout(timer);
173
+ const elapsed = Date.now() - start;
174
+ // 解析 stdout: 如果是 JSON, 提取 deny/系统注入
175
+ let deny = false;
176
+ let reason = '';
177
+ let systemAddition = '';
178
+ try {
179
+ const parsed = JSON.parse(stdout.trim());
180
+ if (parsed.deny) {
181
+ deny = true;
182
+ reason = parsed.reason || 'Hook 拒绝';
183
+ }
184
+ if (parsed.systemAddition) {
185
+ systemAddition = parsed.systemAddition;
186
+ }
187
+ }
188
+ catch {
189
+ // 不是 JSON, 当普通输出
190
+ }
191
+ resolve({
192
+ id: hook.id,
193
+ deny,
194
+ reason,
195
+ systemAddition,
196
+ rawOutput: stdout.slice(0, 1000),
197
+ elapsedMs: elapsed,
198
+ error: code !== 0 ? `exit ${code}: ${stderr.slice(0, 200)}` : undefined,
199
+ });
200
+ });
201
+ proc.on('error', (err) => {
202
+ clearTimeout(timer);
203
+ resolve({
204
+ id: hook.id,
205
+ error: String(err),
206
+ elapsedMs: Date.now() - start,
207
+ });
208
+ });
209
+ });
210
+ }
211
+ /**
212
+ * LLM 模式: 用当前 LLM 评估 hook prompt.
213
+ * 将上下文注入 prompt, 获取 LLM 的判断.
214
+ */
215
+ async execLlm(hook, ctx, start, timeoutMs) {
216
+ try {
217
+ const minimax = getMinimax();
218
+ if (!minimax) {
219
+ return { id: hook.id, error: 'LLM 不可用 (minimax 未初始化)', elapsedMs: Date.now() - start };
220
+ }
221
+ // 构建评估 prompt
222
+ const contextStr = [
223
+ `事件: ${ctx.event}`,
224
+ ctx.toolName ? `工具: ${ctx.toolName}` : '',
225
+ ctx.toolArgs ? `参数: ${JSON.stringify(ctx.toolArgs).slice(0, 500)}` : '',
226
+ ctx.toolResult ? `结果: ${JSON.stringify(ctx.toolResult).slice(0, 500)}` : '',
227
+ ctx.messageContent ? `消息: ${ctx.messageContent.slice(0, 500)}` : '',
228
+ ctx.error ? `错误: ${ctx.error}` : '',
229
+ ].filter(Boolean).join('\n');
230
+ const fullPrompt = `【Hook 评估】\n${contextStr}\n\n【规则】\n${hook.prompt}\n\n请以 JSON 格式回答, 包含字段: deny (boolean), reason (string), systemAddition (string, 可选).`;
231
+ const response = await minimax.chat(fullPrompt, '', undefined);
232
+ const reply = (response.reply || '').trim();
233
+ let parsed;
234
+ try {
235
+ // 尝试提取 JSON
236
+ const jsonMatch = reply.match(/\{[\s\S]*\}/);
237
+ parsed = jsonMatch ? JSON.parse(jsonMatch[0]) : { deny: false };
238
+ }
239
+ catch {
240
+ parsed = { deny: false };
241
+ }
242
+ return {
243
+ id: hook.id,
244
+ deny: parsed.deny === true,
245
+ reason: parsed.reason || '',
246
+ systemAddition: parsed.systemAddition || '',
247
+ rawOutput: reply.slice(0, 1000),
248
+ elapsedMs: Date.now() - start,
249
+ };
250
+ }
251
+ catch (e) {
252
+ return {
253
+ id: hook.id,
254
+ error: String(e),
255
+ elapsedMs: Date.now() - start,
256
+ };
257
+ }
258
+ }
259
+ /**
260
+ * 从 ~/.bolloon/hooks.yaml 加载配置.
261
+ * 失败静默 (没有 hook 配置也正常).
262
+ */
263
+ async loadFromConfig() {
264
+ try {
265
+ const raw = await fs.readFile(this.configPath, 'utf-8');
266
+ // 简单解析 YAML-like: id/event/mode/command/prompt/toolFilter
267
+ const lines = raw.split('\n');
268
+ let current = {};
269
+ for (const line of lines) {
270
+ const trimmed = line.trim();
271
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('---'))
272
+ continue;
273
+ // hook 分隔: 空行或 ---
274
+ if (trimmed === '---' || (trimmed.length === 0 && current.id)) {
275
+ if (current.id && current.event) {
276
+ this.register(current);
277
+ }
278
+ current = {};
279
+ continue;
280
+ }
281
+ const sep = trimmed.indexOf(':');
282
+ if (sep < 0)
283
+ continue;
284
+ const key = trimmed.slice(0, sep).trim();
285
+ const val = trimmed.slice(sep + 1).trim();
286
+ switch (key) {
287
+ case 'id':
288
+ current.id = val;
289
+ break;
290
+ case 'event':
291
+ current.event = val;
292
+ break;
293
+ case 'mode':
294
+ current.mode = val;
295
+ break;
296
+ case 'command':
297
+ current.command = val;
298
+ break;
299
+ case 'prompt':
300
+ current.prompt = val;
301
+ break;
302
+ case 'timeout_ms':
303
+ current.timeoutMs = parseInt(val, 10) || 5000;
304
+ break;
305
+ case 'enabled':
306
+ current.enabled = val === 'true';
307
+ break;
308
+ case 'description':
309
+ current.description = val;
310
+ break;
311
+ case 'tool_filter':
312
+ current.toolFilter = val.split(',').map(s => s.trim()).filter(Boolean);
313
+ break;
314
+ }
315
+ }
316
+ // 最后一条
317
+ if (current.id && current.event) {
318
+ this.register(current);
319
+ }
320
+ }
321
+ catch {
322
+ // 文件不存在 / 无法解析 — 静默
323
+ }
324
+ }
325
+ /** 获取默认配置路径 */
326
+ getConfigPath() {
327
+ return this.configPath;
328
+ }
329
+ }