@twinsearth/agent-universe 2.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.d.ts +108 -0
- package/index.js +52 -0
- package/lib/dht.js +92 -0
- package/lib/keychain.js +74 -0
- package/lib/market.js +240 -0
- package/lib/models.js +124 -0
- package/package.json +43 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Type definitions for @twinsearth/agent-universe
|
|
2
|
+
|
|
3
|
+
export const version: string;
|
|
4
|
+
export const MIN_STAKE: number;
|
|
5
|
+
|
|
6
|
+
export class Keypair {
|
|
7
|
+
publicKey: string;
|
|
8
|
+
privateKey: string;
|
|
9
|
+
did: string;
|
|
10
|
+
static generate(): Keypair;
|
|
11
|
+
sign(message: string | Buffer): string;
|
|
12
|
+
verify(message: string | Buffer, signatureHex: string): boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class AgentCard {
|
|
16
|
+
did: string;
|
|
17
|
+
name: string;
|
|
18
|
+
capabilities: string[];
|
|
19
|
+
skills: string[];
|
|
20
|
+
version: string;
|
|
21
|
+
stake: number;
|
|
22
|
+
reputation: number;
|
|
23
|
+
static new(opts: { did: string; name: string }): AgentCard;
|
|
24
|
+
withCapability(cap: string): this;
|
|
25
|
+
withSkill(skill: string): this;
|
|
26
|
+
hasCapability(cap: string): boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const TaskStatus: Record<string, string>;
|
|
30
|
+
|
|
31
|
+
export class Task {
|
|
32
|
+
taskId: string;
|
|
33
|
+
goal: string;
|
|
34
|
+
status: string;
|
|
35
|
+
assignee: string | null;
|
|
36
|
+
history: string[];
|
|
37
|
+
constructor(taskId: string, goal: string);
|
|
38
|
+
assign(did: string): this;
|
|
39
|
+
start(): this;
|
|
40
|
+
complete(): this;
|
|
41
|
+
verify(passed: boolean): this;
|
|
42
|
+
settle(): this;
|
|
43
|
+
readonly isTerminal: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class MemoryDHT {
|
|
47
|
+
put(key: string, value: unknown): void;
|
|
48
|
+
get(key: string): unknown;
|
|
49
|
+
has(key: string): boolean;
|
|
50
|
+
remove(key: string): boolean;
|
|
51
|
+
readonly size: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class ShardedIndex {
|
|
55
|
+
constructor(numShards?: number);
|
|
56
|
+
put(key: string, value: unknown): number;
|
|
57
|
+
get(key: string): unknown;
|
|
58
|
+
has(key: string): boolean;
|
|
59
|
+
remove(key: string): boolean;
|
|
60
|
+
readonly size: number;
|
|
61
|
+
distribution(): number[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function shardOf(key: string, numShards: number): number;
|
|
65
|
+
|
|
66
|
+
export class Bid {
|
|
67
|
+
agentId: string;
|
|
68
|
+
price: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class Reputation {
|
|
72
|
+
quality: number;
|
|
73
|
+
speed: number;
|
|
74
|
+
honesty: number;
|
|
75
|
+
availability: number;
|
|
76
|
+
overall(): number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class AgentMarket {
|
|
80
|
+
agents: Map<string, unknown>;
|
|
81
|
+
tasks: Map<string, Task>;
|
|
82
|
+
deposit(account: string, amount: number): void;
|
|
83
|
+
balance(account: string): number;
|
|
84
|
+
registerAgent(card: AgentCard, stake: number): boolean;
|
|
85
|
+
publishTask(taskId: string, goal: string, budget: number, requester: string): Task;
|
|
86
|
+
submitBid(taskId: string, agentId: string, price: number): boolean;
|
|
87
|
+
matchTask(taskId: string): Bid;
|
|
88
|
+
completeTask(taskId: string, passed?: boolean): Task;
|
|
89
|
+
settle(taskId: string): { paid: number; reason: string };
|
|
90
|
+
slash(agentId: string, amount: number, reason: string): unknown;
|
|
91
|
+
discoverBySkill(skill: string): AgentCard[];
|
|
92
|
+
leaderboard(limit?: number): unknown[];
|
|
93
|
+
conservationCheck(totalDeposits: number): {
|
|
94
|
+
conserved: boolean;
|
|
95
|
+
balanceSum: number;
|
|
96
|
+
totalSlashed: number;
|
|
97
|
+
expected: number;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class AgentUniverse {
|
|
102
|
+
identity: Keypair;
|
|
103
|
+
dht: MemoryDHT;
|
|
104
|
+
market: AgentMarket;
|
|
105
|
+
readonly did: string;
|
|
106
|
+
register(card: AgentCard): AgentCard;
|
|
107
|
+
discover(capability: string): AgentCard[];
|
|
108
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// index.js
|
|
2
|
+
// Agent Universe(智能体宇宙)主入口
|
|
3
|
+
// 群众化的 AGI 路线,去中心化的智能体共享 & 开源网络
|
|
4
|
+
|
|
5
|
+
const { Keypair } = require('./lib/keychain');
|
|
6
|
+
const { AgentCard, Task, TaskStatus } = require('./lib/models');
|
|
7
|
+
const { MemoryDHT, ShardedIndex, shardOf } = require('./lib/dht');
|
|
8
|
+
const { AgentMarket, Bid, Reputation, MIN_STAKE } = require('./lib/market');
|
|
9
|
+
|
|
10
|
+
const version = '2.3.4';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* AgentUniverse 门面:聚合身份、网络、市场能力
|
|
14
|
+
*/
|
|
15
|
+
class AgentUniverse {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.identity = Keypair.generate();
|
|
18
|
+
this.dht = new MemoryDHT();
|
|
19
|
+
this.market = new AgentMarket();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get did() {
|
|
23
|
+
return this.identity.did;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 注册一张 AgentCard 到本地网络 */
|
|
27
|
+
register(card) {
|
|
28
|
+
this.dht.put(card.did, card);
|
|
29
|
+
return card;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 发现某能力的 Agent */
|
|
33
|
+
discover(capability) {
|
|
34
|
+
return this.market.discoverBySkill(capability);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
version,
|
|
40
|
+
AgentUniverse,
|
|
41
|
+
Keypair,
|
|
42
|
+
AgentCard,
|
|
43
|
+
Task,
|
|
44
|
+
TaskStatus,
|
|
45
|
+
MemoryDHT,
|
|
46
|
+
ShardedIndex,
|
|
47
|
+
shardOf,
|
|
48
|
+
AgentMarket,
|
|
49
|
+
Bid,
|
|
50
|
+
Reputation,
|
|
51
|
+
MIN_STAKE,
|
|
52
|
+
};
|
package/lib/dht.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// lib/dht.js
|
|
2
|
+
// MemoryDHT / ShardedIndex / shardOf(SHA-256 分片)
|
|
3
|
+
|
|
4
|
+
const crypto = require('node:crypto');
|
|
5
|
+
|
|
6
|
+
/** 计算 key 所属分片:SHA-256(key) 前 8 hex 转整数,对 numShards 取模 */
|
|
7
|
+
function shardOf(key, numShards) {
|
|
8
|
+
const hash = crypto.createHash('sha256').update(String(key)).digest('hex');
|
|
9
|
+
const prefix = parseInt(hash.slice(0, 8), 16);
|
|
10
|
+
return prefix % numShards;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 内存 DHT(键值存储) */
|
|
14
|
+
class MemoryDHT {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.store = new Map();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
put(key, value) {
|
|
20
|
+
this.store.set(String(key), value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get(key) {
|
|
24
|
+
return this.store.get(String(key));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
has(key) {
|
|
28
|
+
return this.store.has(String(key));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
remove(key) {
|
|
32
|
+
return this.store.delete(String(key));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get size() {
|
|
36
|
+
return this.store.size;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 按前缀查找 */
|
|
40
|
+
findByPrefix(prefix) {
|
|
41
|
+
const result = [];
|
|
42
|
+
for (const [k, v] of this.store) {
|
|
43
|
+
if (k.startsWith(prefix)) result.push({ key: k, value: v });
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 分片索引:把 key 均匀分布到 numShards 个分片 */
|
|
50
|
+
class ShardedIndex {
|
|
51
|
+
constructor(numShards = 16) {
|
|
52
|
+
this.numShards = numShards;
|
|
53
|
+
this.shards = [];
|
|
54
|
+
for (let i = 0; i < numShards; i++) this.shards.push(new Map());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
put(key, value) {
|
|
58
|
+
const s = shardOf(key, this.numShards);
|
|
59
|
+
this.shards[s].set(String(key), value);
|
|
60
|
+
return s;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get(key) {
|
|
64
|
+
const s = shardOf(key, this.numShards);
|
|
65
|
+
return this.shards[s].get(String(key));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
has(key) {
|
|
69
|
+
const s = shardOf(key, this.numShards);
|
|
70
|
+
return this.shards[s].has(String(key));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
remove(key) {
|
|
74
|
+
const s = shardOf(key, this.numShards);
|
|
75
|
+
return this.shards[s].delete(String(key));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
shardSize(i) {
|
|
79
|
+
return this.shards[i].size;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get size() {
|
|
83
|
+
return this.shards.reduce((sum, m) => sum + m.size, 0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 各分片条目数(用于验证分布均匀性) */
|
|
87
|
+
distribution() {
|
|
88
|
+
return this.shards.map((m) => m.size);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { MemoryDHT, ShardedIndex, shardOf };
|
package/lib/keychain.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// lib/keychain.js
|
|
2
|
+
// Ed25519 密钥对 + DID 身份(零依赖,使用 node:crypto)
|
|
3
|
+
|
|
4
|
+
const crypto = require('node:crypto');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 密钥对与去中心化身份
|
|
8
|
+
*/
|
|
9
|
+
class Keypair {
|
|
10
|
+
constructor(publicKey, privateKey, did) {
|
|
11
|
+
this.publicKey = publicKey; // hex
|
|
12
|
+
this.privateKey = privateKey; // hex(node KeyObject 可导出的形式)
|
|
13
|
+
this.did = did; // did:au:<hash>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 生成新的 Ed25519 密钥对与 DID */
|
|
17
|
+
static generate() {
|
|
18
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
|
|
19
|
+
const pubHex = publicKey.export({ format: 'der', type: 'spki' })
|
|
20
|
+
.toString('hex');
|
|
21
|
+
// DID 由公钥的 SHA-256 哈希(取前 32 字符)派生
|
|
22
|
+
const didHash = crypto.createHash('sha256')
|
|
23
|
+
.update(publicKey.export({ format: 'der', type: 'spki' }))
|
|
24
|
+
.digest('hex')
|
|
25
|
+
.slice(0, 32);
|
|
26
|
+
const did = `did:au:${didHash}`;
|
|
27
|
+
|
|
28
|
+
// 私钥以 PKCS8 DER hex 保存,便于重建 KeyObject
|
|
29
|
+
const privHex = privateKey.export({ format: 'der', type: 'pkcs8' })
|
|
30
|
+
.toString('hex');
|
|
31
|
+
|
|
32
|
+
const kp = new Keypair(pubHex, privHex, did);
|
|
33
|
+
kp._pubKeyObj = publicKey;
|
|
34
|
+
kp._privKeyObj = privateKey;
|
|
35
|
+
return kp;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 获取可用于签名的私钥 KeyObject */
|
|
39
|
+
_privateKeyObject() {
|
|
40
|
+
if (this._privKeyObj) return this._privKeyObj;
|
|
41
|
+
this._privKeyObj = crypto.createPrivateKey({
|
|
42
|
+
key: Buffer.from(this.privateKey, 'hex'),
|
|
43
|
+
format: 'der',
|
|
44
|
+
type: 'pkcs8',
|
|
45
|
+
});
|
|
46
|
+
return this._privKeyObj;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 获取公钥 KeyObject */
|
|
50
|
+
_publicKeyObject() {
|
|
51
|
+
if (this._pubKeyObj) return this._pubKeyObj;
|
|
52
|
+
this._pubKeyObj = crypto.createPublicKey({
|
|
53
|
+
key: Buffer.from(this.publicKey, 'hex'),
|
|
54
|
+
format: 'der',
|
|
55
|
+
type: 'spki',
|
|
56
|
+
});
|
|
57
|
+
return this._pubKeyObj;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 对消息签名,返回 hex */
|
|
61
|
+
sign(message) {
|
|
62
|
+
const data = Buffer.isBuffer(message) ? message : Buffer.from(message);
|
|
63
|
+
return crypto.sign(null, data, this._privateKeyObject()).toString('hex');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 验证签名(默认用本密钥对的公钥) */
|
|
67
|
+
verify(message, signatureHex) {
|
|
68
|
+
const data = Buffer.isBuffer(message) ? message : Buffer.from(message);
|
|
69
|
+
const sig = Buffer.from(signatureHex, 'hex');
|
|
70
|
+
return crypto.verify(null, data, this._publicKeyObject(), sig);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { Keypair };
|
package/lib/market.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// lib/market.js
|
|
2
|
+
// v2.3.4 Agent Market:注册 / 发布 / 投标 / 匹配 / 结算 / 罚没 / 守恒
|
|
3
|
+
|
|
4
|
+
const { Task, TaskStatus } = require('./models');
|
|
5
|
+
|
|
6
|
+
const MIN_STAKE = 100;
|
|
7
|
+
|
|
8
|
+
/** 投标 */
|
|
9
|
+
class Bid {
|
|
10
|
+
constructor(agentId, price) {
|
|
11
|
+
this.agentId = agentId;
|
|
12
|
+
this.price = price;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 信誉档案(简化四维) */
|
|
17
|
+
class Reputation {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.quality = 0;
|
|
20
|
+
this.speed = 0;
|
|
21
|
+
this.honesty = 0;
|
|
22
|
+
this.availability = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
overall() {
|
|
26
|
+
return this.quality * 0.35 + this.speed * 0.20 +
|
|
27
|
+
this.honesty * 0.30 + this.availability * 0.15;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 智能体市场
|
|
33
|
+
*/
|
|
34
|
+
class AgentMarket {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.agents = new Map(); // agentId → { card, stake, reputation }
|
|
37
|
+
this.tasks = new Map(); // taskId → Task
|
|
38
|
+
this.bids = new Map(); // taskId → Bid[]
|
|
39
|
+
this.balances = new Map(); // 账户 → 余额
|
|
40
|
+
this.totalSlashed = 0;
|
|
41
|
+
this.paidTasks = new Set(); // 已结算任务(防重复支付)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 充值 / 质押金进入账户 */
|
|
45
|
+
deposit(account, amount) {
|
|
46
|
+
if (amount < 0) throw new Error('金额不能为负');
|
|
47
|
+
const cur = this.balances.get(account) || 0;
|
|
48
|
+
this.balances.set(account, cur + amount);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
balance(account) {
|
|
52
|
+
return this.balances.get(account) || 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 质押锁定账户(仍计入系统总余额,只是不可流动) */
|
|
56
|
+
_stakeAccount(agentId) {
|
|
57
|
+
return `__stake__:${agentId}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 注册 Agent(需质押 ≥ MIN_STAKE) */
|
|
61
|
+
registerAgent(card, stake) {
|
|
62
|
+
if (!card || !card.did) throw new Error('agent_id 不能为空');
|
|
63
|
+
if (!card.name) throw new Error('name 不能为空');
|
|
64
|
+
if (this.agents.has(card.did)) throw new Error('Agent 已存在');
|
|
65
|
+
if (stake < MIN_STAKE) {
|
|
66
|
+
throw new Error(`质押不足,最低 ${MIN_STAKE}`);
|
|
67
|
+
}
|
|
68
|
+
// 质押金从发布者账户转入锁定账户(不退出系统,仍计入总余额)
|
|
69
|
+
const owner = card.did;
|
|
70
|
+
if (this.balance(owner) < stake) {
|
|
71
|
+
throw new Error('质押账户余额不足,请先 deposit');
|
|
72
|
+
}
|
|
73
|
+
const stakeAcc = this._stakeAccount(owner);
|
|
74
|
+
this.balances.set(owner, this.balance(owner) - stake);
|
|
75
|
+
this.balances.set(stakeAcc, this.balance(stakeAcc) + stake);
|
|
76
|
+
|
|
77
|
+
this.agents.set(card.did, {
|
|
78
|
+
card,
|
|
79
|
+
stake,
|
|
80
|
+
reputation: new Reputation(),
|
|
81
|
+
status: 'active',
|
|
82
|
+
});
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 发布任务 */
|
|
87
|
+
publishTask(taskId, goal, budget, requester) {
|
|
88
|
+
if (!goal) throw new Error('goal 不能为空');
|
|
89
|
+
if (budget <= 0) throw new Error('预算必须为正');
|
|
90
|
+
if (this.balance(requester) < budget) {
|
|
91
|
+
throw new Error('需求方余额不足以覆盖预算');
|
|
92
|
+
}
|
|
93
|
+
// 预算锁定
|
|
94
|
+
this.balances.set(requester, this.balance(requester) - budget);
|
|
95
|
+
|
|
96
|
+
const task = new Task(taskId, goal);
|
|
97
|
+
task.transition(TaskStatus.OPEN);
|
|
98
|
+
task.requester = requester;
|
|
99
|
+
task.budget = budget;
|
|
100
|
+
this.tasks.set(taskId, task);
|
|
101
|
+
this.bids.set(taskId, []);
|
|
102
|
+
return task;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 提交投标 */
|
|
106
|
+
submitBid(taskId, agentId, price) {
|
|
107
|
+
if (!this.agents.has(agentId)) throw new Error('Agent 未注册');
|
|
108
|
+
const task = this.tasks.get(taskId);
|
|
109
|
+
if (!task || task.status !== TaskStatus.OPEN) {
|
|
110
|
+
throw new Error('任务不在开放状态');
|
|
111
|
+
}
|
|
112
|
+
this.bids.get(taskId).push(new Bid(agentId, price));
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 匹配:选性价比最高(信誉/价格)的投标,平局选价格低者 */
|
|
117
|
+
matchTask(taskId) {
|
|
118
|
+
const bids = this.bids.get(taskId) || [];
|
|
119
|
+
if (bids.length === 0) throw new Error('无投标,无法匹配');
|
|
120
|
+
|
|
121
|
+
let best = bids[0];
|
|
122
|
+
let bestScore = this._bidScore(best);
|
|
123
|
+
for (let i = 1; i < bids.length; i++) {
|
|
124
|
+
const score = this._bidScore(bids[i]);
|
|
125
|
+
if (score > bestScore ||
|
|
126
|
+
(score === bestScore && bids[i].price < best.price)) {
|
|
127
|
+
best = bids[i];
|
|
128
|
+
bestScore = score;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const task = this.tasks.get(taskId);
|
|
133
|
+
task.assign(best.agentId);
|
|
134
|
+
task.winnerPrice = best.price;
|
|
135
|
+
return best;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_bidScore(bid) {
|
|
139
|
+
const agent = this.agents.get(bid.agentId);
|
|
140
|
+
const rep = agent.reputation.overall();
|
|
141
|
+
// 性价比 = 信誉 / 价格(价格为 0 时退化为只看信誉)
|
|
142
|
+
return bid.price > 0 ? rep / bid.price : rep;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** 执行 → 完成 → 验证 */
|
|
146
|
+
completeTask(taskId, passed = true) {
|
|
147
|
+
const task = this.tasks.get(taskId);
|
|
148
|
+
task.start();
|
|
149
|
+
task.complete();
|
|
150
|
+
task.verify(passed);
|
|
151
|
+
return task;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 结算(验收通过才支付) */
|
|
155
|
+
settle(taskId) {
|
|
156
|
+
const task = this.tasks.get(taskId);
|
|
157
|
+
|
|
158
|
+
// 结算优先级短路
|
|
159
|
+
if (this.paidTasks.has(taskId)) {
|
|
160
|
+
return { paid: 0, reason: 'already_paid' };
|
|
161
|
+
}
|
|
162
|
+
if (task.status !== TaskStatus.VERIFIED) {
|
|
163
|
+
return { paid: 0, reason: 'rejected' };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const price = task.winnerPrice || task.budget;
|
|
167
|
+
const pay = Math.min(price, task.budget);
|
|
168
|
+
|
|
169
|
+
// 从任务锁定预算中支付给执行者(账户间转账,系统总余额不变)
|
|
170
|
+
const assignee = task.assignee;
|
|
171
|
+
this.balances.set(assignee, this.balance(assignee) + pay);
|
|
172
|
+
// 未用完的预算退还需求方
|
|
173
|
+
const refund = task.budget - pay;
|
|
174
|
+
if (refund > 0) {
|
|
175
|
+
this.balances.set(task.requester, this.balance(task.requester) + refund);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
task.settle();
|
|
179
|
+
this.paidTasks.add(taskId);
|
|
180
|
+
return { paid: pay, reason: 'settled' };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 罚没(作恶):从质押锁定账户扣除,罚没部分退出系统(总余额减少) */
|
|
184
|
+
slash(agentId, amount, reason) {
|
|
185
|
+
const entry = this.agents.get(agentId);
|
|
186
|
+
if (!entry) throw new Error('Agent 不存在');
|
|
187
|
+
const actual = Math.min(amount, entry.stake);
|
|
188
|
+
entry.stake -= actual;
|
|
189
|
+
// 从锁定账户实际扣除(退出系统)
|
|
190
|
+
const stakeAcc = this._stakeAccount(agentId);
|
|
191
|
+
this.balances.set(stakeAcc, this.balance(stakeAcc) - actual);
|
|
192
|
+
this.totalSlashed += actual;
|
|
193
|
+
entry.status = entry.stake < MIN_STAKE ? 'suspended' : entry.status;
|
|
194
|
+
return { slashed: actual, reason, remainingStake: entry.stake };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** 按技能发现 Agent */
|
|
198
|
+
discoverBySkill(skill) {
|
|
199
|
+
const target = String(skill).toLowerCase();
|
|
200
|
+
const result = [];
|
|
201
|
+
for (const [id, entry] of this.agents) {
|
|
202
|
+
const skills = (entry.card.skills || [])
|
|
203
|
+
.map((s) => String(s).toLowerCase());
|
|
204
|
+
const caps = (entry.card.capabilities || [])
|
|
205
|
+
.map((s) => String(s).toLowerCase());
|
|
206
|
+
if (skills.includes(target) || caps.includes(target)) {
|
|
207
|
+
result.push(entry.card);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** 排行榜(按信誉综合分) */
|
|
214
|
+
leaderboard(limit = 10) {
|
|
215
|
+
const entries = [...this.agents.values()]
|
|
216
|
+
.filter((e) => e.status === 'active')
|
|
217
|
+
.map((e) => ({ agentId: e.card.did, score: e.reputation.overall() }))
|
|
218
|
+
.sort((a, b) => b.score - a.score);
|
|
219
|
+
return entries.slice(0, limit);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 守恒检查
|
|
224
|
+
* 支付是账户间转账(系统总余额不变),只有罚没减少系统总余额。
|
|
225
|
+
* 总充值(预算/质押)− 总罚没 = 当前全部账户余额之和
|
|
226
|
+
*/
|
|
227
|
+
conservationCheck(totalDeposits) {
|
|
228
|
+
let balanceSum = 0;
|
|
229
|
+
for (const v of this.balances.values()) balanceSum += v;
|
|
230
|
+
const expected = totalDeposits - this.totalSlashed;
|
|
231
|
+
return {
|
|
232
|
+
conserved: Math.abs(balanceSum - expected) < 1e-9,
|
|
233
|
+
balanceSum,
|
|
234
|
+
totalSlashed: this.totalSlashed,
|
|
235
|
+
expected,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = { AgentMarket, Bid, Reputation, MIN_STAKE };
|
package/lib/models.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// lib/models.js
|
|
2
|
+
// AgentCard / Task / TaskStatus 状态机
|
|
3
|
+
|
|
4
|
+
/** 任务状态枚举 */
|
|
5
|
+
const TaskStatus = {
|
|
6
|
+
PENDING: 'pending',
|
|
7
|
+
OPEN: 'open',
|
|
8
|
+
MATCHED: 'matched',
|
|
9
|
+
ASSIGNED: 'assigned',
|
|
10
|
+
RUNNING: 'running',
|
|
11
|
+
VERIFYING: 'verifying',
|
|
12
|
+
COMPLETED: 'completed',
|
|
13
|
+
VERIFIED: 'verified',
|
|
14
|
+
SETTLED: 'settled',
|
|
15
|
+
REWORK: 'rework',
|
|
16
|
+
DISPUTED: 'disputed',
|
|
17
|
+
REJECTED: 'rejected',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// 合法状态转移表
|
|
21
|
+
const TRANSITIONS = {
|
|
22
|
+
[TaskStatus.PENDING]: [TaskStatus.OPEN],
|
|
23
|
+
[TaskStatus.OPEN]: [TaskStatus.MATCHED, TaskStatus.ASSIGNED],
|
|
24
|
+
[TaskStatus.MATCHED]: [TaskStatus.ASSIGNED],
|
|
25
|
+
[TaskStatus.ASSIGNED]: [TaskStatus.RUNNING],
|
|
26
|
+
[TaskStatus.RUNNING]: [TaskStatus.COMPLETED, TaskStatus.REWORK],
|
|
27
|
+
[TaskStatus.COMPLETED]: [TaskStatus.VERIFIED, TaskStatus.DISPUTED, TaskStatus.REWORK],
|
|
28
|
+
[TaskStatus.VERIFIED]: [TaskStatus.SETTLED],
|
|
29
|
+
[TaskStatus.REWORK]: [TaskStatus.RUNNING],
|
|
30
|
+
[TaskStatus.DISPUTED]: [TaskStatus.SETTLED, TaskStatus.REJECTED],
|
|
31
|
+
[TaskStatus.SETTLED]: [],
|
|
32
|
+
[TaskStatus.REJECTED]: [],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** 智能体卡片 */
|
|
36
|
+
class AgentCard {
|
|
37
|
+
constructor(did, name) {
|
|
38
|
+
this.did = did;
|
|
39
|
+
this.name = name;
|
|
40
|
+
this.capabilities = [];
|
|
41
|
+
this.skills = [];
|
|
42
|
+
this.version = '1.0.0';
|
|
43
|
+
this.stake = 0;
|
|
44
|
+
this.reputation = 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static new({ did, name }) {
|
|
48
|
+
return new AgentCard(did, name);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
withCapability(cap) {
|
|
52
|
+
if (!this.capabilities.includes(cap)) this.capabilities.push(cap);
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
withSkill(skill) {
|
|
57
|
+
if (!this.skills.includes(skill)) this.skills.push(skill);
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
hasCapability(cap) {
|
|
62
|
+
return this.capabilities.includes(cap);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
toJSON() {
|
|
66
|
+
return {
|
|
67
|
+
did: this.did, name: this.name,
|
|
68
|
+
capabilities: this.capabilities, skills: this.skills,
|
|
69
|
+
version: this.version, stake: this.stake, reputation: this.reputation,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 任务与状态机 */
|
|
75
|
+
class Task {
|
|
76
|
+
constructor(taskId, goal) {
|
|
77
|
+
this.taskId = taskId;
|
|
78
|
+
this.goal = goal;
|
|
79
|
+
this.status = TaskStatus.PENDING;
|
|
80
|
+
this.assignee = null;
|
|
81
|
+
this.history = [TaskStatus.PENDING];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
transition(next) {
|
|
85
|
+
const allowed = TRANSITIONS[this.status] || [];
|
|
86
|
+
if (!allowed.includes(next)) {
|
|
87
|
+
throw new Error(`非法状态转移: ${this.status} → ${next}`);
|
|
88
|
+
}
|
|
89
|
+
this.status = next;
|
|
90
|
+
this.history.push(next);
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
assign(did) {
|
|
95
|
+
if (this.status === TaskStatus.OPEN) this.transition(TaskStatus.MATCHED);
|
|
96
|
+
if (this.status === TaskStatus.MATCHED) this.transition(TaskStatus.ASSIGNED);
|
|
97
|
+
this.assignee = did;
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
start() {
|
|
102
|
+
return this.transition(TaskStatus.RUNNING);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
complete() {
|
|
106
|
+
return this.transition(TaskStatus.COMPLETED);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
verify(passed) {
|
|
110
|
+
if (passed) return this.transition(TaskStatus.VERIFIED);
|
|
111
|
+
return this.transition(TaskStatus.DISPUTED);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
settle() {
|
|
115
|
+
if (this.status === TaskStatus.VERIFIED) return this.transition(TaskStatus.SETTLED);
|
|
116
|
+
throw new Error('只有验证通过的任务才能结算');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get isTerminal() {
|
|
120
|
+
return this.status === TaskStatus.SETTLED || this.status === TaskStatus.REJECTED;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { AgentCard, Task, TaskStatus, TRANSITIONS };
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@twinsearth/agent-universe",
|
|
3
|
+
"version": "2.3.4",
|
|
4
|
+
"description": "Agent Universe - 群众化的 AGI 路线,去中心化的智能体共享 & 开源网络。使命:让科技造福全人类!",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node test/test.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"agi",
|
|
12
|
+
"agent",
|
|
13
|
+
"p2p",
|
|
14
|
+
"decentralized",
|
|
15
|
+
"swarm",
|
|
16
|
+
"dht",
|
|
17
|
+
"mcp",
|
|
18
|
+
"a2a",
|
|
19
|
+
"agent-market"
|
|
20
|
+
],
|
|
21
|
+
"author": "TwinsEarth",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/TwinsEarth/agent-universe.git",
|
|
26
|
+
"directory": "js"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/TwinsEarth/agent-universe#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/TwinsEarth/agent-universe/issues"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"registry": "https://registry.npmjs.org/"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"index.js",
|
|
40
|
+
"index.d.ts",
|
|
41
|
+
"lib"
|
|
42
|
+
]
|
|
43
|
+
}
|