@mixrpay/agent-sdk 0.8.2 → 0.8.4

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/dist/index.js CHANGED
@@ -298,6 +298,13 @@ var SessionKey = class _SessionKey {
298
298
  get privateKeyHex() {
299
299
  return this.privateKey.slice(2);
300
300
  }
301
+ /**
302
+ * Get the raw private key with 0x prefix.
303
+ * Used internally for signing operations.
304
+ */
305
+ get rawPrivateKey() {
306
+ return this.privateKey;
307
+ }
301
308
  /**
302
309
  * Parse a session key string into a SessionKey object.
303
310
  *
@@ -488,7 +495,7 @@ function getAmountUsd(requirements) {
488
495
  }
489
496
 
490
497
  // src/agent-wallet.ts
491
- var SDK_VERSION = "0.8.2";
498
+ var SDK_VERSION = "0.8.3";
492
499
  var DEFAULT_BASE_URL = process.env.MIXRPAY_BASE_URL || "https://www.mixrpay.com";
493
500
  var DEFAULT_TIMEOUT = 3e4;
494
501
  var NETWORKS = {
@@ -1190,6 +1197,160 @@ var AgentWallet = class {
1190
1197
  };
1191
1198
  }
1192
1199
  // ===========================================================================
1200
+ // Nested Budget Delegation Methods
1201
+ // ===========================================================================
1202
+ /**
1203
+ * Spawn a child invite for sub-agents.
1204
+ *
1205
+ * Allows an agent to create an invite code for a sub-agent with a portion
1206
+ * of their remaining budget (max 20%). The child inherits merchant restrictions
1207
+ * and cannot outlive the parent session.
1208
+ *
1209
+ * @param options - Spawn options
1210
+ * @returns The created child invite details
1211
+ *
1212
+ * @throws {MixrPayError} If spawning fails (insufficient budget, depth limit, etc.)
1213
+ *
1214
+ * @example
1215
+ * ```typescript
1216
+ * const wallet = new AgentWallet({ sessionKey: 'sk_live_...' });
1217
+ *
1218
+ * // Spawn a child invite for a sub-agent
1219
+ * const childInvite = await wallet.spawnChildInvite({
1220
+ * budgetUsd: 10.00, // Max 20% of available budget
1221
+ * name: 'Research Sub-Agent',
1222
+ * allowedMerchants: ['firecrawl.dev'], // Must be subset of parent
1223
+ * expiresInDays: 7, // Will be capped to parent's expiry
1224
+ * });
1225
+ *
1226
+ * console.log(`Share with sub-agent: ${childInvite.inviteCode}`);
1227
+ * console.log(`Child can spawn up to: $${childInvite.maxSpawnBudget}`);
1228
+ * ```
1229
+ */
1230
+ async spawnChildInvite(options) {
1231
+ const { budgetUsd, name, allowedMerchants, expiresInDays } = options;
1232
+ const timestamp = Date.now();
1233
+ const nonce = crypto.randomUUID();
1234
+ const message = `spawn:${timestamp}:${nonce}`;
1235
+ const signature = await signMessage({
1236
+ message,
1237
+ privateKey: this.sessionKey.rawPrivateKey
1238
+ });
1239
+ const response = await fetch(`${this.baseUrl}/api/v1/agent/spawn`, {
1240
+ method: "POST",
1241
+ headers: { "Content-Type": "application/json" },
1242
+ body: JSON.stringify({
1243
+ session_key: this.sessionKey.toString(),
1244
+ signature,
1245
+ message,
1246
+ budget_usd: budgetUsd,
1247
+ name,
1248
+ allowed_merchants: allowedMerchants,
1249
+ expires_in_days: expiresInDays
1250
+ })
1251
+ });
1252
+ if (!response.ok) {
1253
+ const error = await response.json().catch(() => ({}));
1254
+ if (response.status === 409) {
1255
+ throw new MixrPayError("Concurrent modification - please retry");
1256
+ }
1257
+ if (response.status === 429) {
1258
+ throw new MixrPayError("Rate limited - too many spawn attempts");
1259
+ }
1260
+ throw new MixrPayError(error.error || `Failed to spawn child: ${response.status}`);
1261
+ }
1262
+ const data = await response.json();
1263
+ return {
1264
+ inviteCode: data.invite_code,
1265
+ inviteId: data.invite_id,
1266
+ budgetUsd: data.budget_usd,
1267
+ expiresAt: new Date(data.expires_at),
1268
+ depth: data.depth,
1269
+ maxSpawnBudget: data.max_spawn_budget,
1270
+ allowedMerchants: data.allowed_merchants || []
1271
+ };
1272
+ }
1273
+ /**
1274
+ * Get available budget information for spawning.
1275
+ *
1276
+ * Returns the current budget status including how much can be spawned
1277
+ * to child agents (20% of available).
1278
+ *
1279
+ * @returns Budget information
1280
+ *
1281
+ * @example
1282
+ * ```typescript
1283
+ * const budget = await wallet.getAvailableBudget();
1284
+ *
1285
+ * console.log(`Total budget: $${budget.totalBudget}`);
1286
+ * console.log(`Spent: $${budget.spent}`);
1287
+ * console.log(`Allocated to children: $${budget.allocatedToChildren}`);
1288
+ * console.log(`Available: $${budget.available}`);
1289
+ * console.log(`Max spawn budget: $${budget.maxSpawnBudget}`);
1290
+ *
1291
+ * if (budget.canSpawn && budget.maxSpawnBudget >= 5.00) {
1292
+ * // Safe to spawn a $5 child
1293
+ * }
1294
+ * ```
1295
+ */
1296
+ async getAvailableBudget() {
1297
+ const response = await fetch(`${this.baseUrl}/api/v1/agent/descendants`, {
1298
+ method: "GET",
1299
+ headers: {
1300
+ "Authorization": `Bearer ${this.sessionKey.toString()}`
1301
+ }
1302
+ });
1303
+ if (!response.ok) {
1304
+ const error = await response.json().catch(() => ({}));
1305
+ throw new MixrPayError(error.error || `Failed to get budget: ${response.status}`);
1306
+ }
1307
+ const data = await response.json();
1308
+ return {
1309
+ totalBudget: data.budget.total_usd,
1310
+ spent: data.budget.spent_usd,
1311
+ allocatedToChildren: data.budget.allocated_to_children_usd,
1312
+ available: data.budget.available_usd,
1313
+ maxSpawnBudget: data.max_spawn_budget,
1314
+ canSpawn: data.can_spawn
1315
+ };
1316
+ }
1317
+ /**
1318
+ * Get all child sessions spawned by this session.
1319
+ *
1320
+ * Returns a tree of child invites/sessions including their spending status.
1321
+ *
1322
+ * @returns Array of child sessions
1323
+ *
1324
+ * @example
1325
+ * ```typescript
1326
+ * const children = await wallet.getChildSessions();
1327
+ *
1328
+ * for (const child of children) {
1329
+ * console.log(`${child.name}: $${child.spentUsd}/$${child.budgetUsd}`);
1330
+ * console.log(` Status: ${child.status}`);
1331
+ * console.log(` Depth: ${child.depth}`);
1332
+ *
1333
+ * if (child.children) {
1334
+ * console.log(` Has ${child.children.length} grandchildren`);
1335
+ * }
1336
+ * }
1337
+ * ```
1338
+ */
1339
+ async getChildSessions() {
1340
+ const response = await fetch(`${this.baseUrl}/api/v1/agent/descendants`, {
1341
+ method: "GET",
1342
+ headers: {
1343
+ "Authorization": `Bearer ${this.sessionKey.toString()}`
1344
+ }
1345
+ });
1346
+ if (!response.ok) {
1347
+ const error = await response.json().catch(() => ({}));
1348
+ throw new MixrPayError(error.error || `Failed to get children: ${response.status}`);
1349
+ }
1350
+ const data = await response.json();
1351
+ return data.children || [];
1352
+ }
1353
+ // ===========================================================================
1193
1354
  // Core Methods
1194
1355
  // ===========================================================================
1195
1356
  /**
@@ -1414,7 +1575,7 @@ var AgentWallet = class {
1414
1575
  try {
1415
1576
  const infoResponse = await fetch(`${this.baseUrl}/api/v1/session-key/info`, {
1416
1577
  headers: {
1417
- "X-Session-Key": this.sessionKey.address
1578
+ "X-Session-Key": this.sessionKey.address.toLowerCase()
1418
1579
  }
1419
1580
  });
1420
1581
  if (!infoResponse.ok) {
@@ -1492,7 +1653,7 @@ var AgentWallet = class {
1492
1653
  try {
1493
1654
  const response = await fetch(`${this.baseUrl}/api/v1/session-key/info`, {
1494
1655
  headers: {
1495
- "X-Session-Key": this.sessionKey.address
1656
+ "X-Session-Key": this.sessionKey.address.toLowerCase()
1496
1657
  }
1497
1658
  });
1498
1659
  if (response.ok) {
@@ -1547,7 +1708,7 @@ var AgentWallet = class {
1547
1708
  try {
1548
1709
  const response = await fetch(`${this.baseUrl}/api/v1/session-key/stats`, {
1549
1710
  headers: {
1550
- "X-Session-Key": this.sessionKey.address
1711
+ "X-Session-Key": this.sessionKey.address.toLowerCase()
1551
1712
  }
1552
1713
  });
1553
1714
  if (response.ok) {
@@ -2398,9 +2559,8 @@ Timestamp: ${timestamp}`;
2398
2559
  idempotencyKey,
2399
2560
  onEvent
2400
2561
  } = options;
2401
- this.logger.debug("runAgent", { sessionId, messageCount: messages.length, config, stream });
2562
+ this.logger.debug("runAgent", { sessionId: sessionId || "(from signature)", messageCount: messages.length, config, stream });
2402
2563
  const body = {
2403
- session_id: sessionId,
2404
2564
  messages: messages.map((m) => ({ role: m.role, content: m.content })),
2405
2565
  config: {
2406
2566
  model: config.model,
@@ -2411,13 +2571,17 @@ Timestamp: ${timestamp}`;
2411
2571
  stream,
2412
2572
  idempotency_key: idempotencyKey
2413
2573
  };
2574
+ if (sessionId) {
2575
+ body.session_id = sessionId;
2576
+ }
2414
2577
  const AGENT_RUN_TIMEOUT = 18e4;
2415
2578
  if (!stream) {
2579
+ const authHeaders = await this.getSessionAuthHeaders();
2416
2580
  const response = await fetch(`${this.baseUrl}/api/v2/agent/run`, {
2417
2581
  method: "POST",
2418
2582
  headers: {
2419
2583
  "Content-Type": "application/json",
2420
- "X-Mixr-Session": sessionId
2584
+ ...authHeaders
2421
2585
  },
2422
2586
  body: JSON.stringify(body),
2423
2587
  signal: AbortSignal.timeout(AGENT_RUN_TIMEOUT)
@@ -2460,18 +2624,19 @@ Timestamp: ${timestamp}`;
2460
2624
  txHash: data.tx_hash
2461
2625
  };
2462
2626
  }
2463
- return this.runAgentStreaming(sessionId, body, onEvent);
2627
+ return this.runAgentStreaming(body, onEvent);
2464
2628
  }
2465
2629
  /**
2466
2630
  * Internal: Handle streaming agent run via SSE
2467
2631
  */
2468
- async runAgentStreaming(sessionId, body, onEvent) {
2632
+ async runAgentStreaming(body, onEvent) {
2469
2633
  const STREAMING_TIMEOUT = 3e5;
2634
+ const authHeaders = await this.getSessionAuthHeaders();
2470
2635
  const response = await fetch(`${this.baseUrl}/api/v2/agent/run`, {
2471
2636
  method: "POST",
2472
2637
  headers: {
2473
2638
  "Content-Type": "application/json",
2474
- "X-Mixr-Session": sessionId
2639
+ ...authHeaders
2475
2640
  },
2476
2641
  body: JSON.stringify(body),
2477
2642
  signal: AbortSignal.timeout(STREAMING_TIMEOUT)
@@ -2618,12 +2783,11 @@ Timestamp: ${timestamp}`;
2618
2783
  * }
2619
2784
  * ```
2620
2785
  */
2621
- async getAgentRunStatus(runId, sessionId) {
2622
- this.logger.debug("getAgentRunStatus", { runId, sessionId });
2786
+ async getAgentRunStatus(runId, _sessionId) {
2787
+ this.logger.debug("getAgentRunStatus", { runId });
2788
+ const authHeaders = await this.getSessionAuthHeaders();
2623
2789
  const response = await fetch(`${this.baseUrl}/api/v2/agent/run/${runId}`, {
2624
- headers: {
2625
- "X-Mixr-Session": sessionId
2626
- }
2790
+ headers: authHeaders
2627
2791
  });
2628
2792
  if (!response.ok) {
2629
2793
  const error = await response.json().catch(() => ({}));
@@ -2722,6 +2886,724 @@ Timestamp: ${timestamp}`;
2722
2886
  latencyMs: mixrpay.latencyMs
2723
2887
  };
2724
2888
  }
2889
+ // ===========================================================================
2890
+ // Task Board Methods
2891
+ // ===========================================================================
2892
+ /**
2893
+ * Create a new task on the Task Board.
2894
+ *
2895
+ * Users (human or agent) can post tasks with budgets for other agents to complete.
2896
+ * The listing fee is charged when an agent is accepted, not at creation time.
2897
+ *
2898
+ * @param params - Task creation parameters
2899
+ * @returns The created task
2900
+ *
2901
+ * @example
2902
+ * ```typescript
2903
+ * const task = await wallet.createTask({
2904
+ * title: 'Research crypto regulations',
2905
+ * description: 'Research and summarize crypto regulations in the EU...',
2906
+ * budgetUsd: 100,
2907
+ * deliverables: ['PDF report', 'Summary document'],
2908
+ * category: 'research',
2909
+ * });
2910
+ * console.log(`Task created: ${task.id}`);
2911
+ * ```
2912
+ */
2913
+ async createTask(params) {
2914
+ this.logger.info(`Creating task: ${params.title}`);
2915
+ const response = await this.callApi("/api/v2/tasks", {
2916
+ method: "POST",
2917
+ headers: { "Content-Type": "application/json" },
2918
+ body: JSON.stringify({
2919
+ title: params.title,
2920
+ description: params.description,
2921
+ budget_usd: params.budgetUsd,
2922
+ deliverables: params.deliverables,
2923
+ category: params.category,
2924
+ expires_in_days: params.expiresInDays
2925
+ })
2926
+ });
2927
+ const result = await response.json();
2928
+ if (!response.ok) {
2929
+ throw new MixrPayError(result.error || "Failed to create task");
2930
+ }
2931
+ return this.parseTask(result.task);
2932
+ }
2933
+ /**
2934
+ * List open tasks on the Task Board.
2935
+ *
2936
+ * Browse available tasks that agents can request to work on.
2937
+ *
2938
+ * @param params - Optional filter parameters
2939
+ * @returns List of tasks with pagination
2940
+ *
2941
+ * @example
2942
+ * ```typescript
2943
+ * const { tasks, pagination } = await wallet.listTasks({
2944
+ * minBudget: 50,
2945
+ * category: 'research',
2946
+ * });
2947
+ * console.log(`Found ${pagination.total} matching tasks`);
2948
+ * ```
2949
+ */
2950
+ async listTasks(params) {
2951
+ const searchParams = new URLSearchParams();
2952
+ if (params?.status) searchParams.set("status", params.status);
2953
+ if (params?.category) searchParams.set("category", params.category);
2954
+ if (params?.minBudget !== void 0) searchParams.set("min_budget", String(params.minBudget));
2955
+ if (params?.maxBudget !== void 0) searchParams.set("max_budget", String(params.maxBudget));
2956
+ if (params?.limit !== void 0) searchParams.set("limit", String(params.limit));
2957
+ if (params?.offset !== void 0) searchParams.set("offset", String(params.offset));
2958
+ const url = `/api/v2/tasks${searchParams.toString() ? `?${searchParams}` : ""}`;
2959
+ const response = await this.callApi(url, { method: "GET" });
2960
+ const result = await response.json();
2961
+ if (!response.ok) {
2962
+ throw new MixrPayError(result.error || "Failed to list tasks");
2963
+ }
2964
+ return {
2965
+ tasks: result.tasks.map((t) => this.parseTask(t)),
2966
+ pagination: result.pagination
2967
+ };
2968
+ }
2969
+ /**
2970
+ * Get details for a specific task.
2971
+ *
2972
+ * @param taskId - The task ID
2973
+ * @returns Task details
2974
+ */
2975
+ async getTask(taskId) {
2976
+ const response = await this.callApi(`/api/v2/tasks/${taskId}`, { method: "GET" });
2977
+ const result = await response.json();
2978
+ if (!response.ok) {
2979
+ throw new MixrPayError(result.error || "Failed to get task");
2980
+ }
2981
+ return this.parseTask(result.task);
2982
+ }
2983
+ /**
2984
+ * Get tasks created by or assigned to the authenticated user.
2985
+ *
2986
+ * @param options - Filter and pagination options
2987
+ * @returns List of tasks with pagination info
2988
+ *
2989
+ * @example
2990
+ * ```typescript
2991
+ * // Get all my tasks
2992
+ * const { tasks, pagination } = await wallet.getMyTasks();
2993
+ *
2994
+ * // Get only tasks I created
2995
+ * const created = await wallet.getMyTasks({ role: 'creator' });
2996
+ *
2997
+ * // Get only tasks assigned to me
2998
+ * const assigned = await wallet.getMyTasks({ role: 'agent' });
2999
+ *
3000
+ * // Filter by status
3001
+ * const pending = await wallet.getMyTasks({ status: 'submitted' });
3002
+ * ```
3003
+ */
3004
+ async getMyTasks(options = {}) {
3005
+ const params = new URLSearchParams();
3006
+ if (options.role) params.set("role", options.role);
3007
+ if (options.status) params.set("status", options.status);
3008
+ if (options.page) params.set("page", options.page.toString());
3009
+ if (options.limit) params.set("limit", options.limit.toString());
3010
+ const queryString = params.toString();
3011
+ const url = `/api/v2/tasks/my${queryString ? `?${queryString}` : ""}`;
3012
+ const response = await this.callApi(url, { method: "GET" });
3013
+ const result = await response.json();
3014
+ if (!response.ok) {
3015
+ throw new MixrPayError(result.error || "Failed to get tasks");
3016
+ }
3017
+ return {
3018
+ tasks: result.tasks.map((t) => ({
3019
+ ...this.parseTask(t),
3020
+ isCreator: t.is_creator,
3021
+ isAgent: t.is_agent
3022
+ })),
3023
+ pagination: {
3024
+ page: result.pagination.page,
3025
+ limit: result.pagination.limit,
3026
+ total: result.pagination.total,
3027
+ totalPages: result.pagination.total_pages
3028
+ }
3029
+ };
3030
+ }
3031
+ /**
3032
+ * Request to claim a task.
3033
+ *
3034
+ * Agents use this to express interest in completing a task.
3035
+ * The task creator will review requests and accept one agent.
3036
+ *
3037
+ * @param taskId - The task ID to request
3038
+ * @param message - Optional pitch/message to the task creator
3039
+ * @returns The created request
3040
+ *
3041
+ * @example
3042
+ * ```typescript
3043
+ * const request = await wallet.requestTask('task_123',
3044
+ * 'I have experience with crypto research and can deliver within 24 hours.'
3045
+ * );
3046
+ * console.log(`Request submitted: ${request.id}`);
3047
+ * ```
3048
+ */
3049
+ async requestTask(taskId, message) {
3050
+ this.logger.info(`Requesting task: ${taskId}`);
3051
+ const response = await this.callApi(`/api/v2/tasks/${taskId}/request`, {
3052
+ method: "POST",
3053
+ headers: { "Content-Type": "application/json" },
3054
+ body: message ? JSON.stringify({ message }) : "{}"
3055
+ });
3056
+ const result = await response.json();
3057
+ if (!response.ok) {
3058
+ throw new MixrPayError(result.error || "Failed to request task");
3059
+ }
3060
+ return {
3061
+ id: result.request.id,
3062
+ taskId: result.request.task_id,
3063
+ status: result.request.status,
3064
+ message: result.request.message,
3065
+ createdAt: new Date(result.request.created_at)
3066
+ };
3067
+ }
3068
+ /**
3069
+ * Get requests for a task you created.
3070
+ *
3071
+ * @param taskId - The task ID
3072
+ * @returns List of agent requests
3073
+ */
3074
+ async getTaskRequests(taskId) {
3075
+ const response = await this.callApi(`/api/v2/tasks/${taskId}/requests`, { method: "GET" });
3076
+ const result = await response.json();
3077
+ if (!response.ok) {
3078
+ throw new MixrPayError(result.error || "Failed to get task requests");
3079
+ }
3080
+ return result.requests.map((r) => ({
3081
+ id: r.id,
3082
+ status: r.status,
3083
+ message: r.message,
3084
+ createdAt: new Date(r.created_at),
3085
+ reviewedAt: r.reviewed_at ? new Date(r.reviewed_at) : void 0,
3086
+ agent: r.agent
3087
+ }));
3088
+ }
3089
+ /**
3090
+ * Accept an agent's request to work on your task.
3091
+ *
3092
+ * This will:
3093
+ * - Charge the listing fee from your wallet
3094
+ * - Create a session for the agent with your task budget
3095
+ * - Mark the task as claimed
3096
+ * - Reject all other pending requests
3097
+ *
3098
+ * @param taskId - The task ID
3099
+ * @param requestId - The request ID to accept
3100
+ * @returns Acceptance result with session info
3101
+ *
3102
+ * @example
3103
+ * ```typescript
3104
+ * const result = await wallet.acceptTaskRequest('task_123', 'req_456');
3105
+ * console.log(`Agent accepted! Session budget: $${result.session.budgetUsd}`);
3106
+ * ```
3107
+ */
3108
+ async acceptTaskRequest(taskId, requestId) {
3109
+ this.logger.info(`Accepting request ${requestId} for task ${taskId}`);
3110
+ const response = await this.callApi(`/api/v2/tasks/${taskId}/requests/${requestId}/accept`, {
3111
+ method: "POST"
3112
+ });
3113
+ const result = await response.json();
3114
+ if (!response.ok) {
3115
+ throw new MixrPayError(result.error || "Failed to accept request");
3116
+ }
3117
+ return {
3118
+ success: result.success,
3119
+ task: {
3120
+ id: result.task.id,
3121
+ status: result.task.status,
3122
+ agentUserId: result.task.agent_user_id
3123
+ },
3124
+ session: {
3125
+ id: result.session.id,
3126
+ sessionKey: result.session.session_key,
3127
+ // Agent needs this to authenticate API calls
3128
+ address: result.session.address,
3129
+ expiresAt: new Date(result.session.expires_at),
3130
+ budgetUsd: result.session.budget_usd,
3131
+ allowedMerchants: result.session.allowed_merchants || []
3132
+ },
3133
+ invite: {
3134
+ id: result.invite.id,
3135
+ code: result.invite.code
3136
+ },
3137
+ listingFeeTxHash: result.listing_fee_tx_hash
3138
+ };
3139
+ }
3140
+ /**
3141
+ * Reject an agent's request.
3142
+ *
3143
+ * @param taskId - The task ID
3144
+ * @param requestId - The request ID to reject
3145
+ */
3146
+ async rejectTaskRequest(taskId, requestId) {
3147
+ const response = await this.callApi(`/api/v2/tasks/${taskId}/requests/${requestId}/reject`, {
3148
+ method: "POST"
3149
+ });
3150
+ const result = await response.json();
3151
+ if (!response.ok) {
3152
+ throw new MixrPayError(result.error || "Failed to reject request");
3153
+ }
3154
+ }
3155
+ /**
3156
+ * Submit deliverables for a task you're working on.
3157
+ *
3158
+ * After being accepted to work on a task, use this to submit your work
3159
+ * for the task creator's review.
3160
+ *
3161
+ * @param taskId - The task ID
3162
+ * @param output - The deliverables (text and/or URL)
3163
+ *
3164
+ * @example
3165
+ * ```typescript
3166
+ * await wallet.submitTask('task_123', {
3167
+ * text: 'Here is my research report...',
3168
+ * url: 'https://docs.google.com/document/d/...',
3169
+ * });
3170
+ * ```
3171
+ */
3172
+ async submitTask(taskId, output) {
3173
+ this.logger.info(`Submitting task: ${taskId}`);
3174
+ const response = await this.callApi(`/api/v2/tasks/${taskId}/submit`, {
3175
+ method: "POST",
3176
+ headers: { "Content-Type": "application/json" },
3177
+ body: JSON.stringify({
3178
+ output_text: output.text,
3179
+ output_url: output.url
3180
+ })
3181
+ });
3182
+ const result = await response.json();
3183
+ if (!response.ok) {
3184
+ throw new MixrPayError(result.error || "Failed to submit task");
3185
+ }
3186
+ }
3187
+ /**
3188
+ * Approve a submitted task and pay the agent.
3189
+ *
3190
+ * This will:
3191
+ * - Calculate remaining budget (budget - spent on tools)
3192
+ * - Transfer remaining budget to the agent's wallet
3193
+ * - Mark the task as completed
3194
+ * - Revoke the agent's session
3195
+ *
3196
+ * @param taskId - The task ID
3197
+ * @returns Payout details
3198
+ *
3199
+ * @example
3200
+ * ```typescript
3201
+ * const result = await wallet.approveTask('task_123');
3202
+ * console.log(`Paid agent $${result.payout.amountUsd}`);
3203
+ * ```
3204
+ */
3205
+ async approveTask(taskId) {
3206
+ this.logger.info(`Approving task: ${taskId}`);
3207
+ const response = await this.callApi(`/api/v2/tasks/${taskId}/approve`, {
3208
+ method: "POST"
3209
+ });
3210
+ const result = await response.json();
3211
+ if (!response.ok) {
3212
+ throw new MixrPayError(result.error || "Failed to approve task");
3213
+ }
3214
+ return {
3215
+ success: result.success,
3216
+ task: {
3217
+ id: result.task.id,
3218
+ status: result.task.status,
3219
+ completedAt: new Date(result.task.completed_at)
3220
+ },
3221
+ payout: {
3222
+ status: result.payout.status,
3223
+ amountUsd: result.payout.amount_usd,
3224
+ txHash: result.payout.tx_hash
3225
+ }
3226
+ };
3227
+ }
3228
+ /**
3229
+ * Reject a task submission and send it back for revision.
3230
+ *
3231
+ * @param taskId - The task ID
3232
+ * @param feedback - Optional feedback for the agent
3233
+ */
3234
+ async rejectTaskSubmission(taskId, feedback) {
3235
+ const response = await this.callApi(`/api/v2/tasks/${taskId}/reject`, {
3236
+ method: "POST",
3237
+ headers: { "Content-Type": "application/json" },
3238
+ body: feedback ? JSON.stringify({ feedback }) : "{}"
3239
+ });
3240
+ const result = await response.json();
3241
+ if (!response.ok) {
3242
+ throw new MixrPayError(result.error || "Failed to reject task");
3243
+ }
3244
+ }
3245
+ /**
3246
+ * Cancel a task you created.
3247
+ *
3248
+ * Can only cancel tasks in 'open' or 'claimed' status.
3249
+ * If the task was claimed, the agent's session will be revoked.
3250
+ *
3251
+ * @param taskId - The task ID
3252
+ */
3253
+ async cancelTask(taskId) {
3254
+ const response = await this.callApi(`/api/v2/tasks/${taskId}`, {
3255
+ method: "DELETE"
3256
+ });
3257
+ const result = await response.json();
3258
+ if (!response.ok) {
3259
+ throw new MixrPayError(result.error || "Failed to cancel task");
3260
+ }
3261
+ }
3262
+ // ===========================================================================
3263
+ // Mission Control: Subtask Management
3264
+ // ===========================================================================
3265
+ /**
3266
+ * Create a subtask under an existing task.
3267
+ * Requires session key authentication.
3268
+ * Budget is allocated from the parent task's remaining budget.
3269
+ *
3270
+ * @param options - Subtask creation options
3271
+ * @returns The created subtask
3272
+ *
3273
+ * @example
3274
+ * ```typescript
3275
+ * const subtask = await wallet.createSubtask({
3276
+ * parentTaskId: 'task_123',
3277
+ * title: 'Research competitor pricing',
3278
+ * description: 'Find pricing info for top 5 competitors',
3279
+ * budgetUsd: 10,
3280
+ * });
3281
+ * ```
3282
+ */
3283
+ async createSubtask(options) {
3284
+ this.logger.info(`Creating subtask: ${options.title}`);
3285
+ const response = await this.callApi("/api/v2/tasks/create-subtask", {
3286
+ method: "POST",
3287
+ headers: { "Content-Type": "application/json" },
3288
+ body: JSON.stringify({
3289
+ parent_task_id: options.parentTaskId,
3290
+ title: options.title,
3291
+ description: options.description,
3292
+ deliverables: options.deliverables,
3293
+ category: options.category,
3294
+ budget_usd: options.budgetUsd,
3295
+ allow_sub_agents: options.allowSubAgents,
3296
+ expires_in_days: options.expiresInDays,
3297
+ idempotency_key: options.idempotencyKey
3298
+ })
3299
+ });
3300
+ const result = await response.json();
3301
+ if (!response.ok) {
3302
+ throw new MixrPayError(result.error || "Failed to create subtask");
3303
+ }
3304
+ return this.parseTask(result.task);
3305
+ }
3306
+ /**
3307
+ * Update the status of a task.
3308
+ * Requires session key authentication.
3309
+ * Creates an audit trail via TaskStatusUpdate records.
3310
+ *
3311
+ * @param taskId - The task ID
3312
+ * @param status - New status
3313
+ * @param note - Optional note explaining the status change
3314
+ * @returns The updated task
3315
+ *
3316
+ * @example
3317
+ * ```typescript
3318
+ * await wallet.updateTaskStatus('task_123', 'submitted', 'All deliverables completed');
3319
+ * ```
3320
+ */
3321
+ async updateTaskStatus(taskId, status, note, idempotencyKey) {
3322
+ this.logger.info(`Updating task ${taskId} status to ${status}`);
3323
+ const response = await this.callApi(`/api/v2/tasks/${taskId}/status`, {
3324
+ method: "PATCH",
3325
+ headers: { "Content-Type": "application/json" },
3326
+ body: JSON.stringify({
3327
+ status,
3328
+ note,
3329
+ idempotency_key: idempotencyKey
3330
+ })
3331
+ });
3332
+ const result = await response.json();
3333
+ if (!response.ok) {
3334
+ throw new MixrPayError(result.error || "Failed to update task status");
3335
+ }
3336
+ return this.parseTask(result.task);
3337
+ }
3338
+ /**
3339
+ * Get subtasks of a task.
3340
+ * Requires session key authentication.
3341
+ *
3342
+ * @param taskId - The parent task ID
3343
+ * @returns List of subtasks
3344
+ *
3345
+ * @example
3346
+ * ```typescript
3347
+ * const subtasks = await wallet.getSubtasks('task_123');
3348
+ * console.log(`Found ${subtasks.length} subtasks`);
3349
+ * ```
3350
+ */
3351
+ async getSubtasks(taskId) {
3352
+ const response = await this.callApi(`/api/v2/tasks/${taskId}/subtasks`, {
3353
+ method: "GET"
3354
+ });
3355
+ const result = await response.json();
3356
+ if (!response.ok) {
3357
+ throw new MixrPayError(result.error || "Failed to get subtasks");
3358
+ }
3359
+ return (result.subtasks || []).map((t) => this.parseTask(t));
3360
+ }
3361
+ /**
3362
+ * Get the status history of a task.
3363
+ * Requires session key authentication.
3364
+ *
3365
+ * @param taskId - The task ID
3366
+ * @returns Status history with audit trail
3367
+ *
3368
+ * @example
3369
+ * ```typescript
3370
+ * const history = await wallet.getTaskStatusHistory('task_123');
3371
+ * for (const entry of history.history) {
3372
+ * console.log(`${entry.oldStatus} -> ${entry.newStatus}: ${entry.note}`);
3373
+ * }
3374
+ * ```
3375
+ */
3376
+ async getTaskStatusHistory(taskId) {
3377
+ const response = await this.callApi(`/api/v2/tasks/${taskId}/status`, {
3378
+ method: "GET"
3379
+ });
3380
+ const result = await response.json();
3381
+ if (!response.ok) {
3382
+ throw new MixrPayError(result.error || "Failed to get task status history");
3383
+ }
3384
+ return {
3385
+ taskId: result.task_id,
3386
+ currentStatus: result.current_status,
3387
+ history: (result.history || []).map((h) => ({
3388
+ id: h.id,
3389
+ oldStatus: h.old_status,
3390
+ newStatus: h.new_status,
3391
+ note: h.note,
3392
+ createdAt: new Date(h.created_at)
3393
+ }))
3394
+ };
3395
+ }
3396
+ // ===========================================================================
3397
+ // Mission Control: Approval System
3398
+ // ===========================================================================
3399
+ /**
3400
+ * Request approval from a human user for an action.
3401
+ * Returns immediately if the action is auto-approved.
3402
+ *
3403
+ * @param options - Approval request options
3404
+ * @returns Approval request or auto-approval result
3405
+ *
3406
+ * @example
3407
+ * ```typescript
3408
+ * const result = await wallet.requestApproval({
3409
+ * actionType: 'external_communication',
3410
+ * actionPayload: { recipient: 'user@example.com', message: 'Hello!' },
3411
+ * context: 'Sending welcome email to new customer',
3412
+ * });
3413
+ *
3414
+ * if ('autoApproved' in result) {
3415
+ * console.log('Auto-approved:', result.reason);
3416
+ * } else {
3417
+ * console.log('Waiting for approval:', result.id);
3418
+ * }
3419
+ * ```
3420
+ */
3421
+ async requestApproval(options) {
3422
+ this.logger.info(`Requesting approval for: ${options.actionType}`);
3423
+ const response = await this.callApi("/api/v2/approvals/request", {
3424
+ method: "POST",
3425
+ headers: { "Content-Type": "application/json" },
3426
+ body: JSON.stringify({
3427
+ action_type: options.actionType,
3428
+ action_payload: options.actionPayload,
3429
+ context: options.context,
3430
+ expires_in_hours: options.expiresInHours,
3431
+ idempotency_key: options.idempotencyKey
3432
+ })
3433
+ });
3434
+ const result = await response.json();
3435
+ if (!response.ok) {
3436
+ throw new MixrPayError(result.error || "Failed to request approval");
3437
+ }
3438
+ if (result.auto_approved) {
3439
+ return {
3440
+ autoApproved: true,
3441
+ reason: result.reason
3442
+ };
3443
+ }
3444
+ return this.parseApprovalRequest(result.approval_request);
3445
+ }
3446
+ /**
3447
+ * Check the status of an approval request.
3448
+ *
3449
+ * @param requestId - The approval request ID
3450
+ * @returns Current status of the request
3451
+ *
3452
+ * @example
3453
+ * ```typescript
3454
+ * const status = await wallet.checkApprovalStatus('req_123');
3455
+ * if (status.status === 'approved') {
3456
+ * console.log('Approved! Proceeding with action...');
3457
+ * }
3458
+ * ```
3459
+ */
3460
+ async checkApprovalStatus(requestId) {
3461
+ const response = await this.callApi(`/api/v2/approvals/check?request_id=${requestId}`, {
3462
+ method: "GET"
3463
+ });
3464
+ const result = await response.json();
3465
+ if (!response.ok) {
3466
+ throw new MixrPayError(result.error || "Failed to check approval status");
3467
+ }
3468
+ return {
3469
+ requestId: result.request_id,
3470
+ status: result.status,
3471
+ responseNote: result.response_note,
3472
+ approvalScope: result.approval_scope,
3473
+ respondedAt: result.responded_at ? new Date(result.responded_at) : void 0
3474
+ };
3475
+ }
3476
+ /**
3477
+ * Check if an action type needs approval before execution.
3478
+ *
3479
+ * @param actionType - The type of action to check
3480
+ * @param amountUsd - Optional amount for spend actions
3481
+ * @returns Whether approval is needed
3482
+ *
3483
+ * @example
3484
+ * ```typescript
3485
+ * const check = await wallet.checkActionPermission('external_communication');
3486
+ * if (check.needsApproval) {
3487
+ * const approval = await wallet.requestApproval({ ... });
3488
+ * }
3489
+ * ```
3490
+ */
3491
+ async checkActionPermission(actionType, amountUsd) {
3492
+ let url = `/api/v2/approvals/check?action_type=${encodeURIComponent(actionType)}`;
3493
+ if (amountUsd !== void 0) {
3494
+ url += `&amount_usd=${amountUsd}`;
3495
+ }
3496
+ const response = await this.callApi(url, { method: "GET" });
3497
+ const result = await response.json();
3498
+ if (!response.ok) {
3499
+ throw new MixrPayError(result.error || "Failed to check action permission");
3500
+ }
3501
+ return {
3502
+ actionType: result.action_type,
3503
+ needsApproval: result.needs_approval,
3504
+ reason: result.reason
3505
+ };
3506
+ }
3507
+ /**
3508
+ * Wait for an approval request to be resolved.
3509
+ * Polls the status until approved, rejected, or expired.
3510
+ *
3511
+ * @param requestId - The approval request ID
3512
+ * @param options - Polling options
3513
+ * @returns Final status of the request
3514
+ *
3515
+ * @example
3516
+ * ```typescript
3517
+ * const approval = await wallet.requestApproval({ ... });
3518
+ * if (!('autoApproved' in approval)) {
3519
+ * const result = await wallet.waitForApproval(approval.id, { timeoutMs: 60000 });
3520
+ * if (result.status === 'approved') {
3521
+ * // Proceed with action
3522
+ * }
3523
+ * }
3524
+ * ```
3525
+ */
3526
+ async waitForApproval(requestId, options = {}) {
3527
+ const pollInterval = options.pollIntervalMs || 5e3;
3528
+ const timeout = options.timeoutMs || 3e5;
3529
+ const startTime = Date.now();
3530
+ while (Date.now() - startTime < timeout) {
3531
+ const status = await this.checkApprovalStatus(requestId);
3532
+ if (status.status !== "pending") {
3533
+ return status;
3534
+ }
3535
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
3536
+ }
3537
+ return {
3538
+ requestId,
3539
+ status: "expired"
3540
+ };
3541
+ }
3542
+ /** Helper to parse approval request from API response */
3543
+ parseApprovalRequest(r) {
3544
+ return {
3545
+ id: r.id,
3546
+ actionType: r.action_type,
3547
+ actionPayload: r.action_payload,
3548
+ context: r.context,
3549
+ status: r.status,
3550
+ responseNote: r.response_note,
3551
+ approvalScope: r.approval_scope,
3552
+ createdAt: new Date(r.created_at),
3553
+ expiresAt: new Date(r.expires_at),
3554
+ respondedAt: r.responded_at ? new Date(r.responded_at) : void 0
3555
+ };
3556
+ }
3557
+ /** Helper to parse task from API response */
3558
+ parseTask(t) {
3559
+ return {
3560
+ id: t.id,
3561
+ title: t.title,
3562
+ description: t.description,
3563
+ deliverables: t.deliverables || [],
3564
+ category: t.category,
3565
+ budgetUsd: t.budget_usd,
3566
+ listingFeeUsd: t.listing_fee_usd,
3567
+ status: t.status,
3568
+ createdAt: new Date(t.created_at),
3569
+ updatedAt: t.updated_at ? new Date(t.updated_at) : void 0,
3570
+ expiresAt: t.expires_at ? new Date(t.expires_at) : void 0,
3571
+ claimedAt: t.claimed_at ? new Date(t.claimed_at) : void 0,
3572
+ submittedAt: t.submitted_at ? new Date(t.submitted_at) : void 0,
3573
+ completedAt: t.completed_at ? new Date(t.completed_at) : void 0,
3574
+ creator: t.creator,
3575
+ assignedAgent: t.assigned_agent,
3576
+ requestCount: t.request_count,
3577
+ output: t.output,
3578
+ payment: t.payment
3579
+ };
3580
+ }
3581
+ /** Helper to call our API with auth */
3582
+ async callApi(path, init = {}) {
3583
+ const url = `${this.baseUrl}${path}`;
3584
+ const headers = {
3585
+ ...init.headers
3586
+ };
3587
+ if (this.sessionKey) {
3588
+ const account = privateKeyToAccount2(this.sessionKey.rawPrivateKey);
3589
+ const address = account.address.toLowerCase();
3590
+ const timestamp = Date.now();
3591
+ const message = `MixrPay:${timestamp}:${address}`;
3592
+ const signature = await signMessage({
3593
+ message,
3594
+ privateKey: this.sessionKey.rawPrivateKey
3595
+ });
3596
+ headers["X-Session-Auth"] = JSON.stringify({
3597
+ address,
3598
+ timestamp,
3599
+ signature
3600
+ });
3601
+ }
3602
+ return fetch(url, {
3603
+ ...init,
3604
+ headers
3605
+ });
3606
+ }
2725
3607
  };
2726
3608
  export {
2727
3609
  AgentWallet,