@varun-ai07/covenant-mcp 1.2.3 → 1.3.2

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 (159) hide show
  1. package/README.md +1056 -170
  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/lib/did.d.ts +72 -0
  15. package/dist/lib/did.d.ts.map +1 -0
  16. package/dist/lib/did.js +115 -0
  17. package/dist/lib/did.js.map +1 -0
  18. package/dist/lib/formatResponse.d.ts +33 -0
  19. package/dist/lib/formatResponse.d.ts.map +1 -0
  20. package/dist/lib/formatResponse.js +92 -0
  21. package/dist/lib/formatResponse.js.map +1 -0
  22. package/dist/lib/schemaHelpers.d.ts +11 -0
  23. package/dist/lib/schemaHelpers.d.ts.map +1 -0
  24. package/dist/lib/schemaHelpers.js +11 -0
  25. package/dist/lib/schemaHelpers.js.map +1 -0
  26. package/dist/lib/store.d.ts +10 -0
  27. package/dist/lib/store.d.ts.map +1 -0
  28. package/dist/lib/store.js +39 -0
  29. package/dist/lib/store.js.map +1 -0
  30. package/dist/lib/verify.d.ts +21 -0
  31. package/dist/lib/verify.d.ts.map +1 -0
  32. package/dist/lib/verify.js +568 -0
  33. package/dist/lib/verify.js.map +1 -0
  34. package/dist/schemas.d.ts +5 -5
  35. package/dist/schemas.d.ts.map +1 -1
  36. package/dist/server.d.ts +1 -25
  37. package/dist/server.d.ts.map +1 -1
  38. package/dist/server.js +53 -37
  39. package/dist/server.js.map +1 -1
  40. package/dist/shared-types.d.ts +67 -0
  41. package/dist/shared-types.d.ts.map +1 -0
  42. package/dist/shared-types.js +86 -0
  43. package/dist/shared-types.js.map +1 -0
  44. package/dist/tools/account-abstraction.d.ts +3 -0
  45. package/dist/tools/account-abstraction.d.ts.map +1 -0
  46. package/dist/tools/account-abstraction.js +364 -0
  47. package/dist/tools/account-abstraction.js.map +1 -0
  48. package/dist/tools/batches.d.ts.map +1 -1
  49. package/dist/tools/batches.js +68 -37
  50. package/dist/tools/batches.js.map +1 -1
  51. package/dist/tools/bounties.d.ts +3 -0
  52. package/dist/tools/bounties.d.ts.map +1 -0
  53. package/dist/tools/bounties.js +304 -0
  54. package/dist/tools/bounties.js.map +1 -0
  55. package/dist/tools/bridge.d.ts +3 -0
  56. package/dist/tools/bridge.d.ts.map +1 -0
  57. package/dist/tools/bridge.js +190 -0
  58. package/dist/tools/bridge.js.map +1 -0
  59. package/dist/tools/collectives.d.ts.map +1 -1
  60. package/dist/tools/collectives.js +74 -46
  61. package/dist/tools/collectives.js.map +1 -1
  62. package/dist/tools/covenant-help.d.ts +3 -0
  63. package/dist/tools/covenant-help.d.ts.map +1 -0
  64. package/dist/tools/covenant-help.js +321 -0
  65. package/dist/tools/covenant-help.js.map +1 -0
  66. package/dist/tools/cross-chain.d.ts +3 -0
  67. package/dist/tools/cross-chain.d.ts.map +1 -0
  68. package/dist/tools/cross-chain.js +77 -0
  69. package/dist/tools/cross-chain.js.map +1 -0
  70. package/dist/tools/disputes.d.ts.map +1 -1
  71. package/dist/tools/disputes.js +39 -33
  72. package/dist/tools/disputes.js.map +1 -1
  73. package/dist/tools/escrow.d.ts.map +1 -1
  74. package/dist/tools/escrow.js +248 -199
  75. package/dist/tools/escrow.js.map +1 -1
  76. package/dist/tools/fiat-onramp.d.ts +3 -0
  77. package/dist/tools/fiat-onramp.d.ts.map +1 -0
  78. package/dist/tools/fiat-onramp.js +108 -0
  79. package/dist/tools/fiat-onramp.js.map +1 -0
  80. package/dist/tools/governance.d.ts +3 -0
  81. package/dist/tools/governance.d.ts.map +1 -0
  82. package/dist/tools/governance.js +271 -0
  83. package/dist/tools/governance.js.map +1 -0
  84. package/dist/tools/grants.d.ts +3 -0
  85. package/dist/tools/grants.d.ts.map +1 -0
  86. package/dist/tools/grants.js +269 -0
  87. package/dist/tools/grants.js.map +1 -0
  88. package/dist/tools/insurance.d.ts.map +1 -1
  89. package/dist/tools/insurance.js +92 -45
  90. package/dist/tools/insurance.js.map +1 -1
  91. package/dist/tools/market.d.ts.map +1 -1
  92. package/dist/tools/market.js +122 -103
  93. package/dist/tools/market.js.map +1 -1
  94. package/dist/tools/matching.d.ts +3 -0
  95. package/dist/tools/matching.d.ts.map +1 -0
  96. package/dist/tools/matching.js +233 -0
  97. package/dist/tools/matching.js.map +1 -0
  98. package/dist/tools/messaging.d.ts +3 -0
  99. package/dist/tools/messaging.d.ts.map +1 -0
  100. package/dist/tools/messaging.js +159 -0
  101. package/dist/tools/messaging.js.map +1 -0
  102. package/dist/tools/multi-token.d.ts +3 -0
  103. package/dist/tools/multi-token.d.ts.map +1 -0
  104. package/dist/tools/multi-token.js +274 -0
  105. package/dist/tools/multi-token.js.map +1 -0
  106. package/dist/tools/offchain-coordinator.d.ts +3 -0
  107. package/dist/tools/offchain-coordinator.d.ts.map +1 -0
  108. package/dist/tools/offchain-coordinator.js +436 -0
  109. package/dist/tools/offchain-coordinator.js.map +1 -0
  110. package/dist/tools/protocol.d.ts.map +1 -1
  111. package/dist/tools/protocol.js +19 -6
  112. package/dist/tools/protocol.js.map +1 -1
  113. package/dist/tools/receipts.d.ts.map +1 -1
  114. package/dist/tools/receipts.js +39 -39
  115. package/dist/tools/receipts.js.map +1 -1
  116. package/dist/tools/registry.d.ts.map +1 -1
  117. package/dist/tools/registry.js +90 -43
  118. package/dist/tools/registry.js.map +1 -1
  119. package/dist/tools/reputation-vc.d.ts +3 -0
  120. package/dist/tools/reputation-vc.d.ts.map +1 -0
  121. package/dist/tools/reputation-vc.js +438 -0
  122. package/dist/tools/reputation-vc.js.map +1 -0
  123. package/dist/tools/revisions.d.ts +3 -0
  124. package/dist/tools/revisions.d.ts.map +1 -0
  125. package/dist/tools/revisions.js +108 -0
  126. package/dist/tools/revisions.js.map +1 -0
  127. package/dist/tools/router.d.ts +3 -0
  128. package/dist/tools/router.d.ts.map +1 -0
  129. package/dist/tools/router.js +104 -0
  130. package/dist/tools/router.js.map +1 -0
  131. package/dist/tools/streaming.d.ts +3 -0
  132. package/dist/tools/streaming.d.ts.map +1 -0
  133. package/dist/tools/streaming.js +350 -0
  134. package/dist/tools/streaming.js.map +1 -0
  135. package/dist/tools/templates.d.ts +3 -0
  136. package/dist/tools/templates.d.ts.map +1 -0
  137. package/dist/tools/templates.js +392 -0
  138. package/dist/tools/templates.js.map +1 -0
  139. package/dist/tools/training.d.ts +3 -0
  140. package/dist/tools/training.d.ts.map +1 -0
  141. package/dist/tools/training.js +304 -0
  142. package/dist/tools/training.js.map +1 -0
  143. package/dist/tools/v2-settlement.d.ts +3 -0
  144. package/dist/tools/v2-settlement.d.ts.map +1 -0
  145. package/dist/tools/v2-settlement.js +226 -0
  146. package/dist/tools/v2-settlement.js.map +1 -0
  147. package/dist/tools/verification.d.ts +3 -0
  148. package/dist/tools/verification.d.ts.map +1 -0
  149. package/dist/tools/verification.js +215 -0
  150. package/dist/tools/verification.js.map +1 -0
  151. package/dist/tools/verify-deep.d.ts +3 -0
  152. package/dist/tools/verify-deep.d.ts.map +1 -0
  153. package/dist/tools/verify-deep.js +125 -0
  154. package/dist/tools/verify-deep.js.map +1 -0
  155. package/dist/types.d.ts +16 -3
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js +4 -23
  158. package/dist/types.js.map +1 -1
  159. package/package.json +2 -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,39 +55,29 @@ 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;
