dsh-deeppilot 0.4.0 → 0.5.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.
package/lib/client.js CHANGED
@@ -27,21 +27,48 @@ window.__ModuleLoader__.load({
27
27
  enumerable: true
28
28
  }) : target, mod));
29
29
  //#endregion
30
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
31
30
  let react = require("react");
31
+ //#region src/client/snapshot-store.ts
32
+ function createSnapshotStore(initial) {
33
+ let snapshot = initial;
34
+ const listeners = /* @__PURE__ */ new Set();
35
+ return {
36
+ getSnapshot: () => snapshot,
37
+ subscribe(listener) {
38
+ listeners.add(listener);
39
+ return () => listeners.delete(listener);
40
+ },
41
+ set(value) {
42
+ if (Object.is(value, snapshot)) return;
43
+ snapshot = value;
44
+ for (const listener of listeners) listener();
45
+ }
46
+ };
47
+ }
48
+ //#endregion
49
+ //#region src/device-auth.ts
50
+ const DEVICE_SCOPES = [
51
+ "sessions.read",
52
+ "prompt.send",
53
+ "sessions.manage",
54
+ "interactions.respond",
55
+ "notifications.register"
56
+ ];
57
+ const DEFAULT_DEVICE_SCOPES = DEVICE_SCOPES;
58
+ function normalizeDeviceScopes(value) {
59
+ if (!Array.isArray(value)) return [...DEFAULT_DEVICE_SCOPES];
60
+ const allowed = new Set(DEVICE_SCOPES);
61
+ return [...new Set(value.filter((scope) => typeof scope === "string" && allowed.has(scope)))];
62
+ }
63
+ //#endregion
32
64
  //#region src/report-wire.ts
33
65
  /** The npm package identity both contribution registrations claim. */
34
66
  const REPORT_REMOTE_PACKAGE = "dsh-deeppilot";
35
67
  /** Canonical `<namespace>/<method>` endpoint of the report Remote. */
36
68
  const REPORT_ENDPOINT = "deeppilot/report";
37
- /** Explicit, user-triggered endpoint for revealing the pairing secret. */
38
- const REVEAL_TOKEN_ENDPOINT = "deeppilot/revealToken";
39
- /**
40
- * Explicit, user-triggered endpoint that replaces the pairing secret. The old
41
- * token stops working immediately; the fresh one is returned so the page can
42
- * show/QR it right away.
43
- */
44
- const ROTATE_TOKEN_ENDPOINT = "deeppilot/rotateToken";
69
+ const BEGIN_PAIRING_ENDPOINT = "deeppilot/beginPairing";
70
+ const REVOKE_DEVICE_ENDPOINT = "deeppilot/revokeDevice";
71
+ const SET_DEVICE_SCOPES_ENDPOINT = "deeppilot/setDeviceScopes";
45
72
  function reject(field) {
46
73
  throw new TypeError(`deeppilot/report result: invalid ${field}`);
47
74
  }
@@ -89,6 +116,9 @@ window.__ModuleLoader__.load({
89
116
  appVersion: str(s, "appVersion", "device.appVersion"),
90
117
  firstSeenTs: int(s, "firstSeenTs", "device.firstSeenTs"),
91
118
  lastSeenTs: int(s, "lastSeenTs", "device.lastSeenTs"),
119
+ fingerprint: str(s, "fingerprint", "device.fingerprint"),
120
+ scopes: normalizeDeviceScopes(s.scopes),
121
+ ...s.revokedAt !== void 0 ? { revokedAt: int(s, "revokedAt", "device.revokedAt") } : {},
92
122
  ...apns ? { apns } : {}
93
123
  };
94
124
  }
