moltlaunch 2.0.0 → 2.0.1

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 (105) hide show
  1. package/package.json +5 -1
  2. package/.claude/commands/deploy.md +0 -33
  3. package/.claude/hooks/regenerate-docs.sh +0 -12
  4. package/.claude/settings.json +0 -15
  5. package/.env.example +0 -2
  6. package/.github/workflows/deploy.yml +0 -37
  7. package/ROADMAP.md +0 -29
  8. package/contracts/MandateEscrowV4.sol +0 -281
  9. package/contracts/mocks/MockFlaunchBuyback.sol +0 -24
  10. package/hardhat.config.cjs +0 -29
  11. package/scripts/check-deploy-cost.ts +0 -15
  12. package/scripts/deploy-escrow-v4.ts +0 -81
  13. package/scripts/deploy-escrow.cjs +0 -22
  14. package/scripts/generate-docs.ts +0 -309
  15. package/shared/manifest.json +0 -87
  16. package/site/.vscode/extensions.json +0 -4
  17. package/site/.vscode/launch.json +0 -11
  18. package/site/README.md +0 -43
  19. package/site/astro.config.mjs +0 -21
  20. package/site/functions/agent/[[path]].ts +0 -9
  21. package/site/functions/task/[[path]].ts +0 -9
  22. package/site/index.html.bak +0 -1755
  23. package/site/package-lock.json +0 -6165
  24. package/site/package.json +0 -17
  25. package/site/public/_redirects +0 -1
  26. package/site/public/art/hero.webp +0 -0
  27. package/site/public/favicon.ico +0 -0
  28. package/site/public/favicon.svg +0 -4
  29. package/site/public/logo.png +0 -0
  30. package/site/public/skill.md +0 -276
  31. package/site/src/components/AgentGridCard.astro +0 -97
  32. package/site/src/components/AgentRow.astro +0 -75
  33. package/site/src/components/Footer.astro +0 -71
  34. package/site/src/components/GigCard.astro +0 -36
  35. package/site/src/components/Navbar.astro +0 -93
  36. package/site/src/components/ReviewCard.astro +0 -29
  37. package/site/src/components/SkillPill.astro +0 -19
  38. package/site/src/components/StatusBadge.astro +0 -27
  39. package/site/src/components/TaskEntry.astro +0 -98
  40. package/site/src/layouts/Layout.astro +0 -268
  41. package/site/src/lib/api.ts +0 -342
  42. package/site/src/pages/404.astro +0 -33
  43. package/site/src/pages/admin.astro +0 -445
  44. package/site/src/pages/agent/[...id].astro +0 -678
  45. package/site/src/pages/agents/index.astro +0 -235
  46. package/site/src/pages/dashboard.astro +0 -244
  47. package/site/src/pages/docs.astro +0 -191
  48. package/site/src/pages/how.astro +0 -156
  49. package/site/src/pages/index.astro +0 -226
  50. package/site/src/pages/leaderboard.astro +0 -155
  51. package/site/src/pages/task/[...id].astro +0 -1467
  52. package/site/src/styles/global.css +0 -159
  53. package/site/tailwind.config.mjs +0 -94
  54. package/site/tsconfig.json +0 -5
  55. package/site/wrangler.toml +0 -5
  56. package/src/commands/accept.ts +0 -135
  57. package/src/commands/agents.ts +0 -190
  58. package/src/commands/approve.ts +0 -127
  59. package/src/commands/claim.ts +0 -130
  60. package/src/commands/decline.ts +0 -55
  61. package/src/commands/dispute.ts +0 -92
  62. package/src/commands/earnings.ts +0 -86
  63. package/src/commands/feedback.ts +0 -147
  64. package/src/commands/gig.ts +0 -141
  65. package/src/commands/hire.ts +0 -96
  66. package/src/commands/inbox.ts +0 -135
  67. package/src/commands/message.ts +0 -97
  68. package/src/commands/profile.ts +0 -62
  69. package/src/commands/quote.ts +0 -80
  70. package/src/commands/refund.ts +0 -82
  71. package/src/commands/register.ts +0 -250
  72. package/src/commands/resolve.ts +0 -104
  73. package/src/commands/reviews.ts +0 -78
  74. package/src/commands/revise.ts +0 -65
  75. package/src/commands/submit.ts +0 -123
  76. package/src/commands/tasks.ts +0 -224
  77. package/src/commands/view.ts +0 -122
  78. package/src/commands/wallet.ts +0 -42
  79. package/src/index.ts +0 -285
  80. package/src/lib/agent0.ts +0 -158
  81. package/src/lib/auth.ts +0 -25
  82. package/src/lib/constants.ts +0 -55
  83. package/src/lib/escrow.ts +0 -374
  84. package/src/lib/files.ts +0 -87
  85. package/src/lib/flaunch.ts +0 -277
  86. package/src/lib/mandate.ts +0 -623
  87. package/src/lib/tasks.ts +0 -466
  88. package/src/lib/types.ts +0 -112
  89. package/src/lib/wallet.ts +0 -119
  90. package/src/lib/x402.ts +0 -86
  91. package/test/MandateEscrowV4.test.cjs +0 -568
  92. package/tsconfig.json +0 -19
  93. package/tsup.config.ts +0 -15
  94. package/worker/package-lock.json +0 -1812
  95. package/worker/package.json +0 -18
  96. package/worker/src/agents.ts +0 -755
  97. package/worker/src/auth.ts +0 -126
  98. package/worker/src/files.ts +0 -40
  99. package/worker/src/index.ts +0 -963
  100. package/worker/src/profiles.ts +0 -85
  101. package/worker/src/ratelimit.ts +0 -45
  102. package/worker/src/tasks.ts +0 -498
  103. package/worker/src/types.ts +0 -95
  104. package/worker/tsconfig.json +0 -15
  105. 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
- }