@a2hmarket/a2hmarket 1.3.2 → 1.3.4
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 +28 -37
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/pending-welcome.ts +95 -28
package/index.ts
CHANGED
|
@@ -46,7 +46,7 @@ export default {
|
|
|
46
46
|
description:
|
|
47
47
|
"A2H Market — AI agent marketplace with self-managed A2A messaging and human notification.",
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
register(api: OpenClawPluginApi) {
|
|
50
50
|
// ── Init runtime & credentials ───────────────────────────────
|
|
51
51
|
setA2HRuntime(api.runtime);
|
|
52
52
|
initCredentials(api.pluginConfig as Record<string, unknown> | undefined);
|
|
@@ -103,8 +103,9 @@ export default {
|
|
|
103
103
|
} else {
|
|
104
104
|
tools.alsoAllow = [PLUGIN_ID];
|
|
105
105
|
}
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
api.runtime.config.writeConfigFile(cfg as any).then(() => {
|
|
107
|
+
api.logger.info(`a2hmarket: added plugin ID to tools allowlist for profile "${profile}"`);
|
|
108
|
+
}).catch(() => {});
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
} catch {
|
|
@@ -202,40 +203,6 @@ export default {
|
|
|
202
203
|
return { cancel: true };
|
|
203
204
|
});
|
|
204
205
|
|
|
205
|
-
// ── Welcome message on first boot after install ────────────────
|
|
206
|
-
api.on("gateway_start", async () => {
|
|
207
|
-
try {
|
|
208
|
-
const { readPendingWelcome, deletePendingWelcome, sendWelcome } =
|
|
209
|
-
await import("./src/pending-welcome.js");
|
|
210
|
-
const pending = readPendingWelcome();
|
|
211
|
-
if (!pending) return;
|
|
212
|
-
|
|
213
|
-
const cfg = api.runtime.config.loadConfig() as Record<string, unknown>;
|
|
214
|
-
const channels = (cfg.channels ?? {}) as Record<string, Record<string, unknown>>;
|
|
215
|
-
const channelCfg = channels[pending.channel];
|
|
216
|
-
if (!channelCfg) {
|
|
217
|
-
api.logger.warn(
|
|
218
|
-
`a2hmarket: welcome skipped, no config for channel "${pending.channel}"`,
|
|
219
|
-
);
|
|
220
|
-
deletePendingWelcome();
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const sent = await sendWelcome(pending, channelCfg, {
|
|
225
|
-
info: (m) => api.logger.info(`a2hmarket: ${m}`),
|
|
226
|
-
warn: (m) => api.logger.warn(`a2hmarket: ${m}`),
|
|
227
|
-
});
|
|
228
|
-
deletePendingWelcome();
|
|
229
|
-
if (sent) {
|
|
230
|
-
api.logger.info("a2hmarket: welcome message sent");
|
|
231
|
-
}
|
|
232
|
-
} catch (err) {
|
|
233
|
-
api.logger.warn(
|
|
234
|
-
`a2hmarket: welcome failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
|
|
239
206
|
// ── Register agent service ───────────────────────────────────
|
|
240
207
|
let serviceAbort: AbortController | null = null;
|
|
241
208
|
api.registerService({
|
|
@@ -265,6 +232,30 @@ export default {
|
|
|
265
232
|
warn: (m: string) => ctx.logger.warn(`[a2hmarket] ${m}`),
|
|
266
233
|
};
|
|
267
234
|
|
|
235
|
+
// ── Welcome message on first boot after install ──────────
|
|
236
|
+
try {
|
|
237
|
+
const { readPendingWelcome, deletePendingWelcome, sendWelcome } =
|
|
238
|
+
await import("./src/pending-welcome.js");
|
|
239
|
+
const pending = readPendingWelcome();
|
|
240
|
+
if (pending) {
|
|
241
|
+
const cfg = ctx.config as Record<string, unknown>;
|
|
242
|
+
const channels = (cfg.channels ?? {}) as Record<string, Record<string, unknown>>;
|
|
243
|
+
const channelCfg = channels[pending.channel];
|
|
244
|
+
if (channelCfg) {
|
|
245
|
+
const sent = await sendWelcome(pending, channelCfg, {
|
|
246
|
+
info: (m) => serviceLog.info(m),
|
|
247
|
+
warn: (m) => serviceLog.warn(m),
|
|
248
|
+
});
|
|
249
|
+
if (sent) serviceLog.info("welcome message sent");
|
|
250
|
+
} else {
|
|
251
|
+
serviceLog.warn(`welcome skipped, no config for channel "${pending.channel}"`);
|
|
252
|
+
}
|
|
253
|
+
deletePendingWelcome();
|
|
254
|
+
}
|
|
255
|
+
} catch (err) {
|
|
256
|
+
serviceLog.warn(`welcome failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
268
259
|
serviceAbort = new AbortController();
|
|
269
260
|
try {
|
|
270
261
|
await startAgentService({
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/pending-welcome.ts
CHANGED
|
@@ -49,32 +49,93 @@ export function deletePendingWelcome(): void {
|
|
|
49
49
|
|
|
50
50
|
const WELCOME_TITLE = "🎉 A2H Market 插件已就绪";
|
|
51
51
|
|
|
52
|
-
const
|
|
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
|
-
|
|
52
|
+
const AUTH_API_URL = "https://web.a2hmarket.ai";
|
|
53
|
+
const LOGIN_URL = "https://a2hmarket.ai";
|
|
54
|
+
|
|
55
|
+
async function generateAuthUrl(): Promise<string | null> {
|
|
56
|
+
try {
|
|
57
|
+
const { networkInterfaces } = await import("node:os");
|
|
58
|
+
const { createHash, randomBytes } = await import("node:crypto");
|
|
59
|
+
|
|
60
|
+
let mac = "00:00:00:00:00:00";
|
|
61
|
+
const ifaces = networkInterfaces();
|
|
62
|
+
for (const name of Object.keys(ifaces)) {
|
|
63
|
+
for (const iface of ifaces[name] ?? []) {
|
|
64
|
+
if (!iface.internal && iface.mac && iface.mac !== "00:00:00:00:00:00") {
|
|
65
|
+
mac = iface.mac;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (mac !== "00:00:00:00:00:00") break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
73
|
+
try {
|
|
74
|
+
const formData = new URLSearchParams({ timestamp, mac });
|
|
75
|
+
const resp = await fetch(`${AUTH_API_URL}/v1/auth/init-login`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
78
|
+
body: formData.toString(),
|
|
79
|
+
});
|
|
80
|
+
const data = (await resp.json()) as { code?: string; url?: string };
|
|
81
|
+
if (data.url) return data.url;
|
|
82
|
+
} catch {
|
|
83
|
+
// fallback to local generation
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const randomHex = randomBytes(32).toString("hex");
|
|
87
|
+
const macClean = mac.replace(/:/g, "");
|
|
88
|
+
const raw = `${randomHex}_${timestamp}_${macClean}`;
|
|
89
|
+
const code = createHash("md5").update(raw).digest("hex");
|
|
90
|
+
return `${LOGIN_URL}/authcode?code=${code}`;
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function buildFeishuElements(authUrl: string | null) {
|
|
97
|
+
const elements = [
|
|
98
|
+
{
|
|
99
|
+
tag: "markdown" as const,
|
|
100
|
+
content:
|
|
101
|
+
"我是你的 **A2H Market** AI 助手,可以帮你:\n" +
|
|
102
|
+
"📦 发布商品、浏览市场\n" +
|
|
103
|
+
"💬 自动与其他 Agent 谈判协商\n" +
|
|
104
|
+
"📋 管理订单、处理支付",
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
if (authUrl) {
|
|
108
|
+
elements.push({
|
|
109
|
+
tag: "markdown" as const,
|
|
110
|
+
content: `---\n🔐 **[点击这里完成授权登录](${authUrl})**`,
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
elements.push({
|
|
114
|
+
tag: "markdown" as const,
|
|
115
|
+
content: '---\n🔐 首次使用请发送 **"登录 A2H Market"** 完成授权',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return elements;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function buildWelcomeText(authUrl: string | null): string {
|
|
122
|
+
const lines = [
|
|
123
|
+
`**${WELCOME_TITLE}**`,
|
|
124
|
+
"",
|
|
125
|
+
"我是你的 A2H Market AI 助手,可以帮你:",
|
|
126
|
+
"📦 发布商品、浏览市场",
|
|
127
|
+
"💬 自动与其他 Agent 谈判协商",
|
|
128
|
+
"📋 管理订单、处理支付",
|
|
129
|
+
"",
|
|
130
|
+
"---",
|
|
131
|
+
];
|
|
132
|
+
if (authUrl) {
|
|
133
|
+
lines.push(`🔐 点击完成授权登录: ${authUrl}`);
|
|
134
|
+
} else {
|
|
135
|
+
lines.push('🔐 首次使用请发送 "登录 A2H Market" 完成授权');
|
|
136
|
+
}
|
|
137
|
+
return lines.join("\n");
|
|
138
|
+
}
|
|
78
139
|
|
|
79
140
|
// ── Send Welcome ───────────────────────────────────────────────────────
|
|
80
141
|
|
|
@@ -92,6 +153,12 @@ export async function sendWelcome(
|
|
|
92
153
|
channelCfg: Record<string, unknown>,
|
|
93
154
|
log: WelcomeLog,
|
|
94
155
|
): Promise<boolean> {
|
|
156
|
+
// Generate auth URL (best-effort, falls back to text prompt)
|
|
157
|
+
const authUrl = await generateAuthUrl();
|
|
158
|
+
if (authUrl) {
|
|
159
|
+
log.info(`welcome: auth url generated`);
|
|
160
|
+
}
|
|
161
|
+
|
|
95
162
|
if (pending.channel === "feishu") {
|
|
96
163
|
if (!channelCfg.appId || !channelCfg.appSecret) {
|
|
97
164
|
log.warn("welcome: feishu channel credentials not configured, skipped");
|
|
@@ -104,7 +171,7 @@ export async function sendWelcome(
|
|
|
104
171
|
target: pending.target,
|
|
105
172
|
title: WELCOME_TITLE,
|
|
106
173
|
titleColor: "green",
|
|
107
|
-
elements:
|
|
174
|
+
elements: buildFeishuElements(authUrl),
|
|
108
175
|
});
|
|
109
176
|
return true;
|
|
110
177
|
}
|
|
@@ -137,7 +204,7 @@ export async function sendWelcome(
|
|
|
137
204
|
const resp = await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, {
|
|
138
205
|
method: "POST",
|
|
139
206
|
headers,
|
|
140
|
-
body: JSON.stringify({ content:
|
|
207
|
+
body: JSON.stringify({ content: buildWelcomeText(authUrl) }),
|
|
141
208
|
});
|
|
142
209
|
if (!resp.ok) {
|
|
143
210
|
const data = (await resp.json()) as { message?: string };
|