@ton-agent-kit/plugin-identity 1.0.1 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ton-agent-kit/plugin-identity",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Identity plugin for TON Agent Kit — agent registry, discovery, and reputation",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -0,0 +1,77 @@
1
+ import { z } from "zod";
2
+ import { Address, internal, toNano, beginCell } from "@ton/core";
3
+
4
+ import { defineAction, toFriendlyAddress, sendTransaction } from "@ton-agent-kit/core";
5
+ import { saveReputationConfig } from "../reputation-config";
6
+
7
+ export const deployReputationContractAction = defineAction({
8
+ name: "deploy_reputation_contract",
9
+ description:
10
+ "Deploy the on-chain Reputation smart contract (Tact) to TON. Once deployed, register_agent and get_agent_reputation will use it automatically for verifiable, immutable reputation scores.",
11
+ schema: z.object({
12
+ fee: z
13
+ .string()
14
+ .optional()
15
+ .describe("Fee per write operation in TON. Defaults to '0.01'."),
16
+ }),
17
+ handler: async (agent, params) => {
18
+ try {
19
+ // Dynamic import of compiled Tact contract
20
+ // This file is generated by: bun run contracts/deploy-reputation.ts
21
+ let Reputation: any;
22
+ let storeDeploy: any;
23
+ try {
24
+ const mod = await import("../contracts/Reputation_Reputation");
25
+ Reputation = mod.Reputation;
26
+ storeDeploy = mod.storeDeploy;
27
+ } catch {
28
+ return {
29
+ deployed: false,
30
+ message:
31
+ "Compiled contract not found. Run 'bun run contracts/deploy-reputation.ts' first to compile the Tact contract, then copy output to packages/plugin-identity/src/contracts/.",
32
+ };
33
+ }
34
+
35
+ const ownerAddress = agent.wallet.address;
36
+ const reputation = await Reputation.fromInit(ownerAddress);
37
+
38
+ const deployBody = beginCell()
39
+ .store(storeDeploy({ $$type: "Deploy", queryId: BigInt(0) }))
40
+ .endCell();
41
+
42
+ await sendTransaction(agent, [
43
+ internal({
44
+ to: reputation.address,
45
+ value: toNano("0.05"),
46
+ bounce: false,
47
+ init: reputation.init!,
48
+ body: deployBody,
49
+ }),
50
+ ]);
51
+
52
+ const contractAddr = reputation.address.toRawString();
53
+
54
+ // Save config so other actions auto-detect the contract
55
+ saveReputationConfig({
56
+ contractAddress: contractAddr,
57
+ network: agent.network,
58
+ deployedAt: new Date().toISOString(),
59
+ });
60
+
61
+ return {
62
+ deployed: true,
63
+ contractAddress: contractAddr,
64
+ friendlyContract: toFriendlyAddress(reputation.address, agent.network),
65
+ network: agent.network,
66
+ fee: params.fee || "0.01",
67
+ message: `Reputation contract deployed at ${toFriendlyAddress(reputation.address, agent.network)}. Agents can now register on-chain.`,
68
+ };
69
+ } catch (err: any) {
70
+ return {
71
+ deployed: false,
72
+ error: err.message,
73
+ message: `Failed to deploy reputation contract: ${err.message}`,
74
+ };
75
+ }
76
+ },
77
+ });
@@ -2,50 +2,98 @@ import { z } from "zod";
2
2
  import { Address } from "@ton/core";
3
3
  import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
4
4
  import { loadAgentRegistry } from "../utils";
5
+ import { resolveContractAddress } from "../reputation-config";
6
+ import { callContractGetter, lookupAgentIndex, parseAgentDataFromStack } from "../reputation-helpers";
5
7
 
