@furongjun1999/dsh-memory 0.3.1 → 0.4.1
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/README.md +522 -545
- package/docs/lingshu_tutorial.html +14956 -0
- package/docs//344/277/241/346/201/257/345/267/256/344/270/272/344/273/200/344/271/210/345/277/205/347/204/266/345/255/230/345/234/250/344/270/224/350/207/252/347/204/266/346/211/251/345/244/247.md +239 -0
- package/docs//346/231/272/350/203/275/347/232/204/345/205/254/347/220/206/345/214/226/345/237/272/347/237/263.md +789 -0
- package/docs//346/231/272/350/203/275/347/232/204/350/256/244/347/237/245/350/277/207/347/250/213.md +379 -0
- package/docs//347/201/265/346/236/242MCP/345/267/245/345/205/267/346/200/273/350/241/250_v3.4.md +163 -0
- package/docs//347/231/275/347/256/261/346/231/272/350/203/275/346/230/257/344/273/200/344/271/210/357/274/237.md +812 -0
- package/lib/bridge.d.ts +4 -0
- package/lib/bridge.js +32 -2
- package/lib/index.js +4 -0
- package/lib/roleplay_web.js +30 -7
- package/package.json +1 -1
- package/src/bridge.ts +34 -2
- package/src/index.ts +4 -0
- package/src/roleplay_web.ts +30 -7
package/lib/bridge.d.ts
CHANGED
|
@@ -49,6 +49,10 @@ export declare class LingshuBridge {
|
|
|
49
49
|
private retryTimer;
|
|
50
50
|
private bootQueue;
|
|
51
51
|
private readyState;
|
|
52
|
+
/** issue #7:当前子进程启动时间(计算 uptime,区分秒退与长存后外部关闭) */
|
|
53
|
+
private procStartedAt;
|
|
54
|
+
/** issue #7:运行期反复退出的滑动窗口(时间戳列表)——防无限重启刷屏 */
|
|
55
|
+
private unexpectedExits;
|
|
52
56
|
constructor(options: BridgeOptions);
|
|
53
57
|
/**
|
|
54
58
|
* 启动灵枢进程并完成握手(initialize → notifications/initialized)。
|
package/lib/bridge.js
CHANGED
|
@@ -48,6 +48,10 @@ export class LingshuBridge {
|
|
|
48
48
|
retryTimer = null;
|
|
49
49
|
bootQueue = [];
|
|
50
50
|
readyState = 'pending';
|
|
51
|
+
/** issue #7:当前子进程启动时间(计算 uptime,区分秒退与长存后外部关闭) */
|
|
52
|
+
procStartedAt = 0;
|
|
53
|
+
/** issue #7:运行期反复退出的滑动窗口(时间戳列表)——防无限重启刷屏 */
|
|
54
|
+
unexpectedExits = [];
|
|
51
55
|
constructor(options) {
|
|
52
56
|
this.options = options;
|
|
53
57
|
}
|
|
@@ -112,6 +116,7 @@ export class LingshuBridge {
|
|
|
112
116
|
spawnAndHandshake() {
|
|
113
117
|
if (this.disposed)
|
|
114
118
|
return;
|
|
119
|
+
this.procStartedAt = Date.now();
|
|
115
120
|
const { python, args, env, cwd } = this.options;
|
|
116
121
|
const childEnv = { ...process.env, ...env };
|
|
117
122
|
// 确保 DB 目录存在(灵枢 server 也会防御性创建,这里提前为可读错误)
|
|
@@ -165,12 +170,37 @@ export class LingshuBridge {
|
|
|
165
170
|
});
|
|
166
171
|
proc.on('exit', (code, signal) => {
|
|
167
172
|
// 探针:写独立文件记录退出(绕过 DSH 日志系统,便于定位)
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
const uptimeS = this.procStartedAt
|
|
174
|
+
? Math.round((Date.now() - this.procStartedAt) / 1000)
|
|
175
|
+
: -1;
|
|
176
|
+
probe(`exit code=${code} signal=${signal} disposed=${this.disposed} ready=${this.readyState} started=${this.started} uptime=${uptimeS}s`);
|
|
177
|
+
console.error(`[lingshu-bridge] 灵枢进程退出 code=${code} signal=${signal}(存活 ${uptimeS}s)`);
|
|
170
178
|
this.rl?.close();
|
|
171
179
|
this.rl = null;
|
|
172
180
|
this.rejectAll(new Error(`灵枢进程已退出(code=${code} signal=${signal ?? 'none'})`));
|
|
173
181
|
if (!this.disposed) {
|
|
182
|
+
// issue #7:长存后 code=0 退出 = stdin 被外部关闭(非崩溃)。
|
|
183
|
+
// 记录滑动窗口(10 分钟内 ≥3 次)→ 进入冷却,防「外部反复关闭 +
|
|
184
|
+
// 无限重启」刷屏;冷却结束自动恢复,不进入放弃终态。
|
|
185
|
+
this.unexpectedExits = this.unexpectedExits.filter((t) => Date.now() - t < 600_000);
|
|
186
|
+
this.unexpectedExits.push(Date.now());
|
|
187
|
+
if (uptimeS >= 0 && uptimeS < 5) {
|
|
188
|
+
console.error('[lingshu-bridge] 进程启动后 5 秒内即退出——请检查 python 可执行文件与 aeis 安装。');
|
|
189
|
+
}
|
|
190
|
+
else if (this.unexpectedExits.length >= 3) {
|
|
191
|
+
console.error(`[lingshu-bridge] 10 分钟内已意外退出 ${this.unexpectedExits.length} 次,` +
|
|
192
|
+
'冷却 5 分钟后自动恢复。若持续出现,请检查是否有多个插件/脚本'
|
|
193
|
+
+ '同时管理灵枢进程(重复 spawn 会互相关闭对方子进程的 stdin)。');
|
|
194
|
+
probe(`cooldown: ${this.unexpectedExits.length} unexpected exits in 10min`);
|
|
195
|
+
this.retryTimer = setTimeout(() => {
|
|
196
|
+
this.retryTimer = null;
|
|
197
|
+
this.unexpectedExits = [];
|
|
198
|
+
if (!this.disposed)
|
|
199
|
+
this.spawnAndHandshake();
|
|
200
|
+
}, 300_000);
|
|
201
|
+
this.retryTimer.unref();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
174
204
|
// 有挂起请求的失败是异常的;仅启动失败的等待者得到 false
|
|
175
205
|
this.readyState = 'failed';
|
|
176
206
|
this.flushBootQueue(false);
|
package/lib/index.js
CHANGED
|
@@ -75,6 +75,10 @@ export const Config = z.object({
|
|
|
75
75
|
toolCallTimeoutMs: z.number().default(60_000),
|
|
76
76
|
maxRetryDelayMs: z.number().default(30_000),
|
|
77
77
|
failOnStartupError: z.boolean().default(false),
|
|
78
|
+
/** 角色扮演入口按钮(issue #8):注入 dsh 首页右上角浮动入口;
|
|
79
|
+
* 关闭后不注入(/roleplay 页面仍可直接访问)。按钮本身支持拖动,
|
|
80
|
+
* 位置记忆在浏览器 localStorage。 */
|
|
81
|
+
roleplayEntryButton: z.boolean().default(true),
|
|
78
82
|
/** 互维维护(v1.1):心跳 10min / 任务验证双通道。 */
|
|
79
83
|
mutual: z
|
|
80
84
|
.object({
|
package/lib/roleplay_web.js
CHANGED
|
@@ -839,13 +839,36 @@ export async function installRoleplayWeb(ctx, bridge, config, disposers) {
|
|
|
839
839
|
};
|
|
840
840
|
disposers.push(webServer.register({ kind: 'prefix', path: '/roleplay', handler }));
|
|
841
841
|
// 在 GUI 首页注入浮动入口按钮(APP 内置页面内一键进入角色扮演,右上角)
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
842
|
+
// issue #8:按钮可隐藏(roleplayEntryButton=false 不注入)+ 可拖动
|
|
843
|
+
// (pointer 拖拽,位置记忆在 localStorage;移动 >6px 视为拖动,否则放行点击跳转)
|
|
844
|
+
if (config.roleplayEntryButton) {
|
|
845
|
+
disposers.push(webServer.tapIndex((html) => {
|
|
846
|
+
if (html.includes('href="/roleplay"'))
|
|
847
|
+
return html;
|
|
848
|
+
const btn = '<a id="dshm-rp" href="/roleplay" style="position:fixed;top:12px;right:14px;z-index:999999;background:#7c8cff;color:#0b0d14;border-radius:999px;padding:9px 15px;font:600 13px/1 system-ui,sans-serif;text-decoration:none;box-shadow:0 4px 14px rgba(0,0,0,.45);touch-action:none;user-select:none;cursor:grab">🎭 角色扮演</a>' +
|
|
849
|
+
'<script>(function(){var b=document.getElementById("dshm-rp");if(!b)return;' +
|
|
850
|
+
'try{var p=JSON.parse(localStorage.getItem("dshm_rlp")||"null");' +
|
|
851
|
+
'if(p&&p.l!=null){b.style.right="auto";b.style.left=p.l+"px";b.style.top=p.t+"px";}}catch(e){}' +
|
|
852
|
+
'var sx=0,sy=0,sl=0,st=0,drag=false;' +
|
|
853
|
+
'b.addEventListener("pointerdown",function(e){sx=e.clientX;sy=e.clientY;' +
|
|
854
|
+
'var r=b.getBoundingClientRect();sl=r.left;st=r.top;drag=false;' +
|
|
855
|
+
'try{b.setPointerCapture(e.pointerId);}catch(err){}});' +
|
|
856
|
+
'b.addEventListener("pointermove",function(e){' +
|
|
857
|
+
'if(e.buttons===0||!drag&&!sx&&!sy)return;' +
|
|
858
|
+
'var dx=e.clientX-sx,dy=e.clientY-sy;' +
|
|
859
|
+
'if(!drag&&Math.sqrt(dx*dx+dy*dy)>6){drag=true;b.style.right="auto";b.style.cursor="grabbing";}' +
|
|
860
|
+
'if(drag){var L=Math.max(4,Math.min(window.innerWidth-b.offsetWidth-4,sl+dx)),' +
|
|
861
|
+
'T=Math.max(4,Math.min(window.innerHeight-b.offsetHeight-4,st+dy));' +
|
|
862
|
+
'b.style.left=L+"px";b.style.top=T+"px";e.preventDefault();}});' +
|
|
863
|
+
'b.addEventListener("pointerup",function(e){' +
|
|
864
|
+
'if(drag){e.preventDefault();' +
|
|
865
|
+
'try{var r=b.getBoundingClientRect();' +
|
|
866
|
+
'localStorage.setItem("dshm_rlp",JSON.stringify({l:r.left,t:r.top}));}catch(err){}}' +
|
|
867
|
+
'drag=false;sx=0;sy=0;});})();</script>';
|
|
868
|
+
return html.replace('</body>', `${btn}\n</body>`);
|
|
869
|
+
}));
|
|
870
|
+
}
|
|
871
|
+
ctx.logger.info(`dsh-memory: 角色扮演网页已挂载 /roleplay(data_dir=${roleDataDir},入口按钮=${config.roleplayEntryButton ? '显示(可拖动)' : '隐藏'})`);
|
|
849
872
|
}
|
|
850
873
|
catch (err) {
|
|
851
874
|
ctx.logger.warn(`dsh-memory: 角色扮演网页挂载失败(不影响其他功能): ${String(err && err.message || err)}`);
|
package/package.json
CHANGED
package/src/bridge.ts
CHANGED
|
@@ -86,6 +86,10 @@ export class LingshuBridge {
|
|
|
86
86
|
private retryTimer: NodeJS.Timeout | null = null
|
|
87
87
|
private bootQueue: Array<(ok: boolean) => void> = []
|
|
88
88
|
private readyState: 'pending' | 'ok' | 'failed' = 'pending'
|
|
89
|
+
/** issue #7:当前子进程启动时间(计算 uptime,区分秒退与长存后外部关闭) */
|
|
90
|
+
private procStartedAt = 0
|
|
91
|
+
/** issue #7:运行期反复退出的滑动窗口(时间戳列表)——防无限重启刷屏 */
|
|
92
|
+
private unexpectedExits: number[] = []
|
|
89
93
|
|
|
90
94
|
constructor(options: BridgeOptions) {
|
|
91
95
|
this.options = options
|
|
@@ -154,6 +158,7 @@ export class LingshuBridge {
|
|
|
154
158
|
|
|
155
159
|
private spawnAndHandshake(): void {
|
|
156
160
|
if (this.disposed) return
|
|
161
|
+
this.procStartedAt = Date.now()
|
|
157
162
|
const { python, args, env, cwd } = this.options
|
|
158
163
|
const childEnv = { ...process.env, ...env }
|
|
159
164
|
// 确保 DB 目录存在(灵枢 server 也会防御性创建,这里提前为可读错误)
|
|
@@ -207,12 +212,39 @@ export class LingshuBridge {
|
|
|
207
212
|
|
|
208
213
|
proc.on('exit', (code, signal) => {
|
|
209
214
|
// 探针:写独立文件记录退出(绕过 DSH 日志系统,便于定位)
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
const uptimeS = this.procStartedAt
|
|
216
|
+
? Math.round((Date.now() - this.procStartedAt) / 1000)
|
|
217
|
+
: -1
|
|
218
|
+
probe(`exit code=${code} signal=${signal} disposed=${this.disposed} ready=${this.readyState} started=${this.started} uptime=${uptimeS}s`)
|
|
219
|
+
console.error(`[lingshu-bridge] 灵枢进程退出 code=${code} signal=${signal}(存活 ${uptimeS}s)`)
|
|
212
220
|
this.rl?.close()
|
|
213
221
|
this.rl = null
|
|
214
222
|
this.rejectAll(new Error(`灵枢进程已退出(code=${code} signal=${signal ?? 'none'})`))
|
|
215
223
|
if (!this.disposed) {
|
|
224
|
+
// issue #7:长存后 code=0 退出 = stdin 被外部关闭(非崩溃)。
|
|
225
|
+
// 记录滑动窗口(10 分钟内 ≥3 次)→ 进入冷却,防「外部反复关闭 +
|
|
226
|
+
// 无限重启」刷屏;冷却结束自动恢复,不进入放弃终态。
|
|
227
|
+
this.unexpectedExits = this.unexpectedExits.filter(
|
|
228
|
+
(t) => Date.now() - t < 600_000,
|
|
229
|
+
)
|
|
230
|
+
this.unexpectedExits.push(Date.now())
|
|
231
|
+
if (uptimeS >= 0 && uptimeS < 5) {
|
|
232
|
+
console.error(
|
|
233
|
+
'[lingshu-bridge] 进程启动后 5 秒内即退出——请检查 python 可执行文件与 aeis 安装。')
|
|
234
|
+
} else if (this.unexpectedExits.length >= 3) {
|
|
235
|
+
console.error(
|
|
236
|
+
`[lingshu-bridge] 10 分钟内已意外退出 ${this.unexpectedExits.length} 次,` +
|
|
237
|
+
'冷却 5 分钟后自动恢复。若持续出现,请检查是否有多个插件/脚本'
|
|
238
|
+
+ '同时管理灵枢进程(重复 spawn 会互相关闭对方子进程的 stdin)。')
|
|
239
|
+
probe(`cooldown: ${this.unexpectedExits.length} unexpected exits in 10min`)
|
|
240
|
+
this.retryTimer = setTimeout(() => {
|
|
241
|
+
this.retryTimer = null
|
|
242
|
+
this.unexpectedExits = []
|
|
243
|
+
if (!this.disposed) this.spawnAndHandshake()
|
|
244
|
+
}, 300_000)
|
|
245
|
+
this.retryTimer.unref()
|
|
246
|
+
return
|
|
247
|
+
}
|
|
216
248
|
// 有挂起请求的失败是异常的;仅启动失败的等待者得到 false
|
|
217
249
|
this.readyState = 'failed'
|
|
218
250
|
this.flushBootQueue(false)
|
package/src/index.ts
CHANGED
|
@@ -114,6 +114,10 @@ export const Config: z<Config> = z.object({
|
|
|
114
114
|
toolCallTimeoutMs: z.number().default(60_000),
|
|
115
115
|
maxRetryDelayMs: z.number().default(30_000),
|
|
116
116
|
failOnStartupError: z.boolean().default(false),
|
|
117
|
+
/** 角色扮演入口按钮(issue #8):注入 dsh 首页右上角浮动入口;
|
|
118
|
+
* 关闭后不注入(/roleplay 页面仍可直接访问)。按钮本身支持拖动,
|
|
119
|
+
* 位置记忆在浏览器 localStorage。 */
|
|
120
|
+
roleplayEntryButton: z.boolean().default(true),
|
|
117
121
|
/** 互维维护(v1.1):心跳 10min / 任务验证双通道。 */
|
|
118
122
|
mutual: z
|
|
119
123
|
.object({
|
package/src/roleplay_web.ts
CHANGED
|
@@ -839,13 +839,36 @@ export async function installRoleplayWeb(ctx, bridge, config, disposers) {
|
|
|
839
839
|
};
|
|
840
840
|
disposers.push(webServer.register({ kind: 'prefix', path: '/roleplay', handler }));
|
|
841
841
|
// 在 GUI 首页注入浮动入口按钮(APP 内置页面内一键进入角色扮演,右上角)
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
842
|
+
// issue #8:按钮可隐藏(roleplayEntryButton=false 不注入)+ 可拖动
|
|
843
|
+
// (pointer 拖拽,位置记忆在 localStorage;移动 >6px 视为拖动,否则放行点击跳转)
|
|
844
|
+
if (config.roleplayEntryButton) {
|
|
845
|
+
disposers.push(webServer.tapIndex((html) => {
|
|
846
|
+
if (html.includes('href="/roleplay"'))
|
|
847
|
+
return html;
|
|
848
|
+
const btn = '<a id="dshm-rp" href="/roleplay" style="position:fixed;top:12px;right:14px;z-index:999999;background:#7c8cff;color:#0b0d14;border-radius:999px;padding:9px 15px;font:600 13px/1 system-ui,sans-serif;text-decoration:none;box-shadow:0 4px 14px rgba(0,0,0,.45);touch-action:none;user-select:none;cursor:grab">🎭 角色扮演</a>' +
|
|
849
|
+
'<script>(function(){var b=document.getElementById("dshm-rp");if(!b)return;' +
|
|
850
|
+
'try{var p=JSON.parse(localStorage.getItem("dshm_rlp")||"null");' +
|
|
851
|
+
'if(p&&p.l!=null){b.style.right="auto";b.style.left=p.l+"px";b.style.top=p.t+"px";}}catch(e){}' +
|
|
852
|
+
'var sx=0,sy=0,sl=0,st=0,drag=false;' +
|
|
853
|
+
'b.addEventListener("pointerdown",function(e){sx=e.clientX;sy=e.clientY;' +
|
|
854
|
+
'var r=b.getBoundingClientRect();sl=r.left;st=r.top;drag=false;' +
|
|
855
|
+
'try{b.setPointerCapture(e.pointerId);}catch(err){}});' +
|
|
856
|
+
'b.addEventListener("pointermove",function(e){' +
|
|
857
|
+
'if(e.buttons===0||!drag&&!sx&&!sy)return;' +
|
|
858
|
+
'var dx=e.clientX-sx,dy=e.clientY-sy;' +
|
|
859
|
+
'if(!drag&&Math.sqrt(dx*dx+dy*dy)>6){drag=true;b.style.right="auto";b.style.cursor="grabbing";}' +
|
|
860
|
+
'if(drag){var L=Math.max(4,Math.min(window.innerWidth-b.offsetWidth-4,sl+dx)),' +
|
|
861
|
+
'T=Math.max(4,Math.min(window.innerHeight-b.offsetHeight-4,st+dy));' +
|
|
862
|
+
'b.style.left=L+"px";b.style.top=T+"px";e.preventDefault();}});' +
|
|
863
|
+
'b.addEventListener("pointerup",function(e){' +
|
|
864
|
+
'if(drag){e.preventDefault();' +
|
|
865
|
+
'try{var r=b.getBoundingClientRect();' +
|
|
866
|
+
'localStorage.setItem("dshm_rlp",JSON.stringify({l:r.left,t:r.top}));}catch(err){}}' +
|
|
867
|
+
'drag=false;sx=0;sy=0;});})();</script>';
|
|
868
|
+
return html.replace('</body>', `${btn}\n</body>`);
|
|
869
|
+
}));
|
|
870
|
+
}
|
|
871
|
+
ctx.logger.info(`dsh-memory: 角色扮演网页已挂载 /roleplay(data_dir=${roleDataDir},入口按钮=${config.roleplayEntryButton ? '显示(可拖动)' : '隐藏'})`);
|
|
849
872
|
}
|
|
850
873
|
catch (err) {
|
|
851
874
|
ctx.logger.warn(`dsh-memory: 角色扮演网页挂载失败(不影响其他功能): ${String(err && err.message || err)}`);
|