@soimy/dingtalk 3.6.10 → 3.7.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.
Files changed (163) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +11655 -10324
  3. package/dist/src/{ack-reaction-service.d.ts → ack-reaction/ack-reaction-service.d.ts} +1 -1
  4. package/dist/src/ack-reaction/dynamic-ack-reaction-controller.d.ts +3 -3
  5. package/dist/src/card/ask-user-question-context.d.ts +1 -1
  6. package/dist/src/card/ask-user-question-store.d.ts +1 -1
  7. package/dist/src/card/ask-user-question.d.ts +1 -1
  8. package/dist/src/card/card-action-handler.d.ts +2 -2
  9. package/dist/src/{card-draft-controller.d.ts → card/card-draft-controller.d.ts} +5 -1
  10. package/dist/src/card/card-run-registry.d.ts +2 -2
  11. package/dist/src/{card-service.d.ts → card/card-service.d.ts} +1 -1
  12. package/dist/src/card/card-stop-handler.d.ts +1 -1
  13. package/dist/src/card/card-streaming-mode.d.ts +1 -1
  14. package/dist/src/card/card-task-progress.d.ts +60 -0
  15. package/dist/src/channel.d.ts +6 -6
  16. package/dist/src/command/card-stop-command.d.ts +1 -1
  17. package/dist/src/{feedback-learning-service.d.ts → command/feedback-learning-service.d.ts} +1 -1
  18. package/dist/src/command/inbound-command-dispatch-service.d.ts +2 -2
  19. package/dist/src/{learning-command-service.d.ts → command/learning-command-service.d.ts} +1 -1
  20. package/dist/src/gateway/channel-gateway.d.ts +1 -1
  21. package/dist/src/{connection-manager.d.ts → gateway/connection-manager.d.ts} +1 -1
  22. package/dist/src/{docs-service.d.ts → gateway/docs-service.d.ts} +5 -5
  23. package/dist/src/{inbound-handler.d.ts → gateway/inbound-handler.d.ts} +1 -1
  24. package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts +1 -1
  25. package/dist/src/messaging/attachment-text-extractor.d.ts +1 -1
  26. package/dist/src/messaging/btw-deliver.d.ts +1 -1
  27. package/dist/src/messaging/channel-outbound.d.ts +1 -1
  28. package/dist/src/messaging/inline-directives.d.ts +42 -0
  29. package/dist/src/{media-utils.d.ts → messaging/media-utils.d.ts} +1 -1
  30. package/dist/src/{message-context-store.d.ts → messaging/message-context-store.d.ts} +7 -1
  31. package/dist/src/{message-utils.d.ts → messaging/message-utils.d.ts} +1 -1
  32. package/dist/src/messaging/quoted-context.d.ts +2 -2
  33. package/dist/src/messaging/quoted-file-service.d.ts +1 -1
  34. package/dist/src/messaging/quoted-ref.d.ts +2 -2
  35. package/dist/src/{reply-strategy-card.d.ts → messaging/reply-strategy-card.d.ts} +1 -1
  36. package/dist/src/{reply-strategy-types.d.ts → messaging/reply-strategy-types.d.ts} +10 -1
  37. package/dist/src/{reply-strategy-with-reaction.d.ts → messaging/reply-strategy-with-reaction.d.ts} +1 -1
  38. package/dist/src/{reply-strategy.d.ts → messaging/reply-strategy.d.ts} +1 -1
  39. package/dist/src/{send-service.d.ts → messaging/send-service.d.ts} +1 -1
  40. package/dist/src/platform/channel-status.d.ts +1 -1
  41. package/dist/src/platform/config.d.ts +110 -0
  42. package/dist/src/platform/runtime-events.d.ts +67 -0
  43. package/dist/src/{types.d.ts → platform/types.d.ts} +75 -2
  44. package/dist/src/{persistence-store.d.ts → shared/persistence-store.d.ts} +1 -1
  45. package/dist/src/{utils.d.ts → shared/utils.d.ts} +1 -1
  46. package/dist/src/targeting/agent-name-matcher.d.ts +1 -1
  47. package/dist/src/targeting/agent-routing.d.ts +3 -3
  48. package/dist/src/{session-routing.d.ts → targeting/session-routing.d.ts} +1 -1
  49. package/index.ts +193 -69
  50. package/openclaw.plugin.json +158 -2
  51. package/package.json +16 -16
  52. package/src/{ack-reaction-classifier.ts → ack-reaction/ack-reaction-classifier.ts} +85 -13
  53. package/src/{ack-reaction-service.ts → ack-reaction/ack-reaction-service.ts} +13 -7
  54. package/src/ack-reaction/dynamic-ack-reaction-controller.ts +46 -31
  55. package/src/ack-reaction/dynamic-ack-reaction-progress.ts +4 -1
  56. package/src/card/ask-user-question-context.ts +1 -1
  57. package/src/card/ask-user-question-store.ts +2 -2
  58. package/src/card/ask-user-question.ts +11 -7
  59. package/src/card/card-action-handler.ts +2 -2
  60. package/src/{card-callback-service.ts → card/card-callback-service.ts} +7 -9
  61. package/src/card/card-draft-controller.ts +770 -0
  62. package/src/card/card-markdown-image-reroute.ts +8 -2
  63. package/src/card/card-run-registry.ts +17 -7
  64. package/src/{card-service.ts → card/card-service.ts} +91 -61
  65. package/src/card/card-stop-handler.ts +12 -8
  66. package/src/card/card-streaming-mode.ts +1 -1
  67. package/src/card/card-task-progress.ts +330 -0
  68. package/src/card/draft-stream-loop.ts +119 -0
  69. package/src/card/reasoning-answer-split.ts +125 -121
  70. package/src/card/reasoning-block-assembler.ts +122 -123
  71. package/src/{run-usage-store.ts → card/run-usage-store.ts} +18 -6
  72. package/src/card/statusline-renderer.ts +14 -5
  73. package/src/card/task-model-metadata.ts +8 -3
  74. package/src/channel.ts +14 -16
  75. package/src/command/card-stop-command.ts +2 -2
  76. package/src/{feedback-learning-service.ts → command/feedback-learning-service.ts} +36 -16
  77. package/src/{feedback-learning-store.ts → command/feedback-learning-store.ts} +133 -81
  78. package/src/command/inbound-command-dispatch-service.ts +12 -6
  79. package/src/{learning-command-service.ts → command/learning-command-service.ts} +36 -29
  80. package/src/{session-command-service.ts → command/session-command-service.ts} +3 -1
  81. package/src/gateway/channel-gateway.ts +106 -28
  82. package/src/{connection-manager.ts → gateway/connection-manager.ts} +45 -30
  83. package/src/{docs-service.ts → gateway/docs-service.ts} +5 -5
  84. package/src/{inbound-handler.ts → gateway/inbound-handler.ts} +144 -85
  85. package/src/gateway/inbound-session-queue-dispatcher.ts +18 -14
  86. package/src/{session-lock.ts → gateway/session-lock.ts} +14 -14
  87. package/src/messaging/attachment-text-extractor.ts +21 -7
  88. package/src/messaging/btw-deliver.ts +2 -2
  89. package/src/messaging/channel-actions.ts +15 -7
  90. package/src/messaging/channel-outbound.ts +16 -11
  91. package/src/messaging/inline-directives.ts +342 -0
  92. package/src/{media-utils.ts → messaging/media-utils.ts} +276 -72
  93. package/src/{message-context-store.ts → messaging/message-context-store.ts} +11 -2
  94. package/src/{message-utils.ts → messaging/message-utils.ts} +46 -11
  95. package/src/messaging/quoted-context.ts +2 -2
  96. package/src/messaging/quoted-file-service.ts +311 -294
  97. package/src/messaging/quoted-ref.ts +11 -7
  98. package/src/{reply-strategy-card.ts → messaging/reply-strategy-card.ts} +178 -101
  99. package/src/{reply-strategy-markdown.ts → messaging/reply-strategy-markdown.ts} +8 -2
  100. package/src/{reply-strategy-types.ts → messaging/reply-strategy-types.ts} +11 -1
  101. package/src/{reply-strategy-with-reaction.ts → messaging/reply-strategy-with-reaction.ts} +6 -1
  102. package/src/{reply-strategy.ts → messaging/reply-strategy.ts} +2 -2
  103. package/src/{send-service.ts → messaging/send-service.ts} +100 -65
  104. package/src/{auth.ts → platform/auth.ts} +2 -2
  105. package/src/platform/channel-status.ts +3 -3
  106. package/src/{config-schema.ts → platform/config-schema.ts} +81 -4
  107. package/src/{config.ts → platform/config.ts} +167 -19
  108. package/src/{device-registration.ts → platform/device-registration.ts} +5 -4
  109. package/src/{onboarding.ts → platform/onboarding.ts} +1 -4
  110. package/src/{plugin-sdk-channel-actions-augment.ts → platform/plugin-sdk-channel-actions-augment.ts} +6 -5
  111. package/src/platform/runtime-events.ts +202 -0
  112. package/src/{secret-input.ts → platform/secret-input.ts} +117 -11
  113. package/src/{types.ts → platform/types.ts} +77 -2
  114. package/src/{http-client.ts → shared/http-client.ts} +1 -3
  115. package/src/{persistence-store.ts → shared/persistence-store.ts} +6 -6
  116. package/src/{utils.ts → shared/utils.ts} +30 -13
  117. package/src/targeting/agent-name-matcher.ts +1 -1
  118. package/src/targeting/agent-routing.ts +7 -7
  119. package/src/targeting/group-members-store.ts +1 -1
  120. package/src/{session-peer-store.ts → targeting/session-peer-store.ts} +9 -4
  121. package/src/{session-routing.ts → targeting/session-routing.ts} +1 -1
  122. package/src/targeting/target-directory-adapter.ts +3 -3
  123. package/src/targeting/target-directory-store.ts +1 -1
  124. package/src/targeting/target-input.ts +1 -1
  125. package/dist/src/ack-reaction/dynamic-ack-reaction-events.d.ts +0 -35
  126. package/dist/src/config.d.ts +0 -59
  127. package/src/ack-reaction/dynamic-ack-reaction-events.ts +0 -123
  128. package/src/card-draft-controller.ts +0 -637
  129. package/src/draft-stream-loop.ts +0 -119
  130. /package/dist/src/{ack-reaction-classifier.d.ts → ack-reaction/ack-reaction-classifier.d.ts} +0 -0
  131. /package/dist/src/{card-callback-service.d.ts → card/card-callback-service.d.ts} +0 -0
  132. /package/dist/src/{draft-stream-loop.d.ts → card/draft-stream-loop.d.ts} +0 -0
  133. /package/dist/src/{run-usage-store.d.ts → card/run-usage-store.d.ts} +0 -0
  134. /package/dist/src/{feedback-learning-store.d.ts → command/feedback-learning-store.d.ts} +0 -0
  135. /package/dist/src/{session-command-service.d.ts → command/session-command-service.d.ts} +0 -0
  136. /package/dist/src/{session-lock.d.ts → gateway/session-lock.d.ts} +0 -0
  137. /package/dist/src/{proactive-risk-registry.d.ts → messaging/proactive-risk-registry.d.ts} +0 -0
  138. /package/dist/src/{reply-strategy-markdown.d.ts → messaging/reply-strategy-markdown.d.ts} +0 -0
  139. /package/dist/src/{access-control.d.ts → platform/access-control.d.ts} +0 -0
  140. /package/dist/src/{auth.d.ts → platform/auth.d.ts} +0 -0
  141. /package/dist/src/{config-schema.d.ts → platform/config-schema.d.ts} +0 -0
  142. /package/dist/src/{device-registration.d.ts → platform/device-registration.d.ts} +0 -0
  143. /package/dist/src/{logger-context.d.ts → platform/logger-context.d.ts} +0 -0
  144. /package/dist/src/{onboarding.d.ts → platform/onboarding.d.ts} +0 -0
  145. /package/dist/src/{plugin-sdk-channel-actions-augment.d.ts → platform/plugin-sdk-channel-actions-augment.d.ts} +0 -0
  146. /package/dist/src/{runtime.d.ts → platform/runtime.d.ts} +0 -0
  147. /package/dist/src/{secret-input.d.ts → platform/secret-input.d.ts} +0 -0
  148. /package/dist/src/{session-state.d.ts → platform/session-state.d.ts} +0 -0
  149. /package/dist/src/{signature.d.ts → platform/signature.d.ts} +0 -0
  150. /package/dist/src/{dedup.d.ts → shared/dedup.d.ts} +0 -0
  151. /package/dist/src/{http-client.d.ts → shared/http-client.d.ts} +0 -0
  152. /package/dist/src/{path-utils.d.ts → shared/path-utils.d.ts} +0 -0
  153. /package/dist/src/{peer-id-registry.d.ts → targeting/peer-id-registry.d.ts} +0 -0
  154. /package/dist/src/{session-peer-store.d.ts → targeting/session-peer-store.d.ts} +0 -0
  155. /package/src/{proactive-risk-registry.ts → messaging/proactive-risk-registry.ts} +0 -0
  156. /package/src/{access-control.ts → platform/access-control.ts} +0 -0
  157. /package/src/{logger-context.ts → platform/logger-context.ts} +0 -0
  158. /package/src/{runtime.ts → platform/runtime.ts} +0 -0
  159. /package/src/{session-state.ts → platform/session-state.ts} +0 -0
  160. /package/src/{signature.ts → platform/signature.ts} +0 -0
  161. /package/src/{dedup.ts → shared/dedup.ts} +0 -0
  162. /package/src/{path-utils.ts → shared/path-utils.ts} +0 -0
  163. /package/src/{peer-id-registry.ts → targeting/peer-id-registry.ts} +0 -0
