@awiki/dsh-plugin 0.2.1 → 0.2.3

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.
Files changed (38) hide show
  1. package/README.md +42 -3
  2. package/README.zh.md +38 -3
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/lib/client.js +67 -14
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +209 -2
  7. package/lib/provider.js +3 -2
  8. package/lib/{sdk-adapter-DPPZhs6p.mjs → sdk-adapter-DK6RqV-c.mjs} +39 -0
  9. package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
  10. package/lib/types/client/AwikiOverlay.js +27 -2
  11. package/lib/types/client/AwikiOverlay.js.map +1 -1
  12. package/lib/types/client/controller.d.ts +7 -0
  13. package/lib/types/client/controller.d.ts.map +1 -1
  14. package/lib/types/client/controller.js +42 -12
  15. package/lib/types/client/controller.js.map +1 -1
  16. package/lib/types/client/index.d.ts.map +1 -1
  17. package/lib/types/client/index.js +1 -0
  18. package/lib/types/client/index.js.map +1 -1
  19. package/lib/types/client/slots.d.ts +2 -0
  20. package/lib/types/client/slots.d.ts.map +1 -1
  21. package/lib/types/external-http-auth.d.ts +23 -0
  22. package/lib/types/external-http-auth.d.ts.map +1 -0
  23. package/lib/types/external-http-auth.js +185 -0
  24. package/lib/types/external-http-auth.js.map +1 -0
  25. package/lib/types/index.d.ts +7 -0
  26. package/lib/types/index.d.ts.map +1 -1
  27. package/lib/types/index.js +47 -0
  28. package/lib/types/index.js.map +1 -1
  29. package/lib/types/provider-api.d.ts +28 -0
  30. package/lib/types/provider-api.d.ts.map +1 -1
  31. package/lib/types/provider.d.ts.map +1 -1
  32. package/lib/types/provider.js +1 -0
  33. package/lib/types/provider.js.map +1 -1
  34. package/lib/types/sdk-adapter.d.ts +2 -1
  35. package/lib/types/sdk-adapter.d.ts.map +1 -1
  36. package/lib/types/sdk-adapter.js +31 -0
  37. package/lib/types/sdk-adapter.js.map +1 -1
  38. package/package.json +39 -39
package/README.md CHANGED
@@ -19,7 +19,7 @@ TypeScript SDK `identity.json`; create a new Rust-backed identity after upgradin
19
19
  - Register one deployment-level AWiki identity from the Web UI.
20
20
  - Open the top-left AWiki account menu to sign out locally without deleting the encrypted identity or message database; **Resume** restores the same DID and Handle, including across DSH restarts.
21
21
  - Reuse that identity across the root Agent and its subagents.
22
- - Direct-message and existing-group conversation lists, unread counts, latest-message previews, and persisted display names. Opening a conversation keeps loading feedback inside the history and lands on the newest message; scrolling up reveals a latest-message control that counts newer arrivals without interrupting reading.
22
+ - Direct-message and existing-group conversation lists, unread counts, latest-message previews, and persisted display names. Opening a conversation keeps loading feedback inside the history and lands on the newest message; scrolling up reveals a latest-message control that counts newer arrivals without interrupting reading. A conversation is marked read only after its newest rendered message reaches the visible bottom.
23
23
  - Text messages plus one attachment per message, with Enter-to-send, Shift+Enter line breaks, optimistic sending bubbles, image previews, and SHA verification.
24
24
  - A draggable circular launcher, adaptive popup placement, dark mode, and remembered active conversation.
25
25
  - User-triggered AI summaries for up to 50 recent or unread messages, kept only in runtime memory with explicit stale, retry, copy, and source-navigation states.
@@ -36,9 +36,17 @@ group administration, realtime push, or multiple attachments in one message.
36
36
  Install the official public npm package:
37
37
 
38
38
  ```bash
39
- pnpm add @awiki/dsh-plugin@next
39
+ dsh plugin --profile web add @awiki/dsh-plugin@latest
40
40
  ```
41
41
 