@@ -96,18 +88,33 @@ export function registerEscrowTools(server) {
96
88
  const totalFeeBps = PROTOCOL_FEE_BPS + priorityFeeBps;
97
89
  const feeAmount = paymentWei * totalFeeBps / 10000n;
98
90
  const totalValue = paymentWei + feeAmount;
99
- // Use createAndFundTask which is the combined function used by client.ts
100
91
  const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "createAndFundTask", [
101
- validatedWorker,
92
+ worker,
102
93
  paymentWei,
103
- BigInt(validatedDeadline),
104
- validatedDescriptionHash,
105
- ], totalValue // send payment + fees as msg.value
106
- );
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
+ }
107
113
  return formatTxResult(result);
108
114
  }
109
115
  catch (e) {
110
- return formatError(e);
116
+ const parsed = parseContractError(e);
117
+ return formatStructuredError(parsed.error, parsed.cause, parsed.fix, parsed.retryable);
111
118
  }
112
119
  });
113
120
  // ──────────────────────────────────────────────────────────────
@@ -115,23 +122,22 @@ export function registerEscrowTools(server) {
115
122
  // ──────────────────────────────────────────────────────────────
116
123
  server.registerTool("corven_get_task", {
117
124
  title: "Get Task Details",
118
- description: "Retrieve full on-chain details for a task by its numeric ID. " +
119
- "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.",
120
130
  inputSchema: {
121
- taskId: z.number().describe("Numeric task ID"),
131
+ taskId: taskIdSchema,
122
132
  },
123
133
  }, async ({ taskId }) => {
124
134
  try {
125
- // Validate input
126
- const taskIdParam = taskId;
127
- const validationResult = getTaskSchema.safeParse({ taskId: taskIdParam });
135
+ const validationResult = getTaskSchema.safeParse({ taskId });
128
136
  if (!validationResult.success) {
129
- 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);
130
138
  }
131
- // Use validated taskId
132
139
  const validatedTaskId = validationResult.data.taskId;
133
140
  const data = await readContract(CONTRACTS.TaskEscrow, ABI, "getTask", [BigInt(validatedTaskId)]);
134
- // Enrich status and priority with human-readable labels
135
141
  const enriched = {
136
142
  ...data,
137
143
  statusLabel: TASK_STATUS[data.status] ?? `Unknown(${data.status})`,
@@ -140,7 +146,8 @@ export function registerEscrowTools(server) {
140
146
  return formatReadResult(enriched, `Task #${taskId}`);
141
147
  }
142
148
  catch (e) {
143
- return formatError(e);
149
+ const parsed = parseContractError(e);
150
+ return formatStructuredError(parsed.error, parsed.cause, parsed.fix, parsed.retryable);
144
151
  }
145
152
  });
146
153
  // ──────────────────────────────────────────────────────────────
@@ -148,33 +155,35 @@ export function registerEscrowTools(server) {
148
155
  // ──────────────────────────────────────────────────────────────
149
156
  server.registerTool("corven_submit_work", {
150
157
  title: "Submit Work Deliverable",
151
- description: "Worker submits a deliverable hash (typically IPFS CID) for a task. " +
152
- "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.",
153
164
  inputSchema: {
154
- taskId: z.number().describe("Numeric task ID"),
155
- deliverableHash: z
156
- .string()
157
- .describe("IPFS CID or hash of the deliverable"),
165
+ taskId: taskIdSchema,
166
+ deliverableHash: ipfsCid,
158
167
  },
159
168
  }, async ({ taskId, deliverableHash }) => {
160
169
  try {
161
- // Validate input
162
170
  const validationResult = submitWorkSchema.safeParse({ taskId, deliverableHash });
163
171
  if (!validationResult.success) {
164
- 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);
165
173
  }
166
- // Use validated values
167
- const validatedTaskId = validationResult.data.taskId;
168
- const validatedDeliverableHash = validationResult.data.deliverableHash;
169
174
  const account = getAccount();
170
175
  if (!account) {
171
- 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."]);
172
181
  }
173
- const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "submitWork", [BigInt(validatedTaskId), validatedDeliverableHash]);
174
182
  return formatTxResult(result);
175
183
  }
176
184
  catch (e) {
177
- return formatError(e);
185
+ const parsed = parseContractError(e);
186
+ return formatStructuredError(parsed.error, parsed.cause, parsed.fix, parsed.retryable);
178
187
  }
179
188
  });
180
189
  // ──────────────────────────────────────────────────────────────
@@ -182,30 +191,39 @@ export function registerEscrowTools(server) {
182
191
  // ──────────────────────────────────────────────────────────────
183
192
  server.registerTool("corven_verify_task", {
184
193
  title: "Verify & Approve Task",
185
- description: "Client verifies a submitted task and releases payment to the worker. " +
186
- "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.",
187
200
  inputSchema: {
188
- taskId: z.number().describe("Numeric task ID"),
189
- 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"),
190
203
  },
191
204
  }, async ({ taskId, success }) => {
192
205
  try {
193
- // Validate input
194
206
  const validationResult = verifyTaskSchema.safeParse({ taskId, success });
195
207
  if (!validationResult.success) {
196
- 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);
197
209
  }
198
- // Use validated values
199
- const { taskId: validatedTaskId, success: validatedSuccess } = validationResult.data;
200
210
  const account = getAccount();
201
211
  if (!account) {
202
- 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."]);
203
221
  }
204
- const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "verifyTask", [BigInt(validatedTaskId), validatedSuccess], undefined, "TaskEscrow");
205
222
  return formatTxResult(result);
206
223
  }
207
224
  catch (e) {
208
- return formatError(e);
225
+ const parsed = parseContractError(e);
226
+ return formatStructuredError(parsed.error, parsed.cause, parsed.fix, parsed.retryable);
209
227
  }
210
228
  });
