@varun-ai07/covenant-mcp 1.2.2 → 1.3.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/README.md +128 -12
- package/dist/abis/MultiTokenEscrow.json +836 -0
- package/dist/abis/v2/AgentRegistry.json +872 -0
- package/dist/abis/v2/DisputeResolution.json +493 -0
- package/dist/abis/v2/InsurancePool.json +645 -0
- package/dist/abis/v2/ReceiptVerifier.json +394 -0
- package/dist/abis/v2/RevisionManager.json +544 -0
- package/dist/abis/v2/TaskEscrow.json +1018 -0
- package/dist/cli.js +0 -0
- package/dist/config.d.ts +13 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +84 -25
- package/dist/config.js.map +1 -1
- package/dist/handlers/wallet.d.ts.map +1 -1
- package/dist/handlers/wallet.js +1 -1
- package/dist/handlers/wallet.js.map +1 -1
- package/dist/lib/did.d.ts +72 -0
- package/dist/lib/did.d.ts.map +1 -0
- package/dist/lib/did.js +115 -0
- package/dist/lib/did.js.map +1 -0
- package/dist/lib/events.d.ts.map +1 -1
- package/dist/lib/events.js +2 -2
- package/dist/lib/events.js.map +1 -1
- package/dist/lib/formatResponse.d.ts +33 -0
- package/dist/lib/formatResponse.d.ts.map +1 -0
- package/dist/lib/formatResponse.js +92 -0
- package/dist/lib/formatResponse.js.map +1 -0
- package/dist/lib/schemaHelpers.d.ts +11 -0
- package/dist/lib/schemaHelpers.d.ts.map +1 -0
- package/dist/lib/schemaHelpers.js +11 -0
- package/dist/lib/schemaHelpers.js.map +1 -0
- package/dist/lib/store.d.ts +10 -0
- package/dist/lib/store.d.ts.map +1 -0
- package/dist/lib/store.js +39 -0
- package/dist/lib/store.js.map +1 -0
- package/dist/lib/verify.d.ts +21 -0
- package/dist/lib/verify.d.ts.map +1 -0
- package/dist/lib/verify.js +568 -0
- package/dist/lib/verify.js.map +1 -0
- package/dist/schemas.d.ts +5 -5
- package/dist/schemas.d.ts.map +1 -1
- package/dist/server.d.ts +1 -25
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +53 -37
- package/dist/server.js.map +1 -1
- package/dist/tools/account-abstraction.d.ts +3 -0
- package/dist/tools/account-abstraction.d.ts.map +1 -0
- package/dist/tools/account-abstraction.js +364 -0
- package/dist/tools/account-abstraction.js.map +1 -0
- package/dist/tools/batches.d.ts.map +1 -1
- package/dist/tools/batches.js +68 -37
- package/dist/tools/batches.js.map +1 -1
- package/dist/tools/bounties.d.ts +3 -0
- package/dist/tools/bounties.d.ts.map +1 -0
- package/dist/tools/bounties.js +304 -0
- package/dist/tools/bounties.js.map +1 -0
- package/dist/tools/bridge.d.ts +3 -0
- package/dist/tools/bridge.d.ts.map +1 -0
- package/dist/tools/bridge.js +190 -0
- package/dist/tools/bridge.js.map +1 -0
- package/dist/tools/collectives.d.ts.map +1 -1
- package/dist/tools/collectives.js +75 -48
- package/dist/tools/collectives.js.map +1 -1
- package/dist/tools/covenant-help.d.ts +3 -0
- package/dist/tools/covenant-help.d.ts.map +1 -0
- package/dist/tools/covenant-help.js +321 -0
- package/dist/tools/covenant-help.js.map +1 -0
- package/dist/tools/cross-chain.d.ts +3 -0
- package/dist/tools/cross-chain.d.ts.map +1 -0
- package/dist/tools/cross-chain.js +77 -0
- package/dist/tools/cross-chain.js.map +1 -0
- package/dist/tools/disputes.d.ts.map +1 -1
- package/dist/tools/disputes.js +39 -33
- package/dist/tools/disputes.js.map +1 -1
- package/dist/tools/escrow.d.ts.map +1 -1
- package/dist/tools/escrow.js +277 -202
- package/dist/tools/escrow.js.map +1 -1
- package/dist/tools/fiat-onramp.d.ts +3 -0
- package/dist/tools/fiat-onramp.d.ts.map +1 -0
- package/dist/tools/fiat-onramp.js +108 -0
- package/dist/tools/fiat-onramp.js.map +1 -0
- package/dist/tools/governance.d.ts +3 -0
- package/dist/tools/governance.d.ts.map +1 -0
- package/dist/tools/governance.js +271 -0
- package/dist/tools/governance.js.map +1 -0
- package/dist/tools/grants.d.ts +3 -0
- package/dist/tools/grants.d.ts.map +1 -0
- package/dist/tools/grants.js +269 -0
- package/dist/tools/grants.js.map +1 -0
- package/dist/tools/insurance.d.ts.map +1 -1
- package/dist/tools/insurance.js +92 -45
- package/dist/tools/insurance.js.map +1 -1
- package/dist/tools/market.d.ts.map +1 -1
- package/dist/tools/market.js +122 -103
- package/dist/tools/market.js.map +1 -1
- package/dist/tools/matching.d.ts +3 -0
- package/dist/tools/matching.d.ts.map +1 -0
- package/dist/tools/matching.js +233 -0
- package/dist/tools/matching.js.map +1 -0
- package/dist/tools/messaging.d.ts +3 -0
- package/dist/tools/messaging.d.ts.map +1 -0
- package/dist/tools/messaging.js +159 -0
- package/dist/tools/messaging.js.map +1 -0
- package/dist/tools/multi-token.d.ts +3 -0
- package/dist/tools/multi-token.d.ts.map +1 -0
- package/dist/tools/multi-token.js +274 -0
- package/dist/tools/multi-token.js.map +1 -0
- package/dist/tools/offchain-coordinator.d.ts +3 -0
- package/dist/tools/offchain-coordinator.d.ts.map +1 -0
- package/dist/tools/offchain-coordinator.js +436 -0
- package/dist/tools/offchain-coordinator.js.map +1 -0
- package/dist/tools/protocol.d.ts.map +1 -1
- package/dist/tools/protocol.js +19 -6
- package/dist/tools/protocol.js.map +1 -1
- package/dist/tools/receipts.d.ts.map +1 -1
- package/dist/tools/receipts.js +45 -22
- package/dist/tools/receipts.js.map +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +90 -43
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/reputation-vc.d.ts +3 -0
- package/dist/tools/reputation-vc.d.ts.map +1 -0
- package/dist/tools/reputation-vc.js +438 -0
- package/dist/tools/reputation-vc.js.map +1 -0
- package/dist/tools/revisions.d.ts +3 -0
- package/dist/tools/revisions.d.ts.map +1 -0
- package/dist/tools/revisions.js +108 -0
- package/dist/tools/revisions.js.map +1 -0
- package/dist/tools/router.d.ts +3 -0
- package/dist/tools/router.d.ts.map +1 -0
- package/dist/tools/router.js +104 -0
- package/dist/tools/router.js.map +1 -0
- package/dist/tools/streaming.d.ts +3 -0
- package/dist/tools/streaming.d.ts.map +1 -0
- package/dist/tools/streaming.js +350 -0
- package/dist/tools/streaming.js.map +1 -0
- package/dist/tools/templates.d.ts +3 -0
- package/dist/tools/templates.d.ts.map +1 -0
- package/dist/tools/templates.js +392 -0
- package/dist/tools/templates.js.map +1 -0
- package/dist/tools/training.d.ts +3 -0
- package/dist/tools/training.d.ts.map +1 -0
- package/dist/tools/training.js +304 -0
- package/dist/tools/training.js.map +1 -0
- package/dist/tools/v2-settlement.d.ts +3 -0
- package/dist/tools/v2-settlement.d.ts.map +1 -0
- package/dist/tools/v2-settlement.js +226 -0
- package/dist/tools/v2-settlement.js.map +1 -0
- package/dist/tools/verification.d.ts +3 -0
- package/dist/tools/verification.d.ts.map +1 -0
- package/dist/tools/verification.js +215 -0
- package/dist/tools/verification.js.map +1 -0
- package/dist/tools/verify-deep.d.ts +3 -0
- package/dist/tools/verify-deep.d.ts.map +1 -0
- package/dist/tools/verify-deep.js +125 -0
- package/dist/tools/verify-deep.js.map +1 -0
- package/dist/types.d.ts +16 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -23
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +2 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +8 -34
- package/dist/utils.js.map +1 -1
- package/package.json +3 -1
package/dist/tools/escrow.js
CHANGED
|
@@ -11,7 +11,9 @@ import { z } from "zod";
|
|
|
11
11
|
import { parseEther, formatEther, isAddress } from "viem";
|
|
12
12
|
import { loadAbi, CONTRACTS, getAccount } from "../config.js";
|
|
13
13
|
import { executeOrPrepare, readContract } from "../handlers/wallet.js";
|
|
14
|
-
import { formatTxResult, formatReadResult
|
|
14
|
+
import { formatTxResult, formatReadResult } from "../handlers/transactions.js";
|
|
15
|
+
import { formatSuccess, formatStructuredError, parseContractError } from "../lib/formatResponse.js";
|
|
16
|
+
import { ethAddress, ethAmount, ipfsCid, unixDeadline, taskId as taskIdSchema, priority as prioritySchema } from "../lib/schemaHelpers.js";
|
|
15
17
|
import { TASK_STATUS } from "../types.js";
|
|
16
18
|
const ABI = loadAbi("TaskEscrow");
|
|
17
19
|
// Input validation schemas
|
|
@@ -53,54 +55,66 @@ export function registerEscrowTools(server) {
|
|
|
53
55
|
// ──────────────────────────────────────────────────────────────
|
|
54
56
|
server.registerTool("corven_create_task", {
|
|
55
57
|
title: "Create & Fund Task",
|
|
56
|
-
description: "
|
|
57
|
-
"
|
|
58
|
-
"
|
|
58
|
+
description: "Creates a direct-hire task and locks payment in escrow in a single transaction.\n" +
|
|
59
|
+
"USE WHEN: You have a specific worker address and want to hire them directly. Use corven_post_open_task if you want competitive bidding instead.\n" +
|
|
60
|
+
"REQUIRES: Both client AND worker must be registered with corven_register_agent. Client wallet needs payment amount plus ~0.0003 ETH gas.\n" +
|
|
61
|
+
"RETURNS: taskId (save this for all subsequent calls), escrow status, deadline, worker address, Basescan link.\n" +
|
|
62
|
+
"COMES AFTER: corven_find_workers to get the worker address.\n" +
|
|
63
|
+
"COMES BEFORE: Worker calls corven_submit_work. Client calls corven_verify_task.\n" +
|
|
64
|
+
"NOTE: Payment is locked — neither party can access it until verification completes.",
|
|
59
65
|
inputSchema: {
|
|
60
|
-
worker:
|
|
61
|
-
payment:
|
|
62
|
-
deadline:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
descriptionHash: z
|
|
66
|
-
.string()
|
|
67
|
-
.describe("IPFS CID or on-chain hash for task description"),
|
|
68
|
-
priority: z
|
|
69
|
-
.number()
|
|
70
|
-
.optional()
|
|
71
|
-
.describe("Priority level 0-3 (Low/Medium/High/Urgent). Default 1 (Medium)"),
|
|
66
|
+
worker: ethAddress,
|
|
67
|
+
payment: ethAmount,
|
|
68
|
+
deadline: unixDeadline,
|
|
69
|
+
descriptionHash: ipfsCid,
|
|
70
|
+
priority: prioritySchema,
|
|
72
71
|
},
|
|
73
72
|
}, async ({ worker, payment, deadline, descriptionHash, priority }) => {
|
|
74
73
|
try {
|
|
75
|
-
// Validate input
|
|
76
74
|
const validationResult = createTaskSchema.safeParse({ worker, payment, deadline, descriptionHash, priority });
|
|
77
75
|
if (!validationResult.success) {
|
|
78
|
-
return
|
|
76
|
+
return formatStructuredError("Invalid task parameters.", validationResult.error.issues.map((e) => e.message).join(", "), "Check: worker must be full 42-char 0x address, payment decimal ETH string (0.001-1000), deadline future Unix timestamp, descriptionHash valid IPFS CID.", true);
|
|
79
77
|
}
|
|
80
|
-
// Validation successful, use validated values
|
|
81
|
-
const validatedWorker = worker;
|
|
82
|
-
const validatedPayment = payment;
|
|
83
|
-
const validatedDeadline = deadline;
|
|
84
|
-
const validatedDescriptionHash = descriptionHash;
|
|
85
|
-
const validatedPriority = priority;
|
|
86
78
|
const account = getAccount();
|
|
87
79
|
if (!account) {
|
|
88
|
-
return
|
|
80
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY environment variable is not set.", "Set PRIVATE_KEY in your .env file.", false);
|
|
89
81
|
}
|
|
90
82
|
const paymentWei = parseEther(payment);
|
|
91
83
|
const priorityLevel = priority ?? 1;
|
|
92
|
-
//
|
|
84
|
+
// Calculate total value: payment + protocol fee (1%) + priority fee
|
|
85
|
+
const PROTOCOL_FEE_BPS = 100n; // 1%
|
|
86
|
+
const PRIORITY_FEES = [50n, 100n, 200n, 500n]; // Low, Medium, High, Urgent
|
|
87
|
+
const priorityFeeBps = PRIORITY_FEES[priorityLevel] ?? 100n;
|
|
88
|
+
const totalFeeBps = PROTOCOL_FEE_BPS + priorityFeeBps;
|
|
89
|
+
const feeAmount = paymentWei * totalFeeBps / 10000n;
|
|
90
|
+
const totalValue = paymentWei + feeAmount;
|
|
93
91
|
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "createAndFundTask", [
|
|
94
|
-
|
|
92
|
+
worker,
|
|
95
93
|
paymentWei,
|
|
96
|
-
BigInt(
|
|
97
|
-
|
|
98
|
-
],
|
|
99
|
-
)
|
|
94
|
+
BigInt(deadline),
|
|
95
|
+
descriptionHash,
|
|
96
|
+
], totalValue);
|
|
97
|
+
if (result.status === "success") {
|
|
98
|
+
const deadlineDate = new Date(deadline * 1000).toUTCString();
|
|
99
|
+
return formatSuccess(`Task created. ${payment} ETH locked in escrow for worker.`, {
|
|
100
|
+
worker,
|
|
101
|
+
client: account,
|
|
102
|
+
payment: `${payment} ETH`,
|
|
103
|
+
deadline: deadlineDate,
|
|
104
|
+
specificationIpfs: descriptionHash,
|
|
105
|
+
status: "Funded",
|
|
106
|
+
priority: priorityLevel,
|
|
107
|
+
}, result.txHash, [
|
|
108
|
+
"Wait for worker to call corven_submit_work with your taskId.",
|
|
109
|
+
"Then call corven_verify_task to release payment after reviewing work.",
|
|
110
|
+
"Check status anytime with corven_get_task.",
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
100
113
|
return formatTxResult(result);
|
|
101
114
|
}
|
|
102
115
|
catch (e) {
|
|
103
|
-
|
|
116
|
+
const parsed = parseContractError(e);
|
|
117
|
+
return formatStructuredError(parsed.error, parsed.cause, parsed.fix, parsed.retryable);
|
|
104
118
|
}
|
|
105
119
|
});
|
|
106
120
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -108,23 +122,22 @@ export function registerEscrowTools(server) {
|
|
|
108
122
|
// ──────────────────────────────────────────────────────────────
|
|
109
123
|
server.registerTool("corven_get_task", {
|
|
110
124
|
title: "Get Task Details",
|
|
111
|
-
description: "
|
|
112
|
-
"
|
|
125
|
+
description: "Returns complete details for any task including current lifecycle status.\n" +
|
|
126
|
+
"USE WHEN: Checking if a worker has submitted work. Confirming payment released. Getting deliverable IPFS hash. Checking deadline.\n" +
|
|
127
|
+
"REQUIRES: Nothing. Free read-only call.\n" +
|
|
128
|
+
"RETURNS: Status, client/worker addresses, payment, deadline, specification hash, deliverable hash.\n" +
|
|
129
|
+
"STATUS MEANINGS: Funded=worker can begin. InProgress=worker acknowledged. Submitted=work ready for review. Completed=paid. Failed=rejected or expired.",
|
|
113
130
|
inputSchema: {
|
|
114
|
-
taskId:
|
|
131
|
+
taskId: taskIdSchema,
|
|
115
132
|
},
|
|
116
133
|
}, async ({ taskId }) => {
|
|
117
134
|
try {
|
|
118
|
-
|
|
119
|
-
const taskIdParam = taskId;
|
|
120
|
-
const validationResult = getTaskSchema.safeParse({ taskId: taskIdParam });
|
|
135
|
+
const validationResult = getTaskSchema.safeParse({ taskId });
|
|
121
136
|
if (!validationResult.success) {
|
|
122
|
-
return
|
|
137
|
+
return formatStructuredError("Invalid task ID.", `Received '${taskId}' — must be a positive integer.`, "Pass the numeric taskId returned by corven_create_task. Find your task IDs with corven_get_client_tasks or corven_get_worker_tasks.", false);
|
|
123
138
|
}
|
|
124
|
-
// Use validated taskId
|
|
125
139
|
const validatedTaskId = validationResult.data.taskId;
|
|
126
140
|
const data = await readContract(CONTRACTS.TaskEscrow, ABI, "getTask", [BigInt(validatedTaskId)]);
|
|
127
|
-
// Enrich status and priority with human-readable labels
|
|
128
141
|
const enriched = {
|
|
129
142
|
...data,
|
|
130
143
|
statusLabel: TASK_STATUS[data.status] ?? `Unknown(${data.status})`,
|
|
@@ -133,7 +146,8 @@ export function registerEscrowTools(server) {
|
|
|
133
146
|
return formatReadResult(enriched, `Task #${taskId}`);
|
|
134
147
|
}
|
|
135
148
|
catch (e) {
|
|
136
|
-
|
|
149
|
+
const parsed = parseContractError(e);
|
|
150
|
+
return formatStructuredError(parsed.error, parsed.cause, parsed.fix, parsed.retryable);
|
|
137
151
|
}
|
|
138
152
|
});
|
|
139
153
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -141,33 +155,35 @@ export function registerEscrowTools(server) {
|
|
|
141
155
|
// ──────────────────────────────────────────────────────────────
|
|
142
156
|
server.registerTool("corven_submit_work", {
|
|
143
157
|
title: "Submit Work Deliverable",
|
|
144
|
-
description: "Worker submits
|
|
145
|
-
"
|
|
158
|
+
description: "Worker submits completed deliverable IPFS hash on-chain. Commits work permanently and notifies the client.\n" +
|
|
159
|
+
"USE WHEN: You are the worker and have finished executing the task. Upload deliverable to IPFS first. Then call this with the CID.\n" +
|
|
160
|
+
"REQUIRES: You must be the assigned worker. Task status must be Funded or InProgress. Deadline must not have passed.\n" +
|
|
161
|
+
"RETURNS: Submission confirmation, IPFS hash recorded on-chain, next action for the client.\n" +
|
|
162
|
+
"COMES AFTER: Worker executes task off-chain and uploads deliverable to IPFS.\n" +
|
|
163
|
+
"COMES BEFORE: Client calls corven_verify_task.",
|
|
146
164
|
inputSchema: {
|
|
147
|
-
taskId:
|
|
148
|
-
deliverableHash:
|
|
149
|
-
.string()
|
|
150
|
-
.describe("IPFS CID or hash of the deliverable"),
|
|
165
|
+
taskId: taskIdSchema,
|
|
166
|
+
deliverableHash: ipfsCid,
|
|
151
167
|
},
|
|
152
168
|
}, async ({ taskId, deliverableHash }) => {
|
|
153
169
|
try {
|
|
154
|
-
// Validate input
|
|
155
170
|
const validationResult = submitWorkSchema.safeParse({ taskId, deliverableHash });
|
|
156
171
|
if (!validationResult.success) {
|
|
157
|
-
return
|
|
172
|
+
return formatStructuredError("Invalid parameters.", validationResult.error.issues.map((e) => e.message).join(", "), "taskId must be a positive integer. deliverableHash must be a valid IPFS CID (Qm... or bafy...).", true);
|
|
158
173
|
}
|
|
159
|
-
// Use validated values
|
|
160
|
-
const validatedTaskId = validationResult.data.taskId;
|
|
161
|
-
const validatedDeliverableHash = validationResult.data.deliverableHash;
|
|
162
174
|
const account = getAccount();
|
|
163
175
|
if (!account) {
|
|
164
|
-
return
|
|
176
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
|
|
177
|
+
}
|
|
178
|
+
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "submitWork", [BigInt(taskId), deliverableHash]);
|
|
179
|
+
if (result.status === "success") {
|
|
180
|
+
return formatSuccess(`Work submitted for Task #${taskId}. Client will be notified.`, { taskId, deliverableHash, status: "Submitted" }, result.txHash, ["Wait for client to review and call corven_verify_task.", "Payment releases automatically on approval."]);
|
|
165
181
|
}
|
|
166
|
-
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "submitWork", [BigInt(validatedTaskId), validatedDeliverableHash]);
|
|
167
182
|
return formatTxResult(result);
|
|
168
183
|
}
|
|
169
184
|
catch (e) {
|
|
170
|
-
|
|
185
|
+
const parsed = parseContractError(e);
|
|
186
|
+
return formatStructuredError(parsed.error, parsed.cause, parsed.fix, parsed.retryable);
|
|
171
187
|
}
|
|
172
188
|
});
|
|
173
189
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -175,30 +191,39 @@ export function registerEscrowTools(server) {
|
|
|
175
191
|
// ──────────────────────────────────────────────────────────────
|
|
176
192
|
server.registerTool("corven_verify_task", {
|
|
177
193
|
title: "Verify & Approve Task",
|
|
178
|
-
description: "Client
|
|
179
|
-
"
|
|
194
|
+
description: "Client approves submitted work. Triggers automatic payment release. Worker receives ETH. Reputation updates. ERC-8004 receipt created.\n" +
|
|
195
|
+
"USE WHEN: You are the client. Worker has submitted work (corven_get_task shows status Submitted). You have reviewed and approve.\n" +
|
|
196
|
+
"REQUIRES: You must be the client. Task status must be Submitted. Call corven_dispute_task instead to reject.\n" +
|
|
197
|
+
"RETURNS: Payment release confirmation, new reputation scores for both agents, receipt ID, Basescan link.\n" +
|
|
198
|
+
"COMES AFTER: corven_submit_work by the worker.\n" +
|
|
199
|
+
"NOTE: This is the final step. Payment releases automatically — no manual transfer needed.",
|
|
180
200
|
inputSchema: {
|
|
181
|
-
taskId:
|
|
182
|
-
success: z.boolean().describe("
|
|
201
|
+
taskId: taskIdSchema,
|
|
202
|
+
success: z.boolean().describe("true = approve work and release payment, false = reject work and refund client"),
|
|
183
203
|
},
|
|
184
204
|
}, async ({ taskId, success }) => {
|
|
185
205
|
try {
|
|
186
|
-
// Validate input
|
|
187
206
|
const validationResult = verifyTaskSchema.safeParse({ taskId, success });
|
|
188
207
|
if (!validationResult.success) {
|
|
189
|
-
return
|
|
208
|
+
return formatStructuredError("Invalid parameters.", validationResult.error.issues.map((e) => e.message).join(", "), "taskId must be positive integer. success must be boolean.", true);
|
|
190
209
|
}
|
|
191
|
-
// Use validated values
|
|
192
|
-
const { taskId: validatedTaskId, success: validatedSuccess } = validationResult.data;
|
|
193
210
|
const account = getAccount();
|
|
194
211
|
if (!account) {
|
|
195
|
-
return
|
|
212
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
|
|
213
|
+
}
|
|
214
|
+
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "verifyTask", [BigInt(taskId), success], undefined, "TaskEscrow");
|
|
215
|
+
if (result.status === "success") {
|
|
216
|
+
return formatSuccess(success
|
|
217
|
+
? `Task #${taskId} approved. Payment released to worker automatically.`
|
|
218
|
+
: `Task #${taskId} rejected. Payment refunded to client.`, { taskId, verdict: success ? "APPROVED" : "REJECTED" }, result.txHash, success
|
|
219
|
+
? ["Payment has been transferred to the worker's wallet.", "Both agents' reputation scores have been updated.", "An ERC-8004 receipt has been created as permanent proof."]
|
|
220
|
+
: ["Payment has been refunded to your wallet.", "Worker's reputation has been penalized."]);
|
|
196
221
|
}
|
|
197
|
-
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "verifyTask", [BigInt(validatedTaskId), validatedSuccess], undefined, "TaskEscrow");
|
|
198
222
|
return formatTxResult(result);
|
|
199
223
|
}
|
|
200
224
|
catch (e) {
|
|
201
|
-
|
|
225
|
+
const parsed = parseContractError(e);
|
|
226
|
+
return formatStructuredError(parsed.error, parsed.cause, parsed.fix, parsed.retryable);
|
|
202
227
|
}
|
|
203
228
|
});
|
|
204
229
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -206,34 +231,30 @@ export function registerEscrowTools(server) {
|
|
|
206
231
|
// ──────────────────────────────────────────────────────────────
|
|
207
232
|
server.registerTool("corven_dispute_task", {
|
|
208
233
|
title: "Dispute a Task",
|
|
209
|
-
description: "
|
|
210
|
-
"
|
|
234
|
+
description: "Freezes a task and initiates jury-based dispute resolution. Three randomly-selected agents vote. Majority decides.\n" +
|
|
235
|
+
"USE WHEN: You are the client and submitted work clearly fails the specification. Or you are the worker and were unfairly rejected.\n" +
|
|
236
|
+
"REQUIRES: Task status must be Submitted. Either client or worker can call.\n" +
|
|
237
|
+
"RETURNS: Dispute ID, jury selection confirmation, voting deadline.\n" +
|
|
238
|
+
"NOTE: Call corven_get_task first to confirm the task is in Submitted status.",
|
|
211
239
|
inputSchema: {
|
|
212
|
-
taskId:
|
|
213
|
-
reason: z
|
|
214
|
-
.string()
|
|
215
|
-
.optional()
|
|
216
|
-
.describe("Optional reason for the dispute (stored off-chain / emitted in event)"),
|
|
240
|
+
taskId: taskIdSchema,
|
|
241
|
+
reason: z.string().max(500).optional().describe("Optional reason for the dispute"),
|
|
217
242
|
},
|
|
218
243
|
}, async ({ taskId, reason }) => {
|
|
219
244
|
try {
|
|
220
|
-
// Validate input
|
|
221
|
-
const validationResult = disputeTaskSchema.safeParse({ taskId, reason });
|
|
222
|
-
if (!validationResult.success) {
|
|
223
|
-
return formatError(new Error(`Invalid input: ${validationResult.error.issues.map((e) => e.message).join(", ")}`));
|
|
224
|
-
}
|
|
225
|
-
// Use validated values
|
|
226
|
-
const validatedTaskId = validationResult.data.taskId;
|
|
227
|
-
const validatedReason = validationResult.data.reason;
|
|
228
245
|
const account = getAccount();
|
|
229
246
|
if (!account) {
|
|
230
|
-
return
|
|
247
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
|
|
248
|
+
}
|
|
249
|
+
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "disputeTask", [BigInt(taskId)], undefined, "TaskEscrow");
|
|
250
|
+
if (result.status === "success") {
|
|
251
|
+
return formatSuccess(`Task #${taskId} disputed. Payment frozen pending jury resolution.`, { taskId, reason: reason || "No reason provided", status: "Disputed" }, result.txHash, ["Three jurors will be randomly selected to vote.", "Resolution typically takes 24-48 hours."]);
|
|
231
252
|
}
|
|
232
|
-
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "disputeTask", [BigInt(validatedTaskId)], undefined, "TaskEscrow");
|
|
233
253
|
return formatTxResult(result);
|
|
234
254
|
}
|
|
235
255
|
catch (e) {
|
|
236
|
-
|
|
256
|
+
const parsed = parseContractError(e);
|
|
257
|
+
return formatStructuredError(parsed.error, parsed.cause, parsed.fix, parsed.retryable);
|
|
237
258
|
}
|
|
238
259
|
});
|
|
239
260
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -241,26 +262,39 @@ export function registerEscrowTools(server) {
|
|
|
241
262
|
// ──────────────────────────────────────────────────────────────
|
|
242
263
|
server.registerTool("corven_create_task_with_priority", {
|
|
243
264
|
title: "Create Task with Priority",
|
|
244
|
-
description: "
|
|
245
|
-
"
|
|
265
|
+
description: "Creates a direct-hire task with a specific priority level and locks payment in escrow.\n" +
|
|
266
|
+
"USE WHEN: You have a specific worker and need urgent or high-priority execution with guaranteed faster attention.\n" +
|
|
267
|
+
"REQUIRES: Both client AND worker registered. Client wallet needs payment + priority fee + ~0.0003 ETH gas.\n" +
|
|
268
|
+
"RETURNS: taskId, escrow status, priority level, total cost breakdown, Basescan link.\n" +
|
|
269
|
+
"COMES AFTER: corven_find_workers to get the worker address.\n" +
|
|
270
|
+
"COMES BEFORE: Worker calls corven_submit_work. Client calls corven_verify_task.\n" +
|
|
271
|
+
"NOTE: Priority fees — Low: 0.5%, Medium: 1%, High: 2%, Urgent: 5%. Use corven_create_task for default Medium priority.",
|
|
246
272
|
inputSchema: {
|
|
247
|
-
worker:
|
|
248
|
-
payment:
|
|
249
|
-
deadline:
|
|
250
|
-
descriptionHash:
|
|
251
|
-
priority:
|
|
273
|
+
worker: ethAddress,
|
|
274
|
+
payment: ethAmount,
|
|
275
|
+
deadline: unixDeadline,
|
|
276
|
+
descriptionHash: ipfsCid,
|
|
277
|
+
priority: prioritySchema,
|
|
252
278
|
},
|
|
253
279
|
}, async ({ worker, payment, deadline, descriptionHash, priority }) => {
|
|
254
280
|
try {
|
|
255
281
|
const account = getAccount();
|
|
256
282
|
if (!account)
|
|
257
|
-
return
|
|
283
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
|
|
258
284
|
const paymentWei = parseEther(payment);
|
|
259
|
-
|
|
285
|
+
// Calculate total value: payment + protocol fee (1%) + priority fee
|
|
286
|
+
const PROTOCOL_FEE_BPS = 100n;
|
|
287
|
+
const PRIORITY_FEES = [50n, 100n, 200n, 500n];
|
|
288
|
+
const priorityFeeBps = PRIORITY_FEES[priority] ?? 100n;
|
|
289
|
+
const totalFeeBps = PROTOCOL_FEE_BPS + priorityFeeBps;
|
|
290
|
+
const feeAmount = paymentWei * totalFeeBps / 10000n;
|
|
291
|
+
const totalValue = paymentWei + feeAmount;
|
|
292
|
+
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "createAndFundTaskWithPriority", [worker, paymentWei, BigInt(deadline), descriptionHash, priority], totalValue);
|
|
260
293
|
return formatTxResult(result);
|
|
261
294
|
}
|
|
262
295
|
catch (e) {
|
|
263
|
-
|
|
296
|
+
const p = parseContractError(e);
|
|
297
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
264
298
|
}
|
|
265
299
|
});
|
|
266
300
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -268,13 +302,18 @@ export function registerEscrowTools(server) {
|
|
|
268
302
|
// ──────────────────────────────────────────────────────────────
|
|
269
303
|
server.registerTool("corven_create_milestone_task", {
|
|
270
304
|
title: "Create Milestone Task",
|
|
271
|
-
description: "
|
|
272
|
-
"
|
|
305
|
+
description: "Creates a task with incremental milestone-based payments. Each milestone is verified and paid independently.\n" +
|
|
306
|
+
"USE WHEN: Task has distinct phases (e.g., research, draft, final). You want partial payment tied to checkpoints.\n" +
|
|
307
|
+
"REQUIRES: Both client AND worker registered. Client wallet needs totalPayment + ~2% fees + ~0.0003 ETH gas.\n" +
|
|
308
|
+
"RETURNS: taskId, milestone count, individual milestone amounts, total escrowed, Basescan link.\n" +
|
|
309
|
+
"COMES AFTER: corven_find_workers to get the worker address.\n" +
|
|
310
|
+
"COMES BEFORE: Worker calls corven_submit_milestone per milestone. Client calls corven_verify_milestone to release each payment.\n" +
|
|
311
|
+
"NOTE: totalPayment must equal the sum of milestonePayments. Milestones are verified in order (0, 1, 2, ...).",
|
|
273
312
|
inputSchema: {
|
|
274
|
-
worker:
|
|
275
|
-
totalPayment:
|
|
276
|
-
deadline:
|
|
277
|
-
descriptionHash:
|
|
313
|
+
worker: ethAddress,
|
|
314
|
+
totalPayment: ethAmount,
|
|
315
|
+
deadline: unixDeadline,
|
|
316
|
+
descriptionHash: ipfsCid,
|
|
278
317
|
milestoneDescriptions: z.array(z.string()).describe("Array of milestone descriptions"),
|
|
279
318
|
milestonePayments: z.array(z.string()).describe("Array of milestone payments in ETH"),
|
|
280
319
|
},
|
|
@@ -282,14 +321,21 @@ export function registerEscrowTools(server) {
|
|
|
282
321
|
try {
|
|
283
322
|
const account = getAccount();
|
|
284
323
|
if (!account)
|
|
285
|
-
return
|
|
324
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
|
|
286
325
|
const paymentWei = parseEther(totalPayment);
|
|
287
326
|
const payments = milestonePayments.map(p => parseEther(p));
|
|
288
|
-
|
|
327
|
+
// Calculate total value: payment + protocol fee (1%) + priority fee (default Medium=1%)
|
|
328
|
+
const PROTOCOL_FEE_BPS = 100n;
|
|
329
|
+
const PRIORITY_FEE_BPS = 100n; // Default Medium priority
|
|
330
|
+
const totalFeeBps = PROTOCOL_FEE_BPS + PRIORITY_FEE_BPS;
|
|
331
|
+
const feeAmount = paymentWei * totalFeeBps / 10000n;
|
|
332
|
+
const totalValue = paymentWei + feeAmount;
|
|
333
|
+
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "createTaskWithMilestones", [worker, paymentWei, BigInt(deadline), descriptionHash, milestoneDescriptions, payments], totalValue);
|
|
289
334
|
return formatTxResult(result);
|
|
290
335
|
}
|
|
291
336
|
catch (e) {
|
|
292
|
-
|
|
337
|
+
const p = parseContractError(e);
|
|
338
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
293
339
|
}
|
|
294
340
|
});
|
|
295
341
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -297,22 +343,29 @@ export function registerEscrowTools(server) {
|
|
|
297
343
|
// ──────────────────────────────────────────────────────────────
|
|
298
344
|
server.registerTool("corven_submit_milestone", {
|
|
299
345
|
title: "Submit Milestone",
|
|
300
|
-
description: "
|
|
346
|
+
description: "Worker submits a deliverable for a specific milestone in a milestone-based task.\n" +
|
|
347
|
+
"USE WHEN: You are the worker and have completed one milestone phase. Upload deliverable to IPFS first.\n" +
|
|
348
|
+
"REQUIRES: You must be the assigned worker. Milestone must not already be submitted. Task status must be Funded or InProgress.\n" +
|
|
349
|
+
"RETURNS: Submission confirmation, milestone index, IPFS hash recorded on-chain.\n" +
|
|
350
|
+
"COMES AFTER: corven_create_milestone_task created the task with milestones.\n" +
|
|
351
|
+
"COMES BEFORE: Client calls corven_verify_milestone to approve and release payment for this milestone.\n" +
|
|
352
|
+
"NOTE: Milestones must be submitted in order. You cannot skip ahead.",
|
|
301
353
|
inputSchema: {
|
|
302
|
-
taskId:
|
|
354
|
+
taskId: taskIdSchema,
|
|
303
355
|
milestoneIndex: z.number().describe("Milestone index (0-based)"),
|
|
304
|
-
deliverableHash:
|
|
356
|
+
deliverableHash: ipfsCid,
|
|
305
357
|
},
|
|
306
358
|
}, async ({ taskId, milestoneIndex, deliverableHash }) => {
|
|
307
359
|
try {
|
|
308
360
|
const account = getAccount();
|
|
309
361
|
if (!account)
|
|
310
|
-
return
|
|
362
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
|
|
311
363
|
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "submitMilestone", [BigInt(taskId), BigInt(milestoneIndex), deliverableHash]);
|
|
312
364
|
return formatTxResult(result);
|
|
313
365
|
}
|
|
314
366
|
catch (e) {
|
|
315
|
-
|
|
367
|
+
const p = parseContractError(e);
|
|
368
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
316
369
|
}
|
|
317
370
|
});
|
|
318
371
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -320,9 +373,15 @@ export function registerEscrowTools(server) {
|
|
|
320
373
|
// ──────────────────────────────────────────────────────────────
|
|
321
374
|
server.registerTool("corven_verify_milestone", {
|
|
322
375
|
title: "Verify Milestone",
|
|
323
|
-
description: "
|
|
376
|
+
description: "Client verifies a submitted milestone and releases its payment to the worker.\n" +
|
|
377
|
+
"USE WHEN: You are the client. Worker has submitted a milestone (corven_get_milestone shows submitted). You have reviewed and approve.\n" +
|
|
378
|
+
"REQUIRES: You must be the client. Milestone must have been submitted by the worker.\n" +
|
|
379
|
+
"RETURNS: Verification result, payment released amount, milestone status update.\n" +
|
|
380
|
+
"COMES AFTER: corven_submit_milestone by the worker.\n" +
|
|
381
|
+
"COMES BEFORE: Next milestone submission, or task completion if this was the final milestone.\n" +
|
|
382
|
+
"NOTE: Rejecting a milestone does not refund the entire task — only that milestone's payment is withheld.",
|
|
324
383
|
inputSchema: {
|
|
325
|
-
taskId:
|
|
384
|
+
taskId: taskIdSchema,
|
|
326
385
|
milestoneIndex: z.number().describe("Milestone index (0-based)"),
|
|
327
386
|
success: z.boolean().describe("Whether the milestone passes verification"),
|
|
328
387
|
},
|
|
@@ -330,47 +389,42 @@ export function registerEscrowTools(server) {
|
|
|
330
389
|
try {
|
|
331
390
|
const account = getAccount();
|
|
332
391
|
if (!account)
|
|
333
|
-
return
|
|
392
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
|
|
334
393
|
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "verifyMilestone", [BigInt(taskId), BigInt(milestoneIndex), success], undefined, "TaskEscrow");
|
|
335
394
|
return formatTxResult(result);
|
|
336
395
|
}
|
|
337
396
|
catch (e) {
|
|
338
|
-
|
|
397
|
+
const p = parseContractError(e);
|
|
398
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
339
399
|
}
|
|
340
400
|
});
|
|
341
401
|
// ──────────────────────────────────────────────────────────────
|
|
342
|
-
// corven_get_milestone
|
|
402
|
+
// corven_get_milestone (includes count when no index provided)
|
|
343
403
|
// ──────────────────────────────────────────────────────────────
|
|
344
404
|
server.registerTool("corven_get_milestone", {
|
|
345
|
-
title: "Get Milestone
|
|
346
|
-
description: "
|
|
405
|
+
title: "Get Milestone",
|
|
406
|
+
description: "Reads milestone details by index, or returns the total milestone count if no index is provided.\n" +
|
|
407
|
+
"USE WHEN: Checking which milestones have been submitted or verified. Counting milestones before submission.\n" +
|
|
408
|
+
"REQUIRES: Nothing. Free read-only call.\n" +
|
|
409
|
+
"RETURNS: Milestone description, payment amount, submission hash, verification status — or milestone count if index omitted.\n" +
|
|
410
|
+
"COMES AFTER: corven_create_milestone_task created the task.\n" +
|
|
411
|
+
"COMES BEFORE: corven_submit_milestone or corven_verify_milestone.",
|
|
347
412
|
inputSchema: {
|
|
348
|
-
taskId:
|
|
349
|
-
milestoneIndex: z.number().describe("Milestone index (0-based)"),
|
|
413
|
+
taskId: taskIdSchema,
|
|
414
|
+
milestoneIndex: z.number().optional().describe("Milestone index (0-based). Omit to get count."),
|
|
350
415
|
},
|
|
351
416
|
}, async ({ taskId, milestoneIndex }) => {
|
|
352
417
|
try {
|
|
418
|
+
if (milestoneIndex === undefined) {
|
|
419
|
+
const count = await readContract(CONTRACTS.TaskEscrow, ABI, "getMilestoneCount", [BigInt(taskId)]);
|
|
420
|
+
return formatReadResult({ taskId, milestoneCount: Number(count) }, `Milestone count for Task #${taskId}`);
|
|
421
|
+
}
|
|
353
422
|
const data = await readContract(CONTRACTS.TaskEscrow, ABI, "getMilestone", [BigInt(taskId), BigInt(milestoneIndex)]);
|
|
354
423
|
return formatReadResult(data, `Milestone ${milestoneIndex} of Task #${taskId}`);
|
|
355
424
|
}
|
|
356
425
|
catch (e) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
});
|
|
360
|
-
// ──────────────────────────────────────────────────────────────
|
|
361
|
-
// corven_get_milestone_count
|
|
362
|
-
// ──────────────────────────────────────────────────────────────
|
|
363
|
-
server.registerTool("corven_get_milestone_count", {
|
|
364
|
-
title: "Get Milestone Count",
|
|
365
|
-
description: "Get the number of milestones in a task.",
|
|
366
|
-
inputSchema: { taskId: z.number().describe("Task ID") },
|
|
367
|
-
}, async ({ taskId }) => {
|
|
368
|
-
try {
|
|
369
|
-
const count = await readContract(CONTRACTS.TaskEscrow, ABI, "getMilestoneCount", [BigInt(taskId)]);
|
|
370
|
-
return formatReadResult({ taskId, milestoneCount: Number(count) }, `Milestones for Task #${taskId}`);
|
|
371
|
-
}
|
|
372
|
-
catch (e) {
|
|
373
|
-
return formatError(e);
|
|
426
|
+
const p = parseContractError(e);
|
|
427
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
374
428
|
}
|
|
375
429
|
});
|
|
376
430
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -378,25 +432,38 @@ export function registerEscrowTools(server) {
|
|
|
378
432
|
// ──────────────────────────────────────────────────────────────
|
|
379
433
|
server.registerTool("corven_create_subtask", {
|
|
380
434
|
title: "Create Subtask",
|
|
381
|
-
description: "
|
|
435
|
+
description: "Creates a child task under a parent task with its own worker, payment, and deadline.\n" +
|
|
436
|
+
"USE WHEN: Decomposing a large task into parallel subtasks for different specialists.\n" +
|
|
437
|
+
"REQUIRES: Parent task must exist. Both client and subtask worker must be registered. Client wallet needs payment + ~2% fees.\n" +
|
|
438
|
+
"RETURNS: Subtask taskId, parent reference, worker, payment, deadline.\n" +
|
|
439
|
+
"COMES AFTER: corven_create_task created the parent task.\n" +
|
|
440
|
+
"COMES BEFORE: Subtask worker calls corven_submit_work. Client calls corven_verify_task on the subtask.\n" +
|
|
441
|
+
"NOTE: Subtask payment is funded independently from the parent. Use corven_get_child_tasks to list all subtasks.",
|
|
382
442
|
inputSchema: {
|
|
383
|
-
parentTaskId:
|
|
384
|
-
worker:
|
|
385
|
-
payment:
|
|
386
|
-
deadline:
|
|
387
|
-
descriptionHash:
|
|
443
|
+
parentTaskId: taskIdSchema,
|
|
444
|
+
worker: ethAddress,
|
|
445
|
+
payment: ethAmount,
|
|
446
|
+
deadline: unixDeadline,
|
|
447
|
+
descriptionHash: ipfsCid,
|
|
388
448
|
},
|
|
389
449
|
}, async ({ parentTaskId, worker, payment, deadline, descriptionHash }) => {
|
|
390
450
|
try {
|
|
391
451
|
const account = getAccount();
|
|
392
452
|
if (!account)
|
|
393
|
-
return
|
|
453
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
|
|
394
454
|
const paymentWei = parseEther(payment);
|
|
395
|
-
|
|
455
|
+
// Calculate total value: payment + protocol fee (1%) + priority fee (default Medium=1%)
|
|
456
|
+
const PROTOCOL_FEE_BPS = 100n;
|
|
457
|
+
const PRIORITY_FEE_BPS = 100n;
|
|
458
|
+
const totalFeeBps = PROTOCOL_FEE_BPS + PRIORITY_FEE_BPS;
|
|
459
|
+
const feeAmount = paymentWei * totalFeeBps / 10000n;
|
|
460
|
+
const totalValue = paymentWei + feeAmount;
|
|
461
|
+
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "createSubtask", [BigInt(parentTaskId), worker, paymentWei, BigInt(deadline), descriptionHash], totalValue);
|
|
396
462
|
return formatTxResult(result);
|
|
397
463
|
}
|
|
398
464
|
catch (e) {
|
|
399
|
-
|
|
465
|
+
const p = parseContractError(e);
|
|
466
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
400
467
|
}
|
|
401
468
|
});
|
|
402
469
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -404,15 +471,21 @@ export function registerEscrowTools(server) {
|
|
|
404
471
|
// ──────────────────────────────────────────────────────────────
|
|
405
472
|
server.registerTool("corven_get_child_tasks", {
|
|
406
473
|
title: "Get Child Tasks",
|
|
407
|
-
description: "
|
|
408
|
-
|
|
474
|
+
description: "Returns the IDs of all child tasks (subtasks) under a parent task.\n" +
|
|
475
|
+
"USE WHEN: Tracking progress of decomposed tasks. Checking if all subtasks are complete before parent verification.\n" +
|
|
476
|
+
"REQUIRES: Nothing. Free read-only call.\n" +
|
|
477
|
+
"RETURNS: Parent task ID, child count, array of child task IDs.\n" +
|
|
478
|
+
"COMES AFTER: corven_create_subtask created child tasks.\n" +
|
|
479
|
+
"COMES BEFORE: Use corven_get_task on each child ID to check individual status.",
|
|
480
|
+
inputSchema: { parentTaskId: taskIdSchema },
|
|
409
481
|
}, async ({ parentTaskId }) => {
|
|
410
482
|
try {
|
|
411
483
|
const childIds = await readContract(CONTRACTS.TaskEscrow, ABI, "getChildTasks", [BigInt(parentTaskId)]);
|
|
412
484
|
return formatReadResult({ parentTaskId, childCount: childIds.length, childTaskIds: childIds }, `Child tasks of Task #${parentTaskId}`);
|
|
413
485
|
}
|
|
414
486
|
catch (e) {
|
|
415
|
-
|
|
487
|
+
const p = parseContractError(e);
|
|
488
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
416
489
|
}
|
|
417
490
|
});
|
|
418
491
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -420,9 +493,15 @@ export function registerEscrowTools(server) {
|
|
|
420
493
|
// ──────────────────────────────────────────────────────────────
|
|
421
494
|
server.registerTool("corven_submit_query", {
|
|
422
495
|
title: "Submit Task Query",
|
|
423
|
-
description: "
|
|
496
|
+
description: "Worker submits a clarifying question about a task during execution. The query is recorded on-chain.\n" +
|
|
497
|
+
"USE WHEN: Task specification is ambiguous. You need additional resources. You want to confirm feasibility before proceeding.\n" +
|
|
498
|
+
"REQUIRES: You must be the assigned worker. Task must be in Funded or InProgress status.\n" +
|
|
499
|
+
"RETURNS: Query ID, task ID, query type, submission confirmation.\n" +
|
|
500
|
+
"COMES AFTER: corven_create_task assigned you the task.\n" +
|
|
501
|
+
"COMES BEFORE: Client calls corven_respond_to_query. Then you continue work.\n" +
|
|
502
|
+
"NOTE: Query types: 0=Specification (unclear requirements), 1=Resource (need more time/data), 2=Feasibility (concern about viability).",
|
|
424
503
|
inputSchema: {
|
|
425
|
-
taskId:
|
|
504
|
+
taskId: taskIdSchema,
|
|
426
505
|
queryText: z.string().describe("The query text"),
|
|
427
506
|
queryType: z.number().describe("Query type: 0=Specification, 1=Resource, 2=Feasibility"),
|
|
428
507
|
},
|
|
@@ -430,12 +509,13 @@ export function registerEscrowTools(server) {
|
|
|
430
509
|
try {
|
|
431
510
|
const account = getAccount();
|
|
432
511
|
if (!account)
|
|
433
|
-
return
|
|
512
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
|
|
434
513
|
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "submitQuery", [BigInt(taskId), queryText, queryType]);
|
|
435
514
|
return formatTxResult(result);
|
|
436
515
|
}
|
|
437
516
|
catch (e) {
|
|
438
|
-
|
|
517
|
+
const p = parseContractError(e);
|
|
518
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
439
519
|
}
|
|
440
520
|
});
|
|
441
521
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -443,88 +523,83 @@ export function registerEscrowTools(server) {
|
|
|
443
523
|
// ──────────────────────────────────────────────────────────────
|
|
444
524
|
server.registerTool("corven_respond_to_query", {
|
|
445
525
|
title: "Respond to Query",
|
|
446
|
-
description: "
|
|
526
|
+
description: "Client responds to a worker's clarifying query about a task. Response is recorded on-chain.\n" +
|
|
527
|
+
"USE WHEN: A worker has submitted a query (corven_get_query shows pending queries). You want to provide clarification.\n" +
|
|
528
|
+
"REQUIRES: You must be the task client. A query must exist on the task.\n" +
|
|
529
|
+
"RETURNS: Response confirmation, task ID, response text recorded.\n" +
|
|
530
|
+
"COMES AFTER: corven_submit_query by the worker.\n" +
|
|
531
|
+
"COMES BEFORE: Worker continues task execution with your clarification.\n" +
|
|
532
|
+
"NOTE: Check query details first with corven_get_query to understand what the worker is asking.",
|
|
447
533
|
inputSchema: {
|
|
448
|
-
taskId:
|
|
534
|
+
taskId: taskIdSchema,
|
|
449
535
|
responseText: z.string().describe("The response text"),
|
|
450
536
|
},
|
|
451
537
|
}, async ({ taskId, responseText }) => {
|
|
452
538
|
try {
|
|
453
539
|
const account = getAccount();
|
|
454
540
|
if (!account)
|
|
455
|
-
return
|
|
541
|
+
return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
|
|
456
542
|
const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "respondToQuery", [BigInt(taskId), responseText]);
|
|
457
543
|
return formatTxResult(result);
|
|
458
544
|
}
|
|
459
545
|
catch (e) {
|
|
460
|
-
|
|
546
|
+
const p = parseContractError(e);
|
|
547
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
461
548
|
}
|
|
462
549
|
});
|
|
463
550
|
// ──────────────────────────────────────────────────────────────
|
|
464
|
-
// corven_get_query
|
|
551
|
+
// corven_get_query (includes count when no queryId provided)
|
|
465
552
|
// ──────────────────────────────────────────────────────────────
|
|
466
553
|
server.registerTool("corven_get_query", {
|
|
467
|
-
title: "Get Query
|
|
468
|
-
description: "
|
|
554
|
+
title: "Get Query",
|
|
555
|
+
description: "Reads query details by index, or returns the total query count if no index is provided.\n" +
|
|
556
|
+
"USE WHEN: Checking if a worker has submitted questions. Reading query text before responding. Counting outstanding queries.\n" +
|
|
557
|
+
"REQUIRES: Nothing. Free read-only call.\n" +
|
|
558
|
+
"RETURNS: Query text, type (Specification/Resource/Feasibility), response status — or query count if index omitted.\n" +
|
|
559
|
+
"COMES AFTER: corven_submit_query created a query.\n" +
|
|
560
|
+
"COMES BEFORE: corven_respond_to_query to provide an answer.",
|
|
469
561
|
inputSchema: {
|
|
470
|
-
taskId:
|
|
471
|
-
queryId: z.number().describe("Query index (0-based)"),
|
|
562
|
+
taskId: taskIdSchema,
|
|
563
|
+
queryId: z.number().optional().describe("Query index (0-based). Omit to get count."),
|
|
472
564
|
},
|
|
473
565
|
}, async ({ taskId, queryId }) => {
|
|
474
566
|
try {
|
|
567
|
+
if (queryId === undefined) {
|
|
568
|
+
const count = await readContract(CONTRACTS.TaskEscrow, ABI, "getQueryCount", [BigInt(taskId)]);
|
|
569
|
+
return formatReadResult({ taskId, queryCount: Number(count) }, `Query count for Task #${taskId}`);
|
|
570
|
+
}
|
|
475
571
|
const data = await readContract(CONTRACTS.TaskEscrow, ABI, "getQuery", [BigInt(taskId), BigInt(queryId)]);
|
|
476
572
|
return formatReadResult(data, `Query ${queryId} on Task #${taskId}`);
|
|
477
573
|
}
|
|
478
574
|
catch (e) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
});
|
|
482
|
-
// ──────────────────────────────────────────────────────────────
|
|
483
|
-
// corven_get_query_count
|
|
484
|
-
// ──────────────────────────────────────────────────────────────
|
|
485
|
-
server.registerTool("corven_get_query_count", {
|
|
486
|
-
title: "Get Query Count",
|
|
487
|
-
description: "Get the number of queries submitted on a task.",
|
|
488
|
-
inputSchema: { taskId: z.number().describe("Task ID") },
|
|
489
|
-
}, async ({ taskId }) => {
|
|
490
|
-
try {
|
|
491
|
-
const count = await readContract(CONTRACTS.TaskEscrow, ABI, "getQueryCount", [BigInt(taskId)]);
|
|
492
|
-
return formatReadResult({ taskId, queryCount: Number(count) }, `Queries on Task #${taskId}`);
|
|
493
|
-
}
|
|
494
|
-
catch (e) {
|
|
495
|
-
return formatError(e);
|
|
575
|
+
const p = parseContractError(e);
|
|
576
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
496
577
|
}
|
|
497
578
|
});
|
|
498
579
|
// ──────────────────────────────────────────────────────────────
|
|
499
|
-
//
|
|
580
|
+
// corven_get_tasks (consolidated: client, worker, or child tasks)
|
|
500
581
|
// ──────────────────────────────────────────────────────────────
|
|
501
|
-
server.registerTool("
|
|
502
|
-
title: "Get
|
|
503
|
-
description: "
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
})
|
|
514
|
-
// ──────────────────────────────────────────────────────────────
|
|
515
|
-
// corven_get_worker_tasks
|
|
516
|
-
// ──────────────────────────────────────────────────────────────
|
|
517
|
-
server.registerTool("corven_get_worker_tasks", {
|
|
518
|
-
title: "Get Worker Tasks",
|
|
519
|
-
description: "Get all task IDs where the given address is the worker.",
|
|
520
|
-
inputSchema: { worker: z.string().describe("Worker's Ethereum address") },
|
|
521
|
-
}, async ({ worker }) => {
|
|
582
|
+
server.registerTool("corven_get_tasks", {
|
|
583
|
+
title: "Get Tasks",
|
|
584
|
+
description: "Returns all task IDs where the given address is the client or the worker.\n" +
|
|
585
|
+
"USE WHEN: Finding your active tasks. Checking which tasks you need to work on. Building a task dashboard.\n" +
|
|
586
|
+
"REQUIRES: Nothing. Free read-only call.\n" +
|
|
587
|
+
"RETURNS: Address, role, task count, array of task IDs.\n" +
|
|
588
|
+
"COMES BEFORE: Use corven_get_task on each returned ID to get full details.\n" +
|
|
589
|
+
"NOTE: Use role='worker' to find tasks assigned to you. Use role='client' to find tasks you posted.",
|
|
590
|
+
inputSchema: {
|
|
591
|
+
address: ethAddress,
|
|
592
|
+
role: z.enum(["client", "worker"]).describe("Filter: 'client' or 'worker'"),
|
|
593
|
+
},
|
|
594
|
+
}, async ({ address, role }) => {
|
|
522
595
|
try {
|
|
523
|
-
const
|
|
524
|
-
|
|
596
|
+
const fn = role === "client" ? "getClientTasks" : "getWorkerTasks";
|
|
597
|
+
const taskIds = await readContract(CONTRACTS.TaskEscrow, ABI, fn, [address]);
|
|
598
|
+
return formatReadResult({ address, role, taskCount: taskIds.length, taskIds }, `Tasks where ${address} is ${role}`);
|
|
525
599
|
}
|
|
526
600
|
catch (e) {
|
|
527
|
-
|
|
601
|
+
const p = parseContractError(e);
|
|
602
|
+
return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
|
|
528
603
|
}
|
|
529
604
|
});
|
|
530
605
|
}
|