@a2hmarket/a2hmarket 0.9.0 → 0.9.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
@@ -142,6 +142,9 @@ export default {
142
142
  if (!a2hToolUsedSessions.has(sessionKey)) return;
143
143
  a2hToolUsedSessions.delete(sessionKey);
144
144
 
145
+ // Skip DM per-peer sessions — they have their own notification via deliver callback
146
+ if (sessionKey.includes(":a2hmarket:")) return;
147
+
145
148
  const content = (event as any)?.content ?? "";
146
149
  if (typeof content !== "string" || !content.trim()) return;
147
150
 
@@ -2,7 +2,7 @@
2
2
  "id": "a2hmarket",
3
3
  "name": "A2H Market",
4
4
  "description": "A2H Market — AI agent marketplace with self-managed A2A messaging via MQTT.",
5
- "version": "0.9.0",
5
+ "version": "0.9.1",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a2hmarket/a2hmarket",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "index.ts",
@@ -8,6 +8,11 @@ version: 2.0.0
8
8
 
9
9
  A2H Market 是一个人类和 AI Agent 都可以使用的 AI 交易市场。你代理人类在市场中进行买卖交易。
10
10
 
11
+ > ⚠️ **最高优先级规则:你是代理人,不是决策者。**
12
+ > 涉及价格、报价、还价、创建订单、接受订单、支付、收款确认、交付验收等决定时,
13
+ > **必须调用 `a2h_create_approval` 让人类确认,不得自行决定。**
14
+ > 你只能自主回答帖子里已有的信息。其他一切需要人类拍板。
15
+
11
16
  **用三个概念理解你在市场里做的事:**
12
17
 
13
18
  | 概念 | 含义 | 角色 |
@@ -13,10 +13,12 @@
13
13
 
14
14
  ## 关键节点即时汇报
15
15
 
16
- 以下事件发生时,**立刻**在对话中告知人类用户,等待确认后再继续:
16
+ 以下事件发生时,**必须调用 `a2h_create_approval`** 创建审批,等待人类确认后再继续。**不要自行决定,不要只是"告知"——必须用审批工具。**
17
17
 
18
- | 事件 | 通知内容 | 人类需要做什么 |
18
+ | 事件 | 审批内容 | 人类需要做什么 |
19
19
  |------|---------|--------------|
20
+ | **对方报价 / 你需要报价** | 报价金额和条件 | 确认是否接受 / 确定报价 |
21
+ | **对方还价** | 还价金额 vs 原价 | 接受 / 拒绝 / 继续还价 |
20
22
  | 订单创建(买方收到) | 订单信息、价格、条件 | 确认是否接受订单 |
21
23
  | 订单确认(卖方收到) | 买家已确认订单 | 知悉,进入付款阶段 |
22
24
  | **付款阶段(卖方)** | 买家已付款通知 | 确认是否真的收到款项 |
@@ -25,6 +27,19 @@
25
27
  | 帖子信息不足 | 对方提出帖子中未明确的条件 | 确认后更新帖子 |
26
28
  | 交易异常/破裂 | 异常原因 | 决定下一步 |
27
29
 
30
+ **调用示例:**
31
+
32
+ ```
33
+ a2h_create_approval(
34
+ peer_id: "ag_xxx",
35
+ question: "对方报价500元,是否接受?",
36
+ context: "对方昵称:小明\n1. 对方咨询PPT服务\n2. 你报价800\n3. 对方还价500",
37
+ options: ["接受", "拒绝", "还价"]
38
+ )
39
+ ```
40
+
41
+ 调用后**不要回复对方**,等待 `[Human Approval Result]` 消息到达后再根据人类决定行动。
42
+
28
43
 
29
44
  ---
30
45
 
@@ -24,7 +24,7 @@ import { setApprovalConfig } from "./tools/approval.js";
24
24
 
25
25
  // ── MQTT Send Helper ─────────────────────────────────────────────────────
26
26
 
27
- async function mqttSendText(
27
+ export async function mqttSendText(
28
28
  creds: { agentId: string; agentKey: string; apiUrl: string; mqttUrl: string },
29
29
  targetAgentId: string,
30
30
  text: string,
@@ -107,3 +107,9 @@ export function listPending(): Approval[] {
107
107
  evict();
108
108
  return [...approvals.values()].filter(a => a.status === "pending");
109
109
  }
110
+
111
+ export function hasApprovedForPeer(peerId: string): boolean {
112
+ return [...approvals.values()].some(
113
+ a => a.peerId === peerId && (a.status === "approved" || a.status === "custom")
114
+ );
115
+ }
@@ -8,6 +8,7 @@ import { createApproval, resolveApproval, getApproval, markNotified, listPending
8
8
  import { notifyApproval, type NotifyLog } from "../notify.js";
9
9
  import { loadCredentials } from "../credentials.js";
10
10
  import { getA2HRuntime } from "../runtime.js";
11
+ import { mqttSendText } from "../agent-service.js";
11
12
 
12
13
  let _cfg: OpenClawConfig | null = null;
13
14
 
@@ -65,8 +66,55 @@ export function registerApprovalTools(api: OpenClawPluginApi) {
65
66
  const approval = createApproval({ peerId, sessionKey, question, context, options });
66
67
 
67
68
  const creds = loadCredentials();
69
+ // Send notification card to human (visual notification)
68
70
  notifyApproval(approval, creds.agentId, notifyLog);
69
71
 
72
+ // Inject approval request into the human's channel session so the AI there has context
73
+ if (_cfg && creds.notify?.target) {
74
+ const runtime = getA2HRuntime();
75
+ const channelTarget = creds.notify.target;
76
+ const channel = creds.notify.channel;
77
+
78
+ const optionsLine = options.length > 0 ? `Options: ${options.join(" / ")}` : "";
79
+ const contextLine = context ? `Context: ${context}` : "";
80
+ const approvalMessage = [
81
+ `[A2H Market Pending Approval — DO NOT reply to this message. Wait for human's next message.]`,
82
+ `Approval ID: ${approval.id}`,
83
+ `From: ${peerId}`,
84
+ `Question: ${question}`,
85
+ contextLine,
86
+ optionsLine,
87
+ ``,
88
+ `INSTRUCTION: When the human sends their next message with a decision about this approval,`,
89
+ `call a2h_approval_response(approval_id='${approval.id}', decision='<the human's decision>').`,
90
+ `Do NOT reply to the human about this — just call the tool silently.`,
91
+ ].filter(Boolean).join("\n");
92
+
93
+ dispatchInboundDirectDmWithRuntime({
94
+ cfg: _cfg,
95
+ runtime,
96
+ channel,
97
+ channelLabel: channel,
98
+ accountId: "default",
99
+ peer: { kind: "direct", id: channelTarget },
100
+ senderId: "a2hmarket-system",
101
+ senderAddress: `a2hmarket:system`,
102
+ recipientAddress: `${channel}:${channelTarget}`,
103
+ conversationLabel: channelTarget,
104
+ rawBody: approvalMessage,
105
+ messageId: `approval_req_${approval.id}`,
106
+ timestamp: Date.now(),
107
+ commandAuthorized: true,
108
+ deliver: async () => {
109
+ // Don't send AI's response back — this is just context injection
110
+ },
111
+ onRecordError: () => {},
112
+ onDispatchError: () => {},
113
+ }).catch((err) => {
114
+ notifyLog.error(`approval feishu session inject failed: ${err instanceof Error ? err.message : String(err)}`);
115
+ });
116
+ }
117
+
70
118
  return {
71
119
  result: JSON.stringify({
72
120
  approval_id: approval.id,
@@ -127,12 +175,28 @@ export function registerApprovalTools(api: OpenClawPluginApi) {
127
175
  if (_cfg) {
128
176
  const runtime = getA2HRuntime();
129
177
  const creds = loadCredentials();
130
- const resultMessage = [
178
+ const resultParts = [
131
179
  `[Human Approval Result]`,
132
180
  `Approval: ${resolved.id}`,
181
+ `Peer: ${resolved.peerId}`,
133
182
  `Decision: ${decision}`,
183
+ ``,
134
184
  `Original question: ${resolved.question}`,
135
- ].join("\n");
185
+ ];
186
+ if (resolved.context) {
187
+ resultParts.push(``, `Context: ${resolved.context}`);
188
+ }
189
+ if (resolved.options.length > 0) {
190
+ resultParts.push(`Options were: ${resolved.options.join(" / ")}`);
191
+ }
192
+ resultParts.push(
193
+ ``,
194
+ `Action required: Based on the human's decision above, proceed accordingly.`,
195
+ `If the decision is to accept/approve, continue with the next step (e.g., create order with a2h_order_create).`,
196
+ `If the decision is to reject, inform the counterparty.`,
197
+ `If the decision is custom text, follow the human's specific instruction.`,
198
+ );
199
+ const resultMessage = resultParts.join("\n");
136
200
 
137
201
  try {
138
202
  await dispatchInboundDirectDmWithRuntime({
@@ -150,7 +214,24 @@ export function registerApprovalTools(api: OpenClawPluginApi) {
150
214
  messageId: `approval_result_${resolved.id}`,
151
215
  timestamp: Date.now(),
152
216
  commandAuthorized: true,
153
- deliver: async () => {},
217
+ deliver: async (payload) => {
218
+ // DM session AI may reply to counterparty after processing approval result
219
+ const replyText =
220
+ payload && typeof payload === "object" && "text" in payload
221
+ ? String((payload as { text?: string }).text ?? "")
222
+ : "";
223
+ if (!replyText.trim()) return;
224
+ // Send to counterparty via MQTT
225
+ try {
226
+ await mqttSendText(creds, resolved.peerId, replyText);
227
+ notifyLog.info(`approval follow-up sent to ${resolved.peerId}: ${replyText.slice(0, 80)}`);
228
+ } catch (err) {
229
+ notifyLog.error(`approval follow-up send failed: ${err instanceof Error ? err.message : String(err)}`);
230
+ }
231
+ // Notify human
232
+ const { notifyHuman: notifyH } = await import("../notify.js");
233
+ notifyH("reply", resolved.peerId, replyText.slice(0, 500), creds.agentId, notifyLog);
234
+ },
154
235
  onRecordError: (err) => {
155
236
  notifyLog.error(`approval dispatch record error: ${err instanceof Error ? err.message : String(err)}`);
156
237
  },
@@ -1,5 +1,6 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import type { A2HApiClient } from "../api-client.js";
3
+ import { hasApprovedForPeer } from "../approval-store.js";
3
4
 
4
5
  export function registerOrderTools(api: OpenClawPluginApi, client: A2HApiClient) {
5
6
  api.registerTool({
@@ -19,6 +20,16 @@ export function registerOrderTools(api: OpenClawPluginApi, client: A2HApiClient)
19
20
  required: ["customer_id", "title", "content", "price_cent", "product_id", "order_type"],
20
21
  },
21
22
  execute: async (_toolCallId: string, params: Record<string, unknown>) => {
23
+ // Require human approval before creating orders
24
+ const customerId = params.customer_id as string;
25
+ if (!hasApprovedForPeer(customerId)) {
26
+ throw new Error(
27
+ "Cannot create order without human approval. " +
28
+ "Call a2h_create_approval(peer_id, question, context, options) first, " +
29
+ "wait for [Human Approval Result], then retry."
30
+ );
31
+ }
32
+
22
33
  const body = {
23
34
  providerId: client.agentId,
24
35
  customerId: params.customer_id,