42
+ The profile installer both adds the package and activates its bundle layer. Do
43
+ not add the plugin to the DSH CLI project root with npm or pnpm: profile-managed
44
+ plugins resolve Host packages from the running Harness installation. This
45
+ release line targets the `0.1.0-rc.6` package family and pins those Host peer
46
+ ranges exactly. The Harness profile sets `autoInstallPeers: false`, so an
47
+ external plugin never installs an `rc.7` Host package into an `rc.6`
48
+ deployment.
49
+
42
50
  `@awiki/dsh-plugin` is the canonical package identity starting with
43
51
  `0.2.0-rc.4`. The former `@awiki/dsh` registry entry was unpublished and is
44
52
  not an installation source for this release line.
@@ -107,6 +115,37 @@ stale, without another model call, when newer messages arrive. The replaceable
107
115
  for one direct `ctx.llm.stream` request; it does not create an Agent or write an
108
116
  Agent session.
109
117
 
118
+ ## External HTTP ANP authentication
119
+
120
+ Trusted same-process DSH Host plugins can authenticate an externally transported HTTP request
121
+ without handling ANP signatures, access tokens, challenges, or retries themselves:
122
+
123
+ ```ts
124
+ const response = await ctx.awiki.externalHttpAuth.dispatch(
125
+ new Request('https://api.example.com/orders', {
126
+ method: 'POST',
127
+ headers: { 'content-type': 'application/json' },
128
+ body: JSON.stringify({ productId: '123' }),
129
+ }),
130
+ request => fetch(request),
131
+ )
132
+ ```
133
+
134
+ The callback remains the only network transport owner. AWiki buffers at most 4 MiB of exact body
135
+ bytes, forces manual redirects, asks Rust to select an origin-scoped in-memory Bearer token or a
136
+ fresh HTTP Message Signature, observes only authentication response headers, and invokes the
137
+ transport at most twice for one bounded `401` authentication retry. The final `Response` body is
138
+ untouched. Transport rejections preserve their original error identity.
139
+
140
+ The unsigned input must not contain `Authorization`, `Signature-Input`, `Signature`, or
141
+ `Content-Digest`. Production targets require HTTPS; test-only loopback HTTP uses the existing
142
+ `allowInsecureLoopbackForTesting` deployment gate. Tokens come only from successful
143
+ `Authentication-Info` responses, are scoped to the current identity/signing key/origin, and are
144
+ not persisted across Harness restarts.
145
+
146
+ `externalHttpAuth` is deliberately absent from Browser Remote, Agent tools, Typert Remote, and the
147
+ Web client bundle. Exposing it across an untrusted boundary would create a signing oracle.
148
+
110
149
  ## Development
111
150
 
112
151
  Requirements: Node.js 22.19+ (or 24+) and pnpm 11.7.
@@ -117,7 +156,7 @@ pnpm run verify
117
156
  pnpm pack --dry-run
118
157
  ```
119
158
 
120
- The production Host loads the exact `@awiki/im-core-node@0.1.2` runtime package;
159
+ The production Host loads the exact `@awiki/im-core-node@0.1.3` runtime package;
121
160
  the platform-specific native addon is selected through its optional dependencies
122
161
  and remains external to the JavaScript bundle. Consumers do not need Rust or an
123
162
  `awiki-cli-rs2` checkout. See `THIRD_PARTY_NOTICES.md` for provenance and
package/README.zh.md CHANGED
@@ -13,7 +13,7 @@ Rust 身份。
13
13
  - 在 Web UI 中注册一个部署级 AWiki 身份,根 Agent 与子 Agent 共用。
14
14
  - 点击 AWiki 面板左上角图标可打开账户菜单;普通退出只锁定本机会话,不删除加密身份或消息数据库,重新进入及重启 DSH 后仍恢复同一个 DID 和 Handle。
15
15
  - 注册失败时保留手机号、Handle、验证码和本机待注册密钥;注册未开放、验证码状态失效和提交冲突会给出对应的安全处理提示。
16
- - 私聊和已有群聊列表、未读角标、最新消息预览、时间更新与昵称持久化。打开会话时在消息区内显示加载状态并落到最新消息;向上阅读时显示下滑箭头,新消息到达后在同一控件中累计数量且不打断阅读位置。
16
+ - 私聊和已有群聊列表、未读角标、最新消息预览、时间更新与昵称持久化。打开会话时在消息区内显示加载状态并落到最新消息;向上阅读时显示下滑箭头,新消息到达后在同一控件中累计数量且不打断阅读位置。只有最新一条已渲染消息到达可视区域底部后,当前会话才会自动标记为已读。
17
17
  - 文本和单附件消息;Enter 发送、Shift+Enter 换行,发送中立即显示带 loading 动画的乐观气泡,并支持图片预览、附件说明与 SHA 校验。
18
18
  - 圆形可拖动入口、自适应四角弹窗、深色模式和当前会话记忆。
19
19
  - 用户点击后才生成的 AI 对话总结:最多处理 50 条最近或未读消息,按会话保留本次运行期缓存,并支持过期提示、重试、复制与跳转原消息。
@@ -29,9 +29,15 @@ Rust 身份。
29
29
  安装公开发布的官方 npm 包:
30
30
 
31
31
  ```bash
