@a2hmarket/a2hmarket 0.2.0 → 0.2.1

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 CHANGED
@@ -26,7 +26,7 @@ import { startAgentService } from "./src/agent-service.js";
26
26
  import { initReplyBridge } from "./src/reply-bridge.js";
27
27
 
28
28
  // Tool registrations
29
- import { registerStatusTool } from "./src/tools/status.js";
29
+ import { registerStatusTool, registerInboxHistoryTool } from "./src/tools/status.js";
30
30
  import { registerProfileTools } from "./src/tools/profile.js";
31
31
  import { registerFileTools } from "./src/tools/file.js";
32
32
  import { registerWorksTools } from "./src/tools/works.js";
@@ -61,6 +61,7 @@ export default {
61
61
  registerWorksTools(api, apiClient);
62
62
  registerOrderTools(api, apiClient);
63
63
  registerSendTool(api, creds);
64
+ registerInboxHistoryTool(api, apiClient);
64
65
  }
65
66
 
66
67
  // ── Track last channel when a2h_* tools are used ─────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a2hmarket/a2hmarket",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "A2H Market OpenClaw plugin — AI agent marketplace with A2A messaging via MQTT.",
6
6
  "license": "MIT-0",
@@ -24,6 +24,11 @@ export class MqttListener {
24
24
  private handler: MessageHandler | null = null;
25
25
  private log: { info: (m: string) => void; error: (m: string) => void; warn: (m: string) => void };
26
26
 
27
+ /** Rapid-reconnect detection: timestamps of recent reconnects within the sliding window. */
28
+ private reconnectTimestamps: number[] = [];
29
+ private static readonly RECONNECT_WINDOW_MS = 60_000; // 60 seconds
30
+ private static readonly RECONNECT_THRESHOLD = 5;
31
+
27
32
  constructor(
28
33
  creds: A2HCredentials,
29
34
  log?: { info: (m: string) => void; error: (m: string) => void; warn: (m: string) => void },
@@ -57,8 +62,21 @@ export class MqttListener {
57
62
  });
58
63
 
59
64
  this.transport.onReconnect(() => {
60
- // Reconnects are expected when another client uses the same clientId (e.g. mobile app).
61
- // Log at warn level but don't flood — this is a known RocketMQ P2P clientId constraint.
65
+ const now = Date.now();
66
+ // Prune timestamps outside the sliding window
67
+ this.reconnectTimestamps = this.reconnectTimestamps.filter(
68
+ (ts) => now - ts < MqttListener.RECONNECT_WINDOW_MS,
69
+ );
70
+ this.reconnectTimestamps.push(now);
71
+
72
+ if (this.reconnectTimestamps.length >= MqttListener.RECONNECT_THRESHOLD) {
73
+ this.log.warn(
74
+ `clientId conflict detected: ${this.reconnectTimestamps.length} reconnects in the last 60s. ` +
75
+ `Another client (mobile app, second instance) is likely using the same clientId ` +
76
+ `"${buildClientId(this.creds.agentId)}". RocketMQ will keep kicking both clients. ` +
77
+ `Stop the other client or run on a different agentId.`,
78
+ );
79
+ }
62
80
  });
63
81
 
64
82
  try {
package/src/tools/send.ts CHANGED
@@ -8,19 +8,34 @@ export function registerSendTool(api: OpenClawPluginApi, creds: A2HCredentials)
8
8
  api.registerTool({
9
9
  name: "a2h_send",
10
10
  description:
11
- "Send an A2A message to another agent. Supports text, attachment URL, payment QR code, and structured payload.",
11
+ "Send an A2A message to another agent. Supports text, payment QR code (payload.payment_qr), " +
12
+ "external attachment (payload.attachment with url/source/name/mime_type), and arbitrary extra payload fields. " +
13
+ "payment_qr and attachment can coexist in the same message.",
12
14
  parameters: {
13
15
  type: "object",
14
16
  properties: {
15
17
  target_agent_id: { type: "string", description: "Target agent ID" },
16
- text: { type: "string", description: "Message text" },
17
- payment_qr: { type: "string", description: "Payment QR code image URL" },
18
- attachment_url: { type: "string", description: "External attachment URL" },
19
- attachment_name: { type: "string", description: "Filename hint for attachment" },
18
+ text: { type: "string", description: "Message text (sets payload.text)" },
19
+ payment_qr: {
20
+ type: "string",
21
+ description: "Payment QR code image URL (must start with http:// or https://). Sets payload.payment_qr",
22
+ },
23
+ attachment_url: {
24
+ type: "string",
25
+ description: "External attachment URL (must start with http:// or https://). Creates payload.attachment object",
26
+ },
27
+ attachment_name: {
28
+ type: "string",
29
+ description: "Filename hint for attachment (used as payload.attachment.name)",
30
+ },
31
+ attachment_mime: {
32
+ type: "string",
33
+ description: "MIME type hint for attachment (e.g. image/png, application/pdf). Sets payload.attachment.mime_type",
34
+ },
20
35
  message_type: { type: "string", description: "Message type (default: chat.request)" },
21
36
  extra_payload: {
22
37
  type: "object",
23
- description: "Additional payload fields (e.g. orderId)",
38
+ description: "Additional payload fields merged into the envelope payload (e.g. orderId)",
24
39
  },
25
40
  },
26
41
  required: ["target_agent_id"],
@@ -42,6 +57,7 @@ export function registerSendTool(api: OpenClawPluginApi, creds: A2HCredentials)
42
57
  source: "external",
43
58
  };
44
59
  if (params.attachment_name) att.name = params.attachment_name;
60
+ if (params.attachment_mime) att.mime_type = params.attachment_mime;
45
61
  payload.attachment = att;
46
62
  }
47
63
 
@@ -24,3 +24,60 @@ export function registerStatusTool(api: OpenClawPluginApi, client: A2HApiClient)
24
24
  },
25
25
  });
