dsh-jiuling-remote-mobile 1.0.8 → 1.0.9
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/lib/index.js +96 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -554,6 +554,102 @@ export function apply(ctx) {
|
|
|
554
554
|
};
|
|
555
555
|
}, 'dsh-remote-mobile: prompt/cancel 拦截');
|
|
556
556
|
|
|
557
|
+
// ---- 屏蔽远程会话在手机本地的标题生成 ----
|
|
558
|
+
//
|
|
559
|
+
// ⚠️ 官方 `@deepseek-ai/dsh-session-title/lib/index.js` 的
|
|
560
|
+
// `ctx.on("session/event")` 对**任何** `source.kind === 'user'` 的
|
|
561
|
+
// `user/message` 都会在**本地**写一条 `session/title`
|
|
562
|
+
//(`onUserMessage()` → `ensureFallback()`,源码 379-403 行),并在首轮再置
|
|
563
|
+
// `state.pending`,让标题 LLM 补一条 `session/title-llm-request`
|
|
564
|
+
//(`onRequestHeader()` 消费,源码 386 行那个条件)。
|
|
565
|
+
//
|
|
566
|
+
// 手机本地日志是电脑日志的**逐条镜像**,`mirrorEvent` 靠 seq 折算对齐
|
|
567
|
+
//(见其注释)。镜像进来的 `user/message` 同样满足那个条件,于是手机端
|
|
568
|
+
// **本地多写 2 条**事件,把电脑端同一 seq 上的 2 条真实事件挤掉 —— 而
|
|
569
|
+
// `mirrorEvent` 对「折算 seq < 本地 seq」的事件是按「重连补发的历史」
|
|
570
|
+
// **静默丢弃**的,被挤掉的那 2 条在手机上永久缺失。
|
|
571
|
+
//
|
|
572
|
+
// 真机实测(探针会话 `session-dc52fa34`,两轮对话,`_test/compare-logs.mjs`):
|
|
573
|
+
// 电脑端 41 条、手机端 41 条,seq 0-40 只有 **seq 9 与 seq 16** 不一致,
|
|
574
|
+
// 丢的是电脑端的 workspace 说明(`system-reminder` 那条 `user/message`)
|
|
575
|
+
// 与电脑端自己的 `session/title`,而手机端那两格是**本地生成**的
|
|
576
|
+
// (phone seq=9 `time=1792088906625` `source:{kind:"fallback"}`,与电脑端
|
|
577
|
+
// 同一条的 `time` 不同 ⇒ 确认不是镜像来的)。本轮丢的是说明文字,
|
|
578
|
+
// 时序一变被挤掉的就会是 `assistant/message` —— 也就是**用户看不到 AI 回复**。
|
|
579
|
+
//
|
|
580
|
+
// 标题的真源在电脑端:电脑端生成后会把 `session/title` 流过来,所以远程
|
|
581
|
+
// 会话在手机本地**一律不生成**标题。这样本地 seq 不再被抢占,两端日志
|
|
582
|
+
// 恢复逐条一致,也省掉一次白烧用户额度的本地标题 LLM 调用。
|
|
583
|
+
ctx.effect(() => {
|
|
584
|
+
/** 已完成的还原函数(插件卸载时逐个执行)。 */
|
|
585
|
+
const undo = [];
|
|
586
|
+
let done = false;
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* 给标题服务挂上「远程会话直接短路」的包装。
|
|
590
|
+
*
|
|
591
|
+
* @param titleService - `ctx.sessionTitle` 实例;未就绪时为 undefined。
|
|
592
|
+
* @returns 是否已经完成(含此前已完成)。
|
|
593
|
+
*/
|
|
594
|
+
const patchTitleService = (titleService) => {
|
|
595
|
+
if (done) return true;
|
|
596
|
+
if (titleService === undefined || typeof titleService.onUserMessage !== 'function') {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
const original = titleService.onUserMessage;
|
|
600
|
+
// ⚠️ 必须用 `function` 而不是箭头函数:官方是 `this.onUserMessage(...)`
|
|
601
|
+
// 这样调的,箭头函数会让 `this` 丢失、`original.call(this, ...)` 里的
|
|
602
|
+
// `this` 落成 undefined,标题服务内部读 `this.registration` 即抛错。
|
|
603
|
+
titleService.onUserMessage = function (session, event) {
|
|
604
|
+
if (remoteSessions.has(session.id)) return undefined;
|
|
605
|
+
return original.call(this, session, event);
|
|
606
|
+
};
|
|
607
|
+
undo.push(() => {
|
|
608
|
+
titleService.onUserMessage = original;
|
|
609
|
+
});
|
|
610
|
+
done = true;
|
|
611
|
+
return true;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
// ⚠️ 两条路径都各自包 try/catch:屏蔽失败只是退化成「远端事件可能错位」,
|
|
615
|
+
// 但**绝不能拖垮整个插件** —— 那会让远程功能完全不可用,比错位严重得多。
|
|
616
|
+
//(`ctx.get(name)` 在服务未注册时返回 `undefined`,不抛错;这里防的是
|
|
617
|
+
// `ctx.get` / `ctx.inject` 本身在引擎版本变化后不再可用。)
|
|
618
|
+
//
|
|
619
|
+
// ⚠️ **两条路要分开 try**:若合在一个 try 里,`ctx.get` 一旦抛错,
|
|
620
|
+
// `ctx.inject` 这条「服务迟一点才注册」的兜底路径会被一并跳过 ——
|
|
621
|
+
// 守卫就这么**静默失效**了,而且日志里只有一条 warn,很难看出少兜了一次。
|
|
622
|
+
const warn = (error) =>
|
|
623
|
+
ctx.logger?.warn?.(
|
|
624
|
+
`[dsh-remote-mobile] 未能屏蔽本地标题生成,远端事件镜像可能错位: ${String(
|
|
625
|
+
error?.message ?? error,
|
|
626
|
+
)}`,
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
// 路径一:服务通常在本插件加载前就已注册,先同步打一次。
|
|
630
|
+
let patched = false;
|
|
631
|
+
try {
|
|
632
|
+
patched = patchTitleService(ctx.get('sessionTitle'));
|
|
633
|
+
} catch (error) {
|
|
634
|
+
warn(error);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// 路径二:还没就绪(或路径一失败)则交给依赖注入,等服务注册后再打。
|
|
638
|
+
if (!patched) {
|
|
639
|
+
try {
|
|
640
|
+
ctx.inject(['sessionTitle'], (scoped) => {
|
|
641
|
+
patchTitleService(scoped.sessionTitle);
|
|
642
|
+
});
|
|
643
|
+
} catch (error) {
|
|
644
|
+
warn(error);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return () => {
|
|
649
|
+
for (const fn of undo) fn();
|
|
650
|
+
};
|
|
651
|
+
}, 'dsh-remote-mobile: 屏蔽远程会话的本地标题生成');
|
|
652
|
+
|
|
557
653
|
// ---- 浏览器桥:目录浏览 / 建会话 ----
|
|
558
654
|
ctx.effect(
|
|
559
655
|
() =>
|