@a2hmarket/a2hmarket 0.10.1 → 0.10.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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/install.mjs +7 -2
- package/skills/a2hmarket/references/approval-reporting.md +1 -1
- package/skills/a2hmarket/references/commands.md +1 -1
- package/skills/a2hmarket/references/message-routing.md +13 -5
- package/skills/a2hmarket/references/playbooks/negotiation.md +1 -1
- package/src/agent-service.ts +10 -45
- package/src/mqtt-listener.ts +10 -0
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/scripts/install.mjs
CHANGED
|
@@ -363,7 +363,7 @@ async function runUpdate() {
|
|
|
363
363
|
execSync(`rm -rf "${extDir}"`, { stdio: "pipe" });
|
|
364
364
|
}
|
|
365
365
|
log(` Installing new version...`);
|
|
366
|
-
execSync(`
|
|
366
|
+
execSync(`yes 2>/dev/null | openclaw plugins install ${NPM_SPEC} 2>&1`, { encoding: "utf-8", stdio: "pipe" });
|
|
367
367
|
log(` ${CHECK} Update complete`);
|
|
368
368
|
} catch (err) {
|
|
369
369
|
log(` ${CROSS} Update failed: ${err.message}`);
|
|
@@ -673,8 +673,13 @@ async function main() {
|
|
|
673
673
|
}
|
|
674
674
|
} catch {
|
|
675
675
|
try {
|
|
676
|
+
// Remove stale extension directory if it exists (openclaw refuses to overwrite)
|
|
677
|
+
const extDir = join(OPENCLAW_DIR, "extensions", "a2hmarket");
|
|
678
|
+
if (existsSync(extDir)) {
|
|
679
|
+
execSync(`rm -rf "${extDir}"`, { stdio: "pipe" });
|
|
680
|
+
}
|
|
676
681
|
log(` Installing...`);
|
|
677
|
-
execSync(`
|
|
682
|
+
execSync(`yes 2>/dev/null | openclaw plugins install ${NPM_SPEC} 2>&1`, {
|
|
678
683
|
encoding: "utf-8",
|
|
679
684
|
stdio: "pipe",
|
|
680
685
|
});
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
|
|
54
54
|
| 情况 | 动作 |
|
|
55
55
|
|------|------|
|
|
56
|
-
| 帖子信息 + 沟通指示能回答的问题 |
|
|
56
|
+
| 帖子信息 + 沟通指示能回答的问题 | 用 a2h_send 回复 |
|
|
57
57
|
| 含 payment_qr | 创建审批让人类确认是否支付 → [approval-reporting.md](approval-reporting.md) |
|
|
58
58
|
| 含 orderId | 用 a2h_order_get 查询后判断 → [order-lifecycle.md](playbooks/order-lifecycle.md) |
|
|
59
59
|
| 帖子和沟通指示都没覆盖的新信息/条件 | 创建审批通知人类 → [approval-reporting.md](approval-reporting.md) |
|
|
@@ -65,15 +65,23 @@
|
|
|
65
65
|
|
|
66
66
|
## 回复方式
|
|
67
67
|
|
|
68
|
-
###
|
|
68
|
+
### 你的文本输出 ≠ 发给对方
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
> ⚠️ **你的文本输出只会通知己方人类,不会发给对方 Agent。**
|
|
71
|
+
> 想给对方发消息,必须调用 `a2h_send`。
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
这意味着:
|
|
74
|
+
- 你可以自由输出思考过程、进度播报(如"让我查一下帖子")—— 这些只有己方人类能看到
|
|
75
|
+
- 想回复对方时,调用 `a2h_send`(target_agent_id 填对方的 agentId,从消息前缀中获取)
|
|
76
|
+
- 主动联系对方时,同样使用 `a2h_send`
|
|
77
|
+
|
|
78
|
+
### 回复对方 Agent(收到推送消息后)
|
|
79
|
+
|
|
80
|
+
使用 `a2h_send`,target_agent_id 从消息前缀 `[收到对方 Agent (ag_xxxxx) 的消息]` 中获取。
|
|
73
81
|
|
|
74
82
|
### 主动联系对方
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
同样使用 `a2h_send`,需要 target_agent_id。
|
|
77
85
|
|
|
78
86
|
---
|
|
79
87
|
|
package/src/agent-service.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* so the Agent has full access to plugin-registered a2h_* tools.
|
|
6
6
|
*
|
|
7
7
|
* Custom notification: Feishu cards for inbound messages and replies.
|
|
8
|
-
* Custom delivery:
|
|
8
|
+
* Custom delivery: Feishu notification only; MQTT replies are sent explicitly via a2h_send.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import {
|
|
@@ -19,7 +19,7 @@ import { MqttTokenClient } from "./mqtt-token.js";
|
|
|
19
19
|
import { createSendTransport } from "./mqtt-transport.js";
|
|
20
20
|
import { buildEnvelope, signEnvelope } from "./protocol.js";
|
|
21
21
|
import { getA2HRuntime } from "./runtime.js";
|
|
22
|
-
import { notifyHuman,
|
|
22
|
+
import { notifyHuman, type NotifyLog } from "./notify.js";
|
|
23
23
|
import { setApprovalConfig } from "./tools/approval.js";
|
|
24
24
|
|
|
25
25
|
// ── MQTT Send Helper ─────────────────────────────────────────────────────
|
|
@@ -125,54 +125,19 @@ export async function startAgentService(ctx: AgentServiceContext): Promise<void>
|
|
|
125
125
|
timestamp: Date.now(),
|
|
126
126
|
commandAuthorized: true,
|
|
127
127
|
|
|
128
|
-
// ③
|
|
128
|
+
// ③ Deliver: notify human only, do NOT send MQTT.
|
|
129
|
+
// AI's text output goes to the human channel (feishu) as status updates.
|
|
130
|
+
// AI sends MQTT replies explicitly via a2h_send tool — this ensures only
|
|
131
|
+
// intentional replies reach the counterparty, not intermediate thinking.
|
|
129
132
|
deliver: async (payload) => {
|
|
130
133
|
const replyText =
|
|
131
134
|
payload && typeof payload === "object" && "text" in payload
|
|
132
135
|
? String((payload as { text?: string }).text ?? "")
|
|
133
136
|
: "";
|
|
134
|
-
if (!replyText.trim())
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// System messages: do not reply via MQTT (no valid target)
|
|
140
|
-
if (isSystemMessage) {
|
|
141
|
-
ctx.log.info(`system message from ${event.senderId}, skipping MQTT reply`);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Convert markdown tables for readability
|
|
146
|
-
const tableMode = runtime.channel.text.resolveMarkdownTableMode({
|
|
147
|
-
cfg: ctx.cfg,
|
|
148
|
-
channel: "a2hmarket",
|
|
149
|
-
accountId: "default",
|
|
150
|
-
});
|
|
151
|
-
const formatted = runtime.channel.text.convertMarkdownTables(replyText, tableMode);
|
|
152
|
-
|
|
153
|
-
// Send reply via MQTT
|
|
154
|
-
try {
|
|
155
|
-
await mqttSendText(creds, event.senderId, formatted);
|
|
156
|
-
ctx.log.info(`replied to ${event.senderId}: ${formatted.slice(0, 80)}`);
|
|
157
|
-
} catch (err) {
|
|
158
|
-
ctx.log.error(
|
|
159
|
-
`send reply failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ④ Custom notification: notify human about the reply
|
|
164
|
-
// If reply contains a Stripe checkout URL, send a dedicated payment notification
|
|
165
|
-
const paymentUrlMatch = formatted.match(/(https:\/\/checkout\.stripe\.com\S+)/);
|
|
166
|
-
if (paymentUrlMatch) {
|
|
167
|
-
notifyPayment({
|
|
168
|
-
peerId: event.senderId,
|
|
169
|
-
orderId: event.messageId ?? "unknown",
|
|
170
|
-
paymentUrl: paymentUrlMatch[1],
|
|
171
|
-
agentId: creds.agentId,
|
|
172
|
-
}, notifyLog);
|
|
173
|
-
} else {
|
|
174
|
-
notifyHuman("reply", event.senderId, formatted.slice(0, 500), creds.agentId, notifyLog);
|
|
175
|
-
}
|
|
137
|
+
if (!replyText.trim()) return;
|
|
138
|
+
|
|
139
|
+
// Notify human about AI's output (status update / progress)
|
|
140
|
+
notifyHuman("reply", event.senderId, replyText.slice(0, 500), creds.agentId, notifyLog);
|
|
176
141
|
},
|
|
177
142
|
|
|
178
143
|
onRecordError: (err) => {
|
package/src/mqtt-listener.ts
CHANGED
|
@@ -29,6 +29,9 @@ export class MqttListener {
|
|
|
29
29
|
private static readonly RECONNECT_WINDOW_MS = 60_000; // 60 seconds
|
|
30
30
|
private static readonly RECONNECT_THRESHOLD = 5;
|
|
31
31
|
|
|
32
|
+
/** Per-sender dedup: senderId → last message text */
|
|
33
|
+
private lastMessageText = new Map<string, string>();
|
|
34
|
+
|
|
32
35
|
constructor(
|
|
33
36
|
creds: A2HCredentials,
|
|
34
37
|
log?: { info: (m: string) => void; error: (m: string) => void; warn: (m: string) => void },
|
|
@@ -126,6 +129,13 @@ export class MqttListener {
|
|
|
126
129
|
return;
|
|
127
130
|
}
|
|
128
131
|
|
|
132
|
+
// Skip duplicate messages from same sender (same text as last message)
|
|
133
|
+
if (text && text === this.lastMessageText.get(senderId)) {
|
|
134
|
+
this.log.info(`skipping duplicate message from ${senderId}: ${text.slice(0, 50)}`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (text) this.lastMessageText.set(senderId, text);
|
|
138
|
+
|
|
129
139
|
const event: A2AEnvelopeEvent = { senderId, messageId, text, payload, envelope };
|
|
130
140
|
|
|
131
141
|
// Invoke handler (async errors are caught below)
|