agentcourt 1.0.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/dist/index.d.ts +192 -0
- package/dist/index.js +323 -0
- package/package.json +26 -0
- package/src/index.ts +416 -0
- package/tsconfig.json +14 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
export type AgentCourtPassport = {
|
|
2
|
+
issuer: string;
|
|
3
|
+
trustLevel: "verified" | "unverified";
|
|
4
|
+
issuedAt: string;
|
|
5
|
+
};
|
|
6
|
+
export type AgentCourtAgent = {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
owner: string;
|
|
10
|
+
ownerAddress?: string;
|
|
11
|
+
strategy: string;
|
|
12
|
+
passport: AgentCourtPassport;
|
|
13
|
+
};
|
|
14
|
+
export type ToolDecision = "ALLOW" | "STOP_TOOL" | "HUMAN_IN_THE_LOOP";
|
|
15
|
+
export type ToolCallEvent = {
|
|
16
|
+
id: string;
|
|
17
|
+
agent: AgentCourtAgent;
|
|
18
|
+
tool: string;
|
|
19
|
+
args: Record<string, unknown>;
|
|
20
|
+
decision: ToolDecision;
|
|
21
|
+
reason: string;
|
|
22
|
+
evidenceHash: string;
|
|
23
|
+
latencyMs: number;
|
|
24
|
+
createdAt: string;
|
|
25
|
+
};
|
|
26
|
+
export type AgentCourtPolicy = {
|
|
27
|
+
maxTradeUsd: number;
|
|
28
|
+
maxDailyUsd: number;
|
|
29
|
+
allowedTools: string[];
|
|
30
|
+
humanApprovalTools: string[];
|
|
31
|
+
sensitivePatterns: Array<{
|
|
32
|
+
name: string;
|
|
33
|
+
pattern: string;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
export declare const AGENTCOURT_AGORA_POLICY: AgentCourtPolicy;
|
|
37
|
+
export declare class AgentCourtOrchestrator {
|
|
38
|
+
readonly policy: AgentCourtPolicy;
|
|
39
|
+
readonly rpcUrl: string;
|
|
40
|
+
readonly contractAddress: string;
|
|
41
|
+
private dailySpendUsd;
|
|
42
|
+
constructor(policy?: AgentCourtPolicy, rpcUrl?: string, contractAddress?: string);
|
|
43
|
+
/**
|
|
44
|
+
* Low-dependency JSON-RPC client over fetch
|
|
45
|
+
*/
|
|
46
|
+
ethCall(to: string, data: string): Promise<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Reads profile on-chain from the AgentCourt smart contract registry
|
|
49
|
+
*/
|
|
50
|
+
queryOnChainProfile(ownerAddress: string): Promise<{
|
|
51
|
+
status: "simulated";
|
|
52
|
+
reason: string;
|
|
53
|
+
id?: undefined;
|
|
54
|
+
owner?: undefined;
|
|
55
|
+
stake?: undefined;
|
|
56
|
+
reputation?: undefined;
|
|
57
|
+
totalViolations?: undefined;
|
|
58
|
+
totalSlashed?: undefined;
|
|
59
|
+
onChainStatus?: undefined;
|
|
60
|
+
rawStatusCode?: undefined;
|
|
61
|
+
error?: undefined;
|
|
62
|
+
} | {
|
|
63
|
+
status: "unregistered";
|
|
64
|
+
id: number;
|
|
65
|
+
reason?: undefined;
|
|
66
|
+
owner?: undefined;
|
|
67
|
+
stake?: undefined;
|
|
68
|
+
reputation?: undefined;
|
|
69
|
+
totalViolations?: undefined;
|
|
70
|
+
totalSlashed?: undefined;
|
|
71
|
+
onChainStatus?: undefined;
|
|
72
|
+
rawStatusCode?: undefined;
|
|
73
|
+
error?: undefined;
|
|
74
|
+
} | {
|
|
75
|
+
status: "verified";
|
|
76
|
+
id: number;
|
|
77
|
+
owner: string;
|
|
78
|
+
stake: number;
|
|
79
|
+
reputation: number;
|
|
80
|
+
totalViolations: number;
|
|
81
|
+
totalSlashed: number;
|
|
82
|
+
onChainStatus: string;
|
|
83
|
+
rawStatusCode: number;
|
|
84
|
+
reason?: undefined;
|
|
85
|
+
error?: undefined;
|
|
86
|
+
} | {
|
|
87
|
+
status: "sandbox";
|
|
88
|
+
error: any;
|
|
89
|
+
reason?: undefined;
|
|
90
|
+
id?: undefined;
|
|
91
|
+
owner?: undefined;
|
|
92
|
+
stake?: undefined;
|
|
93
|
+
reputation?: undefined;
|
|
94
|
+
totalViolations?: undefined;
|
|
95
|
+
totalSlashed?: undefined;
|
|
96
|
+
onChainStatus?: undefined;
|
|
97
|
+
rawStatusCode?: undefined;
|
|
98
|
+
}>;
|
|
99
|
+
callTool(agent: AgentCourtAgent, tool: string, args: Record<string, unknown>, execute?: () => Promise<unknown> | unknown): Promise<{
|
|
100
|
+
event: ToolCallEvent;
|
|
101
|
+
result: unknown;
|
|
102
|
+
}>;
|
|
103
|
+
evaluate(agent: AgentCourtAgent, tool: string, args: Record<string, unknown>, onChainSlashed?: boolean): ToolCallEvent;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The AgoraAgentClient is a zero-dependency client that interfaces with both the Arc Testnet RPC and the Megent Orchestration Gateway
|
|
107
|
+
*/
|
|
108
|
+
export declare class AgoraAgentClient {
|
|
109
|
+
readonly endpoint: string;
|
|
110
|
+
readonly agent: AgentCourtAgent | undefined;
|
|
111
|
+
readonly runId: string | undefined;
|
|
112
|
+
readonly rpcUrl: string;
|
|
113
|
+
readonly contractAddress: string;
|
|
114
|
+
constructor({ endpoint, agent, runId, rpcUrl, contractAddress }?: {
|
|
115
|
+
endpoint?: string;
|
|
116
|
+
agent?: AgentCourtAgent;
|
|
117
|
+
runId?: string;
|
|
118
|
+
rpcUrl?: string;
|
|
119
|
+
contractAddress?: string;
|
|
120
|
+
});
|
|
121
|
+
/**
|
|
122
|
+
* Low-dependency JSON-RPC client over fetch
|
|
123
|
+
*/
|
|
124
|
+
ethCall(to: string, data: string): Promise<string>;
|
|
125
|
+
/**
|
|
126
|
+
* Queries the AgentCourt contract to verify agent's profile on-chain.
|
|
127
|
+
*/
|
|
128
|
+
queryOnChainProfile(ownerAddress: string): Promise<{
|
|
129
|
+
status: "simulated";
|
|
130
|
+
reason: string;
|
|
131
|
+
id?: undefined;
|
|
132
|
+
owner?: undefined;
|
|
133
|
+
stake?: undefined;
|
|
134
|
+
reputation?: undefined;
|
|
135
|
+
totalViolations?: undefined;
|
|
136
|
+
totalSlashed?: undefined;
|
|
137
|
+
onChainStatus?: undefined;
|
|
138
|
+
rawStatusCode?: undefined;
|
|
139
|
+
error?: undefined;
|
|
140
|
+
} | {
|
|
141
|
+
status: "unregistered";
|
|
142
|
+
id: number;
|
|
143
|
+
reason?: undefined;
|
|
144
|
+
owner?: undefined;
|
|
145
|
+
stake?: undefined;
|
|
146
|
+
reputation?: undefined;
|
|
147
|
+
totalViolations?: undefined;
|
|
148
|
+
totalSlashed?: undefined;
|
|
149
|
+
onChainStatus?: undefined;
|
|
150
|
+
rawStatusCode?: undefined;
|
|
151
|
+
error?: undefined;
|
|
152
|
+
} | {
|
|
153
|
+
status: "verified";
|
|
154
|
+
id: number;
|
|
155
|
+
owner: string;
|
|
156
|
+
stake: number;
|
|
157
|
+
reputation: number;
|
|
158
|
+
totalViolations: number;
|
|
159
|
+
totalSlashed: number;
|
|
160
|
+
onChainStatus: string;
|
|
161
|
+
rawStatusCode: number;
|
|
162
|
+
reason?: undefined;
|
|
163
|
+
error?: undefined;
|
|
164
|
+
} | {
|
|
165
|
+
status: "sandbox";
|
|
166
|
+
error: any;
|
|
167
|
+
reason?: undefined;
|
|
168
|
+
id?: undefined;
|
|
169
|
+
owner?: undefined;
|
|
170
|
+
stake?: undefined;
|
|
171
|
+
reputation?: undefined;
|
|
172
|
+
totalViolations?: undefined;
|
|
173
|
+
totalSlashed?: undefined;
|
|
174
|
+
onChainStatus?: undefined;
|
|
175
|
+
rawStatusCode?: undefined;
|
|
176
|
+
}>;
|
|
177
|
+
callTool(tool: string, args: Record<string, unknown>, execute?: (args: Record<string, unknown>) => Promise<unknown> | unknown): Promise<{
|
|
178
|
+
ok: boolean;
|
|
179
|
+
needsApproval: boolean;
|
|
180
|
+
event: any;
|
|
181
|
+
result: null;
|
|
182
|
+
} | {
|
|
183
|
+
ok: boolean;
|
|
184
|
+
event: any;
|
|
185
|
+
result: unknown;
|
|
186
|
+
needsApproval?: undefined;
|
|
187
|
+
}>;
|
|
188
|
+
}
|
|
189
|
+
export declare function createVerifiedAgent(input: Omit<AgentCourtAgent, "passport"> & {
|
|
190
|
+
ownerAddress?: string;
|
|
191
|
+
}): AgentCourtAgent;
|
|
192
|
+
export declare function hashEvidence(value: unknown): string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
export const AGENTCOURT_AGORA_POLICY = {
|
|
3
|
+
maxTradeUsd: 2500,
|
|
4
|
+
maxDailyUsd: 10000,
|
|
5
|
+
allowedTools: [
|
|
6
|
+
"market.read",
|
|
7
|
+
"risk.score",
|
|
8
|
+
"arc.quote",
|
|
9
|
+
"arc.transfer_usdc",
|
|
10
|
+
"arc.publish_trace",
|
|
11
|
+
],
|
|
12
|
+
humanApprovalTools: ["arc.transfer_usdc"],
|
|
13
|
+
sensitivePatterns: [
|
|
14
|
+
{ name: "private key", pattern: "private[_ -]?key|seed phrase|mnemonic" },
|
|
15
|
+
{ name: "phone", pattern: "\\+?\\d[\\d\\s().-]{7,}\\d" },
|
|
16
|
+
{ name: "email", pattern: "[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}" },
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
export class AgentCourtOrchestrator {
|
|
20
|
+
policy;
|
|
21
|
+
rpcUrl;
|
|
22
|
+
contractAddress;
|
|
23
|
+
dailySpendUsd = 0;
|
|
24
|
+
constructor(policy = AGENTCOURT_AGORA_POLICY, rpcUrl = "https://rpc.testnet.arc.network", contractAddress = "0x1a6389aa779BD3C01B7867bB76a9B51f283f9B3a") {
|
|
25
|
+
this.policy = policy;
|
|
26
|
+
this.rpcUrl = rpcUrl;
|
|
27
|
+
this.contractAddress = contractAddress;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Low-dependency JSON-RPC client over fetch
|
|
31
|
+
*/
|
|
32
|
+
async ethCall(to, data) {
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(this.rpcUrl, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "Content-Type": "application/json" },
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
jsonrpc: "2.0",
|
|
39
|
+
id: Date.now(),
|
|
40
|
+
method: "eth_call",
|
|
41
|
+
params: [
|
|
42
|
+
{ to, data },
|
|
43
|
+
"latest"
|
|
44
|
+
]
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
const result = await response.json();
|
|
48
|
+
if (result.error) {
|
|
49
|
+
throw new Error(result.error.message);
|
|
50
|
+
}
|
|
51
|
+
return result.result;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new Error(`RPC Connection failed: ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Reads profile on-chain from the AgentCourt smart contract registry
|
|
59
|
+
*/
|
|
60
|
+
async queryOnChainProfile(ownerAddress) {
|
|
61
|
+
if (!ownerAddress || !ownerAddress.startsWith("0x")) {
|
|
62
|
+
return { status: "simulated", reason: "Invalid owner address" };
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
// 1. Get Agent ID of Owner
|
|
66
|
+
const addressClean = ownerAddress.slice(2).toLowerCase();
|
|
67
|
+
const agentIdData = `0xb3631cc9` + addressClean.padStart(64, "0");
|
|
68
|
+
const agentIdHex = await this.ethCall(this.contractAddress, agentIdData);
|
|
69
|
+
if (!agentIdHex || agentIdHex === "0x" || /^0x0*$/.test(agentIdHex)) {
|
|
70
|
+
return { status: "unregistered", id: 0 };
|
|
71
|
+
}
|
|
72
|
+
const agentId = parseInt(agentIdHex, 16);
|
|
73
|
+
// 2. Get Agent Profile from ID
|
|
74
|
+
const profileData = `0x16b06387` + agentId.toString(16).padStart(64, "0");
|
|
75
|
+
const profileHex = await this.ethCall(this.contractAddress, profileData);
|
|
76
|
+
if (!profileHex || profileHex === "0x") {
|
|
77
|
+
return { status: "unregistered", id: agentId };
|
|
78
|
+
}
|
|
79
|
+
const dataHex = profileHex.startsWith("0x") ? profileHex.slice(2) : profileHex;
|
|
80
|
+
const ownerHex = "0x" + dataHex.substring(24, 64);
|
|
81
|
+
const stake = BigInt("0x" + dataHex.substring(128, 192));
|
|
82
|
+
const reputation = parseInt(dataHex.substring(192, 256), 16);
|
|
83
|
+
const totalViolations = parseInt(dataHex.substring(256, 320), 16);
|
|
84
|
+
const totalSlashed = BigInt("0x" + dataHex.substring(320, 384));
|
|
85
|
+
const statusCode = parseInt(dataHex.substring(384, 448), 16);
|
|
86
|
+
const statuses = ["None", "Active", "Slashed"];
|
|
87
|
+
const status = statuses[statusCode] || "Unknown";
|
|
88
|
+
return {
|
|
89
|
+
status: "verified",
|
|
90
|
+
id: agentId,
|
|
91
|
+
owner: ownerHex,
|
|
92
|
+
stake: Number(stake) / 1e6, // USDC decimals
|
|
93
|
+
reputation,
|
|
94
|
+
totalViolations,
|
|
95
|
+
totalSlashed: Number(totalSlashed) / 1e6,
|
|
96
|
+
onChainStatus: status,
|
|
97
|
+
rawStatusCode: statusCode
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return {
|
|
102
|
+
status: "sandbox",
|
|
103
|
+
error: error.message
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async callTool(agent, tool, args, execute) {
|
|
108
|
+
// Check status on-chain
|
|
109
|
+
let onChainSlashed = false;
|
|
110
|
+
const owner = agent.owner || agent.ownerAddress;
|
|
111
|
+
if (owner) {
|
|
112
|
+
const profile = await this.queryOnChainProfile(owner);
|
|
113
|
+
if (profile.status === "verified" && profile.onChainStatus === "Slashed") {
|
|
114
|
+
onChainSlashed = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const event = this.evaluate(agent, tool, args, onChainSlashed);
|
|
118
|
+
if (event.decision !== "ALLOW") {
|
|
119
|
+
return { event, result: null };
|
|
120
|
+
}
|
|
121
|
+
const amount = amountFromArgs(args);
|
|
122
|
+
this.dailySpendUsd += amount;
|
|
123
|
+
const result = execute ? await execute() : { accepted: true };
|
|
124
|
+
return { event, result };
|
|
125
|
+
}
|
|
126
|
+
evaluate(agent, tool, args, onChainSlashed = false) {
|
|
127
|
+
const started = performance.now();
|
|
128
|
+
const amount = amountFromArgs(args);
|
|
129
|
+
const sensitiveFindings = findSensitiveValues(this.policy, args);
|
|
130
|
+
let decision = "ALLOW";
|
|
131
|
+
let reason = "Policy checks passed.";
|
|
132
|
+
if (onChainSlashed) {
|
|
133
|
+
decision = "STOP_TOOL";
|
|
134
|
+
reason = "Agent status on-chain is Slashed. Tool calls permanently frozen.";
|
|
135
|
+
}
|
|
136
|
+
else if (!this.policy.allowedTools.includes(tool)) {
|
|
137
|
+
decision = "STOP_TOOL";
|
|
138
|
+
reason = "Tool is not allowlisted for this agent.";
|
|
139
|
+
}
|
|
140
|
+
else if (agent.passport.trustLevel !== "verified") {
|
|
141
|
+
decision = "STOP_TOOL";
|
|
142
|
+
reason = "Agent passport is missing or unverified.";
|
|
143
|
+
}
|
|
144
|
+
else if (sensitiveFindings.length > 0) {
|
|
145
|
+
decision = "STOP_TOOL";
|
|
146
|
+
reason = `Sensitive data detected: ${sensitiveFindings.join(", ")}.`;
|
|
147
|
+
}
|
|
148
|
+
else if (amount > this.policy.maxTradeUsd) {
|
|
149
|
+
decision = "HUMAN_IN_THE_LOOP";
|
|
150
|
+
reason = `Trade exceeds per-action limit of ${this.policy.maxTradeUsd} USDC.`;
|
|
151
|
+
}
|
|
152
|
+
else if (this.dailySpendUsd + amount > this.policy.maxDailyUsd) {
|
|
153
|
+
decision = "HUMAN_IN_THE_LOOP";
|
|
154
|
+
reason = `Run would exceed daily budget of ${this.policy.maxDailyUsd} USDC.`;
|
|
155
|
+
}
|
|
156
|
+
else if (this.policy.humanApprovalTools.includes(tool) && amount > 1000) {
|
|
157
|
+
decision = "HUMAN_IN_THE_LOOP";
|
|
158
|
+
reason = "High-value Arc settlement requires operator approval.";
|
|
159
|
+
}
|
|
160
|
+
const evidenceHash = hashEvidence({ agentId: agent.id, tool, args, decision, reason });
|
|
161
|
+
return {
|
|
162
|
+
id: `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`,
|
|
163
|
+
agent,
|
|
164
|
+
tool,
|
|
165
|
+
args,
|
|
166
|
+
decision,
|
|
167
|
+
reason,
|
|
168
|
+
evidenceHash,
|
|
169
|
+
latencyMs: Number((performance.now() - started).toFixed(2)),
|
|
170
|
+
createdAt: new Date().toISOString(),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The AgoraAgentClient is a zero-dependency client that interfaces with both the Arc Testnet RPC and the Megent Orchestration Gateway
|
|
176
|
+
*/
|
|
177
|
+
export class AgoraAgentClient {
|
|
178
|
+
endpoint;
|
|
179
|
+
agent;
|
|
180
|
+
runId;
|
|
181
|
+
rpcUrl;
|
|
182
|
+
contractAddress;
|
|
183
|
+
constructor({ endpoint = "http://localhost:4141", agent, runId, rpcUrl = "https://rpc.testnet.arc.network", contractAddress = "0x1a6389aa779BD3C01B7867bB76a9B51f283f9B3a" } = {}) {
|
|
184
|
+
this.endpoint = endpoint.replace(/\/$/, "");
|
|
185
|
+
this.agent = agent;
|
|
186
|
+
this.runId = runId;
|
|
187
|
+
this.rpcUrl = rpcUrl;
|
|
188
|
+
this.contractAddress = contractAddress;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Low-dependency JSON-RPC client over fetch
|
|
192
|
+
*/
|
|
193
|
+
async ethCall(to, data) {
|
|
194
|
+
try {
|
|
195
|
+
const response = await fetch(this.rpcUrl, {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: { "Content-Type": "application/json" },
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
jsonrpc: "2.0",
|
|
200
|
+
id: Date.now(),
|
|
201
|
+
method: "eth_call",
|
|
202
|
+
params: [
|
|
203
|
+
{ to, data },
|
|
204
|
+
"latest"
|
|
205
|
+
]
|
|
206
|
+
})
|
|
207
|
+
});
|
|
208
|
+
const result = await response.json();
|
|
209
|
+
if (result.error) {
|
|
210
|
+
throw new Error(result.error.message);
|
|
211
|
+
}
|
|
212
|
+
return result.result;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
throw new Error(`RPC Connection failed: ${error.message}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Queries the AgentCourt contract to verify agent's profile on-chain.
|
|
220
|
+
*/
|
|
221
|
+
async queryOnChainProfile(ownerAddress) {
|
|
222
|
+
if (!ownerAddress || !ownerAddress.startsWith("0x")) {
|
|
223
|
+
return { status: "simulated", reason: "Invalid owner address" };
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const addressClean = ownerAddress.slice(2).toLowerCase();
|
|
227
|
+
const agentIdData = `0xb3631cc9` + addressClean.padStart(64, "0");
|
|
228
|
+
const agentIdHex = await this.ethCall(this.contractAddress, agentIdData);
|
|
229
|
+
if (!agentIdHex || agentIdHex === "0x" || /^0x0*$/.test(agentIdHex)) {
|
|
230
|
+
return { status: "unregistered", id: 0 };
|
|
231
|
+
}
|
|
232
|
+
const agentId = parseInt(agentIdHex, 16);
|
|
233
|
+
const profileData = `0x16b06387` + agentId.toString(16).padStart(64, "0");
|
|
234
|
+
const profileHex = await this.ethCall(this.contractAddress, profileData);
|
|
235
|
+
if (!profileHex || profileHex === "0x") {
|
|
236
|
+
return { status: "unregistered", id: agentId };
|
|
237
|
+
}
|
|
238
|
+
const dataHex = profileHex.startsWith("0x") ? profileHex.slice(2) : profileHex;
|
|
239
|
+
const ownerHex = "0x" + dataHex.substring(24, 64);
|
|
240
|
+
const stake = BigInt("0x" + dataHex.substring(128, 192));
|
|
241
|
+
const reputation = parseInt(dataHex.substring(192, 256), 16);
|
|
242
|
+
const totalViolations = parseInt(dataHex.substring(256, 320), 16);
|
|
243
|
+
const totalSlashed = BigInt("0x" + dataHex.substring(320, 384));
|
|
244
|
+
const statusCode = parseInt(dataHex.substring(384, 448), 16);
|
|
245
|
+
const statuses = ["None", "Active", "Slashed"];
|
|
246
|
+
const status = statuses[statusCode] || "Unknown";
|
|
247
|
+
return {
|
|
248
|
+
status: "verified",
|
|
249
|
+
id: agentId,
|
|
250
|
+
owner: ownerHex,
|
|
251
|
+
stake: Number(stake) / 1e6, // USDC
|
|
252
|
+
reputation,
|
|
253
|
+
totalViolations,
|
|
254
|
+
totalSlashed: Number(totalSlashed) / 1e6,
|
|
255
|
+
onChainStatus: status,
|
|
256
|
+
rawStatusCode: statusCode
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
return {
|
|
261
|
+
status: "sandbox",
|
|
262
|
+
error: error.message
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async callTool(tool, args, execute) {
|
|
267
|
+
let onChainProfile = null;
|
|
268
|
+
const owner = this.agent?.owner || this.agent?.ownerAddress;
|
|
269
|
+
if (this.agent && owner) {
|
|
270
|
+
onChainProfile = await this.queryOnChainProfile(owner);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
onChainProfile = { status: "sandbox", reason: "No agent owner address provided" };
|
|
274
|
+
}
|
|
275
|
+
const response = await fetch(`${this.endpoint}/api/tool-call`, {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: { "Content-Type": "application/json" },
|
|
278
|
+
body: JSON.stringify({
|
|
279
|
+
runId: this.runId,
|
|
280
|
+
agent: {
|
|
281
|
+
...this.agent,
|
|
282
|
+
onChainProfile
|
|
283
|
+
},
|
|
284
|
+
tool,
|
|
285
|
+
args
|
|
286
|
+
})
|
|
287
|
+
});
|
|
288
|
+
const event = await response.json();
|
|
289
|
+
if (event.verdict?.action === "STOP_TOOL") {
|
|
290
|
+
return { ok: false, event, result: null };
|
|
291
|
+
}
|
|
292
|
+
if (event.verdict?.action === "HUMAN_IN_THE_LOOP") {
|
|
293
|
+
return { ok: false, needsApproval: true, event, result: null };
|
|
294
|
+
}
|
|
295
|
+
const result = execute ? await execute(args) : { accepted: true };
|
|
296
|
+
return { ok: true, event, result };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
export function createVerifiedAgent(input) {
|
|
300
|
+
return {
|
|
301
|
+
...input,
|
|
302
|
+
owner: input.owner || input.ownerAddress || `0x${Math.random().toString(16).slice(2).padEnd(40, "0")}`,
|
|
303
|
+
passport: {
|
|
304
|
+
issuer: "agentcourt-arc",
|
|
305
|
+
trustLevel: "verified",
|
|
306
|
+
issuedAt: new Date().toISOString(),
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
export function hashEvidence(value) {
|
|
311
|
+
return `0x${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
312
|
+
}
|
|
313
|
+
function amountFromArgs(args) {
|
|
314
|
+
const value = args.amountUsd ?? args.notionalUsd ?? 0;
|
|
315
|
+
return typeof value === "number" ? value : Number(value) || 0;
|
|
316
|
+
}
|
|
317
|
+
function findSensitiveValues(policy, value) {
|
|
318
|
+
const raw = JSON.stringify(value ?? {});
|
|
319
|
+
return policy.sensitivePatterns
|
|
320
|
+
.map((item) => ({ ...item, regex: new RegExp(item.pattern, "i") }))
|
|
321
|
+
.filter((item) => item.regex.test(raw))
|
|
322
|
+
.map((item) => item.name);
|
|
323
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentcourt",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready zero-dependency Web3 security gateway and orchestration SDK for AI agents on Arc Network",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"agents",
|
|
14
|
+
"arc",
|
|
15
|
+
"web3",
|
|
16
|
+
"agentcourt",
|
|
17
|
+
"security",
|
|
18
|
+
"governance"
|
|
19
|
+
],
|
|
20
|
+
"author": "Megents Team",
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^5.0.0",
|
|
24
|
+
"@types/node": "^20.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
|
|
3
|
+
export type AgentCourtPassport = {
|
|
4
|
+
issuer: string;
|
|
5
|
+
trustLevel: "verified" | "unverified";
|
|
6
|
+
issuedAt: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type AgentCourtAgent = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
owner: string;
|
|
13
|
+
ownerAddress?: string;
|
|
14
|
+
strategy: string;
|
|
15
|
+
passport: AgentCourtPassport;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ToolDecision = "ALLOW" | "STOP_TOOL" | "HUMAN_IN_THE_LOOP";
|
|
19
|
+
|
|
20
|
+
export type ToolCallEvent = {
|
|
21
|
+
id: string;
|
|
22
|
+
agent: AgentCourtAgent;
|
|
23
|
+
tool: string;
|
|
24
|
+
args: Record<string, unknown>;
|
|
25
|
+
decision: ToolDecision;
|
|
26
|
+
reason: string;
|
|
27
|
+
evidenceHash: string;
|
|
28
|
+
latencyMs: number;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type AgentCourtPolicy = {
|
|
33
|
+
maxTradeUsd: number;
|
|
34
|
+
maxDailyUsd: number;
|
|
35
|
+
allowedTools: string[];
|
|
36
|
+
humanApprovalTools: string[];
|
|
37
|
+
sensitivePatterns: Array<{ name: string; pattern: string }>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const AGENTCOURT_AGORA_POLICY: AgentCourtPolicy = {
|
|
41
|
+
maxTradeUsd: 2500,
|
|
42
|
+
maxDailyUsd: 10000,
|
|
43
|
+
allowedTools: [
|
|
44
|
+
"market.read",
|
|
45
|
+
"risk.score",
|
|
46
|
+
"arc.quote",
|
|
47
|
+
"arc.transfer_usdc",
|
|
48
|
+
"arc.publish_trace",
|
|
49
|
+
],
|
|
50
|
+
humanApprovalTools: ["arc.transfer_usdc"],
|
|
51
|
+
sensitivePatterns: [
|
|
52
|
+
{ name: "private key", pattern: "private[_ -]?key|seed phrase|mnemonic" },
|
|
53
|
+
{ name: "phone", pattern: "\\+?\\d[\\d\\s().-]{7,}\\d" },
|
|
54
|
+
{ name: "email", pattern: "[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}" },
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export class AgentCourtOrchestrator {
|
|
59
|
+
private dailySpendUsd = 0;
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
public readonly policy: AgentCourtPolicy = AGENTCOURT_AGORA_POLICY,
|
|
63
|
+
public readonly rpcUrl: string = "https://rpc.testnet.arc.network",
|
|
64
|
+
public readonly contractAddress: string = "0x1a6389aa779BD3C01B7867bB76a9B51f283f9B3a"
|
|
65
|
+
) {}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Low-dependency JSON-RPC client over fetch
|
|
69
|
+
*/
|
|
70
|
+
async ethCall(to: string, data: string): Promise<string> {
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(this.rpcUrl, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: { "Content-Type": "application/json" },
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
jsonrpc: "2.0",
|
|
77
|
+
id: Date.now(),
|
|
78
|
+
method: "eth_call",
|
|
79
|
+
params: [
|
|
80
|
+
{ to, data },
|
|
81
|
+
"latest"
|
|
82
|
+
]
|
|
83
|
+
})
|
|
84
|
+
});
|
|
85
|
+
const result = await response.json();
|
|
86
|
+
if (result.error) {
|
|
87
|
+
throw new Error(result.error.message);
|
|
88
|
+
}
|
|
89
|
+
return result.result;
|
|
90
|
+
} catch (error: any) {
|
|
91
|
+
throw new Error(`RPC Connection failed: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Reads profile on-chain from the AgentCourt smart contract registry
|
|
97
|
+
*/
|
|
98
|
+
async queryOnChainProfile(ownerAddress: string) {
|
|
99
|
+
if (!ownerAddress || !ownerAddress.startsWith("0x")) {
|
|
100
|
+
return { status: "simulated" as const, reason: "Invalid owner address" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// 1. Get Agent ID of Owner
|
|
105
|
+
const addressClean = ownerAddress.slice(2).toLowerCase();
|
|
106
|
+
const agentIdData = `0xb3631cc9` + addressClean.padStart(64, "0");
|
|
107
|
+
const agentIdHex = await this.ethCall(this.contractAddress, agentIdData);
|
|
108
|
+
|
|
109
|
+
if (!agentIdHex || agentIdHex === "0x" || /^0x0*$/.test(agentIdHex)) {
|
|
110
|
+
return { status: "unregistered" as const, id: 0 };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const agentId = parseInt(agentIdHex, 16);
|
|
114
|
+
|
|
115
|
+
// 2. Get Agent Profile from ID
|
|
116
|
+
const profileData = `0x16b06387` + agentId.toString(16).padStart(64, "0");
|
|
117
|
+
const profileHex = await this.ethCall(this.contractAddress, profileData);
|
|
118
|
+
|
|
119
|
+
if (!profileHex || profileHex === "0x") {
|
|
120
|
+
return { status: "unregistered" as const, id: agentId };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const dataHex = profileHex.startsWith("0x") ? profileHex.slice(2) : profileHex;
|
|
124
|
+
const ownerHex = "0x" + dataHex.substring(24, 64);
|
|
125
|
+
const stake = BigInt("0x" + dataHex.substring(128, 192));
|
|
126
|
+
const reputation = parseInt(dataHex.substring(192, 256), 16);
|
|
127
|
+
const totalViolations = parseInt(dataHex.substring(256, 320), 16);
|
|
128
|
+
const totalSlashed = BigInt("0x" + dataHex.substring(320, 384));
|
|
129
|
+
const statusCode = parseInt(dataHex.substring(384, 448), 16);
|
|
130
|
+
|
|
131
|
+
const statuses = ["None", "Active", "Slashed"];
|
|
132
|
+
const status = statuses[statusCode] || "Unknown";
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
status: "verified" as const,
|
|
136
|
+
id: agentId,
|
|
137
|
+
owner: ownerHex,
|
|
138
|
+
stake: Number(stake) / 1e6, // USDC decimals
|
|
139
|
+
reputation,
|
|
140
|
+
totalViolations,
|
|
141
|
+
totalSlashed: Number(totalSlashed) / 1e6,
|
|
142
|
+
onChainStatus: status,
|
|
143
|
+
rawStatusCode: statusCode
|
|
144
|
+
};
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
return {
|
|
147
|
+
status: "sandbox" as const,
|
|
148
|
+
error: error.message
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async callTool(
|
|
154
|
+
agent: AgentCourtAgent,
|
|
155
|
+
tool: string,
|
|
156
|
+
args: Record<string, unknown>,
|
|
157
|
+
execute?: () => Promise<unknown> | unknown
|
|
158
|
+
) {
|
|
159
|
+
// Check status on-chain
|
|
160
|
+
let onChainSlashed = false;
|
|
161
|
+
const owner = agent.owner || agent.ownerAddress;
|
|
162
|
+
if (owner) {
|
|
163
|
+
const profile = await this.queryOnChainProfile(owner);
|
|
164
|
+
if (profile.status === "verified" && profile.onChainStatus === "Slashed") {
|
|
165
|
+
onChainSlashed = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const event = this.evaluate(agent, tool, args, onChainSlashed);
|
|
170
|
+
|
|
171
|
+
if (event.decision !== "ALLOW") {
|
|
172
|
+
return { event, result: null };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const amount = amountFromArgs(args);
|
|
176
|
+
this.dailySpendUsd += amount;
|
|
177
|
+
const result = execute ? await execute() : { accepted: true };
|
|
178
|
+
return { event, result };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
evaluate(
|
|
182
|
+
agent: AgentCourtAgent,
|
|
183
|
+
tool: string,
|
|
184
|
+
args: Record<string, unknown>,
|
|
185
|
+
onChainSlashed: boolean = false
|
|
186
|
+
): ToolCallEvent {
|
|
187
|
+
const started = performance.now();
|
|
188
|
+
const amount = amountFromArgs(args);
|
|
189
|
+
const sensitiveFindings = findSensitiveValues(this.policy, args);
|
|
190
|
+
let decision: ToolDecision = "ALLOW";
|
|
191
|
+
let reason = "Policy checks passed.";
|
|
192
|
+
|
|
193
|
+
if (onChainSlashed) {
|
|
194
|
+
decision = "STOP_TOOL";
|
|
195
|
+
reason = "Agent status on-chain is Slashed. Tool calls permanently frozen.";
|
|
196
|
+
} else if (!this.policy.allowedTools.includes(tool)) {
|
|
197
|
+
decision = "STOP_TOOL";
|
|
198
|
+
reason = "Tool is not allowlisted for this agent.";
|
|
199
|
+
} else if (agent.passport.trustLevel !== "verified") {
|
|
200
|
+
decision = "STOP_TOOL";
|
|
201
|
+
reason = "Agent passport is missing or unverified.";
|
|
202
|
+
} else if (sensitiveFindings.length > 0) {
|
|
203
|
+
decision = "STOP_TOOL";
|
|
204
|
+
reason = `Sensitive data detected: ${sensitiveFindings.join(", ")}.`;
|
|
205
|
+
} else if (amount > this.policy.maxTradeUsd) {
|
|
206
|
+
decision = "HUMAN_IN_THE_LOOP";
|
|
207
|
+
reason = `Trade exceeds per-action limit of ${this.policy.maxTradeUsd} USDC.`;
|
|
208
|
+
} else if (this.dailySpendUsd + amount > this.policy.maxDailyUsd) {
|
|
209
|
+
decision = "HUMAN_IN_THE_LOOP";
|
|
210
|
+
reason = `Run would exceed daily budget of ${this.policy.maxDailyUsd} USDC.`;
|
|
211
|
+
} else if (this.policy.humanApprovalTools.includes(tool) && amount > 1000) {
|
|
212
|
+
decision = "HUMAN_IN_THE_LOOP";
|
|
213
|
+
reason = "High-value Arc settlement requires operator approval.";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const evidenceHash = hashEvidence({ agentId: agent.id, tool, args, decision, reason });
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
id: `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`,
|
|
220
|
+
agent,
|
|
221
|
+
tool,
|
|
222
|
+
args,
|
|
223
|
+
decision,
|
|
224
|
+
reason,
|
|
225
|
+
evidenceHash,
|
|
226
|
+
latencyMs: Number((performance.now() - started).toFixed(2)),
|
|
227
|
+
createdAt: new Date().toISOString(),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The AgoraAgentClient is a zero-dependency client that interfaces with both the Arc Testnet RPC and the Megent Orchestration Gateway
|
|
234
|
+
*/
|
|
235
|
+
export class AgoraAgentClient {
|
|
236
|
+
public readonly endpoint: string;
|
|
237
|
+
public readonly agent: AgentCourtAgent | undefined;
|
|
238
|
+
public readonly runId: string | undefined;
|
|
239
|
+
public readonly rpcUrl: string;
|
|
240
|
+
public readonly contractAddress: string;
|
|
241
|
+
|
|
242
|
+
constructor({
|
|
243
|
+
endpoint = "http://localhost:4141",
|
|
244
|
+
agent,
|
|
245
|
+
runId,
|
|
246
|
+
rpcUrl = "https://rpc.testnet.arc.network",
|
|
247
|
+
contractAddress = "0x1a6389aa779BD3C01B7867bB76a9B51f283f9B3a"
|
|
248
|
+
}: {
|
|
249
|
+
endpoint?: string;
|
|
250
|
+
agent?: AgentCourtAgent;
|
|
251
|
+
runId?: string;
|
|
252
|
+
rpcUrl?: string;
|
|
253
|
+
contractAddress?: string;
|
|
254
|
+
} = {}) {
|
|
255
|
+
this.endpoint = endpoint.replace(/\/$/, "");
|
|
256
|
+
this.agent = agent;
|
|
257
|
+
this.runId = runId;
|
|
258
|
+
this.rpcUrl = rpcUrl;
|
|
259
|
+
this.contractAddress = contractAddress;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Low-dependency JSON-RPC client over fetch
|
|
264
|
+
*/
|
|
265
|
+
async ethCall(to: string, data: string): Promise<string> {
|
|
266
|
+
try {
|
|
267
|
+
const response = await fetch(this.rpcUrl, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: { "Content-Type": "application/json" },
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
jsonrpc: "2.0",
|
|
272
|
+
id: Date.now(),
|
|
273
|
+
method: "eth_call",
|
|
274
|
+
params: [
|
|
275
|
+
{ to, data },
|
|
276
|
+
"latest"
|
|
277
|
+
]
|
|
278
|
+
})
|
|
279
|
+
});
|
|
280
|
+
const result = await response.json();
|
|
281
|
+
if (result.error) {
|
|
282
|
+
throw new Error(result.error.message);
|
|
283
|
+
}
|
|
284
|
+
return result.result;
|
|
285
|
+
} catch (error: any) {
|
|
286
|
+
throw new Error(`RPC Connection failed: ${error.message}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Queries the AgentCourt contract to verify agent's profile on-chain.
|
|
292
|
+
*/
|
|
293
|
+
async queryOnChainProfile(ownerAddress: string) {
|
|
294
|
+
if (!ownerAddress || !ownerAddress.startsWith("0x")) {
|
|
295
|
+
return { status: "simulated" as const, reason: "Invalid owner address" };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const addressClean = ownerAddress.slice(2).toLowerCase();
|
|
300
|
+
const agentIdData = `0xb3631cc9` + addressClean.padStart(64, "0");
|
|
301
|
+
const agentIdHex = await this.ethCall(this.contractAddress, agentIdData);
|
|
302
|
+
|
|
303
|
+
if (!agentIdHex || agentIdHex === "0x" || /^0x0*$/.test(agentIdHex)) {
|
|
304
|
+
return { status: "unregistered" as const, id: 0 };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const agentId = parseInt(agentIdHex, 16);
|
|
308
|
+
|
|
309
|
+
const profileData = `0x16b06387` + agentId.toString(16).padStart(64, "0");
|
|
310
|
+
const profileHex = await this.ethCall(this.contractAddress, profileData);
|
|
311
|
+
|
|
312
|
+
if (!profileHex || profileHex === "0x") {
|
|
313
|
+
return { status: "unregistered" as const, id: agentId };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const dataHex = profileHex.startsWith("0x") ? profileHex.slice(2) : profileHex;
|
|
317
|
+
|
|
318
|
+
const ownerHex = "0x" + dataHex.substring(24, 64);
|
|
319
|
+
const stake = BigInt("0x" + dataHex.substring(128, 192));
|
|
320
|
+
const reputation = parseInt(dataHex.substring(192, 256), 16);
|
|
321
|
+
const totalViolations = parseInt(dataHex.substring(256, 320), 16);
|
|
322
|
+
const totalSlashed = BigInt("0x" + dataHex.substring(320, 384));
|
|
323
|
+
const statusCode = parseInt(dataHex.substring(384, 448), 16);
|
|
324
|
+
|
|
325
|
+
const statuses = ["None", "Active", "Slashed"];
|
|
326
|
+
const status = statuses[statusCode] || "Unknown";
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
status: "verified" as const,
|
|
330
|
+
id: agentId,
|
|
331
|
+
owner: ownerHex,
|
|
332
|
+
stake: Number(stake) / 1e6, // USDC
|
|
333
|
+
reputation,
|
|
334
|
+
totalViolations,
|
|
335
|
+
totalSlashed: Number(totalSlashed) / 1e6,
|
|
336
|
+
onChainStatus: status,
|
|
337
|
+
rawStatusCode: statusCode
|
|
338
|
+
};
|
|
339
|
+
} catch (error: any) {
|
|
340
|
+
return {
|
|
341
|
+
status: "sandbox" as const,
|
|
342
|
+
error: error.message
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async callTool(
|
|
348
|
+
tool: string,
|
|
349
|
+
args: Record<string, unknown>,
|
|
350
|
+
execute?: (args: Record<string, unknown>) => Promise<unknown> | unknown
|
|
351
|
+
) {
|
|
352
|
+
let onChainProfile = null;
|
|
353
|
+
const owner = this.agent?.owner || this.agent?.ownerAddress;
|
|
354
|
+
if (this.agent && owner) {
|
|
355
|
+
onChainProfile = await this.queryOnChainProfile(owner);
|
|
356
|
+
} else {
|
|
357
|
+
onChainProfile = { status: "sandbox" as const, reason: "No agent owner address provided" };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const response = await fetch(`${this.endpoint}/api/tool-call`, {
|
|
361
|
+
method: "POST",
|
|
362
|
+
headers: { "Content-Type": "application/json" },
|
|
363
|
+
body: JSON.stringify({
|
|
364
|
+
runId: this.runId,
|
|
365
|
+
agent: {
|
|
366
|
+
...this.agent,
|
|
367
|
+
onChainProfile
|
|
368
|
+
},
|
|
369
|
+
tool,
|
|
370
|
+
args
|
|
371
|
+
})
|
|
372
|
+
});
|
|
373
|
+
const event = await response.json();
|
|
374
|
+
|
|
375
|
+
if (event.verdict?.action === "STOP_TOOL") {
|
|
376
|
+
return { ok: false, event, result: null };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (event.verdict?.action === "HUMAN_IN_THE_LOOP") {
|
|
380
|
+
return { ok: false, needsApproval: true, event, result: null };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const result = execute ? await execute(args) : { accepted: true };
|
|
384
|
+
return { ok: true, event, result };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function createVerifiedAgent(input: Omit<AgentCourtAgent, "passport"> & { ownerAddress?: string }): AgentCourtAgent {
|
|
389
|
+
return {
|
|
390
|
+
...input,
|
|
391
|
+
owner: input.owner || input.ownerAddress || `0x${Math.random().toString(16).slice(2).padEnd(40, "0")}`,
|
|
392
|
+
passport: {
|
|
393
|
+
issuer: "agentcourt-arc",
|
|
394
|
+
trustLevel: "verified",
|
|
395
|
+
issuedAt: new Date().toISOString(),
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function hashEvidence(value: unknown) {
|
|
401
|
+
return `0x${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function amountFromArgs(args: Record<string, unknown>) {
|
|
405
|
+
const value = args.amountUsd ?? args.notionalUsd ?? 0;
|
|
406
|
+
return typeof value === "number" ? value : Number(value) || 0;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function findSensitiveValues(policy: AgentCourtPolicy, value: Record<string, unknown>) {
|
|
410
|
+
const raw = JSON.stringify(value ?? {});
|
|
411
|
+
|
|
412
|
+
return policy.sensitivePatterns
|
|
413
|
+
.map((item) => ({ ...item, regex: new RegExp(item.pattern, "i") }))
|
|
414
|
+
.filter((item) => item.regex.test(raw))
|
|
415
|
+
.map((item) => item.name);
|
|
416
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|