211
229
  // ──────────────────────────────────────────────────────────────
@@ -213,34 +231,30 @@ export function registerEscrowTools(server) {
213
231
  // ──────────────────────────────────────────────────────────────
214
232
  server.registerTool("corven_dispute_task", {
215
233
  title: "Dispute a Task",
216
- description: "Open a dispute on a task. Either the client or worker can dispute. " +
217
- "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.",
218
239
  inputSchema: {
219
- taskId: z.number().describe("Numeric task ID"),
220
- reason: z
221
- .string()
222
- .optional()
223
- .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"),
224
242
  },
225
243
  }, async ({ taskId, reason }) => {
226
244
  try {
227
- // Validate input
228
- const validationResult = disputeTaskSchema.safeParse({ taskId, reason });
229
- if (!validationResult.success) {
230
- return formatError(new Error(`Invalid input: ${validationResult.error.issues.map((e) => e.message).join(", ")}`));
231
- }
232
- // Use validated values
233
- const validatedTaskId = validationResult.data.taskId;
234
- const validatedReason = validationResult.data.reason;
235
245
  const account = getAccount();
236
246
  if (!account) {
237
- 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."]);
238
252
  }
239
- const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "disputeTask", [BigInt(validatedTaskId)], undefined, "TaskEscrow");
240
253
  return formatTxResult(result);
241
254
  }
242
255
  catch (e) {
243
- return formatError(e);
256
+ const parsed = parseContractError(e);
257
+ return formatStructuredError(parsed.error, parsed.cause, parsed.fix, parsed.retryable);
244
258
  }
245
259
  });
