@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
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Cross-domain runtime agent-event infrastructure.
3
+ *
4
+ * Owns the generic OpenClaw `events.onAgentEvent` surface that several
5
+ * domains consume: event typing, reference-counted fan-out, field accessors,
6
+ * and run correlation. Domain owners (ack reactions, AI card task progress)
7
+ * build their own behavior on top of these primitives instead of importing
8
+ * each other's private modules.
9
+ */
10
+ export type RuntimeEventsLogger = {
11
+ debug?: (msg: string) => void;
12
+ info?: (msg: string) => void;
13
+ warn?: (msg: string) => void;
14
+ };
15
+ export type RuntimeAgentEvent = {
16
+ stream?: string;
17
+ runId?: string;
18
+ sessionKey?: string;
19
+ data?: {
20
+ phase?: string;
21
+ name?: string;
22
+ args?: unknown;
23
+ runId?: string;
24
+ sessionKey?: string;
25
+ toolCallId?: string;
26
+ meta?: {
27
+ runId?: string;
28
+ sessionKey?: string;
29
+ } | null;
30
+ };
31
+ };
32
+ export type RuntimeEventsSurface = {
33
+ onAgentEvent?: (listener: (event: unknown) => void) => () => void;
34
+ };
35
+ /**
36
+ * Fan one upstream `onAgentEvent` subscription out to multiple local consumers.
37
+ *
38
+ * The upstream subscription is reference-counted: it is created with the first
39
+ * local listener and released with the last one. Each listener is isolated so a
40
+ * throwing consumer can neither starve the remaining consumers nor bubble back
41
+ * into the host event emitter.
42
+ */
43
+ export declare function createRuntimeEventsFanout(upstream: RuntimeEventsSurface | undefined, options?: {
44
+ log?: RuntimeEventsLogger;
45
+ }): RuntimeEventsSurface;
46
+ export declare function getEventRunId(event: RuntimeAgentEvent | undefined): string | undefined;
47
+ export declare function getEventSessionKey(event: RuntimeAgentEvent | undefined): string | undefined;
48
+ export declare function describeEvent(event: RuntimeAgentEvent | undefined): string;
49
+ /**
50
+ * Build a predicate that decides whether an agent event belongs to one reply.
51
+ *
52
+ * Correlation prefers an already captured `runId`; otherwise it accepts events
53
+ * whose `sessionKey` matches, and finally falls back to optimistically
54
+ * capturing the first session-less lifecycle start inside a short window.
55
+ *
56
+ * `consumer` labels the debug logs so two consumers sharing the same session
57
+ * (ack reactions and card task progress) stay distinguishable while triaging.
58
+ */
59
+ export declare function createAgentEventCorrelator(params: {
60
+ /** Short label identifying the consuming domain in debug logs. */
61
+ consumer: string;
62
+ sessionKey: string;
63
+ enabled: boolean;
64
+ createdAt: number;
65
+ optimisticCaptureWindowMs: number;
66
+ log?: RuntimeEventsLogger;
67
+ }): (event: RuntimeAgentEvent | undefined) => boolean;
@@ -8,14 +8,39 @@
8
8
  * - Media files and streams
9
9
  * - Session and token management
10
10
  */