6
- export const discoverAgentAction = defineAction({
7
- name: "discover_agent",
8
- description:
9
- "Find registered agents by capability or name. Search the local agent registry to find agents that can perform specific tasks.",
10
- schema: z.object({
11
- capability: z
12
- .string()
13
- .optional()
14
- .describe("Capability to search for (e.g., 'price_feed', 'trading')"),
15
- name: z.string().optional().describe("Agent name to search for"),
16
- }),
17
- handler: async (agent, params) => {
18
- const registry = loadAgentRegistry();
19
- let results = Object.values(registry);
20
-
21
- if (params.capability) {
22
- const cap = params.capability.toLowerCase();
23
- results = results.filter((a: any) =>
24
- a.capabilities.some((c: string) => c.toLowerCase().includes(cap)),
25
- );
26
- }
27
-
28
- if (params.name) {
29
- const name = params.name.toLowerCase();
30
- results = results.filter((a: any) =>
31
- a.name.toLowerCase().includes(name),
32
- );
33
- }
34
-
35
- return {
36
- query: { capability: params.capability, name: params.name },
37
- count: results.length,
38
- agents: results.map((a: any) => ({
39
- id: a.id,
40
- name: a.name,
41
- address: a.address,
42
- friendlyAddress: toFriendlyAddress(Address.parse(a.address), agent.network),
43
- capabilities: a.capabilities,
44
- description: a.description,
45
- endpoint: a.endpoint,
46
- reputation: a.reputation,
47
- registeredAt: a.registeredAt,
48
- })),
49
- };
50
- },
51
- });
8
+ export function createDiscoverAgentAction(contractAddress?: string) {
9
+ return defineAction({
10
+ name: "discover_agent",
11
+ description:
12
+ "Find registered agents by capability or name. Searches both on-chain (if contract deployed) and local registry. Set includeOffline=true to include unavailable agents.",
13
+ schema: z.object({
14
+ capability: z
15
+ .string()
16
+ .optional()
17
+ .describe("Capability to search for (e.g., 'price_feed', 'trading')"),
18
+ name: z.string().optional().describe("Agent name to search for"),
19
+ includeOffline: z
20
+ .boolean()
21
+ .optional()
22
+ .describe("Include offline/unavailable agents. Defaults to false."),
23
+ }),
24
+ handler: async (agent, params) => {
25
+ const addr = resolveContractAddress(contractAddress, agent.network);
26
+
27
+ // ── Hybrid mode: JSON registry for discovery, on-chain for reputation ──
28
+ const registry = loadAgentRegistry();
29
+ let results = Object.values(registry);
30
+
31
+ if (!params.includeOffline) {
32
+ results = results.filter((a: any) => a.available !== false);
33
+ }
34
+
35
+ if (params.capability) {
36
+ const cap = params.capability.toLowerCase();
37
+ results = results.filter((a: any) =>
38
+ a.capabilities.some((c: string) => c.toLowerCase().includes(cap)),
39
+ );
40
+ }
41
+
42
+ if (params.name) {
43
+ const name = params.name.toLowerCase();
44
+ results = results.filter((a: any) => a.name.toLowerCase().includes(name));
45
+ }
46
+
47
+ // Enrich with on-chain reputation if contract is available
48
+ if (addr) {
49
+ const apiBase =
50
+ agent.network === "testnet"
51
+ ? "https://testnet.tonapi.io/v2"
52
+ : "https://tonapi.io/v2";
53
+
54
+ for (const a of results as any[]) {
55
+ try {
56
+ const idx = await lookupAgentIndex(apiBase, addr, a.name, agent.config.TONAPI_KEY);
57
+ if (idx !== null) {
58
+ const dataRes = await callContractGetter(
59
+ apiBase, addr, "agentData", [idx.toString()], agent.config.TONAPI_KEY,
60
+ );
61
+ const parsed = parseAgentDataFromStack(dataRes?.stack);
62
+ if (parsed) {
63
+ const rep = parsed.totalTasks > 0
64
+ ? Math.round((parsed.successes / parsed.totalTasks) * 100)
65
+ : 0;
66
+ a.reputation = { score: rep, totalTasks: parsed.totalTasks, successfulTasks: parsed.successes };
67
+ a.onChain = true;
68
+ }
69
+ }
70
+ } catch {
71
+ // On-chain lookup failed for this agent — keep JSON data
72
+ }
73
+ }
74
+ }
75
+
76
+ return {
77
+ query: { capability: params.capability, name: params.name, includeOffline: params.includeOffline },
78
+ count: results.length,
79
+ agents: results.map((a: any) => ({
80
+ id: a.id,
81
+ name: a.name,
82
+ address: a.address,
83
+ friendlyAddress: a.address ? toFriendlyAddress(Address.parse(a.address), agent.network) : "",
84
+ capabilities: a.capabilities,
85
+ available: a.available !== false,
86
+ description: a.description,
87
+ endpoint: a.endpoint,
88
+ reputation: a.reputation,
89
+ registeredAt: a.registeredAt,
90
+ onChain: a.onChain || false,
91
+ })),
92
+ onChain: !!addr,
93
+ contractAddress: addr || undefined,
94
+ };
95
+ },
96
+ });
97
+ }
98
+
99
+ export const discoverAgentAction = createDiscoverAgentAction();
@@ -0,0 +1,84 @@
1
+ import { z } from "zod";
2
+ import { defineAction } from "@ton-agent-kit/core";
3
+ import { resolveContractAddress } from "../reputation-config";
4
+ import { callContractGetter } from "../reputation-helpers";
5
+
6
+ export const getAgentCleanupInfoAction = defineAction({
7
+ name: "get_agent_cleanup_info",
8
+ description:
9
+ "Check if an agent is eligible for cleanup. Shows score, ratings count, last active date, and which cleanup condition would trigger removal. " +
10
+ "Reasons: 1=bad_score (<20% with 100+ ratings), 2=inactive (30+ days), 3=ghost (0 ratings after 7 days).",
11
+ schema: z.object({
12
+ agentIndex: z.coerce.number().describe("Agent index to check"),
13
+ }),
14
+ handler: async (agent, params) => {
15
+ const addr = resolveContractAddress(undefined, agent.network);
16
+ if (!addr) {
17
+ return { message: "No reputation contract configured" };
18
+ }
19
+
20
+ const apiBase = agent.network === "testnet"
21
+ ? "https://testnet.tonapi.io/v2"
22
+ : "https://tonapi.io/v2";
23
+
24
+ const result = await callContractGetter(
25
+ apiBase, addr, "agentCleanupInfo",
26
+ [params.agentIndex.toString()],
27
+ agent.config.TONAPI_KEY,
28
+ );
29
+
30
+ if (!result?.stack || result.stack.length === 0) {
31
+ return { message: "Failed to read cleanup info" };
32
+ }
33
+
34
+ // agentCleanupInfo returns a struct as a tuple
35
+ const stack = result.stack;
36
+ let items = stack;
37
+ if (stack[0]?.type === "tuple" && stack[0].tuple) {
38
+ items = stack[0].tuple;
39
+ }
40
+
41
+ const parseNum = (item: any): number => {
42
+ if (!item || item.type !== "num") return 0;
43
+ const s = item.num as string;
44
+ if (s.startsWith("-0x") || s.startsWith("-0X")) return Number(-BigInt(s.slice(1)));
45
+ return Number(BigInt(s));
46
+ };
47
+ const parseBool = (item: any): boolean => {
48
+ if (!item) return false;
49
+ if (item.type === "num") {
50
+ const s = item.num as string;
51
+ if (s.startsWith("-0x")) return -BigInt(s.slice(1)) !== 0n;
52
+ return BigInt(s) !== 0n;
53
+ }
54
+ return false;
55
+ };
56
+
57
+ const info = {
58
+ index: parseNum(items[0]),
59
+ exists: parseBool(items[1]),
60
+ score: parseNum(items[2]),
61
+ totalRatings: parseNum(items[3]),
62
+ registeredAt: parseNum(items[4]),
63
+ lastActive: parseNum(items[5]),
64
+ daysSinceActive: parseNum(items[6]),
65
+ daysSinceRegistered: parseNum(items[7]),
66
+ eligibleForCleanup: parseBool(items[8]),
67
+ cleanupReason: parseNum(items[9]),
68
+ };
69
+
70
+ const reasons: Record<number, string> = {
71
+ 0: "none — agent is healthy",
72
+ 1: "bad_score — score < 20% with 100+ ratings",
73
+ 2: "inactive — no activity for 30+ days",
74
+ 3: "ghost — 0 ratings after 7+ days since registration",
75
+ };
76
+
77
+ return {
78
+ ...info,
79
+ cleanupReasonText: reasons[info.cleanupReason] || "unknown",
80
+ onChain: true,
81
+ contractAddress: addr,
82
+ };
83
+ },
84
+ });
@@ -1,53 +1,126 @@
1
1
  import { z } from "zod";
