@soimy/dingtalk 3.4.0 → 3.4.2
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/index.ts +71 -66
- package/package.json +4 -4
- package/src/channel.ts +47 -20
- package/src/config.ts +1 -1
- package/src/connection-manager.ts +16 -5
- package/src/media-utils.ts +99 -36
- package/src/message-utils.ts +1 -1
- package/src/onboarding.ts +372 -317
- package/src/runtime.ts +5 -7
- package/src/send-service.ts +24 -10
- package/src/targeting/agent-name-matcher.ts +1 -1
- package/src/targeting/agent-routing.ts +1 -1
- package/src/targeting/target-directory-adapter.ts +2 -1
- package/src/types.ts +11 -13
package/src/runtime.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
const runtimeStore = createPluginRuntimeStore<PluginRuntime>("DingTalk runtime not initialized");
|
|
4
5
|
|
|
5
6
|
export function setDingTalkRuntime(next: PluginRuntime): void {
|
|
6
|
-
|
|
7
|
+
runtimeStore.setRuntime(next);
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function getDingTalkRuntime(): PluginRuntime {
|
|
10
|
-
|
|
11
|
-
throw new Error("DingTalk runtime not initialized");
|
|
12
|
-
}
|
|
13
|
-
return runtime;
|
|
11
|
+
return runtimeStore.getRuntime();
|
|
14
12
|
}
|
package/src/send-service.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./card-service";
|
|
9
9
|
import { stripTargetPrefix } from "./config";
|
|
10
10
|
import { getLogger } from "./logger-context";
|
|
11
|
-
import { getVoiceDurationMs, uploadMedia as uploadMediaUtil } from "./media-utils";
|
|
11
|
+
import { getVoiceDurationMs, uploadMedia as uploadMediaUtil, type UploadMediaResult } from "./media-utils";
|
|
12
12
|
import { convertMarkdownTablesToPlainText, detectMarkdownAndExtractTitle } from "./message-utils";
|
|
13
13
|
import { DEFAULT_MESSAGE_CONTEXT_TTL_DAYS, upsertOutboundMessageContext } from "./message-context-store";
|
|
14
14
|
import { resolveOriginalPeerId } from "./peer-id-registry";
|
|
@@ -203,14 +203,16 @@ function isProactivePermissionOrScopeError(code: string | null): boolean {
|
|
|
203
203
|
|
|
204
204
|
/**
|
|
205
205
|
* Wrapper to upload media with shared getAccessToken binding.
|
|
206
|
+
* Supports sandbox/container paths via mediaLocalRoots option.
|
|
206
207
|
*/
|
|
207
208
|
export async function uploadMedia(
|
|
208
209
|
config: DingTalkConfig,
|
|
209
210
|
mediaPath: string,
|
|
210
211
|
mediaType: "image" | "voice" | "video" | "file",
|
|
211
212
|
log?: Logger,
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
options?: { mediaLocalRoots?: string[] },
|
|
214
|
+
): Promise<UploadMediaResult | null> {
|
|
215
|
+
return uploadMediaUtil(config, mediaPath, mediaType, getAccessToken, log, options);
|
|
214
216
|
}
|
|
215
217
|
|
|
216
218
|
export async function sendProactiveTextOrMarkdown(
|
|
@@ -341,16 +343,19 @@ export async function sendProactiveMedia(
|
|
|
341
343
|
target: string,
|
|
342
344
|
mediaPath: string,
|
|
343
345
|
mediaType: "image" | "voice" | "video" | "file",
|
|
344
|
-
options: SendMessageOptions & { accountId?: string } = {},
|
|
346
|
+
options: SendMessageOptions & { accountId?: string; mediaLocalRoots?: string[] } = {},
|
|
345
347
|
): Promise<{ ok: boolean; error?: string; data?: any; messageId?: string }> {
|
|
346
348
|
const log = options.log || getLogger();
|
|
347
349
|
|
|
348
350
|
try {
|
|
349
351
|
// Upload first, then send by media_id.
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
+
const uploadResult = await uploadMedia(config, mediaPath, mediaType, log, {
|
|
353
|
+
mediaLocalRoots: options.mediaLocalRoots,
|
|
354
|
+
});
|
|
355
|
+
if (!uploadResult) {
|
|
352
356
|
return { ok: false, error: "Failed to upload media" };
|
|
353
357
|
}
|
|
358
|
+
const { mediaId, buffer: uploadedBuffer } = uploadResult;
|
|
354
359
|
|
|
355
360
|
const token = await getAccessToken(config, log);
|
|
356
361
|
const { targetId, isExplicitUser } = stripTargetPrefix(target);
|
|
@@ -371,7 +376,10 @@ export async function sendProactiveMedia(
|
|
|
371
376
|
msgParam = JSON.stringify({ photoURL: mediaId });
|
|
372
377
|
} else if (mediaType === "voice") {
|
|
373
378
|
msgKey = "sampleAudio";
|
|
374
|
-
|
|
379
|
+
// Reuse buffer from upload to avoid reading the file twice
|
|
380
|
+
const durationMs = await getVoiceDurationMs(mediaPath, mediaType, log, {
|
|
381
|
+
preReadBuffer: uploadedBuffer,
|
|
382
|
+
});
|
|
375
383
|
msgParam = JSON.stringify({ mediaId, duration: String(durationMs) });
|
|
376
384
|
} else {
|
|
377
385
|
// sampleVideo requires picMediaId; fallback to sampleFile for broader compatibility.
|
|
@@ -497,14 +505,20 @@ export async function sendBySession(
|
|
|
497
505
|
|
|
498
506
|
// Session webhook supports native media messages; prefer that when media info is available.
|
|
499
507
|
if (options.mediaPath && options.mediaType) {
|
|
500
|
-
const
|
|
501
|
-
|
|
508
|
+
const uploadResult = await uploadMedia(config, options.mediaPath, options.mediaType, log, {
|
|
509
|
+
mediaLocalRoots: options.mediaLocalRoots,
|
|
510
|
+
});
|
|
511
|
+
if (uploadResult) {
|
|
512
|
+
const { mediaId, buffer: uploadedBuffer } = uploadResult;
|
|
502
513
|
let body: any;
|
|
503
514
|
|
|
504
515
|
if (options.mediaType === "image") {
|
|
505
516
|
body = { msgtype: "image", image: { media_id: mediaId } };
|
|
506
517
|
} else if (options.mediaType === "voice") {
|
|
507
|
-
|
|
518
|
+
// Reuse buffer from upload to avoid reading the file twice
|
|
519
|
+
const durationMs = await getVoiceDurationMs(options.mediaPath, options.mediaType, log, {
|
|
520
|
+
preReadBuffer: uploadedBuffer,
|
|
521
|
+
});
|
|
508
522
|
body = { msgtype: "voice", voice: { media_id: mediaId, duration: String(durationMs) } };
|
|
509
523
|
} else if (options.mediaType === "video") {
|
|
510
524
|
body = { msgtype: "video", video: { media_id: mediaId } };
|
|
@@ -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
|
|
|
7
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
7
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
8
8
|
import type { AtMention, AgentNameMatch } from "../types";
|
|
9
9
|
|
|
10
10
|
interface AgentConfig {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* (channel + accountId + peer), not content-based dynamic routing.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
10
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
11
11
|
import { resolveAtAgents } from "./agent-name-matcher";
|
|
12
12
|
import { parseLearnCommand } from "../learning-command-service";
|
|
13
13
|
import { getDingTalkRuntime } from "../runtime";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk/directory-runtime";
|
|
2
3
|
import { getConfig, stripTargetPrefix } from "../config";
|
|
3
4
|
import { resolveOriginalPeerId } from "../peer-id-registry";
|
|
4
5
|
import { getDingTalkRuntime } from "../runtime";
|
package/src/types.ts
CHANGED
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
|
+
ChannelPlugin as SDKChannelPlugin,
|
|
13
14
|
OpenClawConfig,
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
} from "openclaw/plugin-sdk/core";
|
|
16
|
+
import type {
|
|
16
17
|
ChannelAccountSnapshot as SDKChannelAccountSnapshot,
|
|
17
18
|
ChannelGatewayContext as SDKChannelGatewayContext,
|
|
18
|
-
|
|
19
|
-
} from "openclaw/plugin-sdk";
|
|
19
|
+
ChannelLogSink as SDKChannelLogSink,
|
|
20
|
+
} from "openclaw/plugin-sdk/channel-runtime";
|
|
21
|
+
import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
|
|
20
22
|
import { mergeAccountWithDefaults } from "./config";
|
|
21
23
|
|
|
22
24
|
export type AckReactionMode = "off" | "emoji" | "kaomoji";
|
|
@@ -24,14 +26,6 @@ export type AckReactionMode = "off" | "emoji" | "kaomoji";
|
|
|
24
26
|
// explicit modes remain: "off" | "emoji" | "kaomoji".
|
|
25
27
|
export type AckReactionConfigValue = string;
|
|
26
28
|
|
|
27
|
-
export interface DingtalkPluginModule {
|
|
28
|
-
id: string;
|
|
29
|
-
name: string;
|
|
30
|
-
description?: string;
|
|
31
|
-
configSchema?: unknown;
|
|
32
|
-
register?: (api: OpenClawPluginApi) => void | Promise<void>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
29
|
/**
|
|
36
30
|
* DingTalk channel configuration (extends base OpenClaw config)
|
|
37
31
|
*/
|
|
@@ -393,6 +387,8 @@ export interface SendMessageOptions {
|
|
|
393
387
|
/** Force markdown/text delivery even when messageType is "card". Bypasses card
|
|
394
388
|
* creation while preserving journal writes and other side-effects. */
|
|
395
389
|
forceMarkdown?: boolean;
|
|
390
|
+
/** Allowed local roots for sandbox/container media path resolution. */
|
|
391
|
+
mediaLocalRoots?: string[];
|
|
396
392
|
}
|
|
397
393
|
|
|
398
394
|
export interface DingTalkTrackingMetadata {
|
|
@@ -579,7 +575,9 @@ export interface GatewayStopResult {
|
|
|
579
575
|
/**
|
|
580
576
|
* DingTalk channel plugin definition
|
|
581
577
|
*/
|
|
582
|
-
export type DingTalkChannelPlugin = SDKChannelPlugin<ResolvedAccount & { configured: boolean }
|
|
578
|
+
export type DingTalkChannelPlugin = SDKChannelPlugin<ResolvedAccount & { configured: boolean }> & {
|
|
579
|
+
setupWizard?: ChannelSetupWizard;
|
|
580
|
+
};
|
|
583
581
|
|
|
584
582
|
/**
|
|
585
583
|
* Result of target resolution validation
|