@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.
- package/README.md +42 -3
- package/README.zh.md +38 -3
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/lib/client.js +67 -14
- package/lib/client.js.map +1 -1
- package/lib/index.js +209 -2
- package/lib/provider.js +3 -2
- package/lib/{sdk-adapter-DPPZhs6p.mjs → sdk-adapter-DK6RqV-c.mjs} +39 -0
- package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
- package/lib/types/client/AwikiOverlay.js +27 -2
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/controller.d.ts +7 -0
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +42 -12
- package/lib/types/client/controller.js.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +1 -0
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/slots.d.ts +2 -0
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/external-http-auth.d.ts +23 -0
- package/lib/types/external-http-auth.d.ts.map +1 -0
- package/lib/types/external-http-auth.js +185 -0
- package/lib/types/external-http-auth.js.map +1 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +47 -0
- package/lib/types/index.js.map +1 -1
- package/lib/types/provider-api.d.ts +28 -0
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +1 -0
- package/lib/types/provider.js.map +1 -1
- package/lib/types/sdk-adapter.d.ts +2 -1
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +31 -0
- package/lib/types/sdk-adapter.js.map +1 -1
- 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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## AWiki Rust IM Core Node SDK
|
|
4
4
|
|
|
5
|
-
`@awiki/im-core-node@0.1.
|
|
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
|
-
|
|
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
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
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)
|
|
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)
|
|
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) => {
|