26
26
  }
27
+
28
+ interface InboxMessage {
29
+ id?: string;
30
+ messageId?: string;
31
+ senderId?: string;
32
+ content?: string;
33
+ createdAt?: string;
34
+ createTime?: string;
35
+ }
36
+
37
+ export function registerInboxHistoryTool(api: OpenClawPluginApi, client: A2HApiClient) {
38
+ api.registerTool({
39
+ name: "a2h_inbox_history",
40
+ description:
41
+ "Retrieve message history with a specific agent. Returns messages in reverse chronological order.",
42
+ parameters: {
43
+ type: "object",
44
+ properties: {
45
+ peer_id: { type: "string", description: "The other agent's ID" },
46
+ page: { type: "number", description: "Page number (default 1)" },
47
+ limit: { type: "number", description: "Messages per page (default 20, max 100)" },
48
+ },
49
+ required: ["peer_id"],
50
+ },
51
+ execute: async (params: Record<string, unknown>) => {
52
+ const peerId = params.peer_id as string;
53
+ const page = (params.page as number) || 1;
54
+ const limit = Math.min((params.limit as number) || 20, 100);
55
+
56
+ const sessionId = [client.agentId, peerId].sort().join("_");
57
+ const qs = `?sessionId=${sessionId}&page=${page}&limit=${limit}`;
58
+ const apiPath = `/agent-message/api/v1/agents/sessions/messages${qs}`;
59
+ const signPath = "/agent-message/api/v1/agents/sessions/messages";
60
+
61
+ const data = await client.getJSON<{ records?: InboxMessage[] }>(apiPath, signPath);
62
+ const records = (data as unknown as { records?: InboxMessage[] })?.records ?? [];
63
+
64
+ const messages = records.map((msg) => {
65
+ let text = "";
66
+ try {
67
+ const envelope = JSON.parse(msg.content ?? "{}");
68
+ text = envelope?.payload?.text ?? "";
69
+ } catch {
70
+ text = msg.content ?? "";
71
+ }
72
+ return {
73
+ message_id: msg.messageId ?? msg.id ?? "",
74
+ direction: msg.senderId === client.agentId ? "sent" : "recv",
75
+ timestamp: msg.createdAt ?? msg.createTime ?? "",
76
+ text,
77
+ };
78
+ });
79
+
80
+ return { result: JSON.stringify({ session_id: sessionId, page, limit, messages }, null, 2) };
81
+ },
82
+ });
83
+ }