@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.
Files changed (165) hide show
  1. package/README.md +128 -12
  2. package/dist/abis/MultiTokenEscrow.json +836 -0
  3. package/dist/abis/v2/AgentRegistry.json +872 -0
  4. package/dist/abis/v2/DisputeResolution.json +493 -0
  5. package/dist/abis/v2/InsurancePool.json +645 -0
  6. package/dist/abis/v2/ReceiptVerifier.json +394 -0
  7. package/dist/abis/v2/RevisionManager.json +544 -0
  8. package/dist/abis/v2/TaskEscrow.json +1018 -0
  9. package/dist/cli.js +0 -0
  10. package/dist/config.d.ts +13 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +84 -25
  13. package/dist/config.js.map +1 -1
  14. package/dist/handlers/wallet.d.ts.map +1 -1
  15. package/dist/handlers/wallet.js +1 -1
  16. package/dist/handlers/wallet.js.map +1 -1
  17. package/dist/lib/did.d.ts +72 -0
  18. package/dist/lib/did.d.ts.map +1 -0
  19. package/dist/lib/did.js +115 -0
  20. package/dist/lib/did.js.map +1 -0
  21. package/dist/lib/events.d.ts.map +1 -1
  22. package/dist/lib/events.js +2 -2
  23. package/dist/lib/events.js.map +1 -1
  24. package/dist/lib/formatResponse.d.ts +33 -0
  25. package/dist/lib/formatResponse.d.ts.map +1 -0
  26. package/dist/lib/formatResponse.js +92 -0
  27. package/dist/lib/formatResponse.js.map +1 -0
  28. package/dist/lib/schemaHelpers.d.ts +11 -0
  29. package/dist/lib/schemaHelpers.d.ts.map +1 -0
  30. package/dist/lib/schemaHelpers.js +11 -0
  31. package/dist/lib/schemaHelpers.js.map +1 -0
  32. package/dist/lib/store.d.ts +10 -0
  33. package/dist/lib/store.d.ts.map +1 -0
  34. package/dist/lib/store.js +39 -0
  35. package/dist/lib/store.js.map +1 -0
  36. package/dist/lib/verify.d.ts +21 -0
  37. package/dist/lib/verify.d.ts.map +1 -0
  38. package/dist/lib/verify.js +568 -0
  39. package/dist/lib/verify.js.map +1 -0
  40. package/dist/schemas.d.ts +5 -5
  41. package/dist/schemas.d.ts.map +1 -1
  42. package/dist/server.d.ts +1 -25
  43. package/dist/server.d.ts.map +1 -1
  44. package/dist/server.js +53 -37
  45. package/dist/server.js.map +1 -1
  46. package/dist/tools/account-abstraction.d.ts +3 -0
  47. package/dist/tools/account-abstraction.d.ts.map +1 -0
  48. package/dist/tools/account-abstraction.js +364 -0
  49. package/dist/tools/account-abstraction.js.map +1 -0
  50. package/dist/tools/batches.d.ts.map +1 -1
  51. package/dist/tools/batches.js +68 -37
  52. package/dist/tools/batches.js.map +1 -1
  53. package/dist/tools/bounties.d.ts +3 -0
  54. package/dist/tools/bounties.d.ts.map +1 -0
  55. package/dist/tools/bounties.js +304 -0
  56. package/dist/tools/bounties.js.map +1 -0
  57. package/dist/tools/bridge.d.ts +3 -0
  58. package/dist/tools/bridge.d.ts.map +1 -0
  59. package/dist/tools/bridge.js +190 -0
  60. package/dist/tools/bridge.js.map +1 -0
  61. package/dist/tools/collectives.d.ts.map +1 -1
  62. package/dist/tools/collectives.js +75 -48
  63. package/dist/tools/collectives.js.map +1 -1
  64. package/dist/tools/covenant-help.d.ts +3 -0
  65. package/dist/tools/covenant-help.d.ts.map +1 -0
  66. package/dist/tools/covenant-help.js +321 -0
  67. package/dist/tools/covenant-help.js.map +1 -0
  68. package/dist/tools/cross-chain.d.ts +3 -0
  69. package/dist/tools/cross-chain.d.ts.map +1 -0
  70. package/dist/tools/cross-chain.js +77 -0
  71. package/dist/tools/cross-chain.js.map +1 -0
  72. package/dist/tools/disputes.d.ts.map +1 -1
  73. package/dist/tools/disputes.js +39 -33
  74. package/dist/tools/disputes.js.map +1 -1
  75. package/dist/tools/escrow.d.ts.map +1 -1
  76. package/dist/tools/escrow.js +277 -202
  77. package/dist/tools/escrow.js.map +1 -1
  78. package/dist/tools/fiat-onramp.d.ts +3 -0
  79. package/dist/tools/fiat-onramp.d.ts.map +1 -0
  80. package/dist/tools/fiat-onramp.js +108 -0
  81. package/dist/tools/fiat-onramp.js.map +1 -0
  82. package/dist/tools/governance.d.ts +3 -0
  83. package/dist/tools/governance.d.ts.map +1 -0
  84. package/dist/tools/governance.js +271 -0
  85. package/dist/tools/governance.js.map +1 -0
  86. package/dist/tools/grants.d.ts +3 -0
  87. package/dist/tools/grants.d.ts.map +1 -0
  88. package/dist/tools/grants.js +269 -0
  89. package/dist/tools/grants.js.map +1 -0
  90. package/dist/tools/insurance.d.ts.map +1 -1
  91. package/dist/tools/insurance.js +92 -45
  92. package/dist/tools/insurance.js.map +1 -1
  93. package/dist/tools/market.d.ts.map +1 -1
  94. package/dist/tools/market.js +122 -103
  95. package/dist/tools/market.js.map +1 -1
  96. package/dist/tools/matching.d.ts +3 -0
  97. package/dist/tools/matching.d.ts.map +1 -0
  98. package/dist/tools/matching.js +233 -0
  99. package/dist/tools/matching.js.map +1 -0
  100. package/dist/tools/messaging.d.ts +3 -0
  101. package/dist/tools/messaging.d.ts.map +1 -0
  102. package/dist/tools/messaging.js +159 -0
  103. package/dist/tools/messaging.js.map +1 -0
  104. package/dist/tools/multi-token.d.ts +3 -0
  105. package/dist/tools/multi-token.d.ts.map +1 -0
  106. package/dist/tools/multi-token.js +274 -0
  107. package/dist/tools/multi-token.js.map +1 -0
  108. package/dist/tools/offchain-coordinator.d.ts +3 -0
  109. package/dist/tools/offchain-coordinator.d.ts.map +1 -0
  110. package/dist/tools/offchain-coordinator.js +436 -0
  111. package/dist/tools/offchain-coordinator.js.map +1 -0
  112. package/dist/tools/protocol.d.ts.map +1 -1
  113. package/dist/tools/protocol.js +19 -6
  114. package/dist/tools/protocol.js.map +1 -1
  115. package/dist/tools/receipts.d.ts.map +1 -1
  116. package/dist/tools/receipts.js +45 -22
  117. package/dist/tools/receipts.js.map +1 -1
  118. package/dist/tools/registry.d.ts.map +1 -1
  119. package/dist/tools/registry.js +90 -43
  120. package/dist/tools/registry.js.map +1 -1
  121. package/dist/tools/reputation-vc.d.ts +3 -0
  122. package/dist/tools/reputation-vc.d.ts.map +1 -0
  123. package/dist/tools/reputation-vc.js +438 -0
  124. package/dist/tools/reputation-vc.js.map +1 -0
  125. package/dist/tools/revisions.d.ts +3 -0
  126. package/dist/tools/revisions.d.ts.map +1 -0
  127. package/dist/tools/revisions.js +108 -0
  128. package/dist/tools/revisions.js.map +1 -0
  129. package/dist/tools/router.d.ts +3 -0
  130. package/dist/tools/router.d.ts.map +1 -0
  131. package/dist/tools/router.js +104 -0
  132. package/dist/tools/router.js.map +1 -0
  133. package/dist/tools/streaming.d.ts +3 -0
  134. package/dist/tools/streaming.d.ts.map +1 -0
  135. package/dist/tools/streaming.js +350 -0
  136. package/dist/tools/streaming.js.map +1 -0
  137. package/dist/tools/templates.d.ts +3 -0
  138. package/dist/tools/templates.d.ts.map +1 -0
  139. package/dist/tools/templates.js +392 -0
  140. package/dist/tools/templates.js.map +1 -0
  141. package/dist/tools/training.d.ts +3 -0
  142. package/dist/tools/training.d.ts.map +1 -0
  143. package/dist/tools/training.js +304 -0
  144. package/dist/tools/training.js.map +1 -0
  145. package/dist/tools/v2-settlement.d.ts +3 -0
  146. package/dist/tools/v2-settlement.d.ts.map +1 -0
  147. package/dist/tools/v2-settlement.js +226 -0
  148. package/dist/tools/v2-settlement.js.map +1 -0
  149. package/dist/tools/verification.d.ts +3 -0
  150. package/dist/tools/verification.d.ts.map +1 -0
  151. package/dist/tools/verification.js +215 -0
  152. package/dist/tools/verification.js.map +1 -0
  153. package/dist/tools/verify-deep.d.ts +3 -0
  154. package/dist/tools/verify-deep.d.ts.map +1 -0
  155. package/dist/tools/verify-deep.js +125 -0
  156. package/dist/tools/verify-deep.js.map +1 -0
  157. package/dist/types.d.ts +16 -3
  158. package/dist/types.d.ts.map +1 -1
  159. package/dist/types.js +4 -23
  160. package/dist/types.js.map +1 -1
  161. package/dist/utils.d.ts +2 -3
  162. package/dist/utils.d.ts.map +1 -1
  163. package/dist/utils.js +8 -34
  164. package/dist/utils.js.map +1 -1
  165. package/package.json +3 -1
