@wzfukui/ani 2026.3.28 → 2026.3.281
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/package.json +1 -1
- package/src/monitor/handler.ts +41 -2
- package/src/monitor/send.ts +40 -0
- package/src/tools.ts +3 -1
package/package.json
CHANGED
package/src/monitor/handler.ts
CHANGED
|
@@ -11,10 +11,12 @@ import {
|
|
|
11
11
|
sendAniMessage,
|
|
12
12
|
sendAniProgress,
|
|
13
13
|
fetchConversation,
|
|
14
|
+
fetchAniMessage,
|
|
14
15
|
fetchConversationMemories,
|
|
15
16
|
toggleAniReaction,
|
|
16
17
|
type AniArtifact,
|
|
17
18
|
type AniConversation,
|
|
19
|
+
type AniMessage,
|
|
18
20
|
type AniMemory,
|
|
19
21
|
} from "./send.js";
|
|
20
22
|
import { createInboundDebouncer } from "./debounce.js";
|
|
@@ -27,6 +29,7 @@ export type AniWsMessage = {
|
|
|
27
29
|
conversation_id?: number;
|
|
28
30
|
sender_id?: number;
|
|
29
31
|
sender_type?: string;
|
|
32
|
+
reply_to?: number;
|
|
30
33
|
layers?: {
|
|
31
34
|
summary?: string;
|
|
32
35
|
detail?: string;
|
|
@@ -164,6 +167,33 @@ export function isTextFile(mimeType?: string, filename?: string): boolean {
|
|
|
164
167
|
return false;
|
|
165
168
|
}
|
|
166
169
|
|
|
170
|
+
export function messageTextOf(message: Pick<AniMessage, "layers"> | null | undefined): string {
|
|
171
|
+
const dataBody = typeof message?.layers?.data === "object" && message.layers?.data !== null
|
|
172
|
+
? (message.layers.data as Record<string, unknown>).body
|
|
173
|
+
: undefined;
|
|
174
|
+
return (typeof dataBody === "string" ? dataBody : null) ??
|
|
175
|
+
message?.layers?.summary ??
|
|
176
|
+
message?.layers?.detail ??
|
|
177
|
+
"";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function formatReplyContext(message: AniMessage | null | undefined): string {
|
|
181
|
+
if (!message?.id) return "";
|
|
182
|
+
const senderName = message.sender?.display_name ?? `entity-${message.sender_id ?? "unknown"}`;
|
|
183
|
+
const sentAt = message.created_at ? ` at ${message.created_at}` : "";
|
|
184
|
+
const text = messageTextOf(message).trim();
|
|
185
|
+
const attachments = message.attachments ?? [];
|
|
186
|
+
const attachmentText = attachments.length > 0
|
|
187
|
+
? ` Attachments: ${attachments.map((attachment) => attachment.filename || attachment.type || "file").join(", ")}.`
|
|
188
|
+
: "";
|
|
189
|
+
const excerpt = text ? ` ${text}` : "";
|
|
190
|
+
return [
|
|
191
|
+
"Reply context:",
|
|
192
|
+
`This message replies to ANI message #${message.id} from ${senderName}${sentAt}.`,
|
|
193
|
+
`${excerpt}${attachmentText}`.trim(),
|
|
194
|
+
].filter(Boolean).join("\n");
|
|
195
|
+
}
|
|
196
|
+
|
|
167
197
|
function formatFileSize(bytes?: number): string {
|
|
168
198
|
if (bytes == null) return 'unknown size';
|
|
169
199
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -674,7 +704,16 @@ export function createAniMessageHandler(params: AniHandlerParams) {
|
|
|
674
704
|
sessionKey: route.sessionKey,
|
|
675
705
|
});
|
|
676
706
|
|
|
677
|
-
|
|
707
|
+
let replyContext = "";
|
|
708
|
+
if (msg.reply_to) {
|
|
709
|
+
const parentMessage = await fetchAniMessage({ serverUrl, apiKey, messageId: msg.reply_to });
|
|
710
|
+
replyContext = parentMessage
|
|
711
|
+
? formatReplyContext(parentMessage)
|
|
712
|
+
: `Reply context:\nThis message replies to ANI message #${msg.reply_to}, but the original message could not be fetched. Use ani_fetch_chat_history_messages if that missing context matters.`;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const rawBodyParts = [replyContext, text, attachmentText].filter((part) => part && part.trim());
|
|
716
|
+
const rawBody = rawBodyParts.join("\n\n");
|
|
678
717
|
logVerbose(`ani: rawBody for envelope (${rawBody.length} chars): ${rawBody.slice(0, 500)}`);
|
|
679
718
|
|
|
680
719
|
const body = core.channel.reply.formatAgentEnvelope({
|
|
@@ -989,7 +1028,7 @@ export function createAniMessageHandler(params: AniHandlerParams) {
|
|
|
989
1028
|
const dataBody = typeof msg.layers?.data === "object" && msg.layers?.data !== null
|
|
990
1029
|
? (msg.layers.data as Record<string, unknown>).body
|
|
991
1030
|
: undefined;
|
|
992
|
-
const text = (
|
|
1031
|
+
const text = messageTextOf(msg);
|
|
993
1032
|
|
|
994
1033
|
// Process attachments:
|
|
995
1034
|
// 1. Download and save to disk for OpenClaw media pipeline (MediaPath/MediaType)
|
package/src/monitor/send.ts
CHANGED
|
@@ -119,6 +119,26 @@ export interface AniAttachment {
|
|
|
119
119
|
content?: string;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
export interface AniMessage {
|
|
123
|
+
id?: number;
|
|
124
|
+
conversation_id?: number;
|
|
125
|
+
sender_id?: number;
|
|
126
|
+
sender_type?: string;
|
|
127
|
+
reply_to?: number;
|
|
128
|
+
created_at?: string;
|
|
129
|
+
sender?: {
|
|
130
|
+
id?: number;
|
|
131
|
+
display_name?: string;
|
|
132
|
+
entity_type?: string;
|
|
133
|
+
};
|
|
134
|
+
layers?: {
|
|
135
|
+
summary?: string;
|
|
136
|
+
detail?: string;
|
|
137
|
+
data?: unknown;
|
|
138
|
+
};
|
|
139
|
+
attachments?: AniAttachment[];
|
|
140
|
+
}
|
|
141
|
+
|
|
122
142
|
/** Send a message to an ANI conversation via REST API. */
|
|
123
143
|
export async function sendAniMessage(opts: {
|
|
124
144
|
serverUrl: string;
|
|
@@ -211,6 +231,26 @@ export async function fetchConversation(opts: {
|
|
|
211
231
|
}
|
|
212
232
|
}
|
|
213
233
|
|
|
234
|
+
/** Fetch a single message with sender details for reply-context reconstruction. */
|
|
235
|
+
export async function fetchAniMessage(opts: {
|
|
236
|
+
serverUrl: string;
|
|
237
|
+
apiKey: string;
|
|
238
|
+
messageId: number;
|
|
239
|
+
}): Promise<AniMessage | null> {
|
|
240
|
+
const url = `${opts.serverUrl}/api/v1/messages/${opts.messageId}`;
|
|
241
|
+
try {
|
|
242
|
+
const res = await fetchWithRetry(url, {
|
|
243
|
+
headers: { Authorization: `Bearer ${opts.apiKey}` },
|
|
244
|
+
signal: AbortSignal.timeout(30_000),
|
|
245
|
+
});
|
|
246
|
+
if (!res.ok) return null;
|
|
247
|
+
const json = (await res.json()) as { data?: AniMessage };
|
|
248
|
+
return json.data ?? null;
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
214
254
|
/** Fetch conversation memories. */
|
|
215
255
|
export async function fetchConversationMemories(opts: {
|
|
216
256
|
serverUrl: string;
|
package/src/tools.ts
CHANGED
|
@@ -261,14 +261,16 @@ export function createGetHistoryTool(): ChannelAgentTool {
|
|
|
261
261
|
|
|
262
262
|
// Format messages for LLM readability
|
|
263
263
|
const formatted = messages.map((m: Record<string, unknown>) => {
|
|
264
|
+
const id = typeof m.id === "number" ? `#${m.id}` : "#?";
|
|
264
265
|
const sender = (m.sender as Record<string, unknown>)?.display_name ?? `entity-${m.sender_id}`;
|
|
265
266
|
const text = ((m.layers as Record<string, unknown>)?.summary as string) ?? "";
|
|
266
267
|
const time = m.created_at as string;
|
|
268
|
+
const replyTo = typeof m.reply_to === "number" ? ` ↪ reply_to #${m.reply_to}` : "";
|
|
267
269
|
const atts = (m.attachments as Array<Record<string, unknown>>) ?? [];
|
|
268
270
|
const attDesc = atts.length > 0
|
|
269
271
|
? ` [${atts.length} attachment(s): ${atts.map((a) => a.filename ?? a.type).join(", ")}]`
|
|
270
272
|
: "";
|
|
271
|
-
return `[${time}] ${sender}: ${text}${attDesc}`;
|
|
273
|
+
return `[${time}] ${id}${replyTo} ${sender}: ${text}${attDesc}`;
|
|
272
274
|
}).reverse(); // oldest first for readability
|
|
273
275
|
|
|
274
276
|
return {
|