moltlaunch 2.0.0 → 2.0.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 (108) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +18 -18
  3. package/dist/index.js.map +1 -1
  4. package/package.json +6 -2
  5. package/.claude/commands/deploy.md +0 -33
  6. package/.claude/hooks/regenerate-docs.sh +0 -12
  7. package/.claude/settings.json +0 -15
  8. package/.env.example +0 -2
  9. package/.github/workflows/deploy.yml +0 -37
  10. package/ROADMAP.md +0 -29
  11. package/contracts/MandateEscrowV4.sol +0 -281
  12. package/contracts/mocks/MockFlaunchBuyback.sol +0 -24
  13. package/hardhat.config.cjs +0 -29
  14. package/scripts/check-deploy-cost.ts +0 -15
  15. package/scripts/deploy-escrow-v4.ts +0 -81
  16. package/scripts/deploy-escrow.cjs +0 -22
  17. package/scripts/generate-docs.ts +0 -309
  18. package/shared/manifest.json +0 -87
  19. package/site/.vscode/extensions.json +0 -4
  20. package/site/.vscode/launch.json +0 -11
  21. package/site/README.md +0 -43
  22. package/site/astro.config.mjs +0 -21
  23. package/site/functions/agent/[[path]].ts +0 -9
  24. package/site/functions/task/[[path]].ts +0 -9
  25. package/site/index.html.bak +0 -1755
  26. package/site/package-lock.json +0 -6165
  27. package/site/package.json +0 -17
  28. package/site/public/_redirects +0 -1
  29. package/site/public/art/hero.webp +0 -0
  30. package/site/public/favicon.ico +0 -0
  31. package/site/public/favicon.svg +0 -4
  32. package/site/public/logo.png +0 -0
  33. package/site/public/skill.md +0 -276
  34. package/site/src/components/AgentGridCard.astro +0 -97
  35. package/site/src/components/AgentRow.astro +0 -75
  36. package/site/src/components/Footer.astro +0 -71
  37. package/site/src/components/GigCard.astro +0 -36
  38. package/site/src/components/Navbar.astro +0 -93
  39. package/site/src/components/ReviewCard.astro +0 -29
  40. package/site/src/components/SkillPill.astro +0 -19
  41. package/site/src/components/StatusBadge.astro +0 -27
  42. package/site/src/components/TaskEntry.astro +0 -98
  43. package/site/src/layouts/Layout.astro +0 -268
  44. package/site/src/lib/api.ts +0 -342
  45. package/site/src/pages/404.astro +0 -33
  46. package/site/src/pages/admin.astro +0 -445
  47. package/site/src/pages/agent/[...id].astro +0 -678
  48. package/site/src/pages/agents/index.astro +0 -235
  49. package/site/src/pages/dashboard.astro +0 -244
  50. package/site/src/pages/docs.astro +0 -191
  51. package/site/src/pages/how.astro +0 -156
  52. package/site/src/pages/index.astro +0 -226
  53. package/site/src/pages/leaderboard.astro +0 -155
  54. package/site/src/pages/task/[...id].astro +0 -1467
  55. package/site/src/styles/global.css +0 -159
  56. package/site/tailwind.config.mjs +0 -94
  57. package/site/tsconfig.json +0 -5
  58. package/site/wrangler.toml +0 -5
  59. package/src/commands/accept.ts +0 -135
  60. package/src/commands/agents.ts +0 -190
  61. package/src/commands/approve.ts +0 -127
  62. package/src/commands/claim.ts +0 -130
  63. package/src/commands/decline.ts +0 -55
  64. package/src/commands/dispute.ts +0 -92
  65. package/src/commands/earnings.ts +0 -86
  66. package/src/commands/feedback.ts +0 -147
  67. package/src/commands/gig.ts +0 -141
  68. package/src/commands/hire.ts +0 -96
  69. package/src/commands/inbox.ts +0 -135
  70. package/src/commands/message.ts +0 -97
  71. package/src/commands/profile.ts +0 -62
  72. package/src/commands/quote.ts +0 -80
  73. package/src/commands/refund.ts +0 -82
  74. package/src/commands/register.ts +0 -250
  75. package/src/commands/resolve.ts +0 -104
  76. package/src/commands/reviews.ts +0 -78
  77. package/src/commands/revise.ts +0 -65
  78. package/src/commands/submit.ts +0 -123
  79. package/src/commands/tasks.ts +0 -224
  80. package/src/commands/view.ts +0 -122
  81. package/src/commands/wallet.ts +0 -42
  82. package/src/index.ts +0 -285
  83. package/src/lib/agent0.ts +0 -158
  84. package/src/lib/auth.ts +0 -25
  85. package/src/lib/constants.ts +0 -55
  86. package/src/lib/escrow.ts +0 -374
  87. package/src/lib/files.ts +0 -87
  88. package/src/lib/flaunch.ts +0 -277
  89. package/src/lib/mandate.ts +0 -623
  90. package/src/lib/tasks.ts +0 -466
  91. package/src/lib/types.ts +0 -112
  92. package/src/lib/wallet.ts +0 -119
  93. package/src/lib/x402.ts +0 -86
  94. package/test/MandateEscrowV4.test.cjs +0 -568
  95. package/tsconfig.json +0 -19
  96. package/tsup.config.ts +0 -15
  97. package/worker/package-lock.json +0 -1812
  98. package/worker/package.json +0 -18
  99. package/worker/src/agents.ts +0 -755
  100. package/worker/src/auth.ts +0 -126
  101. package/worker/src/files.ts +0 -40
  102. package/worker/src/index.ts +0 -963
  103. package/worker/src/profiles.ts +0 -85
  104. package/worker/src/ratelimit.ts +0 -45
  105. package/worker/src/tasks.ts +0 -498
  106. package/worker/src/types.ts +0 -95
  107. package/worker/tsconfig.json +0 -15
  108. package/worker/wrangler.toml +0 -19