32
- pnpm add @awiki/dsh-plugin@next
32
+ dsh plugin --profile web add @awiki/dsh-plugin@latest
33
33
  ```
34
34
 
35
+ Profile 安装器会同时添加包并激活 bundle layer。不要在 DSH CLI 项目根目录用
36
+ npm 或 pnpm 添加插件;Profile 管理的插件会从当前 Harness 安装解析 Host 包。
37
+ 本发布线面向 `0.1.0-rc.6` 包族,并精确锁定这些 Host peer。Harness Profile
38
+ 设置了 `autoInstallPeers: false`,因此外部插件不会在 `rc.6` 部署中自动混入
39
+ `rc.7` Host 包。
40
+
35
41
  从 `0.2.0-rc.4` 起,`@awiki/dsh-plugin` 是唯一规范包名。原
36
42
  `@awiki/dsh` registry 条目已被 unpublish,不再作为本发布线的安装来源。
37
43
 
@@ -87,6 +93,35 @@ MIME、大小和说明,不发送文件二进制;序列化后的对话内容
87
93
  调用模型。可替换的 `@awiki/dsh-plugin/summary-provider` 使用 Harness 当前默认 provider/model
88
94
  执行一次直接的 `ctx.llm.stream`,不会创建 Agent,也不会写入 Agent session。
89
95
 
96
+ ## 外部 HTTP ANP 身份认证
97
+
98
+ 可信的 DSH Host 同进程插件可以认证由外部 transport 发送的 HTTP 请求,而无需自行处理
99
+ ANP 签名、Access Token、challenge 或重试:
100
+
101
+ ```ts
102
+ const response = await ctx.awiki.externalHttpAuth.dispatch(
103
+ new Request('https://api.example.com/orders', {
104
+ method: 'POST',
105
+ headers: { 'content-type': 'application/json' },
106
+ body: JSON.stringify({ productId: '123' }),
107
+ }),
108
+ request => fetch(request),
109
+ )
110
+ ```
111
+
112
+ 回调函数仍是唯一网络 transport owner。AWiki 最多缓冲 4 MiB 精确 body bytes,强制 manual
113
+ redirect,由 Rust 自动选择当前 origin 的进程内 Bearer Token 或新 HTTP Message Signature,
114
+ 只观察认证相关响应头,并且每个逻辑请求最多调用 transport 两次,第二次只能是一次受限的
115
+ `401` 认证重试。最终 `Response` 正文不会被读取;transport rejection 保留原始错误对象。
116
+
117
+ 输入请求不得自行携带 `Authorization`、`Signature-Input`、`Signature` 或
118
+ `Content-Digest`。生产目标必须使用 HTTPS;测试用 loopback HTTP 复用现有
119
+ `allowInsecureLoopbackForTesting` 部署开关。Token 只接受成功响应中的
120
+ `Authentication-Info`,并按当前 identity、signing key 和 origin 隔离;Harness 重启后不保留。
121
+
122
+ `externalHttpAuth` 不进入 Browser Remote、Agent tools、Typert Remote 或 Web client bundle,
123
+ 避免形成跨不可信边界的签名 oracle。
124
+
90
125
  ## 开发与验证
91
126
 
92
127
  需要 Node.js 22.19+(或 24+)以及 pnpm 11.7:
@@ -97,7 +132,7 @@ pnpm run verify
97
132
  pnpm pack --dry-run
98
133
  ```
99
134
 
100
- 生产 Host 加载固定版本 `@awiki/im-core-node@0.1.2`;平台原生 addon 由它的
135
+ 生产 Host 加载固定版本 `@awiki/im-core-node@0.1.3`;平台原生 addon 由它的
101
136
  optional dependencies 选择,并保持在 JavaScript bundle 外。使用者无需安装 Rust,
