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.
- package/README.md +2 -2
- package/dist/index.js +18 -18
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/.claude/commands/deploy.md +0 -33
- package/.claude/hooks/regenerate-docs.sh +0 -12
- package/.claude/settings.json +0 -15
- package/.env.example +0 -2
- package/.github/workflows/deploy.yml +0 -37
- package/ROADMAP.md +0 -29
- package/contracts/MandateEscrowV4.sol +0 -281
- package/contracts/mocks/MockFlaunchBuyback.sol +0 -24
- package/hardhat.config.cjs +0 -29
- package/scripts/check-deploy-cost.ts +0 -15
- package/scripts/deploy-escrow-v4.ts +0 -81
- package/scripts/deploy-escrow.cjs +0 -22
- package/scripts/generate-docs.ts +0 -309
- package/shared/manifest.json +0 -87
- package/site/.vscode/extensions.json +0 -4
- package/site/.vscode/launch.json +0 -11
- package/site/README.md +0 -43
- package/site/astro.config.mjs +0 -21
- package/site/functions/agent/[[path]].ts +0 -9
- package/site/functions/task/[[path]].ts +0 -9
- package/site/index.html.bak +0 -1755
- package/site/package-lock.json +0 -6165
- package/site/package.json +0 -17
- package/site/public/_redirects +0 -1
- package/site/public/art/hero.webp +0 -0
- package/site/public/favicon.ico +0 -0
- package/site/public/favicon.svg +0 -4
- package/site/public/logo.png +0 -0
- package/site/public/skill.md +0 -276
- package/site/src/components/AgentGridCard.astro +0 -97
- package/site/src/components/AgentRow.astro +0 -75
- package/site/src/components/Footer.astro +0 -71
- package/site/src/components/GigCard.astro +0 -36
- package/site/src/components/Navbar.astro +0 -93
- package/site/src/components/ReviewCard.astro +0 -29
- package/site/src/components/SkillPill.astro +0 -19
- package/site/src/components/StatusBadge.astro +0 -27
- package/site/src/components/TaskEntry.astro +0 -98
- package/site/src/layouts/Layout.astro +0 -268
- package/site/src/lib/api.ts +0 -342
- package/site/src/pages/404.astro +0 -33
- package/site/src/pages/admin.astro +0 -445
- package/site/src/pages/agent/[...id].astro +0 -678
- package/site/src/pages/agents/index.astro +0 -235
- package/site/src/pages/dashboard.astro +0 -244
- package/site/src/pages/docs.astro +0 -191
- package/site/src/pages/how.astro +0 -156
- package/site/src/pages/index.astro +0 -226
- package/site/src/pages/leaderboard.astro +0 -155
- package/site/src/pages/task/[...id].astro +0 -1467
- package/site/src/styles/global.css +0 -159
- package/site/tailwind.config.mjs +0 -94
- package/site/tsconfig.json +0 -5
- package/site/wrangler.toml +0 -5
- package/src/commands/accept.ts +0 -135
- package/src/commands/agents.ts +0 -190
- package/src/commands/approve.ts +0 -127
- package/src/commands/claim.ts +0 -130
- package/src/commands/decline.ts +0 -55
- package/src/commands/dispute.ts +0 -92
- package/src/commands/earnings.ts +0 -86
- package/src/commands/feedback.ts +0 -147
- package/src/commands/gig.ts +0 -141
- package/src/commands/hire.ts +0 -96
- package/src/commands/inbox.ts +0 -135
- package/src/commands/message.ts +0 -97
- package/src/commands/profile.ts +0 -62
- package/src/commands/quote.ts +0 -80
- package/src/commands/refund.ts +0 -82
- package/src/commands/register.ts +0 -250
- package/src/commands/resolve.ts +0 -104
- package/src/commands/reviews.ts +0 -78
- package/src/commands/revise.ts +0 -65
- package/src/commands/submit.ts +0 -123
- package/src/commands/tasks.ts +0 -224
- package/src/commands/view.ts +0 -122
- package/src/commands/wallet.ts +0 -42
- package/src/index.ts +0 -285
- package/src/lib/agent0.ts +0 -158
- package/src/lib/auth.ts +0 -25
- package/src/lib/constants.ts +0 -55
- package/src/lib/escrow.ts +0 -374
- package/src/lib/files.ts +0 -87
- package/src/lib/flaunch.ts +0 -277
- package/src/lib/mandate.ts +0 -623
- package/src/lib/tasks.ts +0 -466
- package/src/lib/types.ts +0 -112
- package/src/lib/wallet.ts +0 -119
- package/src/lib/x402.ts +0 -86
- package/test/MandateEscrowV4.test.cjs +0 -568
- package/tsconfig.json +0 -19
- package/tsup.config.ts +0 -15
- package/worker/package-lock.json +0 -1812
- package/worker/package.json +0 -18
- package/worker/src/agents.ts +0 -755
- package/worker/src/auth.ts +0 -126
- package/worker/src/files.ts +0 -40
- package/worker/src/index.ts +0 -963
- package/worker/src/profiles.ts +0 -85
- package/worker/src/ratelimit.ts +0 -45
- package/worker/src/tasks.ts +0 -498
- package/worker/src/types.ts +0 -95
- package/worker/tsconfig.json +0 -15
- package/worker/wrangler.toml +0 -19
package/worker/src/profiles.ts
DELETED
|
@@ -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
|
-
}
|
package/worker/src/ratelimit.ts
DELETED
|
@@ -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
|
-
}
|
package/worker/src/tasks.ts
DELETED
|
@@ -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
|
-
}
|