dsh-remote-control 2.0.3 → 2.0.4

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.
@@ -10509,7 +10509,17 @@ function defaultHistory(sessionId) {
10509
10509
  },
10510
10510
  { kind: "delta", sessionId, messageId: "mock_h3", text: "\u7ED3\u6784\u6E05\u695A\u4E86\u3002", role: "assistant", done: true },
10511
10511
  { kind: "delta", sessionId, messageId: "mock_h4", text: "\u518D\u8DD1\u4E00\u904D\u6D4B\u8BD5", role: "user", done: true },
10512
- { kind: "delta", sessionId, messageId: "mock_h5", text: "\u6D4B\u8BD5\u5168\u8FC7\u3002", role: "assistant", done: true }
10512
+ { kind: "delta", sessionId, messageId: "mock_h5", text: "\u6D4B\u8BD5\u5168\u8FC7\u3002", role: "assistant", done: true },
10513
+ // 2026-10-05 补:待办快照也进历史页(wire 1.5.0)。放在最后——一页里最后一条
10514
+ // 就是那一页截止时的清单,本地全链路要真的走到这条路径,而不是只在单测里摆帧。
10515
+ {
10516
+ kind: "todo",
10517
+ sessionId,
10518
+ todos: [
10519
+ { content: "\u590D\u73B0\u7528\u6237\u62A5\u7684\u95EE\u9898", status: "completed" },
10520
+ { content: "\u6539\u5B8C\u8DD1\u4E00\u904D\u672C\u5730\u5168\u94FE\u8DEF", status: "in_progress" }
10521
+ ]
10522
+ }
10513
10523
  ];
10514
10524
  }