102
137
  也无需检出 `awiki-cli-rs2`。来源与许可证见 `THIRD_PARTY_NOTICES.md`。
103
138
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## AWiki Rust IM Core Node SDK
4
4
 
5
- `@awiki/im-core-node@0.1.2` and its target-specific optional package provide the
5
+ `@awiki/im-core-node@0.1.3` and its target-specific optional package provide the
6
6
  Rust IM Core runtime used by the Host provider. These packages are distributed
7
7
  under AGPL-3.0-only. Each package carries its own `LICENSE`, `NOTICE.md`,
8
8
  `SOURCE.md`, CycloneDX SBOM, checksums, and build provenance. The corresponding
package/lib/client.js CHANGED
@@ -5451,6 +5451,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5451
5451
  generation = 0;
5452
5452
  disposed = false;
5453
5453
  polling = false;
5454
+ markReadInFlight = /* @__PURE__ */ new Map();
5454
5455
  unreadAtOpen = /* @__PURE__ */ new Map();
5455
5456
  summaryBaselines = /* @__PURE__ */ new Map();
5456
5457
  /** @param remote - generated Host Remote namespace. */
@@ -5560,6 +5561,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5560
5561
  if (this.timer !== void 0) clearInterval(this.timer);
5561
5562
  this.timer = void 0;
5562
5563
  this.polling = false;
5564
+ this.markReadInFlight.clear();
5563
5565
  }
5564
5566
  /**
5565
5567
  * Request one phone verification challenge.
@@ -5716,24 +5718,51 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5716
5718
  })
5717
5719
  });
5718
5720
  if (!loaded.ok) return loaded;
5719
- const marked = await call(() => this.remote.markConversationRead({ conversationId }));
5720
- if (!marked.ok) return this.fail(marked.error);
5721
- if (!this.current(generation) || this.view.selectedConversationId !== conversationId) return {
5721
+ return {
5722
5722
  ok: true,
5723
5723
  value: void 0
5724
5724
  };
5725
- this.publish({
5726
- ...this.view,
5727
- conversations: this.view.conversations.map((conversation) => conversation.id === conversationId ? {
5728
- ...conversation,
5729
- unreadCount: 0
5730
- } : conversation),
5731
- error: null
5732
- });
5733
- return {
5725
+ }
5726
+ /**
5727
+ * Mark the selected conversation read after the UI proves its newest message is visible.
5728
+ * Repeated scroll and layout notifications share one Host request, while a failed
5729
+ * background attempt keeps the unread badge so reaching the bottom can retry.
5730
+ */
5731
+ async markSelectedConversationRead() {
5732
+ if (this.disposed) return {
5733
+ ok: false,
5734
+ error: "AWiki 插件已卸载"
5735
+ };
5736
+ const conversation = this.selectedConversation();
5737
+ if (conversation === void 0 || (conversation.unreadCount ?? 0) <= 0) return {
5734
5738
  ok: true,
5735
5739
  value: void 0
5736
5740
  };
5741
+ const existing = this.markReadInFlight.get(conversation.id);
5742
+ if (existing !== void 0) return existing;
5743
+ const conversationId = conversation.id;
5744
+ const generation = this.generation;
5745
+ const operation = (async () => {
5746
+ const result = await call(() => this.remote.markConversationRead({ conversationId }));
5747
+ if (!result.ok) return result;
5748
+ if (this.current(generation)) this.publish({
5749
+ ...this.view,
5750
+ conversations: this.view.conversations.map((current) => current.id === conversationId ? {
5751
+ ...current,
5752
+ unreadCount: 0
5753
+ } : current)
5754
+ });
5755
+ return {
5756
+ ok: true,
5757
+ value: void 0
5758
+ };
5759
+ })();
5760
+ this.markReadInFlight.set(conversationId, operation);
5761
+ try {
5762
+ return await operation;
5763
+ } finally {
5764
+ if (this.markReadInFlight.get(conversationId) === operation) this.markReadInFlight.delete(conversationId);
5765
+ }
5737
5766
  }
