dsh-deeppilot 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PRIVACY.md CHANGED
@@ -7,7 +7,7 @@ DeepPilot connects directly to the DSH Host controlled by the user. The plugin d
7
7
  By default, runtime state is stored under `$DSH_HOME/deeppilot/`:
8
8
 
9
9
  - `host-id`: a random, non-secret stable audience identifier;
10
- - `devices-v2.json`: paired public keys, fingerprints, scopes, device metadata, revocation/last-seen timestamps, notification preferences, and APNs registrations;
10
+ - `devices-v2.json`: paired public keys, fingerprints, scopes, device metadata, revocation/last-seen timestamps, notification preferences, APNs alert registrations, and WidgetKit push registrations (a separate token per device used only for content-free widget refresh invalidations);
11
11
  - `tailscale/`: local state for the optional embedded tsnet node.
12
12
 
13
13
  The directory is owner-only mode `0700`. The Mac never receives or stores iPhone private signing keys. Pairing codes exist only in plugin memory for up to five minutes and are not written to disk.
@@ -31,6 +31,20 @@ Direct APNs mode sends the device token and notification payload to Apple using
31
31
 
32
32
  The relay does not receive complete history, attachments, live token streams, unrelated prompts, pairing codes, device keys, the device registry, or Tailscale state. Disabling offline push removes this path.
33
33
 
34
+ ## WidgetKit refresh push
35
+
36
+ The iOS home-screen widget may register a separate WidgetKit push token (iOS 26+). These invalidations are content-free: the APNs payload is fixed to `{ "aps": { "content-changed": true } }` with no session, title, or body fields, so neither Apple nor the relay learns anything about Host activity beyond "something changed, please refetch". The widget then pulls data over its own short-lived, authenticated read-only connection directly to the user's DSH Host. Widget tokens are stored alongside the alert token in `devices-v2.json`, are throttled to at most one invalidation per Host per 30 seconds, expire after 7 days without re-registration, and are deleted when the device is revoked.
37
+
34
38
  ## Logs
35
39
 
36
40
  Routine logs contain lifecycle state, delivery outcomes, and process-local salted hashes for source/device identifiers. Logs are designed not to contain pairing codes, key material, APNs tokens, or message bodies. Frame diagnostics appear only with `debug`.
