dsh-remote-control 2.0.0 → 2.0.2

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.
@@ -73,14 +73,20 @@ function pillLabel(status) {
73
73
  if (paired > 0) {
74
74
  const waiting = waitingCount(status);
75
75
  if (waiting > 0) return { text: `\u7B49 ${waiting} \u4EF6\u4E8B`, tone: "wait" };
76
- return { text: "\u5DF2\u914D\u5BF9", tone: "on" };
76
+ return { text: "\u5DF2\u8FDE\u63A5", tone: "on" };
77
77
  }
78
- return { text: "\u672A\u914D\u5BF9", tone: "off" };
78
+ if (pairingCount(status) > 0) return { text: "\u624B\u673A\u79BB\u7EBF", tone: "wait" };
79
+ return { text: "\u8FDC\u7A0B\u672A\u8FDE\u63A5", tone: "off" };
79
80
  }
80
81
  function waitingCount(status) {
81
82
  const count = status?.waiting;
82
83
  return typeof count === "number" && count > 0 ? Math.min(Math.floor(count), 99) : 0;
83
84
  }
85
+ function pairingCount(status) {
86
+ const count = status?.pairings;
87
+ return typeof count === "number" ? count : 0;
88
+ }
89
+ var GUIDE_LINE = "\u6253\u5F00\u5C0F\u7A0B\u5E8F\u626B\u8FD9\u4E2A\u7801 \u8FDC\u7A0B\u63A7\u5236\u8FD9\u53F0\u7535\u8111";
84
90
  function humanDuration(sec) {
85
91
  const total = typeof sec === "number" && sec > 0 ? Math.floor(sec) : 0;
86
92
  if (total < 60) return `${total} \u79D2`;
@@ -91,7 +97,7 @@ function waitingRow(status) {
91
97
  const count = waitingCount(status);
92
98
  if (count === 0) return "";
93
99
  const oldest = typeof status?.waitingOldestSec === "number" ? status.waitingOldestSec : 0;
94
- return `${count} \u4EF6${oldest > 0 ? ` \xB7 \u6700\u4E45 ${humanDuration(oldest)}` : ""}`.slice(0, 40);
100
+ return `${count} \u4EF6${oldest > 0 ? ` \u6700\u4E45 ${humanDuration(oldest)}` : ""}`.slice(0, 40);
95
101
  }
96
102
  function relayAddress(url) {
97
103
  if (typeof url !== "string" || url.trim() === "") return "";
@@ -104,7 +110,7 @@ function relayRow(status) {
104
110
  }
105
111
  function relayStateRow(status) {
106
112
  const relay = status?.relay;
107
- if (relay === "online") return "\u5DF2\u8FDE\u63A5";
113
+ if (relay === "online") return "\u5DF2\u5C31\u7EEA";
108
114
  if (relay === "connecting") return "\u8FDE\u63A5\u4E2D";
109
115
  if (relay === "offline") return "\u5DF2\u65AD\u5F00";
110
116
  if (relay === "idle") return "\u672A\u542F\u52A8";
@@ -160,7 +166,10 @@ var CSS = `
160
166
  .drc-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
161
167
  .drc-qr { display: block; width: 200px; height: 200px; margin: 8px auto 0; image-rendering: pixelated; }
162
168
  .drc-code { margin: 8px 0 0; text-align: center; font-weight: 600; font-size: 14px; letter-spacing: 2px; }
163
- .drc-note { margin: 4px 0 0; text-align: center; color: var(--dsw-alias-label-tertiary); }
169
+ /* \u5F15\u5BFC\u884C\uFF1A\u6BD4 note \u91CD\u4E00\u6863\uFF08\u7B2C\u4E00\u6B21\u7528\u7684\u4EBA\u8981\u770B\u6E05\uFF09\uFF0C\u4F46\u4E0D\u52A0\u56FE\u6807\u4E0D\u52A0\u5E95\u8272\u2014\u2014
170
+ \u8FD9\u4E00\u5C4F\u7684\u4E3B\u89D2\u662F\u4E8C\u7EF4\u7801\uFF0C\u591A\u4E00\u4E2A\u8272\u5757\u5C31\u6210\u4E86\u63A8\u9500\u800C\u4E0D\u662F\u5F15\u5BFC\u3002 */
171
+ .drc-guide { margin: 8px 0 0; text-align: center; color: var(--dsw-alias-label-secondary, #5f6368); font-size: 12px; }
172
+ .drc-note { margin: 4px 0 0; text-align: center; color: var(--dsw-alias-label-tertiary); }
164
173
  .drc-note[data-kind="failed"] { color: var(--dsw-alias-state-error-primary, #ff4d4f); }
165
174
  `;
166
175
  function isSlots(value) {
@@ -236,7 +245,7 @@ function buildPill(root, deps) {
236
245
  let expiresInMs = 0;
237
246
  let lastStatus;
238
247
  let disposed = false;
239
- const pairedNow = () => typeof lastStatus?.paired === "number" ? lastStatus.paired : 0;
248
+ const pairingNow = () => pairingCount(lastStatus);
240
249
  const write = () => {
241
250
  const next = pillLabel(lastStatus);
242
251
  label.textContent = next.text;
@@ -258,7 +267,7 @@ function buildPill(root, deps) {
258
267
  node.addEventListener("click", onClick);
259
268
  return node;
260
269
  };
261
- const qrNote = () => expiresInMs > 0 ? `\u626B\u7801\u914D\u5BF9 \xB7 ${secondsText(expiresInMs)}\u540E\u8FC7\u671F` : "\u914D\u5BF9\u7801\u5DF2\u8FC7\u671F\uFF0C\u70B9\u53F3\u4E0A\u89D2\u300C\u5237\u65B0\u300D\u91CD\u65B0\u751F\u6210";
270
+ const qrNote = () => expiresInMs > 0 ? `\u5FAE\u4FE1\u626B\u7801\u914D\u5BF9 ${secondsText(expiresInMs)}\u540E\u8FC7\u671F` : "\u914D\u5BF9\u7801\u5DF2\u8FC7\u671F \u70B9\u53F3\u4E0A\u89D2\u5237\u65B0\u91CD\u65B0\u751F\u6210";
262
271
  const header = () => {
263
272
  const head = doc.createElement("div");
264
273
  head.className = "drc-head";
@@ -268,11 +277,16 @@ function buildPill(root, deps) {
268
277
  head.appendChild(stateText);
269
278
  const actions = doc.createElement("div");
270
279
  actions.className = "drc-actions";
271
- const action = pairedNow() > 0 ? { label: "\u9000\u51FA\u914D\u5BF9", run: requestUnpair } : { label: "\u5237\u65B0", run: requestPairing };
280
+ const action = pairingNow() > 0 ? { label: "\u9000\u51FA\u914D\u5BF9", run: requestUnpair } : { label: "\u5237\u65B0", run: requestPairing };
272
281
  actions.appendChild(buttonOf(action.label, () => void action.run()));
273
282
  head.appendChild(actions);
274
283
  return head;
275
284
  };
285
+ const offlineNote = () => {
286
+ if (!lastStatus) return null;
287
+ if (pillLabel(lastStatus).text !== "\u624B\u673A\u79BB\u7EBF") return null;
288
+ return text("p", "drc-note", "\u914D\u5BF9\u8FD8\u5728 \u5C0F\u7A0B\u5E8F\u56DE\u524D\u53F0\u4F1A\u81EA\u52A8\u91CD\u8FDE \u4E0D\u7528\u91CD\u65B0\u626B\u7801");
289
+ };
276
290
  const infoRows = () => {
277
291
  if (!lastStatus) return [];
278
292
  const rows = [
@@ -295,24 +309,27 @@ function buildPill(root, deps) {
295
309
  panel.replaceChildren();
296
310
  panel.appendChild(header());
297
311
  if (view.kind === "loading") {
298
- panel.appendChild(text("p", "drc-note", "\u6B63\u5728\u751F\u6210\u914D\u5BF9\u7801\u2026"));
312
+ panel.appendChild(text("p", "drc-note", "\u6B63\u5728\u751F\u6210\u914D\u5BF9\u7801"));
299
313
  } else if (view.kind === "qr") {
300
314
  const image = doc.createElement("img");
301
315
  image.className = "drc-qr";
302
316
  image.setAttribute("alt", "\u914D\u5BF9\u4E8C\u7EF4\u7801");
303
317
  image.src = view.imageSrc;
304
318
  panel.appendChild(image);
319
+ panel.appendChild(text("p", "drc-guide", GUIDE_LINE));
305
320
  panel.appendChild(text("p", "drc-code", view.token));
306
321
  panel.appendChild(text("p", "drc-note", qrNote()));
307
322
  } else if (view.kind === "unavailable" || view.kind === "failed") {
308
- const note = view.kind === "unavailable" ? text("p", "drc-note", view.reason) : (() => {
323
+ const note2 = view.kind === "unavailable" ? text("p", "drc-note", view.reason) : (() => {
309
324
  const node = text("p", "drc-note", `\u914D\u5BF9\u8BF7\u6C42\u6CA1\u6210\u529F\uFF1A${view.detail}`);
310
325
  node.setAttribute("data-kind", "failed");
311
326
  return node;
312
327
  })();
313
- panel.appendChild(note);
328
+ panel.appendChild(note2);
314
329
  }
315
330
  for (const row of infoRows()) panel.appendChild(row);
331
+ const note = offlineNote();
332
+ if (note) panel.appendChild(note);
316
333
  };
317
334
  const closePanel = () => {
318
335
  if (countTimer !== void 0) {
@@ -373,7 +390,7 @@ function buildPill(root, deps) {
373
390
  warn("\u9000\u51FA\u914D\u5BF9\u8BF7\u6C42\u5931\u8D25\uFF08\u9762\u677F\u4F1A\u9760\u4E0B\u4E00\u6B21\u8F6E\u8BE2\u5BF9\u8D26\uFF09", error);
374
391
  }
375
392
  if (disposed || !panel) return;
376
- if (lastStatus) lastStatus = { ...lastStatus, paired: 0 };
393
+ if (lastStatus) lastStatus = { ...lastStatus, paired: 0, pairings: 0 };
377
394
  write();
378
395
  view = { kind: "loading" };
379
396
  paint();
@@ -388,7 +405,7 @@ function buildPill(root, deps) {
388
405
  panel.tabIndex = -1;
389
406
  root.appendChild(panel);
390
407
  button.setAttribute("aria-expanded", "true");
391
- if (pairedNow() > 0) {
408
+ if (pairingNow() > 0) {
392
409
  view = { kind: "info" };
393
410
  paint();
394
411
  } else {
@@ -430,7 +447,7 @@ function buildPill(root, deps) {
430
447
  const body = await response.json?.();
431
448
  if (!body || typeof body !== "object") return;
432
449
  lastStatus = body;
433
- if (view.kind === "qr" && pairedNow() > 0) {
450
+ if (view.kind === "qr" && pairingNow() > 0) {
434
451
  view = { kind: "info" };
435
452
  if (countTimer !== void 0) {
436
453
  clearInterval(countTimer);
@@ -7486,10 +7486,10 @@ var require_segments = __commonJS({
7486
7486
  const segs = getSegmentsFromString(data, Utils.isKanjiModeEnabled());
7487
7487
  const nodes = buildNodes(segs);
7488
7488
  const graph = buildGraph(nodes, version2);
7489
- const path6 = dijkstra.find_path(graph.map, "start", "end");
7489
+ const path7 = dijkstra.find_path(graph.map, "start", "end");
7490
7490
  const optimizedSegs = [];
7491
- for (let i = 1; i < path6.length - 1; i++) {
7492
- optimizedSegs.push(graph.table[path6[i]].node);
7491
+ for (let i = 1; i < path7.length - 1; i++) {
7492
+ optimizedSegs.push(graph.table[path7[i]].node);
7493
7493
  }
7494
7494
  return exports.fromArray(mergeSegments(optimizedSegs));
7495
7495
  };
@@ -9925,7 +9925,7 @@ var require_utils2 = __commonJS({
9925
9925
  // ../../node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/renderer/png.js
9926
9926
  var require_png2 = __commonJS({
9927
9927
  "../../node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/renderer/png.js"(exports) {
9928
- var fs = __require("fs");
9928
+ var fs2 = __require("fs");
9929
9929
  var PNG = require_png().PNG;
9930
9930
  var Utils = require_utils2();
9931
9931
  exports.render = function render(qrData, options) {
@@ -9966,7 +9966,7 @@ var require_png2 = __commonJS({
9966
9966
  });
9967
9967
  png.pack();
9968
9968
  };
9969
- exports.renderToFile = function renderToFile(path6, qrData, options, cb) {
9969
+ exports.renderToFile = function renderToFile(path7, qrData, options, cb) {
9970
9970
  if (typeof cb === "undefined") {
9971
9971
  cb = options;
9972
9972
  options = void 0;
@@ -9977,7 +9977,7 @@ var require_png2 = __commonJS({
9977
9977
  called = true;
9978
9978
  cb.apply(null, args);
9979
9979
  };
9980
- const stream = fs.createWriteStream(path6);
9980
+ const stream = fs2.createWriteStream(path7);
9981
9981
  stream.on("error", done);
9982
9982
  stream.on("close", done);
9983
9983
  exports.renderToFileStream(stream, qrData, options);
@@ -10039,14 +10039,14 @@ var require_utf8 = __commonJS({
10039
10039
  }
10040
10040
  return output2;
10041
10041
  };
10042
- exports.renderToFile = function renderToFile(path6, qrData, options, cb) {
10042
+ exports.renderToFile = function renderToFile(path7, qrData, options, cb) {
10043
10043
  if (typeof cb === "undefined") {
10044
10044
  cb = options;
10045
10045
  options = void 0;
10046
10046
  }
10047
- const fs = __require("fs");
10047
+ const fs2 = __require("fs");
10048
10048
  const utf82 = exports.render(qrData, options);
10049
- fs.writeFile(path6, utf82, cb);
10049
+ fs2.writeFile(path7, utf82, cb);
10050
10050
  };
10051
10051
  }
10052
10052
  });
@@ -10167,7 +10167,7 @@ var require_svg_tag = __commonJS({
10167
10167
  return str;
10168
10168
  }
10169
10169
  function qrToPath(data, size, margin) {
10170
- let path6 = "";
10170
+ let path7 = "";
10171
10171
  let moveBy = 0;
10172
10172
  let newRow = false;
10173
10173
  let lineLength = 0;
@@ -10178,19 +10178,19 @@ var require_svg_tag = __commonJS({
10178
10178
  if (data[i]) {
10179
10179
  lineLength++;
10180
10180
  if (!(i > 0 && col > 0 && data[i - 1])) {
10181
- path6 += newRow ? svgCmd("M", col + margin, 0.5 + row + margin) : svgCmd("m", moveBy, 0);
10181
+ path7 += newRow ? svgCmd("M", col + margin, 0.5 + row + margin) : svgCmd("m", moveBy, 0);
10182
10182
  moveBy = 0;
10183
10183
  newRow = false;
10184
10184
  }
10185
10185
  if (!(col + 1 < size && data[i + 1])) {
10186
- path6 += svgCmd("h", lineLength);
10186
+ path7 += svgCmd("h", lineLength);
10187
10187
  lineLength = 0;
10188
10188
  }
10189
10189
  } else {
10190
10190
  moveBy++;
10191
10191
  }
10192
10192
  }
10193
- return path6;
10193
+ return path7;
10194
10194
  }
10195
10195
  exports.render = function render(qrData, options, cb) {
10196
10196
  const opts = Utils.getOptions(options);
@@ -10198,10 +10198,10 @@ var require_svg_tag = __commonJS({
10198
10198
  const data = qrData.modules.data;
10199
10199
  const qrcodesize = size + opts.margin * 2;
10200
10200
  const bg = !opts.color.light.a ? "" : "<path " + getColorAttrib(opts.color.light, "fill") + ' d="M0 0h' + qrcodesize + "v" + qrcodesize + 'H0z"/>';
10201
- const path6 = "<path " + getColorAttrib(opts.color.dark, "stroke") + ' d="' + qrToPath(data, size, opts.margin) + '"/>';
10201
+ const path7 = "<path " + getColorAttrib(opts.color.dark, "stroke") + ' d="' + qrToPath(data, size, opts.margin) + '"/>';
10202
10202
  const viewBox = 'viewBox="0 0 ' + qrcodesize + " " + qrcodesize + '"';
10203
10203
  const width = !opts.width ? "" : 'width="' + opts.width + '" height="' + opts.width + '" ';
10204
- const svgTag = '<svg xmlns="http://www.w3.org/2000/svg" ' + width + viewBox + ' shape-rendering="crispEdges">' + bg + path6 + "</svg>\n";
10204
+ const svgTag = '<svg xmlns="http://www.w3.org/2000/svg" ' + width + viewBox + ' shape-rendering="crispEdges">' + bg + path7 + "</svg>\n";
10205
10205
  if (typeof cb === "function") {
10206
10206
  cb(null, svgTag);
10207
10207
  }
@@ -10215,15 +10215,15 @@ var require_svg = __commonJS({
10215
10215
  "../../node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/renderer/svg.js"(exports) {
10216
10216
  var svgTagRenderer = require_svg_tag();
10217
10217
  exports.render = svgTagRenderer.render;
10218
- exports.renderToFile = function renderToFile(path6, qrData, options, cb) {
10218
+ exports.renderToFile = function renderToFile(path7, qrData, options, cb) {
10219
10219
  if (typeof cb === "undefined") {
10220
10220
  cb = options;
10221
10221
  options = void 0;
10222
10222
  }
10223
- const fs = __require("fs");
10223
+ const fs2 = __require("fs");
10224
10224
  const svgTag = exports.render(qrData, options);
10225
10225
  const xmlStr = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' + svgTag;
10226
- fs.writeFile(path6, xmlStr, cb);
10226
+ fs2.writeFile(path7, xmlStr, cb);
10227
10227
  };
10228
10228
  }
10229
10229
  });
@@ -10381,8 +10381,8 @@ var require_server = __commonJS({
10381
10381
  cb
10382
10382
  };
10383
10383
  }
10384
- function getTypeFromFilename(path6) {
10385
- return path6.slice((path6.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
10384
+ function getTypeFromFilename(path7) {
10385
+ return path7.slice((path7.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
10386
10386
  }
10387
10387
  function getRendererFromType(type) {
10388
10388
  switch (type) {
@@ -10446,17 +10446,17 @@ var require_server = __commonJS({
10446
10446
  const renderer = getRendererFromType(params.opts.type);
10447
10447
  return render(renderer.renderToBuffer, text, params);
10448
10448
  };
10449
- exports.toFile = function toFile(path6, text, opts, cb) {
10450
- if (typeof path6 !== "string" || !(typeof text === "string" || typeof text === "object")) {
10449
+ exports.toFile = function toFile(path7, text, opts, cb) {
10450
+ if (typeof path7 !== "string" || !(typeof text === "string" || typeof text === "object")) {
10451
10451
  throw new Error("Invalid argument");
10452
10452
  }
10453
10453
  if (arguments.length < 3 && !canPromise()) {
10454
10454
  throw new Error("Too few arguments provided");
10455
10455
  }
10456
10456
  const params = checkParams(text, opts, cb);
10457
- const type = params.opts.type || getTypeFromFilename(path6);
10457
+ const type = params.opts.type || getTypeFromFilename(path7);
10458
10458
  const renderer = getRendererFromType(type);
10459
- const renderToFile = renderer.renderToFile.bind(null, path6);
10459
+ const renderToFile = renderer.renderToFile.bind(null, path7);
10460
10460
  return render(renderToFile, text, params);
10461
10461
  };
10462
10462
  exports.toFileStream = function toFileStream(stream, text, opts) {
@@ -10538,6 +10538,22 @@ var init_mock_kernel = __esm({
10538
10538
  if (found.state === "archived") return { ok: false, message: "\u4F1A\u8BDD\u5DF2\u5F52\u6863\uFF0C\u9700\u5148\u6062\u590D" };
10539
10539
  this.running.add(sessionId);
10540
10540
  this.emit({ kind: "run-state", sessionId, state: "running" });
10541
+ this.emit({
10542
+ kind: "todo",
10543
+ sessionId,
10544
+ todos: [
10545
+ { content: "\u590D\u73B0\u7528\u6237\u62A5\u7684\u95EE\u9898", status: "completed" },
10546
+ { content: "\u6539\u5B8C\u8DD1\u4E00\u904D\u672C\u5730\u5168\u94FE\u8DEF", status: "pending" }
10547
+ ]
10548
+ });
10549
+ this.emit({
10550
+ kind: "todo",
10551
+ sessionId,
10552
+ todos: [
10553
+ { content: "\u590D\u73B0\u7528\u6237\u62A5\u7684\u95EE\u9898", status: "completed" },
10554
+ { content: "\u6539\u5B8C\u8DD1\u4E00\u904D\u672C\u5730\u5168\u94FE\u8DEF", status: "in_progress" }
10555
+ ]
10556
+ });
10541
10557
  if (this.options.autoApproval !== false && this.sink) {
10542
10558
  const decision = await this.sink.approval({ sessionId, action: "bash echo", reason: "mock \u5BA1\u6279" });
10543
10559
  this.emit({
@@ -10655,9 +10671,9 @@ var init_mock_kernel = __esm({
10655
10671
  });
10656
10672
 
10657
10673
  // src/index.ts
10658
- import path5 from "node:path";
10674
+ import path6 from "node:path";
10659
10675
 
10660
- // ../../node_modules/.pnpm/dsh-remote-wire@1.2.0/node_modules/dsh-remote-wire/dist/src/crypto/bytes.js
10676
+ // ../../node_modules/.pnpm/dsh-remote-wire@1.4.0/node_modules/dsh-remote-wire/dist/src/crypto/bytes.js
10661
10677
  import { Buffer as Buffer2 } from "node:buffer";
10662
10678
  var encoder = new TextEncoder();
10663
10679
  var decoder = new TextDecoder("utf-8", { fatal: false });
@@ -10684,7 +10700,7 @@ function concat(parts) {
10684
10700
  return out;
10685
10701
  }
10686
10702
 
10687
- // ../../node_modules/.pnpm/dsh-remote-wire@1.2.0/node_modules/dsh-remote-wire/dist/src/crypto/keys.js
10703
+ // ../../node_modules/.pnpm/dsh-remote-wire@1.4.0/node_modules/dsh-remote-wire/dist/src/crypto/keys.js
10688
10704
  import { createHash, randomBytes } from "node:crypto";
10689
10705
  var KDF_NAMESPACE = "dsh-rc/v1";
10690
10706
  var UNIT_SEPARATOR = 31;
@@ -10714,7 +10730,7 @@ function randomNonce() {
10714
10730
  return new Uint8Array(randomBytes(NONCE_BYTES));
10715
10731
  }
10716
10732
 
10717
- // ../../node_modules/.pnpm/dsh-remote-wire@1.2.0/node_modules/dsh-remote-wire/dist/src/crypto/record.js
10733
+ // ../../node_modules/.pnpm/dsh-remote-wire@1.4.0/node_modules/dsh-remote-wire/dist/src/crypto/record.js
10718
10734
  var import_tweetnacl = __toESM(require_nacl_fast(), 1);
10719
10735
  var MAC_BYTES = 16;
10720
10736
  var MIN_RECORD_BYTES = NONCE_BYTES + MAC_BYTES;
@@ -10747,7 +10763,7 @@ function open(key, record2) {
10747
10763
  }
10748
10764
  }
10749
10765
 
10750
- // ../../node_modules/.pnpm/dsh-remote-wire@1.2.0/node_modules/dsh-remote-wire/dist/src/identity/pairing.js
10766
+ // ../../node_modules/.pnpm/dsh-remote-wire@1.4.0/node_modules/dsh-remote-wire/dist/src/identity/pairing.js
10751
10767
  import { randomBytes as randomBytes2 } from "node:crypto";
10752
10768
  var URI_PREFIX = "dshr:";
10753
10769
  var TOKEN_RE = /^\d{6}$/;
@@ -11593,10 +11609,10 @@ function mergeDefs(...defs) {
11593
11609
  function cloneDef(schema) {
11594
11610
  return mergeDefs(schema._zod.def);
11595
11611
  }
11596
- function getElementAtPath(obj, path6) {
11597
- if (!path6)
11612
+ function getElementAtPath(obj, path7) {
11613
+ if (!path7)
11598
11614
  return obj;
11599
- return path6.reduce((acc, key) => acc?.[key], obj);
11615
+ return path7.reduce((acc, key) => acc?.[key], obj);
11600
11616
  }
11601
11617
  function promiseAllObject(promisesObj) {
11602
11618
  const keys = Object.keys(promisesObj);
@@ -11936,11 +11952,11 @@ function explicitlyAborted(x, startIndex = 0) {
11936
11952
  }
11937
11953
  return false;
11938
11954
  }
11939
- function prefixIssues(path6, issues) {
11955
+ function prefixIssues(path7, issues) {
11940
11956
  return issues.map((iss) => {
11941
11957
  var _a3;
11942
11958
  (_a3 = iss).path ?? (_a3.path = []);
11943
- iss.path.unshift(path6);
11959
+ iss.path.unshift(path7);
11944
11960
  return iss;
11945
11961
  });
11946
11962
  }
@@ -12390,16 +12406,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
12390
12406
  }
12391
12407
  function formatError(error62, mapper = (issue2) => issue2.message) {
12392
12408
  const fieldErrors = { _errors: [] };
12393
- const processError = (error63, path6 = []) => {
12409
+ const processError = (error63, path7 = []) => {
12394
12410
  for (const issue2 of error63.issues) {
12395
12411
  if (issue2.code === "invalid_union" && issue2.errors.length) {
12396
- issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
12412
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
12397
12413
  } else if (issue2.code === "invalid_key") {
12398
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
12414
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
12399
12415
  } else if (issue2.code === "invalid_element") {
12400
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
12416
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
12401
12417
  } else {
12402
- const fullpath = [...path6, ...issue2.path];
12418
+ const fullpath = [...path7, ...issue2.path];
12403
12419
  if (fullpath.length === 0) {
12404
12420
  fieldErrors._errors.push(mapper(issue2));
12405
12421
  } else {
@@ -12438,17 +12454,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
12438
12454
  }
12439
12455
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
12440
12456
  const result2 = { errors: [] };
12441
- const processError = (error63, path6 = []) => {
12457
+ const processError = (error63, path7 = []) => {
12442
12458
  var _a3;
12443
12459
  for (const issue2 of error63.issues) {
12444
12460
  if (issue2.code === "invalid_union" && issue2.errors.length) {
12445
- issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
12461
+ issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
12446
12462
  } else if (issue2.code === "invalid_key") {
12447
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
12463
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
12448
12464
  } else if (issue2.code === "invalid_element") {
12449
- processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
12465
+ processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
12450
12466
  } else {
12451
- const fullpath = [...path6, ...issue2.path];
12467
+ const fullpath = [...path7, ...issue2.path];
12452
12468
  if (fullpath.length === 0) {
12453
12469
  result2.errors.push(mapper(issue2));
12454
12470
  continue;
@@ -12487,8 +12503,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
12487
12503
  }
12488
12504
  function toDotPath(_path) {
12489
12505
  const segs = [];
12490
- const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12491
- for (const seg of path6) {
12506
+ const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12507
+ for (const seg of path7) {
12492
12508
  if (typeof seg === "number")
12493
12509
  segs.push(`[${seg}]`);
12494
12510
  else if (typeof seg === "symbol")
@@ -29590,13 +29606,13 @@ function resolveRef(ref, ctx) {
29590
29606
  if (!ref.startsWith("#")) {
29591
29607
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
29592
29608
  }
29593
- const path6 = ref.slice(1).split("/").filter(Boolean);
29594
- if (path6.length === 0) {
29609
+ const path7 = ref.slice(1).split("/").filter(Boolean);
29610
+ if (path7.length === 0) {
29595
29611
  return ctx.rootSchema;
29596
29612
  }
29597
29613
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
29598
- if (path6[0] === defsKey) {
29599
- const key = path6[1] === void 0 ? void 0 : decodeJSONPointerSegment(path6[1]);
29614
+ if (path7[0] === defsKey) {
29615
+ const key = path7[1] === void 0 ? void 0 : decodeJSONPointerSegment(path7[1]);
29600
29616
  if (!key || !ctx.defs[key]) {
29601
29617
  throw new Error(`Reference not found: ${ref}`);
29602
29618
  }
@@ -30445,7 +30461,7 @@ function date4(params) {
30445
30461
  return _coercedDate(ZodDate, params);
30446
30462
  }
30447
30463
 
30448
- // ../../node_modules/.pnpm/dsh-remote-wire@1.2.0/node_modules/dsh-remote-wire/dist/src/wire/frames.js
30464
+ // ../../node_modules/.pnpm/dsh-remote-wire@1.4.0/node_modules/dsh-remote-wire/dist/src/wire/frames.js
30449
30465
  var PAIRING_TOKEN_RE = /^\d{6}$/;
30450
30466
  var nonEmpty = external_exports.string().min(1);
30451
30467
  var pairingToken = external_exports.string().regex(PAIRING_TOKEN_RE);
@@ -30604,7 +30620,7 @@ function parseRelayFrameText(raw) {
30604
30620
  }
30605
30621
  }
30606
30622
 
30607
- // ../../node_modules/.pnpm/dsh-remote-wire@1.2.0/node_modules/dsh-remote-wire/dist/src/wire/payloads.js
30623
+ // ../../node_modules/.pnpm/dsh-remote-wire@1.4.0/node_modules/dsh-remote-wire/dist/src/wire/payloads.js
30608
30624
  var SESSION_STATES = [
30609
30625
  "idle",
30610
30626
  "running",
@@ -30711,6 +30727,16 @@ var evRunState = external_exports.object({
30711
30727
  state: external_exports.enum(["running", "idle"]),
30712
30728
  detail: external_exports.string().optional()
30713
30729
  });
30730
+ var todoItem = external_exports.object({
30731
+ /** 待办正文。内核侧不限长,协议侧截到 200 字(面板高度有限,超长的部分看不见)。 */
30732
+ content: external_exports.string(),
30733
+ status: external_exports.enum(["pending", "in_progress", "completed"])
30734
+ });
30735
+ var evTodo = external_exports.object({
30736
+ t: external_exports.literal("ev.todo"),
30737
+ sessionId: nonEmpty2.optional(),
30738
+ todos: external_exports.array(todoItem)
30739
+ });
30714
30740
  var evKeepAwakeState = external_exports.object({
30715
30741
  t: external_exports.literal("ev.keep_awake_state"),
30716
30742
  enabled: external_exports.boolean(),
@@ -30762,17 +30788,34 @@ var evPayload = external_exports.discriminatedUnion("t", [
30762
30788
  evQuestionRequest,
30763
30789
  evQuestionResolved,
30764
30790
  evRunState,
30791
+ evTodo,
30765
30792
  evKeepAwakeState,
30766
30793
  evModel,
30767
30794
  evResult,
30768
30795
  evSessionHistory
30769
30796
  ]);
30797
+ var imageAttachment = external_exports.object({
30798
+ /** 落盘用的文件名(mp 侧生成,形如 `img-<ms>-<n>.jpg`;主机侧还会再收敛一次字符集)。 */
30799
+ name: nonEmpty2,
30800
+ mediaType: external_exports.literal("image/jpeg"),
30801
+ /** base64 字节,**不带** `data:` 前缀。 */
30802
+ data: nonEmpty2,
30803
+ /** 压缩后的像素尺寸,主机写旁车元数据用(排错时对得上)。 */
30804
+ width: external_exports.number().int().positive().optional(),
30805
+ height: external_exports.number().int().positive().optional()
30806
+ });
30770
30807
  var cmdSendPrompt = external_exports.object({
30771
30808
  t: external_exports.literal("cmd.send_prompt"),
30772
30809
  cmdId: nonEmpty2,
30773
30810
  /** DSH 会话 id,不是配对通道 id。 */
30774
30811
  sessionId: nonEmpty2,
30775
- text: external_exports.string()
30812
+ text: external_exports.string(),
30813
+ /**
30814
+ * 随消息的图片附件。**可选**:老版本手机不带这个字段,老主机看到也不认
30815
+ * (zod 默认 strip 未知键,而 text 照旧)——所以这是向后兼容的增补,不升协议版本。
30816
+ * 上限 4 张:再多对"看清楚这张图"没有帮助,只是把帧撑爆。
30817
+ */
30818
+ images: external_exports.array(imageAttachment).max(4).optional()
30776
30819
  });
30777
30820
  var answerItem = external_exports.object({
30778
30821
  questionId: nonEmpty2,
@@ -30855,13 +30898,14 @@ var PAYLOAD_TYPES = {
30855
30898
  evQuestionRequest: "ev.question_request",
30856
30899
  evQuestionResolved: "ev.question_resolved",
30857
30900
  evRunState: "ev.run_state",
30901
+ evTodo: "ev.todo",
30858
30902
  evKeepAwakeState: "ev.keep_awake_state",
30859
30903
  evModel: "ev.model",
30860
30904
  evResult: "ev.result",
30861
30905
  evSessionHistory: "ev.session_history"
30862
30906
  };
30863
30907
 
30864
- // ../../node_modules/.pnpm/dsh-remote-wire@1.2.0/node_modules/dsh-remote-wire/dist/src/wire/outbound.js
30908
+ // ../../node_modules/.pnpm/dsh-remote-wire@1.4.0/node_modules/dsh-remote-wire/dist/src/wire/outbound.js
30865
30909
  function sessionChanged(sessions, reason) {
30866
30910
  return { t: PAYLOAD_TYPES.evSessionChanged, sessions, ...reason === void 0 ? {} : { reason } };
30867
30911
  }
@@ -30886,6 +30930,9 @@ function questionResolved(args) {
30886
30930
  function runState(args) {
30887
30931
  return { t: PAYLOAD_TYPES.evRunState, ...args };
30888
30932
  }
30933
+ function todoList(args) {
30934
+ return { t: PAYLOAD_TYPES.evTodo, ...args };
30935
+ }
30889
30936
  function keepAwakeState(args) {
30890
30937
  return { t: PAYLOAD_TYPES.evKeepAwakeState, ...args };
30891
30938
  }
@@ -30920,7 +30967,7 @@ function sessionHistory(args) {
30920
30967
  };
30921
30968
  }
30922
30969
 
30923
- // ../../node_modules/.pnpm/dsh-remote-wire@1.2.0/node_modules/dsh-remote-wire/dist/src/platform/sleep.js
30970
+ // ../../node_modules/.pnpm/dsh-remote-wire@1.4.0/node_modules/dsh-remote-wire/dist/src/platform/sleep.js
30924
30971
  function detectBackend(platform) {
30925
30972
  if (platform === "darwin")
30926
30973
  return "caffeinate";
@@ -30961,6 +31008,81 @@ function buildSleepCommand(platform, ownerPid, keepDisplay = false) {
30961
31008
  // src/core/runtime.ts
30962
31009
  import { randomUUID } from "node:crypto";
30963
31010
 
31011
+ // src/shell/uploads.ts
31012
+ import fs from "node:fs";
31013
+ import path from "node:path";
31014
+ function safeSegment(raw, fallback, maxLen = 96) {
31015
+ const cleaned = String(raw || "").replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^[._-]+/, "").slice(0, maxLen).replace(/[._-]+$/, "");
31016
+ return cleaned === "" ? fallback : cleaned;
31017
+ }
31018
+ function decodeBase64(data) {
31019
+ if (!/^[A-Za-z0-9+/=\s]+$/.test(data)) return null;
31020
+ const buf = Buffer.from(data, "base64");
31021
+ return buf.length > 0 ? buf : null;
31022
+ }
31023
+ function looksLikeJpeg(buf) {
31024
+ return buf.length > 3 && buf[0] === 255 && buf[1] === 216 && buf[2] === 255;
31025
+ }
31026
+ function saveImageAttachments(options) {
31027
+ const maxCount = options.maxCount ?? 4;
31028
+ const maxBytes = options.maxBytesPerImage ?? 4 * 1024 * 1024;
31029
+ const images = options.images || [];
31030
+ if (images.length === 0) return { ok: true, saved: [], dir: options.dir };
31031
+ if (images.length > maxCount) {
31032
+ return { ok: false, message: `\u4E00\u6B21\u6700\u591A\u5E26 ${maxCount} \u5F20\u56FE\u7247\uFF0C\u6536\u5230 ${images.length} \u5F20` };
31033
+ }
31034
+ const decoded = [];
31035
+ for (const [i, img] of images.entries()) {
31036
+ if (img.mediaType !== "image/jpeg") {
31037
+ return { ok: false, message: `\u7B2C ${i + 1} \u5F20\u4E0D\u662F jpeg\uFF08${img.mediaType || "\u7A7A"}\uFF09\uFF0C\u4E3B\u673A\u53EA\u6536 jpeg` };
31038
+ }
31039
+ const buf = decodeBase64(img.data);
31040
+ if (!buf) return { ok: false, message: `\u7B2C ${i + 1} \u5F20\u7684\u89E3\u7801\u5931\u8D25\uFF08\u4E0D\u662F\u5408\u6CD5 base64\uFF09` };
31041
+ if (buf.length > maxBytes) {
31042
+ return {
31043
+ ok: false,
31044
+ message: `\u7B2C ${i + 1} \u5F20\u6709 ${Math.round(buf.length / 1024)}KB\uFF0C\u8D85\u8FC7\u5355\u5F20\u4E0A\u9650 ${Math.round(maxBytes / 1024)}KB`
31045
+ };
31046
+ }
31047
+ if (!looksLikeJpeg(buf)) {
31048
+ return { ok: false, message: `\u7B2C ${i + 1} \u5F20\u7684\u5185\u5BB9\u4E0D\u662F jpeg\uFF08\u6587\u4EF6\u5934\u5BF9\u4E0D\u4E0A\uFF09` };
31049
+ }
31050
+ decoded.push({ img, buf });
31051
+ }
31052
+ const sessionDir = path.join(options.dir, safeSegment(options.sessionId, "session"));
31053
+ const written = [];
31054
+ try {
31055
+ fs.mkdirSync(sessionDir, { recursive: true });
31056
+ for (const [i, one] of decoded.entries()) {
31057
+ const base = safeSegment(one.img.name, `img-${i + 1}.jpg`).replace(/\.jpg$/i, "") + ".jpg";
31058
+ let name = base;
31059
+ let n = 1;
31060
+ while (fs.existsSync(path.join(sessionDir, name))) {
31061
+ name = base.replace(/\.jpg$/, `-${++n}.jpg`);
31062
+ }
31063
+ const file2 = path.join(sessionDir, name);
31064
+ fs.writeFileSync(file2, one.buf, { mode: 384 });
31065
+ written.push({ name, path: file2, bytes: one.buf.length });
31066
+ }
31067
+ } catch (error62) {
31068
+ return {
31069
+ ok: false,
31070
+ message: `\u56FE\u7247\u843D\u76D8\u5931\u8D25\uFF1A${String(error62?.message ?? error62).slice(0, 160)}`
31071
+ };
31072
+ }
31073
+ return { ok: true, saved: written, dir: sessionDir };
31074
+ }
31075
+ function appendImageNote(text, saved) {
31076
+ if (saved.length === 0) return text;
31077
+ const lines = saved.map((img, i) => `${i + 1}. ${img.path}`);
31078
+ const note = `
31079
+
31080
+ [\u56FE\u7247\u9644\u4EF6 ${saved.length} \u5F20\uFF0C\u5DF2\u5B58\u5230\u672C\u673A]
31081
+ ${lines.join("\n")}`;
31082
+ const head = String(text || "");
31083
+ return head === "" ? note.replace(/^\n\n/, "") : head + note;
31084
+ }
31085
+
30964
31086
  // src/core/window.ts
30965
31087
  var DEFAULT_WINDOW_OPTIONS = {
30966
31088
  windowMs: 120,
@@ -31131,7 +31253,11 @@ var DEFAULTS = {
31131
31253
  listingRefreshMs: 15e3,
31132
31254
  approvalTimeoutMs: 18e4,
31133
31255
  questionTimeoutMs: 3e5,
31134
- unarchiveOnPrompt: true
31256
+ unarchiveOnPrompt: true,
31257
+ // 图片附件:默认**不收**(uploadDir 空 = 拒收)。要放开必须在插件配置里显式
31258
+ // 给一个目录——落盘是往用户磁盘写文件,不该由库默认值悄悄代劳。
31259
+ uploadDir: "",
31260
+ maxImageBytes: 4 * 1024 * 1024
31135
31261
  };
31136
31262
  var LISTING_LIMIT = 100;
31137
31263
  var HISTORY_LIMIT = 40;
@@ -31218,7 +31344,32 @@ var HostRuntime = class {
31218
31344
  return;
31219
31345
  }
31220
31346
  }
31221
- const sent = await this.kernel.sendPrompt(cmd.sessionId, cmd.text);
31347
+ let text = cmd.text;
31348
+ const images = Array.isArray(cmd.images) ? cmd.images : [];
31349
+ if (images.length > 0) {
31350
+ if (!this.options.uploadDir) {
31351
+ reply(false, { message: "\u8FD9\u53F0\u4E3B\u673A\u6CA1\u914D\u56FE\u7247\u843D\u76D8\u76EE\u5F55\uFF08uploadDir\uFF09\uFF0C\u6536\u4E0D\u4E86\u56FE\u7247\u9644\u4EF6" });
31352
+ return;
31353
+ }
31354
+ const saved = saveImageAttachments({
31355
+ images,
31356
+ dir: this.options.uploadDir,
31357
+ sessionId: cmd.sessionId,
31358
+ maxBytesPerImage: this.options.maxImageBytes
31359
+ });
31360
+ if (!saved.ok) {
31361
+ reply(false, { message: saved.message });
31362
+ return;
31363
+ }
31364
+ this.options.log?.("\u56FE\u7247\u9644\u4EF6\u843D\u76D8", {
31365
+ sessionId: cmd.sessionId,
31366
+ images: saved.saved.length,
31367
+ bytes: saved.saved.reduce((sum, img) => sum + img.bytes, 0),
31368
+ dir: saved.dir
31369
+ });
31370
+ text = appendImageNote(text, saved.saved);
31371
+ }
31372
+ const sent = await this.kernel.sendPrompt(cmd.sessionId, text);
31222
31373
  reply(sent.ok, sent.message ? { message: sent.message } : {});
31223
31374
  if (sent.ok) {
31224
31375
  this.sleep.markActive();
@@ -31356,6 +31507,11 @@ var HostRuntime = class {
31356
31507
  if (event.state === "running") this.sleep.markActive();
31357
31508
  void this.pushSessions("run-state");
31358
31509
  return;
31510
+ case "todo":
31511
+ this.window.flushSession(event.sessionId);
31512
+ this.broadcast(todoList({ todos: event.todos, sessionId: event.sessionId }));
31513
+ this.sleep.markActive();
31514
+ return;
31359
31515
  case "title":
31360
31516
  void this.pushSessions("title");
31361
31517
  return;
@@ -31567,9 +31723,11 @@ var HostRuntime = class {
31567
31723
  * 再补一帧 `ev.run_state` 兜住"手机上装的是老版小程序"这一档。
31568
31724
  *
31569
31725
  * 为什么要两条而不是一条:`ev.permission_resolved` / `ev.question_resolved` 是按
31570
- * `requestId` 收单的(一次答完不会误收别的会话、别的请求那张卡),但**用户手机上现在装的
31571
- * 那一版还不认这两帧**——小程序的分发是一串 `if (p.t === …)`,认不出来的 `t` 静默忽略。
31572
- * 而 `ev.run_state` 正好是它早就认的那条"挂着的审批/提问作废"信号
31726
+ * `requestId` 收单的(一次答完不会误收别的会话、别的请求那张卡),这是**正路**——
31727
+ * mp 1.0.1 起就认这两帧(`pages/chat/chat.js` 的 `_onPermissionResolved` /
31728
+ * `_onQuestionResolved`)。而 `ev.run_state` 是它从第一版就认的粗粒度信号
31729
+ * (整会话两张卡一起收),发它只为兜住 **1.0.1 以前**的安装存量——那一档小程序
31730
+ * 的分发是一串 `if (p.t === …)`,认不出来的 `t` 静默忽略。
31573
31731
  * (`pages/chat/chat.js` 的 `_onRunState` 同时清 `pendingPermission` 与 `pendingQuestion`,
31574
31732
  * 并停掉那条还在走的本地倒数)。所以两条都发:新版手机收到精确帧就精收一张,
31575
31733
  * 老版手机靠 `run_state` 也能当场收卡,不用等用户重新上传小程序。
@@ -32782,8 +32940,18 @@ function createServicesKernel(services, options) {
32782
32940
  return { ok: false, message: `sessionController.commands \u4E0A\u6CA1\u6709 create()\uFF08\u6210\u5458\uFF1A${shapeOf(commands)}\uFF09` };
32783
32941
  }
32784
32942
  try {
32943
+ let cwd = String(options.newSessionCwd ?? "").trim();
32944
+ if (!cwd) {
32945
+ try {
32946
+ const newest = (await listSessions(1))[0];
32947
+ cwd = String(newest?.summary?.workspace ?? "").trim();
32948
+ } catch {
32949
+ }
32950
+ }
32951
+ const request = cwd === "" ? {} : { cwd };
32952
+ if (cwd === "") log("new session without explicit cwd; host default applies", {});
32785
32953
  const made = await withTimeout(
32786
- Promise.resolve(create.call(commands, {})),
32954
+ Promise.resolve(create.call(commands, request)),
32787
32955
  options.createTimeoutMs ?? CREATE_TIMEOUT_DEFAULT
32788
32956
  );
32789
32957
  const id = typeof made?.sessionId === "string" ? made.sessionId : "";
@@ -33218,12 +33386,6 @@ function createServicesKernel(services, options) {
33218
33386
  modelFace: modelFaceSummary(),
33219
33387
  workspaceRegistry: typeof services.workspaceRegistry === "object",
33220
33388
  userQuestions: typeof services.userQuestions === "object",
33221
- // 这个键**已经不生效**了:提问现在跟审批一样是 waterfall 参与者,两端同弹、
33222
- // 谁先答谁算,不再有"接管/取代桌面"这回事(那条 `registerProvider` 单提供者
33223
- // 路线在这一代宿主上根本不存在)。这里照实把配置值报出来,真正的状态看
33224
- // `questionsFace`。(删键要动 config.ts/index.ts,那两棵树此刻有并行会话在飞的改动。)
33225
- takeOverQuestions: options.takeOverQuestions === true,
33226
- takeOverQuestionsInert: options.takeOverQuestions === true,
33227
33389
  archivedSessions: archivedIds().size,
33228
33390
  hasOn: typeof services.on === "function",
33229
33391
  // 真机排错的第一现场:内核到底发了哪些事件类型、其中哪些我们没认。
@@ -33258,7 +33420,10 @@ var MAPPED_SESSION_EVENTS = /* @__PURE__ */ new Set([
33258
33420
  "turn/end",
33259
33421
  "session/title",
33260
33422
  "approval/asked",
33261
- "approval/decided"
33423
+ "approval/decided",
33424
+ // 内核的 todo/write 在真机的 eventTypes 里(status.json 的 unmappedEventTypes 曾一直记着它),
33425
+ // 但没人转发到手机——于是手机上一整轮"现在在干什么"都是空白。2026-10-05 补上。
33426
+ "todo/write"
33262
33427
  ]);
33263
33428
  function sessionEventKernelEvents(input2) {
33264
33429
  const { sessionId, type, data } = input2;
@@ -33305,6 +33470,21 @@ function sessionEventKernelEvents(input2) {
33305
33470
  }
33306
33471
  ];
33307
33472
  }
33473
+ case "todo/write": {
33474
+ const raw = Array.isArray(data.todos) ? data.todos : [];
33475
+ const todos = [];
33476
+ for (const item of raw) {
33477
+ if (todos.length >= 50) break;
33478
+ const content = typeof item?.content === "string" ? item.content.trim() : "";
33479
+ if (!content) continue;
33480
+ const status = item?.status;
33481
+ todos.push({
33482
+ content: content.slice(0, 200),
33483
+ status: status === "in_progress" || status === "completed" || status === "pending" ? status : "pending"
33484
+ });
33485
+ }
33486
+ return [{ kind: "todo", sessionId, todos }];
33487
+ }
33308
33488
  case "tool/result": {
33309
33489
  const message = data.message;
33310
33490
  const callId = data.callId ?? message?.toolCallId ?? message?.source?.callId ?? "";
@@ -33576,7 +33756,7 @@ function createOneShotTimers(clock) {
33576
33756
 
33577
33757
  // src/shell/status.ts
33578
33758
  import { mkdirSync, renameSync, writeFileSync } from "node:fs";
33579
- import path from "node:path";
33759
+ import path2 from "node:path";
33580
33760
  var StatusFile = class {
33581
33761
  constructor(file2, clock, refreshMs = 3e3) {
33582
33762
  this.file = file2;
@@ -33590,7 +33770,7 @@ var StatusFile = class {
33590
33770
  write(snapshot) {
33591
33771
  if (!this.file) return;
33592
33772
  try {
33593
- mkdirSync(path.dirname(this.file), { recursive: true });
33773
+ mkdirSync(path2.dirname(this.file), { recursive: true });
33594
33774
  const tmp = `${this.file}.tmp-${process.pid}`;
33595
33775
  writeFileSync(
33596
33776
  tmp,
@@ -33633,7 +33813,7 @@ var StatusFile = class {
33633
33813
  // src/shell/host-id.ts
33634
33814
  import { randomBytes as randomBytes4 } from "node:crypto";
33635
33815
  import { chmodSync, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
33636
- import path2 from "node:path";
33816
+ import path3 from "node:path";
33637
33817
  var HOST_ID = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/;
33638
33818
  function newHostId() {
33639
33819
  return `drc-${randomBytes4(6).toString("hex")}`;
@@ -33641,7 +33821,7 @@ function newHostId() {
33641
33821
  function resolveHostId(dir, configured) {
33642
33822
  if (configured) return configured;
33643
33823
  if (!dir) return newHostId();
33644
- const file2 = path2.join(dir, "host-id");
33824
+ const file2 = path3.join(dir, "host-id");
33645
33825
  let existing;
33646
33826
  try {
33647
33827
  existing = readFileSync(file2, "utf8").trim();
@@ -33673,7 +33853,7 @@ function resolveHostId(dir, configured) {
33673
33853
  // src/shell/pair-store.ts
33674
33854
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
33675
33855
  import { createHash as createHash2 } from "node:crypto";
33676
- import path3 from "node:path";
33856
+ import path4 from "node:path";
33677
33857
  var storedConversationSchema = external_exports.object({
33678
33858
  id: external_exports.string().min(1),
33679
33859
  /** 配对 PSK(base64)。两把方向密钥在恢复时由它现派生,不落盘。 */
@@ -33691,14 +33871,14 @@ var storeSchema = external_exports.object({
33691
33871
  savedAt: external_exports.number().finite().nonnegative(),
33692
33872
  conversations: external_exports.array(storedConversationSchema)
33693
33873
  });
33694
- function safeSegment(hostId) {
33874
+ function safeSegment2(hostId) {
33695
33875
  const cleaned = hostId.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 48);
33696
33876
  const tag = createHash2("sha256").update(hostId).digest("hex").slice(0, 8);
33697
33877
  return `${cleaned || "host"}-${tag}`;
33698
33878
  }
33699
33879
  function defaultPairStoreFile(statusFile, hostId) {
33700
33880
  if (!statusFile) return "";
33701
- return path3.join(path3.dirname(statusFile), `conversations-${safeSegment(hostId)}.json`);
33881
+ return path4.join(path4.dirname(statusFile), `conversations-${safeSegment2(hostId)}.json`);
33702
33882
  }
33703
33883
  var PairStore = class {
33704
33884
  constructor(options) {
@@ -33768,7 +33948,7 @@ var PairStore = class {
33768
33948
  const body = JSON.stringify({ version: 1, hostId, savedAt: now, conversations }) + "\n";
33769
33949
  const tmp = `${file2}.tmp-${process.pid}`;
33770
33950
  try {
33771
- mkdirSync3(path3.dirname(file2), { recursive: true });
33951
+ mkdirSync3(path4.dirname(file2), { recursive: true });
33772
33952
  writeFileSync3(tmp, body, { encoding: "utf8", mode: 384 });
33773
33953
  chmodSync2(tmp, 384);
33774
33954
  renameSync2(tmp, file2);
@@ -33797,7 +33977,7 @@ function safeJsonParse(raw) {
33797
33977
 
33798
33978
  // src/shell/config.ts
33799
33979
  import { homedir } from "node:os";
33800
- import path4 from "node:path";
33980
+ import path5 from "node:path";
33801
33981
  var DEFAULT_CONFIG = {
33802
33982
  enabled: true,
33803
33983
  serverUrl: "ws://127.0.0.1:8787",
@@ -33807,12 +33987,11 @@ var DEFAULT_CONFIG = {
33807
33987
  hostLabel: "dsh-host",
33808
33988
  keepAwake: { enabled: true, idleReleaseSec: 300, keepDisplay: false },
33809
33989
  mockBridge: false,
33810
- statusFile: path4.join(homedir(), ".dsh", "dsh-remote-control", "status.json"),
33990
+ statusFile: path5.join(homedir(), ".dsh", "dsh-remote-control", "status.json"),
33811
33991
  unarchiveOnPrompt: true,
33812
33992
  pairOnStartSec: 0,
33813
33993
  pairTtlMs: 12e4,
33814
33994
  carrierGraceMs: 5e3,
33815
- takeOverQuestions: false,
33816
33995
  approvalTimeoutSec: 180,
33817
33996
  listingRefreshSec: 15,
33818
33997
  conversationIdleTtlSec: 86400,
@@ -33820,6 +33999,10 @@ var DEFAULT_CONFIG = {
33820
33999
  restoredIdleTtlSec: 604800,
33821
34000
  // 密钥簿落盘:默认跟着 statusFile 走(见 `resolvePairStoreFile`)。
33822
34001
  pairStoreFile: "",
34002
+ // 图片附件落盘:默认 status.json 同目录的 uploads/(见 `resolveUploadDir`)。
34003
+ uploadDir: "",
34004
+ maxImageBytes: 4 * 1024 * 1024,
34005
+ newSessionCwd: "",
33823
34006
  // 配对的唯一入口。关掉它 = 这台主机**没有**配对入口(`/drc pair` 与文本二维码都在
33824
34007
  // 2026-10-03 删掉了),所以 index.ts 会把它记成一条 warn 而不是安静地什么都不做。
33825
34008
  pill: { enabled: true }
@@ -33831,7 +34014,17 @@ function resolvePairStoreFile(config2, hostId) {
33831
34014
  if (!configured) return defaultPairStoreFile(config2.statusFile, hostId);
33832
34015
  if (configured.startsWith("/")) return configured;
33833
34016
  if (!config2.statusFile) return configured;
33834
- return path4.join(path4.dirname(config2.statusFile), configured);
34017
+ return path5.join(path5.dirname(config2.statusFile), configured);
34018
+ }
34019
+ function resolveUploadDir(config2) {
34020
+ const configured = config2.uploadDir.trim();
34021
+ if (!configured) {
34022
+ const base = config2.statusFile ? path5.dirname(config2.statusFile) : path5.join(homedir(), ".dsh", "dsh-remote-control");
34023
+ return path5.join(base, "uploads");
34024
+ }
34025
+ if (configured.startsWith("/")) return configured;
34026
+ if (!config2.statusFile) return configured;
34027
+ return path5.join(path5.dirname(config2.statusFile), configured);
33835
34028
  }
33836
34029
  var loopback = /^wss?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:|\/|$)/;
33837
34030
  var wsUrl = /^wss?:\/\/.+/;
@@ -33860,7 +34053,7 @@ function readConfig(injected, env = process.env) {
33860
34053
  if (fromEnv("DRC_PAIR_ON_START_SEC")) merged.pairOnStartSec = envSeconds(env.DRC_PAIR_ON_START_SEC);
33861
34054
  if (fromEnv("DRC_PILL")) merged.pill.enabled = envFlag(env.DRC_PILL, merged.pill.enabled);
33862
34055
  if (fromEnv("DRC_MOCK_BRIDGE") === "1") merged.mockBridge = true;
33863
- if (fromEnv("DRC_TAKE_OVER_QUESTIONS") === "1") merged.takeOverQuestions = true;
34056
+ if (fromEnv("DRC_NEW_SESSION_CWD")) merged.newSessionCwd = env.DRC_NEW_SESSION_CWD;
33864
34057
  if (merged.hostTokenEnv) merged.hostToken = env[merged.hostTokenEnv] ?? merged.hostToken;
33865
34058
  return merged;
33866
34059
  }
@@ -33935,13 +34128,6 @@ function validateConfig(config2) {
33935
34128
  if (!config2.statusFile) {
33936
34129
  problems.push({ level: "warn", field: "statusFile", message: "\u4E3A\u7A7A\u5C06\u5173\u95ED\u72B6\u6001\u5FEB\u7167\uFF1BGUI \u5BBF\u4E3B\u91CC\u8FD9\u662F\u552F\u4E00\u7684\u6392\u9519\u5165\u53E3" });
33937
34130
  }
33938
- if (config2.takeOverQuestions) {
33939
- problems.push({
33940
- level: "warn",
33941
- field: "takeOverQuestions",
33942
- message: "\u63D0\u95EE\u63A5\u7BA1\u4F1A**\u53D6\u4EE3\u684C\u9762 UI \u7684\u63D0\u95EE\u80FD\u529B**\uFF08ctx.userQuestions \u53EA\u5141\u8BB8\u4E00\u4E2A\u6D3B\u8DC3 provider\uFF09\uFF1B\u624B\u673A\u7AEF\u4E0D\u5728\u7EBF\u65F6\u63D0\u95EE\u4F1A\u76F4\u63A5\u5931\u8D25"
33943
- });
33944
- }
33945
34131
  return problems;
33946
34132
  }
33947
34133
  function redact(value) {
@@ -34098,6 +34284,7 @@ function pillStatusHandler(deps) {
34098
34284
  json2(response, 200, {
34099
34285
  relay: status.relay,
34100
34286
  paired: status.paired,
34287
+ pairings: status.pairings,
34101
34288
  serverUrl: status.serverUrl,
34102
34289
  version: status.version,
34103
34290
  waiting: status.waiting,
@@ -34261,7 +34448,7 @@ function startPill(ctx, deps) {
34261
34448
  }
34262
34449
 
34263
34450
  // src/version.ts
34264
- var PLUGIN_VERSION = true ? "2.0.0" : "dev";
34451
+ var PLUGIN_VERSION = true ? "2.0.2" : "dev";
34265
34452
 
34266
34453
  // src/index.ts
34267
34454
  var SERVICE_NAMES = [
@@ -34317,7 +34504,7 @@ function applyInner(ctx, injected) {
34317
34504
  const clock = systemClock;
34318
34505
  const oneShot = createOneShotTimers(clock);
34319
34506
  const status = new StatusFile(config2.statusFile, clock);
34320
- const hostId = resolveHostId(config2.statusFile ? path5.dirname(config2.statusFile) : "", config2.hostId);
34507
+ const hostId = resolveHostId(config2.statusFile ? path6.dirname(config2.statusFile) : "", config2.hostId);
34321
34508
  const log = (message, fields = {}) => {
34322
34509
  try {
34323
34510
  process.stdout.write(
@@ -34441,6 +34628,8 @@ function applyInner(ctx, injected) {
34441
34628
  runtime = new HostRuntime(port, transport, sleep, clock, {
34442
34629
  listingRefreshMs: config2.listingRefreshSec * 1e3,
34443
34630
  unarchiveOnPrompt: config2.unarchiveOnPrompt,
34631
+ uploadDir: resolveUploadDir(config2),
34632
+ maxImageBytes: config2.maxImageBytes,
34444
34633
  approvalTimeoutMs: config2.approvalTimeoutSec * 1e3,
34445
34634
  log: (message, fields) => log(message, fields)
34446
34635
  });
@@ -34571,12 +34760,14 @@ function applyInner(ctx, injected) {
34571
34760
  function currentPairing() {
34572
34761
  const shown = active.pairing;
34573
34762
  if (!shown) return null;
34574
- if (!slots.resolveFor(shown.token)) return null;
34575
- return { qr: shown.qr, token: shown.token, expiresAt: shown.expiresAt };
34763
+ const slot = slots.resolveFor(shown.token);
34764
+ if (!slot) return null;
34765
+ return { qr: shown.qr, token: shown.token, expiresAt: slot.expiresAt };
34576
34766
  }
34577
34767
  function ensureFreshPairing() {
34578
34768
  const fresh = currentPairing() ?? createPairing();
34579
34769
  if (!fresh) return null;
34770
+ if (fresh.expiresAt <= Date.now()) return createPairing();
34580
34771
  return { qr: fresh.qr, token: fresh.token, expiresAt: fresh.expiresAt };
34581
34772
  }
34582
34773
  function pillStatus() {
@@ -34584,6 +34775,11 @@ function applyInner(ctx, injected) {
34584
34775
  return {
34585
34776
  relay: relay ? relayState() : "idle",
34586
34777
  paired: relay?.clientCount ?? 0,
34778
+ // 配对簿里几条会话 = 配对还在不在。为什么必须与 paired 分开:pair-store 之后
34779
+ // 配对按 D3 长存、落盘,手机退后台只是 socket 断——只用 clientCount 回答 配没配上,
34780
+ // 用户把小程序放进后台的每一分钟,桌面那颗 pill 都会说 未配对,点开还会烧一张
34781
+ // 不需要的新码(2026-10-04 用户报的误解)。
34782
+ pairings: relay?.conversationCount ?? 0,
34587
34783
  serverUrl: config2.serverUrl,
34588
34784
  version: PLUGIN_VERSION,
34589
34785
  waiting: waiting.count,
@@ -34625,7 +34821,13 @@ function applyInner(ctx, injected) {
34625
34821
  return;
34626
34822
  }
34627
34823
  decided = true;
34628
- start(createServicesKernel(collected, { log, takeOverQuestions: config2.takeOverQuestions, clock }));
34824
+ start(
34825
+ createServicesKernel(collected, {
34826
+ log,
34827
+ clock,
34828
+ newSessionCwd: config2.newSessionCwd
34829
+ })
34830
+ );
34629
34831
  return;
34630
34832
  }
34631
34833
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-control",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
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",
@@ -53,7 +53,7 @@
53
53
  "provenance": true
54
54
  },
55
55
  "dependencies": {
56
- "dsh-remote-wire": "^1.2.0",
56
+ "dsh-remote-wire": "^1.4.0",
57
57
  "qrcode": "^1.5.4",
58
58
  "tweetnacl": "^1.0.3",
59
59
  "ws": "^8.18.0",