5738
5767
  /**
5739
5768
  * Load one older history page before the currently rendered messages.
@@ -6991,6 +7020,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6991
7020
  const summaryPanelId = (0, react.useId)();
6992
7021
  selectedConversationId.current = view.selectedConversationId;
6993
7022
  const visibleSendingDraft = sendingDraft?.conversationId === view.selectedConversationId ? sendingDraft : null;
7023
+ const markSelectedConversationReadAtBottom = () => {
7024
+ const node = history.current;
7025
+ const newestRendered = view.messages.at(-1);
7026
+ if (node === null || selected === void 0 || newestRendered === void 0 || (selected.unreadCount ?? 0) <= 0 || view.pending === "加载消息" || selected.lastMessageAt !== void 0 && newestRendered.sentAt < selected.lastMessageAt || node.scrollHeight - node.scrollTop - node.clientHeight > HISTORY_BOTTOM_THRESHOLD) return;
7027
+ props.markSelectedConversationRead();
7028
+ };
6994
7029
  const scrollHistoryToLatest = (smooth) => {
6995
7030
  const node = history.current;
6996
7031
  if (node === null) return;
@@ -7003,6 +7038,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7003
7038
  historyPinnedToBottom.current = true;
7004
7039
  setHistoryAwayFromBottom(false);
7005
7040
  setUnseenMessageCount(0);
7041
+ if (!smooth || reduceMotion || typeof node.scrollTo !== "function") markSelectedConversationReadAtBottom();
7006
7042
  };
7007
7043
  const syncHistoryPosition = () => {
7008
7044
  const node = history.current;
@@ -7010,8 +7046,20 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7010
7046
  const atBottom = node.scrollHeight - node.scrollTop - node.clientHeight <= HISTORY_BOTTOM_THRESHOLD;
7011
7047
  historyPinnedToBottom.current = atBottom;
7012
7048
  setHistoryAwayFromBottom(!atBottom);
7013
- if (atBottom) setUnseenMessageCount(0);
7049
+ if (atBottom) {
7050
+ setUnseenMessageCount(0);
7051
+ markSelectedConversationReadAtBottom();
7052
+ }
7014
7053
  };
7054
+ (0, react.useLayoutEffect)(() => {
7055
+ markSelectedConversationReadAtBottom();
7056
+ }, [
7057
+ selected?.id,
7058
+ selected?.lastMessageAt,
7059
+ selected?.unreadCount,
7060
+ view.messages,
7061
+ view.pending
7062
+ ]);
7015
7063
  (0, react.useLayoutEffect)(() => {
7016
7064
  const conversationId = view.selectedConversationId;
7017
7065
  if (conversationId !== previousConversationId.current) {
@@ -7036,7 +7084,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7036
7084
  messageId: view.messages.at(-1)?.id ?? null
7037
7085
  };
7038
7086
  scrollHistoryToLatest(false);
7039
- if (pendingInitialImages.current.size === 0) conversationAwaitingBottom.current = null;
7087
+ if (pendingInitialImages.current.size === 0) {
7088
+ conversationAwaitingBottom.current = null;
7089
+ markSelectedConversationReadAtBottom();
7090
+ }
7040
7091
  return;
7041
7092
  }
7042
7093
  const previous = previousMessageTail.current;
@@ -7067,6 +7118,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7067
7118
  if (!pendingInitialImages.current.delete(messageId)) return;
7068
7119
  if (history.current !== null) scrollHistoryToLatest(false);
7069
7120
  if (pendingInitialImages.current.size === 0) conversationAwaitingBottom.current = null;
7121
+ if (pendingInitialImages.current.size === 0) markSelectedConversationReadAtBottom();
7070
7122
  };
7071
7123
  const viewSummarySource = (messageId) => {
7072
7124
  if (selected === void 0) return;
@@ -8522,6 +8574,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8522
8574
  loadMoreConversations: () => controller.loadMoreConversations(),
8523
8575
  startDirectChat: (handle) => controller.startDirectChat(handle),
8524
8576
  selectConversation: (conversationId) => controller.selectConversation(conversationId),
8577
+ markSelectedConversationRead: () => controller.markSelectedConversationRead(),
8525
8578
  loadOlderHistory: () => controller.loadOlderHistory(),
8526
8579
  summarizeConversation: () => controller.summarizeConversation(),
8527
8580
  setSummaryCollapsed: (conversationId, collapsed) => {