@@ -196,8 +226,8 @@ window.__ModuleLoader__.load({
196
226
  ...s.updateAvailable === true ? { updateAvailable: true } : {},
197
227
  ...typeof releaseUrl === "string" && releaseUrl.length > 0 ? { releaseUrl } : {},
198
228
  enabled: bool(s, "enabled", "enabled"),
199
- tokenPath: str(s, "tokenPath", "tokenPath"),
200
- tokenReady: bool(s, "tokenReady", "tokenReady"),
229
+ identityPath: str(s, "identityPath", "identityPath"),
230
+ pairingReady: bool(s, "pairingReady", "pairingReady"),
201
231
  activeConnections: int(s, "activeConnections", "activeConnections"),
202
232
  historyBufferMax: int(s, "historyBufferMax", "historyBufferMax"),
203
233
  debug: bool(s, "debug", "debug"),
@@ -209,10 +239,24 @@ window.__ModuleLoader__.load({
209
239
  const reportSchema = { parse: parseReport };
210
240
  const relayTestSchema = { parse: parseRelayTestResult };
211
241
  const pushTestSchema = { parse: parsePushTestResult };
212
- const pairingTokenSchema = { parse(value) {
213
- if (typeof value !== "string" || value.length < 32) throw new TypeError("deeppilot/revealToken result: invalid token");
242
+ const pairingGrantSchema = { parse(value) {
243
+ const s = rec(value, "pairing grant");
244
+ const code = str(s, "code", "pairingGrant.code");
245
+ if (code.length < 32) reject("pairingGrant.code");
246
+ return {
247
+ code,
248
+ expiresAt: int(s, "expiresAt", "pairingGrant.expiresAt"),
249
+ audience: str(s, "audience", "pairingGrant.audience")
250
+ };
251
+ } };
252
+ const deviceIdSchema = { parse(value) {
253
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value)) reject("deviceId");
214
254
  return value;
215
255
  } };
256
+ const scopesSchema = { parse(value) {
257
+ if (!Array.isArray(value) || value.some((scope) => typeof scope !== "string" || !DEVICE_SCOPES.includes(scope))) reject("scopes");
258
+ return normalizeDeviceScopes(value);
259
+ } };
216
260
  const REPORT_REMOTE_CONTRIBUTION = {
217
261
  package: REPORT_REMOTE_PACKAGE,
218
262
  descriptors: [
@@ -230,29 +274,72 @@ window.__ModuleLoader__.load({
230
274
  }
231
275
  },
232
276
  {
233
- id: `${REPORT_REMOTE_PACKAGE}#${REVEAL_TOKEN_ENDPOINT}`,
277
+ id: `${REPORT_REMOTE_PACKAGE}#${BEGIN_PAIRING_ENDPOINT}`,
234
278
  service: "deeppilotReport",
235
279
  namespace: "deeppilot",
236
- method: "revealToken",
280
+ method: "beginPairing",
237
281
  invocation: { kind: "direct" },
238
282
  parameters: [],
239
283
  result: {
240
284
  mode: "strict",
241
- typeSymbol: `${REPORT_REMOTE_PACKAGE}#PairingToken`,
242
- schema: pairingTokenSchema
285
+ typeSymbol: `${REPORT_REMOTE_PACKAGE}#PairingGrantSnapshot`,
286
+ schema: pairingGrantSchema
243
287
  }
244
288
  },
245
289
  {
246
- id: `${REPORT_REMOTE_PACKAGE}#${ROTATE_TOKEN_ENDPOINT}`,
290
+ id: `${REPORT_REMOTE_PACKAGE}#${REVOKE_DEVICE_ENDPOINT}`,
247
291
  service: "deeppilotReport",
248
292
  namespace: "deeppilot",
249
- method: "rotateToken",
293
+ method: "revokeDevice",
250
294
  invocation: { kind: "direct" },
251
- parameters: [],
295
+ parameters: [{
296
+ name: "deviceId",
297
+ wire: "deviceId",
298
+ source: "json",
299
+ codec: {
300
+ mode: "strict",
301
+ typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceId`,
302
+ schema: deviceIdSchema
303
+ }
304
+ }],
305
+ result: {
306
+ mode: "strict",
307
+ typeSymbol: `${REPORT_REMOTE_PACKAGE}#Boolean`,
308
+ schema: { parse(value) {
309
+ if (typeof value !== "boolean") reject("boolean");
310
+ return value;
311
+ } }
312
+ }
313
+ },
314
+ {
315
+ id: `${REPORT_REMOTE_PACKAGE}#${SET_DEVICE_SCOPES_ENDPOINT}`,
316
+ service: "deeppilotReport",
317
+ namespace: "deeppilot",
318
+ method: "setDeviceScopes",
319
+ invocation: { kind: "direct" },
320
+ parameters: [{
321
+ name: "deviceId",
322
+ wire: "deviceId",
323
+ source: "json",
324
+ codec: {
325
+ mode: "strict",
326
+ typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceId`,
327
+ schema: deviceIdSchema
328
+ }
329
+ }, {
330
+ name: "scopes",
331
+ wire: "scopes",
332
+ source: "json",
333
+ codec: {
334
+ mode: "strict",
335
+ typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceScopes`,
336
+ schema: scopesSchema
337
+ }
338
+ }],
252
339
  result: {
253
340
  mode: "strict",
254
- typeSymbol: `${REPORT_REMOTE_PACKAGE}#PairingToken`,
255
- schema: pairingTokenSchema
341
+ typeSymbol: `${REPORT_REMOTE_PACKAGE}#DeviceScopes`,
342
+ schema: scopesSchema
256
343
  }
257
344
  },
258
345
  {
@@ -319,7 +406,7 @@ window.__ModuleLoader__.load({
319
406
  zh: {
320
407
  "nav": "DeepPilot",
321
408
  "meta.title": "DeepPilot",
322
- "meta.intro": "把 iPhone 与这台电脑上的 DeepSeek Harness(DSH)连接起来(协议 v1)。",
409
+ "meta.intro": "把 iPhone 与这台电脑上的 DeepSeek Harness(DSH)连接起来(协议 v2)。",
323
410
  "meta.refresh": "刷新",
324
411
  "master.title": "DeepPilot 连接",
325
412
  "master.loading": "正在读取配置…",
@@ -329,6 +416,13 @@ window.__ModuleLoader__.load({
329
416
  "remote.on": "已配置:内嵌 Funnel 会自动启动并同步状态。",
330
417
  "remote.off": "关闭:仅保留局域网连接。",
331
418
  "remote.openAuth": "打开授权页面",
419
+ "remote.advancedSettings": "高级设置",
420
+ "remote.limitTitle": "每个公网来源的连接上限",
421
+ "remote.limitDescription": "默认 8,范围 1–16。应用后 Funnel 会重启,已有远程连接将短暂重连。",
422
+ "remote.limitApply": "应用",
423
+ "remote.limitApplied": "连接上限已保存,远程入口正在重新加载。",
424
+ "remote.limitInvalid": "请输入 1–16 之间的整数。",
425
+ "remote.limitFailed": "连接上限保存失败:",
332
426
  "phase.disabled": "未启用",
333
427
  "phase.starting": "正在启动",
334
428
  "phase.login_required": "等待 Tailscale 授权",
@@ -338,6 +432,9 @@ window.__ModuleLoader__.load({
338
432
  "phase.stopped": "已停止",
339
433
  "phase.unknown": "状态未知",
340
434
  "panel.activeConnections": "当前连接",
435
+ "panel.identity": "设备身份认证",
436
+ "panel.identityReady": "v2 已就绪",
437
+ "panel.identityNotReady": "不可用",
341
438
  "panel.token": "配对 Token",
342
439
  "panel.tokenReady": "已就绪",
343
440
  "panel.tokenNotReady": "未生成",
@@ -367,13 +464,17 @@ window.__ModuleLoader__.load({
367
464
  "pair.qrGenerating": "生成中…",
368
465
  "pair.qrAutoHidden": "配对二维码已自动隐藏",
369
466
  "pair.qrFailed": "二维码生成失败:",
467
+ "pair.codeLabel": "配对码(模拟器可手动输入)",
468
+ "pair.codeCopyDone": "配对码已复制",
469
+ "pair.codeCopyFailed": "配对码复制失败:",
370
470
  "pair.publicCopyDone": "公网地址已复制",
371
471
  "pair.publicCopyFailed": "复制失败:",
372
- "pair.qrHint": "二维码包含{kind}地址和配对 Token,将在 60 秒后自动隐藏。",
472
+ "pair.qrHint": "二维码包含{kind}地址和一次性配对码;配对码 5 分钟后失效,二维码将在 60 秒后自动隐藏。",
373
473
  "advanced.summary": "高级信息",
374
474
  "advanced.protocolVersion": "协议版本",
375
475
  "advanced.serverVersion": "服务器版本",
376
476
  "advanced.tokenPath": "Token 路径",
477
+ "advanced.identityPath": "设备注册表路径",
377
478
  "advanced.bufferMax": "重放缓冲上限",
378
479
  "advanced.frames": " 帧",
379
480
  "push.relayTitle": "离线推送中继",
@@ -394,16 +495,22 @@ window.__ModuleLoader__.load({
394
495
  "push.relayStep.enroll": "自动注册",
395
496
  "push.prefix.ok": "✓ ",
396
497
  "push.prefix.fail": "✗ ",
397
- "devices.title": "已配对设备",
498
+ "devices.title": "设备",
398
499
  "devices.col.name": "设备",
399
500
  "devices.col.appVersion": "App 版本",
400
501
  "devices.col.push": "离线推送",
401
502
  "devices.col.lastSeen": "最近在线",
503
+ "devices.col.fingerprint": "密钥指纹",
504
+ "devices.col.actions": "操作",
505
+ "devices.revoke": "删除",
506
+ "devices.revokeConfirm": "确定删除设备“{name}”吗?该设备会立即断开,再次使用时需要重新配对。",
507
+ "devices.revoked": "设备已删除",
508
+ "devices.revokeFailed": "设备删除失败:",
402
509
  "devices.pushRegistered": "已注册(",
403
510
  "devices.pushNotRegistered": "未注册",
404
511
  "devices.pushEnvProduction": "生产",
405
512
  "devices.pushEnvDevelopment": "开发",
406
- "devices.empty": "还没有设备配对过。在 iPhone 上的 DeepPilot App 中扫码或填入本机地址与 Token 即可配对。",
513
+ "devices.empty": "还没有 v2 设备。请在 iPhone 上扫描一次性配对二维码。",
407
514
  "help.remoteTitle": "远程连接帮助",
408
515
  "help.recommended": "推荐设置流程",
409
516
  "help.step1": "先打开\"DeepPilot 连接\",再打开\"远程连接(Tailscale Funnel)\"。",
@@ -425,9 +532,9 @@ window.__ModuleLoader__.load({
425
532
  "help.faq3": "已有公网地址但手机超时:等待几分钟后重试,同时确认这台电脑未休眠、DSH 正在运行,再重新扫描最新二维码。",
426
533
  "help.faq4": "没有公网地址:检查 Tailscale 授权是否完成,然后点击\"刷新\"或重启 DSH。",
427
534
  "help.securityTitle": "安全说明",
428
- "help.security1": "Funnel 公网地址可从互联网访问,但手机接口仍需要配对 Token。不要分享二维码、Token 或包含它们的截图。",
429
- "help.security2": "怀疑 Token 泄露时,点击上方\"更换\"生成新 Token:旧 Token 立即失效,所有已连接设备会被断开并需要重新配对。",
430
- "help.security3": "插件只通过 Funnel 转发 /phone 和 /phone/health,不会新增 3098 端口。",
535
+ "help.security1": "Funnel 公网地址可从互联网访问,但连接必须使用已配对设备的 P-256 私钥签名;不要分享仍在有效期内的配对二维码。",
536
+ "help.security2": "丢失设备时可在设备列表中单独删除;该设备会立即断开,不影响其他手机。",
537
+ "help.security3": "插件只通过 Funnel 转发 /phone、/phone/pair 和 /phone/health,不会新增 3098 端口。",
431
538
  "help.security4": "局域网连接沿用 DSH 的 3080 端口;仅在可信网络中使用,不建议直接把 3080 暴露到公网。",
432
539
  "help.security5": "关闭远程连接开关会停止 Funnel,但不会影响可用的局域网连接。",
433
540
  "help.funnelDocs": "Tailscale Funnel 官方文档",
@@ -441,13 +548,15 @@ window.__ModuleLoader__.load({
441
548
  "diag.missingReportHook": "useDeepPilotReport hook 缺失",
442
549
  "diag.missingEnabledHook": "useDeepPilotEnabled hook 缺失",
443
550
  "diag.missingRemoteEnabledHook": "useDeepPilotRemoteEnabled hook 缺失",
551
+ "diag.missingRemoteLimitHook": "useDeepPilotRemoteConnectionLimit hook 缺失",
444
552
  "diag.missingRefresh": "refresh 回调缺失",
445
- "diag.missingReveal": "revealPairingToken 回调缺失",
446
- "diag.missingRotate": "rotatePairingToken 回调缺失",
553
+ "diag.missingReveal": "beginPairing 回调缺失",
554
+ "diag.missingRotate": "revokeDevice 回调缺失",
447
555
  "diag.missingTestRelay": "testRelay 回调缺失",
448
556
  "diag.missingTestPush": "testPush 回调缺失",
449
557
  "diag.missingSetEnabled": "setDeepPilotEnabled 回调缺失",
450
558
  "diag.missingSetRemote": "setDeepPilotRemoteEnabled 回调缺失",
559
+ "diag.missingSetRemoteLimit": "setDeepPilotRemoteConnectionLimit 回调缺失",
451
560
  "diag.renderError": "渲染异常: ",
452
561
  "diag.prefix": "diag: ",
453
562
  "clipboard.rejected": "浏览器拒绝了剪贴板写入",
@@ -460,7 +569,7 @@ window.__ModuleLoader__.load({
460
569
  en: {
461
570
  "nav": "DeepPilot",
462
571
  "meta.title": "DeepPilot",
463
- "meta.intro": "Connect your iPhone to DeepSeek Harness (DSH) on this Mac (protocol v1).",
572
+ "meta.intro": "Connect your iPhone to DeepSeek Harness (DSH) on this Mac (protocol v2).",
464
573
  "meta.refresh": "Refresh",
465
574
  "master.title": "DeepPilot connection",
466
575
  "master.loading": "Loading settings…",
@@ -470,6 +579,13 @@ window.__ModuleLoader__.load({
470
579
  "remote.on": "Configured: the embedded Funnel will start and sync state automatically.",
471
580
  "remote.off": "Off: LAN connections only.",
472
581
  "remote.openAuth": "Open authorization page",
582
+ "remote.advancedSettings": "Advanced settings",
583
+ "remote.limitTitle": "Connections per public source",
584
+ "remote.limitDescription": "Default 8; range 1–16. Applying restarts Funnel and briefly reconnects existing remote clients.",
585
+ "remote.limitApply": "Apply",
586
+ "remote.limitApplied": "Connection limit saved; the remote endpoint is reloading.",
587
+ "remote.limitInvalid": "Enter an integer from 1 to 16.",
588
+ "remote.limitFailed": "Failed to save connection limit: ",
473
589
  "phase.disabled": "Disabled",
474
590
  "phase.starting": "Starting",
475
591
  "phase.login_required": "Awaiting Tailscale authorization",
@@ -479,6 +595,9 @@ window.__ModuleLoader__.load({
479
595
  "phase.stopped": "Stopped",
480
596
  "phase.unknown": "Status unknown",
481
597
  "panel.activeConnections": "Active connections",
598
+ "panel.identity": "Device authentication",
599
+ "panel.identityReady": "v2 ready",
600
+ "panel.identityNotReady": "Unavailable",
482
601
  "panel.token": "Pairing token",
483
602
  "panel.tokenReady": "Ready",
484
603
  "panel.tokenNotReady": "Not generated",
@@ -508,13 +627,17 @@ window.__ModuleLoader__.load({
508
627
  "pair.qrGenerating": "Generating…",
509
628
  "pair.qrAutoHidden": "Pairing QR auto-hidden",
510
629
  "pair.qrFailed": "QR generation failed: ",
630
+ "pair.codeLabel": "Pairing code (enter manually in Simulator)",
631
+ "pair.codeCopyDone": "Pairing code copied",
632
+ "pair.codeCopyFailed": "Failed to copy pairing code: ",
511
633
  "pair.publicCopyDone": "Public URL copied",
512
634
  "pair.publicCopyFailed": "Copy failed: ",
513
- "pair.qrHint": "The QR contains a {kind} address and the pairing token; it auto-hides after 60 seconds.",
635
+ "pair.qrHint": "The QR contains a {kind} address and a single-use pairing code. The code expires after 5 minutes; the QR auto-hides after 60 seconds.",
514
636
  "advanced.summary": "Advanced info",
515
637
  "advanced.protocolVersion": "Protocol version",
516
638
  "advanced.serverVersion": "Server version",
517
639
  "advanced.tokenPath": "Token path",
640
+ "advanced.identityPath": "Device registry path",
518
641
  "advanced.bufferMax": "Replay buffer cap",
519
642
  "advanced.frames": " frames",
520
643
  "push.relayTitle": "Offline push relay",
@@ -535,16 +658,22 @@ window.__ModuleLoader__.load({
535
658
  "push.relayStep.enroll": "Auto enrollment",
536
659
  "push.prefix.ok": "✓ ",
537
660
  "push.prefix.fail": "✗ ",
538
- "devices.title": "Paired devices",
661
+ "devices.title": "Devices",
539
662
  "devices.col.name": "Device",
540
663
  "devices.col.appVersion": "App version",
541
664
  "devices.col.push": "Push",
542
665
  "devices.col.lastSeen": "Last seen",
666
+ "devices.col.fingerprint": "Key fingerprint",
667
+ "devices.col.actions": "Actions",
668
+ "devices.revoke": "Delete",
669
+ "devices.revokeConfirm": "Delete “{name}”? It will disconnect immediately and must pair again before reconnecting.",
670
+ "devices.revoked": "Device deleted",
671
+ "devices.revokeFailed": "Failed to delete device: ",
543
672
  "devices.pushRegistered": "Registered (",
544
673
  "devices.pushNotRegistered": "Not registered",
545
674
  "devices.pushEnvProduction": "Production",
546
675
  "devices.pushEnvDevelopment": "Development",
547
- "devices.empty": "No devices paired yet. Scan the QR code or enter the host address + token in DeepPilot on iPhone to pair.",
676
+ "devices.empty": "No v2 devices yet. Scan a single-use pairing QR code in DeepPilot on iPhone.",
548
677
  "help.remoteTitle": "Remote connection help",
549
678
  "help.recommended": "Recommended setup",
550
679
  "help.step1": "Turn on \"DeepPilot connection\" first, then \"Remote connection (Tailscale Funnel)\".",
@@ -566,9 +695,9 @@ window.__ModuleLoader__.load({
566
695
  "help.faq3": "Public URL exists but the phone times out: wait a few minutes, confirm the Mac is awake and DSH is running, then re-scan the latest QR code.",
567
696
  "help.faq4": "No public URL: check that Tailscale authorization completed, then click \"Refresh\" or restart DSH.",
568
697
  "help.securityTitle": "Security notes",
569
- "help.security1": "A Funnel public URL is reachable from the public Internet, but the phone interface still needs the pairing token. Do not share the QR code, the token, or any screenshot that contains them.",
570
- "help.security2": "If you suspect the token leaked, click \"Rotate\" above to mint a new one. The old token stops working immediately and every paired device must re-pair.",
571
- "help.security3": "The plugin only forwards /phone and /phone/health through Funnel; no new port 3098 is opened.",
698
+ "help.security1": "A Funnel URL is public, but every connection requires a signature from a paired device P-256 private key. Do not share a pairing QR while it is valid.",
699
+ "help.security2": "If a device is lost, delete only that device from the list. It disconnects immediately; other phones stay paired.",
700
+ "help.security3": "The plugin only forwards /phone, /phone/pair, and /phone/health through Funnel; no new port 3098 is opened.",
572
701
  "help.security4": "LAN connections reuse DSH's existing 3080 port. Use them only on trusted networks; do not expose 3080 directly to the public Internet.",
573
702
  "help.security5": "Disabling the remote switch stops Funnel but does not affect any LAN connection you already have.",
574
703
  "help.funnelDocs": "Tailscale Funnel docs",
@@ -582,13 +711,15 @@ window.__ModuleLoader__.load({
582
711
  "diag.missingReportHook": "useDeepPilotReport hook missing",
583
712
  "diag.missingEnabledHook": "useDeepPilotEnabled hook missing",
584
713
  "diag.missingRemoteEnabledHook": "useDeepPilotRemoteEnabled hook missing",
714
+ "diag.missingRemoteLimitHook": "useDeepPilotRemoteConnectionLimit hook missing",
585
715
  "diag.missingRefresh": "refresh callback missing",
586
- "diag.missingReveal": "revealPairingToken callback missing",
587
- "diag.missingRotate": "rotatePairingToken callback missing",
716
+ "diag.missingReveal": "beginPairing callback missing",
717
+ "diag.missingRotate": "revokeDevice callback missing",
588
718
  "diag.missingTestRelay": "testRelay callback missing",
589
719
  "diag.missingTestPush": "testPush callback missing",
590
720
  "diag.missingSetEnabled": "setDeepPilotEnabled callback missing",
591
721
  "diag.missingSetRemote": "setDeepPilotRemoteEnabled callback missing",
722
+ "diag.missingSetRemoteLimit": "setDeepPilotRemoteConnectionLimit callback missing",
592
723
  "diag.renderError": "Render error: ",
593
724
  "diag.prefix": "diag: ",
594
725
  "clipboard.rejected": "Clipboard write rejected by the browser",
@@ -694,6 +825,10 @@ window.__ModuleLoader__.load({
694
825
  ".pbb-actionDanger:not(:disabled){color:#fff;background:var(--dsw-alias-label-error);border-color:var(--dsw-alias-label-error)}",
695
826
  ".pbb-qrPanel{display:flex;flex-direction:column;align-items:center;gap:9px;padding:12px 0 4px}",
696
827
  ".pbb-qrImage{width:240px;height:240px;max-width:100%;background:#fff;border-radius:10px;padding:8px;box-sizing:border-box}",
828
+ ".pbb-pairCodeBlock{width:min(100%,420px);display:flex;flex-direction:column;gap:5px}",
829
+ ".pbb-pairCodeLabel{font-size:11px;color:var(--dsw-alias-label-tertiary);text-align:center}",
830
+ ".pbb-pairCodeRow{display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap}",
831
+ ".pbb-pairCode{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;line-height:1.4;letter-spacing:.04em;color:var(--dsw-alias-label-primary);word-break:break-all;text-align:center;user-select:all}",
697
832
  ".pbb-qrHint{max-width:420px;text-align:center;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-tertiary);margin:0}",
698
833
  ".pbb-switchRow{padding:12px 0;display:flex;align-items:center;gap:12px}",
699
834
  ".pbb-switchRow+.pbb-field{border-top:1px solid var(--dsw-alias-border-l2)}",
@@ -711,6 +846,12 @@ window.__ModuleLoader__.load({
711
846
  ".pbb-switchOn{background:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}",
712
847
  ".pbb-switchOn::after{transform:translateX(16px);background:#fff}",
713
848
  ".pbb-switch:disabled{opacity:.5;cursor:default}",
849
+ ".pbb-limitRow{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 0;display:flex;align-items:center;gap:12px}",
850
+ ".pbb-limitNested{border-top:0;padding:0}",
851
+ ".pbb-limitControl{display:flex;align-items:center;gap:8px;flex:none}",
852
+ ".pbb-numberInput{width:72px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;padding:5px 8px}",
853
+ ".pbb-numberInput:focus{outline:2px solid var(--dsw-alias-state-success-primary,#22a06b);outline-offset:1px}",
854
+ ".pbb-numberInput:disabled{opacity:.5}",
714
855
  ".pbb-help{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 0}",
715
856
  ".pbb-help summary{cursor:pointer;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;user-select:none}",
716
857
  ".pbb-help summary:hover{color:var(--dsw-alias-label-secondary)}",
@@ -3139,11 +3280,7 @@ window.__ModuleLoader__.load({
3139
3280
  return SvgRenderer.render(data, opts);
3140
3281
  });
3141
3282
  })))(), 1);
3142
- /**
3143
- * Protocol-v1 compatibility identifier used by the TestFlight build currently
3144
- * under review. This is wire data, not the plugin or product display name.
3145
- */
3146
- const PAIRING_QR_TYPE = "dsh-pocket-pairing";
3283
+ const PAIRING_QR_TYPE = "deeppilot-pairing";
3147
3284
  function isLoopbackHostname(hostname) {
3148
3285
  const normalized = hostname.toLowerCase();
3149
3286
  return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "[::1]" || normalized === "::1" || normalized.startsWith("127.");
@@ -3173,8 +3310,8 @@ window.__ModuleLoader__.load({
3173
3310
  kind: "lan"
3174
3311
  };
3175
3312
  }
3176
- /** Encode an out-of-band pairing payload without putting the token in a URL. */
3177
- function encodePairingQRPayload(host, token) {
3313
+ /** Encode a short-lived, single-use pairing grant without URL credentials. */
3314
+ function encodePairingQRPayload(host, grant) {
3178
3315
  const normalizedHost = host.trim();
3179
3316
  const parsed = new URL(normalizedHost);
3180
3317
  if (![
@@ -3183,15 +3320,21 @@ window.__ModuleLoader__.load({
3183
3320
  "ws:",
3184
3321
  "wss:"
3185
3322
  ].includes(parsed.protocol) || parsed.hostname === "" || parsed.username !== "" || parsed.password !== "") throw new TypeError("pairing QR requires a valid HTTP(S)/WS(S) host");
3186
- if (token.trim().length < 32) throw new TypeError("pairing token is invalid");
3323
+ if (grant.code.trim().length < 32 || !Number.isInteger(grant.expiresAt) || grant.expiresAt <= Date.now()) throw new TypeError("pairing grant is invalid or expired");
3324
+ if (!grant.audience.startsWith("deeppilot:")) throw new TypeError("pairing audience is invalid");
3187
3325
  const payload = {
3188
- v: 1,
3326
+ v: 2,
3189
3327
  type: PAIRING_QR_TYPE,
3190
3328
  host: normalizedHost,
3191
- token: token.trim()
3329
+ code: grant.code.trim(),
3330
+ expiresAt: grant.expiresAt,
3331
+ audience: grant.audience
3192
3332
  };
3193
3333
  return JSON.stringify(payload);
3194
3334
  }
3335
+ function normalizeFunnelConnectionLimit(value) {
3336
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 16 ? value : 8;
3337
+ }
3195
3338
  //#endregion
3196
3339
  //#region src/client/settings-page.ts
3197
3340
  async function writeClipboard(t, value) {
@@ -3245,12 +3388,11 @@ window.__ModuleLoader__.load({
3245
3388
  };
3246
3389
  /** Slot component: hooks come from the slot renderer, named use<Key>. */
3247
3390
  function DeepPilotSettingsPage(props) {
3248
- const [revealedToken, setRevealedToken] = (0, react.useState)(null);
3249
- const [tokenBusy, setTokenBusy] = (0, react.useState)(false);
3250
- const [tokenMessage, setTokenMessage] = (0, react.useState)("");
3251
- const [rotateArmed, setRotateArmed] = (0, react.useState)(false);
3252
- const [remoteMessage, setRemoteMessage] = (0, react.useState)("");
3391
+ const [addressMessage, setAddressMessage] = (0, react.useState)("");
3392
+ const [remoteLimitDraft, setRemoteLimitDraft] = (0, react.useState)(String(8));
3393
+ const [remoteLimitMessage, setRemoteLimitMessage] = (0, react.useState)("");
3253
3394
  const [qrDataURL, setQRDataURL] = (0, react.useState)(null);
3395
+ const [pairingGrant, setPairingGrant] = (0, react.useState)(null);
3254
3396
  const [qrBusy, setQRBusy] = (0, react.useState)(false);
3255
3397
  const [qrMessage, setQRMessage] = (0, react.useState)("");
3256
3398
  const [relayTestBusy, setRelayTestBusy] = (0, react.useState)(false);
@@ -3259,6 +3401,8 @@ window.__ModuleLoader__.load({
3259
3401
  const [pushTestBusy, setPushTestBusy] = (0, react.useState)(false);
3260
3402
  const [pushTestResult, setPushTestResult] = (0, react.useState)(null);
3261
3403
  const [pushTestError, setPushTestError] = (0, react.useState)("");
3404
+ const [deviceBusy, setDeviceBusy] = (0, react.useState)(null);
3405
+ const [deviceMessage, setDeviceMessage] = (0, react.useState)("");
3262
3406
  (0, react.useEffect)(() => {
3263
3407
  if (typeof props.refresh !== "function") return;
3264
3408
  props.refresh();
@@ -3298,32 +3442,20 @@ window.__ModuleLoader__.load({
3298
3442
  });
3299
3443
  };
3300
3444
  (0, react.useEffect)(() => {
3301
- if (revealedToken === null) return;
3302
- const timer = globalThis.setTimeout(() => {
3303
- setRevealedToken(null);
3304
- setTokenMessage(translateWith(props.t, "panel.tokenAutoHidden"));
3305
- }, 3e4);
3306
- return () => globalThis.clearTimeout(timer);
3307
- }, [revealedToken]);
3308
- (0, react.useEffect)(() => {
3309
- if (!tokenMessage) return;
3310
- const timer = globalThis.setTimeout(() => setTokenMessage(""), 4e3);
3445
+ if (!addressMessage) return;
3446
+ const timer = globalThis.setTimeout(() => setAddressMessage(""), 2500);
3311
3447
  return () => globalThis.clearTimeout(timer);
3312
- }, [tokenMessage]);
3448
+ }, [addressMessage]);
3313
3449
  (0, react.useEffect)(() => {
3314
- if (!rotateArmed) return;
3315
- const timer = globalThis.setTimeout(() => setRotateArmed(false), 5e3);
3450
+ if (!remoteLimitMessage) return;
3451
+ const timer = globalThis.setTimeout(() => setRemoteLimitMessage(""), 4e3);
3316
3452
  return () => globalThis.clearTimeout(timer);
3317
- }, [rotateArmed]);
3318
- (0, react.useEffect)(() => {
3319
- if (!remoteMessage) return;
3320
- const timer = globalThis.setTimeout(() => setRemoteMessage(""), 2500);
3321
- return () => globalThis.clearTimeout(timer);
3322
- }, [remoteMessage]);
3453
+ }, [remoteLimitMessage]);
3323
3454
  (0, react.useEffect)(() => {
3324
3455
  if (qrDataURL === null) return;
3325
3456
  const timer = globalThis.setTimeout(() => {
3326
3457
  setQRDataURL(null);
3458
+ setPairingGrant(null);
3327
3459
  setQRMessage(translateWith(props.t, "pair.qrAutoHidden"));
3328
3460
  }, 6e4);
3329
3461
  return () => globalThis.clearTimeout(timer);
@@ -3339,6 +3471,8 @@ window.__ModuleLoader__.load({
3339
3471
  let switchReady = false;
3340
3472
  let remoteEnabled = false;
3341
3473
  let remoteSwitchReady = false;
3474
+ let remoteConnectionLimit = 8;
3475
+ let remoteConnectionLimitReady = false;
3342
3476
  let failed = false;
3343
3477
  try {
3344
3478
  if (typeof props.useDeepPilotReport !== "function") {
@@ -3359,8 +3493,8 @@ window.__ModuleLoader__.load({
3359
3493
  if (state.status === "unavailable") diag.push(translateWith(props.t, "diag.settingsUnavailable"));
3360
3494
  } else diag.push(translateWith(props.t, "diag.missingEnabledHook"));
3361
3495
  if (typeof props.refresh !== "function") diag.push(translateWith(props.t, "diag.missingRefresh"));
3362
- if (typeof props.revealPairingToken !== "function") diag.push(translateWith(props.t, "diag.missingReveal"));
3363
- if (typeof props.rotatePairingToken !== "function") diag.push(translateWith(props.t, "diag.missingRotate"));
3496
+ if (typeof props.beginPairing !== "function") diag.push(translateWith(props.t, "diag.missingReveal"));
3497
+ if (typeof props.revokeDevice !== "function") diag.push(translateWith(props.t, "diag.missingRotate"));
3364
3498
  if (typeof props.testRelay !== "function") diag.push(translateWith(props.t, "diag.missingTestRelay"));
3365
3499
  if (typeof props.testPush !== "function") diag.push(translateWith(props.t, "diag.missingTestPush"));
3366
3500
  if (typeof props.setDeepPilotEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetEnabled"));
@@ -3370,78 +3504,80 @@ window.__ModuleLoader__.load({
3370
3504
  remoteSwitchReady = state.status === "ready";
3371
3505
  } else diag.push(translateWith(props.t, "diag.missingRemoteEnabledHook"));
3372
3506
  if (typeof props.setDeepPilotRemoteEnabled !== "function") diag.push(translateWith(props.t, "diag.missingSetRemote"));
3507
+ if (typeof props.useDeepPilotRemoteConnectionLimit === "function") {
3508
+ const state = props.useDeepPilotRemoteConnectionLimit((s) => s);
3509
+ remoteConnectionLimit = state.value;
3510
+ remoteConnectionLimitReady = state.status === "ready";
3511
+ } else diag.push(translateWith(props.t, "diag.missingRemoteLimitHook"));
3512
+ if (typeof props.setDeepPilotRemoteConnectionLimit !== "function") diag.push(translateWith(props.t, "diag.missingSetRemoteLimit"));
3373
3513
  } catch (error) {
3374
3514
  diag.push(translateWith(props.t, "diag.renderError") + (error instanceof Error ? error.message : String(error)));
3375
3515
  failed = true;
3376
3516
  }
3377
- const pairingTarget = report === null ? null : selectPairingTarget(report.remote, report.lanAddresses, typeof window === "undefined" ? void 0 : window.location.origin);
3378
3517
  (0, react.useEffect)(() => {
3379
- setQRDataURL(null);
3380
- }, [pairingTarget?.host]);
3381
- const toggleToken = () => {
3382
- if (revealedToken !== null) {
3383
- setRevealedToken(null);
3384
- setTokenMessage("");
3385
- return;
3386
- }
3387
- if (typeof props.revealPairingToken !== "function") return;
3388
- setTokenBusy(true);
3389
- setTokenMessage("");
3390
- props.revealPairingToken().then((token) => {
3391
- setRevealedToken(token);
3392
- }, (error) => {
3393
- setTokenMessage(translateWith(props.t, "panel.tokenRevealFailed") + (error instanceof Error ? error.message : String(error)));
3394
- }).finally(() => setTokenBusy(false));
3395
- };
3396
- const copyToken = () => {
3397
- if (typeof props.revealPairingToken !== "function") return;
3398
- setTokenBusy(true);
3399
- setTokenMessage("");
3400
- (revealedToken !== null ? Promise.resolve(revealedToken) : props.revealPairingToken()).then((value) => writeClipboard(props.t, value)).then(() => setTokenMessage(translateWith(props.t, "panel.tokenCopied")), (error) => {
3401
- setTokenMessage(translateWith(props.t, "pair.publicCopyFailed") + (error instanceof Error ? error.message : String(error)));
3402
- }).finally(() => setTokenBusy(false));
3403
- };
3404
- const rotateToken = () => {
3405
- if (typeof props.rotatePairingToken !== "function") return;
3406
- if (!rotateArmed) {
3407
- setRotateArmed(true);
3408
- setRevealedToken(null);
3409
- setQRDataURL(null);
3410
- setTokenMessage(translateWith(props.t, "panel.tokenRotateWarning"));
3518
+ setRemoteLimitDraft(String(remoteConnectionLimit));
3519
+ }, [remoteConnectionLimit]);
3520
+ const parsedRemoteLimit = Number(remoteLimitDraft);
3521
+ const remoteLimitValid = Number.isInteger(parsedRemoteLimit) && parsedRemoteLimit >= 1 && parsedRemoteLimit <= 16;
3522
+ const applyRemoteLimit = () => {
3523
+ if (!remoteLimitValid || typeof props.setDeepPilotRemoteConnectionLimit !== "function") {
3524
+ setRemoteLimitMessage(translateWith(props.t, "remote.limitInvalid"));
3411
3525
  return;
3412
3526
  }
3413
- setRotateArmed(false);
3414
- setTokenBusy(true);
3415
- setTokenMessage("");
3416
- props.rotatePairingToken().then((token) => {
3417
- setRevealedToken(token);
3418
- setTokenMessage(translateWith(props.t, "panel.tokenRotated"));
3527
+ setRemoteLimitMessage("");
3528
+ props.setDeepPilotRemoteConnectionLimit(parsedRemoteLimit).then(() => {
3529
+ setRemoteLimitMessage(translateWith(props.t, "remote.limitApplied"));
3419
3530
  }, (error) => {
3420
- setTokenMessage(translateWith(props.t, "panel.tokenRotateFailed") + (error instanceof Error ? error.message : String(error)));
3421
- }).finally(() => setTokenBusy(false));
3531
+ setRemoteLimitMessage(translateWith(props.t, "remote.limitFailed") + (error instanceof Error ? error.message : String(error)));
3532
+ });
3422
3533
  };
3534
+ const pairingTarget = report === null ? null : selectPairingTarget(report.remote, report.lanAddresses, typeof window === "undefined" ? void 0 : window.location.origin);
3535
+ (0, react.useEffect)(() => {
3536
+ setQRDataURL(null);
3537
+ setPairingGrant(null);
3538
+ }, [pairingTarget?.host]);
3423
3539
  const copyRemoteURL = (url) => {
3424
- setRemoteMessage("");
3540
+ setAddressMessage("");
3425
3541
  writeClipboard(props.t, url).then(() => {
3426
- setRemoteMessage(translateWith(props.t, "pair.publicCopyDone"));
3542
+ setAddressMessage(translateWith(props.t, "pair.publicCopyDone"));
3427
3543
  }, (error) => {
3428
- setRemoteMessage(translateWith(props.t, "pair.publicCopyFailed") + (error instanceof Error ? error.message : String(error)));
3544
+ setAddressMessage(translateWith(props.t, "pair.publicCopyFailed") + (error instanceof Error ? error.message : String(error)));
3429
3545
  });
3430
3546
  };
3431
3547
  const showPairingQR = () => {
3432
- if (typeof props.revealPairingToken !== "function" || pairingTarget === null) return;
3548
+ if (typeof props.beginPairing !== "function" || pairingTarget === null) return;
3433
3549
  setQRBusy(true);
3434
3550
  setQRMessage("");
3435
3551
  setQRDataURL(null);
3436
- props.revealPairingToken().then((pairingToken) => import_browser.toString(encodePairingQRPayload(pairingTarget.host, pairingToken), {
3437
- type: "svg",
3438
- errorCorrectionLevel: "M",
3439
- margin: 2,
3440
- width: 512
3441
- })).then((svg) => setQRDataURL("data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg)), (error) => {
3552
+ setPairingGrant(null);
3553
+ props.beginPairing().then(async (grant) => ({
3554
+ grant,
3555
+ svg: await import_browser.toString(encodePairingQRPayload(pairingTarget.host, grant), {
3556
+ type: "svg",
3557
+ errorCorrectionLevel: "M",
3558
+ margin: 2,
3559
+ width: 512
3560
+ })
3561
+ })).then(({ grant, svg }) => {
3562
+ setPairingGrant(grant);
3563
+ setQRDataURL("data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg));
3564
+ }, (error) => {
3442
3565
  setQRMessage(translateWith(props.t, "pair.qrFailed") + (error instanceof Error ? error.message : String(error)));
3443
3566
  }).finally(() => setQRBusy(false));
3444
3567
  };
3568
+ const hidePairingQR = () => {
3569
+ setQRDataURL(null);
3570
+ setPairingGrant(null);
3571
+ };
3572
+ const copyPairingCode = () => {
3573
+ if (pairingGrant === null) return;
3574
+ setQRMessage("");
3575
+ writeClipboard(props.t, pairingGrant.code).then(() => {
3576
+ setQRMessage(translateWith(props.t, "pair.codeCopyDone"));
3577
+ }, (error) => {
3578
+ setQRMessage(translateWith(props.t, "pair.codeCopyFailed") + (error instanceof Error ? error.message : String(error)));
3579
+ });
3580
+ };
3445
3581
  const primaryRows = [];
3446
3582
  const advancedRows = [];
3447
3583
  if (report !== null) {
@@ -3450,23 +3586,8 @@ window.__ModuleLoader__.load({
3450
3586
  key: "conn"
3451
3587
  }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "panel.activeConnections")), (0, react.createElement)("span", { className: "pbb-value" }, String(report.activeConnections)))), (0, react.createElement)("div", {
3452
3588
  className: "pbb-field",
3453
- key: "token"
3454
- }, (0, react.createElement)("div", { className: "pbb-row pbb-tokenRow" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "panel.token")), (0, react.createElement)("code", { className: "pbb-token " + (report.tokenReady ? "pbb-ok" : "pbb-bad") }, report.tokenReady ? revealedToken ?? "••••••••••••" : translateWith(props.t, "panel.tokenNotReady")), report.tokenReady ? (0, react.createElement)("span", { className: "pbb-tokenActions" }, (0, react.createElement)("button", {
3455
- type: "button",
3456
- className: "pbb-action",
3457
- disabled: tokenBusy,
3458
- onClick: toggleToken
3459
- }, tokenBusy ? translateWith(props.t, "panel.tokenAction.showing") : revealedToken === null ? translateWith(props.t, "panel.tokenAction.show") : translateWith(props.t, "panel.tokenAction.hide")), (0, react.createElement)("button", {
3460
- type: "button",
3461
- className: "pbb-action",
3462
- disabled: tokenBusy,
3463
- onClick: copyToken
3464
- }, translateWith(props.t, "panel.tokenAction.copy")), (0, react.createElement)("button", {
3465
- type: "button",
3466
- className: "pbb-action" + (rotateArmed ? " pbb-actionDanger" : ""),
3467
- disabled: tokenBusy,
3468
- onClick: rotateToken
3469
- }, rotateArmed ? translateWith(props.t, "panel.tokenAction.rotateConfirm") : tokenBusy ? translateWith(props.t, "panel.tokenAction.rotating") : translateWith(props.t, "panel.tokenAction.rotate"))) : null), tokenMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, tokenMessage) : null));
3589
+ key: "identity"
3590
+ }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "panel.identity")), (0, react.createElement)("code", { className: "pbb-token " + (report.pairingReady ? "pbb-ok" : "pbb-bad") }, report.pairingReady ? translateWith(props.t, "panel.identityReady") : translateWith(props.t, "panel.identityNotReady")))));
3470
3591
  advancedRows.push((0, react.createElement)("div", {
3471
3592
  className: "pbb-field",
3472
3593
  key: "proto"
@@ -3476,12 +3597,29 @@ window.__ModuleLoader__.load({
3476
3597
  }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.serverVersion")), (0, react.createElement)("span", { className: "pbb-value" }, report.serverVersion))), (0, react.createElement)("div", {
3477
3598
  className: "pbb-field",
3478
3599
  key: "path"
3479
- }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.tokenPath")), (0, react.createElement)("span", { className: "pbb-value" }, report.tokenPath))), (0, react.createElement)("div", {
3600
+ }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.identityPath")), (0, react.createElement)("span", { className: "pbb-value" }, report.identityPath))), (0, react.createElement)("div", {
3480
3601
  className: "pbb-field",
3481
3602
  key: "buffer"
3482
3603
  }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "advanced.bufferMax")), (0, react.createElement)("span", { className: "pbb-value" }, String(report.historyBufferMax) + translateWith(props.t, "advanced.frames")))));
3483
3604
  }
3484
- const deviceTable = report !== null && report.devices.length > 0 ? (0, react.createElement)("table", { className: "pbb-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, (0, react.createElement)("th", null, translateWith(props.t, "devices.col.name")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.appVersion")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.push")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.lastSeen")))), (0, react.createElement)("tbody", null, report.devices.map((d) => (0, react.createElement)("tr", { key: d.deviceId }, (0, react.createElement)("td", null, d.deviceName), (0, react.createElement)("td", null, d.appVersion), (0, react.createElement)("td", null, d.apns ? translateWith(props.t, "devices.pushRegistered") + (d.apns.environment === "production" ? translateWith(props.t, "devices.pushEnvProduction") : translateWith(props.t, "devices.pushEnvDevelopment")) + ")" : translateWith(props.t, "devices.pushNotRegistered")), (0, react.createElement)("td", null, new Date(d.lastSeenTs).toLocaleString()))))) : (0, react.createElement)("p", { className: "pbb-empty" }, translateWith(props.t, "devices.empty"));
3605
+ const revokeDevice = (deviceId, name) => {
3606
+ if (typeof props.revokeDevice !== "function") return;
3607
+ if (typeof window !== "undefined" && !window.confirm(translateWith(props.t, "devices.revokeConfirm", { name }))) return;
3608
+ setDeviceBusy(deviceId);
3609
+ setDeviceMessage("");
3610
+ props.revokeDevice(deviceId).then(() => {
3611
+ setDeviceMessage(translateWith(props.t, "devices.revoked"));
3612
+ }, (error) => {
3613
+ setDeviceMessage(translateWith(props.t, "devices.revokeFailed") + (error instanceof Error ? error.message : String(error)));
3614
+ }).finally(() => setDeviceBusy(null));
3615
+ };
3616
+ const visibleDevices = report?.devices.filter((device) => device.revokedAt === void 0) ?? [];
3617
+ const deviceTable = visibleDevices.length > 0 ? (0, react.createElement)("table", { className: "pbb-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, (0, react.createElement)("th", null, translateWith(props.t, "devices.col.name")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.fingerprint")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.lastSeen")), (0, react.createElement)("th", null, translateWith(props.t, "devices.col.actions")))), (0, react.createElement)("tbody", null, visibleDevices.map((d) => (0, react.createElement)("tr", { key: d.deviceId }, (0, react.createElement)("td", null, d.deviceName, (0, react.createElement)("div", { className: "pbb-diag" }, d.appVersion)), (0, react.createElement)("td", null, (0, react.createElement)("code", { className: "pbb-token" }, d.fingerprint.slice(0, 12))), (0, react.createElement)("td", null, new Date(d.lastSeenTs).toLocaleString()), (0, react.createElement)("td", null, (0, react.createElement)("button", {
3618
+ type: "button",
3619
+ className: "pbb-action pbb-actionDanger",
3620
+ disabled: deviceBusy === d.deviceId,
3621
+ onClick: () => revokeDevice(d.deviceId, d.deviceName)
3622
+ }, translateWith(props.t, "devices.revoke"))))))) : (0, react.createElement)("p", { className: "pbb-empty" }, translateWith(props.t, "devices.empty"));
3485
3623
  const switchTitle = translateWith(props.t, "master.title");
3486
3624
  const switchDesc = switchReady ? enabled ? translateWith(props.t, "master.on") : translateWith(props.t, "master.off") : translateWith(props.t, "master.loading");
3487
3625
  return (0, react.createElement)("div", { className: "pbb-section" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("h2", { className: "pbb-title" }, "DeepPilot"), (0, react.createElement)("div", { style: { flex: "1" } }), (0, react.createElement)("button", {
@@ -3509,7 +3647,7 @@ window.__ModuleLoader__.load({
3509
3647
  href: report.remote.authURL,
3510
3648
  target: "_blank",
3511
3649
  rel: "noreferrer"
3512
- }, translateWith(props.t, "remote.openAuth"))) : null, report !== null && report.remote.message && (report.remote.phase === "error" || report.remote.phase === "unavailable") ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, report.remote.message) : null, remoteMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, remoteMessage) : null), (0, react.createElement)("button", {
3650
+ }, translateWith(props.t, "remote.openAuth"))) : null, report !== null && report.remote.message && (report.remote.phase === "error" || report.remote.phase === "unavailable") ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, report.remote.message) : null), (0, react.createElement)("button", {
3513
3651
  type: "button",
3514
3652
  role: "switch",
3515
3653
  "aria-checked": remoteEnabled,
@@ -3519,7 +3657,33 @@ window.__ModuleLoader__.load({
3519
3657
  onClick: () => {
3520
3658
  if (typeof props.setDeepPilotRemoteEnabled === "function") props.setDeepPilotRemoteEnabled(!remoteEnabled);
3521
3659
  }
3522
- })), (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "help.remoteTitle")), (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.recommended")), (0, react.createElement)("ol", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.step1")), (0, react.createElement)("li", null, translateWith(props.t, "help.step2")), (0, react.createElement)("li", null, translateWith(props.t, "help.step3")), (0, react.createElement)("li", null, translateWith(props.t, "help.step4"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.funnelHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.httpsTitle")), (0, react.createElement)("ol", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep1")), (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep2")), (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep3"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.httpsHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.allowTitle")), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.allowBody")), (0, react.createElement)("code", { className: "pbb-helpCode" }, "\"nodeAttrs\": [\n {\n \"target\": [\"autogroup:member\"],\n \"attr\": [\"funnel\"],\n },\n],"), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.allowHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.faqTitle")), (0, react.createElement)("ul", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.faq1")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq2")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq3")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq4")))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.securityTitle")), (0, react.createElement)("ul", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.security1")), (0, react.createElement)("li", null, translateWith(props.t, "help.security2")), (0, react.createElement)("li", null, translateWith(props.t, "help.security3")), (0, react.createElement)("li", null, translateWith(props.t, "help.security4")), (0, react.createElement)("li", null, translateWith(props.t, "help.security5"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.docsPrefix"), (0, react.createElement)("a", {
3660
+ })), (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "remote.advancedSettings")), (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-limitRow pbb-limitNested" }, (0, react.createElement)("div", { className: "pbb-switchText" }, (0, react.createElement)("label", {
3661
+ className: "pbb-switchTitle",
3662
+ htmlFor: "deeppilot-funnel-source-limit"
3663
+ }, translateWith(props.t, "remote.limitTitle")), (0, react.createElement)("span", { className: "pbb-switchDesc" }, translateWith(props.t, "remote.limitDescription")), !remoteLimitValid ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "remote.limitInvalid")) : remoteLimitMessage ? (0, react.createElement)("p", { className: "pbb-diag" + (remoteLimitMessage.startsWith(translateWith(props.t, "remote.limitFailed")) ? " pbb-diagBad" : "") }, remoteLimitMessage) : null), (0, react.createElement)("div", { className: "pbb-limitControl" }, (0, react.createElement)("input", {
3664
+ id: "deeppilot-funnel-source-limit",
3665
+ className: "pbb-numberInput",
3666
+ type: "number",
3667
+ min: 1,
3668
+ max: 16,
3669
+ step: 1,
3670
+ inputMode: "numeric",
3671
+ value: remoteLimitDraft,
3672
+ disabled: !remoteConnectionLimitReady,
3673
+ "aria-label": translateWith(props.t, "remote.limitTitle"),
3674
+ onChange: (event) => setRemoteLimitDraft(event.currentTarget.value),
3675
+ onKeyDown: (event) => {
3676
+ if (event.key === "Enter") {
3677
+ event.preventDefault();
3678
+ applyRemoteLimit();
3679
+ }
3680
+ }
3681
+ }), (0, react.createElement)("button", {
3682
+ type: "button",
3683
+ className: "pbb-action",
3684
+ disabled: !remoteConnectionLimitReady || !remoteLimitValid || parsedRemoteLimit === remoteConnectionLimit,
3685
+ onClick: applyRemoteLimit
3686
+ }, translateWith(props.t, "remote.limitApply")))))), (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "help.remoteTitle")), (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.recommended")), (0, react.createElement)("ol", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.step1")), (0, react.createElement)("li", null, translateWith(props.t, "help.step2")), (0, react.createElement)("li", null, translateWith(props.t, "help.step3")), (0, react.createElement)("li", null, translateWith(props.t, "help.step4"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.funnelHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.httpsTitle")), (0, react.createElement)("ol", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep1")), (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep2")), (0, react.createElement)("li", null, translateWith(props.t, "help.httpsStep3"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.httpsHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.allowTitle")), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.allowBody")), (0, react.createElement)("code", { className: "pbb-helpCode" }, "\"nodeAttrs\": [\n {\n \"target\": [\"autogroup:member\"],\n \"attr\": [\"funnel\"],\n },\n],"), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.allowHint"))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.faqTitle")), (0, react.createElement)("ul", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.faq1")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq2")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq3")), (0, react.createElement)("li", null, translateWith(props.t, "help.faq4")))), (0, react.createElement)("div", { className: "pbb-helpSection" }, (0, react.createElement)("div", { className: "pbb-helpHeading" }, translateWith(props.t, "help.securityTitle")), (0, react.createElement)("ul", { className: "pbb-helpList" }, (0, react.createElement)("li", null, translateWith(props.t, "help.security1")), (0, react.createElement)("li", null, translateWith(props.t, "help.security2")), (0, react.createElement)("li", null, translateWith(props.t, "help.security3")), (0, react.createElement)("li", null, translateWith(props.t, "help.security4")), (0, react.createElement)("li", null, translateWith(props.t, "help.security5"))), (0, react.createElement)("p", { className: "pbb-helpText" }, translateWith(props.t, "help.docsPrefix"), (0, react.createElement)("a", {
3523
3687
  className: "pbb-helpLink",
3524
3688
  href: "https://tailscale.com/docs/features/tailscale-funnel",
3525
3689
  target: "_blank",
@@ -3527,20 +3691,24 @@ window.__ModuleLoader__.load({
3527
3691
  }, translateWith(props.t, "help.funnelDocs"))))))), report === null ? null : (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "pair.qrPanelTitle")), pairingTarget === null ? (0, react.createElement)("span", { className: "pbb-value pbb-bad" }, translateWith(props.t, "pair.noAddress")) : (0, react.createElement)("span", { className: "pbb-tokenActions" }, (0, react.createElement)("span", { className: "pbb-badge" }, pairingTarget.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan")), (0, react.createElement)("button", {
3528
3692
  type: "button",
3529
3693
  className: "pbb-action",
3530
- disabled: qrBusy || !report.tokenReady,
3694
+ disabled: qrBusy || !report.pairingReady,
3531
3695
  onClick: () => {
3532
3696
  if (qrDataURL === null) showPairingQR();
3533
- else setQRDataURL(null);
3697
+ else hidePairingQR();
3534
3698
  }
3535
- }, qrBusy ? translateWith(props.t, "pair.qrGenerating") : qrDataURL === null ? translateWith(props.t, "pair.qrShow") : translateWith(props.t, "pair.qrHide")))), pairingTarget === null ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "pair.noAddressHelp")) : null, qrMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, qrMessage) : null, pairingTarget === null || qrDataURL === null ? null : (0, react.createElement)("div", { className: "pbb-qrPanel" }, (0, react.createElement)("img", {
3699
+ }, qrBusy ? translateWith(props.t, "pair.qrGenerating") : qrDataURL === null ? translateWith(props.t, "pair.qrShow") : translateWith(props.t, "pair.qrHide")))), pairingTarget === null ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, translateWith(props.t, "pair.noAddressHelp")) : null, qrMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, qrMessage) : null, pairingTarget === null || qrDataURL === null || pairingGrant === null ? null : (0, react.createElement)("div", { className: "pbb-qrPanel" }, (0, react.createElement)("img", {
3536
3700
  className: "pbb-qrImage",
3537
3701
  src: qrDataURL,
3538
3702
  alt: translateWith(props.t, "pair.qrAlt")
3539
- }), (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("code", { className: "pbb-token" }, pairingTarget.host), (0, react.createElement)("button", {
3703
+ }), (0, react.createElement)("div", { className: "pbb-pairCodeBlock" }, (0, react.createElement)("span", { className: "pbb-pairCodeLabel" }, translateWith(props.t, "pair.codeLabel")), (0, react.createElement)("div", { className: "pbb-pairCodeRow" }, (0, react.createElement)("code", { className: "pbb-pairCode" }, pairingGrant.code), (0, react.createElement)("button", {
3704
+ type: "button",
3705
+ className: "pbb-action",
3706
+ onClick: copyPairingCode
3707
+ }, translateWith(props.t, "panel.tokenAction.copy")))), (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("code", { className: "pbb-token" }, pairingTarget.host), (0, react.createElement)("button", {
3540
3708
  type: "button",
3541
3709
  className: "pbb-action",
3542
3710
  onClick: () => copyRemoteURL(pairingTarget.host)
3543
- }, translateWith(props.t, "panel.tokenAction.copy"))), (0, react.createElement)("p", { className: "pbb-qrHint" }, translateWith(props.t, "pair.qrHint", { kind: pairingTarget.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan") }))))), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, primaryRows, advancedRows.length > 0 ? (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "advanced.summary")), (0, react.createElement)("div", { className: "pbb-helpBody" }, advancedRows)) : null)), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "push.relayTitle")), (0, react.createElement)("span", { className: "pbb-tokenActions" }, (0, react.createElement)("button", {
3711
+ }, translateWith(props.t, "panel.tokenAction.copy"))), addressMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, addressMessage) : null, (0, react.createElement)("p", { className: "pbb-qrHint" }, translateWith(props.t, "pair.qrHint", { kind: pairingTarget.kind === "public" ? translateWith(props.t, "pair.kind.public") : translateWith(props.t, "pair.kind.lan") }))))), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, primaryRows, advancedRows.length > 0 ? (0, react.createElement)("details", { className: "pbb-help" }, (0, react.createElement)("summary", null, translateWith(props.t, "advanced.summary")), (0, react.createElement)("div", { className: "pbb-helpBody" }, advancedRows)) : null)), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "push.relayTitle")), (0, react.createElement)("span", { className: "pbb-tokenActions" }, (0, react.createElement)("button", {
3544
3712
  type: "button",
3545
3713
  className: "pbb-action",
3546
3714
  disabled: relayTestBusy,
@@ -3556,7 +3724,7 @@ window.__ModuleLoader__.load({
3556
3724
  }, (step.ok ? "✓ " : "✗ ") + (step.id === "health" ? translateWith(props.t, "push.relayStep.health") : translateWith(props.t, "push.relayStep.enroll")) + (step.latencyMs !== void 0 ? ` (${String(step.latencyMs)}ms)` : "") + " — " + step.message))), pushTestError ? (0, react.createElement)("p", { className: "pbb-diag pbb-diagBad" }, pushTestError) : null, pushTestResult === null ? (0, react.createElement)("p", { className: "pbb-diag" }, translateWith(props.t, "push.pushDefault")) : (0, react.createElement)("div", { className: "pbb-helpBody" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("code", { className: "pbb-token " + (pushTestResult.overall === "sent" ? "pbb-ok" : pushTestResult.overall === "failed" ? "pbb-bad" : "") }, pushTestResult.overall === "sent" ? translateWith(props.t, "push.pushSent") : pushTestResult.overall === "failed" ? translateWith(props.t, "push.pushFailed") : pushTestResult.overall === "no-targets" ? translateWith(props.t, "push.pushNoTargets") : translateWith(props.t, "push.pushNotEnabled"))), pushTestResult.message ? (0, react.createElement)("p", { className: "pbb-diag" }, pushTestResult.message) : null, pushTestResult.results.map((r, index) => (0, react.createElement)("p", {
3557
3725
  className: "pbb-diag",
3558
3726
  key: String(index)
3559
- }, (r.outcome === "sent" ? "✓ " : "✗ ") + r.name + " [" + r.environment + "] — " + r.outcome + (r.reason ? "(" + r.reason + ")" : "") + (r.tokenFingerprint ? " token:" + r.tokenFingerprint + "…" : "")))))), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "devices.title")), (0, react.createElement)("span", { className: "pbb-badge" }, String(report !== null ? report.devices.length : 0))), deviceTable)), report === null ? null : (0, react.createElement)("div", { className: "pbb-versionFooter" }, (0, react.createElement)("span", null, "DeepPilot v" + report.pluginVersion), report.updateAvailable === true ? (0, react.createElement)("a", {
3727
+ }, (r.outcome === "sent" ? "✓ " : "✗ ") + r.name + " [" + r.environment + "] — " + r.outcome + (r.reason ? "(" + r.reason + ")" : "") + (r.tokenFingerprint ? " token:" + r.tokenFingerprint + "…" : "")))))), (0, react.createElement)("div", { className: "pbb-card" }, (0, react.createElement)("div", { className: "pbb-field" }, (0, react.createElement)("div", { className: "pbb-row" }, (0, react.createElement)("span", { className: "pbb-label" }, translateWith(props.t, "devices.title")), (0, react.createElement)("span", { className: "pbb-badge" }, String(visibleDevices.length))), deviceMessage ? (0, react.createElement)("p", { className: "pbb-diag" }, deviceMessage) : null, deviceTable)), report === null ? null : (0, react.createElement)("div", { className: "pbb-versionFooter" }, (0, react.createElement)("span", null, "DeepPilot v" + report.pluginVersion), report.updateAvailable === true ? (0, react.createElement)("a", {
3560
3728
  href: typeof report.releaseUrl === "string" && /^https:\/\//.test(report.releaseUrl) ? report.releaseUrl : "https://github.com/Mars-Sea/dsh-deeppilot/releases",
3561
3729
  target: "_blank",
3562
3730
  rel: "noreferrer"
@@ -3636,7 +3804,7 @@ window.__ModuleLoader__.load({
3636
3804
  const tPage = (key, vars) => t(ctx, key, vars);
3637
3805
  const controller = new ReportController(fetchReport, tPage);
3638
3806
  ctx.effect(() => () => controller.dispose(), "dsh-deeppilot: report controller");
3639
- const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(controller.state());
3807
+ const store = createSnapshotStore(controller.state());
3640
3808
  controller.subscribe(() => store.set(controller.state()));
3641
3809
  ctx.effect(() => {
3642
3810
  let cancelled = false;
@@ -3665,12 +3833,18 @@ window.__ModuleLoader__.load({
3665
3833
  if (unmount !== void 0) unmount();
3666
3834
  };
3667
3835
  }, "dsh-deeppilot: report remote mount");
3668
- const revealPairingToken = async () => {
3836
+ const beginPairing = async () => {
3669
3837
  if (namespace === void 0) throw new Error(mountError !== void 0 ? t(ctx, "diag.mountFailedShort") + mountError : t(ctx, "diag.remoteUnmounted"));
3670
- const result = await namespace.revealToken();
3671
- if (!result.ok) throw new Error(result.error.message ?? t(ctx, "panel.tokenRevealFailed"));
3838
+ const result = await namespace.beginPairing();
3839
+ if (!result.ok) throw new Error(result.error.message ?? t(ctx, "pair.qrFailed"));
3672
3840
  return result.value;
3673
3841
  };
3842
+ const revokeDevice = async (deviceId) => {
3843
+ if (namespace === void 0) throw new Error(t(ctx, "diag.remoteUnmounted"));
3844
+ const result = await namespace.revokeDevice(deviceId);
3845
+ if (!result.ok || result.value !== true) throw new Error(result.ok ? t(ctx, "devices.revokeFailed") : result.error.message ?? t(ctx, "devices.revokeFailed"));
3846
+ await controller.refresh();
3847
+ };
3674
3848
  const sendTestPush = async () => {
3675
3849
  if (namespace === void 0) throw new Error(mountError !== void 0 ? t(ctx, "diag.mountFailedShort") + mountError : t(ctx, "diag.remoteUnmounted"));
3676
3850
  if (typeof namespace.testPush !== "function") throw new Error(t(ctx, "push.staleHostPush"));
@@ -3685,13 +3859,7 @@ window.__ModuleLoader__.load({
3685
3859
  if (!result.ok) throw new Error(result.error.message ?? t(ctx, "push.relayBad"));
3686
3860
  return result.value;
3687
3861
  };
3688
- const rotatePairingToken = async () => {
3689
- if (namespace === void 0) throw new Error(mountError !== void 0 ? t(ctx, "diag.mountFailedShort") + mountError : t(ctx, "diag.remoteUnmounted"));
3690
- const result = await namespace.rotateToken();
3691
- if (!result.ok) throw new Error(result.error.message ?? t(ctx, "panel.tokenRotateFailed"));
3692
- return result.value;
3693
- };
3694
- const enabledStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({
3862
+ const enabledStore = createSnapshotStore({
3695
3863
  status: "loading",
3696
3864
  enabled: true
3697
3865
  });
@@ -3722,6 +3890,11 @@ window.__ModuleLoader__.load({
3722
3890
  if (snap?.status === "ready") return snap.value?.remote?.enabled === true;
3723
3891
  return remoteEnabledStore.getSnapshot().enabled;
3724
3892
  };
3893
+ const lastConfirmedRemoteConnectionLimit = () => {
3894
+ const snap = scope?.getSnapshot();
3895
+ if (snap?.status === "ready") return normalizeFunnelConnectionLimit(snap.value?.remote?.maxConnectionsPerSource);
3896
+ return remoteConnectionLimitStore.getSnapshot().value;
3897
+ };
3725
3898
  const setDeepPilotEnabled = (value) => {
3726
3899
  const previous = lastConfirmedEnabled();
3727
3900
  enabledStore.set({
@@ -3738,21 +3911,36 @@ window.__ModuleLoader__.load({
3738
3911
  console.error("[deeppilot] failed to persist enabled=" + String(value) + ": " + message);
3739
3912
  });
3740
3913
  };
3741
- const remoteEnabledStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({
3914
+ const remoteEnabledStore = createSnapshotStore({
3742
3915
  status: "loading",
3743
3916
  enabled: false
3744
3917
  });
3918
+ const remoteConnectionLimitStore = createSnapshotStore({
3919
+ status: "loading",
3920
+ value: 8
3921
+ });
3745
3922
  const adoptRemoteEnabled = () => {
3746
3923
  if (scope === void 0) return;
3747
3924
  const snap = scope.getSnapshot();
3748
- if (snap.status === "ready") remoteEnabledStore.set({
3749
- status: "ready",
3750
- enabled: snap.value?.remote?.enabled === true
3751
- });
3752
- else if (snap.status === "unavailable") remoteEnabledStore.set({
3753
- status: "unavailable",
3754
- enabled: false
3755
- });
3925
+ if (snap.status === "ready") {
3926
+ remoteEnabledStore.set({
3927
+ status: "ready",
3928
+ enabled: snap.value?.remote?.enabled === true
3929
+ });
3930
+ remoteConnectionLimitStore.set({
3931
+ status: "ready",
3932
+ value: normalizeFunnelConnectionLimit(snap.value?.remote?.maxConnectionsPerSource)
3933
+ });
3934
+ } else if (snap.status === "unavailable") {
3935
+ remoteEnabledStore.set({
3936
+ status: "unavailable",
3937
+ enabled: false
3938
+ });
3939
+ remoteConnectionLimitStore.set({
3940
+ status: "unavailable",
3941
+ value: 8
3942
+ });
3943
+ }
3756
3944
  };
3757
3945
  if (scope !== void 0) {
3758
3946
  scope.subscribe(adoptRemoteEnabled);
@@ -3778,6 +3966,31 @@ window.__ModuleLoader__.load({
3778
3966
  console.error("[deeppilot] failed to persist remote.enabled=" + String(value) + ": " + message);
3779
3967
  });
3780
3968
  };
3969
+ const setDeepPilotRemoteConnectionLimit = async (value) => {
3970
+ const next = normalizeFunnelConnectionLimit(value);
3971
+ if (next !== value) throw new RangeError("maxConnectionsPerSource must be an integer between 1 and 16");
3972
+ if (scope === void 0) throw new Error("settings scope unavailable");
3973
+ const previous = lastConfirmedRemoteConnectionLimit();
3974
+ remoteConnectionLimitStore.set({
3975
+ status: "ready",
3976
+ value: next
3977
+ });
3978
+ const currentRemote = scope.getSnapshot().value?.remote ?? {};
3979
+ try {
3980
+ await scope.set("remote", {
3981
+ ...currentRemote,
3982
+ maxConnectionsPerSource: next
3983
+ });
3984
+ } catch (error) {
3985
+ remoteConnectionLimitStore.set({
3986
+ status: "ready",
3987
+ value: previous
3988
+ });
3989
+ const message = error instanceof Error ? error.message : String(error);
3990
+ console.error("[deeppilot] failed to persist remote.maxConnectionsPerSource=" + String(next) + ": " + message);
3991
+ throw error;
3992
+ }
3993
+ };
3781
3994
  if (anyCtx.slots === void 0) console.error("[deeppilot] settings slots service unavailable; the DeepPilot section will not appear");
3782
3995
  anyCtx.slots?.inject("settings.section", () => anyCtx.slots.register({
3783
3996
  name: "settings.section",
@@ -3792,17 +4005,19 @@ window.__ModuleLoader__.load({
3792
4005
  hooks: {
3793
4006
  deepPilotReport: store,
3794
4007
  deepPilotEnabled: enabledStore,
3795
- deepPilotRemoteEnabled: remoteEnabledStore
4008
+ deepPilotRemoteEnabled: remoteEnabledStore,
4009
+ deepPilotRemoteConnectionLimit: remoteConnectionLimitStore
3796
4010
  },
3797
4011
  refresh: () => {
3798
4012
  controller.refresh();
3799
4013
  },
3800
- revealPairingToken,
3801
- rotatePairingToken,
4014
+ beginPairing,
4015
+ revokeDevice,
3802
4016
  testRelay: testRelayConnection,
3803
4017
  testPush: sendTestPush,
3804
4018
  setDeepPilotEnabled,
3805
4019
  setDeepPilotRemoteEnabled,
4020
+ setDeepPilotRemoteConnectionLimit,
3806
4021
  t: (key, vars) => t(ctx, key, vars)
3807
4022
  })
3808
4023
  }, DeepPilotSettingsPage));