@mnemopay/sdk 0.8.0 → 0.9.0
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/cli/dashboard.d.ts +9 -0
- package/dist/cli/dashboard.d.ts.map +1 -0
- package/dist/cli/dashboard.js +78 -0
- package/dist/cli/dashboard.js.map +1 -0
- package/dist/client.d.ts +182 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +177 -0
- package/dist/client.js.map +1 -0
- package/dist/commerce.d.ts +225 -0
- package/dist/commerce.d.ts.map +1 -0
- package/dist/commerce.js +420 -0
- package/dist/commerce.js.map +1 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +13 -21
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -37
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +298 -6
- package/dist/mcp/server.js.map +1 -1
- package/dist/rails/index.d.ts.map +1 -1
- package/dist/rails/index.js +14 -3
- package/dist/rails/index.js.map +1 -1
- package/dist/rails/paystack.js +2 -2
- package/dist/rails/paystack.js.map +1 -1
- package/package.json +22 -7
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commerce Engine for MnemoPay
|
|
3
|
+
*
|
|
4
|
+
* Autonomous shopping for AI agents. An agent can search products, compare
|
|
5
|
+
* prices, create a purchase order, pay via escrow, and confirm delivery.
|
|
6
|
+
* Every purchase is scoped by a ShoppingMandate — cryptographically enforced
|
|
7
|
+
* spend limits, category restrictions, and merchant whitelists.
|
|
8
|
+
*
|
|
9
|
+
* The escrow flow:
|
|
10
|
+
* 1. Agent searches products → returns ranked results
|
|
11
|
+
* 2. Agent (or user) approves a purchase → creates a PurchaseOrder
|
|
12
|
+
* 3. SDK calls charge() to hold funds in escrow
|
|
13
|
+
* 4. Agent executes the purchase via the commerce provider
|
|
14
|
+
* 5. On delivery confirmation → settle() releases funds
|
|
15
|
+
* 6. On failure/cancellation → refund() returns funds
|
|
16
|
+
*
|
|
17
|
+
* Security model:
|
|
18
|
+
* - ShoppingMandate defines what the agent CAN buy (budget, categories, merchants)
|
|
19
|
+
* - Every purchase validated against mandate before escrow is created
|
|
20
|
+
* - Purchases above approvalThreshold require explicit user confirmation
|
|
21
|
+
* - Full audit trail via MnemoPay's existing audit system
|
|
22
|
+
* - Agent remembers user preferences (sizes, brands, past purchases)
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* const agent = MnemoPay.quick("shopper");
|
|
26
|
+
* const commerce = new CommerceEngine(agent);
|
|
27
|
+
* commerce.setMandate({ budget: 200, categories: ["electronics"] });
|
|
28
|
+
* const results = await commerce.search("USB-C cable under $15");
|
|
29
|
+
* const order = await commerce.purchase(results[0]);
|
|
30
|
+
* // ... later, on delivery ...
|
|
31
|
+
* await commerce.confirmDelivery(order.id);
|
|
32
|
+
*/
|
|
33
|
+
export interface ShoppingMandate {
|
|
34
|
+
/** Maximum total budget for this mandate (USD) */
|
|
35
|
+
budget: number;
|
|
36
|
+
/** Maximum per-item spend (USD). Defaults to budget. */
|
|
37
|
+
maxPerItem?: number;
|
|
38
|
+
/** Allowed product categories (empty = any) */
|
|
39
|
+
categories?: string[];
|
|
40
|
+
/** Blocked product categories */
|
|
41
|
+
blockedCategories?: string[];
|
|
42
|
+
/** Allowed merchant domains (empty = any) */
|
|
43
|
+
allowedMerchants?: string[];
|
|
44
|
+
/** Blocked merchant domains */
|
|
45
|
+
blockedMerchants?: string[];
|
|
46
|
+
/** Purchases above this amount require user approval callback (USD) */
|
|
47
|
+
approvalThreshold?: number;
|
|
48
|
+
/** Mandate expiry (ISO timestamp). Defaults to 24h from creation. */
|
|
49
|
+
expiresAt?: string;
|
|
50
|
+
/** Who issued this mandate (user ID or "system") */
|
|
51
|
+
issuedBy: string;
|
|
52
|
+
}
|
|
53
|
+
export interface ProductResult {
|
|
54
|
+
/** Unique product ID from the provider */
|
|
55
|
+
productId: string;
|
|
56
|
+
/** Product title */
|
|
57
|
+
title: string;
|
|
58
|
+
/** Price in USD */
|
|
59
|
+
price: number;
|
|
60
|
+
/** Currency (default USD) */
|
|
61
|
+
currency?: string;
|
|
62
|
+
/** Product URL */
|
|
63
|
+
url: string;
|
|
64
|
+
/** Image URL */
|
|
65
|
+
imageUrl?: string;
|
|
66
|
+
/** Merchant/seller name */
|
|
67
|
+
merchant: string;
|
|
68
|
+
/** Merchant domain */
|
|
69
|
+
merchantDomain?: string;
|
|
70
|
+
/** Product category */
|
|
71
|
+
category?: string;
|
|
72
|
+
/** Rating (0-5) */
|
|
73
|
+
rating?: number;
|
|
74
|
+
/** Number of reviews */
|
|
75
|
+
reviewCount?: number;
|
|
76
|
+
/** Estimated delivery days */
|
|
77
|
+
deliveryDays?: number;
|
|
78
|
+
/** Whether free shipping */
|
|
79
|
+
freeShipping?: boolean;
|
|
80
|
+
/** Raw provider data */
|
|
81
|
+
raw?: Record<string, unknown>;
|
|
82
|
+
}
|
|
83
|
+
export interface PurchaseOrder {
|
|
84
|
+
/** Unique order ID */
|
|
85
|
+
id: string;
|
|
86
|
+
/** Agent ID that placed the order */
|
|
87
|
+
agentId: string;
|
|
88
|
+
/** Product being purchased */
|
|
89
|
+
product: ProductResult;
|
|
90
|
+
/** MnemoPay transaction ID (escrow) */
|
|
91
|
+
txId: string;
|
|
92
|
+
/** Order status */
|
|
93
|
+
status: "pending_approval" | "escrowed" | "purchased" | "shipped" | "delivered" | "cancelled" | "failed";
|
|
94
|
+
/** Shipping address or delivery instructions */
|
|
95
|
+
deliveryInstructions?: string;
|
|
96
|
+
/** Tracking number */
|
|
97
|
+
trackingNumber?: string;
|
|
98
|
+
/** Tracking URL */
|
|
99
|
+
trackingUrl?: string;
|
|
100
|
+
/** Created timestamp */
|
|
101
|
+
createdAt: Date;
|
|
102
|
+
/** Last updated */
|
|
103
|
+
updatedAt: Date;
|
|
104
|
+
/** Mandate snapshot at time of purchase */
|
|
105
|
+
mandate: ShoppingMandate;
|
|
106
|
+
/** User approval status */
|
|
107
|
+
approved: boolean;
|
|
108
|
+
/** Failure reason if failed */
|
|
109
|
+
failureReason?: string;
|
|
110
|
+
}
|
|
111
|
+
export type ApprovalCallback = (order: PurchaseOrder) => Promise<boolean>;
|
|
112
|
+
/**
|
|
113
|
+
* Product search provider interface. Implement this to connect
|
|
114
|
+
* any product catalog (eBay, custom API, local inventory, etc.)
|
|
115
|
+
*/
|
|
116
|
+
export interface CommerceProvider {
|
|
117
|
+
readonly name: string;
|
|
118
|
+
/** Search for products matching a query */
|
|
119
|
+
search(query: string, options?: SearchOptions): Promise<ProductResult[]>;
|
|
120
|
+
/** Get full product details by ID */
|
|
121
|
+
getProduct(productId: string): Promise<ProductResult | null>;
|
|
122
|
+
/** Execute a purchase (returns external order reference) */
|
|
123
|
+
executePurchase(product: ProductResult, deliveryInstructions?: string): Promise<{
|
|
124
|
+
externalOrderId: string;
|
|
125
|
+
status: string;
|
|
126
|
+
trackingUrl?: string;
|
|
127
|
+
}>;
|
|
128
|
+
/** Check order/delivery status */
|
|
129
|
+
checkStatus(externalOrderId: string): Promise<{
|
|
130
|
+
status: string;
|
|
131
|
+
trackingNumber?: string;
|
|
132
|
+
trackingUrl?: string;
|
|
133
|
+
deliveredAt?: string;
|
|
134
|
+
}>;
|
|
135
|
+
}
|
|
136
|
+
export interface SearchOptions {
|
|
137
|
+
/** Maximum results to return (default: 10) */
|
|
138
|
+
limit?: number;
|
|
139
|
+
/** Minimum price filter */
|
|
140
|
+
minPrice?: number;
|
|
141
|
+
/** Maximum price filter */
|
|
142
|
+
maxPrice?: number;
|
|
143
|
+
/** Category filter */
|
|
144
|
+
category?: string;
|
|
145
|
+
/** Sort by: "price_asc", "price_desc", "rating", "relevance" */
|
|
146
|
+
sortBy?: "price_asc" | "price_desc" | "rating" | "relevance";
|
|
147
|
+
/** Free shipping only */
|
|
148
|
+
freeShippingOnly?: boolean;
|
|
149
|
+
}
|
|
150
|
+
export declare class MockCommerceProvider implements CommerceProvider {
|
|
151
|
+
readonly name = "mock";
|
|
152
|
+
private counter;
|
|
153
|
+
private catalog;
|
|
154
|
+
search(query: string, options?: SearchOptions): Promise<ProductResult[]>;
|
|
155
|
+
getProduct(productId: string): Promise<ProductResult | null>;
|
|
156
|
+
executePurchase(product: ProductResult, _deliveryInstructions?: string): Promise<{
|
|
157
|
+
externalOrderId: string;
|
|
158
|
+
status: string;
|
|
159
|
+
trackingUrl?: string;
|
|
160
|
+
}>;
|
|
161
|
+
checkStatus(externalOrderId: string): Promise<{
|
|
162
|
+
status: string;
|
|
163
|
+
trackingNumber?: string;
|
|
164
|
+
trackingUrl?: string;
|
|
165
|
+
deliveredAt?: string;
|
|
166
|
+
}>;
|
|
167
|
+
}
|
|
168
|
+
export declare class CommerceEngine {
|
|
169
|
+
private agent;
|
|
170
|
+
private provider;
|
|
171
|
+
private mandate;
|
|
172
|
+
private orders;
|
|
173
|
+
private totalSpent;
|
|
174
|
+
private approvalCallback;
|
|
175
|
+
private externalOrderMap;
|
|
176
|
+
constructor(agent: any, provider?: CommerceProvider);
|
|
177
|
+
/**
|
|
178
|
+
* Set a shopping mandate that defines what this agent can buy.
|
|
179
|
+
* All purchases are validated against this mandate.
|
|
180
|
+
*/
|
|
181
|
+
setMandate(mandate: ShoppingMandate): void;
|
|
182
|
+
/** Get the current active mandate */
|
|
183
|
+
getMandate(): ShoppingMandate | null;
|
|
184
|
+
/** Remaining budget under current mandate */
|
|
185
|
+
get remainingBudget(): number;
|
|
186
|
+
/** Set a callback for user approval of high-value purchases */
|
|
187
|
+
onApprovalRequired(callback: ApprovalCallback): void;
|
|
188
|
+
/**
|
|
189
|
+
* Search for products. Uses agent memory to personalize results
|
|
190
|
+
* (e.g., preferred brands, past purchase satisfaction).
|
|
191
|
+
*/
|
|
192
|
+
search(query: string, options?: SearchOptions): Promise<ProductResult[]>;
|
|
193
|
+
/**
|
|
194
|
+
* Create a purchase order for a product. Validates against mandate,
|
|
195
|
+
* creates escrow hold, optionally requests user approval.
|
|
196
|
+
*/
|
|
197
|
+
purchase(product: ProductResult, deliveryInstructions?: string): Promise<PurchaseOrder>;
|
|
198
|
+
/**
|
|
199
|
+
* Confirm delivery of an order. Settles the escrow, releasing funds.
|
|
200
|
+
*/
|
|
201
|
+
confirmDelivery(orderId: string): Promise<PurchaseOrder>;
|
|
202
|
+
/**
|
|
203
|
+
* Cancel an order and refund the escrow.
|
|
204
|
+
*/
|
|
205
|
+
cancelOrder(orderId: string, reason?: string): Promise<PurchaseOrder>;
|
|
206
|
+
/**
|
|
207
|
+
* Check delivery status of an order via the commerce provider.
|
|
208
|
+
*/
|
|
209
|
+
checkDeliveryStatus(orderId: string): Promise<PurchaseOrder>;
|
|
210
|
+
/** Get an order by ID */
|
|
211
|
+
getOrder(orderId: string): PurchaseOrder | null;
|
|
212
|
+
/** List all orders */
|
|
213
|
+
listOrders(status?: PurchaseOrder["status"]): PurchaseOrder[];
|
|
214
|
+
/** Get spending summary under current mandate */
|
|
215
|
+
spendingSummary(): {
|
|
216
|
+
totalSpent: number;
|
|
217
|
+
remainingBudget: number;
|
|
218
|
+
orderCount: number;
|
|
219
|
+
deliveredCount: number;
|
|
220
|
+
pendingCount: number;
|
|
221
|
+
};
|
|
222
|
+
/** Validate a product against the current mandate. Returns null if OK, or violation string. */
|
|
223
|
+
private validateProduct;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=commerce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commerce.d.ts","sourceRoot":"","sources":["../src/commerce.ts"],"names":[],"mappings":"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"}
|
package/dist/commerce.js
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Commerce Engine for MnemoPay
|
|
4
|
+
*
|
|
5
|
+
* Autonomous shopping for AI agents. An agent can search products, compare
|
|
6
|
+
* prices, create a purchase order, pay via escrow, and confirm delivery.
|
|
7
|
+
* Every purchase is scoped by a ShoppingMandate — cryptographically enforced
|
|
8
|
+
* spend limits, category restrictions, and merchant whitelists.
|
|
9
|
+
*
|
|
10
|
+
* The escrow flow:
|
|
11
|
+
* 1. Agent searches products → returns ranked results
|
|
12
|
+
* 2. Agent (or user) approves a purchase → creates a PurchaseOrder
|
|
13
|
+
* 3. SDK calls charge() to hold funds in escrow
|
|
14
|
+
* 4. Agent executes the purchase via the commerce provider
|
|
15
|
+
* 5. On delivery confirmation → settle() releases funds
|
|
16
|
+
* 6. On failure/cancellation → refund() returns funds
|
|
17
|
+
*
|
|
18
|
+
* Security model:
|
|
19
|
+
* - ShoppingMandate defines what the agent CAN buy (budget, categories, merchants)
|
|
20
|
+
* - Every purchase validated against mandate before escrow is created
|
|
21
|
+
* - Purchases above approvalThreshold require explicit user confirmation
|
|
22
|
+
* - Full audit trail via MnemoPay's existing audit system
|
|
23
|
+
* - Agent remembers user preferences (sizes, brands, past purchases)
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* const agent = MnemoPay.quick("shopper");
|
|
27
|
+
* const commerce = new CommerceEngine(agent);
|
|
28
|
+
* commerce.setMandate({ budget: 200, categories: ["electronics"] });
|
|
29
|
+
* const results = await commerce.search("USB-C cable under $15");
|
|
30
|
+
* const order = await commerce.purchase(results[0]);
|
|
31
|
+
* // ... later, on delivery ...
|
|
32
|
+
* await commerce.confirmDelivery(order.id);
|
|
33
|
+
*/
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.CommerceEngine = exports.MockCommerceProvider = void 0;
|
|
36
|
+
// ─── Mock Commerce Provider (for testing/development) ───────────────────────
|
|
37
|
+
class MockCommerceProvider {
|
|
38
|
+
name = "mock";
|
|
39
|
+
counter = 0;
|
|
40
|
+
catalog = [
|
|
41
|
+
{ productId: "mock-001", title: "USB-C Cable 6ft", price: 9.99, url: "https://example.com/usbc", merchant: "TechStore", merchantDomain: "techstore.com", category: "electronics", rating: 4.5, reviewCount: 2340, deliveryDays: 3, freeShipping: true },
|
|
42
|
+
{ productId: "mock-002", title: "Wireless Mouse", price: 24.99, url: "https://example.com/mouse", merchant: "TechStore", merchantDomain: "techstore.com", category: "electronics", rating: 4.2, reviewCount: 890, deliveryDays: 2, freeShipping: true },
|
|
43
|
+
{ productId: "mock-003", title: "Laptop Stand Adjustable", price: 34.99, url: "https://example.com/stand", merchant: "OfficeGear", merchantDomain: "officegear.com", category: "office", rating: 4.7, reviewCount: 5600, deliveryDays: 5, freeShipping: false },
|
|
44
|
+
{ productId: "mock-004", title: "Mechanical Keyboard", price: 79.99, url: "https://example.com/keyboard", merchant: "KeysRUs", merchantDomain: "keysrus.com", category: "electronics", rating: 4.8, reviewCount: 12000, deliveryDays: 3, freeShipping: true },
|
|
45
|
+
{ productId: "mock-005", title: "Noise Cancelling Headphones", price: 199.99, url: "https://example.com/headphones", merchant: "AudioWorld", merchantDomain: "audioworld.com", category: "electronics", rating: 4.6, reviewCount: 8900, deliveryDays: 4, freeShipping: true },
|
|
46
|
+
];
|
|
47
|
+
async search(query, options) {
|
|
48
|
+
const q = query.toLowerCase();
|
|
49
|
+
let results = this.catalog.filter(p => p.title.toLowerCase().includes(q) ||
|
|
50
|
+
p.category?.toLowerCase().includes(q) ||
|
|
51
|
+
q.includes(p.category?.toLowerCase() ?? ""));
|
|
52
|
+
// If no keyword match, return all (simulating broad search)
|
|
53
|
+
if (results.length === 0)
|
|
54
|
+
results = [...this.catalog];
|
|
55
|
+
// Apply filters
|
|
56
|
+
if (options?.minPrice !== undefined)
|
|
57
|
+
results = results.filter(p => p.price >= options.minPrice);
|
|
58
|
+
if (options?.maxPrice !== undefined)
|
|
59
|
+
results = results.filter(p => p.price <= options.maxPrice);
|
|
60
|
+
if (options?.category)
|
|
61
|
+
results = results.filter(p => p.category === options.category);
|
|
62
|
+
if (options?.freeShippingOnly)
|
|
63
|
+
results = results.filter(p => p.freeShipping);
|
|
64
|
+
// Sort
|
|
65
|
+
if (options?.sortBy === "price_asc")
|
|
66
|
+
results.sort((a, b) => a.price - b.price);
|
|
67
|
+
else if (options?.sortBy === "price_desc")
|
|
68
|
+
results.sort((a, b) => b.price - a.price);
|
|
69
|
+
else if (options?.sortBy === "rating")
|
|
70
|
+
results.sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0));
|
|
71
|
+
return results.slice(0, options?.limit ?? 10);
|
|
72
|
+
}
|
|
73
|
+
async getProduct(productId) {
|
|
74
|
+
return this.catalog.find(p => p.productId === productId) ?? null;
|
|
75
|
+
}
|
|
76
|
+
async executePurchase(product, _deliveryInstructions) {
|
|
77
|
+
return {
|
|
78
|
+
externalOrderId: `mock_order_${++this.counter}`,
|
|
79
|
+
status: "confirmed",
|
|
80
|
+
trackingUrl: `https://example.com/track/${this.counter}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async checkStatus(externalOrderId) {
|
|
84
|
+
return {
|
|
85
|
+
status: "delivered",
|
|
86
|
+
trackingNumber: `TRACK${externalOrderId.replace(/\D/g, "")}`,
|
|
87
|
+
trackingUrl: `https://example.com/track/${externalOrderId}`,
|
|
88
|
+
deliveredAt: new Date().toISOString(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.MockCommerceProvider = MockCommerceProvider;
|
|
93
|
+
// ─── Commerce Engine ────────────────────────────────────────────────────────
|
|
94
|
+
class CommerceEngine {
|
|
95
|
+
agent; // MnemoPayLite — avoid circular import
|
|
96
|
+
provider;
|
|
97
|
+
mandate = null;
|
|
98
|
+
orders = new Map();
|
|
99
|
+
totalSpent = 0;
|
|
100
|
+
approvalCallback = null;
|
|
101
|
+
externalOrderMap = new Map(); // externalOrderId → orderId
|
|
102
|
+
constructor(agent, provider) {
|
|
103
|
+
this.agent = agent;
|
|
104
|
+
this.provider = provider ?? new MockCommerceProvider();
|
|
105
|
+
}
|
|
106
|
+
// ── Mandate Management ──────────────────────────────────────────────────
|
|
107
|
+
/**
|
|
108
|
+
* Set a shopping mandate that defines what this agent can buy.
|
|
109
|
+
* All purchases are validated against this mandate.
|
|
110
|
+
*/
|
|
111
|
+
setMandate(mandate) {
|
|
112
|
+
if (!mandate.budget || mandate.budget <= 0) {
|
|
113
|
+
throw new Error("Mandate budget must be positive");
|
|
114
|
+
}
|
|
115
|
+
if (!mandate.issuedBy) {
|
|
116
|
+
throw new Error("Mandate must have an issuer (issuedBy)");
|
|
117
|
+
}
|
|
118
|
+
this.mandate = {
|
|
119
|
+
...mandate,
|
|
120
|
+
maxPerItem: mandate.maxPerItem ?? mandate.budget,
|
|
121
|
+
expiresAt: mandate.expiresAt ?? new Date(Date.now() + 24 * 60 * 60_000).toISOString(),
|
|
122
|
+
};
|
|
123
|
+
this.totalSpent = 0;
|
|
124
|
+
this.agent.audit("commerce:mandate:set", {
|
|
125
|
+
budget: mandate.budget,
|
|
126
|
+
categories: mandate.categories,
|
|
127
|
+
issuedBy: mandate.issuedBy,
|
|
128
|
+
expiresAt: this.mandate.expiresAt,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/** Get the current active mandate */
|
|
132
|
+
getMandate() {
|
|
133
|
+
return this.mandate;
|
|
134
|
+
}
|
|
135
|
+
/** Remaining budget under current mandate */
|
|
136
|
+
get remainingBudget() {
|
|
137
|
+
if (!this.mandate)
|
|
138
|
+
return 0;
|
|
139
|
+
return Math.max(0, this.mandate.budget - this.totalSpent);
|
|
140
|
+
}
|
|
141
|
+
/** Set a callback for user approval of high-value purchases */
|
|
142
|
+
onApprovalRequired(callback) {
|
|
143
|
+
this.approvalCallback = callback;
|
|
144
|
+
}
|
|
145
|
+
// ── Product Search ──────────────────────────────────────────────────────
|
|
146
|
+
/**
|
|
147
|
+
* Search for products. Uses agent memory to personalize results
|
|
148
|
+
* (e.g., preferred brands, past purchase satisfaction).
|
|
149
|
+
*/
|
|
150
|
+
async search(query, options) {
|
|
151
|
+
if (!this.mandate) {
|
|
152
|
+
throw new Error("No shopping mandate set. Call setMandate() first.");
|
|
153
|
+
}
|
|
154
|
+
// Check mandate expiry
|
|
155
|
+
if (new Date() > new Date(this.mandate.expiresAt)) {
|
|
156
|
+
throw new Error("Shopping mandate has expired");
|
|
157
|
+
}
|
|
158
|
+
// Apply mandate constraints to search
|
|
159
|
+
const constrainedOptions = {
|
|
160
|
+
...options,
|
|
161
|
+
maxPrice: options?.maxPrice
|
|
162
|
+
? Math.min(options.maxPrice, this.mandate.maxPerItem)
|
|
163
|
+
: this.mandate.maxPerItem,
|
|
164
|
+
};
|
|
165
|
+
// Recall user preferences from memory to enhance search
|
|
166
|
+
let preferences = "";
|
|
167
|
+
try {
|
|
168
|
+
const memories = await this.agent.recall(query, 3);
|
|
169
|
+
if (memories.length > 0) {
|
|
170
|
+
preferences = memories.map((m) => m.content).join("; ");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Memory recall is best-effort for commerce
|
|
175
|
+
}
|
|
176
|
+
const results = await this.provider.search(query, constrainedOptions);
|
|
177
|
+
// Filter by mandate restrictions
|
|
178
|
+
return results.filter(p => this.validateProduct(p) === null);
|
|
179
|
+
}
|
|
180
|
+
// ── Purchase Flow ───────────────────────────────────────────────────────
|
|
181
|
+
/**
|
|
182
|
+
* Create a purchase order for a product. Validates against mandate,
|
|
183
|
+
* creates escrow hold, optionally requests user approval.
|
|
184
|
+
*/
|
|
185
|
+
async purchase(product, deliveryInstructions) {
|
|
186
|
+
if (!this.mandate) {
|
|
187
|
+
throw new Error("No shopping mandate set. Call setMandate() first.");
|
|
188
|
+
}
|
|
189
|
+
// Validate mandate expiry
|
|
190
|
+
if (new Date() > new Date(this.mandate.expiresAt)) {
|
|
191
|
+
throw new Error("Shopping mandate has expired");
|
|
192
|
+
}
|
|
193
|
+
// Validate product against mandate
|
|
194
|
+
const violation = this.validateProduct(product);
|
|
195
|
+
if (violation) {
|
|
196
|
+
throw new Error(`Mandate violation: ${violation}`);
|
|
197
|
+
}
|
|
198
|
+
// Check remaining budget
|
|
199
|
+
if (product.price > this.remainingBudget) {
|
|
200
|
+
throw new Error(`Insufficient mandate budget: $${product.price.toFixed(2)} exceeds remaining $${this.remainingBudget.toFixed(2)}`);
|
|
201
|
+
}
|
|
202
|
+
const orderId = `order_${this.agent.agentId}_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
203
|
+
const now = new Date();
|
|
204
|
+
const order = {
|
|
205
|
+
id: orderId,
|
|
206
|
+
agentId: this.agent.agentId,
|
|
207
|
+
product,
|
|
208
|
+
txId: "", // Set after escrow
|
|
209
|
+
status: "pending_approval",
|
|
210
|
+
deliveryInstructions,
|
|
211
|
+
createdAt: now,
|
|
212
|
+
updatedAt: now,
|
|
213
|
+
mandate: { ...this.mandate },
|
|
214
|
+
approved: false,
|
|
215
|
+
};
|
|
216
|
+
// Check if user approval is needed
|
|
217
|
+
const threshold = this.mandate.approvalThreshold;
|
|
218
|
+
if (threshold !== undefined && product.price >= threshold && this.approvalCallback) {
|
|
219
|
+
const approved = await this.approvalCallback(order);
|
|
220
|
+
if (!approved) {
|
|
221
|
+
order.status = "cancelled";
|
|
222
|
+
order.failureReason = "User declined approval";
|
|
223
|
+
this.orders.set(orderId, order);
|
|
224
|
+
this.agent.audit("commerce:purchase:declined", { orderId, product: product.title, price: product.price });
|
|
225
|
+
return order;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
order.approved = true;
|
|
229
|
+
// Create escrow hold via MnemoPay charge
|
|
230
|
+
const tx = await this.agent.charge(product.price, `Purchase: ${product.title} from ${product.merchant}`);
|
|
231
|
+
order.txId = tx.id;
|
|
232
|
+
order.status = "escrowed";
|
|
233
|
+
order.updatedAt = new Date();
|
|
234
|
+
this.orders.set(orderId, order);
|
|
235
|
+
this.agent.audit("commerce:purchase:escrowed", {
|
|
236
|
+
orderId,
|
|
237
|
+
txId: tx.id,
|
|
238
|
+
product: product.title,
|
|
239
|
+
price: product.price,
|
|
240
|
+
merchant: product.merchant,
|
|
241
|
+
});
|
|
242
|
+
// Execute the actual purchase via provider
|
|
243
|
+
try {
|
|
244
|
+
const result = await this.provider.executePurchase(product, deliveryInstructions);
|
|
245
|
+
order.status = "purchased";
|
|
246
|
+
order.trackingUrl = result.trackingUrl;
|
|
247
|
+
order.updatedAt = new Date();
|
|
248
|
+
this.externalOrderMap.set(result.externalOrderId, orderId);
|
|
249
|
+
this.agent.audit("commerce:purchase:executed", {
|
|
250
|
+
orderId,
|
|
251
|
+
externalOrderId: result.externalOrderId,
|
|
252
|
+
status: result.status,
|
|
253
|
+
});
|
|
254
|
+
// Remember the purchase for future preference learning
|
|
255
|
+
await this.agent.remember(`Purchased "${product.title}" from ${product.merchant} for $${product.price.toFixed(2)}. Category: ${product.category ?? "unknown"}.`, { importance: 0.6, tags: ["purchase", product.category ?? "shopping"] });
|
|
256
|
+
this.totalSpent += product.price;
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
// Purchase failed — refund escrow
|
|
260
|
+
order.status = "failed";
|
|
261
|
+
order.failureReason = err.message;
|
|
262
|
+
order.updatedAt = new Date();
|
|
263
|
+
try {
|
|
264
|
+
await this.agent.refund(tx.id);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// Refund best-effort, log it
|
|
268
|
+
this.agent.audit("commerce:refund:failed", { orderId, txId: tx.id });
|
|
269
|
+
}
|
|
270
|
+
this.agent.audit("commerce:purchase:failed", {
|
|
271
|
+
orderId,
|
|
272
|
+
reason: err.message,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return order;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Confirm delivery of an order. Settles the escrow, releasing funds.
|
|
279
|
+
*/
|
|
280
|
+
async confirmDelivery(orderId) {
|
|
281
|
+
const order = this.orders.get(orderId);
|
|
282
|
+
if (!order)
|
|
283
|
+
throw new Error(`Order not found: ${orderId}`);
|
|
284
|
+
if (order.status === "delivered")
|
|
285
|
+
throw new Error("Order already delivered");
|
|
286
|
+
if (order.status === "cancelled" || order.status === "failed") {
|
|
287
|
+
throw new Error(`Cannot confirm delivery on ${order.status} order`);
|
|
288
|
+
}
|
|
289
|
+
// Settle the escrow
|
|
290
|
+
await this.agent.settle(order.txId);
|
|
291
|
+
order.status = "delivered";
|
|
292
|
+
order.updatedAt = new Date();
|
|
293
|
+
this.agent.audit("commerce:delivery:confirmed", {
|
|
294
|
+
orderId,
|
|
295
|
+
txId: order.txId,
|
|
296
|
+
product: order.product.title,
|
|
297
|
+
price: order.product.price,
|
|
298
|
+
});
|
|
299
|
+
// Reinforce memory of successful purchase
|
|
300
|
+
await this.agent.remember(`Delivery confirmed: "${order.product.title}" from ${order.product.merchant}. Satisfied.`, { importance: 0.5, tags: ["delivery", "satisfied"] });
|
|
301
|
+
return order;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Cancel an order and refund the escrow.
|
|
305
|
+
*/
|
|
306
|
+
async cancelOrder(orderId, reason) {
|
|
307
|
+
const order = this.orders.get(orderId);
|
|
308
|
+
if (!order)
|
|
309
|
+
throw new Error(`Order not found: ${orderId}`);
|
|
310
|
+
if (order.status === "delivered")
|
|
311
|
+
throw new Error("Cannot cancel a delivered order");
|
|
312
|
+
if (order.status === "cancelled")
|
|
313
|
+
throw new Error("Order already cancelled");
|
|
314
|
+
// Refund escrow
|
|
315
|
+
if (order.txId) {
|
|
316
|
+
await this.agent.refund(order.txId);
|
|
317
|
+
this.totalSpent = Math.max(0, this.totalSpent - order.product.price);
|
|
318
|
+
}
|
|
319
|
+
order.status = "cancelled";
|
|
320
|
+
order.failureReason = reason ?? "Cancelled by user";
|
|
321
|
+
order.updatedAt = new Date();
|
|
322
|
+
this.agent.audit("commerce:order:cancelled", {
|
|
323
|
+
orderId,
|
|
324
|
+
txId: order.txId,
|
|
325
|
+
reason: order.failureReason,
|
|
326
|
+
});
|
|
327
|
+
return order;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Check delivery status of an order via the commerce provider.
|
|
331
|
+
*/
|
|
332
|
+
async checkDeliveryStatus(orderId) {
|
|
333
|
+
const order = this.orders.get(orderId);
|
|
334
|
+
if (!order)
|
|
335
|
+
throw new Error(`Order not found: ${orderId}`);
|
|
336
|
+
// Find external order ID
|
|
337
|
+
let externalOrderId;
|
|
338
|
+
for (const [extId, oId] of this.externalOrderMap) {
|
|
339
|
+
if (oId === orderId) {
|
|
340
|
+
externalOrderId = extId;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (externalOrderId) {
|
|
345
|
+
const status = await this.provider.checkStatus(externalOrderId);
|
|
346
|
+
if (status.trackingNumber)
|
|
347
|
+
order.trackingNumber = status.trackingNumber;
|
|
348
|
+
if (status.trackingUrl)
|
|
349
|
+
order.trackingUrl = status.trackingUrl;
|
|
350
|
+
if (status.status === "delivered" && order.status !== "delivered") {
|
|
351
|
+
order.status = "shipped"; // Mark shipped, user still needs to confirmDelivery()
|
|
352
|
+
}
|
|
353
|
+
else if (status.status === "shipped" && order.status === "purchased") {
|
|
354
|
+
order.status = "shipped";
|
|
355
|
+
}
|
|
356
|
+
order.updatedAt = new Date();
|
|
357
|
+
}
|
|
358
|
+
return order;
|
|
359
|
+
}
|
|
360
|
+
// ── Order Management ────────────────────────────────────────────────────
|
|
361
|
+
/** Get an order by ID */
|
|
362
|
+
getOrder(orderId) {
|
|
363
|
+
return this.orders.get(orderId) ?? null;
|
|
364
|
+
}
|
|
365
|
+
/** List all orders */
|
|
366
|
+
listOrders(status) {
|
|
367
|
+
const all = Array.from(this.orders.values());
|
|
368
|
+
if (status)
|
|
369
|
+
return all.filter(o => o.status === status);
|
|
370
|
+
return all.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
371
|
+
}
|
|
372
|
+
/** Get spending summary under current mandate */
|
|
373
|
+
spendingSummary() {
|
|
374
|
+
const orders = Array.from(this.orders.values());
|
|
375
|
+
return {
|
|
376
|
+
totalSpent: this.totalSpent,
|
|
377
|
+
remainingBudget: this.remainingBudget,
|
|
378
|
+
orderCount: orders.length,
|
|
379
|
+
deliveredCount: orders.filter(o => o.status === "delivered").length,
|
|
380
|
+
pendingCount: orders.filter(o => ["escrowed", "purchased", "shipped"].includes(o.status)).length,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
// ── Validation ──────────────────────────────────────────────────────────
|
|
384
|
+
/** Validate a product against the current mandate. Returns null if OK, or violation string. */
|
|
385
|
+
validateProduct(product) {
|
|
386
|
+
if (!this.mandate)
|
|
387
|
+
return "No mandate set";
|
|
388
|
+
// Price check
|
|
389
|
+
if (product.price > this.mandate.maxPerItem) {
|
|
390
|
+
return `Price $${product.price.toFixed(2)} exceeds per-item limit $${this.mandate.maxPerItem.toFixed(2)}`;
|
|
391
|
+
}
|
|
392
|
+
// Category check
|
|
393
|
+
const cat = product.category?.toLowerCase();
|
|
394
|
+
if (cat && this.mandate.blockedCategories?.length) {
|
|
395
|
+
if (this.mandate.blockedCategories.some(c => c.toLowerCase() === cat)) {
|
|
396
|
+
return `Category "${product.category}" is blocked`;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (cat && this.mandate.categories?.length) {
|
|
400
|
+
if (!this.mandate.categories.some(c => c.toLowerCase() === cat)) {
|
|
401
|
+
return `Category "${product.category}" not in allowed list`;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// Merchant check
|
|
405
|
+
const domain = product.merchantDomain?.toLowerCase();
|
|
406
|
+
if (domain && this.mandate.blockedMerchants?.length) {
|
|
407
|
+
if (this.mandate.blockedMerchants.some(m => m.toLowerCase() === domain)) {
|
|
408
|
+
return `Merchant "${product.merchant}" is blocked`;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (domain && this.mandate.allowedMerchants?.length) {
|
|
412
|
+
if (!this.mandate.allowedMerchants.some(m => m.toLowerCase() === domain)) {
|
|
413
|
+
return `Merchant "${product.merchant}" not in allowed list`;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
exports.CommerceEngine = CommerceEngine;
|
|
420
|
+
//# sourceMappingURL=commerce.js.map
|