246
260
  // ──────────────────────────────────────────────────────────────
@@ -248,20 +262,25 @@ export function registerEscrowTools(server) {
248
262
  // ──────────────────────────────────────────────────────────────
249
263
  server.registerTool("corven_create_task_with_priority", {
250
264
  title: "Create Task with Priority",
251
- description: "Create a task with a specific priority level (0=Low, 1=Medium, 2=High, 3=Urgent). " +
252
- "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.",
253
272
  inputSchema: {
254
- worker: z.string().describe("Worker agent's Ethereum address"),
255
- payment: z.string().describe("Payment amount in ETH"),
256
- deadline: z.number().describe("Unix timestamp deadline (seconds)"),
257
- descriptionHash: z.string().describe("IPFS CID for task description"),
258
- 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,
259
278
  },
260
279
  }, async ({ worker, payment, deadline, descriptionHash, priority }) => {
261
280
  try {
262
281
  const account = getAccount();
263
282
  if (!account)
264
- 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);
265
284
  const paymentWei = parseEther(payment);
266
285
  // Calculate total value: payment + protocol fee (1%) + priority fee
267
286
  const PROTOCOL_FEE_BPS = 100n;
@@ -274,7 +293,8 @@ export function registerEscrowTools(server) {
274
293
  return formatTxResult(result);
275
294
  }
276
295
  catch (e) {
277
- return formatError(e);
296
+ const p = parseContractError(e);
297
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
278
298
  }
279
299
  });
280
300
  // ──────────────────────────────────────────────────────────────
@@ -282,13 +302,18 @@ export function registerEscrowTools(server) {
282
302
  // ──────────────────────────────────────────────────────────────
283
303
  server.registerTool("corven_create_milestone_task", {
284
304
  title: "Create Milestone Task",
285
- description: "Create a task with milestone-based payments. Each milestone has its own description and payment amount. " +
286
- "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, ...).",
287
312
  inputSchema: {
288
- worker: z.string().describe("Worker agent's Ethereum address"),
289
- totalPayment: z.string().describe("Total payment in ETH (sum of milestones)"),
290
- deadline: z.number().describe("Unix timestamp deadline"),
291
- descriptionHash: z.string().describe("IPFS CID for task description"),
313
+ worker: ethAddress,
314
+ totalPayment: ethAmount,
315
+ deadline: unixDeadline,
316
+ descriptionHash: ipfsCid,
292
317
  milestoneDescriptions: z.array(z.string()).describe("Array of milestone descriptions"),
293
318
  milestonePayments: z.array(z.string()).describe("Array of milestone payments in ETH"),
294
319
  },
@@ -296,7 +321,7 @@ export function registerEscrowTools(server) {
296
321
  try {
297
322
  const account = getAccount();
298
323
  if (!account)
299
- 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);
300
325
  const paymentWei = parseEther(totalPayment);
301
326
  const payments = milestonePayments.map(p => parseEther(p));
302
327
  // Calculate total value: payment + protocol fee (1%) + priority fee (default Medium=1%)
@@ -309,7 +334,8 @@ export function registerEscrowTools(server) {
309
334
  return formatTxResult(result);
310
335
  }
311
336
  catch (e) {
312
- return formatError(e);
337
+ const p = parseContractError(e);
338
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
313
339
  }
314
340
  });
315
341
  // ──────────────────────────────────────────────────────────────
@@ -317,22 +343,29 @@ export function registerEscrowTools(server) {
317
343
  // ──────────────────────────────────────────────────────────────
318
344
  server.registerTool("corven_submit_milestone", {
319
345
  title: "Submit Milestone",
320
- 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.",
321
353
  inputSchema: {
322
- taskId: z.number().describe("Task ID"),
354
+ taskId: taskIdSchema,
323
355
  milestoneIndex: z.number().describe("Milestone index (0-based)"),
324
- deliverableHash: z.string().describe("IPFS CID of milestone deliverable"),
356
+ deliverableHash: ipfsCid,
325
357
  },
326
358
  }, async ({ taskId, milestoneIndex, deliverableHash }) => {
327
359
  try {
328
360
  const account = getAccount();
329
361
  if (!account)
330
- 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);
331
363
  const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "submitMilestone", [BigInt(taskId), BigInt(milestoneIndex), deliverableHash]);
332
364
  return formatTxResult(result);
333
365
  }
334
366
  catch (e) {
335
- return formatError(e);
367
+ const p = parseContractError(e);
368
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
336
369
  }
337
370
  });
338
371
  // ──────────────────────────────────────────────────────────────
@@ -340,9 +373,15 @@ export function registerEscrowTools(server) {
340
373
  // ──────────────────────────────────────────────────────────────
341
374
  server.registerTool("corven_verify_milestone", {
342
375
  title: "Verify Milestone",
343
- 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.",
344
383
  inputSchema: {
345
- taskId: z.number().describe("Task ID"),
384
+ taskId: taskIdSchema,
346
385
  milestoneIndex: z.number().describe("Milestone index (0-based)"),
347
386
  success: z.boolean().describe("Whether the milestone passes verification"),
348
387
  },
@@ -350,47 +389,42 @@ export function registerEscrowTools(server) {
350
389
  try {
351
390
  const account = getAccount();
352
391
  if (!account)
353
- 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);
354
393
  const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "verifyMilestone", [BigInt(taskId), BigInt(milestoneIndex), success], undefined, "TaskEscrow");
355
394
  return formatTxResult(result);
356
395
  }
357
396
  catch (e) {
358
- return formatError(e);
397
+ const p = parseContractError(e);
398
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
359
399
  }
360
400
  });
361
401
  // ──────────────────────────────────────────────────────────────
362
- // corven_get_milestone
402
+ // corven_get_milestone (includes count when no index provided)
363
403
  // ──────────────────────────────────────────────────────────────
364
404
  server.registerTool("corven_get_milestone", {
365
- title: "Get Milestone Details",
366
- 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.",
367
412
  inputSchema: {
368
- taskId: z.number().describe("Task ID"),
369
- 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."),
370
415
  },
371
416
  }, async ({ taskId, milestoneIndex }) => {
372
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
+ }
373
422
  const data = await readContract(CONTRACTS.TaskEscrow, ABI, "getMilestone", [BigInt(taskId), BigInt(milestoneIndex)]);
374
423
  return formatReadResult(data, `Milestone ${milestoneIndex} of Task #${taskId}`);
375
424
  }
376
425
  catch (e) {
377
- return formatError(e);
378
- }
379
- });
380
- // ──────────────────────────────────────────────────────────────
381
- // corven_get_milestone_count
382
- // ──────────────────────────────────────────────────────────────
383
- server.registerTool("corven_get_milestone_count", {
384
- title: "Get Milestone Count",
385
- description: "Get the number of milestones in a task.",
386
- inputSchema: { taskId: z.number().describe("Task ID") },
387
- }, async ({ taskId }) => {
388
- try {
389
- const count = await readContract(CONTRACTS.TaskEscrow, ABI, "getMilestoneCount", [BigInt(taskId)]);
390
- return formatReadResult({ taskId, milestoneCount: Number(count) }, `Milestones for Task #${taskId}`);
391
- }
392
- catch (e) {
393
- return formatError(e);
426
+ const p = parseContractError(e);
427
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
394
428
  }
395
429
  });
