File size: 3,466 Bytes
e249c6d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import crypto from "node:crypto";
import { extractMessagingToolSend } from "../embedded-agent-messaging-extraction.js";
import { isMessagingToolTargetEvidenceAction } from "../embedded-agent-messaging.js";
import type { MessagingToolSend } from "../embedded-agent-messaging.types.js";
import {
  collectMessagingMediaUrlsFromRecord,
  collectMessagingMediaUrlsFromToolResult,
} from "../embedded-agent-tool-media.js";
import { stripOpenClawMcpToolPrefix } from "./tool-policy.js";
import type { PreparedCliRunContext } from "./types.js";

export const CLI_MESSAGING_EVIDENCE_MAX_CALLS = 64;

// One canonical prefix-strip implementation: the loopback transport prefix is
// a tool-policy concept shared with the embedded CLI-dispatch bridge, and
// drifting copies would desync tool-name correlation across those surfaces.
export const normalizeCliMessagingToolName = stripOpenClawMcpToolPrefix;

export function extractCliMessagingTarget(
  context: PreparedCliRunContext,
  toolName: string,
  args: Record<string, unknown>,
): MessagingToolSend | undefined {
  const normalizedToolName = normalizeCliMessagingToolName(toolName);
  const currentProvider = context.params.messageChannel ?? context.params.messageProvider;
  const hasExplicitProvider =
    (typeof args.provider === "string" && args.provider.trim().length > 0) ||
    (typeof args.channel === "string" && args.channel.trim().length > 0);
  const targetArgs =
    normalizedToolName === "message" && currentProvider && !hasExplicitProvider
      ? { ...args, provider: currentProvider }
      : args;
  if (!isMessagingToolTargetEvidenceAction(normalizedToolName, targetArgs)) {
    return undefined;
  }
  return extractMessagingToolSend(normalizedToolName, targetArgs, {
    config: context.params.config,
    currentChannelId: context.params.currentChannelId,
    currentThreadId: context.params.currentThreadTs,
    currentMessageId: context.params.currentMessageId,
  });
}

export function buildMessagingToolSendEvidenceKey(send: MessagingToolSend): string {
  return crypto
    .createHash("sha256")
    .update(
      JSON.stringify([
        send.tool,
        send.provider,
        send.accountId,
        send.to,
        send.threadId,
        send.threadImplicit,
        send.threadSuppressed,
        send.text,
        send.mediaUrls,
      ]),
    )
    .digest("hex");
}

export function extractCliMessagingContent(
  args: Record<string, unknown>,
  result: unknown,
): Pick<MessagingToolSend, "text" | "mediaUrls"> {
  const text = ["message", "SendMessage", "content", "text", "caption"]
    .map((key) => args[key])
    .find((value): value is string => typeof value === "string" && value.trim().length > 0);
  const mediaUrls = [
    ...collectMessagingMediaUrlsFromRecord(args),
    ...collectMessagingMediaUrlsFromToolResult(result),
  ].filter((url, index, all) => all.indexOf(url) === index);
  return {
    ...(text ? { text } : {}),
    ...(mediaUrls.length > 0 ? { mediaUrls } : {}),
  };
}

export function appendUniqueCliMessagingEvidence(
  values: string[],
  valueKeys: Set<string>,
  additions: readonly string[],
): void {
  for (const addition of additions) {
    if (!addition || valueKeys.has(addition)) {
      continue;
    }
    if (values.length >= CLI_MESSAGING_EVIDENCE_MAX_CALLS) {
      const removed = values.shift();
      if (removed) {
        valueKeys.delete(removed);
      }
    }
    values.push(addition);
    valueKeys.add(addition);
  }
}