@@ -1,85 +0,0 @@
1
- // Profile & Gig CRUD — KV-stored agent profiles and gig offerings
2
-
3
- import type { Env, AgentProfile, Gig } from './types';
4
-
5
- // --- Profile CRUD ---
6
-
7
- export async function getProfile(env: Env, agentId: string): Promise<AgentProfile | null> {
8
- const raw = await env.TASKS_KV.get(`profile:${agentId}`);
9
- if (!raw) return null;
10
- return JSON.parse(raw) as AgentProfile;
11
- }
12
-
13
- export async function updateProfile(
14
- env: Env,
15
- agentId: string,
16
- updates: Partial<Omit<AgentProfile, 'agentId' | 'updatedAt'>>,
17
- ): Promise<AgentProfile> {
18
- const existing = await getProfile(env, agentId);
19
- const profile: AgentProfile = {
20
- agentId,
21
- ...(existing || {}),
22
- ...updates,
23
- updatedAt: Date.now(),
24
- };
25
- await env.TASKS_KV.put(`profile:${agentId}`, JSON.stringify(profile));
26
- return profile;
27
- }
28
-
29
- // --- Gig CRUD ---
30
-
31
- export async function getGigs(env: Env, agentId: string): Promise<Gig[]> {
32
- const raw = await env.TASKS_KV.get(`gigs:${agentId}`);
33
- if (!raw) return [];
34
- const gigs = JSON.parse(raw) as Gig[];
35
- return gigs.filter((g) => g.active);
36
- }
37
-
38
- export async function getAllGigs(env: Env, agentId: string): Promise<Gig[]> {
39
- const raw = await env.TASKS_KV.get(`gigs:${agentId}`);
40
- if (!raw) return [];
41
- return JSON.parse(raw) as Gig[];
42
- }
43
-
44
- export async function createGig(
45
- env: Env,
46
- agentId: string,
47
- gig: Omit<Gig, 'id' | 'agentId' | 'active' | 'createdAt' | 'updatedAt'>,
48
- ): Promise<Gig> {
49
- const gigs = await getAllGigs(env, agentId);
50
- const newGig: Gig = {
51
- id: crypto.randomUUID(),
52
- agentId,
53
- ...gig,
54
- active: true,
55
- createdAt: Date.now(),
56
- updatedAt: Date.now(),
57
- };
58
- gigs.push(newGig);
59
- await env.TASKS_KV.put(`gigs:${agentId}`, JSON.stringify(gigs));
60
- return newGig;
61
- }
62
-
63
- export async function updateGig(
64
- env: Env,
65
- agentId: string,
66
- gigId: string,
67
- updates: Partial<Pick<Gig, 'title' | 'description' | 'priceWei' | 'deliveryTime' | 'category' | 'active'>>,
68
- ): Promise<Gig | null> {
69
- const gigs = await getAllGigs(env, agentId);
70
- const idx = gigs.findIndex((g) => g.id === gigId);
71
- if (idx === -1) return null;
72
- gigs[idx] = { ...gigs[idx], ...updates, updatedAt: Date.now() };
73
- await env.TASKS_KV.put(`gigs:${agentId}`, JSON.stringify(gigs));
74
- return gigs[idx];
75
- }
76
-
77
- export async function removeGig(env: Env, agentId: string, gigId: string): Promise<boolean> {
78
- const gigs = await getAllGigs(env, agentId);
79
- const idx = gigs.findIndex((g) => g.id === gigId);
80
- if (idx === -1) return false;
81
- gigs[idx].active = false;
82
- gigs[idx].updatedAt = Date.now();
83
- await env.TASKS_KV.put(`gigs:${agentId}`, JSON.stringify(gigs));
84
- return true;
85
- }
@@ -1,45 +0,0 @@
1
- // IP-based rate limiting using KV sliding window counters
2
- // Separate limits for reads (GET) and writes (POST)
3
-
4
- import type { Env } from './types';
5
-
6
- const READ_LIMIT = 60; // per minute
7
- const WRITE_LIMIT = 20; // per minute
8
- const WINDOW_SECONDS = 60;
9
-
10
- interface RateLimitResult {
11
- allowed: boolean;
12
- remaining: number;
13
- resetAt: number;
14
- }
15
-
16
- /** Check rate limit for an IP + method combination */
17
- export async function checkRateLimit(
18
- env: Env,
19
- ip: string,
20
- method: string,
21
- ): Promise<RateLimitResult> {
22
- const isWrite = method === 'POST' || method === 'PUT' || method === 'DELETE';
23
- const limit = isWrite ? WRITE_LIMIT : READ_LIMIT;
24
- const bucket = isWrite ? 'w' : 'r';
25
-
26
- // Key: ratelimit:<ip>:<bucket>:<window>
27
- const now = Math.floor(Date.now() / 1000);
28
- const window = Math.floor(now / WINDOW_SECONDS);
29
- const key = `ratelimit:${ip}:${bucket}:${window}`;
30
- const resetAt = (window + 1) * WINDOW_SECONDS;
31
-
32
- const raw = await env.TASKS_KV.get(key);
33
- const count = raw ? parseInt(raw, 10) : 0;
34
-
35
- if (count >= limit) {
36
- return { allowed: false, remaining: 0, resetAt };
37
- }
38
-
39
- // Increment counter with TTL matching window expiry
40
- await env.TASKS_KV.put(key, String(count + 1), {
41
- expirationTtl: WINDOW_SECONDS * 2,
42
- });
43
-
44
- return { allowed: true, remaining: limit - count - 1, resetAt };
45
- }
@@ -1,498 +0,0 @@
1
- // MANDATE Task Queue
2
- // Quote-based flow: request → quote → accept → submit → complete
3
-
4
- import type { Env, Task, TaskFile, TaskMessage, AgentStats } from './types';
5
-
6
- const TASK_PREFIX = 'task:';
7
- const AGENT_INBOX_PREFIX = 'inbox:';
8
- const CLIENT_TASKS_PREFIX = 'client:';
9
- const RECENT_TASKS_KEY = 'recent:tasks';
10
- const STATS_PREFIX = 'stats:';
11
- const RECENT_TASKS_MAX = 200;
12
- const TASK_TTL = 60 * 60 * 24 * 7; // 7 days
13
-
14
- interface RecentTaskEntry {
15
- id: string;
16
- agentId: string;
17
- status: Task['status'];
18
- createdAt: number;
19
- updatedAt: number;
20
- }
21
-
22
- /** Update the global recent tasks index */
23
- async function updateRecentIndex(env: Env, task: Task): Promise<void> {
24
- const raw = await env.TASKS_KV.get(RECENT_TASKS_KEY);
25
- const entries: RecentTaskEntry[] = raw ? JSON.parse(raw) : [];
26
-
27
- const existing = entries.findIndex((e) => e.id === task.id);
28
- const entry: RecentTaskEntry = {
29
- id: task.id,
30
- agentId: task.agentId,
31
- status: task.status,
32
- createdAt: task.createdAt,
33
- updatedAt: Date.now(),
34
- };
35
-
36
- if (existing >= 0) {
37
- entries.splice(existing, 1);
38
- }
39
- entries.unshift(entry);
40
- const trimmed = entries.slice(0, RECENT_TASKS_MAX);
41
-
42
- await env.TASKS_KV.put(RECENT_TASKS_KEY, JSON.stringify(trimmed), { expirationTtl: TASK_TTL });
43
- }
44
-
45
- /** Generate a unique task ID */
46
- function generateTaskId(): string {
47
- const timestamp = Date.now().toString(36);
48
- const random = Math.random().toString(36).slice(2, 8);
49
- return `${timestamp}-${random}`;
50
- }
51
-
52
- /** Get a task by ID */
53
- export async function getTask(env: Env, taskId: string): Promise<Task | null> {
54
- const data = await env.TASKS_KV.get(`${TASK_PREFIX}${taskId}`);
55
- if (!data) return null;
56
- return JSON.parse(data) as Task;
57
- }
58
-
59
- /** Save a task */
60
- async function saveTask(env: Env, task: Task): Promise<void> {
61
- await env.TASKS_KV.put(
62
- `${TASK_PREFIX}${task.id}`,
63
- JSON.stringify(task),
64
- { expirationTtl: TASK_TTL }
65
- );
66
- }
67
-
68
- /** Add task ID to an agent's inbox */
69
- async function addToInbox(env: Env, agentId: string, taskId: string): Promise<void> {
70
- const key = `${AGENT_INBOX_PREFIX}${agentId}`;
71
- const existing = await env.TASKS_KV.get(key);
72
- const inbox: string[] = existing ? JSON.parse(existing) : [];
73
-
74
- inbox.unshift(taskId);
75
- const trimmed = inbox.slice(0, 100);
76
-
77
- await env.TASKS_KV.put(key, JSON.stringify(trimmed), { expirationTtl: TASK_TTL });
78
- }
79
-
80
- /** Add task ID to a client's task list */
81
- async function addToClientTasks(env: Env, clientAddress: string, taskId: string): Promise<void> {
82
- const key = `${CLIENT_TASKS_PREFIX}${clientAddress.toLowerCase()}`;
83
- const existing = await env.TASKS_KV.get(key);
84
- const tasks: string[] = existing ? JSON.parse(existing) : [];
85
-
86
- tasks.unshift(taskId);
87
- const trimmed = tasks.slice(0, 100);
88
-
89
- await env.TASKS_KV.put(key, JSON.stringify(trimmed), { expirationTtl: TASK_TTL });
90
- }
91
-
92
- /** Get agent stats from KV */
93
- export async function getAgentStats(env: Env, agentId: string): Promise<AgentStats> {
94
- const raw = await env.TASKS_KV.get(`${STATS_PREFIX}${agentId}`);
95
- if (raw) {
96
- const stats = JSON.parse(raw) as AgentStats;
97
- // Backfill for stats created before rating fields existed
98
- if (stats.ratingSum === undefined) stats.ratingSum = 0;
99
- if (stats.ratingCount === undefined) stats.ratingCount = 0;
100
- return stats;
101
- }
102
- return {
103
- completedTasks: 0,
104
- totalEarningsWei: '0',
105
- declinedTasks: 0,
106
- totalResponseMs: 0,
107
- taskCount: 0,
108
- ratingSum: 0,
109
- ratingCount: 0,
110
- };
111
- }
112
-
113
- /** Save agent stats to KV */
114
- async function saveAgentStats(env: Env, agentId: string, stats: AgentStats): Promise<void> {
115
- await env.TASKS_KV.put(`${STATS_PREFIX}${agentId}`, JSON.stringify(stats));
116
- }
117
-
118
- /** Create a new task request (no price - agent will quote) */
119
- export async function createTask(
120
- env: Env,
121
- agentId: string,
122
- clientAddress: string,
123
- taskDescription: string,
124
- ): Promise<Task> {
125
- const task: Task = {
126
- id: generateTaskId(),
127
- agentId,
128
- clientAddress: clientAddress.toLowerCase(),
129
- task: taskDescription,
130
- status: 'requested',
131
- createdAt: Date.now(),
132
- };
133
-
134
- await saveTask(env, task);
135
- await addToInbox(env, agentId, task.id);
136
- await addToClientTasks(env, clientAddress, task.id);
137
- await updateRecentIndex(env, task);
138
-
139
- // Update stats: increment task count
140
- const stats = await getAgentStats(env, agentId);
141
- stats.taskCount++;
142
- await saveAgentStats(env, agentId, stats);
143
-
144
- return task;
145
- }
146
-
147
- /** Get tasks for an agent (requests + accepted work) */
148
- export async function getAgentInbox(env: Env, agentId: string): Promise<Task[]> {
149
- const key = `${AGENT_INBOX_PREFIX}${agentId}`;
150
- const inboxData = await env.TASKS_KV.get(key);
151
- if (!inboxData) return [];
152
-
153
- const taskIds: string[] = JSON.parse(inboxData);
154
- const tasks: Task[] = [];
155
-
156
- for (const id of taskIds) {
157
- const task = await getTask(env, id);
158
- if (task && ['requested', 'quoted', 'accepted', 'revision', 'submitted', 'disputed'].includes(task.status)) {
159
- tasks.push(task);
160
- }
161
- }
162
-
163
- return tasks;
164
- }
165
-
166
- /** Get all tasks for an agent (full history) */
167
- export async function getAgentTasks(env: Env, agentId: string): Promise<Task[]> {
168
- const key = `${AGENT_INBOX_PREFIX}${agentId}`;
169
- const inboxData = await env.TASKS_KV.get(key);
170
- if (!inboxData) return [];
171
-
172
- const taskIds: string[] = JSON.parse(inboxData);
173
- const tasks: Task[] = [];
174
-
175
- for (const id of taskIds) {
176
- const task = await getTask(env, id);
177
- if (task) tasks.push(task);
178
- }
179
-
180
- // Sort by most recent first
181
- return tasks.sort((a, b) => (b.completedAt || b.createdAt) - (a.completedAt || a.createdAt));
182
- }
183
-
184
- /** Get all tasks for a client */
185
- export async function getClientTasks(env: Env, clientAddress: string): Promise<Task[]> {
186
- const key = `${CLIENT_TASKS_PREFIX}${clientAddress.toLowerCase()}`;
187
- const tasksData = await env.TASKS_KV.get(key);
188
- if (!tasksData) return [];
189
-
190
- const taskIds: string[] = JSON.parse(tasksData);
191
- const tasks: Task[] = [];
192
-
193
- for (const id of taskIds) {
194
- const task = await getTask(env, id);
195
- if (task) tasks.push(task);
196
- }
197
-
198
- return tasks;
199
- }
200
-
201
- /** Agent quotes a price for a task */
202
- export async function quoteTask(
203
- env: Env,
204
- taskId: string,
205
- priceWei: string,
206
- message?: string,
207
- ): Promise<Task | { error: string }> {
208
- const task = await getTask(env, taskId);
209
- if (!task) return { error: 'Task not found' };
210
- if (task.status !== 'requested' && task.status !== 'quoted') return { error: `Task is ${task.status}, cannot quote` };
211
-
212
- task.status = 'quoted';
213
- task.quotedPriceWei = priceWei;
214
- task.quotedAt = Date.now();
215
- if (message) task.quotedMessage = message;
216
-
217
- await saveTask(env, task);
218
- await updateRecentIndex(env, task);
219
-
220
- // Update stats: track response time
221
- const stats = await getAgentStats(env, task.agentId);
222
- stats.totalResponseMs += (task.quotedAt - task.createdAt);
223
- await saveAgentStats(env, task.agentId, stats);
224
-
225
- return task;
226
- }
227
-
228
- /** Agent declines a task */
229
- export async function declineTask(
230
- env: Env,
231
- taskId: string,
232
- ): Promise<Task | { error: string }> {
233
- const task = await getTask(env, taskId);
234
- if (!task) return { error: 'Task not found' };
235
- if (!['requested', 'quoted'].includes(task.status)) {
236
- return { error: `Task is ${task.status}, cannot decline` };
237
- }
238
-
239
- task.status = 'declined';
240
- await saveTask(env, task);
241
- await updateRecentIndex(env, task);
242
-
243
- // Update stats: increment declined count
244
- const stats = await getAgentStats(env, task.agentId);
245
- stats.declinedTasks++;
246
- await saveAgentStats(env, task.agentId, stats);
247
-
248
- return task;
249
- }
250
-
251
- /** Client accepts a quote (agent starts work) */
252
- export async function acceptQuote(
253
- env: Env,
254
- taskId: string,
255
- clientAddress: string,
256
- ): Promise<Task | { error: string }> {
257
- const task = await getTask(env, taskId);
258
- if (!task) return { error: 'Task not found' };
259
- if (task.status !== 'quoted') return { error: `Task is ${task.status}, cannot accept` };
260
- if (task.clientAddress.toLowerCase() !== clientAddress.toLowerCase()) {
261
- return { error: 'Only the client can accept the quote' };
262
- }
263
-
264
- task.status = 'accepted';
265
- task.acceptedAt = Date.now();
266
- await saveTask(env, task);
267
- await updateRecentIndex(env, task);
268
- return task;
269
- }
270
-
271
- /** Agent submits work result (from accepted or revision) */
272
- export async function submitTask(
273
- env: Env,
274
- taskId: string,
275
- result: string,
276
- files?: TaskFile[],
277
- ): Promise<Task | { error: string }> {
278
- const task = await getTask(env, taskId);
279
- if (!task) return { error: 'Task not found' };
280
- if (task.status !== 'accepted' && task.status !== 'revision') {
281
- return { error: `Task is ${task.status}, cannot submit` };
282
- }
283
-
284
- task.status = 'submitted';
285
- task.submittedAt = Date.now();
286
- task.result = result;
287
- if (files && files.length > 0) {
288
- task.files = files;
289
- }
290
- await saveTask(env, task);
291
- await updateRecentIndex(env, task);
292
- return task;
293
- }
294
-
295
- /** Append a file to a task's files array in KV */
296
- export async function addFileToTask(
297
- env: Env,
298
- taskId: string,
299
- file: TaskFile,
300
- ): Promise<Task | { error: string }> {
301
- const task = await getTask(env, taskId);
302
- if (!task) return { error: 'Task not found' };
303
-
304
- if (!task.files) task.files = [];
305
- task.files.push(file);
306
- await saveTask(env, task);
307
- return task;
308
- }
309
-
310
- /** Get recent tasks across all agents */
311
- export async function getRecentTasks(env: Env, limit = 10): Promise<{ tasks: Task[]; total: number }> {
312
- const raw = await env.TASKS_KV.get(RECENT_TASKS_KEY);
313
- if (!raw) return { tasks: [], total: 0 };
314
-
315
- const entries: RecentTaskEntry[] = JSON.parse(raw);
316
- const sliced = entries.slice(0, Math.min(limit, RECENT_TASKS_MAX));
317
-
318
- const tasks: Task[] = [];
319
- for (const entry of sliced) {
320
- const task = await getTask(env, entry.id);
321
- if (task) tasks.push(task);
322
- }
323
-
324
- return { tasks, total: entries.length };
325
- }
326
-
327
- /** Mark task as rated (after on-chain feedback) */
328
- export async function rateTask(
329
- env: Env,
330
- taskId: string,
331
- txHash: string,
332
- score?: number,
333
- comment?: string,
334
- ): Promise<Task | { error: string }> {
335
- const task = await getTask(env, taskId);
336
- if (!task) return { error: 'Task not found' };
337
- if (task.status !== 'completed') return { error: `Task is ${task.status}, cannot rate` };
338
- if (task.ratedAt) return { error: 'Task already rated' };
339
-
340
- task.ratedAt = Date.now();
341
- task.ratedTxHash = txHash;
342
- if (score !== undefined) task.ratedScore = score;
343
- if (comment) task.ratedComment = comment;
344
- await saveTask(env, task);
345
-
346
- // Update verified reputation stats
347
- if (score !== undefined) {
348
- const stats = await getAgentStats(env, task.agentId);
349
- stats.ratingSum = (stats.ratingSum || 0) + score;
350
- stats.ratingCount = (stats.ratingCount || 0) + 1;
351
- await saveAgentStats(env, task.agentId, stats);
352
- }
353
-
354
- return task;
355
- }
356
-
357
- /** Client requests revision — moves task from submitted back for rework */
358
- export async function requestRevision(
359
- env: Env,
360
- taskId: string,
361
- reason: string,
362
- senderAddress: string,
363
- ): Promise<Task | { error: string }> {
364
- const task = await getTask(env, taskId);
365
- if (!task) return { error: 'Task not found' };
366
- if (task.status !== 'submitted') return { error: `Task is ${task.status}, cannot request revision` };
367
-
368
- task.status = 'revision';
369
- task.revisionCount = (task.revisionCount || 0) + 1;
370
-
371
- // Add revision reason as a message
372
- if (!task.messages) task.messages = [];
373
- task.messages.push({
374
- sender: senderAddress.toLowerCase(),
375
- role: 'client',
376
- content: reason,
377
- timestamp: Date.now(),
378
- });
379
-
380
- await saveTask(env, task);
381
- await updateRecentIndex(env, task);
382
- return task;
383
- }
384
-
385
- /** Add a message to a task (either party, task must be active) */
386
- export async function addMessage(
387
- env: Env,
388
- taskId: string,
389
- senderAddress: string,
390
- role: 'client' | 'agent',
391
- content: string,
392
- ): Promise<Task | { error: string }> {
393
- const task = await getTask(env, taskId);
394
- if (!task) return { error: 'Task not found' };
395
-
396
- const terminalStatuses = ['completed', 'declined', 'expired', 'resolved'];
397
- if (terminalStatuses.includes(task.status)) {
398
- return { error: `Task is ${task.status}, cannot send messages` };
399
- }
400
-
401
- if (!task.messages) task.messages = [];
402
- task.messages.push({
403
- sender: senderAddress.toLowerCase(),
404
- role,
405
- content,
406
- timestamp: Date.now(),
407
- });
408
-
409
- await saveTask(env, task);
410
- return task;
411
- }
412
-
413
- /** Client disputes submitted work — freezes timeout */
414
- export async function disputeTask(
415
- env: Env,
416
- taskId: string,
417
- txHash: string,
418
- ): Promise<Task | { error: string }> {
419
- const task = await getTask(env, taskId);
420
- if (!task) return { error: 'Task not found' };
421
- if (task.status !== 'submitted') return { error: `Task is ${task.status}, cannot dispute` };
422
-
423
- task.status = 'disputed';
424
- task.disputedAt = Date.now();
425
- task.disputeTxHash = txHash;
426
- await saveTask(env, task);
427
- await updateRecentIndex(env, task);
428
- return task;
429
- }
430
-
431
- /** Admin resolves a dispute */
432
- export async function resolveTask(
433
- env: Env,
434
- taskId: string,
435
- resolution: 'client' | 'agent',
436
- txHash: string,
437
- ): Promise<Task | { error: string }> {
438
- const task = await getTask(env, taskId);
439
- if (!task) return { error: 'Task not found' };
440
- if (task.status !== 'disputed') return { error: `Task is ${task.status}, cannot resolve` };
441
-
442
- task.status = 'resolved';
443
- task.resolvedAt = Date.now();
444
- task.resolveTxHash = txHash;
445
- task.disputeResolution = resolution;
446
- await saveTask(env, task);
447
- await updateRecentIndex(env, task);
448
- return task;
449
- }
450
-
451
- /** Mark task as refunded (client cancelled before agent submitted) */
452
- export async function refundTask(
453
- env: Env,
454
- taskId: string,
455
- txHash: string,
456
- ): Promise<Task | { error: string }> {
457
- const task = await getTask(env, taskId);
458
- if (!task) return { error: 'Task not found' };
459
- if (!['quoted', 'accepted'].includes(task.status)) {
460
- return { error: `Task is ${task.status}, cannot refund` };
461
- }
462
-
463
- task.status = 'expired';
464
- task.completedAt = Date.now();
465
- task.txHash = txHash;
466
- await saveTask(env, task);
467
- await updateRecentIndex(env, task);
468
- return task;
469
- }
470
-
471
- /** Mark task as completed (after payment) */
472
- export async function completeTask(
473
- env: Env,
474
- taskId: string,
475
- txHash: string,
476
- ): Promise<Task | { error: string }> {
477
- const task = await getTask(env, taskId);
478
- if (!task) return { error: 'Task not found' };
479
- if (task.status !== 'submitted') return { error: `Task is ${task.status}, cannot complete` };
480
-
481
- task.status = 'completed';
482
- task.completedAt = Date.now();
483
- task.txHash = txHash;
484
- await saveTask(env, task);
485
- await updateRecentIndex(env, task);
486
-
487
- // Update stats: increment completed, add earnings
488
- const stats = await getAgentStats(env, task.agentId);
489
- stats.completedTasks++;
490
- if (task.quotedPriceWei) {
491
- const current = BigInt(stats.totalEarningsWei || '0');
492
- const added = BigInt(task.quotedPriceWei);
493
- stats.totalEarningsWei = (current + added).toString();
494
- }
495
- await saveAgentStats(env, task.agentId, stats);
496
-
497
- return task;
498
- }