@@ -1,5 +1,5 @@
1
- import { normalizeAllowFrom, isSenderOwner } from "./access-control";
2
- import type { DingTalkConfig } from "./types";
1
+ import { normalizeAllowFrom, isSenderOwner } from "../platform/access-control";
2
+ import type { DingTalkConfig } from "../platform/types";
3
3
 
4
4
  export interface ParsedLearnCommand {
5
5
  scope:
@@ -33,31 +33,29 @@ export function parseLearnCommand(text: string | undefined): ParsedLearnCommand
33
33
  return { scope: "unknown" };
34
34
  }
35
35
  const normalized = raw.toLowerCase();
36
- if (
37
- normalized === "/learn help"
38
- ) {
36
+ if (normalized === "/learn help") {
39
37
  return { scope: "help" };
40
38
  }
41
39
  if (
42
- normalized === "/learn whoami"
43
- || normalized === "/whoami"
44
- || normalized === "我是谁"
45
- || normalized === "我的信息"
40
+ normalized === "/learn whoami" ||
41
+ normalized === "/whoami" ||
42
+ normalized === "我是谁" ||
43
+ normalized === "我的信息"
46
44
  ) {
47
45
  return { scope: "whoami" };
48
46
  }
49
47
  if (
50
- normalized === "/learn whereami"
51
- || normalized === "这里是谁"
52
- || normalized === "这个群是谁"
53
- || normalized === "这个会话是谁"
48
+ normalized === "/learn whereami" ||
49
+ normalized === "这里是谁" ||
50
+ normalized === "这个群是谁" ||
51
+ normalized === "这个会话是谁"
54
52
  ) {
55
53
  return { scope: "whereami" };
56
54
  }
57
55
  if (
58
- normalized === "/learn owner status"
59
- || normalized === "/owner status"
60
- || normalized === "/owner-status"
56
+ normalized === "/learn owner status" ||
57
+ normalized === "/owner status" ||
58
+ normalized === "/owner-status"
61
59
  ) {
62
60
  return { scope: "owner-status" };
63
61
  }
@@ -184,7 +182,9 @@ export function formatWhereAmIReply(params: {
184
182
  params.peerId ? `- peerId: \`${params.peerId}\`` : undefined,
185
183
  "",
186
184
  "后续如果要定向注入到这里,就使用这个 conversationId。",
187
- ].filter(Boolean).join("\n");
185
+ ]
186
+ .filter(Boolean)
187
+ .join("\n");
188
188
  }
