@bountyagents/bountyagents-task 2026.3.99 → 2026.3.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts DELETED
@@ -1,46 +0,0 @@
1
- import { json, KEY_PATH } from "./src/helper.js";
2
- import { registerPublisherTools } from "./src/publisher.js";
3
- import { registerWorkerTools } from "./src/worker.js";
4
- import { generatePrivateKey } from "viem/accounts";
5
- import * as fs from "fs";
6
-
7
- export default function register(api: any) {
8
- // api.registerCommand({
9
- // name: "upclaw",
10
- // description: "UpClaw Bounty Agents Task commands",
11
- // acceptsArgs: true,
12
- // handler: async (ctx: any) => {
13
- // const args = ctx.args?.trim() ?? "";
14
- // const tokens = args.split(/\s+/).filter(Boolean);
15
- // const action = (tokens[0] ?? "status").toLowerCase();
16
-
17
- // if (action === "init") {
18
- // // try {
19
- // // if (!fs.existsSync(KEY_PATH)) {
20
- // // const pk = generatePrivateKey();
21
- // // fs.writeFileSync(KEY_PATH, pk, "utf-8");
22
- // // return json({
23
- // // text: `Initialized new EVM private key and saved to ${KEY_PATH}`,
24
- // // });
25
- // // } else {
26
- // // return json({
27
- // // text: `EVM private key already exists at ${KEY_PATH}`,
28
- // // });
29
- // // }
30
- // // } catch (error: any) {
31
- // // return json({
32
- // // text: "Failed to initialize key:",
33
- // // error: error.message,
34
- // // });
35
- // // }
36
- // }
37
-
38
- // return json({
39
- // text: ["UpClaw Bounty Agents Task commands:", "", "/upclaw-task init"].join("\n"),
40
- // });
41
- // },
42
- // });
43
-
44
- registerPublisherTools(api);
45
- registerWorkerTools(api);
46
- }
package/src/escrow.ts DELETED
@@ -1,27 +0,0 @@
1
- import { Hex, encodePacked, getAddress, keccak256, stringToBytes } from 'viem';
2
-
3
- export const taskDepositKey = (taskId: string): Hex => keccak256(stringToBytes(taskId));
4
-
5
- export const buildSettleDataHash = (
6
- contractAddress: string,
7
- key: Hex,
8
- owner: string,
9
- token: string,
10
- worker: string,
11
- amount: bigint
12
- ): Hex => {
13
- return keccak256(
14
- encodePacked(
15
- ['string', 'address', 'bytes32', 'address', 'address', 'address', 'uint256'],
16
- [
17
- 'SETTLE',
18
- getAddress(contractAddress as `0x${string}`),
19
- key,
20
- getAddress(owner as `0x${string}`),
21
- getAddress(token as `0x${string}`),
22
- getAddress(worker as `0x${string}`),
23
- amount
24
- ]
25
- )
26
- );
27
- };
package/src/helper.ts DELETED
@@ -1,23 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as os from "os";
4
-
5
- export function json(data: unknown) {
6
- return {
7
- content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
8
- details: data,
9
- };
10
- }
11
-
12
- export const KEY_PATH = path.join(os.homedir(), ".bountyagents_key");
13
-
14
- export function getPrivateKey(): `0x${string}` {
15
- if (fs.existsSync(KEY_PATH)) {
16
- const key = fs.readFileSync(KEY_PATH, "utf-8").trim();
17
- if (key.startsWith("0x")) {
18
- return key as `0x${string}`;
19
- }
20
- }
21
- // Fallback to the default one if the file doesn't exist to not break existing functionality directly, but throw error if user wants to use init
22
- return "0x289f92dd30c36ff24b75b48623c70dea2b428dabac7a52c5ca810bcda64d861b";
23
- }
package/src/index.ts DELETED
@@ -1,431 +0,0 @@
1
- import { fetch } from "undici";
2
- import { z } from "zod";
3
- import { getAddress, Hex } from "viem";
4
- import { ResponseRecord, TaskRecord, responseRecordSchema, taskRecordSchema } from "./task-db-types.js";
5
- import { Signer } from "./signers.js";
6
- import {
7
- CancelTaskPayload,
8
- CreateTaskPayload,
9
- DecisionPayload,
10
- FundTaskPayload,
11
- SettleTaskPayload,
12
- SubmitResponsePayload,
13
- TaskQueryPayload,
14
- TaskResponsesQueryPayload,
15
- WorkerResponsesQueryPayload,
16
- cancelTaskPayloadSchema,
17
- createTaskPayloadSchema,
18
- decisionPayloadSchema,
19
- fundTaskPayloadSchema,
20
- settleTaskPayloadSchema,
21
- submitResponsePayloadSchema,
22
- taskQueryPayloadSchema,
23
- taskResponsesQueryPayloadSchema,
24
- workerResponsesQueryPayloadSchema,
25
- } from "./types.js";
26
- import {
27
- cancelTaskSignaturePayload,
28
- decisionSignaturePayload,
29
- responseSignaturePayload,
30
- taskFundSignaturePayload,
31
- taskResponsesQuerySignaturePayload,
32
- taskSettleSignaturePayload,
33
- taskSignaturePayload,
34
- workerResponsesQuerySignaturePayload,
35
- } from "./signing.js";
36
- import { parseTokenIdentifier } from "./token.js";
37
- import { buildSettleDataHash, taskDepositKey } from "./escrow.js";
38
-
39
- export interface BountyAgentsPluginOptions {
40
- serviceUrl: string;
41
- contractAddress: string;
42
- }
43
-
44
- export interface PluginTool {
45
- name: string;
46
- description: string;
47
- inputSchema: z.ZodTypeAny;
48
- execute: (input: unknown) => Promise<unknown>;
49
- }
50
-
51
- const normalizeUrl = (url: string) => url.replace(/\/$/, "");
52
-
53
- const taskResponseSchema = z.object({ task: taskRecordSchema });
54
- const submissionResponseSchema = z.object({ response: responseRecordSchema });
55
- const tasksListSchema = z.object({ tasks: z.array(taskRecordSchema) });
56
- const responsesListSchema = z.object({
57
- responses: z.array(responseRecordSchema),
58
- });
59
- const settleResponseSchema = z.object({
60
- task: taskRecordSchema,
61
- settlementSignature: z.string(),
62
- });
63
-
64
- abstract class BaseBountyPlugin {
65
- protected readonly baseUrl: string;
66
- protected readonly contractAddress: `0x${string}`;
67
- private readonly tools: PluginTool[] = [];
68
-
69
- constructor(
70
- protected readonly signer: Signer,
71
- options: BountyAgentsPluginOptions
72
- ) {
73
- this.baseUrl = normalizeUrl(options.serviceUrl);
74
- this.contractAddress = getAddress(options.contractAddress as `0x${string}`);
75
- }
76
-
77
- getTools(): PluginTool[] {
78
- return this.tools;
79
- }
80
-
81
- async executeTool(name: string, input: unknown): Promise<unknown> {
82
- const tool = this.tools.find((t) => t.name === name);
83
- if (!tool) {
84
- throw new Error(`Tool ${name} not found`);
85
- }
86
- return tool.execute(input);
87
- }
88
-
89
- protected registerTool<TSchema extends z.ZodTypeAny>(
90
- name: string,
91
- description: string,
92
- schema: TSchema,
93
- executor: (input: z.infer<TSchema>) => Promise<unknown>
94
- ) {
95
- this.tools.push({
96
- name,
97
- description,
98
- inputSchema: schema,
99
- execute: (rawInput: unknown) => executor(schema.parse(rawInput)),
100
- });
101
- }
102
-
103
- protected async request(
104
- path: string,
105
- body?: unknown,
106
- method: "GET" | "POST" = "POST"
107
- ): Promise<unknown> {
108
- const response = await fetch(`${this.baseUrl}${path}`, {
109
- method,
110
- headers: {
111
- "content-type": "application/json",
112
- },
113
- body: body ? JSON.stringify(body) : undefined,
114
- });
115
- const payload = await response.text();
116
- const data = payload ? JSON.parse(payload) : {};
117
- if (!response.ok) {
118
- const errorMessage =
119
- typeof data.message === "string" ? data.message : response.statusText;
120
- throw new Error(`Service error: ${errorMessage}`);
121
- }
122
- return data;
123
- }
124
-
125
- protected signPayload(payload: string): Promise<Hex> {
126
- return this.signer.signMessage(payload);
127
- }
128
-
129
- protected signDigest(digest: Hex): Promise<Hex> {
130
- return this.signer.signDigest(digest);
131
- }
132
-
133
- protected async fetchTask(taskId: string): Promise<TaskRecord> {
134
- const response = await this.request(`/tasks/${taskId}`, undefined, "GET");
135
- return taskResponseSchema.parse(response).task;
136
- }
137
-
138
- protected async fetchResponse(responseId: string): Promise<ResponseRecord> {
139
- const response = await this.request(
140
- `/responses/${responseId}`,
141
- undefined,
142
- "GET"
143
- );
144
- return submissionResponseSchema.parse(response).response;
145
- }
146
- }
147
-
148
- export class BountyAgentsPublisherPlugin extends BaseBountyPlugin {
149
- constructor(signer: Signer, options: BountyAgentsPluginOptions) {
150
- super(signer, options);
151
- this.registerTool(
152
- "bountyagents.publisher.task.create",
153
- "Create a draft bounty task on the remote service",
154
- createTaskPayloadSchema,
155
- (input) => this.createTask(input)
156
- );
157
- this.registerTool(
158
- "bountyagents.publisher.task.fund",
159
- "Attach price/token metadata after depositing escrow funds",
160
- fundTaskPayloadSchema,
161
- (input) => this.fundTask(input)
162
- );
163
- this.registerTool(
164
- "bountyagents.publisher.task.decision",
165
- "Approve or reject a response and cache the settlement signature",
166
- decisionPayloadSchema,
167
- (input) => this.decideOnResponse(input)
168
- );
169
- this.registerTool(
170
- "bountyagents.publisher.task.cancel",
171
- "Cancel an active task and retrieve the admin withdraw signature",
172
- cancelTaskPayloadSchema,
173
- (input) => this.cancelTask(input)
174
- );
175
- this.registerTool(
176
- "bountyagents.publisher.task.query",
177
- "Search tasks with keyword, publisher and price filters",
178
- taskQueryPayloadSchema,
179
- (input) => this.queryTasks(input)
180
- );
181
- this.registerTool(
182
- "bountyagents.publisher.task.response.query",
183
- "List responses for a task (owner signature required)",
184
- taskResponsesQueryPayloadSchema,
185
- (input) => this.queryTaskResponses(input)
186
- );
187
- }
188
-
189
- private async createTask(payload: CreateTaskPayload): Promise<TaskRecord> {
190
- const body = {
191
- ...payload,
192
- ownerAddress: this.signer.address,
193
- signature: await this.signPayload(taskSignaturePayload(payload)),
194
- };
195
- const response = await this.request("/tasks", body);
196
- return taskResponseSchema.parse(response).task;
197
- }
198
-
199
- private async fundTask(payload: FundTaskPayload): Promise<TaskRecord> {
200
- const body = {
201
- ...payload,
202
- ownerAddress: this.signer.address,
203
- signature: await this.signPayload(taskFundSignaturePayload(payload)),
204
- };
205
- const response = await this.request(`/tasks/${payload.taskId}/fund`, body);
206
- return taskResponseSchema.parse(response).task;
207
- }
208
-
209
- private async decideOnResponse(
210
- payload: DecisionPayload
211
- ): Promise<ResponseRecord> {
212
- const responseRecord = await this.fetchResponse(payload.responseId);
213
- const task = await this.fetchTask(responseRecord.task_id);
214
- if (!task.token || task.price === "0") {
215
- throw new Error("Task is not funded yet");
216
- }
217
-
218
- // Use the price from the task record for the settlement signature
219
- const taskPrice = task.price;
220
-
221
- if (payload.price !== taskPrice) {
222
- throw new Error(`Price mismatch: payload price ${payload.price} does not match task price ${taskPrice}`);
223
- }
224
- const workerAddress = getAddress(payload.workerAddress as `0x${string}`);
225
- if (workerAddress !== getAddress(responseRecord.worker as `0x${string}`)) {
226
- throw new Error("Worker address mismatch");
227
- }
228
-
229
- const settlementSignature =
230
- payload.status === "approved"
231
- ? await this.createSettlementSignature(
232
- responseRecord.task_id,
233
- workerAddress,
234
- taskPrice,
235
- task.token
236
- )
237
- : undefined;
238
-
239
- const body = {
240
- responseId: payload.responseId,
241
- workerAddress,
242
- price: payload.price,
243
- status: payload.status,
244
- settlementSignature,
245
- ownerAddress: this.signer.address,
246
- signature: await this.signPayload(
247
- decisionSignaturePayload({
248
- responseId: payload.responseId,
249
- workerAddress,
250
- price: payload.price,
251
- status: payload.status,
252
- settlementSignature,
253
- })
254
- ),
255
- };
256
- const response = await this.request(
257
- `/responses/${payload.responseId}/decision`,
258
- body
259
- );
260
- return submissionResponseSchema.parse(response).response;
261
- }
262
-
263
- private async cancelTask(payload: CancelTaskPayload): Promise<TaskRecord> {
264
- const body = {
265
- ...payload,
266
- ownerAddress: this.signer.address,
267
- signature: await this.signPayload(cancelTaskSignaturePayload(payload)),
268
- };
269
- const response = await this.request(
270
- `/tasks/${payload.taskId}/cancel`,
271
- body
272
- );
273
- return taskResponseSchema.parse(response).task;
274
- }
275
-
276
- private async queryTasks(payload: TaskQueryPayload): Promise<TaskRecord[]> {
277
- const canonicalFilter = (payload.filter ?? {}) as NonNullable<
278
- TaskQueryPayload["filter"]
279
- >;
280
- const pageSize = payload.pageSize ?? 50;
281
- const pageNum = payload.pageNum ?? 0;
282
- const body = {
283
- filter: canonicalFilter,
284
- sortBy: payload.sortBy ?? "created_at",
285
- pageSize,
286
- pageNum,
287
- };
288
- const response = await this.request("/tasks/query", body);
289
- return tasksListSchema.parse(response).tasks;
290
- }
291
-
292
- private async queryTaskResponses(
293
- payload: TaskResponsesQueryPayload
294
- ): Promise<ResponseRecord[]> {
295
- const pageSize = payload.pageSize ?? 50;
296
- const pageNum = payload.pageNum ?? 0;
297
- const canonicalPayload = {
298
- taskId: payload.taskId,
299
- workerAddress: payload.workerAddress,
300
- pageSize,
301
- pageNum,
302
- };
303
- const body = {
304
- ...canonicalPayload,
305
- ownerAddress: this.signer.address,
306
- signature: await this.signPayload(
307
- taskResponsesQuerySignaturePayload({
308
- ...canonicalPayload,
309
- ownerAddress: this.signer.address,
310
- })
311
- ),
312
- };
313
- const response = await this.request("/tasks/responses/query", body);
314
- return responsesListSchema.parse(response).responses;
315
- }
316
-
317
- private async createSettlementSignature(
318
- taskId: string,
319
- workerAddress: `0x${string}`,
320
- price: string,
321
- tokenIdentifier: string
322
- ): Promise<Hex> {
323
- const token = parseTokenIdentifier(tokenIdentifier);
324
- const depositKey = taskDepositKey(taskId);
325
- const digest = buildSettleDataHash(
326
- this.contractAddress,
327
- depositKey,
328
- this.signer.address,
329
- token.address,
330
- workerAddress,
331
- BigInt(price)
332
- );
333
- return this.signDigest(digest);
334
- }
335
- }
336
-
337
- export class BountyAgentsWorkerPlugin extends BaseBountyPlugin {
338
- constructor(signer: Signer, options: BountyAgentsPluginOptions) {
339
- super(signer, options);
340
- this.registerTool(
341
- "bountyagents.worker.task.respond",
342
- "Submit a response for an active task",
343
- submitResponsePayloadSchema,
344
- (input) => this.submitResponse(input)
345
- );
346
- this.registerTool(
347
- "bountyagents.worker.response.query",
348
- "List responses submitted by this worker",
349
- workerResponsesQueryPayloadSchema,
350
- (input) => this.queryWorkerResponses(input)
351
- );
352
- this.registerTool(
353
- "bountyagents.worker.task.query",
354
- "Search tasks with keyword, publisher and price filters",
355
- taskQueryPayloadSchema,
356
- (input) => this.queryTasks(input)
357
- );
358
- }
359
-
360
- private async queryTasks(payload: TaskQueryPayload): Promise<TaskRecord[]> {
361
- const canonicalFilter = (payload.filter ?? {}) as NonNullable<
362
- TaskQueryPayload["filter"]
363
- >;
364
- const pageSize = payload.pageSize ?? 50;
365
- const pageNum = payload.pageNum ?? 0;
366
- const body = {
367
- filter: canonicalFilter,
368
- sortBy: payload.sortBy ?? "created_at",
369
- pageSize,
370
- pageNum,
371
- };
372
- const response = await this.request("/tasks/query", body);
373
- return tasksListSchema.parse(response).tasks;
374
- }
375
-
376
- private async submitResponse(
377
- payload: SubmitResponsePayload
378
- ): Promise<ResponseRecord> {
379
- const body = {
380
- ...payload,
381
- workerAddress: this.signer.address,
382
- signature: await this.signPayload(responseSignaturePayload(payload)),
383
- };
384
- const response = await this.request(
385
- `/tasks/${payload.taskId}/responses`,
386
- body
387
- );
388
- return submissionResponseSchema.parse(response).response;
389
- }
390
-
391
- private async queryWorkerResponses(
392
- payload: WorkerResponsesQueryPayload
393
- ): Promise<ResponseRecord[]> {
394
- const pageSize = payload.pageSize ?? 50;
395
- const pageNum = payload.pageNum ?? 0;
396
- const canonicalPayload = {
397
- taskId: payload.taskId,
398
- pageSize,
399
- pageNum,
400
- };
401
- const body = {
402
- ...canonicalPayload,
403
- workerAddress: this.signer.address,
404
- signature: await this.signPayload(
405
- workerResponsesQuerySignaturePayload({
406
- ...canonicalPayload,
407
- workerAddress: this.signer.address,
408
- })
409
- ),
410
- };
411
- const response = await this.request("/workers/responses/query", body);
412
- return responsesListSchema.parse(response).responses;
413
- }
414
-
415
- private async settleTask(
416
- payload: SettleTaskPayload
417
- ): Promise<{ task: TaskRecord; settlementSignature: string }> {
418
- const canonicalPayload = { ...payload, workerAddress: this.signer.address };
419
- const body = {
420
- ...canonicalPayload,
421
- signature: await this.signPayload(
422
- taskSettleSignaturePayload(canonicalPayload)
423
- ),
424
- };
425
- const response = await this.request(
426
- `/tasks/${payload.taskId}/settle`,
427
- body
428
- );
429
- return settleResponseSchema.parse(response);
430
- }
431
- }