@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/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
- let runtime: PluginRuntime | null = null;
4
+ const runtimeStore = createPluginRuntimeStore<PluginRuntime>("DingTalk runtime not initialized");
4
5
 
5
6
  export function setDingTalkRuntime(next: PluginRuntime): void {
6
- runtime = next;
7
+ runtimeStore.setRuntime(next);
7
8
  }
8
9
 
9
10
  export function getDingTalkRuntime(): PluginRuntime {
10
- if (!runtime) {
11
- throw new Error("DingTalk runtime not initialized");
12
- }
13
- return runtime;
11
+ return runtimeStore.getRuntime();
14
12
  }
@@ -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
- ): Promise<string | null> {
213
- return uploadMediaUtil(config, mediaPath, mediaType, getAccessToken, log);
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 mediaId = await uploadMedia(config, mediaPath, mediaType, log);
351
- if (!mediaId) {
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
- const durationMs = await getVoiceDurationMs(mediaPath, mediaType, log);
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 mediaId = await uploadMedia(config, options.mediaPath, options.mediaType, log);
501
- if (mediaId) {
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
- const durationMs = await getVoiceDurationMs(options.mediaPath, options.mediaType, log);
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 { ChannelDirectoryEntry, OpenClawConfig } from "openclaw/plugin-sdk";
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
- OpenClawPluginApi,
15
- ChannelLogSink as SDKChannelLogSink,
15
+ } from "openclaw/plugin-sdk/core";
16
+ import type {
16
17
  ChannelAccountSnapshot as SDKChannelAccountSnapshot,
17
18
  ChannelGatewayContext as SDKChannelGatewayContext,
18
- ChannelPlugin as SDKChannelPlugin,
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