11
- import type { ChannelAccountSnapshot as SDKChannelAccountSnapshot, ChannelGatewayContext as SDKChannelGatewayContext, ChannelLogSink as SDKChannelLogSink } from "openclaw/plugin-sdk/channel-runtime";
11
+ import type { ChannelAccountSnapshot as SDKChannelAccountSnapshot, ChannelGatewayContext as SDKChannelGatewayContext, ChannelLogSink as SDKChannelLogSink } from "openclaw/plugin-sdk/channel-contract";
12
12
  import type { ChannelPlugin as SDKChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk/core";
13
13
  import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
14
14
  import type { SecretInput } from "./secret-input";
15
15
  export type AckReactionMode = "off" | "emoji" | "kaomoji";
16
16
  export type AckReactionConfigValue = string;
17
17
  export type CardStreamingMode = "off" | "answer" | "all";
18
+ /** How tool-driven task-progress changes reach the card:
19
+ * - `heartbeat` (default): coalesced into the 30s progress heartbeat
20
+ * - `interval`: pushed on every correlated tool event, throttled by `cardStreamInterval` */
21
+ export type CardTaskProgressRefresh = "heartbeat" | "interval";
18
22
  export type ContextVisibilityMode = "all" | "allowlist" | "allowlist_quote";
23
+ /**
24
+ * Gateway RPC capability configuration (Issue #608, 问题 3).
25
+ * Aligns with the official connector's `tools: { docs, media }` gate shape.
26
+ * All capabilities default to enabled for backward compatibility.
27
+ */
28
+ export interface DingTalkGatewayCapabilitiesConfig {
29
+ tools?: {
30
+ /** Enable `dingtalk.docs.*` and `dingtalk-connector.docs.*` Gateway RPCs (default: true) */
31
+ docs?: boolean;
32
+ /** Enable proactive-send Gateway RPCs: `dingtalk-connector.sendToUser/sendToGroup/send` (default: true) */
33
+ proactiveSend?: boolean;
34
+ };
35
+ /** Optional doc-space allowlist; when set, docs RPCs only accept these `spaceId` values */
36
+ docs?: {
37
+ allowedSpaceIds?: string[];
38
+ };
39
+ /** Optional proactive-send target allowlist; entries must be `user:*` or `group:*` */
40
+ send?: {
41
+ allowedTargets?: string[];
42
+ };
43
+ }
19
44
  /**
20
45
  * DingTalk channel configuration (extends base OpenClaw config)
21
46
  */
@@ -70,10 +95,23 @@ export interface DingTalkConfig extends OpenClawConfig {
70
95
  * - answer: stream answer text
71
96
  * - all: stream answer + reasoning text */
72
97
  cardStreamingMode?: CardStreamingMode;
98
+ /** Internal derived flag, not a user-facing config key: whether
99
+ * `cardStreamingMode` came from an explicit user setting instead of the
100
+ * runtime default. Written by config normalization only. */
101
+ cardStreamingModeConfigured?: boolean;
73
102
  /** @deprecated Use `cardStreamingMode` instead. */
74
103
  cardRealTimeStream?: boolean;
75
104
  /** Throttle interval in ms for card stream updates (default 1000) */
76
105
  cardStreamInterval?: number;
106
+ /** Live "task in progress" block on AI cards for long-running tasks.
107
+ * `true` forces it on, `false` forces it off; when unset it is on unless
108
+ * `cardStreamingMode` is explicitly `"off"`. */
109
+ cardTaskProgress?: boolean;
110
+ /** How stage/step changes reach the progress block. `heartbeat` (default)
111
+ * keeps the documented "once on appearance, then once per 30s" budget;
112
+ * `interval` pushes on every correlated tool event, throttled by
113
+ * `cardStreamInterval`, trading card updates for a fresher stage label. */
114
+ cardTaskProgressRefresh?: CardTaskProgressRefresh;
77
115
  /** AICard degrade duration in milliseconds after trigger errors (default 30m) */
78
116
  aicardDegradeMs?: number;
79
117
  /** Enable local learning loop (events/reflections/session notes/global rules) */
@@ -95,6 +133,8 @@ export interface DingTalkConfig extends OpenClawConfig {
95
133
  tokens?: boolean;
96
134
  dapiUsage?: boolean;
97
135
  };
136
+ /** Gateway RPC capability gates and allowlists (docs RPCs + proactive-send RPCs). Named `gatewayCapabilities` because host `OpenClawConfig` already owns `gateway` and `tools`, and a bare `capabilities` key would collide with this plugin's channel capabilities metadata. */
137
+ gatewayCapabilities?: DingTalkGatewayCapabilitiesConfig;
98
138
  }
99
139
  /**
100
140
  * Multi-account DingTalk configuration wrapper
@@ -150,10 +190,23 @@ export interface DingTalkChannelConfig {
150
190
  * - answer: stream answer text
151
191
  * - all: stream answer + reasoning text */
152
192
  cardStreamingMode?: CardStreamingMode;
193
+ /** Internal derived flag, not a user-facing config key: whether
194
+ * `cardStreamingMode` came from an explicit user setting instead of the
195
+ * runtime default. Written by config normalization only. */
196
+ cardStreamingModeConfigured?: boolean;
153
197
  /** @deprecated Use `cardStreamingMode` instead. */
154
198
  cardRealTimeStream?: boolean;
155
199
  /** Throttle interval in ms for card stream updates (default 1000) */
156
200
  cardStreamInterval?: number;
201
+ /** Live "task in progress" block on AI cards for long-running tasks.
202
+ * `true` forces it on, `false` forces it off; when unset it is on unless
203
+ * `cardStreamingMode` is explicitly `"off"`. */
204
+ cardTaskProgress?: boolean;
205
+ /** How stage/step changes reach the progress block. `heartbeat` (default)
206
+ * keeps the documented "once on appearance, then once per 30s" budget;
207
+ * `interval` pushes on every correlated tool event, throttled by
208
+ * `cardStreamInterval`, trading card updates for a fresher stage label. */
209
+ cardTaskProgressRefresh?: CardTaskProgressRefresh;
157
210
  /** AICard degrade duration in milliseconds after trigger errors (default 30m) */
158
211
  aicardDegradeMs?: number;
159
212
  /** Enable local learning loop (events/reflections/session notes/global rules) */
@@ -166,6 +219,8 @@ export interface DingTalkChannelConfig {
166
219
  convertMarkdownTables?: boolean;
167
220
  /** @mention the sender after card finalization in group chats; value is the message text */
168
221
  cardAtSender?: string;
222
+ /** Gateway RPC capability gates and allowlists (docs RPCs + proactive-send RPCs). Named `gatewayCapabilities` because host `OpenClawConfig` already owns `gateway` and `tools`, and a bare `capabilities` key would collide with this plugin's channel capabilities metadata. */
223
+ gatewayCapabilities?: DingTalkGatewayCapabilitiesConfig;
169
224
  }
170
225
  /**
171
226
  * DingTalk token info for caching
@@ -211,6 +266,16 @@ export interface DocInfo {
211
266
  creatorId?: string;
212
267
  updatedAt?: number | string;
213
268
  }
269
+ /**
270
+ * Recursive element node of an interactiveCard `cardContent` payload.
271
+ * Real DingTalk quote callbacks carry card text as nested element trees
272
+ * (e.g. LIST -> RICHTEXT -> TEXT nodes) rather than a flat `text` field.
273
+ */
274
+ export interface DingTalkCardContentNode {
275
+ elementType?: string;
276
+ value?: string;
277
+ children?: DingTalkCardContentNode[];
278
+ }
214
279
  /**
215
280
  * DingTalk incoming message (Stream mode)
216
281
  */
@@ -236,9 +301,13 @@ export interface DingTalkInboundMessage {
236
301
  createdAt?: number;
237
302
  content?: {
238
303
  text?: string;
304
+ /** Quoted text message body in real payloads (`msgType: "text"`). */
305
+ content?: string;
239
306
  downloadCode?: string;
240
307
  fileName?: string;
241
308
  biz_custom_action_url?: string;
309
+ /** interactiveCard quoted content as nested element tree. */
310
+ cardContent?: DingTalkCardContentNode[];
242
311
  richText?: Array<{
243
312
  msgType?: string;
244
313
  type?: string;
@@ -386,7 +455,11 @@ export interface SendMessageOptions {
386
455
  /** Force markdown/text delivery even when messageType is "card". Bypasses card
387
456
  * creation while preserving journal writes and other side-effects. */
388
457
  forceMarkdown?: boolean;
389
- /** Allowed local roots for sandbox/container media path resolution. */
458
+ /**
459
+ * Host-provided allowlist of local media roots. When set, caller-supplied
460
+ * host paths whose canonical location is outside these roots are resolved
461
+ * through the runtime media bridge instead of being read directly.
462
+ */
390
463
  mediaLocalRoots?: string[];
391
464
  }
392
465
  export interface DingTalkTrackingMetadata {
@@ -1,4 +1,4 @@
1
- import type { Logger } from "./types";
1
+ import type { Logger } from "../platform/types";
2
2
  type NamespaceFormat = "json";
3
3
  export interface PersistenceScope {
4
4
  accountId?: string;
@@ -1,7 +1,7 @@
1
1
  import * as dns from "node:dns";
2
2
  import * as fs from "node:fs";
3
3
  import * as net from "node:net";
4
- import type { Logger, RetryOptions } from "./types";
4
+ import type { Logger, RetryOptions } from "../platform/types";
5
5
  type PluginDebugLogParams = {
6
6
  accountId: string;
7
7
  storePath?: string;
@@ -4,7 +4,7 @@
4
4
  * Matches @mentions to agent IDs based on name and id fields in agents.list config.
5
5
  */
6
6
  import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
7
- import type { AtMention, AgentNameMatch } from "../types";
7
+ import type { AtMention, AgentNameMatch } from "../platform/types";
8
8
  interface AgentConfig {
9
9
  id: string;
10
10
  name?: string;
@@ -7,9 +7,9 @@
7
7
  * (channel + accountId + peer), not content-based dynamic routing.
8
8
  */
9
9
  import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
10
- import { getDingTalkRuntime } from "../runtime";
11
- import type { AgentNameMatch, DingTalkConfig, DingTalkInboundMessage, HandleDingTalkMessageParams, Logger, MessageContent, ResolvedDingTalkRoute } from "../types";
12
- import type { ResolvedDingTalkSessionPeer } from "../session-routing";
10
+ import { getDingTalkRuntime } from "../platform/runtime";
11
+ import type { AgentNameMatch, DingTalkConfig, DingTalkInboundMessage, HandleDingTalkMessageParams, Logger, MessageContent, ResolvedDingTalkRoute } from "../platform/types";
12
+ import type { ResolvedDingTalkSessionPeer } from "./session-routing";
13
13
  export declare class HostRoutingHelperUnavailableError extends Error {
14
14
  constructor(message?: string);
15
15
  }
@@ -1,4 +1,4 @@
1
- import type { DingTalkConfig } from "./types";
1
+ import type { DingTalkConfig } from "../platform/types";
2
2
  export interface ResolveDingTalkSessionPeerParams {
3
3
  isDirect: boolean;
4
4
  senderId: string;
package/index.ts CHANGED
@@ -4,26 +4,106 @@ import {
4
4
  type OpenClawPluginDefinition,
5
5
  } from "openclaw/plugin-sdk/core";
6
6
  import { readStringParam } from "openclaw/plugin-sdk/param-readers";
7
- import { getAccessToken } from "./src/auth";
8
7
  import { registerDingTalkAskUserQuestionTool } from "./src/card/ask-user-question";
8
+ import { accumulateUsage } from "./src/card/run-usage-store";
9
9
  import { dingtalkPlugin } from "./src/channel";
10
- import { getConfig, listDingTalkAccountIds, resolveDingTalkAccount } from "./src/config";
11
10
  import {
12
11
  appendToDoc,
13
12
  createDoc,
14
13
  DocCreateAppendError,
15
14
  listDocs,
16
15
  searchDocs,
17
- } from "./src/docs-service";
18
- import { accumulateUsage } from "./src/run-usage-store";
19
- import { setDingTalkRuntime } from "./src/runtime";
20
- import { sendMessage } from "./src/send-service";
16
+ } from "./src/gateway/docs-service";
17
+ import { sendMessage } from "./src/messaging/send-service";
18
+ import { getAccessToken } from "./src/platform/auth";
19
+ import {
20
+ checkDocsGatewayCapability,
21
+ checkProactiveSendGatewayCapability,
22
+ getConfig,
23
+ listDingTalkAccountIds,
24
+ PROACTIVE_SEND_GATE_DISABLED_REASON,
25
+ resolveDingTalkAccount,
26
+ resolveGatewayCapabilityConfig,
27
+ } from "./src/platform/config";
28
+ import { setDingTalkRuntime } from "./src/platform/runtime";
21
29
 
22
30
  type GatewayMethodContext = Pick<
23
31
  Parameters<Parameters<OpenClawPluginApi["registerGatewayMethod"]>[1]>[0],
24
32
  "context" | "params" | "respond"
25
33
  >;
26
34
 
35
+ type GatewayMethodArgs = Parameters<Parameters<OpenClawPluginApi["registerGatewayMethod"]>[1]>[0];
36
+
37
+ /**
38
+ * Respond with a capability denial for a Gateway RPC call.
39
+ * Logged under `[DingTalk][GatewayRPC][Denied]` for fast audit diagnosis.
40
+ */
41
+ function denyGatewayCapability(
42
+ api: OpenClawPluginApi,
43
+ method: string,
44
+ reason: string,
45
+ respond: GatewayMethodContext["respond"],
46
+ ): ReturnType<GatewayMethodContext["respond"]> {
47
+ api.logger?.warn?.(`[DingTalk][GatewayRPC][Denied] ${method}: ${reason}`);
48
+ return respond(false, { error: reason });
49
+ }
50
+
51
+ /**
52
+ * Wrap a docs Gateway RPC handler with the `gatewayCapabilities.tools.docs` capability gate
53
+ * and the optional `gatewayCapabilities.docs.allowedSpaceIds` allowlist (Issue #608, 问题 3).
54
+ * `dingtalk.docs.*` and `dingtalk-connector.docs.*` aliases share handlers, so
55
+ * the gate automatically applies to both namespaces.
56
+ */
57
+ function withDocsGatewayCapability(
58
+ api: OpenClawPluginApi,
59
+ method: string,
60
+ handler: (args: GatewayMethodArgs) => Promise<void>,
61
+ ): (args: GatewayMethodArgs) => Promise<void> {
62
+ return async (args: GatewayMethodArgs) => {
63
+ const { respond, params } = args;
64
+ const accountId = readStringParam(params, "accountId");
65
+ const caps = resolveGatewayCapabilityConfig(api.config, accountId ?? undefined);
66
+ // `checkDocsGatewayCapability` evaluates the capability switch first, so a
67
+ // disabled tool answers with a structured deny before any method params
68
+ // (all optional for docs RPCs) are inspected.
69
+ const spaceId = readStringParam(params, "spaceId") ?? undefined;
70
+ const denial = checkDocsGatewayCapability(caps, spaceId);
71
+ if (denial) {
72
+ return denyGatewayCapability(api, method, denial, respond);
73
+ }
74
+ return handler(args);
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Wrap a proactive-send Gateway RPC handler with the `gatewayCapabilities.tools.proactiveSend`
80
+ * capability gate and the optional `gatewayCapabilities.send.allowedTargets` allowlist
81
+ * (Issue #608, 问题 3).
82
+ */
83
+ function withProactiveSendGatewayCapability(
84
+ api: OpenClawPluginApi,
85
+ method: string,
86
+ resolveTarget: (params: Record<string, unknown>) => string | undefined,
87
+ handler: (args: GatewayMethodArgs, target: string) => Promise<void>,
88
+ ): (args: GatewayMethodArgs) => Promise<void> {
89
+ return async (args: GatewayMethodArgs) => {
90
+ const { respond, params } = args;
91
+ const accountId = readStringParam(params, "accountId");
92
+ const caps = resolveGatewayCapabilityConfig(api.config, accountId ?? undefined);
93
+ // Fast-path the capability switch before resolving required params so a
94
+ // disabled tool answers with a structured deny rather than a param error.
95
+ if (!caps.proactiveSendEnabled) {
96
+ return denyGatewayCapability(api, method, PROACTIVE_SEND_GATE_DISABLED_REASON, respond);
97
+ }
98
+ const target = resolveTarget(params) ?? "";
99
+ const denial = checkProactiveSendGatewayCapability(caps, target);
100
+ if (denial) {
101
+ return denyGatewayCapability(api, method, denial, respond);
102
+ }
103
+ return handler(args, target);
104
+ };
105
+ }
106
+
27
107
  /**
28
108
  * Register the canonical OpenClaw DingTalk docs RPC namespace.
29
109
  *
@@ -93,14 +173,38 @@ function registerDingTalkDocsGatewayMethods(api: OpenClawPluginApi): void {
93
173
  return respond(true, { docs });
94
174
  };
95
175
 
96
- api.registerGatewayMethod("dingtalk.docs.create", createHandler);
97
- api.registerGatewayMethod("dingtalk.docs.append", appendHandler);
98
- api.registerGatewayMethod("dingtalk.docs.search", searchHandler);
99
- api.registerGatewayMethod("dingtalk.docs.list", listHandler);
100
- api.registerGatewayMethod("dingtalk-connector.docs.create", createHandler);
101
- api.registerGatewayMethod("dingtalk-connector.docs.append", appendHandler);
102
- api.registerGatewayMethod("dingtalk-connector.docs.search", searchHandler);
103
- api.registerGatewayMethod("dingtalk-connector.docs.list", listHandler);
176
+ api.registerGatewayMethod(
177
+ "dingtalk.docs.create",
178
+ withDocsGatewayCapability(api, "dingtalk.docs.create", createHandler),
179
+ );
180
+ api.registerGatewayMethod(
181
+ "dingtalk.docs.append",
182
+ withDocsGatewayCapability(api, "dingtalk.docs.append", appendHandler),
183
+ );
184
+ api.registerGatewayMethod(
185
+ "dingtalk.docs.search",
186
+ withDocsGatewayCapability(api, "dingtalk.docs.search", searchHandler),
187
+ );
188
+ api.registerGatewayMethod(
189
+ "dingtalk.docs.list",
190
+ withDocsGatewayCapability(api, "dingtalk.docs.list", listHandler),
191
+ );
192
+ api.registerGatewayMethod(
193
+ "dingtalk-connector.docs.create",
194
+ withDocsGatewayCapability(api, "dingtalk-connector.docs.create", createHandler),
195
+ );
196
+ api.registerGatewayMethod(
197
+ "dingtalk-connector.docs.append",
198
+ withDocsGatewayCapability(api, "dingtalk-connector.docs.append", appendHandler),
199
+ );
200
+ api.registerGatewayMethod(
201
+ "dingtalk-connector.docs.search",
202
+ withDocsGatewayCapability(api, "dingtalk-connector.docs.search", searchHandler),
203
+ );
204
+ api.registerGatewayMethod(
205
+ "dingtalk-connector.docs.list",
206
+ withDocsGatewayCapability(api, "dingtalk-connector.docs.list", listHandler),
207
+ );
104
208
  }
105
209
 
106
210
  function getContentParam(params: Record<string, unknown>): string | undefined {
@@ -176,68 +280,88 @@ async function sendGatewayMessage(params: {
176
280
  function registerDingTalkConnectorCompatibilityGatewayMethods(api: OpenClawPluginApi): void {
177
281
  api.registerGatewayMethod(
178
282
  "dingtalk-connector.sendToUser",
179
- async ({ context, respond, params }: GatewayMethodContext) => {
180
- const accountId = readStringParam(params, "accountId");
181
- const userId = readStringParam(params, "userId", { required: true });
182
- const content = getContentParam(params);
183
- if (!content) {
184
- return respond(false, { error: "content or message is required" });
185
- }
186
- return sendGatewayMessage({
187
- api,
188
- respond,
189
- accountId: accountId ?? undefined,
190
- target: `user:${userId}`,
191
- content,
192
- storePath: context?.cronStorePath,
193
- useAICard: params.useAICard,
194
- });
195
- },
283
+ withProactiveSendGatewayCapability(
284
+ api,
285
+ "dingtalk-connector.sendToUser",
286
+ (params) => {
287
+ const userId = readStringParam(params, "userId", { required: true });
288
+ return userId ? `user:${userId}` : undefined;
289
+ },
290
+ async ({ context, respond, params }, target) => {
291
+ const accountId = readStringParam(params, "accountId");
292
+ const content = getContentParam(params);
293
+ if (!content) {
294
+ return respond(false, { error: "content or message is required" });
295
+ }
296
+ return sendGatewayMessage({
297
+ api,
298
+ respond,
299
+ accountId: accountId ?? undefined,
300
+ target,
301
+ content,
302
+ storePath: context?.cronStorePath,
303
+ useAICard: params.useAICard,
304
+ });
305
+ },
306
+ ),
196
307
  );
197
308
 
198
309
  api.registerGatewayMethod(
199
310
  "dingtalk-connector.sendToGroup",
200
- async ({ context, respond, params }: GatewayMethodContext) => {
201
- const accountId = readStringParam(params, "accountId");
202
- const openConversationId = readStringParam(params, "openConversationId", { required: true });
203
- const content = getContentParam(params);
204
- if (!content) {
205
- return respond(false, { error: "content or message is required" });
206
- }
207
- return sendGatewayMessage({
208
- api,
209
- respond,
210
- accountId: accountId ?? undefined,
211
- target: `group:${openConversationId}`,
212
- content,
213
- storePath: context?.cronStorePath,
214
- useAICard: params.useAICard,
215
- });
216
- },
311
+ withProactiveSendGatewayCapability(
312
+ api,
313
+ "dingtalk-connector.sendToGroup",
314
+ (params) => {
315
+ const openConversationId = readStringParam(params, "openConversationId", {
316
+ required: true,
317
+ });
318
+ return openConversationId ? `group:${openConversationId}` : undefined;
319
+ },
320
+ async ({ context, respond, params }, target) => {
321
+ const accountId = readStringParam(params, "accountId");
322
+ const content = getContentParam(params);
323
+ if (!content) {
324
+ return respond(false, { error: "content or message is required" });
325
+ }
326
+ return sendGatewayMessage({
327
+ api,
328
+ respond,
329
+ accountId: accountId ?? undefined,
330
+ target,
331
+ content,
332
+ storePath: context?.cronStorePath,
333
+ useAICard: params.useAICard,
334
+ });
335
+ },
336
+ ),
217
337
  );
218
338
 
219
339
  api.registerGatewayMethod(
220
340
  "dingtalk-connector.send",
221
- async ({ context, respond, params }: GatewayMethodContext) => {
222
- const accountId = readStringParam(params, "accountId");
223
- const target = readStringParam(params, "target", { required: true });
224
- const content = getContentParam(params);
225
- if (!content) {
226
- return respond(false, { error: "content or message is required" });
227
- }
228
- if (!isConnectorSendTarget(target)) {
229
- return respond(false, { error: "target must start with user: or group:" });
230
- }
231
- return sendGatewayMessage({
232
- api,
233
- respond,
234
- accountId: accountId ?? undefined,
235
- target,
236
- content,
237
- storePath: context?.cronStorePath,
238
- useAICard: params.useAICard,
239
- });
240
- },
341
+ withProactiveSendGatewayCapability(
342
+ api,
343
+ "dingtalk-connector.send",
344
+ (params) => readStringParam(params, "target", { required: true }) ?? undefined,
345
+ async ({ context, respond, params }, target) => {
346
+ const accountId = readStringParam(params, "accountId");
347
+ const content = getContentParam(params);
348
+ if (!content) {
349
+ return respond(false, { error: "content or message is required" });
350
+ }
351
+ if (!isConnectorSendTarget(target)) {
352
+ return respond(false, { error: "target must start with user: or group:" });
353
+ }
354
+ return sendGatewayMessage({
355
+ api,
356
+ respond,
357
+ accountId: accountId ?? undefined,
358
+ target,
359
+ content,
360
+ storePath: context?.cronStorePath,
361
+ useAICard: params.useAICard,
362
+ });
363
+ },
364
+ ),
241
365
  );
242
366
 
243
367
  api.registerGatewayMethod(
@@ -282,7 +406,7 @@ function registerDingTalkConnectorCompatibilityGatewayMethods(api: OpenClawPlugi
282
406
  }
283
407
 
284
408
  export { dingtalkPlugin } from "./src/channel";
285
- export { setDingTalkRuntime } from "./src/runtime";
409
+ export { setDingTalkRuntime } from "./src/platform/runtime";
286
410
 
287
411
  const dingtalkEntry = defineChannelPluginEntry({
288
412
  id: "dingtalk",