remnote-bridge 0.1.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.
Files changed (135) hide show
  1. package/dist/cli/commands/connect.d.ts +12 -0
  2. package/dist/cli/commands/connect.js +124 -0
  3. package/dist/cli/commands/disconnect.d.ts +11 -0
  4. package/dist/cli/commands/disconnect.js +100 -0
  5. package/dist/cli/commands/edit-rem.d.ts +13 -0
  6. package/dist/cli/commands/edit-rem.js +83 -0
  7. package/dist/cli/commands/edit-tree.d.ts +14 -0
  8. package/dist/cli/commands/edit-tree.js +67 -0
  9. package/dist/cli/commands/health.d.ts +12 -0
  10. package/dist/cli/commands/health.js +100 -0
  11. package/dist/cli/commands/install-skill.d.ts +6 -0
  12. package/dist/cli/commands/install-skill.js +39 -0
  13. package/dist/cli/commands/read-context.d.ts +20 -0
  14. package/dist/cli/commands/read-context.js +77 -0
  15. package/dist/cli/commands/read-globe.d.ts +16 -0
  16. package/dist/cli/commands/read-globe.js +60 -0
  17. package/dist/cli/commands/read-rem.d.ts +16 -0
  18. package/dist/cli/commands/read-rem.js +80 -0
  19. package/dist/cli/commands/read-tree.d.ts +17 -0
  20. package/dist/cli/commands/read-tree.js +85 -0
  21. package/dist/cli/commands/search.d.ts +12 -0
  22. package/dist/cli/commands/search.js +65 -0
  23. package/dist/cli/config.d.ts +55 -0
  24. package/dist/cli/config.js +139 -0
  25. package/dist/cli/daemon/daemon.d.ts +11 -0
  26. package/dist/cli/daemon/daemon.js +186 -0
  27. package/dist/cli/daemon/dev-server.d.ts +26 -0
  28. package/dist/cli/daemon/dev-server.js +81 -0
  29. package/dist/cli/daemon/pid.d.ts +34 -0
  30. package/dist/cli/daemon/pid.js +67 -0
  31. package/dist/cli/daemon/send-request.d.ts +24 -0
  32. package/dist/cli/daemon/send-request.js +92 -0
  33. package/dist/cli/handlers/context-read-handler.d.ts +18 -0
  34. package/dist/cli/handlers/context-read-handler.js +24 -0
  35. package/dist/cli/handlers/edit-handler.d.ts +30 -0
  36. package/dist/cli/handlers/edit-handler.js +133 -0
  37. package/dist/cli/handlers/globe-read-handler.d.ts +17 -0
  38. package/dist/cli/handlers/globe-read-handler.js +23 -0
  39. package/dist/cli/handlers/read-handler.d.ts +16 -0
  40. package/dist/cli/handlers/read-handler.js +78 -0
  41. package/dist/cli/handlers/rem-cache.d.ts +19 -0
  42. package/dist/cli/handlers/rem-cache.js +63 -0
  43. package/dist/cli/handlers/tree-edit-handler.d.ts +30 -0
  44. package/dist/cli/handlers/tree-edit-handler.js +188 -0
  45. package/dist/cli/handlers/tree-parser.d.ts +95 -0
  46. package/dist/cli/handlers/tree-parser.js +506 -0
  47. package/dist/cli/handlers/tree-read-handler.d.ts +28 -0
  48. package/dist/cli/handlers/tree-read-handler.js +53 -0
  49. package/dist/cli/main.d.ts +7 -0
  50. package/dist/cli/main.js +300 -0
  51. package/dist/cli/protocol.d.ts +39 -0
  52. package/dist/cli/protocol.js +35 -0
  53. package/dist/cli/server/config-server.d.ts +26 -0
  54. package/dist/cli/server/config-server.js +363 -0
  55. package/dist/cli/server/ws-server.d.ts +68 -0
  56. package/dist/cli/server/ws-server.js +335 -0
  57. package/dist/cli/utils/output.d.ts +11 -0
  58. package/dist/cli/utils/output.js +13 -0
  59. package/dist/mcp/daemon-client.d.ts +31 -0
  60. package/dist/mcp/daemon-client.js +99 -0
  61. package/dist/mcp/index.d.ts +7 -0
  62. package/dist/mcp/index.js +68 -0
  63. package/dist/mcp/instructions.d.ts +1 -0
  64. package/dist/mcp/instructions.js +249 -0
  65. package/dist/mcp/resources/edit-tree-guide.d.ts +1 -0
  66. package/dist/mcp/resources/edit-tree-guide.js +197 -0
  67. package/dist/mcp/resources/error-reference.d.ts +1 -0
  68. package/dist/mcp/resources/error-reference.js +132 -0
  69. package/dist/mcp/resources/outline-format.d.ts +1 -0
  70. package/dist/mcp/resources/outline-format.js +104 -0
  71. package/dist/mcp/resources/rem-object-fields.d.ts +1 -0
  72. package/dist/mcp/resources/rem-object-fields.js +331 -0
  73. package/dist/mcp/resources/separator-flashcard.d.ts +1 -0
  74. package/dist/mcp/resources/separator-flashcard.js +120 -0
  75. package/dist/mcp/tools/edit-tools.d.ts +5 -0
  76. package/dist/mcp/tools/edit-tools.js +47 -0
  77. package/dist/mcp/tools/infra-tools.d.ts +5 -0
  78. package/dist/mcp/tools/infra-tools.js +43 -0
  79. package/dist/mcp/tools/read-tools.d.ts +5 -0
  80. package/dist/mcp/tools/read-tools.js +195 -0
  81. package/dist/mcp/types.d.ts +12 -0
  82. package/dist/mcp/types.js +4 -0
  83. package/docs/instruction/connect.md +158 -0
  84. package/docs/instruction/disconnect.md +146 -0
  85. package/docs/instruction/edit-rem.md +509 -0
  86. package/docs/instruction/edit-tree.md +419 -0
  87. package/docs/instruction/health.md +159 -0
  88. package/docs/instruction/overall.md +751 -0
  89. package/docs/instruction/read-context.md +353 -0
  90. package/docs/instruction/read-globe.md +206 -0
  91. package/docs/instruction/read-rem.md +476 -0
  92. package/docs/instruction/read-tree.md +428 -0
  93. package/docs/instruction/search.md +196 -0
  94. package/package.json +41 -0
  95. package/remnote-plugin/package.json +48 -0
  96. package/remnote-plugin/postcss.config.js +5 -0
  97. package/remnote-plugin/public/bridge-icon.svg +8 -0
  98. package/remnote-plugin/public/manifest.json +22 -0
  99. package/remnote-plugin/src/bridge/message-router.ts +57 -0
  100. package/remnote-plugin/src/bridge/websocket-client.ts +245 -0
  101. package/remnote-plugin/src/index.css +1 -0
  102. package/remnote-plugin/src/services/breadcrumb.ts +26 -0
  103. package/remnote-plugin/src/services/create-rem.ts +59 -0
  104. package/remnote-plugin/src/services/delete-rem.ts +29 -0
  105. package/remnote-plugin/src/services/index.ts +16 -0
  106. package/remnote-plugin/src/services/move-rem.ts +39 -0
  107. package/remnote-plugin/src/services/powerup-filter.ts +31 -0
  108. package/remnote-plugin/src/services/read-context.ts +368 -0
  109. package/remnote-plugin/src/services/read-globe.ts +197 -0
  110. package/remnote-plugin/src/services/read-rem.ts +284 -0
  111. package/remnote-plugin/src/services/read-tree.ts +222 -0
  112. package/remnote-plugin/src/services/rem-builder.ts +124 -0
  113. package/remnote-plugin/src/services/reorder-children.ts +61 -0
  114. package/remnote-plugin/src/services/search.ts +56 -0
  115. package/remnote-plugin/src/services/write-rem-fields.ts +254 -0
  116. package/remnote-plugin/src/settings.ts +12 -0
  117. package/remnote-plugin/src/style.css +45 -0
  118. package/remnote-plugin/src/test-scripts/AGENTS.md +46 -0
  119. package/remnote-plugin/src/test-scripts/test-actions.ts +230 -0
  120. package/remnote-plugin/src/test-scripts/test-powerup-rendering.ts +722 -0
  121. package/remnote-plugin/src/test-scripts/test-rem-type-mapping.ts +283 -0
  122. package/remnote-plugin/src/test-scripts/test-richtext-builder.ts +207 -0
  123. package/remnote-plugin/src/test-scripts/test-richtext-matrix.ts +332 -0
  124. package/remnote-plugin/src/test-scripts/test-richtext-remaining.ts +245 -0
  125. package/remnote-plugin/src/test-scripts/test-rw-fields.ts +399 -0
  126. package/remnote-plugin/src/types.ts +419 -0
  127. package/remnote-plugin/src/utils/elision.ts +45 -0
  128. package/remnote-plugin/src/utils/index.ts +10 -0
  129. package/remnote-plugin/src/utils/tree-serializer.ts +269 -0
  130. package/remnote-plugin/src/widgets/bridge_widget.tsx +170 -0
  131. package/remnote-plugin/src/widgets/index.tsx +82 -0
  132. package/remnote-plugin/tailwind.config.js +7 -0
  133. package/remnote-plugin/tsconfig.json +21 -0
  134. package/remnote-plugin/webpack.config.js +125 -0
  135. package/skill/SKILL.md +428 -0