189
189
 
190
190
  export function formatOwnerStatusReply(params: {
@@ -247,15 +247,16 @@ export function formatLearnAppliedReply(params: {
247
247
  targetIds?: string[];
248
248
  setName?: string;
249
249
  }): string {
250
- const scopeLine = params.scope === "global"
251
- ? "- 生效范围:同一钉钉账号下所有会话,将在下一条消息进入时自动加载"
252
- : params.scope === "session"
253
- ? "- 生效范围:当前会话,将在下一条消息进入时自动加载"
254
- : params.scope === "target"
255
- ? `- 生效范围:指定目标 \`${params.targetId}\``
256
- : params.scope === "targets"
257
- ? `- 生效范围:${params.targetIds?.length || 0} 个目标`
258
- : `- 生效范围:目标组 \`${params.setName}\``;
250
+ const scopeLine =
251
+ params.scope === "global"
252
+ ? "- 生效范围:同一钉钉账号下所有会话,将在下一条消息进入时自动加载"
253
+ : params.scope === "session"
254
+ ? "- 生效范围:当前会话,将在下一条消息进入时自动加载"
255
+ : params.scope === "target"
256
+ ? `- 生效范围:指定目标 \`${params.targetId}\``
257
+ : params.scope === "targets"
258
+ ? `- 生效范围:${params.targetIds?.length || 0} 个目标`
259
+ : `- 生效范围:目标组 \`${params.setName}\``;
259
260
  return [
260
261
  params.scope === "global"
261
262
  ? "已注入全局知识。"
@@ -270,7 +271,9 @@ export function formatLearnAppliedReply(params: {
270
271
  params.ruleId ? `- ruleId: \`${params.ruleId}\`` : undefined,
271
272
  `- instruction: ${params.instruction}`,
272
273
  scopeLine,
273
- ].filter(Boolean).join("\n");
274
+ ]
275
+ .filter(Boolean)
276
+ .join("\n");
274
277
  }
275
278
 
276
279
  export function formatLearnListReply(lines: string[]): string {
@@ -298,7 +301,9 @@ export function formatLearnDisabledReply(params: {
298
301
  "已停用规则。",
299
302
  "",
300
303
  `- ruleId: \`${params.ruleId}\``,
301
- params.scope === "target" && params.targetId ? `- scope: target (\`${params.targetId}\`)` : "- scope: global",
304
+ params.scope === "target" && params.targetId
305
+ ? `- scope: target (\`${params.targetId}\`)`
306
+ : "- scope: global",
302
307
  "- 当前效果:这条规则会停止生效,但记录仍会保留,便于后续排查或恢复。",
303
308
  ].join("\n");
304
309
  }
@@ -321,7 +326,9 @@ export function formatLearnDeletedReply(params: {
321
326
  "已删除规则。",
322
327
  "",
323
328
  `- ruleId: \`${params.ruleId}\``,
324
- params.scope === "target" && params.targetId ? `- scope: target (\`${params.targetId}\`)` : "- scope: global",
329
+ params.scope === "target" && params.targetId
330
+ ? `- scope: target (\`${params.targetId}\`)`
331
+ : "- scope: global",
325
332
  ].join("\n");
326
333
  }
327
334
 
@@ -18,7 +18,9 @@ export function parseSessionCommand(text: string | undefined): ParsedSessionComm
18
18
  if (!raw) {
19
19
  return { scope: "unknown" };
20
20
  }
21
- const sessionAliasBindMatch = raw.match(/^\/session-alias\s+(bind|unbind)\s+(direct|group)\s+(\S+)(?:\s+(.+))?$/i);
21
+ const sessionAliasBindMatch = raw.match(
22
+ /^\/session-alias\s+(bind|unbind)\s+(direct|group)\s+(\S+)(?:\s+(.+))?$/i,
23
+ );
22
24
  if (sessionAliasBindMatch) {
23
25
  const action = sessionAliasBindMatch[1]?.toLowerCase();
24
26
  const sourceKind = sessionAliasBindMatch[2]?.toLowerCase() as "direct" | "group";
@@ -1,27 +1,25 @@
1
1
  import { DWClient, TOPIC_CARD, TOPIC_ROBOT } from "dingtalk-stream";
2
2
  import { listAgentIds } from "openclaw/plugin-sdk/agent-runtime";
3
- import { analyzeCardCallback } from "../card-callback-service";
4
- import { finalizeActiveCardsForAccount, recoverPendingCardsForAccount } from "../card-service";
3
+ import {
4
+ deliveryContextFromSession,
5
+ sessionDeliveryChannel,
6
+ sessionDeliveryOrigin,
7
+ sessionDeliveryRoute,
8
+ } from "openclaw/plugin-sdk/session-store-runtime";
9
+ import type { SessionEntry } from "openclaw/plugin-sdk/session-store-runtime";
5
10
  import { recoverAskUserQuestionsForAccount } from "../card/ask-user-question";
6
11
  import { handleCardAction } from "../card/card-action-handler";
7
- import { resolveRobotCode, resolveRuntimeConfig } from "../config";
8
- import { ConnectionManager } from "../connection-manager";
9
- import { isMessageProcessed, markMessageProcessed } from "../dedup";
12
+ import { analyzeCardCallback } from "../card/card-callback-service";
13
+ import { finalizeActiveCardsForAccount, recoverPendingCardsForAccount } from "../card/card-service";
10
14
  import {
11
15
  isLearningAutoApplyEnabled,
12
16
  isLearningEnabled,
13
17
  recordExplicitFeedbackLearning,
14
- } from "../feedback-learning-service";
15
- import { handleDingTalkMessage } from "../inbound-handler";
16
- import { setCurrentLogger } from "../logger-context";
17
- import { registerPeerId } from "../peer-id-registry";
18
- import { getDingTalkRuntime } from "../runtime";
19
- import { sendProactiveTextOrMarkdown } from "../send-service";
20
- import {
21
- listKnownGroupTargets,
22
- listKnownUserTargets,
23
- upsertObservedGroupTarget,
24
- } from "../targeting/target-directory-store";
18
+ } from "../command/feedback-learning-service";
19
+ import { sendProactiveTextOrMarkdown } from "../messaging/send-service";
20
+ import { resolveRobotCode, resolveRuntimeConfig } from "../platform/config";
21
+ import { setCurrentLogger } from "../platform/logger-context";
22
+ import { getDingTalkRuntime } from "../platform/runtime";
25
23
  import type {
26
24
  ConnectionManagerConfig,
27
25
  DingTalkChannelPlugin,
@@ -29,8 +27,9 @@ import type {
29
27
  GatewayStartContext,
30
28
  GatewayStopResult,
31
29
  StreamClientFactory,
32
- } from "../types";
33
- import { ConnectionState } from "../types";
30
+ } from "../platform/types";
31
+ import { ConnectionState } from "../platform/types";
32
+ import { isMessageProcessed, markMessageProcessed } from "../shared/dedup";
34
33
  import {
35
34
  closePluginDebugLog,
36
35
  cleanupOrphanedTempFiles,
@@ -38,7 +37,15 @@ import {
38
37
  formatDingTalkConnectionErrorLog,
39
38
  getCurrentTimestamp,
40
39
  resolvePluginDebugLog,
41
- } from "../utils";
40
+ } from "../shared/utils";
41
+ import { registerPeerId } from "../targeting/peer-id-registry";
42
+ import {
43
+ listKnownGroupTargets,
44
+ listKnownUserTargets,
45
+ upsertObservedGroupTarget,
46
+ } from "../targeting/target-directory-store";
47
+ import { ConnectionManager } from "./connection-manager";
48
+ import { handleDingTalkMessage } from "./inbound-handler";
42
49
 
43
50
  type InstrumentedDWClient = {
44
51
  getEndpoint?: () => Promise<unknown>;
@@ -47,6 +54,82 @@ type InstrumentedDWClient = {
47
54
  dw_url?: string;
48
55
  };
49
56
 
57
+ /**
58
+ * Runtime read of a session entry that may still carry pre-`delivery`
59
+ * delivery fields.
60
+ *
61
+ * Session entries persist `origin` / `lastChannel` / `lastTo` / `lastAccountId`
62
+ * only until `openclaw doctor --fix` migrates them into the canonical
63
+ * `delivery` state, so both shapes can appear when the plugin scans an existing
64
+ * store. The index signature keeps legacy reads without reintroducing the
65
+ * retired fields into typed code paths.
66
+ */
67
+ type SessionEntryWithLegacyDelivery = SessionEntry & Record<string, unknown>;
68
+
69
+ function readLegacySessionString(
70
+ entry: SessionEntryWithLegacyDelivery,
71
+ key: string,
72
+ ): string | undefined {
73
+ const value = entry[key];
74
+ return typeof value === "string" ? value : undefined;
75
+ }
76
+
77
+ function normalizeOptionalSessionString(value: string | undefined): string | undefined {
78
+ return value && value.length > 0 ? value : undefined;
79
+ }
80
+
81
+ function readLegacySessionOrigin(
82
+ entry: SessionEntryWithLegacyDelivery,
83
+ key: "provider" | "surface" | "accountId" | "from" | "to",
84
+ ): string | undefined {
85
+ const origin = entry.origin;
86
+ if (!origin || typeof origin !== "object") {
87
+ return undefined;
88
+ }
89
+ const value = (origin as Record<string, unknown>)[key];
90
+ return typeof value === "string" ? value : undefined;
91
+ }
92
+
93
+ /** Reads one session entry's delivery identity across canonical and legacy shapes. */
94
+ function readSessionDeliveryIdentity(entry: SessionEntry): {
95
+ channels: Array<string | undefined>;
96
+ accountId: string | undefined;
97
+ peerIds: Array<string | undefined>;
98
+ } {
99
+ const legacy = entry as SessionEntryWithLegacyDelivery;
100
+ const origin = sessionDeliveryOrigin(entry);
101
+ const context = deliveryContextFromSession(entry);
102
+ const route = sessionDeliveryRoute(entry);
103
+ return {
104
+ channels: [
105
+ sessionDeliveryChannel(entry),
106
+ route?.channel,
107
+ context?.channel,
108
+ readLegacySessionString(legacy, "lastChannel"),
109
+ readLegacySessionString(legacy, "channel"),
110
+ origin?.provider,
111
+ origin?.surface,
112
+ readLegacySessionOrigin(legacy, "provider"),
113
+ readLegacySessionOrigin(legacy, "surface"),
114
+ ],
115
+ // Canonical `delivery.context` wins, then the pre-`delivery` fields. Empty
116
+ // strings fall through so the chain matches the original `||` semantics.
117
+ accountId:
118
+ normalizeOptionalSessionString(context?.accountId) ??
119
+ normalizeOptionalSessionString(readLegacySessionString(legacy, "lastAccountId")) ??
120
+ normalizeOptionalSessionString(origin?.accountId) ??
121
+ normalizeOptionalSessionString(readLegacySessionOrigin(legacy, "accountId")),
122
+ peerIds: [
123
+ context?.to,
124
+ readLegacySessionString(legacy, "lastTo"),
125
+ origin?.from,
126
+ origin?.to,
127
+ readLegacySessionOrigin(legacy, "from"),
128
+ readLegacySessionOrigin(legacy, "to"),
129
+ ],
130
+ };
131
+ }
132
+
50
133
  function attachConnectionErrorContext(
51
134
  err: unknown,
52
135
  stage: "connect.open" | "connect.websocket",
@@ -205,17 +288,12 @@ export function createDingTalkGateway(): NonNullable<DingTalkChannelPlugin["gate
205
288
  storePath: sessionStorePath,
206
289
  readConsistency: "latest",
207
290
  })) {
208
- const isDingTalkSession = [
209
- entry.lastChannel,
210
- entry.channel,
211
- entry.origin?.provider,
212
- entry.origin?.surface,
213
- ].some((value) => value === "dingtalk");
214
- const sessionAccountId = entry.lastAccountId || entry.origin?.accountId;
215
- if (!isDingTalkSession || sessionAccountId !== account.accountId) {
291
+ const identity = readSessionDeliveryIdentity(entry);
292
+ const isDingTalkSession = identity.channels.some((value) => value === "dingtalk");
293
+ if (!isDingTalkSession || identity.accountId !== account.accountId) {
216
294
  continue;
217
295
  }
218
- for (const peerId of [entry.lastTo, entry.origin?.from, entry.origin?.to]) {
296
+ for (const peerId of identity.peerIds) {
219
297
  if (typeof peerId !== "string" || !peerId.startsWith("cid")) {
220
298
  continue;
221
299
  }
@@ -16,8 +16,8 @@ import type {
16
16
  ConnectionAttemptResult,
17
17
  Logger,
18
18
  StreamClientFactory,
19
- } from "./types";
20
- import { ConnectionState as ConnectionStateEnum } from "./types";
19
+ } from "../platform/types";
20
+ import { ConnectionState as ConnectionStateEnum } from "../platform/types";
21
21
 
22
22
  /**
23
23
  * Thrown when a runtime reconnection cycle exceeds the configured deadline.
@@ -98,7 +98,13 @@ export class ConnectionManager {
98
98
  // Old client pending cleanup after a warm reconnect swap.
99
99
  private pendingOldClient?: DWClient;
100
100
 
101
- constructor(client: DWClient, accountId: string, config: ConnectionManagerConfig, log?: Logger, clientFactory?: StreamClientFactory) {
101
+ constructor(
102
+ client: DWClient,
103
+ accountId: string,
104
+ config: ConnectionManagerConfig,
105
+ log?: Logger,
106
+ clientFactory?: StreamClientFactory,
107
+ ) {
102
108
  this.client = client;
103
109
  this.accountId = accountId;
104
110
  this.config = config;
@@ -148,17 +154,17 @@ export class ConnectionManager {
148
154
  }
149
155
 
150
156
  const now = Date.now();
151
- const idleMs = this.lastSocketActivityAt !== undefined
152
- ? now - this.lastSocketActivityAt
153
- : ConnectionManager.HEARTBEAT_INTERVAL_MS;
157
+ const idleMs =
158
+ this.lastSocketActivityAt !== undefined
159
+ ? now - this.lastSocketActivityAt
160
+ : ConnectionManager.HEARTBEAT_INTERVAL_MS;
154
161
  if (idleMs >= ConnectionManager.HEARTBEAT_INTERVAL_MS) {
155
162
  this.consecutiveHeartbeatMisses += 1;
156
163
  this.runtimeCounters.heartbeatMisses += 1;
157
164
 
158
165
  if (this.consecutiveHeartbeatMisses >= ConnectionManager.HEARTBEAT_MISS_THRESHOLD) {
159
- const lastPingAgoMs = this.lastHeartbeatPingAt !== undefined
160
- ? now - this.lastHeartbeatPingAt
161
- : undefined;
166
+ const lastPingAgoMs =
167
+ this.lastHeartbeatPingAt !== undefined ? now - this.lastHeartbeatPingAt : undefined;
162
168
  this.log?.warn?.(
163
169
  `[${this.accountId}] Connection heartbeat missed ${this.consecutiveHeartbeatMisses}/${ConnectionManager.HEARTBEAT_MISS_THRESHOLD} checks, triggering reconnection (lastPingAgoMs=${lastPingAgoMs ?? "n/a"})`,
164
170
  );
@@ -178,9 +184,7 @@ export class ConnectionManager {
178
184
  try {
179
185
  socket.ping("", true);
180
186
  } catch (err: any) {
181
- this.log?.warn?.(
182
- `[${this.accountId}] Failed to send heartbeat ping: ${err.message}`,
183
- );
187
+ this.log?.warn?.(`[${this.accountId}] Failed to send heartbeat ping: ${err.message}`);
184
188
  }
185
189
  }, ConnectionManager.HEARTBEAT_INTERVAL_MS);
186
190
  }
@@ -211,9 +215,10 @@ export class ConnectionManager {
211
215
  }
212
216
 
213
217
  const defaultOpenTimeout = 10_000;
214
- const openTimeout = this.reconnectDeadline !== undefined
215
- ? Math.min(defaultOpenTimeout, Math.max(1000, this.reconnectDeadline - Date.now()))
216
- : defaultOpenTimeout;
218
+ const openTimeout =
219
+ this.reconnectDeadline !== undefined
220
+ ? Math.min(defaultOpenTimeout, Math.max(1000, this.reconnectDeadline - Date.now()))
221
+ : defaultOpenTimeout;
217
222
 
218
223
  await new Promise<void>((resolve, reject) => {
219
224
  const timeout = setTimeout(() => {
@@ -226,9 +231,18 @@ export class ConnectionManager {
226
231
  socket.removeListener("error", onError);
227
232
  socket.removeListener("close", onClose);
228
233
  };
229
- const onOpen = () => { cleanup(); resolve(); };
230
- const onError = (err: Error) => { cleanup(); reject(err); };
231
- const onClose = () => { cleanup(); reject(new Error("Socket closed before open")); };
234
+ const onOpen = () => {
235
+ cleanup();
236
+ resolve();
237
+ };
238
+ const onError = (err: Error) => {
239
+ cleanup();
240
+ reject(err);
241
+ };
242
+ const onClose = () => {
243
+ cleanup();
244
+ reject(new Error("Socket closed before open"));
245
+ };
232
246
  socket.once("open", onOpen);
233
247
  socket.once("error", onError);
234
248
  socket.once("close", onClose);
@@ -370,7 +384,11 @@ export class ConnectionManager {
370
384
  // Warm-reconnect failed with new client: revert to old client so
371
385
  // subsequent retry attempts don't keep creating throwaway instances.
372
386
  if (this.pendingOldClient) {
373
- try { this.client.disconnect(); } catch { /* best-effort cleanup of failed new client */ }
387
+ try {
388
+ this.client.disconnect();
389
+ } catch {
390
+ /* best-effort cleanup of failed new client */
391
+ }
374
392
  this.client = this.pendingOldClient;
375
393
  this.pendingOldClient = undefined;
376
394
  this.log?.debug?.(
@@ -509,10 +527,7 @@ export class ConnectionManager {
509
527
 
510
528
  this.consecutiveUnhealthyChecks += 1;
511
529
  this.runtimeCounters.healthUnhealthyChecks += 1;
512
- if (
513
- this.consecutiveUnhealthyChecks <
514
- ConnectionManager.HEALTH_CHECK_UNHEALTHY_THRESHOLD
515
- ) {
530
+ if (this.consecutiveUnhealthyChecks < ConnectionManager.HEALTH_CHECK_UNHEALTHY_THRESHOLD) {
516
531
  this.log?.debug?.(
517
532
  `[${this.accountId}] Connection health check unhealthy (${this.consecutiveUnhealthyChecks}/${ConnectionManager.HEALTH_CHECK_UNHEALTHY_THRESHOLD}) connected=${String(client.connected)} registered=${String(registered)} socketReadyState=${socketReadyState ?? "unknown"}`,
518
533
  );
@@ -723,7 +738,9 @@ export class ConnectionManager {
723
738
  if (err instanceof ReconnectDeadlineError) {
724
739
  this.consecutiveDeadlineTimeouts += 1;
725
740
 
726
- if (this.consecutiveDeadlineTimeouts >= ConnectionManager.MAX_CONSECUTIVE_DEADLINE_TIMEOUTS) {
741
+ if (
742
+ this.consecutiveDeadlineTimeouts >= ConnectionManager.MAX_CONSECUTIVE_DEADLINE_TIMEOUTS
743
+ ) {
727
744
  this.log?.error?.(
728
745
  `[${this.accountId}] Max consecutive deadline timeouts (${ConnectionManager.MAX_CONSECUTIVE_DEADLINE_TIMEOUTS}) reached. Giving up.`,
729
746
  );
@@ -740,10 +757,7 @@ export class ConnectionManager {
740
757
 
741
758
  const deadlineMs = this.config.reconnectDeadlineMs ?? 50000;
742
759
  this.reconnectDeadline = Date.now() + deadlineMs;
743
- const delay = Math.min(
744
- this.calculateNextDelay(0),
745
- ConnectionManager.MAX_CYCLE_BACKOFF_MS,
746
- );
760
+ const delay = Math.min(this.calculateNextDelay(0), ConnectionManager.MAX_CYCLE_BACKOFF_MS);
747
761
  this.attemptCount = 0;
748
762
  this.clearReconnectTimer();
749
763
  this.log?.warn?.(
@@ -756,12 +770,13 @@ export class ConnectionManager {
756
770
  }
757
771
 
758
772
  this.runtimeReconnectCycles += 1;
759
- const maxCycles = this.config.maxReconnectCycles ?? ConnectionManager.DEFAULT_MAX_RECONNECT_CYCLES;
773
+ const maxCycles =
774
+ this.config.maxReconnectCycles ?? ConnectionManager.DEFAULT_MAX_RECONNECT_CYCLES;
760
775
 
761
776
  if (this.runtimeReconnectCycles >= maxCycles) {
762
777
  this.log?.error?.(
763
778
  `[${this.accountId}] Max runtime reconnect cycles (${maxCycles}) reached. Giving up. ` +
764
- `Please check network connectivity or restart the gateway.`,
779
+ `Please check network connectivity or restart the gateway.`,
765
780
  );
766
781
  this.state = ConnectionStateEnum.FAILED;
767
782
  this.connectedAt = undefined;
@@ -1,8 +1,8 @@
1
- import axios, { DEFAULT_HTTP_TIMEOUT_MS } from "./http-client";
2
- import { getAccessToken } from "./auth";
3
- import { getLogger } from "./logger-context";
4
- import { getProxyBypassOption } from "./utils";
5
- import type { DingTalkConfig, DocInfo, Logger } from "./types";
1
+ import { getAccessToken } from "../platform/auth";
2
+ import { getLogger } from "../platform/logger-context";
3
+ import type { DingTalkConfig, DocInfo, Logger } from "../platform/types";
4
+ import axios, { DEFAULT_HTTP_TIMEOUT_MS } from "../shared/http-client";
5
+ import { getProxyBypassOption } from "../shared/utils";
6
6
 
7
7
  const DINGTALK_API = "https://api.dingtalk.com";
8
8