@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.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11655 -10324
- package/dist/src/{ack-reaction-service.d.ts → ack-reaction/ack-reaction-service.d.ts} +1 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-controller.d.ts +3 -3
- package/dist/src/card/ask-user-question-context.d.ts +1 -1
- package/dist/src/card/ask-user-question-store.d.ts +1 -1
- package/dist/src/card/ask-user-question.d.ts +1 -1
- package/dist/src/card/card-action-handler.d.ts +2 -2
- package/dist/src/{card-draft-controller.d.ts → card/card-draft-controller.d.ts} +5 -1
- package/dist/src/card/card-run-registry.d.ts +2 -2
- package/dist/src/{card-service.d.ts → card/card-service.d.ts} +1 -1
- package/dist/src/card/card-stop-handler.d.ts +1 -1
- package/dist/src/card/card-streaming-mode.d.ts +1 -1
- package/dist/src/card/card-task-progress.d.ts +60 -0
- package/dist/src/channel.d.ts +6 -6
- package/dist/src/command/card-stop-command.d.ts +1 -1
- package/dist/src/{feedback-learning-service.d.ts → command/feedback-learning-service.d.ts} +1 -1
- package/dist/src/command/inbound-command-dispatch-service.d.ts +2 -2
- package/dist/src/{learning-command-service.d.ts → command/learning-command-service.d.ts} +1 -1
- package/dist/src/gateway/channel-gateway.d.ts +1 -1
- package/dist/src/{connection-manager.d.ts → gateway/connection-manager.d.ts} +1 -1
- package/dist/src/{docs-service.d.ts → gateway/docs-service.d.ts} +5 -5
- package/dist/src/{inbound-handler.d.ts → gateway/inbound-handler.d.ts} +1 -1
- package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts +1 -1
- package/dist/src/messaging/attachment-text-extractor.d.ts +1 -1
- package/dist/src/messaging/btw-deliver.d.ts +1 -1
- package/dist/src/messaging/channel-outbound.d.ts +1 -1
- package/dist/src/messaging/inline-directives.d.ts +42 -0
- package/dist/src/{media-utils.d.ts → messaging/media-utils.d.ts} +1 -1
- package/dist/src/{message-context-store.d.ts → messaging/message-context-store.d.ts} +7 -1
- package/dist/src/{message-utils.d.ts → messaging/message-utils.d.ts} +1 -1
- package/dist/src/messaging/quoted-context.d.ts +2 -2
- package/dist/src/messaging/quoted-file-service.d.ts +1 -1
- package/dist/src/messaging/quoted-ref.d.ts +2 -2
- package/dist/src/{reply-strategy-card.d.ts → messaging/reply-strategy-card.d.ts} +1 -1
- package/dist/src/{reply-strategy-types.d.ts → messaging/reply-strategy-types.d.ts} +10 -1
- package/dist/src/{reply-strategy-with-reaction.d.ts → messaging/reply-strategy-with-reaction.d.ts} +1 -1
- package/dist/src/{reply-strategy.d.ts → messaging/reply-strategy.d.ts} +1 -1
- package/dist/src/{send-service.d.ts → messaging/send-service.d.ts} +1 -1
- package/dist/src/platform/channel-status.d.ts +1 -1
- package/dist/src/platform/config.d.ts +110 -0
- package/dist/src/platform/runtime-events.d.ts +67 -0
- package/dist/src/{types.d.ts → platform/types.d.ts} +75 -2
- package/dist/src/{persistence-store.d.ts → shared/persistence-store.d.ts} +1 -1
- package/dist/src/{utils.d.ts → shared/utils.d.ts} +1 -1
- package/dist/src/targeting/agent-name-matcher.d.ts +1 -1
- package/dist/src/targeting/agent-routing.d.ts +3 -3
- package/dist/src/{session-routing.d.ts → targeting/session-routing.d.ts} +1 -1
- package/index.ts +193 -69
- package/openclaw.plugin.json +158 -2
- package/package.json +16 -16
- package/src/{ack-reaction-classifier.ts → ack-reaction/ack-reaction-classifier.ts} +85 -13
- package/src/{ack-reaction-service.ts → ack-reaction/ack-reaction-service.ts} +13 -7
- package/src/ack-reaction/dynamic-ack-reaction-controller.ts +46 -31
- package/src/ack-reaction/dynamic-ack-reaction-progress.ts +4 -1
- package/src/card/ask-user-question-context.ts +1 -1
- package/src/card/ask-user-question-store.ts +2 -2
- package/src/card/ask-user-question.ts +11 -7
- package/src/card/card-action-handler.ts +2 -2
- package/src/{card-callback-service.ts → card/card-callback-service.ts} +7 -9
- package/src/card/card-draft-controller.ts +770 -0
- package/src/card/card-markdown-image-reroute.ts +8 -2
- package/src/card/card-run-registry.ts +17 -7
- package/src/{card-service.ts → card/card-service.ts} +91 -61
- package/src/card/card-stop-handler.ts +12 -8
- package/src/card/card-streaming-mode.ts +1 -1
- package/src/card/card-task-progress.ts +330 -0
- package/src/card/draft-stream-loop.ts +119 -0
- package/src/card/reasoning-answer-split.ts +125 -121
- package/src/card/reasoning-block-assembler.ts +122 -123
- package/src/{run-usage-store.ts → card/run-usage-store.ts} +18 -6
- package/src/card/statusline-renderer.ts +14 -5
- package/src/card/task-model-metadata.ts +8 -3
- package/src/channel.ts +14 -16
- package/src/command/card-stop-command.ts +2 -2
- package/src/{feedback-learning-service.ts → command/feedback-learning-service.ts} +36 -16
- package/src/{feedback-learning-store.ts → command/feedback-learning-store.ts} +133 -81
- package/src/command/inbound-command-dispatch-service.ts +12 -6
- package/src/{learning-command-service.ts → command/learning-command-service.ts} +36 -29
- package/src/{session-command-service.ts → command/session-command-service.ts} +3 -1
- package/src/gateway/channel-gateway.ts +106 -28
- package/src/{connection-manager.ts → gateway/connection-manager.ts} +45 -30
- package/src/{docs-service.ts → gateway/docs-service.ts} +5 -5
- package/src/{inbound-handler.ts → gateway/inbound-handler.ts} +144 -85
- package/src/gateway/inbound-session-queue-dispatcher.ts +18 -14
- package/src/{session-lock.ts → gateway/session-lock.ts} +14 -14
- package/src/messaging/attachment-text-extractor.ts +21 -7
- package/src/messaging/btw-deliver.ts +2 -2
- package/src/messaging/channel-actions.ts +15 -7
- package/src/messaging/channel-outbound.ts +16 -11
- package/src/messaging/inline-directives.ts +342 -0
- package/src/{media-utils.ts → messaging/media-utils.ts} +276 -72
- package/src/{message-context-store.ts → messaging/message-context-store.ts} +11 -2
- package/src/{message-utils.ts → messaging/message-utils.ts} +46 -11
- package/src/messaging/quoted-context.ts +2 -2
- package/src/messaging/quoted-file-service.ts +311 -294
- package/src/messaging/quoted-ref.ts +11 -7
- package/src/{reply-strategy-card.ts → messaging/reply-strategy-card.ts} +178 -101
- package/src/{reply-strategy-markdown.ts → messaging/reply-strategy-markdown.ts} +8 -2
- package/src/{reply-strategy-types.ts → messaging/reply-strategy-types.ts} +11 -1
- package/src/{reply-strategy-with-reaction.ts → messaging/reply-strategy-with-reaction.ts} +6 -1
- package/src/{reply-strategy.ts → messaging/reply-strategy.ts} +2 -2
- package/src/{send-service.ts → messaging/send-service.ts} +100 -65
- package/src/{auth.ts → platform/auth.ts} +2 -2
- package/src/platform/channel-status.ts +3 -3
- package/src/{config-schema.ts → platform/config-schema.ts} +81 -4
- package/src/{config.ts → platform/config.ts} +167 -19
- package/src/{device-registration.ts → platform/device-registration.ts} +5 -4
- package/src/{onboarding.ts → platform/onboarding.ts} +1 -4
- package/src/{plugin-sdk-channel-actions-augment.ts → platform/plugin-sdk-channel-actions-augment.ts} +6 -5
- package/src/platform/runtime-events.ts +202 -0
- package/src/{secret-input.ts → platform/secret-input.ts} +117 -11
- package/src/{types.ts → platform/types.ts} +77 -2
- package/src/{http-client.ts → shared/http-client.ts} +1 -3
- package/src/{persistence-store.ts → shared/persistence-store.ts} +6 -6
- package/src/{utils.ts → shared/utils.ts} +30 -13
- package/src/targeting/agent-name-matcher.ts +1 -1
- package/src/targeting/agent-routing.ts +7 -7
- package/src/targeting/group-members-store.ts +1 -1
- package/src/{session-peer-store.ts → targeting/session-peer-store.ts} +9 -4
- package/src/{session-routing.ts → targeting/session-routing.ts} +1 -1
- package/src/targeting/target-directory-adapter.ts +3 -3
- package/src/targeting/target-directory-store.ts +1 -1
- package/src/targeting/target-input.ts +1 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-events.d.ts +0 -35
- package/dist/src/config.d.ts +0 -59
- package/src/ack-reaction/dynamic-ack-reaction-events.ts +0 -123
- package/src/card-draft-controller.ts +0 -637
- package/src/draft-stream-loop.ts +0 -119
- /package/dist/src/{ack-reaction-classifier.d.ts → ack-reaction/ack-reaction-classifier.d.ts} +0 -0
- /package/dist/src/{card-callback-service.d.ts → card/card-callback-service.d.ts} +0 -0
- /package/dist/src/{draft-stream-loop.d.ts → card/draft-stream-loop.d.ts} +0 -0
- /package/dist/src/{run-usage-store.d.ts → card/run-usage-store.d.ts} +0 -0
- /package/dist/src/{feedback-learning-store.d.ts → command/feedback-learning-store.d.ts} +0 -0
- /package/dist/src/{session-command-service.d.ts → command/session-command-service.d.ts} +0 -0
- /package/dist/src/{session-lock.d.ts → gateway/session-lock.d.ts} +0 -0
- /package/dist/src/{proactive-risk-registry.d.ts → messaging/proactive-risk-registry.d.ts} +0 -0
- /package/dist/src/{reply-strategy-markdown.d.ts → messaging/reply-strategy-markdown.d.ts} +0 -0
- /package/dist/src/{access-control.d.ts → platform/access-control.d.ts} +0 -0
- /package/dist/src/{auth.d.ts → platform/auth.d.ts} +0 -0
- /package/dist/src/{config-schema.d.ts → platform/config-schema.d.ts} +0 -0
- /package/dist/src/{device-registration.d.ts → platform/device-registration.d.ts} +0 -0
- /package/dist/src/{logger-context.d.ts → platform/logger-context.d.ts} +0 -0
- /package/dist/src/{onboarding.d.ts → platform/onboarding.d.ts} +0 -0
- /package/dist/src/{plugin-sdk-channel-actions-augment.d.ts → platform/plugin-sdk-channel-actions-augment.d.ts} +0 -0
- /package/dist/src/{runtime.d.ts → platform/runtime.d.ts} +0 -0
- /package/dist/src/{secret-input.d.ts → platform/secret-input.d.ts} +0 -0
- /package/dist/src/{session-state.d.ts → platform/session-state.d.ts} +0 -0
- /package/dist/src/{signature.d.ts → platform/signature.d.ts} +0 -0
- /package/dist/src/{dedup.d.ts → shared/dedup.d.ts} +0 -0
- /package/dist/src/{http-client.d.ts → shared/http-client.d.ts} +0 -0
- /package/dist/src/{path-utils.d.ts → shared/path-utils.d.ts} +0 -0
- /package/dist/src/{peer-id-registry.d.ts → targeting/peer-id-registry.d.ts} +0 -0
- /package/dist/src/{session-peer-store.d.ts → targeting/session-peer-store.d.ts} +0 -0
- /package/src/{proactive-risk-registry.ts → messaging/proactive-risk-registry.ts} +0 -0
- /package/src/{access-control.ts → platform/access-control.ts} +0 -0
- /package/src/{logger-context.ts → platform/logger-context.ts} +0 -0
- /package/src/{runtime.ts → platform/runtime.ts} +0 -0
- /package/src/{session-state.ts → platform/session-state.ts} +0 -0
- /package/src/{signature.ts → platform/signature.ts} +0 -0
- /package/src/{dedup.ts → shared/dedup.ts} +0 -0
- /package/src/{path-utils.ts → shared/path-utils.ts} +0 -0
- /package/src/{peer-id-registry.ts → targeting/peer-id-registry.ts} +0 -0
|
@@ -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
|
+
}
|