2
- import { Address } from "@ton/core";
3
- import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
2
+ import { Address, internal, toNano } from "@ton/core";
3
+ import { defineAction, toFriendlyAddress, sendTransaction } from "@ton-agent-kit/core";
4
4
  import { loadAgentRegistry, saveAgentRegistry } from "../utils";
5
+ import { resolveContractAddress } from "../reputation-config";
6
+ import { buildRateBody, callContractGetter, lookupAgentIndex, parseAgentDataFromStack } from "../reputation-helpers";
5
7
 
6
- export const getAgentReputationAction = defineAction({
7
- name: "get_agent_reputation",
8
- description:
9
- "Get the reputation score of a registered agent. Set addTask=true and success=true to record a successful task. Set addTask=true and success=false to record a failed task.",
10
- schema: z.object({
11
- agentId: z.string().describe("Agent ID to check or update"),
12
- addTask: z
13
- .union([z.boolean(), z.string()])
14
- .optional()
15
- .describe("Set to true to record a completed task"),
16
- success: z
17
- .union([z.boolean(), z.string()])
18
- .optional()
19
- .describe("If addTask is true, whether the task was successful"),
20
- }),
21
- handler: async (agent, params) => {
22
- const registry = loadAgentRegistry();
23
- const agentRecord = registry[params.agentId];
24
- if (!agentRecord) throw new Error(`Agent not found: ${params.agentId}`);
25
-
26
- // Coerce string booleans
27
- const addTask = typeof params.addTask === "string" ? params.addTask === "true" : params.addTask;
28
- const success = typeof params.success === "string" ? params.success === "true" : params.success;
29
-
30
- // Update reputation if recording a task
31
- if (addTask) {
32
- agentRecord.reputation.totalTasks += 1;
33
- if (success !== false) {
34
- agentRecord.reputation.successfulTasks += 1;
8
+ export function createGetAgentReputationAction(contractAddress?: string) {
9
+ return defineAction({
10
+ name: "get_agent_reputation",
11
+ description:
12
+ "Get the reputation score of a registered agent. Set addTask=true and success=true/false to record a task result. Works both on-chain and locally.",
13
+ schema: z.object({
14
+ agentId: z.string().describe("Agent ID or name to check or update"),
15
+ addTask: z
16
+ .union([z.boolean(), z.string()])
17
+ .optional()
18
+ .describe("Set to true to record a completed task"),
19
+ success: z
20
+ .union([z.boolean(), z.string()])
21
+ .optional()
22
+ .describe("If addTask is true, whether the task was successful"),
23
+ }),
24
+ handler: async (agent, params) => {
25
+ const addTask = typeof params.addTask === "string" ? params.addTask === "true" : params.addTask;
26
+ const success = typeof params.success === "string" ? params.success === "true" : params.success;
27
+
28
+ const addr = resolveContractAddress(contractAddress, agent.network);
29
+
30
+ // ── On-chain mode ──
31
+ if (addr) {
32
+ try {
33
+ const apiBase =
34
+ agent.network === "testnet"
35
+ ? "https://testnet.tonapi.io/v2"
36
+ : "https://tonapi.io/v2";
37
+
38
+ // Get the actual agent name for on-chain lookup
39
+ // Prefer JSON registry (has the exact name used at registration)
40
+ const jsonRegistry = loadAgentRegistry();
41
+ const jsonRecord = jsonRegistry[params.agentId];
42
+ const agentName = jsonRecord?.name
43
+ || (params.agentId.startsWith("agent_") ? params.agentId.slice(6) : params.agentId);
44
+
45
+ // If rating, send Rate message
46
+ if (addTask) {
47
+ const body = buildRateBody(agentName, success !== false);
48
+
49
+ await sendTransaction(agent, [
50
+ internal({
51
+ to: Address.parse(addr),
52
+ value: toNano("0.03"),
53
+ bounce: true,
54
+ body,
55
+ }),
56
+ ]);
57
+ }
58
+
59
+ // Look up agent index by name hash
60
+ const idx = await lookupAgentIndex(apiBase, addr, agentName, agent.config.TONAPI_KEY);
61
+
62
+ if (idx !== null) {
63
+ // Get on-chain data using index
64
+ const dataRes = await callContractGetter(
65
+ apiBase, addr, "agentData", [idx.toString()], agent.config.TONAPI_KEY,
66
+ );
67
+ const repRes = await callContractGetter(
68
+ apiBase, addr, "agentReputation", [idx.toString()], agent.config.TONAPI_KEY,
69
+ );
70
+
71
+ const agentData = dataRes?.stack ? parseAgentDataFromStack(dataRes.stack) : null;
72
+ const repScore = repRes?.stack?.[0]?.num
73
+ ? Number(BigInt(repRes.stack[0].num))
74
+ : 0;
75
+
76
+ return {
77
+ agentId: params.agentId,
78
+ name: agentName,
79
+ reputation: {
80
+ score: repScore,
81
+ totalTasks: agentData?.totalTasks || 0,
82
+ successfulTasks: agentData?.successes || 0,
83
+ },
84
+ available: agentData?.available ?? true,
85
+ onChain: true,
86
+ contractAddress: addr,
87
+ };
88
+ }
89
+
90
+ // Agent not found on-chain — fall through to JSON
91
+ } catch {
92
+ // Fall through to JSON
93
+ }
35
94
  }
36
- agentRecord.reputation.score = Math.round(
37
- (agentRecord.reputation.successfulTasks /
38
- agentRecord.reputation.totalTasks) *
39
- 100,
40
- );
41
- saveAgentRegistry(registry);
42
- }
43
-
44
- return {
45
- agentId: params.agentId,
46
- name: agentRecord.name,
47
- address: agentRecord.address,
48
- friendlyAddress: toFriendlyAddress(Address.parse(agentRecord.address), agent.network),
49
- reputation: agentRecord.reputation,
50
- registeredAt: agentRecord.registeredAt,
51
- };
52
- },
53
- });
95
+
96
+ // ── JSON fallback mode ──
97
+ const registry = loadAgentRegistry();
98
+ const agentRecord = registry[params.agentId];
99
+ if (!agentRecord) throw new Error(`Agent not found: ${params.agentId}`);
100
+
101
+ if (addTask) {
102
+ agentRecord.reputation.totalTasks += 1;
103
+ if (success !== false) {
104
+ agentRecord.reputation.successfulTasks += 1;
105
+ }
106
+ agentRecord.reputation.score = Math.round(
107
+ (agentRecord.reputation.successfulTasks / agentRecord.reputation.totalTasks) * 100,
108
+ );
109
+ saveAgentRegistry(registry);
110
+ }
111
+
112
+ return {
113
+ agentId: params.agentId,
114
+ name: agentRecord.name,
115
+ address: agentRecord.address,
116
+ friendlyAddress: toFriendlyAddress(Address.parse(agentRecord.address), agent.network),
117
+ reputation: agentRecord.reputation,
118
+ available: agentRecord.available !== false,
119
+ onChain: false,
120
+ registeredAt: agentRecord.registeredAt,
121
+ };
122
+ },
123
+ });
124
+ }
125
+
126
+ export const getAgentReputationAction = createGetAgentReputationAction();
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ import { defineAction } from "@ton-agent-kit/core";
3
+ import { resolveContractAddress } from "../reputation-config";
4
+ import { callContractGetter, parseDisputeData } from "../reputation-helpers";
5
+
6
+ export const getOpenDisputesAction = defineAction({
7
+ name: "get_open_disputes",
8
+ description:
9
+ "Get all open (unsettled) disputes from the reputation contract. Arbiters use this to find disputes they can join and vote on.",
10
+ schema: z.object({
11
+ limit: z.coerce.number().optional().describe("Max disputes to return. Default 20."),
12
+ }),
13
+ handler: async (agent, params) => {
14
+ const addr = resolveContractAddress(undefined, agent.network);
15
+ if (!addr) {
16
+ return { disputes: [], count: 0, message: "No reputation contract configured" };
17
+ }
18
+
19
+ const apiBase = agent.network === "testnet"
20
+ ? "https://testnet.tonapi.io/v2"
21
+ : "https://tonapi.io/v2";
22
+
23
+ const limit = params.limit || 20;
24
+
25
+ const countRes = await callContractGetter(apiBase, addr, "disputeCount", undefined, agent.config.TONAPI_KEY);
26
+ const totalDisputes = countRes?.stack?.[0]?.num ? Number(BigInt(countRes.stack[0].num)) : 0;
27
+
28
+ if (totalDisputes === 0) {
29
+ return { disputes: [], count: 0, total: 0, message: "No disputes registered" };
30
+ }
31
+
32
+ const disputes: any[] = [];
33
+ let checked = 0;
34
+ for (let i = totalDisputes - 1; i >= 0 && disputes.length < limit; i--) {
35
+ try {
36
+ const dataRes = await callContractGetter(apiBase, addr, "disputeData", [i.toString()], agent.config.TONAPI_KEY);
37
+ if (dataRes?.stack) {
38
+ const dispute = parseDisputeData(dataRes.stack);
39
+ if (dispute && !dispute.settled) {
40
+ disputes.push({
41
+ index: i,
42
+ escrowAddress: dispute.escrowAddress,
43
+ depositor: dispute.depositor,
44
+ beneficiary: dispute.beneficiary,
45
+ amount: dispute.amount,
46
+ votingDeadline: new Date(dispute.votingDeadline * 1000).toISOString(),
47
+ isExpired: Date.now() / 1000 > dispute.votingDeadline,
48
+ });
49
+ }
50
+ }
51
+ } catch {}
52
+ checked++;
53
+ if (checked > 100) break;
54
+ }
55
+
56
+ return {
57
+ disputes,
58
+ count: disputes.length,
59
+ total: totalDisputes,
60
+ onChain: true,
61
+ contractAddress: addr,
62
+ message: `Found ${disputes.length} open dispute(s)`,
63
+ };
64
+ },
65
+ });
@@ -0,0 +1,114 @@
1
+ import { z } from "zod";
2
+ import { defineAction } from "@ton-agent-kit/core";
3
+
4
+ export const processPendingRatingsAction = defineAction({
5
+ name: "process_pending_ratings",
6
+ description:
7
+ "Process pending reputation ratings left by completed escrow deals. Each escrow completion (release, auto-release, refund) creates pending ratings for both buyer and seller. Call this to review or auto-submit them.",
8
+ schema: z.object({
9
+ autoSubmit: z
10
+ .boolean()
11
+ .optional()
12
+ .describe("Automatically submit all pending ratings with suggested success values. Defaults to false (returns pending list for review)."),
13
+ }),
14
+ handler: async (agent, params) => {
15
+ // Load pending ratings from memory
16
+ let entries: any[] = [];
17
+ try {
18
+ const r = await (agent as any).runAction("list_context", {
19
+ namespace: "pending_ratings",
20
+ });
21
+ entries = r.entries || [];
22
+ } catch {
23
+ return {
24
+ processed: 0,
25
+ pending: 0,
26
+ ratings: [],
27
+ message: "Memory plugin not available. Cannot process pending ratings.",
28
+ };
29
+ }
30
+
31
+ if (entries.length === 0) {
32
+ return {
33
+ processed: 0,
34
+ pending: 0,
35
+ ratings: [],
36
+ message: "No pending ratings to process.",
37
+ };
38
+ }
39
+
40
+ // Parse entries
41
+ const pending: any[] = [];
42
+ for (const entry of entries) {
43
+ try {
44
+ const data = JSON.parse(entry.value);
45
+ pending.push({ ...data, memoryKey: entry.key });
46
+ } catch {
47
+ continue;
48
+ }
49
+ }
50
+
51
+ if (!params.autoSubmit) {
52
+ return {
53
+ processed: 0,
54
+ pending: pending.length,
55
+ ratings: pending.map((p) => ({
56
+ escrowId: p.escrowId,
57
+ raterRole: p.raterRole,
58
+ targetAddress: p.targetAddress,
59
+ suggestedSuccess: p.suggestedSuccess,
60
+ escrowOutcome: p.escrowOutcome,
61
+ })),
62
+ message: `${pending.length} pending rating(s) found. Set autoSubmit=true to submit them.`,
63
+ };
64
+ }
65
+
66
+ // Auto-submit ratings
67
+ let processed = 0;
68
+ const results: any[] = [];
69
+
70
+ for (const rating of pending) {
71
+ try {
72
+ // Find the target agent ID by address match in the registry
73
+ // Use a simple name-based lookup: the target address is stored in the rating
74
+ await (agent as any).runAction("get_agent_reputation", {
75
+ agentId: rating.targetAddress,
76
+ addTask: true,
77
+ success: rating.suggestedSuccess,
78
+ });
79
+
80
+ // Delete the processed rating from memory
81
+ try {
82
+ await (agent as any).runAction("delete_context", {
83
+ key: rating.memoryKey,
84
+ namespace: "pending_ratings",
85
+ });
86
+ } catch {}
87
+
88
+ processed++;
89
+ results.push({
90
+ escrowId: rating.escrowId,
91
+ raterRole: rating.raterRole,
92
+ targetAddress: rating.targetAddress,
93
+ success: rating.suggestedSuccess,
94
+ submitted: true,
95
+ });
96
+ } catch (err: any) {
97
+ results.push({
98
+ escrowId: rating.escrowId,
99
+ raterRole: rating.raterRole,
100
+ targetAddress: rating.targetAddress,
101
+ submitted: false,
102
+ error: err.message,
103
+ });
104
+ }
105
+ }
106
+
107
+ return {
108
+ processed,
109
+ pending: pending.length - processed,
110
+ ratings: results,
111
+ message: `Processed ${processed}/${pending.length} ratings.`,
112
+ };
113
+ },
114
+ });