@@ -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, formatError } from "../handlers/transactions.js";
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: "Create a new task on TaskEscrow, assign a worker, fund it with ETH, " +
57
- "and set a deadline. The payment value is sent as msg.value. " +
58
- "descriptionHash is typically an IPFS CID pointing to task details.",
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: z.string().describe("Worker agent's Ethereum address"),
61
- payment: z.string().describe("Payment amount in ETH, e.g. '0.01'"),
62
- deadline: z
63
- .number()
64
- .describe("Unix timestamp deadline (seconds since epoch)"),
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 formatError(new Error(`Invalid input: ${validationResult.error.issues.map((e) => e.message).join(", ")}`));
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 formatError(new Error("No private key configured cannot send transactions"));
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
- // Use createAndFundTask which is the combined function used by client.ts
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
- validatedWorker,
92
+ worker,
95
93
  paymentWei,
96
- BigInt(validatedDeadline),
97
- validatedDescriptionHash,
98
- ], paymentWei // send ETH as msg.value
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
- return formatError(e);
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: "Retrieve full on-chain details for a task by its numeric ID. " +
112
- "Returns client, worker, payment, deadline, status (human-readable), hashes, and timestamps.",
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: z.number().describe("Numeric task ID"),
131
+ taskId: taskIdSchema,
115
132
  },