396
430
  // ──────────────────────────────────────────────────────────────
@@ -398,19 +432,25 @@ export function registerEscrowTools(server) {
398
432
  // ──────────────────────────────────────────────────────────────
399
433
  server.registerTool("corven_create_subtask", {
400
434
  title: "Create Subtask",
401
- 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.",
402
442
  inputSchema: {
403
- parentTaskId: z.number().describe("Parent task ID"),
404
- worker: z.string().describe("Worker address for the subtask"),
405
- payment: z.string().describe("Payment in ETH"),
406
- deadline: z.number().describe("Unix timestamp deadline"),
407
- descriptionHash: z.string().describe("IPFS CID for subtask description"),
443
+ parentTaskId: taskIdSchema,
444
+ worker: ethAddress,
445
+ payment: ethAmount,
446
+ deadline: unixDeadline,
447
+ descriptionHash: ipfsCid,
408
448
  },
409
449
  }, async ({ parentTaskId, worker, payment, deadline, descriptionHash }) => {
410
450
  try {
411
451
  const account = getAccount();
412
452
  if (!account)
413
- 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);
414
454
  const paymentWei = parseEther(payment);
415
455
  // Calculate total value: payment + protocol fee (1%) + priority fee (default Medium=1%)
416
456
  const PROTOCOL_FEE_BPS = 100n;
@@ -422,7 +462,8 @@ export function registerEscrowTools(server) {
422
462
  return formatTxResult(result);
423
463
  }
424
464
  catch (e) {
425
- return formatError(e);
465
+ const p = parseContractError(e);
466
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
426
467
  }
427
468
  });
428
469
  // ──────────────────────────────────────────────────────────────
@@ -430,15 +471,21 @@ export function registerEscrowTools(server) {
430
471
  // ──────────────────────────────────────────────────────────────
431
472
  server.registerTool("corven_get_child_tasks", {
432
473
  title: "Get Child Tasks",
433
- description: "Get the IDs of all child tasks under a parent task.",
434
- 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 },
435
481
  }, async ({ parentTaskId }) => {
436
482
  try {
437
483
  const childIds = await readContract(CONTRACTS.TaskEscrow, ABI, "getChildTasks", [BigInt(parentTaskId)]);
438
484
  return formatReadResult({ parentTaskId, childCount: childIds.length, childTaskIds: childIds }, `Child tasks of Task #${parentTaskId}`);
439
485
  }
440
486
  catch (e) {
441
- return formatError(e);
487
+ const p = parseContractError(e);
488
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
442
489
  }
443
490
  });
444
491
  // ──────────────────────────────────────────────────────────────
@@ -446,9 +493,15 @@ export function registerEscrowTools(server) {
446
493
  // ──────────────────────────────────────────────────────────────
447
494
  server.registerTool("corven_submit_query", {
448
495
  title: "Submit Task Query",
449
- 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).",
450
503
  inputSchema: {
451
- taskId: z.number().describe("Task ID"),
504
+ taskId: taskIdSchema,
452
505
  queryText: z.string().describe("The query text"),
453
506
  queryType: z.number().describe("Query type: 0=Specification, 1=Resource, 2=Feasibility"),
454
507
  },
@@ -456,12 +509,13 @@ export function registerEscrowTools(server) {
456
509
  try {
457
510
  const account = getAccount();
458
511
  if (!account)
459
- 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);
460
513
  const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "submitQuery", [BigInt(taskId), queryText, queryType]);
461
514
  return formatTxResult(result);
462
515
  }
463
516
  catch (e) {
464
- return formatError(e);
517
+ const p = parseContractError(e);
518
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
465
519
  }
466
520
  });