41
+
42
+ ### Generic offline notifications
43
+
44
+ Set `push.contentMode` to `generic` in the plugin configuration to replace conversation-derived notification titles and bodies with generic status text **before** either APNs or the Relay receives them. The default is `preview`, preserving short previews. Device tokens, Host audience, session and notification identifiers, and categories are still sent. This option controls offline pushes; local notifications generated by the iOS app from its direct connection are unchanged.
45
+
46
+ For identity-tracked sends, the Host stores a bounded local delivery journal containing
47
+ hashed device/send keys, content fingerprints, session identifiers, timestamps, and
48
+ receipts. It does not store prompt bodies or image data in that journal. The iOS app
49
+ stores unresolved send IDs, display text and attachment counts in its instance-scoped
50
+ SQLite cache; removing that instance removes this outbox with its database.
package/lib/client.js CHANGED
@@ -539,6 +539,8 @@ window.__ModuleLoader__.load({
539
539
  "devices.col.fingerprint": "密钥指纹",
540
540
  "devices.col.actions": "操作",
541
541
  "devices.revoke": "删除",
542
+ "devices.revokeAria": "删除设备“{name}”",
543
+ "devices.deleting": "删除中…",
542
544
  "devices.revokeConfirm": "确定删除设备“{name}”吗?该设备会立即断开,再次使用时需要重新配对。",
543
545
  "devices.revoked": "设备已删除",
544
546
  "devices.revokeFailed": "设备删除失败:",
@@ -719,6 +721,8 @@ window.__ModuleLoader__.load({
719
721
  "devices.col.fingerprint": "Key fingerprint",
720
722
  "devices.col.actions": "Actions",
721
723
  "devices.revoke": "Delete",
724
+ "devices.revokeAria": "Delete device “{name}”",
725
+ "devices.deleting": "Deleting…",
722
726
  "devices.revokeConfirm": "Delete “{name}”? It will disconnect immediately and must pair again before reconnecting.",
723
727
  "devices.revoked": "Device deleted",
724
728
  "devices.revokeFailed": "Failed to delete device: ",
@@ -881,10 +885,15 @@ window.__ModuleLoader__.load({
881
885
  ".pbb-action:disabled{cursor:default;opacity:.5}",
882
886
  ".pbb-actionSelected{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-state-success-primary,#22a06b);background:var(--dsw-alias-bg-module-platform)}",
883
887
  ".pbb-targetList{display:flex;flex-wrap:wrap;gap:6px;padding-top:4px}",
884
- ".pbb-actionDanger{color:#fff;background:#dc2626;border-color:#dc2626;line-height:1.35;padding:4px 10px;box-shadow:0 1px 2px rgba(127,29,29,.18);transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease,transform .1s ease}",
885
- ".pbb-actionDanger:hover:not(:disabled){color:#fff;background:#b91c1c;border-color:#b91c1c;box-shadow:0 2px 5px rgba(127,29,29,.24)}",
886
- ".pbb-actionDanger:active:not(:disabled){background:#991b1b;border-color:#991b1b;box-shadow:0 1px 2px rgba(127,29,29,.2);transform:translateY(1px)}",
887
- ".pbb-actionDanger:focus-visible{outline:2px solid rgba(220,38,38,.32);outline-offset:2px}",
888
+ ".pbb-actionDangerGhost{color:#dc2626;border-color:rgba(220,38,38,.38);background:transparent;line-height:1.35;padding:3px 9px;box-shadow:none;transition:color .15s ease,background-color .15s ease,border-color .15s ease,box-shadow .15s ease,transform .1s ease}",
889
+ ".pbb-actionDangerGhost:hover:not(:disabled){color:#b91c1c;background:rgba(220,38,38,.09);border-color:rgba(185,28,28,.5);box-shadow:0 1px 2px rgba(127,29,29,.1)}",
890
+ ".pbb-actionDangerGhost:active:not(:disabled){color:#991b1b;background:rgba(220,38,38,.14);border-color:rgba(153,27,27,.55);box-shadow:none;transform:translateY(1px)}",
891
+ ".pbb-actionDangerGhost:focus-visible{outline:2px solid rgba(220,38,38,.32);outline-offset:1px}",
892
+ ".pbb-actionDangerGhost:disabled{cursor:default;opacity:.62}",
893
+ ".pbb-actionIcon{flex:none;display:inline-block;vertical-align:-2px;margin-right:5px;pointer-events:none}",
894
+ ".pbb-actionBusy{display:inline-flex;align-items:center;gap:6px}",
895
+ ".pbb-actionBusy::before{content:\"\";width:9px;height:9px;border-radius:50%;border:1.5px solid rgba(220,38,38,.35);border-top-color:currentColor;animation:pbb-spin .7s linear infinite}",
896
+ "@keyframes pbb-spin{to{transform:rotate(360deg)}}",
888
897
  ".pbb-qrPanel{display:flex;flex-direction:column;align-items:center;gap:9px;padding:12px 0 4px}",
889
898
  ".pbb-qrImage{width:240px;height:240px;max-width:100%;background:#fff;border-radius:10px;padding:8px;box-sizing:border-box}",
890
899
  ".pbb-pairCodeBlock{width:min(100%,420px);display:flex;flex-direction:column;gap:5px}",
@@ -3411,6 +3420,21 @@ window.__ModuleLoader__.load({
3411
3420
  }
3412
3421
  //#endregion
3413
3422
  //#region src/client/settings-page.ts
3423
+ /** Inline trash icon for the compact destructive row action. Kept as a tiny
3424
+ * element (no icon dependency) so the client bundle stays dependency-free
3425
+ * apart from React. */
3426
+ const TrashIcon = () => (0, react.createElement)("svg", {
3427
+ className: "pbb-actionIcon",
3428
+ viewBox: "0 0 16 16",
3429
+ width: 13,
3430
+ height: 13,
3431
+ fill: "none",
3432
+ stroke: "currentColor",
3433
+ strokeWidth: 1.5,
3434
+ strokeLinecap: "round",
3435
+ strokeLinejoin: "round",
3436
+ "aria-hidden": true
3437
+ }, (0, react.createElement)("path", { d: "M2.5 4.5h11" }), (0, react.createElement)("path", { d: "M6.5 2.5h3" }), (0, react.createElement)("path", { d: "M4 4.5l.6 8.2a1 1 0 0 0 1 .9h4.8a1 1 0 0 0 1-.9l.6-8.2" }), (0, react.createElement)("path", { d: "M6.6 7v4.2" }), (0, react.createElement)("path", { d: "M9.4 7v4.2" }));
3414
3438
  async function writeClipboard(t, value) {
3415
3439
  try {
3416
3440
  await navigator.clipboard.writeText(value);
@@ -3754,10 +3778,11 @@ window.__ModuleLoader__.load({
3754
3778
  const visibleDevices = report?.devices.filter((device) => device.revokedAt === void 0) ?? [];
3755
3779
  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", {
3756
3780
  type: "button",
3757
- className: "pbb-action pbb-actionDanger",
3781
+ className: "pbb-action pbb-actionDangerGhost",
3758
3782
  disabled: deviceBusy === d.deviceId,
3783
+ "aria-label": deviceBusy === d.deviceId ? translateWith(props.t, "devices.deleting", { name: d.deviceName }) : translateWith(props.t, "devices.revokeAria", { name: d.deviceName }),
3759
3784
  onClick: () => revokeDevice(d.deviceId, d.deviceName)
3760
- }, translateWith(props.t, "devices.revoke"))))))) : (0, react.createElement)("p", { className: "pbb-empty" }, translateWith(props.t, "devices.empty"));
3785
+ }, deviceBusy === d.deviceId ? null : TrashIcon(), deviceBusy === d.deviceId ? (0, react.createElement)("span", { className: "pbb-actionBusy" }, translateWith(props.t, "devices.deleting")) : translateWith(props.t, "devices.revoke"))))))) : (0, react.createElement)("p", { className: "pbb-empty" }, translateWith(props.t, "devices.empty"));
3761
3786
  const switchTitle = translateWith(props.t, "master.title");
3762
3787
  const switchDesc = switchReady ? enabled ? translateWith(props.t, "master.on") : translateWith(props.t, "master.off") : translateWith(props.t, "master.loading");
3763
3788
  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", {