10515
10525
  function createMockKernel(options) {
@@ -32242,6 +32252,8 @@ import { randomBytes as randomBytes3 } from "node:crypto";
32242
32252
  var BACKOFF_MIN_MS = 1e3;
32243
32253
  var BACKOFF_MAX_MS = 3e4;
32244
32254
  var HANDSHAKE_TIMEOUT_MS = 1e4;
32255
+ var PROBE_INTERVAL_MS = 2e4;
32256
+ var PROBE_TIMEOUT_MS = 1e4;
32245
32257
  var VOIDED_MAX = 256;
32246
32258
  var RelayClient = class {
32247
32259
  constructor(options) {
@@ -32263,6 +32275,10 @@ var RelayClient = class {
32263
32275
  stopped = false;
32264
32276
  reconnectTimer;
32265
32277
  handshakeTimer;
32278
+ /** 存活探针的下一次提问(自重启的 setTimeout 链,见 {@link startProbe})。 */
32279
+ probeTimer;
32280
+ /** 已提问、还没等到 pong 的那个定时器;undefined = 当前没有待答的问题。 */
32281
+ pongDeadline;
32266
32282
  /** 连续解不开的会话:解不开说明这端已经没有对应密钥,留着它只会让手机永远转圈。 */
32267
32283
  undecryptable = /* @__PURE__ */ new Map();
32268
32284
  /** 已经向中继声明过的 convId(防重复 `session-leave`);有上界,见 {@link VOIDED_MAX}。 */
@@ -32338,6 +32354,7 @@ var RelayClient = class {
32338
32354
  });
32339
32355
  });
32340
32356
  socket.on("message", (raw) => this.onFrame(String(raw)));
32357
+ this.startProbe(socket);
32341
32358
  socket.on("close", (code, reason) => {
32342
32359
  this.options.clock.clearTimeout(timeout);
32343
32360
  this.emitState("offline", `\u8FDE\u63A5\u5173\u95ED ${code} ${reason ? reason.toString() : ""}`.trim());
@@ -32508,6 +32525,7 @@ var RelayClient = class {
32508
32525
  });
32509
32526
  return;
32510
32527
  case "pong":
32528
+ this.clearPongDeadline();
32511
32529
  return;
32512
32530
  case "enc":
32513
32531
  case "enc-batch":
@@ -32571,6 +32589,7 @@ var RelayClient = class {
32571
32589
  this.options.clock.clearTimeout(this.handshakeTimer);
32572
32590
  this.handshakeTimer = void 0;
32573
32591
  }
32592
+ this.stopProbe();
32574
32593
  if (!socket) return;
32575
32594
  socket.removeAllListeners();
32576
32595
  socket.on("error", () => {
@@ -32580,6 +32599,49 @@ var RelayClient = class {
32580
32599
  } catch {
32581
32600
  }
32582
32601
  }
32602
+ // ── 存活探针 ────────────────────────────────────────────────────────
32603
+ /**
32604
+ * 每隔 `probeIntervalMs` 问中继一句 `ping`,并给答复留 `probeTimeoutMs`。
32605
+ *
32606
+ * 用**自重启的 setTimeout 链**而不是 setInterval:时钟接口只有 setTimeout
32607
+ * (见 `Clock`),而且链式写法天然带上"上一轮还没答就下一轮"的语义——
32608
+ * 超时判死之后这条链也跟着 socket 一起被 closeSocket 收掉。
32609
+ *
32610
+ * `ping`/`pong` 是协议里本来就有的一对应用帧(中继无条件回 pong),
32611
+ * 所以这一跳不需要动线协议;不直接用 ws 协议层 ping 是因为那一条在
32612
+ * Node 的 ws 里由库自动应答,两端都"看得见"却都不留痕,排障时查不到。
32613
+ */
32614
+ startProbe(socket) {
32615
+ this.stopProbe();
32616
+ const interval = this.options.probeIntervalMs ?? PROBE_INTERVAL_MS;
32617
+ const timeout = this.options.probeTimeoutMs ?? PROBE_TIMEOUT_MS;
32618
+ const ask = () => {
32619
+ if (this.stopped || this.socket !== socket) return;
32620
+ if (socket.readyState === import_websocket.default.OPEN) {
32621
+ this.raw({ t: "ping", ts: this.options.clock.now() });
32622
+ this.clearPongDeadline();
32623
+ this.pongDeadline = this.options.clock.setTimeout(() => {
32624
+ this.options.log("relay probe timeout", { waitedMs: timeout });
32625
+ socket.terminate();
32626
+ }, timeout);
32627
+ }
32628
+ this.probeTimer = this.options.clock.setTimeout(ask, interval);
32629
+ };
32630
+ this.probeTimer = this.options.clock.setTimeout(ask, interval);
32631
+ }
32632
+ stopProbe() {
32633
+ if (this.probeTimer !== void 0) {
32634
+ this.options.clock.clearTimeout(this.probeTimer);
32635
+ this.probeTimer = void 0;
32636
+ }
32637
+ this.clearPongDeadline();
32638
+ }
32639
+ clearPongDeadline() {
32640
+ if (this.pongDeadline !== void 0) {
32641
+ this.options.clock.clearTimeout(this.pongDeadline);
32642
+ this.pongDeadline = void 0;
32643
+ }
32644
+ }
32583
32645
  emitState(relay, problem) {
32584
32646
  this.options.onState({
32585
32647
  relay,
@@ -34460,7 +34522,7 @@ function startPill(ctx, deps) {
34460
34522
  }
34461
34523
 
34462
34524
  // src/version.ts
34463
- var PLUGIN_VERSION = true ? "2.0.3" : "dev";
34525
+ var PLUGIN_VERSION = true ? "2.0.4" : "dev";
34464
34526
 
34465
34527
  // src/index.ts
34466
34528
  var SERVICE_NAMES = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-control",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "DSH Remote Control host plugin: pairs a running DeepSeek Harness with the DRC relay so a WeChat Mini Program can send prompts, watch streamed output, and answer approvals. Payload-level E2E encryption; the relay never sees plaintext. Pairing is one click on the status-bar pill (three same-origin routes + a browser half).",
5
5
  "type": "module",
6
6
  "license": "MIT",