@soimy/dingtalk 3.6.9 → 3.6.11
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/dist/index.d.ts +3 -12
- package/dist/index.js +1262 -320
- package/dist/src/access-control.d.ts +0 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-controller.d.ts +0 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-events.d.ts +0 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-progress.d.ts +0 -1
- package/dist/src/ack-reaction-classifier.d.ts +0 -1
- package/dist/src/ack-reaction-service.d.ts +0 -1
- package/dist/src/auth.d.ts +0 -1
- package/dist/src/card/ask-user-question-context.d.ts +0 -1
- package/dist/src/card/ask-user-question-store.d.ts +0 -1
- package/dist/src/card/ask-user-question.d.ts +0 -1
- package/dist/src/card/card-action-handler.d.ts +0 -1
- package/dist/src/card/card-markdown-image-reroute.d.ts +0 -1
- package/dist/src/card/card-run-registry.d.ts +0 -1
- package/dist/src/card/card-stop-handler.d.ts +0 -1
- package/dist/src/card/card-streaming-mode.d.ts +0 -1
- package/dist/src/card/card-template.d.ts +0 -1
- package/dist/src/card/reasoning-answer-split.d.ts +0 -1
- package/dist/src/card/reasoning-block-assembler.d.ts +0 -1
- package/dist/src/card/statusline-renderer.d.ts +0 -1
- package/dist/src/card/task-model-metadata.d.ts +0 -1
- package/dist/src/card-callback-service.d.ts +0 -1
- package/dist/src/card-draft-controller.d.ts +0 -1
- package/dist/src/card-service.d.ts +0 -1
- package/dist/src/channel.d.ts +0 -1
- package/dist/src/command/card-stop-command.d.ts +0 -1
- package/dist/src/command/inbound-command-dispatch-service.d.ts +0 -1
- package/dist/src/config-schema.d.ts +0 -1
- package/dist/src/config.d.ts +0 -1
- package/dist/src/connection-manager.d.ts +0 -1
- package/dist/src/dedup.d.ts +0 -1
- package/dist/src/device-registration.d.ts +0 -1
- package/dist/src/docs-service.d.ts +4 -5
- package/dist/src/draft-stream-loop.d.ts +0 -1
- package/dist/src/feedback-learning-service.d.ts +0 -1
- package/dist/src/feedback-learning-store.d.ts +0 -1
- package/dist/src/gateway/channel-gateway.d.ts +0 -1
- package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts +0 -1
- package/dist/src/gateway/inbound-session-queue.d.ts +0 -1
- package/dist/src/gateway/reply-session-conflict.d.ts +0 -1
- package/dist/src/http-client.d.ts +0 -1
- package/dist/src/inbound-handler.d.ts +0 -1
- package/dist/src/learning-command-service.d.ts +0 -1
- package/dist/src/logger-context.d.ts +0 -1
- package/dist/src/media-utils.d.ts +0 -1
- package/dist/src/message-context-store.d.ts +0 -1
- package/dist/src/message-utils.d.ts +0 -1
- package/dist/src/messaging/attachment-text-extractor.d.ts +0 -1
- package/dist/src/messaging/btw-deliver.d.ts +0 -1
- package/dist/src/messaging/channel-actions.d.ts +0 -1
- package/dist/src/messaging/channel-outbound.d.ts +0 -1
- package/dist/src/messaging/inline-directives.d.ts +42 -0
- package/dist/src/messaging/quoted-context.d.ts +0 -1
- package/dist/src/messaging/quoted-file-service.d.ts +0 -1
- package/dist/src/messaging/quoted-ref.d.ts +0 -1
- package/dist/src/onboarding.d.ts +0 -1
- package/dist/src/path-utils.d.ts +0 -1
- package/dist/src/peer-id-registry.d.ts +0 -12
- package/dist/src/persistence-store.d.ts +0 -1
- package/dist/src/platform/channel-status.d.ts +0 -1
- package/dist/src/plugin-sdk-channel-actions-augment.d.ts +0 -1
- package/dist/src/proactive-risk-registry.d.ts +0 -1
- package/dist/src/reply-strategy-card.d.ts +0 -1
- package/dist/src/reply-strategy-markdown.d.ts +0 -1
- package/dist/src/reply-strategy-types.d.ts +0 -1
- package/dist/src/reply-strategy-with-reaction.d.ts +0 -1
- package/dist/src/reply-strategy.d.ts +0 -1
- package/dist/src/run-usage-store.d.ts +0 -1
- package/dist/src/runtime.d.ts +0 -1
- package/dist/src/secret-input.d.ts +3 -3
- package/dist/src/send-service.d.ts +0 -1
- package/dist/src/session-command-service.d.ts +0 -1
- package/dist/src/session-lock.d.ts +0 -1
- package/dist/src/session-peer-store.d.ts +0 -1
- package/dist/src/session-routing.d.ts +0 -1
- package/dist/src/session-state.d.ts +0 -1
- package/dist/src/signature.d.ts +0 -1
- package/dist/src/targeting/agent-name-matcher.d.ts +0 -1
- package/dist/src/targeting/agent-routing.d.ts +0 -1
- package/dist/src/targeting/group-members-store.d.ts +0 -1
- package/dist/src/targeting/target-directory-adapter.d.ts +0 -1
- package/dist/src/targeting/target-directory-store.d.ts +0 -1
- package/dist/src/targeting/target-input.d.ts +0 -1
- package/dist/src/types.d.ts +1 -2
- package/dist/src/utils.d.ts +0 -1
- package/index.ts +8 -2
- package/openclaw.plugin.json +4 -3
- package/package.json +10 -8
- package/src/channel.ts +4 -3
- package/src/gateway/channel-gateway.ts +67 -4
- package/src/inbound-handler.ts +6 -2
- package/src/messaging/channel-actions.ts +1 -4
- package/src/messaging/inline-directives.ts +342 -0
- package/src/peer-id-registry.ts +0 -100
- package/src/secret-input.ts +33 -33
- package/src/types.ts +1 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -7
- package/dist/src/access-control.d.ts.map +0 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-controller.d.ts.map +0 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-events.d.ts.map +0 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-progress.d.ts.map +0 -1
- package/dist/src/ack-reaction-classifier.d.ts.map +0 -1
- package/dist/src/ack-reaction-service.d.ts.map +0 -1
- package/dist/src/auth.d.ts.map +0 -1
- package/dist/src/card/ask-user-question-context.d.ts.map +0 -1
- package/dist/src/card/ask-user-question-store.d.ts.map +0 -1
- package/dist/src/card/ask-user-question.d.ts.map +0 -1
- package/dist/src/card/card-action-handler.d.ts.map +0 -1
- package/dist/src/card/card-markdown-image-reroute.d.ts.map +0 -1
- package/dist/src/card/card-run-registry.d.ts.map +0 -1
- package/dist/src/card/card-stop-handler.d.ts.map +0 -1
- package/dist/src/card/card-streaming-mode.d.ts.map +0 -1
- package/dist/src/card/card-template.d.ts.map +0 -1
- package/dist/src/card/reasoning-answer-split.d.ts.map +0 -1
- package/dist/src/card/reasoning-block-assembler.d.ts.map +0 -1
- package/dist/src/card/statusline-renderer.d.ts.map +0 -1
- package/dist/src/card/task-model-metadata.d.ts.map +0 -1
- package/dist/src/card-callback-service.d.ts.map +0 -1
- package/dist/src/card-draft-controller.d.ts.map +0 -1
- package/dist/src/card-service.d.ts.map +0 -1
- package/dist/src/channel.d.ts.map +0 -1
- package/dist/src/command/card-stop-command.d.ts.map +0 -1
- package/dist/src/command/inbound-command-dispatch-service.d.ts.map +0 -1
- package/dist/src/config-schema.d.ts.map +0 -1
- package/dist/src/config.d.ts.map +0 -1
- package/dist/src/connection-manager.d.ts.map +0 -1
- package/dist/src/dedup.d.ts.map +0 -1
- package/dist/src/device-registration.d.ts.map +0 -1
- package/dist/src/docs-service.d.ts.map +0 -1
- package/dist/src/draft-stream-loop.d.ts.map +0 -1
- package/dist/src/feedback-learning-service.d.ts.map +0 -1
- package/dist/src/feedback-learning-store.d.ts.map +0 -1
- package/dist/src/gateway/channel-gateway.d.ts.map +0 -1
- package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts.map +0 -1
- package/dist/src/gateway/inbound-session-queue.d.ts.map +0 -1
- package/dist/src/gateway/reply-session-conflict.d.ts.map +0 -1
- package/dist/src/http-client.d.ts.map +0 -1
- package/dist/src/inbound-handler.d.ts.map +0 -1
- package/dist/src/learning-command-service.d.ts.map +0 -1
- package/dist/src/logger-context.d.ts.map +0 -1
- package/dist/src/media-utils.d.ts.map +0 -1
- package/dist/src/message-context-store.d.ts.map +0 -1
- package/dist/src/message-utils.d.ts.map +0 -1
- package/dist/src/messaging/attachment-text-extractor.d.ts.map +0 -1
- package/dist/src/messaging/btw-deliver.d.ts.map +0 -1
- package/dist/src/messaging/channel-actions.d.ts.map +0 -1
- package/dist/src/messaging/channel-outbound.d.ts.map +0 -1
- package/dist/src/messaging/quoted-context.d.ts.map +0 -1
- package/dist/src/messaging/quoted-file-service.d.ts.map +0 -1
- package/dist/src/messaging/quoted-ref.d.ts.map +0 -1
- package/dist/src/onboarding.d.ts.map +0 -1
- package/dist/src/path-utils.d.ts.map +0 -1
- package/dist/src/peer-id-registry.d.ts.map +0 -1
- package/dist/src/persistence-store.d.ts.map +0 -1
- package/dist/src/platform/channel-status.d.ts.map +0 -1
- package/dist/src/plugin-sdk-channel-actions-augment.d.ts.map +0 -1
- package/dist/src/proactive-risk-registry.d.ts.map +0 -1
- package/dist/src/reply-strategy-card.d.ts.map +0 -1
- package/dist/src/reply-strategy-markdown.d.ts.map +0 -1
- package/dist/src/reply-strategy-types.d.ts.map +0 -1
- package/dist/src/reply-strategy-with-reaction.d.ts.map +0 -1
- package/dist/src/reply-strategy.d.ts.map +0 -1
- package/dist/src/run-usage-store.d.ts.map +0 -1
- package/dist/src/runtime.d.ts.map +0 -1
- package/dist/src/secret-input.d.ts.map +0 -1
- package/dist/src/send-service.d.ts.map +0 -1
- package/dist/src/session-command-service.d.ts.map +0 -1
- package/dist/src/session-lock.d.ts.map +0 -1
- package/dist/src/session-peer-store.d.ts.map +0 -1
- package/dist/src/session-routing.d.ts.map +0 -1
- package/dist/src/session-state.d.ts.map +0 -1
- package/dist/src/signature.d.ts.map +0 -1
- package/dist/src/targeting/agent-name-matcher.d.ts.map +0 -1
- package/dist/src/targeting/agent-routing.d.ts.map +0 -1
- package/dist/src/targeting/group-members-store.d.ts.map +0 -1
- package/dist/src/targeting/target-directory-adapter.d.ts.map +0 -1
- package/dist/src/targeting/target-directory-store.d.ts.map +0 -1
- package/dist/src/targeting/target-input.d.ts.map +0 -1
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/utils.d.ts.map +0 -1
package/dist/src/types.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
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-
|
|
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";
|
|
@@ -769,4 +769,3 @@ export type CardBlock = {
|
|
|
769
769
|
mediaId: string;
|
|
770
770
|
text?: string;
|
|
771
771
|
};
|
|
772
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/src/utils.d.ts
CHANGED
package/index.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
defineChannelPluginEntry,
|
|
3
|
+
type OpenClawPluginApi,
|
|
4
|
+
type OpenClawPluginDefinition,
|
|
5
|
+
} from "openclaw/plugin-sdk/core";
|
|
2
6
|
import { readStringParam } from "openclaw/plugin-sdk/param-readers";
|
|
3
7
|
import { getAccessToken } from "./src/auth";
|
|
4
8
|
import { registerDingTalkAskUserQuestionTool } from "./src/card/ask-user-question";
|
|
@@ -311,7 +315,7 @@ const dingtalkEntry = defineChannelPluginEntry({
|
|
|
311
315
|
},
|
|
312
316
|
});
|
|
313
317
|
|
|
314
|
-
|
|
318
|
+
const pluginDefinition: OpenClawPluginDefinition = {
|
|
315
319
|
...dingtalkEntry,
|
|
316
320
|
register(api: OpenClawPluginApi) {
|
|
317
321
|
const registrationMode = api.registrationMode as string | undefined;
|
|
@@ -325,3 +329,5 @@ export default {
|
|
|
325
329
|
dingtalkEntry.register(api);
|
|
326
330
|
},
|
|
327
331
|
};
|
|
332
|
+
|
|
333
|
+
export default pluginDefinition;
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dingtalk",
|
|
3
|
+
"name": "DingTalk",
|
|
3
4
|
"channels": ["dingtalk"],
|
|
4
5
|
"contracts": {
|
|
5
6
|
"tools": ["dingtalk_ask_user_question"]
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
],
|
|
65
|
-
"description": "DingTalk App Secret (Client Secret) used to obtain DingTalk access tokens. Supports
|
|
66
|
+
"description": "DingTalk App Secret (Client Secret) used to obtain DingTalk access tokens. Supports env/file SecretInput references resolved by configured OpenClaw secret providers."
|
|
66
67
|
},
|
|
67
68
|
"dmPolicy": {
|
|
68
69
|
"type": "string",
|
|
@@ -368,7 +369,7 @@
|
|
|
368
369
|
}
|
|
369
370
|
}
|
|
370
371
|
],
|
|
371
|
-
"description": "DingTalk App Secret (Client Secret) used to obtain DingTalk access tokens. Supports
|
|
372
|
+
"description": "DingTalk App Secret (Client Secret) used to obtain DingTalk access tokens. Supports env/file SecretInput references resolved by configured OpenClaw secret providers."
|
|
372
373
|
},
|
|
373
374
|
"dmPolicy": {
|
|
374
375
|
"type": "string",
|
|
@@ -654,7 +655,7 @@
|
|
|
654
655
|
},
|
|
655
656
|
"clientSecret": {
|
|
656
657
|
"label": "DingTalk Client Secret",
|
|
657
|
-
"help": "App Secret used to obtain DingTalk access tokens. SecretInput
|
|
658
|
+
"help": "App Secret used to obtain DingTalk access tokens. SecretInput references are resolved by configured OpenClaw secret providers.",
|
|
658
659
|
"sensitive": true
|
|
659
660
|
},
|
|
660
661
|
"dmPolicy": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soimy/dingtalk",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.11",
|
|
4
4
|
"description": "DingTalk (钉钉) channel plugin for OpenClaw",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bot",
|
|
@@ -20,9 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"dist/**/*.js",
|
|
23
|
-
"dist/**/*.js.map",
|
|
24
23
|
"dist/**/*.d.ts",
|
|
25
|
-
"dist/**/*.d.ts.map",
|
|
26
24
|
"index.ts",
|
|
27
25
|
"src/**/*.ts",
|
|
28
26
|
"docs/assets/dingtalk-ask-user-card-template.json",
|
|
@@ -31,6 +29,9 @@
|
|
|
31
29
|
"LICENSE"
|
|
32
30
|
],
|
|
33
31
|
"type": "module",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0"
|
|
34
|
+
},
|
|
34
35
|
"packageManager": "pnpm@10.33.0",
|
|
35
36
|
"main": "./dist/index.js",
|
|
36
37
|
"types": "./dist/index.d.ts",
|
|
@@ -69,12 +70,13 @@
|
|
|
69
70
|
"form-data": "^4.0.0",
|
|
70
71
|
"mammoth": "^1.12.0",
|
|
71
72
|
"pdf-parse": "^2.4.5",
|
|
72
|
-
"zod": "^4.3
|
|
73
|
+
"zod": "^4.4.3"
|
|
73
74
|
},
|
|
74
75
|
"devDependencies": {
|
|
75
76
|
"@types/node": "^25.2.0",
|
|
76
77
|
"@vitest/coverage-v8": "^3.2.4",
|
|
77
78
|
"esbuild": "^0.27.3",
|
|
79
|
+
"openclaw": "2026.7.1-2",
|
|
78
80
|
"oxfmt": "0.34.0",
|
|
79
81
|
"oxlint": "1.49.0",
|
|
80
82
|
"oxlint-tsgolint": "0.18.1",
|
|
@@ -83,7 +85,7 @@
|
|
|
83
85
|
"vitest": "^3.2.4"
|
|
84
86
|
},
|
|
85
87
|
"peerDependencies": {
|
|
86
|
-
"openclaw": ">=2026.
|
|
88
|
+
"openclaw": ">=2026.7.1-2"
|
|
87
89
|
},
|
|
88
90
|
"peerDependenciesMeta": {
|
|
89
91
|
"openclaw": {
|
|
@@ -92,10 +94,10 @@
|
|
|
92
94
|
},
|
|
93
95
|
"openclaw": {
|
|
94
96
|
"compat": {
|
|
95
|
-
"pluginApi": ">=2026.
|
|
97
|
+
"pluginApi": ">=2026.7.1-2"
|
|
96
98
|
},
|
|
97
99
|
"build": {
|
|
98
|
-
"openclawVersion": "2026.
|
|
100
|
+
"openclawVersion": "2026.7.1-2"
|
|
99
101
|
},
|
|
100
102
|
"extensions": [
|
|
101
103
|
"./index.ts"
|
|
@@ -121,7 +123,7 @@
|
|
|
121
123
|
]
|
|
122
124
|
},
|
|
123
125
|
"install": {
|
|
124
|
-
"minHostVersion": ">=2026.
|
|
126
|
+
"minHostVersion": ">=2026.7.1-2",
|
|
125
127
|
"npmSpec": "@soimy/dingtalk",
|
|
126
128
|
"localPath": ".",
|
|
127
129
|
"defaultChoice": "npm"
|
package/src/channel.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildJsonChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
2
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
3
|
+
import pluginManifest from "../openclaw.plugin.json";
|
|
2
4
|
import { getConfig, isConfigured, mergeAccountWithDefaults, resolveGroupConfig } from "./config";
|
|
3
|
-
import { DingTalkConfigSchema } from "./config-schema.js";
|
|
4
5
|
import {
|
|
5
6
|
CHANNEL_INFLIGHT_NAMESPACE_POLICY,
|
|
6
7
|
createDingTalkGateway,
|
|
@@ -29,7 +30,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
29
30
|
blurb: "钉钉企业内部机器人,使用 Stream 模式,无需公网 IP。",
|
|
30
31
|
aliases: ["dd", "ding"],
|
|
31
32
|
},
|
|
32
|
-
configSchema:
|
|
33
|
+
configSchema: buildJsonChannelConfigSchema(pluginManifest.channelConfigs.dingtalk.schema),
|
|
33
34
|
setup: dingtalkSetupAdapter,
|
|
34
35
|
setupWizard: dingtalkSetupWizard,
|
|
35
36
|
capabilities: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DWClient, TOPIC_CARD, TOPIC_ROBOT } from "dingtalk-stream";
|
|
2
|
+
import { listAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
|
2
3
|
import { analyzeCardCallback } from "../card-callback-service";
|
|
3
4
|
import { finalizeActiveCardsForAccount, recoverPendingCardsForAccount } from "../card-service";
|
|
4
5
|
import { recoverAskUserQuestionsForAccount } from "../card/ask-user-question";
|
|
@@ -13,9 +14,14 @@ import {
|
|
|
13
14
|
} from "../feedback-learning-service";
|
|
14
15
|
import { handleDingTalkMessage } from "../inbound-handler";
|
|
15
16
|
import { setCurrentLogger } from "../logger-context";
|
|
16
|
-
import {
|
|
17
|
+
import { registerPeerId } from "../peer-id-registry";
|
|
17
18
|
import { getDingTalkRuntime } from "../runtime";
|
|
18
19
|
import { sendProactiveTextOrMarkdown } from "../send-service";
|
|
20
|
+
import {
|
|
21
|
+
listKnownGroupTargets,
|
|
22
|
+
listKnownUserTargets,
|
|
23
|
+
upsertObservedGroupTarget,
|
|
24
|
+
} from "../targeting/target-directory-store";
|
|
19
25
|
import type {
|
|
20
26
|
ConnectionManagerConfig,
|
|
21
27
|
DingTalkChannelPlugin,
|
|
@@ -180,6 +186,66 @@ export function createDingTalkGateway(): NonNullable<DingTalkChannelPlugin["gate
|
|
|
180
186
|
debug: config.debug,
|
|
181
187
|
baseLog: ctx.log,
|
|
182
188
|
});
|
|
189
|
+
try {
|
|
190
|
+
const knownGroupIds = new Set<string>();
|
|
191
|
+
for (const group of listKnownGroupTargets({
|
|
192
|
+
storePath: accountStorePath,
|
|
193
|
+
accountId: account.accountId,
|
|
194
|
+
})) {
|
|
195
|
+
registerPeerId(group.conversationId);
|
|
196
|
+
knownGroupIds.add(group.conversationId.toLowerCase());
|
|
197
|
+
}
|
|
198
|
+
const runtime = getDingTalkRuntime();
|
|
199
|
+
for (const agentId of listAgentIds(cfg)) {
|
|
200
|
+
const sessionStorePath = runtime.channel.session.resolveStorePath(cfg.session?.store, {
|
|
201
|
+
agentId,
|
|
202
|
+
});
|
|
203
|
+
for (const { entry } of runtime.agent.session.listSessionEntries({
|
|
204
|
+
agentId,
|
|
205
|
+
storePath: sessionStorePath,
|
|
206
|
+
readConsistency: "latest",
|
|
207
|
+
})) {
|
|
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) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
for (const peerId of [entry.lastTo, entry.origin?.from, entry.origin?.to]) {
|
|
219
|
+
if (typeof peerId !== "string" || !peerId.startsWith("cid")) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
registerPeerId(peerId);
|
|
223
|
+
const normalizedPeerId = peerId.toLowerCase();
|
|
224
|
+
if (!knownGroupIds.has(normalizedPeerId)) {
|
|
225
|
+
upsertObservedGroupTarget({
|
|
226
|
+
storePath: accountStorePath,
|
|
227
|
+
accountId: account.accountId,
|
|
228
|
+
conversationId: peerId,
|
|
229
|
+
seenAt: entry.updatedAt,
|
|
230
|
+
});
|
|
231
|
+
knownGroupIds.add(normalizedPeerId);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
for (const user of listKnownUserTargets({
|
|
237
|
+
storePath: accountStorePath,
|
|
238
|
+
accountId: account.accountId,
|
|
239
|
+
})) {
|
|
240
|
+
registerPeerId(user.canonicalUserId);
|
|
241
|
+
registerPeerId(user.staffId || "");
|
|
242
|
+
registerPeerId(user.senderId);
|
|
243
|
+
}
|
|
244
|
+
} catch (err: unknown) {
|
|
245
|
+
pluginLog?.warn?.(
|
|
246
|
+
`[${account.accountId}] Failed to restore peer IDs: ${err instanceof Error ? err.message : String(err)}`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
183
249
|
// Stream credentials are resolved once per account start. If a file
|
|
184
250
|
// SecretInput rotates, restart the gateway/account so reconnects use the
|
|
185
251
|
// new secret.
|
|
@@ -188,9 +254,6 @@ export function createDingTalkGateway(): NonNullable<DingTalkChannelPlugin["gate
|
|
|
188
254
|
|
|
189
255
|
pluginLog?.info?.(`[${account.accountId}] Initializing DingTalk Stream client...`);
|
|
190
256
|
|
|
191
|
-
preloadPeerIdsFromSessions();
|
|
192
|
-
pluginLog?.debug?.(`[${account.accountId}] Peer ID registry preloaded from sessions`);
|
|
193
|
-
|
|
194
257
|
cleanupOrphanedTempFiles(pluginLog);
|
|
195
258
|
try {
|
|
196
259
|
const recovered = await recoverPendingCardsForAccount(
|
package/src/inbound-handler.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { formatInboundEnvelope } from "openclaw/plugin-sdk/channel-inbound";
|
|
3
4
|
import { isAbortRequestText, isBtwRequestText } from "openclaw/plugin-sdk/reply-runtime";
|
|
4
|
-
import { parseInlineDirectives } from "
|
|
5
|
+
import { parseInlineDirectives } from "./messaging/inline-directives";
|
|
5
6
|
import { normalizeAllowFrom, isSenderAllowed, resolveGroupAccess } from "./access-control";
|
|
6
7
|
import { classifyAckReactionEmoji } from "./ack-reaction-classifier";
|
|
7
8
|
import { attachNativeAckReaction } from "./ack-reaction-service";
|
|
@@ -1792,7 +1793,10 @@ async function handleDingTalkMessageInner(params: HandleDingTalkMessageParams):
|
|
|
1792
1793
|
const groupMembers = !isDirect ? formatGroupMembers(storePath, groupId) : undefined;
|
|
1793
1794
|
|
|
1794
1795
|
const fromLabel = isDirect ? `${senderName} (${senderId})` : `${groupName} - ${senderName}`;
|
|
1795
|
-
|
|
1796
|
+
// `rt.channel.reply.formatInboundEnvelope` was removed from the runtime
|
|
1797
|
+
// facade in OpenClaw 2026.8.x; the helper is still exported from the
|
|
1798
|
+
// `channel-inbound` SDK subpath.
|
|
1799
|
+
const body = formatInboundEnvelope({
|
|
1796
1800
|
channel: "DingTalk",
|
|
1797
1801
|
from: fromLabel,
|
|
1798
1802
|
timestamp: data.createAt,
|
|
@@ -55,12 +55,9 @@ function describeDingTalkMessageTool(cfg: OpenClawConfig) {
|
|
|
55
55
|
if (!configured && !(config.accounts && Object.keys(config.accounts).length > 0)) {
|
|
56
56
|
return { actions: [], capabilities: [], schema: null };
|
|
57
57
|
}
|
|
58
|
-
const hasCardMode =
|
|
59
|
-
config.messageType === "card" ||
|
|
60
|
-
(config.accounts && Object.values(config.accounts).some((account) => account?.messageType === "card"));
|
|
61
58
|
return {
|
|
62
59
|
actions: ["send"] as const,
|
|
63
|
-
capabilities:
|
|
60
|
+
capabilities: [],
|
|
64
61
|
schema: null,
|
|
65
62
|
};
|
|
66
63
|
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline directive parsing (local implementation).
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw retired the `openclaw/plugin-sdk/text-runtime` subpath in
|
|
5
|
+
* 2026.8.x (docs/plugins/sdk-migration.md: "The August 15 compatibility
|
|
6
|
+
* subpaths ... `text-runtime` ... were retired early"). Its replacement
|
|
7
|
+
* `openclaw/plugin-sdk/text-chunking` only re-exports the directive-tag
|
|
8
|
+
* *stripping* helpers and the `InlineDirectiveParseResult` type - not
|
|
9
|
+
* `parseInlineDirectives` itself.
|
|
10
|
+
*
|
|
11
|
+
* This module reproduces the previous host behaviour inside the plugin so no
|
|
12
|
+
* removed SDK internals are imported. Code-region awareness (fenced,
|
|
13
|
+
* indented, and inline code) is sourced from the public SDK helpers
|
|
14
|
+
* `findCodeRegions` / `isInsideCode` (available since 2026.7.1-2), plus a
|
|
15
|
+
* lenient indented-block fallback matching the host whitespace normalizer so
|
|
16
|
+
* directives inside code are never treated as real voice/reply commands.
|
|
17
|
+
*
|
|
18
|
+
* The shipped 2026.7.1-2 SDK `findCodeRegions` is a simplified heuristic: it
|
|
19
|
+
* pairs backtick runs of different lengths, lets shorter fences close longer
|
|
20
|
+
* ones, treats escaped backticks as real delimiters, and pairs backticks
|
|
21
|
+
* inside indented code with prose. `findDelimiterCodeRegions` therefore layers
|
|
22
|
+
* a CommonMark-style fence/inline-code supplement over the SDK regions (union,
|
|
23
|
+
* conservative: in doubt the text stays literal) so protection holds on every
|
|
24
|
+
* supported host. Ponytail: remove the supplement once the minimum host is
|
|
25
|
+
* 2026.8+ and its SDK regions are verified to cover these cases.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { findCodeRegions, isInsideCode, type CodeRegion } from "openclaw/plugin-sdk/text-chunking";
|
|
29
|
+
|
|
30
|
+
const AUDIO_TAG_RE = /\[\[\s*audio_as_voice\s*\]\]/gi;
|
|
31
|
+
const REPLY_TAG_RE = /\[\[\s*(?:reply_to_current|reply_to\s*:\s*([^\]\n]+))\s*\]\]/gi;
|
|
32
|
+
const MAX_REPLY_DIRECTIVE_ID_LENGTH = 256;
|
|
33
|
+
const BLOCK_SENTINEL_SEED = "\uE000";
|
|
34
|
+
/** Lenient indented-code-block lines (4 spaces or tab), same rule as the host whitespace normalizer. */
|
|
35
|
+
const INDENTED_CODE_LINE_RE = /(?:^|\n)((?: {4}|\t)[^\n]*)(?:\n(?: {4}|\t)[^\n]*)*/g;
|
|
36
|
+
|
|
37
|
+
export type ParseInlineDirectivesOptions = {
|
|
38
|
+
currentMessageId?: string;
|
|
39
|
+
stripAudioTag?: boolean;
|
|
40
|
+
stripReplyTags?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type InlineDirectiveParseResult = {
|
|
44
|
+
text: string;
|
|
45
|
+
audioAsVoice: boolean;
|
|
46
|
+
replyToId?: string;
|
|
47
|
+
replyToExplicitId?: string;
|
|
48
|
+
replyToCurrent: boolean;
|
|
49
|
+
hasAudioTag: boolean;
|
|
50
|
+
hasReplyTag: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function normalizeOptionalString(value: unknown): string | undefined {
|
|
54
|
+
if (typeof value !== "string") {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const trimmed = value.trim();
|
|
58
|
+
return trimmed ? trimmed : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* CommonMark code regions (fenced / indented / inline) from the SDK, extended
|
|
63
|
+
* with a lenient indented-block fallback for lines indented by 4 spaces or a
|
|
64
|
+
* tab that follow a paragraph without a blank separator (the host normalizer
|
|
65
|
+
* protects those too).
|
|
66
|
+
*/
|
|
67
|
+
function resolveCodeRegions(text: string): CodeRegion[] {
|
|
68
|
+
const indentedRegions: CodeRegion[] = [];
|
|
69
|
+
const indentRe = new RegExp(INDENTED_CODE_LINE_RE.source, "g");
|
|
70
|
+
let match: RegExpExecArray | null;
|
|
71
|
+
while ((match = indentRe.exec(text)) !== null) {
|
|
72
|
+
const start = match.index + (match[0].charCodeAt(0) === 10 ? 1 : 0);
|
|
73
|
+
indentedRegions.push({ start, end: match.index + match[0].length });
|
|
74
|
+
}
|
|
75
|
+
const sdkRegions = findCodeRegions(text).filter(
|
|
76
|
+
(region) =>
|
|
77
|
+
(text[region.start] !== "`" || !isEscaped(text, region.start)) &&
|
|
78
|
+
!indentedRegions.some(
|
|
79
|
+
(indented) => region.start >= indented.start && region.start < indented.end,
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
const knownRegions = mergeCodeRegions([...sdkRegions, ...indentedRegions]);
|
|
83
|
+
return mergeCodeRegions([...knownRegions, ...findDelimiterCodeRegions(text, knownRegions)]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isEscaped(text: string, offset: number): boolean {
|
|
87
|
+
let slashes = 0;
|
|
88
|
+
for (let index = offset - 1; text[index] === "\\"; index--) {
|
|
89
|
+
slashes++;
|
|
90
|
+
}
|
|
91
|
+
return slashes % 2 === 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isStrictlyInsideCode(offset: number, regions: CodeRegion[]): boolean {
|
|
95
|
+
return regions.some((region) => offset > region.start && offset < region.end);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ponytail: small 2026.7.x delimiter supplement; delete when the minimum host is 2026.8+.
|
|
99
|
+
function findDelimiterCodeRegions(text: string, knownRegions: CodeRegion[]): CodeRegion[] {
|
|
100
|
+
const regions: CodeRegion[] = [];
|
|
101
|
+
let match: RegExpExecArray | null;
|
|
102
|
+
const fences = /^( {0,3})(`{3,}|~{3,})[^\n]*(?:\n|$)/gm;
|
|
103
|
+
let open: { char: string; length: number; start: number } | undefined;
|
|
104
|
+
while ((match = fences.exec(text)) !== null) {
|
|
105
|
+
const delimiter = match[2];
|
|
106
|
+
if (!open) {
|
|
107
|
+
const suffix = match[0].slice(match[1].length + delimiter.length).trim();
|
|
108
|
+
if (
|
|
109
|
+
isStrictlyInsideCode(match.index, knownRegions) ||
|
|
110
|
+
(delimiter[0] === "`" && suffix.includes("`"))
|
|
111
|
+
) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
open = { char: delimiter[0], length: delimiter.length, start: match.index };
|
|
115
|
+
} else if (
|
|
116
|
+
delimiter[0] === open.char &&
|
|
117
|
+
delimiter.length >= open.length &&
|
|
118
|
+
match[0].slice(match[1].length + delimiter.length).trim() === ""
|
|
119
|
+
) {
|
|
120
|
+
regions.push({ start: open.start, end: fences.lastIndex });
|
|
121
|
+
open = undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (open) {
|
|
125
|
+
regions.push({ start: open.start, end: text.length });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const fencedRegions = [...regions];
|
|
129
|
+
const pendingTicks = new Map<number, number>();
|
|
130
|
+
const ticks = /`+/g;
|
|
131
|
+
while ((match = ticks.exec(text)) !== null) {
|
|
132
|
+
const length = match[0].length;
|
|
133
|
+
if (
|
|
134
|
+
isEscaped(text, match.index) ||
|
|
135
|
+
isInsideCode(match.index, fencedRegions) ||
|
|
136
|
+
(pendingTicks.get(length) === undefined && isStrictlyInsideCode(match.index, knownRegions))
|
|
137
|
+
) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const start = pendingTicks.get(length);
|
|
141
|
+
if (start === undefined) {
|
|
142
|
+
pendingTicks.set(length, match.index);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
regions.push({ start, end: ticks.lastIndex });
|
|
146
|
+
for (const [pendingLength, pendingStart] of pendingTicks) {
|
|
147
|
+
if (pendingStart >= start) {
|
|
148
|
+
pendingTicks.delete(pendingLength);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return regions;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function mergeCodeRegions(regions: CodeRegion[]): CodeRegion[] {
|
|
156
|
+
if (regions.length <= 1) {
|
|
157
|
+
return regions;
|
|
158
|
+
}
|
|
159
|
+
const sorted = [...regions].toSorted((a, b) => a.start - b.start || a.end - b.end);
|
|
160
|
+
const merged: CodeRegion[] = [];
|
|
161
|
+
for (const region of sorted) {
|
|
162
|
+
const last = merged[merged.length - 1];
|
|
163
|
+
if (last && region.start <= last.end) {
|
|
164
|
+
last.end = Math.max(last.end, region.end);
|
|
165
|
+
} else {
|
|
166
|
+
merged.push({ start: region.start, end: region.end });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return merged;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function createBlockSentinel(text: string): string {
|
|
173
|
+
let sentinel = BLOCK_SENTINEL_SEED;
|
|
174
|
+
while (text.includes(sentinel)) {
|
|
175
|
+
sentinel += BLOCK_SENTINEL_SEED;
|
|
176
|
+
}
|
|
177
|
+
return sentinel;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function normalizeDirectiveWhitespace(text: string, codeRegions: CodeRegion[] = []): string {
|
|
181
|
+
const blockSentinel = createBlockSentinel(text);
|
|
182
|
+
const blockPlaceholderRe = new RegExp(`${blockSentinel}(\\d+)${blockSentinel}`, "g");
|
|
183
|
+
const blocks: string[] = [];
|
|
184
|
+
|
|
185
|
+
let masked = "";
|
|
186
|
+
let cursor = 0;
|
|
187
|
+
for (const region of codeRegions) {
|
|
188
|
+
if (region.start < cursor) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
blocks.push(text.slice(region.start, region.end));
|
|
192
|
+
masked += `${text.slice(cursor, region.start)}${blockSentinel}${blocks.length - 1}${blockSentinel}`;
|
|
193
|
+
cursor = region.end;
|
|
194
|
+
}
|
|
195
|
+
masked += text.slice(cursor);
|
|
196
|
+
|
|
197
|
+
return masked
|
|
198
|
+
.replace(/\r\n/g, "\n")
|
|
199
|
+
.replace(/([^\s])[ \t]{2,}([^\s])/g, "$1 $2")
|
|
200
|
+
.replace(/^\n+/, "")
|
|
201
|
+
.replace(/^[ \t](?=\S)/, "")
|
|
202
|
+
.replace(/[ \t]+\n/g, "\n")
|
|
203
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
204
|
+
.trimEnd()
|
|
205
|
+
.replace(blockPlaceholderRe, (_full, index: string) => blocks[Number(index)] ?? "");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function replacementPreservesWordBoundary(source: string, offset: number, length: number): string {
|
|
209
|
+
const before = source[offset - 1];
|
|
210
|
+
const after = source[offset + length];
|
|
211
|
+
return before && after && !/\s/u.test(before) && !/\s/u.test(after) ? " " : "";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function stripUnsafeReplyDirectiveChars(value: string): string {
|
|
215
|
+
const chars: string[] = [];
|
|
216
|
+
for (const ch of value) {
|
|
217
|
+
const code = ch.charCodeAt(0);
|
|
218
|
+
if (
|
|
219
|
+
(code >= 0 && code <= 31) ||
|
|
220
|
+
code === 127 ||
|
|
221
|
+
(code >= 128 && code <= 159) ||
|
|
222
|
+
ch === "[" ||
|
|
223
|
+
ch === "]"
|
|
224
|
+
) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
chars.push(ch);
|
|
228
|
+
}
|
|
229
|
+
return chars.join("");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function sanitizeReplyDirectiveId(rawReplyToId: string | undefined): string | undefined {
|
|
233
|
+
const trimmed = rawReplyToId?.trim();
|
|
234
|
+
if (!trimmed) {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
const sanitized = stripUnsafeReplyDirectiveChars(trimmed).trim();
|
|
238
|
+
if (!sanitized) {
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
const chars = Array.from(sanitized);
|
|
242
|
+
if (chars.length > MAX_REPLY_DIRECTIVE_ID_LENGTH) {
|
|
243
|
+
return chars.slice(0, MAX_REPLY_DIRECTIVE_ID_LENGTH).join("");
|
|
244
|
+
}
|
|
245
|
+
return sanitized;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function parseInlineDirectives(
|
|
249
|
+
text: string | undefined,
|
|
250
|
+
options: ParseInlineDirectivesOptions = {},
|
|
251
|
+
): InlineDirectiveParseResult {
|
|
252
|
+
const { currentMessageId, stripAudioTag = true, stripReplyTags = true } = options;
|
|
253
|
+
|
|
254
|
+
if (!text) {
|
|
255
|
+
return {
|
|
256
|
+
text: "",
|
|
257
|
+
audioAsVoice: false,
|
|
258
|
+
replyToCurrent: false,
|
|
259
|
+
hasAudioTag: false,
|
|
260
|
+
hasReplyTag: false,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!text.includes("[[")) {
|
|
265
|
+
return {
|
|
266
|
+
text: normalizeDirectiveWhitespace(text, resolveCodeRegions(text)),
|
|
267
|
+
audioAsVoice: false,
|
|
268
|
+
replyToCurrent: false,
|
|
269
|
+
hasAudioTag: false,
|
|
270
|
+
hasReplyTag: false,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Directives inside code regions are literal text: never voice/reply semantics.
|
|
275
|
+
// Stripping a tag shortens the string, so every pass re-derives regions from
|
|
276
|
+
// the text it actually runs against; offsets from an earlier pass are stale
|
|
277
|
+
// once a preceding tag has been removed.
|
|
278
|
+
const codeRegions = resolveCodeRegions(text);
|
|
279
|
+
let cleaned = text;
|
|
280
|
+
let audioAsVoice = false;
|
|
281
|
+
let hasAudioTag = false;
|
|
282
|
+
let hasReplyTag = false;
|
|
283
|
+
let sawCurrent = false;
|
|
284
|
+
let lastExplicitId: string | undefined;
|
|
285
|
+
|
|
286
|
+
cleaned = cleaned.replace(AUDIO_TAG_RE, (match: string, offset: number, source: string) => {
|
|
287
|
+
if (isInsideCode(offset, codeRegions)) {
|
|
288
|
+
return match;
|
|
289
|
+
}
|
|
290
|
+
audioAsVoice = true;
|
|
291
|
+
hasAudioTag = true;
|
|
292
|
+
return stripAudioTag ? replacementPreservesWordBoundary(source, offset, match.length) : match;
|
|
293
|
+
});
|
|
294
|
+
const codeRegionsAfterAudioStrip = resolveCodeRegions(cleaned);
|
|
295
|
+
|
|
296
|
+
cleaned = cleaned.replace(
|
|
297
|
+
REPLY_TAG_RE,
|
|
298
|
+
(match: string, idRaw: string | undefined, offset: number, source: string) => {
|
|
299
|
+
if (isInsideCode(offset, codeRegionsAfterAudioStrip)) {
|
|
300
|
+
return match;
|
|
301
|
+
}
|
|
302
|
+
hasReplyTag = true;
|
|
303
|
+
if (idRaw === undefined) {
|
|
304
|
+
sawCurrent = true;
|
|
305
|
+
} else {
|
|
306
|
+
const id = sanitizeReplyDirectiveId(idRaw);
|
|
307
|
+
if (id) {
|
|
308
|
+
lastExplicitId = id;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return stripReplyTags
|
|
312
|
+
? replacementPreservesWordBoundary(source, offset, match.length)
|
|
313
|
+
: match;
|
|
314
|
+
},
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// Early return when `[[...]]` matched no known directive: keep the original
|
|
318
|
+
// text untouched instead of running whitespace normalization.
|
|
319
|
+
if (!hasAudioTag && !hasReplyTag) {
|
|
320
|
+
return {
|
|
321
|
+
text,
|
|
322
|
+
audioAsVoice: false,
|
|
323
|
+
replyToCurrent: false,
|
|
324
|
+
hasAudioTag: false,
|
|
325
|
+
hasReplyTag: false,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
cleaned = normalizeDirectiveWhitespace(cleaned, resolveCodeRegions(cleaned));
|
|
330
|
+
const replyToId =
|
|
331
|
+
lastExplicitId ?? (sawCurrent ? normalizeOptionalString(currentMessageId) : undefined);
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
text: cleaned,
|
|
335
|
+
audioAsVoice,
|
|
336
|
+
replyToId,
|
|
337
|
+
replyToExplicitId: lastExplicitId,
|
|
338
|
+
replyToCurrent: sawCurrent,
|
|
339
|
+
hasAudioTag,
|
|
340
|
+
hasReplyTag,
|
|
341
|
+
};
|
|
342
|
+
}
|