@awiki/dsh-plugin 0.2.0-rc.4 → 0.2.0-rc.5

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 CHANGED
@@ -20,9 +20,10 @@ TypeScript SDK `identity.json`; create a new Rust-backed identity after upgradin
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
22
  - Direct-message and existing-group conversation lists, unread counts, latest-message previews, and persisted display names.
23
- - Text messages plus one attachment per message, with image previews and SHA verification.
23
+ - Text messages plus one attachment per message, with Enter-to-send, Shift+Enter line breaks, 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.
26
+ - OTP registration keeps the verification form visible and disables resend with a visible server-directed cooldown countdown.
26
27
  - An AWiki page in DSH Settings for a durable, validated default Handle domain.
27
28
  - A typed second confirmation in the Settings danger zone before permanently clearing local AWiki identity, key, token, registration-draft, and message-index state.
28
29
  - Five approval-aware Agent tools: identity status, conversations, history, text send, and attachment send.
package/README.zh.md CHANGED
@@ -14,9 +14,10 @@ Rust 身份。
14
14
  - 点击 AWiki 面板左上角图标可打开账户菜单;普通退出只锁定本机会话,不删除加密身份或消息数据库,重新进入及重启 DSH 后仍恢复同一个 DID 和 Handle。
15
15
  - 注册失败时保留手机号、Handle、验证码和本机待注册密钥;注册未开放、验证码状态失效和提交冲突会给出对应的安全处理提示。
16
16
  - 私聊和已有群聊列表、未读角标、最新消息预览、时间更新与昵称持久化。
17
- - 文本和单附件消息,支持图片预览、附件说明与 SHA 校验。
17
+ - 文本和单附件消息;Enter 发送、Shift+Enter 换行,并支持图片预览、附件说明与 SHA 校验。
18
18
  - 圆形可拖动入口、自适应四角弹窗、深色模式和当前会话记忆。
19
19
  - 用户点击后才生成的 AI 对话总结:最多处理 50 条最近或未读消息,按会话保留本次运行期缓存,并支持过期提示、重试、复制与跳转原消息。
20
+ - OTP 注册会保留验证码输入表单,并按服务端返回的冷却时间显示重发倒计时、禁用提前重发。
20
21
  - 在 DSH 设置中提供 AWiki 页面,可持久化修改并校验默认 Handle 域名。
21
22
  - 在设置页危险区域中,经输入确认词的二次确认后,永久清空本机 AWiki 身份、密钥、令牌、注册草稿和消息索引。
22
23
  - 五个受 Harness 审批约束的 Agent 工具:身份、会话、历史、文本发送、附件发送。
package/lib/client.js CHANGED
@@ -5452,6 +5452,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5452
5452
  disposed = false;
5453
5453
  polling = false;
5454
5454
  unreadAtOpen = /* @__PURE__ */ new Map();
5455
+ summaryBaselines = /* @__PURE__ */ new Map();
5455
5456
  /** @param remote - generated Host Remote namespace. */
5456
5457
  constructor(remote) {
5457
5458
  this.remote = remote;
@@ -5475,6 +5476,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5475
5476
  error: "AWiki 插件已卸载"
5476
5477
  };
5477
5478
  this.close();
5479
+ this.summaryBaselines.clear();
5478
5480
  const generation = this.generation;