@@ -0,0 +1,186 @@
1
+ /**
2
+ * 守护进程主逻辑
3
+ *
4
+ * 作为 fork 子进程运行:
5
+ * 1. 启动 WS Server
6
+ * 2. 启动 webpack-dev-server 子进程
7
+ * 3. 写入 PID 文件
8
+ * 4. 管理自动超时关闭
9
+ * 5. 通过 IPC 向父进程发送 ready 信号
10
+ */
11
+ import path from 'path';
12
+ import fs from 'fs';
13
+ import { BridgeServer } from '../server/ws-server.js';
14
+ import { ConfigServer } from '../server/config-server.js';
15
+ import { DevServerManager } from './dev-server.js';
16
+ import { writePid, removePid } from './pid.js';
17
+ import { loadConfig, pidFilePath, logFilePath, findProjectRoot } from '../config.js';
18
+ let shutdownInProgress = false;
19
+ async function main() {
20
+ const projectRoot = findProjectRoot();
21
+ let config = loadConfig(projectRoot);
22
+ const pidPath = pidFilePath(projectRoot);
23
+ const logPath = logFilePath(projectRoot);
24
+ // 日志写入文件(追加模式,保留前次会话日志)
25
+ const logStream = fs.createWriteStream(logPath, { flags: 'a' });
26
+ logStream.write(`\n${'='.repeat(60)}\n[${new Date().toISOString()}] 守护进程启动 (PID: ${process.pid})\n${'='.repeat(60)}\n`);
27
+ function log(message, level = 'info') {
28
+ const timestamp = new Date().toISOString();
29
+ const line = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
30
+ logStream.write(line);
31
+ }
32
+ // 超时管理
33
+ let timeoutMs = config.daemonTimeoutMinutes * 60 * 1000;
34
+ let timeoutTimer;
35
+ let lastResetTime = Date.now();
36
+ function resetTimeout() {
37
+ clearTimeout(timeoutTimer);
38
+ lastResetTime = Date.now();
39
+ timeoutTimer = setTimeout(() => {
40
+ log('超时关闭(无 CLI 交互)');
41
+ shutdown();
42
+ }, timeoutMs);
43
+ }
44
+ function getTimeoutRemaining() {
45
+ const elapsed = Date.now() - lastResetTime;
46
+ return Math.max(0, Math.floor((timeoutMs - elapsed) / 1000));
47
+ }
48
+ // 创建 WS Server(抽取为函数,供软重启复用)
49
+ function createServer(cfg) {
50
+ const srv = new BridgeServer({
51
+ port: cfg.wsPort,
52
+ host: '127.0.0.1',
53
+ onLog: log,
54
+ getTimeoutRemaining,
55
+ defaults: cfg.defaults,
56
+ });
57
+ srv.onCliRequest = () => resetTimeout();
58
+ return srv;
59
+ }
60
+ let server = createServer(config);
61
+ // 软重启:关闭旧 WS Server → 重新读配置 → 创建新 WS Server → 启动
62
+ async function reload() {
63
+ log('开始软重启...');
64
+ try {
65
+ await server.stop();
66
+ log('旧 WS Server 已关闭');
67
+ }
68
+ catch (err) {
69
+ log(`旧 WS Server 关闭失败: ${err}`, 'error');
70
+ }
71
+ config = loadConfig(projectRoot);
72
+ timeoutMs = config.daemonTimeoutMinutes * 60 * 1000;
73
+ server = createServer(config);
74
+ await server.start();
75
+ log(`新 WS Server 已启动 (端口 ${config.wsPort})`);
76
+ resetTimeout();
77
+ log('软重启完成');
78
+ }
79
+ // 启动 ConfigServer
80
+ const configServer = new ConfigServer({
81
+ port: config.configPort,
82
+ host: '127.0.0.1',
83
+ projectRoot,
84
+ onRestart: reload,
85
+ onLog: log,
86
+ });
87
+ // 启动 webpack-dev-server
88
+ // 从包安装路径计算 pluginDir(dist/cli/daemon/daemon.js → 包根/remnote-plugin)
89
+ const packageRoot = path.resolve(import.meta.dirname, '..', '..', '..');
90
+ const pluginDir = path.join(packageRoot, 'remnote-plugin');
91
+ const devServer = new DevServerManager({
92
+ pluginDir,
93
+ port: config.devServerPort,
94
+ onLog: log,
95
+ onExit: (code) => {
96
+ if (!shutdownInProgress && code !== 0) {
97
+ log('webpack-dev-server 异常退出,守护进程关闭', 'error');
98
+ shutdown();
99
+ }
100
+ },
101
+ });
102
+ async function shutdown() {
103
+ if (shutdownInProgress)
104
+ return;
105
+ shutdownInProgress = true;
106
+ log('开始优雅关闭...');
107
+ clearTimeout(timeoutTimer);
108
+ try {
109
+ await server.stop();
110
+ log('WS Server 已关闭');
111
+ }
112
+ catch (err) {
113
+ log(`WS Server 关闭失败: ${err}`, 'error');
114
+ }
115
+ try {
116
+ await configServer.stop();
117
+ log('ConfigServer 已关闭');
118
+ }
119
+ catch (err) {
120
+ log(`ConfigServer 关闭失败: ${err}`, 'error');
121
+ }
122
+ try {
123
+ await devServer.stop();
124
+ log('webpack-dev-server 已关闭');
125
+ }
126
+ catch (err) {
127
+ log(`webpack-dev-server 关闭失败: ${err}`, 'error');
128
+ }
129
+ removePid(pidPath);
130
+ log('PID 文件已删除');
131
+ logStream.end(() => {
132
+ process.exit(0);
133
+ });
134
+ }
135
+ // 信号处理
136
+ process.on('SIGTERM', shutdown);
137
+ process.on('SIGINT', shutdown);
138
+ try {
139
+ await server.start();
140
+ log(`WS Server 已启动 (端口 ${config.wsPort})`);
141
+ }
142
+ catch (err) {
143
+ log(`WS Server 启动失败: ${err}`, 'error');
144
+ process.send?.({ type: 'error', message: `WS Server 启动失败: ${err}` });
145
+ process.exit(1);
146
+ }
147
+ try {
148
+ await configServer.start();
149
+ log(`ConfigServer 已启动 (端口 ${config.configPort})`);
150
+ }
151
+ catch (err) {
152
+ log(`ConfigServer 启动失败: ${err}`, 'warn');
153
+ // ConfigServer 非关键,启动失败不阻塞
154
+ }
155
+ try {
156
+ devServer.start();
157
+ log(`webpack-dev-server 启动中 (端口 ${config.devServerPort})`);
158
+ }
159
+ catch (err) {
160
+ log(`webpack-dev-server 启动失败: ${err}`, 'error');
161
+ await server.stop();
162
+ process.send?.({ type: 'error', message: `webpack-dev-server 启动失败: ${err}` });
163
+ process.exit(1);
164
+ }
165
+ // 写入 PID 文件
166
+ writePid(pidPath, process.pid);
167
+ log(`PID 文件已写入: ${pidPath} (PID: ${process.pid})`);
168
+ // 启动超时计时器
169
+ resetTimeout();
170
+ // 通知父进程就绪
171
+ process.send?.({
172
+ type: 'ready',
173
+ wsPort: config.wsPort,
174
+ devServerPort: config.devServerPort,
175
+ configPort: config.configPort,
176
+ pid: process.pid,
177
+ });
178
+ // 断开 IPC 通道(让父进程可以退出)
179
+ if (process.channel) {
180
+ process.channel.unref();
181
+ }
182
+ }
183
+ main().catch((err) => {
184
+ console.error('守护进程启动失败:', err);
185
+ process.exit(1);
186
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * webpack-dev-server 子进程管理
3
+ *
4
+ * 在 remnote-plugin 目录下启动 npm run dev。
5
+ */
6
+ export interface DevServerOptions {
7
+ pluginDir: string;
8
+ port: number;
9
+ onLog?: (message: string, level: 'info' | 'warn' | 'error') => void;
10
+ onExit?: (code: number | null) => void;
11
+ }
12
+ export declare class DevServerManager {
13
+ private child;
14
+ private options;
15
+ constructor(options: DevServerOptions);
16
+ /**
17
+ * 启动 webpack-dev-server。
18
+ * 如果 remnote-plugin 目录不存在,抛出错误。
19
+ */
20
+ start(): void;
21
+ /**
22
+ * 停止 webpack-dev-server。
23
+ */
24
+ stop(): Promise<void>;
25
+ isRunning(): boolean;
26
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * webpack-dev-server 子进程管理
3
+ *
4
+ * 在 remnote-plugin 目录下启动 npm run dev。
5
+ */
6
+ import { spawn, execSync } from 'child_process';
7
+ import path from 'path';
8
+ import fs from 'fs';
9
+ export class DevServerManager {
10
+ child = null;
11
+ options;
12
+ constructor(options) {
13
+ this.options = options;
14
+ }
15
+ /**
16
+ * 启动 webpack-dev-server。
17
+ * 如果 remnote-plugin 目录不存在,抛出错误。
18
+ */
19
+ start() {
20
+ const { pluginDir, port, onLog, onExit } = this.options;
21
+ if (!fs.existsSync(path.join(pluginDir, 'package.json'))) {
22
+ throw new Error(`Plugin 目录不存在或缺少 package.json: ${pluginDir}`);
23
+ }
24
+ // 首次使用时自动安装 plugin 依赖
25
+ if (!fs.existsSync(path.join(pluginDir, 'node_modules'))) {
26
+ onLog?.('[dev-server] remnote-plugin/node_modules 不存在,正在安装依赖...', 'info');
27
+ execSync('npm install', { cwd: pluginDir, stdio: 'pipe' });
28
+ onLog?.('[dev-server] 依赖安装完成', 'info');
29
+ }
30
+ // 通过环境变量传递端口
31
+ this.child = spawn('npm', ['run', 'dev'], {
32
+ cwd: pluginDir,
33
+ env: { ...process.env, PORT: String(port) },
34
+ stdio: 'pipe',
35
+ });
36
+ this.child.stdout?.on('data', (data) => {
37
+ onLog?.(`[dev-server] ${data.toString().trim()}`, 'info');
38
+ });
39
+ this.child.stderr?.on('data', (data) => {
40
+ onLog?.(`[dev-server] ${data.toString().trim()}`, 'warn');
41
+ });
42
+ this.child.on('exit', (code) => {
43
+ onLog?.(`webpack-dev-server 退出 (code: ${code})`, code === 0 ? 'info' : 'error');
44
+ this.child = null;
45
+ onExit?.(code);
46
+ });
47
+ this.child.on('error', (err) => {
48
+ onLog?.(`webpack-dev-server 启动失败: ${err.message}`, 'error');
49
+ this.child = null;
50
+ onExit?.(1);
51
+ });
52
+ }
53
+ /**
54
+ * 停止 webpack-dev-server。
55
+ */
56
+ stop() {
57
+ return new Promise((resolve) => {
58
+ if (!this.child) {
59
+ resolve();
60
+ return;
61
+ }
62
+ // 5 秒后强制 kill
63
+ const forceKillTimer = setTimeout(() => {
64
+ if (this.child) {
65
+ this.child.kill('SIGKILL');
66
+ this.child = null;
67
+ resolve();
68
+ }
69
+ }, 5000);
70
+ this.child.on('exit', () => {
71
+ clearTimeout(forceKillTimer);
72
+ this.child = null;
73
+ resolve();
74
+ });
75
+ this.child.kill('SIGTERM');
76
+ });
77
+ }
78
+ isRunning() {
79
+ return this.child !== null && !this.child.killed;
80
+ }
81
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * PID 文件管理
3
+ *
4
+ * 写入、读取、stale 检测、清理。
5
+ */
6
+ /**
7
+ * 写入 PID 文件
8
+ */
9
+ export declare function writePid(filePath: string, pid: number): void;
10
+ /**
11
+ * 读取 PID 文件。文件不存在返回 null。
12
+ */
13
+ export declare function readPid(filePath: string): number | null;
14
+ /**
15
+ * 删除 PID 文件
16
+ */
17
+ export declare function removePid(filePath: string): void;
18
+ /**
19
+ * 检查进程是否存活
20
+ */
21
+ export declare function isProcessAlive(pid: number): boolean;
22
+ /**
23
+ * 检查守护进程状态。返回:
24
+ * - { running: true, pid } — 守护进程正在运行
25
+ * - { running: false } — 守护进程未运行(无 PID 文件或 stale)
26
+ *
27
+ * 若 PID 文件存在但进程已死(stale),自动清理 PID 文件。
28
+ */
29
+ export declare function checkDaemon(pidPath: string): {
30
+ running: true;
31
+ pid: number;
32
+ } | {
33
+ running: false;
34
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * PID 文件管理
3
+ *
4
+ * 写入、读取、stale 检测、清理。
5
+ */
6
+ import fs from 'fs';
7
+ /**
8
+ * 写入 PID 文件
9
+ */
10
+ export function writePid(filePath, pid) {
11
+ fs.writeFileSync(filePath, String(pid), 'utf-8');
12
+ }
13
+ /**
14
+ * 读取 PID 文件。文件不存在返回 null。
15
+ */
16
+ export function readPid(filePath) {
17
+ try {
18
+ const content = fs.readFileSync(filePath, 'utf-8').trim();
19
+ const pid = parseInt(content, 10);
20
+ return isNaN(pid) ? null : pid;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ /**
27
+ * 删除 PID 文件
28
+ */
29
+ export function removePid(filePath) {
30
+ try {
31
+ fs.unlinkSync(filePath);
32
+ }
33
+ catch {
34
+ // 文件不存在也没关系
35
+ }
36
+ }
37
+ /**
38
+ * 检查进程是否存活
39
+ */
40
+ export function isProcessAlive(pid) {
41
+ try {
42
+ process.kill(pid, 0);
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ }
49
+ /**
50
+ * 检查守护进程状态。返回:
51
+ * - { running: true, pid } — 守护进程正在运行
52
+ * - { running: false } — 守护进程未运行(无 PID 文件或 stale)
53
+ *
54
+ * 若 PID 文件存在但进程已死(stale),自动清理 PID 文件。
55
+ */
56
+ export function checkDaemon(pidPath) {
57
+ const pid = readPid(pidPath);
58
+ if (pid === null) {
59
+ return { running: false };
60
+ }
61
+ if (isProcessAlive(pid)) {
62
+ return { running: true, pid };
63
+ }
64
+ // stale PID 文件,清理
65
+ removePid(pidPath);
66
+ return { running: false };
67
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Daemon 通信工具 — CLI 命令向守护进程发送请求的通用方法
3
+ *
4
+ * 封装 WS 连接建立、请求发送、响应等待、超时处理的完整流程。
5
+ * 所有业务命令(health、read-rem、edit-rem 等)均通过此函数与 daemon 通信。
6
+ */
7
+ export declare class DaemonNotRunningError extends Error {
8
+ constructor();
9
+ }
10
+ export declare class DaemonUnreachableError extends Error {
11
+ constructor(cause: string);
12
+ }
13
+ /**
14
+ * 向守护进程发送请求并等待响应。
15
+ *
16
+ * 流程:读取 PID 文件 → 建立 WS 连接 → 发送 BridgeRequest → 等待 BridgeResponse → 关闭连接
17
+ *
18
+ * @throws DaemonNotRunningError — PID 文件不存在或进程已死
19
+ * @throws DaemonUnreachableError — WS 连接失败
20
+ * @throws Error — daemon 返回 error 字段或响应超时
21
+ */
22
+ export declare function sendDaemonRequest(action: string, payload?: Record<string, unknown>, options?: {
23
+ timeout?: number;
24
+ }): Promise<unknown>;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Daemon 通信工具 — CLI 命令向守护进程发送请求的通用方法
3
+ *
4
+ * 封装 WS 连接建立、请求发送、响应等待、超时处理的完整流程。
5
+ * 所有业务命令(health、read-rem、edit-rem 等)均通过此函数与 daemon 通信。
6
+ */
7
+ import WebSocket from 'ws';
8
+ import crypto from 'crypto';
9
+ import { loadConfig, pidFilePath, findProjectRoot } from '../config.js';
10
+ import { checkDaemon } from './pid.js';
11
+ import { isBridgeResponse } from '../protocol.js';
12
+ const CONNECT_TIMEOUT_MS = 5_000;
13
+ const DEFAULT_RESPONSE_TIMEOUT_MS = 30_000;
14
+ export class DaemonNotRunningError extends Error {
15
+ constructor() {
16
+ super('守护进程未运行,请先执行 remnote-bridge connect');
17
+ this.name = 'DaemonNotRunningError';
18
+ }
19
+ }
20
+ export class DaemonUnreachableError extends Error {
21
+ constructor(cause) {
22
+ super(`无法连接守护进程: ${cause}`);
23
+ this.name = 'DaemonUnreachableError';
24
+ }
25
+ }
26
+ /**
27
+ * 向守护进程发送请求并等待响应。
28
+ *
29
+ * 流程:读取 PID 文件 → 建立 WS 连接 → 发送 BridgeRequest → 等待 BridgeResponse → 关闭连接
30
+ *
31
+ * @throws DaemonNotRunningError — PID 文件不存在或进程已死
32
+ * @throws DaemonUnreachableError — WS 连接失败
33
+ * @throws Error — daemon 返回 error 字段或响应超时
34
+ */
35
+ export async function sendDaemonRequest(action, payload = {}, options) {
36
+ const projectRoot = findProjectRoot();
37
+ const config = loadConfig(projectRoot);
38
+ const pidPath = pidFilePath(projectRoot);
39
+ // 检查 daemon 是否运行
40
+ const daemonStatus = checkDaemon(pidPath);
41
+ if (!daemonStatus.running) {
42
+ throw new DaemonNotRunningError();
43
+ }
44
+ const responseTimeout = options?.timeout ?? DEFAULT_RESPONSE_TIMEOUT_MS;
45
+ return new Promise((resolve, reject) => {
46
+ const ws = new WebSocket(`ws://127.0.0.1:${config.wsPort}`);
47
+ const requestId = crypto.randomUUID();
48
+ let responseTimer = null;
49
+ const connectTimer = setTimeout(() => {
50
+ ws.terminate();
51
+ reject(new DaemonUnreachableError('连接超时'));
52
+ }, CONNECT_TIMEOUT_MS);
53
+ ws.on('open', () => {
54
+ clearTimeout(connectTimer);
55
+ responseTimer = setTimeout(() => {
56
+ ws.terminate();
57
+ reject(new Error(`请求超时 (${responseTimeout / 1000}s): ${action}`));
58
+ }, responseTimeout);
59
+ ws.on('message', (data) => {
60
+ try {
61
+ const msg = JSON.parse(data.toString());
62
+ if (isBridgeResponse(msg) && msg.id === requestId) {
63
+ if (responseTimer)
64
+ clearTimeout(responseTimer);
65
+ ws.close();
66
+ if (msg.error) {
67
+ reject(new Error(msg.error));
68
+ }
69
+ else {
70
+ resolve(msg.result);
71
+ }
72
+ }
73
+ }
74
+ catch {
75
+ // 忽略非 JSON 消息
76
+ }
77
+ });
78
+ // 发送请求
79
+ ws.send(JSON.stringify({
80
+ id: requestId,
81
+ action,
82
+ payload,
83
+ }));
84
+ });
85
+ ws.on('error', (err) => {
86
+ clearTimeout(connectTimer);
87
+ if (responseTimer)
88
+ clearTimeout(responseTimer);
89
+ reject(new DaemonUnreachableError(err.message));
90
+ });
91
+ });
92
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ContextReadHandler — read-context 请求的业务编排
3
+ *
4
+ * 职责:转发到 Plugin 获取上下文视图,返回结果。
5
+ */
6
+ import type { DefaultsConfig } from '../config.js';
7
+ export interface ContextReadResult {
8
+ nodeCount: number;
9
+ outline: string;
10
+ breadcrumb: string[];
11
+ mode: 'focus' | 'page';
12
+ }
13
+ export declare class ContextReadHandler {
14
+ private forwardToPlugin;
15
+ private defaults;
16
+ constructor(forwardToPlugin: (action: string, payload: Record<string, unknown>) => Promise<unknown>, defaults?: DefaultsConfig);
17
+ handleReadContext(payload: Record<string, unknown>): Promise<ContextReadResult>;
18
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * ContextReadHandler — read-context 请求的业务编排
3
+ *
4
+ * 职责:转发到 Plugin 获取上下文视图,返回结果。
5
+ */
6
+ import { DEFAULT_DEFAULTS } from '../config.js';
7
+ export class ContextReadHandler {
8
+ forwardToPlugin;
9
+ defaults;
10
+ constructor(forwardToPlugin, defaults) {
11
+ this.forwardToPlugin = forwardToPlugin;
12
+ this.defaults = defaults ?? DEFAULT_DEFAULTS;
13
+ }
14
+ async handleReadContext(payload) {
15
+ const mode = payload.mode ?? this.defaults.readContextMode;
16
+ const ancestorLevels = payload.ancestorLevels ?? this.defaults.readContextAncestorLevels;
17
+ const depth = payload.depth ?? this.defaults.readContextDepth;
18
+ const maxNodes = payload.maxNodes ?? this.defaults.maxNodes;
19
+ const maxSiblings = payload.maxSiblings ?? this.defaults.maxSiblings;
20
+ return await this.forwardToPlugin('read_context', {
21
+ mode, ancestorLevels, depth, maxNodes, maxSiblings,
22
+ });
23
+ }
24
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * EditHandler — edit-rem 命令的编排器
3
+ *
4
+ * 在 daemon 层实现三道防线 + str_replace + 后处理校验。
5
+ * Plugin 只负责原子写入(write_rem_fields)。
6
+ *
7
+ * 防线 1:缓存存在性检查(必须先 read 再 edit)
8
+ * 防线 2:乐观并发检测(当前 JSON 与缓存 JSON 比较)
9
+ * 防线 3:str_replace 精确匹配(old_str 必须唯一匹配)
10
+ */
11
+ import { RemCache } from './rem-cache.js';
12
+ export interface EditRemPayload {
13
+ remId: string;
14
+ oldStr: string;
15
+ newStr: string;
16
+ }
17
+ export interface EditRemResult {
18
+ ok: boolean;
19
+ changes: string[];
20
+ warnings: string[];
21
+ error?: string;
22
+ appliedChanges?: string[];
23
+ failedField?: string;
24
+ }
25
+ export declare class EditHandler {
26
+ private cache;
27
+ private forwardToPlugin;
28
+ constructor(cache: RemCache, forwardToPlugin: (action: string, payload: Record<string, unknown>) => Promise<unknown>);
29
+ handleEditRem(payload: EditRemPayload): Promise<EditRemResult>;
30
+ }