okx-onchain-console 0.1.0
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/COMPANION.md +42 -0
- package/README.md +240 -0
- package/companion.mjs +826 -0
- package/dist/assets/data-YT0JVIX1.js +1 -0
- package/dist/assets/sidepanel-Bl_Aq3uM.css +1 -0
- package/dist/background.js +1 -0
- package/dist/content.js +1 -0
- package/dist/icons/icon-128.png +0 -0
- package/dist/icons/icon-16.png +0 -0
- package/dist/icons/icon-32.png +0 -0
- package/dist/icons/icon-48.png +0 -0
- package/dist/index.html +15 -0
- package/dist/manifest.json +33 -0
- package/dist/product-icon.png +0 -0
- package/dist/screenshots/console-overview.png +0 -0
- package/dist/sidepanel.js +164 -0
- package/dist-web/assets/sidepanel-Bl_Aq3uM.css +1 -0
- package/dist-web/icons/icon-128.png +0 -0
- package/dist-web/icons/icon-16.png +0 -0
- package/dist-web/icons/icon-32.png +0 -0
- package/dist-web/icons/icon-48.png +0 -0
- package/dist-web/index.html +14 -0
- package/dist-web/manifest.json +33 -0
- package/dist-web/product-icon.png +0 -0
- package/dist-web/screenshots/console-overview.png +0 -0
- package/dist-web/sidepanel.js +164 -0
- package/package.json +49 -0
- package/public/manifest.json +33 -0
- package/web.mjs +161 -0
package/companion.mjs
ADDED
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
5
|
+
import { chmod, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
|
|
11
|
+
const exec = promisify(execFile);
|
|
12
|
+
const hostName = "com.okx.onchain_console";
|
|
13
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
14
|
+
const blockedKeys = new Set([
|
|
15
|
+
"accesstoken",
|
|
16
|
+
"refreshtoken",
|
|
17
|
+
"apikey",
|
|
18
|
+
"secretkey",
|
|
19
|
+
"passphrase",
|
|
20
|
+
"sessionkey",
|
|
21
|
+
"sessioncert",
|
|
22
|
+
"teeid",
|
|
23
|
+
"sateeid",
|
|
24
|
+
"encryptedsessionsk",
|
|
25
|
+
"signingkey",
|
|
26
|
+
"rawtx",
|
|
27
|
+
"unsignedtx",
|
|
28
|
+
"privatekey",
|
|
29
|
+
"mnemonic",
|
|
30
|
+
"seedphrase",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
function cliPath() {
|
|
34
|
+
const candidates = [
|
|
35
|
+
process.env.ONCHAINOS_BIN,
|
|
36
|
+
join(homedir(), ".local/bin/onchainos"),
|
|
37
|
+
"/usr/local/bin/onchainos",
|
|
38
|
+
"/opt/homebrew/bin/onchainos",
|
|
39
|
+
].filter(Boolean);
|
|
40
|
+
const path = candidates.find(existsSync);
|
|
41
|
+
if (!path) throw new Error("未找到 onchainos CLI");
|
|
42
|
+
return path;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sanitize(value) {
|
|
46
|
+
if (Array.isArray(value)) return value.map(sanitize);
|
|
47
|
+
if (!value || typeof value !== "object") return value;
|
|
48
|
+
return Object.fromEntries(
|
|
49
|
+
Object.entries(value)
|
|
50
|
+
.filter(
|
|
51
|
+
([key]) =>
|
|
52
|
+
!blockedKeys.has(key.toLowerCase().replace(/[^a-z0-9]/g, "")),
|
|
53
|
+
)
|
|
54
|
+
.map(([key, child]) => [key, sanitize(child)]),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function run(args) {
|
|
59
|
+
const { stdout } = await exec(cliPath(), args, {
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
timeout: 20_000,
|
|
62
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
63
|
+
});
|
|
64
|
+
const result = sanitize(JSON.parse(stdout.trim()));
|
|
65
|
+
if (!result.ok) throw new Error(result.error || "查询失败");
|
|
66
|
+
return result.data;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function message(error) {
|
|
70
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
71
|
+
if (/session expired|login again|not logged in/i.test(text))
|
|
72
|
+
return "登录已过期,请在终端运行 onchainos wallet login";
|
|
73
|
+
return text.replace(/^Command failed[^\n]*\n?/, "").trim() || "查询失败";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function optional(label, args, warnings) {
|
|
77
|
+
try {
|
|
78
|
+
return await run(args);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
warnings.push(`${label}:${message(error)}`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const array = (value) => (Array.isArray(value) ? value : []);
|
|
86
|
+
const first = (value, keys) => {
|
|
87
|
+
if (!value || typeof value !== "object") return undefined;
|
|
88
|
+
for (const key of keys) {
|
|
89
|
+
const found = value[key];
|
|
90
|
+
if (found !== undefined && found !== null && found !== "") return found;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const text = (value, keys, fallback = "") =>
|
|
94
|
+
String(first(value, keys) ?? fallback);
|
|
95
|
+
const number = (value, keys) => {
|
|
96
|
+
const parsed = Number(first(value, keys));
|
|
97
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
98
|
+
};
|
|
99
|
+
const displayLabel = (value) => {
|
|
100
|
+
const raw = String(value ?? "");
|
|
101
|
+
return (
|
|
102
|
+
{
|
|
103
|
+
"not listed": "未上架",
|
|
104
|
+
"Listing under review": "上架审核中",
|
|
105
|
+
SUCCESS: "成功",
|
|
106
|
+
PENDING: "处理中",
|
|
107
|
+
FAILED: "失败",
|
|
108
|
+
}[raw] ?? raw
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
const cell = (value, names) => {
|
|
112
|
+
for (const item of array(value?.cells)) {
|
|
113
|
+
const label = String(
|
|
114
|
+
first(item, ["key", "label", "name"]) ?? "",
|
|
115
|
+
).toLowerCase();
|
|
116
|
+
if (names.some((name) => label === name.toLowerCase()))
|
|
117
|
+
return first(item, ["value", "text", "content"]);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const field = (value, keys, labels = keys) =>
|
|
121
|
+
first(value, keys) ?? cell(value, labels);
|
|
122
|
+
const balanceRows = (value) =>
|
|
123
|
+
Object.values(value?.details ?? {}).flatMap((group) => array(group?.data));
|
|
124
|
+
const agentGroups = (value) => array(value?.list);
|
|
125
|
+
const taskRows = (value) => array(value?.tasks);
|
|
126
|
+
const subscriptionRows = (value) => array(value?.list);
|
|
127
|
+
const serviceRows = (value) =>
|
|
128
|
+
array(value).flatMap((group) => array(group?.list));
|
|
129
|
+
const feedbackRows = (value) => array(value?.list);
|
|
130
|
+
const arbitrationRows = (value) => array(value?.payload?.items);
|
|
131
|
+
const refundRows = (value) => array(value?.items);
|
|
132
|
+
const historyRows = (value) =>
|
|
133
|
+
array(value).flatMap((group) => array(group?.orderList));
|
|
134
|
+
const userTaskRows = (value) => array(value?.oneTimeTasks?.list);
|
|
135
|
+
const timestamp = (value) => {
|
|
136
|
+
if (!value) return new Date().toISOString();
|
|
137
|
+
const numeric = Number(value);
|
|
138
|
+
if (Number.isFinite(numeric))
|
|
139
|
+
return new Date(
|
|
140
|
+
numeric < 10_000_000_000 ? numeric * 1000 : numeric,
|
|
141
|
+
).toISOString();
|
|
142
|
+
const parsed = new Date(value);
|
|
143
|
+
return Number.isNaN(parsed.getTime())
|
|
144
|
+
? new Date().toISOString()
|
|
145
|
+
: parsed.toISOString();
|
|
146
|
+
};
|
|
147
|
+
const role = (value) => {
|
|
148
|
+
const normalized = String(value ?? "").toLowerCase();
|
|
149
|
+
if (normalized === "1") return "User";
|
|
150
|
+
if (normalized === "2") return "ASP";
|
|
151
|
+
if (normalized === "3") return "Evaluator";
|
|
152
|
+
if (normalized.includes("asp") || normalized.includes("provider"))
|
|
153
|
+
return "ASP";
|
|
154
|
+
if (normalized.includes("eval")) return "Evaluator";
|
|
155
|
+
if (normalized.includes("user") || normalized.includes("buyer"))
|
|
156
|
+
return "User";
|
|
157
|
+
return "Unknown";
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
function agentsFrom(data) {
|
|
161
|
+
return agentGroups(data).flatMap((group) =>
|
|
162
|
+
array(first(group, ["agentList", "agents", "list"])).map((agent) => ({
|
|
163
|
+
group,
|
|
164
|
+
agent,
|
|
165
|
+
})),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function addressesFrom(data) {
|
|
170
|
+
return [
|
|
171
|
+
["EVM", "evm"],
|
|
172
|
+
["X Layer", "xlayer"],
|
|
173
|
+
["Solana", "solana"],
|
|
174
|
+
["Bitcoin", "bitcoin"],
|
|
175
|
+
["Sui", "sui"],
|
|
176
|
+
].flatMap(([network, key]) => {
|
|
177
|
+
const entry = array(data?.[key])[0];
|
|
178
|
+
const address = text(entry, ["address"]);
|
|
179
|
+
return address ? [{ network, address }] : [];
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function event(item, kind, accountId, fallbackRole = "Unknown") {
|
|
184
|
+
const generatedId = createHash("sha256")
|
|
185
|
+
.update(JSON.stringify(item))
|
|
186
|
+
.digest("hex")
|
|
187
|
+
.slice(0, 16);
|
|
188
|
+
return {
|
|
189
|
+
id: text(item, ["jobId", "taskId", "id", "txHash", "orderId"], generatedId),
|
|
190
|
+
accountId: text(item, ["accountId"], accountId),
|
|
191
|
+
kind,
|
|
192
|
+
role: role(first(item, ["myRole", "role"]) ?? fallbackRole),
|
|
193
|
+
title: text(
|
|
194
|
+
item,
|
|
195
|
+
["title", "serviceName", "name", "coinSymbol"],
|
|
196
|
+
"未命名记录",
|
|
197
|
+
),
|
|
198
|
+
status: displayLabel(
|
|
199
|
+
text(item, ["statusLabel", "txStatus", "approvalStatus"], "未提供"),
|
|
200
|
+
),
|
|
201
|
+
createdAt: timestamp(
|
|
202
|
+
first(item, ["createdAt", "createTime", "txTime", "subStartTime"]),
|
|
203
|
+
),
|
|
204
|
+
amount:
|
|
205
|
+
text(item, [
|
|
206
|
+
"tokenAmount",
|
|
207
|
+
"serviceTokenAmount",
|
|
208
|
+
"amount",
|
|
209
|
+
"coinAmount",
|
|
210
|
+
"refundAmount",
|
|
211
|
+
]) || undefined,
|
|
212
|
+
detail: text(
|
|
213
|
+
item,
|
|
214
|
+
["statusDescription", "description", "detail", "failReason"],
|
|
215
|
+
"未提供",
|
|
216
|
+
),
|
|
217
|
+
reference: text(item, ["jobId", "txHash", "orderId"]) || undefined,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function a2mcpInvocations(accountId) {
|
|
222
|
+
const directory = join(
|
|
223
|
+
process.env.ONCHAINOS_HOME || join(homedir(), ".onchainos"),
|
|
224
|
+
"payments",
|
|
225
|
+
);
|
|
226
|
+
if (!existsSync(directory)) return [];
|
|
227
|
+
const files = (await readdir(directory)).filter((name) =>
|
|
228
|
+
/^pay_[a-zA-Z0-9_-]+\.json$/.test(name),
|
|
229
|
+
);
|
|
230
|
+
const records = await Promise.all(
|
|
231
|
+
files.map(async (name) => {
|
|
232
|
+
try {
|
|
233
|
+
const record = JSON.parse(
|
|
234
|
+
await readFile(join(directory, name), "utf8"),
|
|
235
|
+
);
|
|
236
|
+
if (
|
|
237
|
+
record?.source !== "okx_ai_a2mcp" ||
|
|
238
|
+
record?.ownerAccountId !== accountId
|
|
239
|
+
)
|
|
240
|
+
return null;
|
|
241
|
+
const accept = record.selectedAccept ?? {};
|
|
242
|
+
const request = record.frozenRequest ?? {};
|
|
243
|
+
const resource = request.resource ?? {};
|
|
244
|
+
const decimals = Number(accept.decimals);
|
|
245
|
+
const rawAmount = Number(accept.amount);
|
|
246
|
+
const amount =
|
|
247
|
+
Number.isFinite(decimals) && Number.isFinite(rawAmount)
|
|
248
|
+
? String(rawAmount / 10 ** decimals)
|
|
249
|
+
: undefined;
|
|
250
|
+
const state = String(record.execution?.state ?? "");
|
|
251
|
+
const status =
|
|
252
|
+
state === "success"
|
|
253
|
+
? "调用完成"
|
|
254
|
+
: state === "failed"
|
|
255
|
+
? "调用失败"
|
|
256
|
+
: Number(record.expiresAt) < Date.now() / 1000
|
|
257
|
+
? "已过期"
|
|
258
|
+
: "待支付";
|
|
259
|
+
const params = Object.entries(request.typedParams ?? {})
|
|
260
|
+
.filter(
|
|
261
|
+
([key]) =>
|
|
262
|
+
!blockedKeys.has(key.toLowerCase().replace(/[^a-z0-9]/g, "")),
|
|
263
|
+
)
|
|
264
|
+
.map(([key, value]) => `${key}=${String(value).slice(0, 160)}`)
|
|
265
|
+
.join(",");
|
|
266
|
+
return {
|
|
267
|
+
id: String(record.paymentId ?? name.replace(/\.json$/, "")),
|
|
268
|
+
accountId,
|
|
269
|
+
kind: "invocation",
|
|
270
|
+
role: "User",
|
|
271
|
+
title: String(resource.description || "A2MCP 服务调用"),
|
|
272
|
+
status,
|
|
273
|
+
createdAt: timestamp(record.createdAt),
|
|
274
|
+
...(amount ? { amount } : {}),
|
|
275
|
+
detail: [
|
|
276
|
+
request.method ? `请求方式:${request.method}` : "",
|
|
277
|
+
params ? `调用参数:${params}` : "",
|
|
278
|
+
accept.symbol ? `支付资产:${accept.symbol}` : "",
|
|
279
|
+
state === "success" ? "结算状态:本机记录未保存链上凭证" : "",
|
|
280
|
+
]
|
|
281
|
+
.filter(Boolean)
|
|
282
|
+
.join(";"),
|
|
283
|
+
};
|
|
284
|
+
} catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}),
|
|
288
|
+
);
|
|
289
|
+
return records.filter(Boolean);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function snapshot() {
|
|
293
|
+
const warnings = [];
|
|
294
|
+
const status =
|
|
295
|
+
(await optional("钱包状态", ["wallet", "status"], warnings)) ?? {};
|
|
296
|
+
const fallbackId = text(status, ["currentAccountId"], "active");
|
|
297
|
+
const fallbackName = text(status, ["currentAccountName"], "当前账户");
|
|
298
|
+
const fallbackAccount = {
|
|
299
|
+
id: fallbackId,
|
|
300
|
+
name: fallbackName,
|
|
301
|
+
address: "",
|
|
302
|
+
description: text(status, ["email", "loginType"], "OnchainOS"),
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (status.loggedIn !== true) {
|
|
306
|
+
if (!warnings.some((item) => item.includes("登录")))
|
|
307
|
+
warnings.push("登录已过期,请在终端运行 onchainos wallet login");
|
|
308
|
+
return {
|
|
309
|
+
mode: "live",
|
|
310
|
+
capturedAt: new Date().toISOString(),
|
|
311
|
+
warnings,
|
|
312
|
+
accounts: [fallbackAccount],
|
|
313
|
+
wallets: [
|
|
314
|
+
{
|
|
315
|
+
accountId: fallbackId,
|
|
316
|
+
address: "",
|
|
317
|
+
chain: "全部网络",
|
|
318
|
+
totalUsd: 0,
|
|
319
|
+
assets: [],
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
identities: [],
|
|
323
|
+
services: [],
|
|
324
|
+
events: [],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const [
|
|
329
|
+
balances,
|
|
330
|
+
addressData,
|
|
331
|
+
agentData,
|
|
332
|
+
taskData,
|
|
333
|
+
userTaskData,
|
|
334
|
+
buyerSubs,
|
|
335
|
+
providerSubs,
|
|
336
|
+
history,
|
|
337
|
+
invocationData,
|
|
338
|
+
] = await Promise.all([
|
|
339
|
+
optional("钱包资产", ["wallet", "balance", "--all"], warnings),
|
|
340
|
+
optional("钱包地址", ["wallet", "addresses"], warnings),
|
|
341
|
+
optional(
|
|
342
|
+
"Agent 身份",
|
|
343
|
+
["agent", "get-my-agents", "--page-size", "50"],
|
|
344
|
+
warnings,
|
|
345
|
+
),
|
|
346
|
+
optional("任务", ["agent", "active-tasks", "--include-terminal"], warnings),
|
|
347
|
+
optional(
|
|
348
|
+
"用户任务历史",
|
|
349
|
+
[
|
|
350
|
+
"agent",
|
|
351
|
+
"my-tasks",
|
|
352
|
+
"--task-type",
|
|
353
|
+
"one-time",
|
|
354
|
+
"--status-type",
|
|
355
|
+
"0",
|
|
356
|
+
"--page",
|
|
357
|
+
"1",
|
|
358
|
+
"--page-size",
|
|
359
|
+
"50",
|
|
360
|
+
],
|
|
361
|
+
warnings,
|
|
362
|
+
),
|
|
363
|
+
optional(
|
|
364
|
+
"买方订阅",
|
|
365
|
+
["agent", "my-subscriptions", "--role", "buyer"],
|
|
366
|
+
warnings,
|
|
367
|
+
),
|
|
368
|
+
optional(
|
|
369
|
+
"服务方订阅",
|
|
370
|
+
["agent", "my-subscriptions", "--role", "provider"],
|
|
371
|
+
warnings,
|
|
372
|
+
),
|
|
373
|
+
optional("钱包流水", ["wallet", "history", "--limit", "50"], warnings),
|
|
374
|
+
a2mcpInvocations(fallbackId).catch((error) => {
|
|
375
|
+
warnings.push(`A2MCP 调用记录:${message(error)}`);
|
|
376
|
+
return null;
|
|
377
|
+
}),
|
|
378
|
+
]);
|
|
379
|
+
|
|
380
|
+
const currentAddresses = addressesFrom(addressData);
|
|
381
|
+
const primaryAddress =
|
|
382
|
+
currentAddresses.find((item) => item.network === "EVM")?.address ??
|
|
383
|
+
currentAddresses[0]?.address ??
|
|
384
|
+
"";
|
|
385
|
+
|
|
386
|
+
const balanceAccounts = balanceRows(balances);
|
|
387
|
+
const accounts = balanceAccounts.map((item, index) => {
|
|
388
|
+
const id = text(item, ["accountId"], fallbackId);
|
|
389
|
+
return {
|
|
390
|
+
id,
|
|
391
|
+
name: id === fallbackId ? fallbackName : `账户 ${index + 1}`,
|
|
392
|
+
address: id === fallbackId ? primaryAddress : "",
|
|
393
|
+
description:
|
|
394
|
+
id === fallbackId ? fallbackAccount.description : "OnchainOS",
|
|
395
|
+
};
|
|
396
|
+
});
|
|
397
|
+
if (!accounts.length)
|
|
398
|
+
accounts.push({ ...fallbackAccount, address: primaryAddress });
|
|
399
|
+
|
|
400
|
+
const wallets = accounts.map((account) => {
|
|
401
|
+
const item =
|
|
402
|
+
balanceAccounts.find(
|
|
403
|
+
(candidate) => text(candidate, ["accountId"]) === account.id,
|
|
404
|
+
) ?? {};
|
|
405
|
+
const assets = array(item.tokenAssets).map((asset) => ({
|
|
406
|
+
symbol: text(asset, ["symbol", "tokenName"], "未知资产"),
|
|
407
|
+
balance: text(asset, ["balance"], "0"),
|
|
408
|
+
usd: number(asset, ["usdValue"]),
|
|
409
|
+
chain:
|
|
410
|
+
text(asset, ["chainName", "chainSymbol", "chainIndex"]) || undefined,
|
|
411
|
+
}));
|
|
412
|
+
return {
|
|
413
|
+
accountId: account.id,
|
|
414
|
+
address: account.address,
|
|
415
|
+
chain: "全部网络",
|
|
416
|
+
totalUsd:
|
|
417
|
+
accounts.length === 1
|
|
418
|
+
? number(balances, ["totalValueUsd"])
|
|
419
|
+
: assets.reduce((sum, asset) => sum + asset.usd, 0),
|
|
420
|
+
addresses: account.id === fallbackId ? currentAddresses : [],
|
|
421
|
+
assets,
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const ownedAgents = agentsFrom(agentData);
|
|
426
|
+
const identities = ownedAgents
|
|
427
|
+
.map(({ group, agent }) => {
|
|
428
|
+
const agentRole = role(
|
|
429
|
+
field(agent, ["roleLabel", "role", "identity"], ["role"]),
|
|
430
|
+
);
|
|
431
|
+
const id = String(field(agent, ["agentId", "id"], ["agent id"]) ?? "");
|
|
432
|
+
if (!/^\d+$/.test(id)) return null;
|
|
433
|
+
return {
|
|
434
|
+
id,
|
|
435
|
+
accountId: text(group, ["accountId"], fallbackId),
|
|
436
|
+
name: String(
|
|
437
|
+
field(agent, ["name", "agentName"], ["name"]) ?? `Agent ${id}`,
|
|
438
|
+
),
|
|
439
|
+
role: agentRole,
|
|
440
|
+
status:
|
|
441
|
+
agentRole === "ASP"
|
|
442
|
+
? displayLabel(
|
|
443
|
+
field(agent, ["statusLabel"], ["status"]) ?? "未提供",
|
|
444
|
+
)
|
|
445
|
+
: "—",
|
|
446
|
+
review:
|
|
447
|
+
agentRole === "ASP"
|
|
448
|
+
? displayLabel(
|
|
449
|
+
field(
|
|
450
|
+
agent,
|
|
451
|
+
["approvalLabel", "approvalStatusLabel"],
|
|
452
|
+
["approval status"],
|
|
453
|
+
) ?? "未提供",
|
|
454
|
+
)
|
|
455
|
+
: "—",
|
|
456
|
+
rejection: text(agent, ["rejectionReason"]) || undefined,
|
|
457
|
+
online:
|
|
458
|
+
first(agent, ["online", "isOnline"]) === true ||
|
|
459
|
+
Number(first(agent, ["onlineStatus"])) === 1,
|
|
460
|
+
address: text(
|
|
461
|
+
agent,
|
|
462
|
+
["agentWalletAddress", "address", "ownerAddress"],
|
|
463
|
+
text(group, ["ownerAddress"]),
|
|
464
|
+
),
|
|
465
|
+
rating: Number.isFinite(Number(cell(agent, ["rating"])))
|
|
466
|
+
? Number(cell(agent, ["rating"]))
|
|
467
|
+
: null,
|
|
468
|
+
serviceIds: [],
|
|
469
|
+
};
|
|
470
|
+
})
|
|
471
|
+
.filter(Boolean);
|
|
472
|
+
|
|
473
|
+
const serviceResults = await Promise.all(
|
|
474
|
+
identities
|
|
475
|
+
.filter((identity) => identity.role === "ASP")
|
|
476
|
+
.map(async (identity) => ({
|
|
477
|
+
identity,
|
|
478
|
+
data: await optional(
|
|
479
|
+
`${identity.name} 服务`,
|
|
480
|
+
[
|
|
481
|
+
"agent",
|
|
482
|
+
"service-list",
|
|
483
|
+
"--agent-id",
|
|
484
|
+
identity.id,
|
|
485
|
+
"--page",
|
|
486
|
+
"1",
|
|
487
|
+
"--page-size",
|
|
488
|
+
"50",
|
|
489
|
+
],
|
|
490
|
+
warnings,
|
|
491
|
+
),
|
|
492
|
+
})),
|
|
493
|
+
);
|
|
494
|
+
const services = serviceResults.flatMap(({ identity, data }) =>
|
|
495
|
+
serviceRows(data).map((item, index) => {
|
|
496
|
+
const id = text(
|
|
497
|
+
item,
|
|
498
|
+
["serviceId", "id", "sid"],
|
|
499
|
+
`${identity.id}-${index}`,
|
|
500
|
+
);
|
|
501
|
+
identity.serviceIds.push(id);
|
|
502
|
+
const rawType = String(
|
|
503
|
+
field(item, ["serviceType", "type"], ["type"]) ?? "",
|
|
504
|
+
);
|
|
505
|
+
return {
|
|
506
|
+
id,
|
|
507
|
+
accountId: identity.accountId,
|
|
508
|
+
identityId: identity.id,
|
|
509
|
+
name: String(
|
|
510
|
+
field(item, ["serviceName", "name"], ["name"]) ?? "未命名服务",
|
|
511
|
+
),
|
|
512
|
+
type: rawType === "A2A" || rawType === "A2MCP" ? rawType : "未知",
|
|
513
|
+
price: String(field(item, ["fee", "price"], ["fee"]) ?? "0"),
|
|
514
|
+
endpoint: String(field(item, ["endpoint"], ["endpoint"]) ?? "未提供"),
|
|
515
|
+
description: text(item, ["serviceDescription", "description"]),
|
|
516
|
+
volume: number(item, ["salesCount", "soldCount"]),
|
|
517
|
+
review: identity.review,
|
|
518
|
+
};
|
|
519
|
+
}),
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
const activityResults = await Promise.all(
|
|
523
|
+
identities.map(async (identity) => {
|
|
524
|
+
const common = ["--agent-id", identity.id];
|
|
525
|
+
const feedback = await optional(
|
|
526
|
+
`${identity.name} 评价`,
|
|
527
|
+
[
|
|
528
|
+
"agent",
|
|
529
|
+
"feedback-list",
|
|
530
|
+
...common,
|
|
531
|
+
"--page",
|
|
532
|
+
"1",
|
|
533
|
+
"--page-size",
|
|
534
|
+
"50",
|
|
535
|
+
],
|
|
536
|
+
warnings,
|
|
537
|
+
);
|
|
538
|
+
const arbitration =
|
|
539
|
+
identity.role === "User" || identity.role === "ASP"
|
|
540
|
+
? await optional(
|
|
541
|
+
`${identity.name} 争议`,
|
|
542
|
+
[
|
|
543
|
+
"agent",
|
|
544
|
+
"arbitration-list",
|
|
545
|
+
...common,
|
|
546
|
+
"--page",
|
|
547
|
+
"1",
|
|
548
|
+
"--page-size",
|
|
549
|
+
"50",
|
|
550
|
+
],
|
|
551
|
+
warnings,
|
|
552
|
+
)
|
|
553
|
+
: null;
|
|
554
|
+
const refunds =
|
|
555
|
+
identity.role === "User" || identity.role === "ASP"
|
|
556
|
+
? await optional(
|
|
557
|
+
`${identity.name} 退款`,
|
|
558
|
+
[
|
|
559
|
+
"agent",
|
|
560
|
+
"refund-list",
|
|
561
|
+
"--role",
|
|
562
|
+
identity.role === "ASP" ? "provider" : "buyer",
|
|
563
|
+
"--scope",
|
|
564
|
+
"requested",
|
|
565
|
+
...common,
|
|
566
|
+
"--page-size",
|
|
567
|
+
"50",
|
|
568
|
+
],
|
|
569
|
+
warnings,
|
|
570
|
+
)
|
|
571
|
+
: null;
|
|
572
|
+
return { identity, feedback, arbitration, refunds };
|
|
573
|
+
}),
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const tasks = taskRows(taskData);
|
|
577
|
+
const userTasks = userTaskRows(userTaskData);
|
|
578
|
+
const invocations = invocationData ?? [];
|
|
579
|
+
const buyerSubscriptions = subscriptionRows(buyerSubs);
|
|
580
|
+
const providerSubscriptions = subscriptionRows(providerSubs);
|
|
581
|
+
const transactions = historyRows(history);
|
|
582
|
+
const feedbacks = activityResults.flatMap(({ identity, feedback }) =>
|
|
583
|
+
feedbackRows(feedback).map((item) => ({ identity, item })),
|
|
584
|
+
);
|
|
585
|
+
const arbitrations = activityResults.flatMap(({ identity, arbitration }) =>
|
|
586
|
+
arbitrationRows(arbitration).map((item) => ({ identity, item })),
|
|
587
|
+
);
|
|
588
|
+
const refunds = activityResults.flatMap(({ identity, refunds }) =>
|
|
589
|
+
refundRows(refunds).map((item) => ({ identity, item })),
|
|
590
|
+
);
|
|
591
|
+
const events = [
|
|
592
|
+
...invocations,
|
|
593
|
+
...tasks.map((item) =>
|
|
594
|
+
event(
|
|
595
|
+
item,
|
|
596
|
+
first(item, ["jobType", "taskType"]) === 1 ? "subscription" : "task",
|
|
597
|
+
fallbackId,
|
|
598
|
+
),
|
|
599
|
+
),
|
|
600
|
+
...userTasks.map((item) => event(item, "task", fallbackId, "User")),
|
|
601
|
+
...buyerSubscriptions.map((item) =>
|
|
602
|
+
event(item, "subscription", fallbackId, "User"),
|
|
603
|
+
),
|
|
604
|
+
...providerSubscriptions.map((item) =>
|
|
605
|
+
event(item, "subscription", fallbackId, "ASP"),
|
|
606
|
+
),
|
|
607
|
+
...transactions.map((item) => {
|
|
608
|
+
const direction = first(item, ["direction"]);
|
|
609
|
+
const prefix =
|
|
610
|
+
direction === "IN" ? "转入" : direction === "OUT" ? "转出" : "流水";
|
|
611
|
+
return event(
|
|
612
|
+
{ ...item, title: `${prefix} ${text(item, ["coinSymbol"], "资产")}` },
|
|
613
|
+
"transaction",
|
|
614
|
+
fallbackId,
|
|
615
|
+
"User",
|
|
616
|
+
);
|
|
617
|
+
}),
|
|
618
|
+
...feedbacks.map(({ identity, item }) =>
|
|
619
|
+
event(item, "feedback", identity.accountId, identity.role),
|
|
620
|
+
),
|
|
621
|
+
...arbitrations.map(({ identity, item }) =>
|
|
622
|
+
event(item, "arbitration", identity.accountId, identity.role),
|
|
623
|
+
),
|
|
624
|
+
...refunds.map(({ identity, item }) =>
|
|
625
|
+
event(item, "arbitration", identity.accountId, identity.role),
|
|
626
|
+
),
|
|
627
|
+
];
|
|
628
|
+
const uniqueEvents = [
|
|
629
|
+
...new Map(
|
|
630
|
+
events.map((item) => [`${item.kind}:${item.id}`, item]),
|
|
631
|
+
).values(),
|
|
632
|
+
];
|
|
633
|
+
const source = (key, label, data, count, detail) => ({
|
|
634
|
+
key,
|
|
635
|
+
label,
|
|
636
|
+
state: data === null ? "error" : count ? "available" : "empty",
|
|
637
|
+
count,
|
|
638
|
+
...(detail ? { detail } : {}),
|
|
639
|
+
});
|
|
640
|
+
const sources = [
|
|
641
|
+
source(
|
|
642
|
+
"wallet-addresses",
|
|
643
|
+
"钱包地址",
|
|
644
|
+
addressData,
|
|
645
|
+
currentAddresses.length,
|
|
646
|
+
),
|
|
647
|
+
source(
|
|
648
|
+
"wallet-assets",
|
|
649
|
+
"钱包资产",
|
|
650
|
+
balances,
|
|
651
|
+
wallets.reduce((sum, wallet) => sum + wallet.assets.length, 0),
|
|
652
|
+
),
|
|
653
|
+
source("wallet-history", "钱包流水", history, transactions.length),
|
|
654
|
+
source("identities", "Agent 身份", agentData, identities.length),
|
|
655
|
+
source(
|
|
656
|
+
"services",
|
|
657
|
+
"ASP 服务",
|
|
658
|
+
serviceResults.some(({ data }) => data === null) ? null : true,
|
|
659
|
+
services.length,
|
|
660
|
+
),
|
|
661
|
+
source("tasks", "任务", taskData, tasks.length),
|
|
662
|
+
source("user-tasks", "用户任务历史", userTaskData, userTasks.length),
|
|
663
|
+
source(
|
|
664
|
+
"a2mcp-invocations",
|
|
665
|
+
"A2MCP 调用记录",
|
|
666
|
+
invocationData,
|
|
667
|
+
invocations.length,
|
|
668
|
+
"来自 OnchainOS 本机付款记录",
|
|
669
|
+
),
|
|
670
|
+
source(
|
|
671
|
+
"buyer-subscriptions",
|
|
672
|
+
"买方订阅",
|
|
673
|
+
buyerSubs,
|
|
674
|
+
buyerSubscriptions.length,
|
|
675
|
+
),
|
|
676
|
+
source(
|
|
677
|
+
"provider-subscriptions",
|
|
678
|
+
"服务方订阅",
|
|
679
|
+
providerSubs,
|
|
680
|
+
providerSubscriptions.length,
|
|
681
|
+
),
|
|
682
|
+
source(
|
|
683
|
+
"feedback",
|
|
684
|
+
"评价",
|
|
685
|
+
activityResults.some(({ feedback }) => feedback === null) ? null : true,
|
|
686
|
+
feedbacks.length,
|
|
687
|
+
),
|
|
688
|
+
source(
|
|
689
|
+
"arbitration",
|
|
690
|
+
"争议",
|
|
691
|
+
activityResults
|
|
692
|
+
.filter(({ identity }) => ["User", "ASP"].includes(identity.role))
|
|
693
|
+
.some(({ arbitration }) => arbitration === null)
|
|
694
|
+
? null
|
|
695
|
+
: true,
|
|
696
|
+
arbitrations.length,
|
|
697
|
+
),
|
|
698
|
+
source(
|
|
699
|
+
"refunds",
|
|
700
|
+
"退款",
|
|
701
|
+
activityResults
|
|
702
|
+
.filter(({ identity }) => ["User", "ASP"].includes(identity.role))
|
|
703
|
+
.some(({ refunds }) => refunds === null)
|
|
704
|
+
? null
|
|
705
|
+
: true,
|
|
706
|
+
refunds.length,
|
|
707
|
+
),
|
|
708
|
+
{
|
|
709
|
+
key: "claimable",
|
|
710
|
+
label: "待领取奖励",
|
|
711
|
+
state: "unsupported",
|
|
712
|
+
detail: "CLI 4.6.0 当前仅返回文本,暂不展示",
|
|
713
|
+
},
|
|
714
|
+
];
|
|
715
|
+
|
|
716
|
+
return {
|
|
717
|
+
mode: "live",
|
|
718
|
+
capturedAt: new Date().toISOString(),
|
|
719
|
+
warnings: [...new Set(warnings)],
|
|
720
|
+
accounts,
|
|
721
|
+
identities,
|
|
722
|
+
services,
|
|
723
|
+
wallets,
|
|
724
|
+
events: uniqueEvents,
|
|
725
|
+
sources,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function extensionId(key) {
|
|
730
|
+
const digest = createHash("sha256")
|
|
731
|
+
.update(Buffer.from(key, "base64"))
|
|
732
|
+
.digest("hex")
|
|
733
|
+
.slice(0, 32);
|
|
734
|
+
return [...digest]
|
|
735
|
+
.map((value) => "abcdefghijklmnop"[parseInt(value, 16)])
|
|
736
|
+
.join("");
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const shellQuote = (value) => `'${String(value).replaceAll("'", `'"'"'`)}'`;
|
|
740
|
+
|
|
741
|
+
async function install() {
|
|
742
|
+
const manifest = JSON.parse(
|
|
743
|
+
await readFile(join(dirname(scriptPath), "public/manifest.json"), "utf8"),
|
|
744
|
+
);
|
|
745
|
+
const id = extensionId(manifest.key);
|
|
746
|
+
const hostDirectory = join(
|
|
747
|
+
homedir(),
|
|
748
|
+
"Library/Application Support/Google/Chrome/NativeMessagingHosts",
|
|
749
|
+
);
|
|
750
|
+
const target = join(hostDirectory, `${hostName}.json`);
|
|
751
|
+
const launcher = join(hostDirectory, `${hostName}.sh`);
|
|
752
|
+
await mkdir(dirname(target), { recursive: true });
|
|
753
|
+
await chmod(scriptPath, 0o755);
|
|
754
|
+
await writeFile(
|
|
755
|
+
launcher,
|
|
756
|
+
`#!/bin/sh\nexec ${shellQuote(process.execPath)} ${shellQuote(scriptPath)}\n`,
|
|
757
|
+
{ mode: 0o755 },
|
|
758
|
+
);
|
|
759
|
+
await chmod(launcher, 0o755);
|
|
760
|
+
await writeFile(
|
|
761
|
+
target,
|
|
762
|
+
`${JSON.stringify({ name: hostName, description: "Onchain OS Console read-only data bridge", path: launcher, type: "stdio", allowed_origins: [`chrome-extension://${id}/`] }, null, 2)}\n`,
|
|
763
|
+
{ mode: 0o644 },
|
|
764
|
+
);
|
|
765
|
+
console.log(
|
|
766
|
+
JSON.stringify({
|
|
767
|
+
ok: true,
|
|
768
|
+
host: hostName,
|
|
769
|
+
extensionId: id,
|
|
770
|
+
manifest: target,
|
|
771
|
+
launcher,
|
|
772
|
+
}),
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function send(value) {
|
|
777
|
+
const body = Buffer.from(JSON.stringify(value));
|
|
778
|
+
const header = Buffer.alloc(4);
|
|
779
|
+
header.writeUInt32LE(body.length);
|
|
780
|
+
process.stdout.write(Buffer.concat([header, body]));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
async function main() {
|
|
784
|
+
if (process.argv.includes("--install")) return install();
|
|
785
|
+
if (process.argv.includes("--snapshot")) {
|
|
786
|
+
console.log(JSON.stringify({ ok: true, data: await snapshot() }, null, 2));
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
let input = Buffer.alloc(0);
|
|
790
|
+
process.stdin.on("data", async (chunk) => {
|
|
791
|
+
input = Buffer.concat([input, chunk]);
|
|
792
|
+
while (input.length >= 4) {
|
|
793
|
+
const size = input.readUInt32LE(0);
|
|
794
|
+
if (input.length < size + 4) return;
|
|
795
|
+
const request = JSON.parse(input.subarray(4, size + 4).toString("utf8"));
|
|
796
|
+
input = input.subarray(size + 4);
|
|
797
|
+
try {
|
|
798
|
+
if (request?.version !== 1 || request?.method !== "snapshot")
|
|
799
|
+
throw new Error("不支持的请求");
|
|
800
|
+
send({
|
|
801
|
+
version: 1,
|
|
802
|
+
requestId: request.requestId,
|
|
803
|
+
ok: true,
|
|
804
|
+
data: await snapshot(),
|
|
805
|
+
});
|
|
806
|
+
} catch (error) {
|
|
807
|
+
send({
|
|
808
|
+
version: 1,
|
|
809
|
+
requestId: request?.requestId,
|
|
810
|
+
ok: false,
|
|
811
|
+
error: message(error),
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (process.argv[1] && realpathSync(process.argv[1]) === scriptPath)
|
|
819
|
+
main().catch((error) => {
|
|
820
|
+
if (process.argv.some((arg) => arg.startsWith("--"))) {
|
|
821
|
+
console.error(JSON.stringify({ ok: false, error: message(error) }));
|
|
822
|
+
process.exitCode = 1;
|
|
823
|
+
} else {
|
|
824
|
+
send({ version: 1, ok: false, error: message(error) });
|
|
825
|
+
}
|
|
826
|
+
});
|