467
521
  // ──────────────────────────────────────────────────────────────
@@ -469,88 +523,83 @@ export function registerEscrowTools(server) {
469
523
  // ──────────────────────────────────────────────────────────────
470
524
  server.registerTool("corven_respond_to_query", {
471
525
  title: "Respond to Query",
472
- 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.",
473
533
  inputSchema: {
474
- taskId: z.number().describe("Task ID"),
534
+ taskId: taskIdSchema,
475
535
  responseText: z.string().describe("The response text"),
476
536
  },
477
537
  }, async ({ taskId, responseText }) => {
478
538
  try {
479
539
  const account = getAccount();
480
540
  if (!account)
481
- 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);
482
542
  const result = await executeOrPrepare(CONTRACTS.TaskEscrow, ABI, "respondToQuery", [BigInt(taskId), responseText]);
483
543
  return formatTxResult(result);
484
544
  }
485
545
  catch (e) {
486
- return formatError(e);
546
+ const p = parseContractError(e);
547
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
487
548
  }
488
549
  });
489
550
  // ──────────────────────────────────────────────────────────────
490
- // corven_get_query
551
+ // corven_get_query (includes count when no queryId provided)
491
552
  // ──────────────────────────────────────────────────────────────
492
553
  server.registerTool("corven_get_query", {
493
- title: "Get Query Details",
494
- 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.",
495
561
  inputSchema: {
496
- taskId: z.number().describe("Task ID"),
497
- 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."),
498
564
  },
499
565
  }, async ({ taskId, queryId }) => {
500
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
+ }
501
571
  const data = await readContract(CONTRACTS.TaskEscrow, ABI, "getQuery", [BigInt(taskId), BigInt(queryId)]);
502
572
  return formatReadResult(data, `Query ${queryId} on Task #${taskId}`);
503
573
  }