116
133
  }, async ({ taskId }) => {
117
134
  try {
118
- // Validate input
119
- const taskIdParam = taskId;
120
- const validationResult = getTaskSchema.safeParse({ taskId: taskIdParam });
135
+ const validationResult = getTaskSchema.safeParse({ taskId });
121
136
  if (!validationResult.success) {
122
- return formatError(new Error(`Invalid input: ${validationResult.error.issues.map((e) => e.message).join(", ")}`));
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
- return formatError(e);
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 a deliverable hash (typically IPFS CID) for a task. " +
145
- "Only the assigned worker can call this. Transitions task status to Submitted.",
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: z.number().describe("Numeric task ID"),
148
- deliverableHash: z
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 formatError(new Error(`Invalid input: ${validationResult.error.issues.map((e) => e.message).join(", ")}`));
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 formatError(new Error("No private key configured cannot send transactions"));
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
- return formatError(e);
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 verifies a submitted task and releases payment to the worker. " +
179
- "Only the task client can call this. Transitions status to Completed.",
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: z.number().describe("Numeric task ID"),
182
- success: z.boolean().describe("Whether the task passed verification (true = approved, false = rejected)"),
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 formatError(new Error(`Invalid input: ${validationResult.error.issues.map((e) => e.message).join(", ")}`));
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 formatError(new Error("No private key configured cannot send transactions"));
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
- return formatError(e);
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: "Open a dispute on a task. Either the client or worker can dispute. " +
210
- "Transitions status to Disputed and pauses payment release.",
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: z.number().describe("Numeric task ID"),
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 formatError(new Error("No private key configured cannot send transactions"));
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
- return formatError(e);
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: "Create a task with a specific priority level (0=Low, 1=Medium, 2=High, 3=Urgent). " +
245
- "Higher priority incurs additional protocol fees.",
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: z.string().describe("Worker agent's Ethereum address"),
248
- payment: z.string().describe("Payment amount in ETH"),
249
- deadline: z.number().describe("Unix timestamp deadline (seconds)"),
250
- descriptionHash: z.string().describe("IPFS CID for task description"),
251
- priority: z.number().describe("Priority level: 0=Low, 1=Medium, 2=High, 3=Urgent"),
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 formatError(new Error("No private key configured"));
283
+ return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
258
284
  const paymentWei = parseEther(payment);
259
- const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "createAndFundTaskWithPriority", [worker, paymentWei, BigInt(deadline), descriptionHash, priority], paymentWei);
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
- return formatError(e);
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: "Create a task with milestone-based payments. Each milestone has its own description and payment amount. " +
272
- "Total payment = sum of all milestone payments.",
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: z.string().describe("Worker agent's Ethereum address"),
275
- totalPayment: z.string().describe("Total payment in ETH (sum of milestones)"),
276
- deadline: z.number().describe("Unix timestamp deadline"),
277
- descriptionHash: z.string().describe("IPFS CID for task description"),
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 formatError(new Error("No private key configured"));
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
- const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "createTaskWithMilestones", [worker, paymentWei, BigInt(deadline), descriptionHash, milestoneDescriptions, payments], paymentWei);
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
- return formatError(e);
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: "Submit a deliverable for a specific milestone in a milestone-based task.",
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: z.number().describe("Task ID"),
354
+ taskId: taskIdSchema,
303
355
  milestoneIndex: z.number().describe("Milestone index (0-based)"),
304
- deliverableHash: z.string().describe("IPFS CID of milestone deliverable"),
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 formatError(new Error("No private key configured"));
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
- return formatError(e);
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: "Verify a submitted milestone and release its payment to the worker.",
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: z.number().describe("Task ID"),
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 formatError(new Error("No private key configured"));
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
- return formatError(e);
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 Details",
346
- description: "Retrieve details of a specific milestone in a task.",
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: z.number().describe("Task ID"),
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
- return formatError(e);
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: "Create a child task under a parent task. The subtask has its own worker and payment.",
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: z.number().describe("Parent task ID"),
384
- worker: z.string().describe("Worker address for the subtask"),
385
- payment: z.string().describe("Payment in ETH"),
386
- deadline: z.number().describe("Unix timestamp deadline"),
387
- descriptionHash: z.string().describe("IPFS CID for subtask description"),
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 formatError(new Error("No private key configured"));
453
+ return formatStructuredError("No private key configured.", "PRIVATE_KEY not set.", "Set PRIVATE_KEY in .env.", false);
394
454
  const paymentWei = parseEther(payment);
395
- const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "createSubtask", [BigInt(parentTaskId), worker, paymentWei, BigInt(deadline), descriptionHash], paymentWei);
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
- return formatError(e);
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: "Get the IDs of all child tasks under a parent task.",
408
- inputSchema: { parentTaskId: z.number().describe("Parent task ID") },
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
- return formatError(e);
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: "Submit a query about a task during execution. Allows workers to ask clarifying questions.",
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: z.number().describe("Task ID"),
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 formatError(new Error("No private key configured"));
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
- return formatError(e);
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: "Respond to a worker's query about a task. Only the task client can respond.",
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: z.number().describe("Task ID"),
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 formatError(new Error("No private key configured"));
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
- return formatError(e);
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 Details",
468
- description: "Retrieve details of a specific query on a task.",
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: z.number().describe("Task ID"),
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
- return formatError(e);
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
- // corven_get_client_tasks
580
+ // corven_get_tasks (consolidated: client, worker, or child tasks)
500
581
  // ──────────────────────────────────────────────────────────────
501
- server.registerTool("corven_get_client_tasks", {
502
- title: "Get Client Tasks",
503
- description: "Get all task IDs where the given address is the client.",
504
- inputSchema: { client: z.string().describe("Client's Ethereum address") },
505
- }, async ({ client }) => {
506
- try {
507
- const taskIds = await readContract(CONTRACTS.TaskEscrow, ABI, "getClientTasks", [client]);
508
- return formatReadResult({ client, taskCount: taskIds.length, taskIds }, `Tasks by client ${client}`);
509
- }
510
- catch (e) {
511
- return formatError(e);
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 taskIds = await readContract(CONTRACTS.TaskEscrow, ABI, "getWorkerTasks", [worker]);
524
- return formatReadResult({ worker, taskCount: taskIds.length, taskIds }, `Tasks by worker ${worker}`);
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
- return formatError(e);
601
+ const p = parseContractError(e);
602
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
528
603
  }
529
604
  });
530
605
  }