5479
5481
  this.publish({
5480
5482
  ...INITIAL_VIEW,
@@ -5525,6 +5527,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5525
5527
  this.close();
5526
5528
  this.conversationsCursor = void 0;
5527
5529
  this.historyCursor = void 0;
5530
+ this.summaryBaselines.clear();
5528
5531
  this.publish({
5529
5532
  ...INITIAL_VIEW,
5530
5533
  status: "ready",
@@ -5771,6 +5774,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5771
5774
  stale: false,
5772
5775
  result: result.value
5773
5776
  });
5777
+ const latestSentAt = Math.max(result.value.range.endedAt, ...this.view.messages.map((message) => message.sentAt));
5778
+ const messageIdsAtLatest = new Set(this.view.messages.filter((message) => message.sentAt === latestSentAt).map((message) => message.id));
5779
+ if (result.value.range.endedAt === latestSentAt) messageIdsAtLatest.add(result.value.range.lastMessageId);
5780
+ this.summaryBaselines.set(conversationId, {
5781
+ latestSentAt,
5782
+ messageIdsAtLatest
5783
+ });
5774
5784
  return result;
5775
5785
  }
5776
5786
  /** Expand or collapse one cached summary without another model call. */
@@ -5872,6 +5882,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5872
5882
  this.conversationsCursor = void 0;
5873
5883
  this.historyCursor = void 0;
5874
5884
  this.unreadAtOpen.clear();
5885
+ this.summaryBaselines.clear();
5875
5886
  this.publish({
5876
5887
  ...INITIAL_VIEW,
5877
5888
  status: "ready"
@@ -5929,7 +5940,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5929
5940
  ...this.view,
5930
5941
  messages,
5931
5942
  historyHasMore: result.value.hasMore && result.value.nextCursor !== void 0,
5932
- summaries: this.staleSummaries(conversationId, messages)
5943
+ summaries: older ? this.view.summaries : this.staleSummaries(conversationId, result.value.items)
5933
5944
  });
5934
5945
  return {
5935
5946
  ok: true,
@@ -5945,13 +5956,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5945
5956
  if (selected === null || !this.current(generation)) return;
5946
5957
  const result = await call(() => this.remote.getHistory({ conversationId: selected }));
5947
5958
  if (!this.current(generation) || !result.ok || this.view.selectedConversationId !== selected) return;
5959
+ const existingIds = new Set(this.view.messages.map((message) => message.id));
5960
+ const incoming = result.value.items.filter((message) => !existingIds.has(message.id));
5948
5961
  const messages = appendUnique(this.view.messages, result.value.items, (value) => value.id);
5949
5962
  const added = messages.length - this.view.messages.length;
5950
5963
  if (added > 0 && (this.unreadAtOpen.get(selected) ?? 0) > 0) this.unreadAtOpen.set(selected, (this.unreadAtOpen.get(selected) ?? 0) + added);
5951
5964
  this.publish({
5952
5965
  ...this.view,
5953
5966
  messages,
5954
- summaries: this.staleSummaries(selected, messages)
5967
+ summaries: this.staleSummaries(selected, incoming)
5955
5968
  });
5956
5969
  } finally {
5957
5970
  this.polling = false;
@@ -5978,12 +5991,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5978
5991
  return result;
5979
5992
  }
5980
5993
  appendMessage(message) {
5994
+ const isNew = !this.view.messages.some((current) => current.id === message.id);
5981
5995
  const messages = appendUnique(this.view.messages, [message], (value) => value.id);
5982
5996
  if ((this.unreadAtOpen.get(message.conversationId) ?? 0) > 0 && messages.length > this.view.messages.length) this.unreadAtOpen.set(message.conversationId, (this.unreadAtOpen.get(message.conversationId) ?? 0) + 1);
5983
5997
  this.publish({
5984
5998
  ...this.view,
5985
5999
  messages,
5986
- summaries: this.staleSummaries(message.conversationId, messages),
6000
+ summaries: isNew ? this.markSummaryStale(message.conversationId) : this.view.summaries,
5987
6001
  error: null
5988
6002
  });
5989
6003
  }
@@ -5999,7 +6013,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5999
6013
  staleSummaries(conversationId, messages) {
6000
6014
  const summary = this.view.summaries[conversationId];
6001
6015
  if (summary?.status !== "success" || summary.result === void 0 || summary.stale) return this.view.summaries;
6002
- if (!messages.some((message) => message.sentAt > summary.result.range.endedAt)) return this.view.summaries;
6016
+ const baseline = this.summaryBaselines.get(conversationId) ?? {
6017
+ latestSentAt: summary.result.range.endedAt,
6018
+ messageIdsAtLatest: /* @__PURE__ */ new Set([summary.result.range.lastMessageId])
6019
+ };
6020
+ if (!messages.some((message) => message.sentAt > baseline.latestSentAt || message.sentAt === baseline.latestSentAt && !baseline.messageIdsAtLatest.has(message.id))) return this.view.summaries;
6021
+ return this.markSummaryStale(conversationId);
6022
+ }
6023
+ markSummaryStale(conversationId) {
6024
+ const summary = this.view.summaries[conversationId];
6025
+ if (summary?.status !== "success" || summary.stale) return this.view.summaries;
6003
6026
  return Object.freeze({
6004
6027
  ...this.view.summaries,
6005
6028
  [conversationId]: Object.freeze({
@@ -6347,14 +6370,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6347
6370
  const [otp, setOtp] = (0, react.useState)("");
6348
6371
  const [otpSent, setOtpSent] = (0, react.useState)(false);
6349
6372
  const [notice, setNotice] = (0, react.useState)(null);
6373
+ const [retryDeadline, setRetryDeadline] = (0, react.useState)(null);
6374
+ const [retrySeconds, setRetrySeconds] = (0, react.useState)(0);
6375
+ (0, react.useEffect)(() => {
6376
+ if (retryDeadline === null) return;
6377
+ const update = () => {
6378
+ const remaining = Math.max(0, Math.ceil((retryDeadline - Date.now()) / 1e3));
6379
+ setRetrySeconds(remaining);
6380
+ if (remaining === 0) setRetryDeadline(null);
6381
+ };
6382
+ update();
6383
+ const timer = setInterval(update, 250);
6384
+ return () => {
6385
+ clearInterval(timer);
6386
+ };
6387
+ }, [retryDeadline]);
6350
6388
  const requestOtp = async () => {
6351
6389
  const result = await props.sendRegistrationOtp({
6352
6390
  handle: handle.trim(),
6353
6391
  phone: phone.trim()
6354
6392
  });
6355
6393
  if (!result.ok) return;
6394
+ const cooldownSeconds = Math.max(0, Math.ceil(result.value.retryAfterSeconds));
6356
6395
  setOtpSent(true);
6357
- setNotice(`验证码已发送;${result.value.retryAfterSeconds} 秒后可重新获取。`);
6396
+ setRetryDeadline(Date.now() + cooldownSeconds * 1e3);
6397
+ setRetrySeconds(cooldownSeconds);
6398
+ setNotice(`验证码已发送;${cooldownSeconds} 秒后可重新获取。`);
6358
6399
  };
6359
6400
  const register = async () => {
6360
6401
  /* v8 ignore next -- the registration action is rendered only after an OTP challenge starts. */
@@ -6419,13 +6460,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6419
6460
  (0, react_jsx_runtime.jsx)("button", {
6420
6461
  type: "button",
6421
6462
  className: _dsh_awiki_css_AwikiOverlay_module_css_default.linkButton,
6422
- disabled: props.pending,
6463
+ disabled: props.pending || retrySeconds > 0,
6423
6464
  onClick: () => {
6424
- setOtpSent(false);
6425
- setOtp("");
6426
- setNotice(null);
6465
+ requestOtp();
6427
6466
  },
6428
- children: "重新获取验证码"
6467
+ children: retrySeconds > 0 ? `${retrySeconds} 秒后重新获取` : "重新获取验证码"
6429
6468
  })
6430
6469
  ] }),
6431
6470
  notice !== null && (0, react_jsx_runtime.jsx)("p", {
@@ -7135,6 +7174,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7135
7174
  onChange: (event) => {
7136
7175
  setText(event.target.value);
7137
7176
  },
7177
+ onKeyDown: (event) => {
7178
+ if (event.key !== "Enter" || event.shiftKey || event.nativeEvent.isComposing) return;
7179
+ event.preventDefault();
7180
+ if (view.pending === null && (file !== null || text.trim() !== "")) sendMessage();
7181
+ },
7138
7182
  placeholder: "输入消息",
7139
7183
  rows: 2
7140
7184
  }),