504
574
  catch (e) {
505
- return formatError(e);
506
- }
507
- });
508
- // ──────────────────────────────────────────────────────────────
509
- // corven_get_query_count
510
- // ──────────────────────────────────────────────────────────────
511
- server.registerTool("corven_get_query_count", {
512
- title: "Get Query Count",
513
- description: "Get the number of queries submitted on a task.",
514
- inputSchema: { taskId: z.number().describe("Task ID") },
515
- }, async ({ taskId }) => {
516
- try {
517
- const count = await readContract(CONTRACTS.TaskEscrow, ABI, "getQueryCount", [BigInt(taskId)]);
518
- return formatReadResult({ taskId, queryCount: Number(count) }, `Queries on Task #${taskId}`);
519
- }
520
- catch (e) {
521
- return formatError(e);
575
+ const p = parseContractError(e);
576
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
522
577
  }
523
578
  });
524
579
  // ──────────────────────────────────────────────────────────────
525
- // corven_get_client_tasks
580
+ // corven_get_tasks (consolidated: client, worker, or child tasks)
526
581
  // ──────────────────────────────────────────────────────────────
527
- server.registerTool("corven_get_client_tasks", {
528
- title: "Get Client Tasks",
529
- description: "Get all task IDs where the given address is the client.",
530
- inputSchema: { client: z.string().describe("Client's Ethereum address") },
531
- }, async ({ client }) => {
532
- try {
533
- const taskIds = await readContract(CONTRACTS.TaskEscrow, ABI, "getClientTasks", [client]);
534
- return formatReadResult({ client, taskCount: taskIds.length, taskIds }, `Tasks by client ${client}`);
535
- }
536
- catch (e) {
537
- return formatError(e);
538
- }
539
- });
540
- // ──────────────────────────────────────────────────────────────
541
- // corven_get_worker_tasks
542
- // ──────────────────────────────────────────────────────────────
543
- server.registerTool("corven_get_worker_tasks", {
544
- title: "Get Worker Tasks",
545
- description: "Get all task IDs where the given address is the worker.",
546
- inputSchema: { worker: z.string().describe("Worker's Ethereum address") },
547
- }, 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 }) => {
548
595
  try {
549
- const taskIds = await readContract(CONTRACTS.TaskEscrow, ABI, "getWorkerTasks", [worker]);
550
- 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}`);
551
599
  }
552
600
  catch (e) {
553
- return formatError(e);
601
+ const p = parseContractError(e);
602
+ return formatStructuredError(p.error, p.cause, p.fix, p.retryable);
554
603
  }
555
604
  });
556
605
  }