clinch-core 0.1.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/.github/workflows/publish.yml +29 -0
- package/LICENSE +21 -0
- package/README.md +245 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +544 -0
- package/dist/src/index.d.ts +46 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +247 -0
- package/dist/src/index.js.map +1 -0
- package/dist/test.js +70 -0
- package/package.json +28 -0
- package/src/index.d.ts +46 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +247 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +612 -0
- package/test.ts +16 -0
- package/tsconfig.json +14 -0
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import nacl from 'tweetnacl';
|
|
3
|
+
import { sha256 } from 'js-sha256';
|
|
4
|
+
import WebSocket from 'ws';
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// CONFIGURATION & UTILS
|
|
8
|
+
// ============================================================================
|
|
9
|
+
const PROTOCOL_VERSION = "0.1.0";
|
|
10
|
+
const FIREBASE_CONFIG_URL = "https://clinchprotocol.web.app/network-config.json";
|
|
11
|
+
|
|
12
|
+
function toHex(arr: Uint8Array | number[]): string {
|
|
13
|
+
return Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// TYPES & INTERFACES
|
|
18
|
+
// ============================================================================
|
|
19
|
+
export type CoreStatus =
|
|
20
|
+
| 'OFFLINE'
|
|
21
|
+
| 'CONNECTING'
|
|
22
|
+
| 'IDLE'
|
|
23
|
+
| 'RECONNECTING'
|
|
24
|
+
| 'ERROR'
|
|
25
|
+
| 'NEGOTIATING'
|
|
26
|
+
| 'CONVERGED'
|
|
27
|
+
| 'STALEMATE';
|
|
28
|
+
|
|
29
|
+
export interface ParsedAddress {
|
|
30
|
+
mode: string;
|
|
31
|
+
domain: string;
|
|
32
|
+
route: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ClinchConfig {
|
|
36
|
+
registryUrl?: string;
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ConstraintVector {
|
|
41
|
+
intent: string;
|
|
42
|
+
category?: string;
|
|
43
|
+
max_budget: number;
|
|
44
|
+
[key: string]: any;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SessionState {
|
|
48
|
+
sessionId: string;
|
|
49
|
+
sellerId: string;
|
|
50
|
+
keyPair: nacl.SignKeyPair;
|
|
51
|
+
status: 'ACTIVE' | 'EXITED' | 'CLOSED';
|
|
52
|
+
exitTokenHash?: string;
|
|
53
|
+
constraints: ConstraintVector;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SandboxConfig {
|
|
57
|
+
downloadLLM?: boolean;
|
|
58
|
+
modelUrl?: string;
|
|
59
|
+
modelPath?: string;
|
|
60
|
+
maxTurns?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AgentAdapter {
|
|
64
|
+
evaluateOffer(sessionState: any, constraints: any): Promise<any>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// THE CLINCH CORE LIBRARY (Buyer)
|
|
69
|
+
// ============================================================================
|
|
70
|
+
|
|
71
|
+
export class ClinchCore extends EventEmitter {
|
|
72
|
+
private config: ClinchConfig;
|
|
73
|
+
private cachedRegistryUrl: string | null = null;
|
|
74
|
+
|
|
75
|
+
public status: CoreStatus = 'OFFLINE';
|
|
76
|
+
private reconnectAttempts = 0;
|
|
77
|
+
private maxReconnectDelay = 30000;
|
|
78
|
+
|
|
79
|
+
public jwtToken: string | null = null;
|
|
80
|
+
private identityPrivKey: Uint8Array;
|
|
81
|
+
public identityPubKey: string;
|
|
82
|
+
|
|
83
|
+
private activeSessions = new Map<string, SessionState>();
|
|
84
|
+
private ws: WebSocket | null = null;
|
|
85
|
+
|
|
86
|
+
// Sandbox Engine
|
|
87
|
+
private isSandboxMode = false;
|
|
88
|
+
private sandboxContext: any = null;
|
|
89
|
+
private sandboxSequence: any = null;
|
|
90
|
+
private sandboxSession: any = null;
|
|
91
|
+
private sandboxMaxTurns = 6;
|
|
92
|
+
|
|
93
|
+
public currentTurn = 0;
|
|
94
|
+
public lastKnownPrice = 0;
|
|
95
|
+
public activeNegotiationId: string | null = null;
|
|
96
|
+
|
|
97
|
+
constructor(config: ClinchConfig = {}) {
|
|
98
|
+
super();
|
|
99
|
+
this.config = { timeoutMs: 5000, ...config };
|
|
100
|
+
|
|
101
|
+
const keyPair = nacl.sign.keyPair();
|
|
102
|
+
this.identityPrivKey = keyPair.secretKey;
|
|
103
|
+
this.identityPubKey = toHex(keyPair.publicKey);
|
|
104
|
+
|
|
105
|
+
if (this.config.registryUrl) {
|
|
106
|
+
this.cachedRegistryUrl = this.config.registryUrl;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private setStatus(newStatus: CoreStatus) {
|
|
111
|
+
if (this.status !== newStatus) {
|
|
112
|
+
this.status = newStatus;
|
|
113
|
+
this.emit('status_changed', this.status);
|
|
114
|
+
|
|
115
|
+
if (this.status === 'IDLE') this.emit('log', `🟢 [State] ONLINE & IDLE`);
|
|
116
|
+
else if (this.status === 'ERROR') this.emit('log', `🔴 [State] ERROR`);
|
|
117
|
+
else if (this.status === 'NEGOTIATING') this.emit('log', `⚡ [State] NEGOTIATING (Turn ${this.currentTurn})`);
|
|
118
|
+
else this.emit('log', `🟡 [State] ${this.status}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async initialize(cachedToken?: string): Promise<void> {
|
|
123
|
+
if (this.status === 'IDLE' || this.status === 'CONNECTING') return;
|
|
124
|
+
this.setStatus('CONNECTING');
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
this.emit('log', '[Network] Fetching registry configuration...');
|
|
128
|
+
await this.getRegistryUrl();
|
|
129
|
+
|
|
130
|
+
if (cachedToken) {
|
|
131
|
+
this.jwtToken = cachedToken;
|
|
132
|
+
this.emit('log', "[Auth] Restored session from cached JWT. Skipping PoW.");
|
|
133
|
+
} else {
|
|
134
|
+
await this.registerNode();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await this.connectWebSocket();
|
|
138
|
+
this.emit('initialized', { pubKey: this.identityPubKey, registry: this.cachedRegistryUrl });
|
|
139
|
+
} catch (error: any) {
|
|
140
|
+
this.setStatus('ERROR');
|
|
141
|
+
this.emit('error', new Error(`Initialization failed: ${error.message}`));
|
|
142
|
+
setTimeout(() => this.initialize(cachedToken), 5000);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private async getRegistryUrl(forceRefresh = false): Promise<string> {
|
|
147
|
+
if (this.cachedRegistryUrl && !forceRefresh) return this.cachedRegistryUrl;
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
150
|
+
try {
|
|
151
|
+
const res = await fetch(FIREBASE_CONFIG_URL, { signal: controller.signal });
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
const text = await res.text();
|
|
154
|
+
const config = JSON.parse(text);
|
|
155
|
+
this.cachedRegistryUrl = config.registry_nodes[PROTOCOL_VERSION];
|
|
156
|
+
return this.cachedRegistryUrl!;
|
|
157
|
+
} catch (err: any) {
|
|
158
|
+
clearTimeout(timeout);
|
|
159
|
+
throw new Error(`Registry config fetch failed: ${err.message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async networkRequest(endpoint: string, options: any = {}, requireAuth = true): Promise<any> {
|
|
164
|
+
const baseUrl = await this.getRegistryUrl();
|
|
165
|
+
const headers: any = { ...options.headers };
|
|
166
|
+
if (requireAuth && this.jwtToken) headers['Authorization'] = `Bearer ${this.jwtToken}`;
|
|
167
|
+
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
170
|
+
try {
|
|
171
|
+
const res = await fetch(`${baseUrl}${endpoint}`, { ...options, headers, signal: controller.signal });
|
|
172
|
+
clearTimeout(timeout);
|
|
173
|
+
const text = await res.text();
|
|
174
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${text}`);
|
|
175
|
+
return JSON.parse(text);
|
|
176
|
+
} catch (err: any) {
|
|
177
|
+
clearTimeout(timeout);
|
|
178
|
+
throw new Error(`Network request to ${endpoint} failed: ${err.message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private async registerNode(): Promise<void> {
|
|
183
|
+
this.emit('log', "[Auth] Requesting PoW challenge from registry...");
|
|
184
|
+
const challenge = await this.networkRequest('/api/auth/challenge', {}, false);
|
|
185
|
+
const powSolution = await this.solvePoW(challenge.nonce, challenge.difficulty);
|
|
186
|
+
|
|
187
|
+
this.emit('log', "[Auth] Submitting PoW solution...");
|
|
188
|
+
const authRes = await this.networkRequest('/api/auth/verify', {
|
|
189
|
+
method: 'POST',
|
|
190
|
+
headers: { 'Content-Type': 'application/json' },
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
challenge_id: challenge.challenge_id,
|
|
193
|
+
pow_solution: powSolution,
|
|
194
|
+
pubKey: this.identityPubKey
|
|
195
|
+
})
|
|
196
|
+
}, false);
|
|
197
|
+
|
|
198
|
+
this.jwtToken = authRes.token;
|
|
199
|
+
this.emit('token_issued', { token: this.jwtToken });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private async connectWebSocket(): Promise<void> {
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
const wsUrl = this.cachedRegistryUrl!.replace(/^http/, 'ws');
|
|
205
|
+
this.emit('log', `[Network] Connecting to WebSocket at ${wsUrl}...`);
|
|
206
|
+
|
|
207
|
+
this.ws = new WebSocket(wsUrl);
|
|
208
|
+
const connectionTimeout = setTimeout(() => {
|
|
209
|
+
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
210
|
+
this.ws?.close();
|
|
211
|
+
reject(new Error("WebSocket connection timeout"));
|
|
212
|
+
}
|
|
213
|
+
}, this.config.timeoutMs);
|
|
214
|
+
|
|
215
|
+
this.ws.on('open', () => {
|
|
216
|
+
clearTimeout(connectionTimeout);
|
|
217
|
+
this.reconnectAttempts = 0;
|
|
218
|
+
this.emit('log', '[Network] WebSocket connected. Sending AUTH...');
|
|
219
|
+
this.ws!.send(JSON.stringify({ type: 'AUTH', token: this.jwtToken }));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
this.ws.on('message', (data) => {
|
|
223
|
+
const msg = JSON.parse(data.toString());
|
|
224
|
+
if (msg.type === 'AUTH_SUCCESS') {
|
|
225
|
+
this.setStatus('IDLE');
|
|
226
|
+
resolve();
|
|
227
|
+
}
|
|
228
|
+
if (msg.type === 'CALLBACK') {
|
|
229
|
+
this.emit('log', `🔔 [Network] Received callback for session ${msg.session_id}`);
|
|
230
|
+
this.emit('callback_received', { sessionId: msg.session_id, payload: msg.payload });
|
|
231
|
+
this.ws!.send(JSON.stringify({ type: 'ACK', id: msg.id }));
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
this.ws.on('error', (err: any) => {
|
|
236
|
+
clearTimeout(connectionTimeout);
|
|
237
|
+
if (this.status === 'CONNECTING') reject(err);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
this.ws.on('close', () => {
|
|
241
|
+
clearTimeout(connectionTimeout);
|
|
242
|
+
if (this.status !== 'OFFLINE') this.handleReconnect();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private handleReconnect() {
|
|
248
|
+
this.setStatus('RECONNECTING');
|
|
249
|
+
this.reconnectAttempts++;
|
|
250
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
|
|
251
|
+
setTimeout(() => this.connectWebSocket().catch(() => {}), delay);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
public disconnect() {
|
|
255
|
+
this.setStatus('OFFLINE');
|
|
256
|
+
if (this.ws) { this.ws.close(); this.ws = null; }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// --------------------------------------------------------------------------
|
|
260
|
+
// UNIVERSAL PROMPT BUILDER
|
|
261
|
+
// --------------------------------------------------------------------------
|
|
262
|
+
/**
|
|
263
|
+
* Generates a universally formatted System Prompt for external LLMs (Claude, OpenAI, Gemini).
|
|
264
|
+
* Developers can pass this string directly to their AI to ensure protocol-compliant negotiation.
|
|
265
|
+
*/
|
|
266
|
+
public buildAgentPrompt(sessionId: string, incomingMessage: string): string {
|
|
267
|
+
const session = this.activeSessions.get(sessionId);
|
|
268
|
+
if (!session) throw new Error("Cannot build prompt: Session not found.");
|
|
269
|
+
|
|
270
|
+
const gap = this.lastKnownPrice - session.constraints.max_budget;
|
|
271
|
+
const gapText = gap > 0 ? `-$${gap} (Over budget)` : `+$${Math.abs(gap)} (Under budget)`;
|
|
272
|
+
|
|
273
|
+
return `You are a professional AI purchasing agent negotiating via the Clinch Protocol.
|
|
274
|
+
Your only goal is to secure the requested item below the maximum budget.
|
|
275
|
+
|
|
276
|
+
NEGOTIATION STATE:
|
|
277
|
+
- Item: ${session.constraints.item}
|
|
278
|
+
- Category: ${session.constraints.category || 'General'}
|
|
279
|
+
- Your absolute max budget: $${session.constraints.max_budget}
|
|
280
|
+
- Current turn: ${this.currentTurn}
|
|
281
|
+
- Last seller price: $${this.lastKnownPrice}
|
|
282
|
+
- Gap to budget: ${gapText}
|
|
283
|
+
|
|
284
|
+
SELLER'S LATEST MESSAGE:
|
|
285
|
+
"${incomingMessage}"
|
|
286
|
+
|
|
287
|
+
STRATEGY GUIDELINES:
|
|
288
|
+
- Turns 1-2: Open roughly 20-30% below budget to establish an anchor. Be professional but firm.
|
|
289
|
+
- Turns 3-5: Move in small, calculated increments (3-5%).
|
|
290
|
+
- Turn 6+: Issue a final, compelling offer.
|
|
291
|
+
- If the seller's price is at or below your max budget: You MUST choose "accept".
|
|
292
|
+
|
|
293
|
+
OUTPUT FORMAT:
|
|
294
|
+
You MUST respond ONLY in valid JSON matching this exact schema. Do not include markdown blocks (like \`\`\`json).
|
|
295
|
+
{
|
|
296
|
+
"action": "counter" | "accept" | "exit",
|
|
297
|
+
"price": <numeric value, no currency symbols>,
|
|
298
|
+
"message": "<One concise sentence of negotiation dialogue>"
|
|
299
|
+
}`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// --------------------------------------------------------------------------
|
|
303
|
+
// PROTOCOL OPERATIONS
|
|
304
|
+
// --------------------------------------------------------------------------
|
|
305
|
+
async search(query: string, mode?: string): Promise<any> {
|
|
306
|
+
let url = `/api/discover?category=${encodeURIComponent(query)}`;
|
|
307
|
+
if (mode) url += `&mode=${encodeURIComponent(mode)}`;
|
|
308
|
+
return await this.networkRequest(url);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async negotiate(address: string, constraints: ConstraintVector): Promise<string> {
|
|
312
|
+
this.emit('log', `[Protocol] Initiating handshake with ${address}...`);
|
|
313
|
+
const parsed = this.parseAddress(address);
|
|
314
|
+
|
|
315
|
+
const ephemeralKeys = nacl.sign.keyPair();
|
|
316
|
+
const ephemeralPubHex = toHex(ephemeralKeys.publicKey);
|
|
317
|
+
|
|
318
|
+
const initPayload = {
|
|
319
|
+
clinch_version: PROTOCOL_VERSION,
|
|
320
|
+
mode: parsed.mode,
|
|
321
|
+
constraints,
|
|
322
|
+
session_pub_key: ephemeralPubHex,
|
|
323
|
+
timestamp_utc: new Date().toISOString()
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const msgUint8 = new TextEncoder().encode(JSON.stringify(initPayload));
|
|
327
|
+
const signature = toHex(nacl.sign.detached(msgUint8, ephemeralKeys.secretKey));
|
|
328
|
+
|
|
329
|
+
const response = await this.networkRequest(`/api/route/${parsed.domain}/handshake`, {
|
|
330
|
+
method: 'POST',
|
|
331
|
+
headers: { 'Content-Type': 'application/json' },
|
|
332
|
+
body: JSON.stringify({ ...initPayload, sig: signature })
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
this.activeSessions.set(response.session_id, {
|
|
336
|
+
sessionId: response.session_id,
|
|
337
|
+
sellerId: parsed.domain,
|
|
338
|
+
keyPair: ephemeralKeys,
|
|
339
|
+
status: 'ACTIVE',
|
|
340
|
+
constraints
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
this.activeNegotiationId = response.session_id;
|
|
344
|
+
this.currentTurn = 1;
|
|
345
|
+
this.setStatus('NEGOTIATING');
|
|
346
|
+
return response.session_id;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async sendCounter(sessionId: string, price: number, reason: string): Promise<void> {
|
|
350
|
+
const session = this.activeSessions.get(sessionId);
|
|
351
|
+
if (!session) throw new Error("Active session not found");
|
|
352
|
+
|
|
353
|
+
const payload = { session_id: sessionId, turn: this.currentTurn, price, reason };
|
|
354
|
+
const buyer_sig = toHex(nacl.sign.detached(
|
|
355
|
+
new TextEncoder().encode(JSON.stringify(payload)),
|
|
356
|
+
session.keyPair.secretKey
|
|
357
|
+
));
|
|
358
|
+
|
|
359
|
+
await this.networkRequest(`/api/route/${session.sellerId}/counter`, {
|
|
360
|
+
method: 'POST',
|
|
361
|
+
headers: { 'Content-Type': 'application/json' },
|
|
362
|
+
body: JSON.stringify({ ...payload, buyer_sig })
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async exitSession(sessionId: string): Promise<string> {
|
|
367
|
+
const session = this.activeSessions.get(sessionId);
|
|
368
|
+
if (!session) throw new Error("Session not found");
|
|
369
|
+
|
|
370
|
+
const res = await this.networkRequest(`/api/route/${sessionId}/exit`, {
|
|
371
|
+
method: 'POST',
|
|
372
|
+
headers: { 'Content-Type': 'application/json' },
|
|
373
|
+
body: JSON.stringify({ seller_id: session.sellerId })
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
session.status = 'EXITED';
|
|
377
|
+
session.exitTokenHash = res.token_hash;
|
|
378
|
+
return res.token_hash;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// --------------------------------------------------------------------------
|
|
382
|
+
// OUT-OF-THE-BOX SANDBOX (AUTO-TURN ENGINE)
|
|
383
|
+
// --------------------------------------------------------------------------
|
|
384
|
+
async sandbox(config: SandboxConfig = {}): Promise<void> {
|
|
385
|
+
this.isSandboxMode = true;
|
|
386
|
+
this.sandboxMaxTurns = config.maxTurns || 6;
|
|
387
|
+
|
|
388
|
+
await this.initialize();
|
|
389
|
+
await this.setupSandbox(config);
|
|
390
|
+
|
|
391
|
+
this.on('callback_received', async (event) => {
|
|
392
|
+
if (!this.isSandboxMode) return;
|
|
393
|
+
await this.handleAutomaticSandboxTurn(event.sessionId, event.payload);
|
|
394
|
+
});
|
|
395
|
+
this.emit('log', "⚙️ [Sandbox] Auto-Negotiation engine bound and active.");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private async setupSandbox(config: SandboxConfig = {}): Promise<void> {
|
|
399
|
+
const settings = {
|
|
400
|
+
downloadLLM: true,
|
|
401
|
+
modelUrl: "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf",
|
|
402
|
+
modelPath: "./qwen2.5-1.5b-instruct-q4_k_m.gguf",
|
|
403
|
+
...config
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// DYNAMIC IMPORTS: Won't break Webpack/Metro unless sandbox() is actually called!
|
|
407
|
+
let nodeLlama;
|
|
408
|
+
try { nodeLlama = await import('node-llama-cpp'); }
|
|
409
|
+
catch (e) { throw new Error("Sandbox requires 'node-llama-cpp'. Run: npm install node-llama-cpp"); }
|
|
410
|
+
|
|
411
|
+
const fs = await import('fs');
|
|
412
|
+
const path = await import('path');
|
|
413
|
+
const os = await import('os');
|
|
414
|
+
|
|
415
|
+
const resolvedPath = path.resolve(settings.modelPath);
|
|
416
|
+
|
|
417
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
418
|
+
if (!settings.downloadLLM) throw new Error(`Model missing at ${resolvedPath}`);
|
|
419
|
+
this.emit('log', `[Sandbox] Downloading Qwen 1.5B Q4_K_M (1.1GB)...`);
|
|
420
|
+
const { pipeline } = await import('stream/promises');
|
|
421
|
+
const { Readable } = await import('stream');
|
|
422
|
+
|
|
423
|
+
// @ts-ignore
|
|
424
|
+
const response = await fetch(settings.modelUrl);
|
|
425
|
+
if (!response.ok) throw new Error("Fetch failed: " + response.statusText);
|
|
426
|
+
|
|
427
|
+
const fileStream = fs.createWriteStream(resolvedPath);
|
|
428
|
+
await pipeline(Readable.fromWeb(response.body as any), fileStream);
|
|
429
|
+
this.emit('log', "[Sandbox] Download complete.");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const llama = await nodeLlama.getLlama();
|
|
433
|
+
const model = await llama.loadModel({ modelPath: resolvedPath });
|
|
434
|
+
|
|
435
|
+
this.sandboxContext = await model.createContext({
|
|
436
|
+
contextSize: 2048,
|
|
437
|
+
threads: Math.min(4, Math.max(1, os.cpus().length - 1)),
|
|
438
|
+
batchSize: 512
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private async handleAutomaticSandboxTurn(sessionId: string, payload: any) {
|
|
443
|
+
const session = this.activeSessions.get(sessionId);
|
|
444
|
+
if (!session) return;
|
|
445
|
+
|
|
446
|
+
this.currentTurn++;
|
|
447
|
+
this.setStatus('NEGOTIATING');
|
|
448
|
+
|
|
449
|
+
const incomingPrice = this.extractPrice(payload.message || JSON.stringify(payload), 'Counter');
|
|
450
|
+
if (incomingPrice !== null) this.lastKnownPrice = incomingPrice;
|
|
451
|
+
|
|
452
|
+
if (this.lastKnownPrice <= session.constraints.max_budget) {
|
|
453
|
+
this.setStatus('CONVERGED');
|
|
454
|
+
this.activeSessions.delete(sessionId);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (this.currentTurn > this.sandboxMaxTurns) {
|
|
459
|
+
this.setStatus('STALEMATE');
|
|
460
|
+
await this.exitSession(sessionId);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const modelResponse = await this.sandboxEvaluate(session, payload.message || `The price is $${this.lastKnownPrice}`);
|
|
465
|
+
const parsedOffer = this.extractPrice(modelResponse, 'Offer');
|
|
466
|
+
const reasonLine = modelResponse.split('\n').find(l => l.startsWith('Reason:'));
|
|
467
|
+
const reason = reasonLine ? reasonLine.replace('Reason:', '').trim() : "Suggesting a fair counter-offer.";
|
|
468
|
+
|
|
469
|
+
if (parsedOffer !== null) {
|
|
470
|
+
const finalOffer = Math.min(parsedOffer, session.constraints.max_budget);
|
|
471
|
+
await this.sendCounter(sessionId, finalOffer, reason);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private async sandboxEvaluate(session: SessionState, incomingOffer: string): Promise<string> {
|
|
476
|
+
const { LlamaChatSession, ChatMLChatWrapper } = await import('node-llama-cpp');
|
|
477
|
+
|
|
478
|
+
const systemPrompt = this.buildAgentPrompt(session.sessionId, incomingOffer);
|
|
479
|
+
|
|
480
|
+
if (this.sandboxSequence) this.sandboxSequence.dispose();
|
|
481
|
+
this.sandboxSequence = this.sandboxContext.getSequence();
|
|
482
|
+
|
|
483
|
+
this.sandboxSession = new LlamaChatSession({
|
|
484
|
+
contextSequence: this.sandboxSequence,
|
|
485
|
+
systemPrompt: systemPrompt,
|
|
486
|
+
chatWrapper: new ChatMLChatWrapper()
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
let responseText = "";
|
|
490
|
+
await this.sandboxSession.prompt(incomingOffer, {
|
|
491
|
+
maxTokens: 80,
|
|
492
|
+
onTextChunk: (chunk: string) => { responseText += chunk; }
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
return responseText;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// --------------------------------------------------------------------------
|
|
499
|
+
// UTILITIES
|
|
500
|
+
// --------------------------------------------------------------------------
|
|
501
|
+
private extractPrice(text: string, prefix: string): number | null {
|
|
502
|
+
const regex = new RegExp(`${prefix}\\s*\\:?\\s*\\$?(\\d+(?:\\.\\d{2})?)`, 'i');
|
|
503
|
+
const match = text.match(regex);
|
|
504
|
+
if (match) return parseFloat(match[1]);
|
|
505
|
+
|
|
506
|
+
const fallbackRegex = /"price"\s*:\s*(\d+(?:\.\d{2})?)/i;
|
|
507
|
+
const fallbackMatch = text.match(fallbackRegex);
|
|
508
|
+
return fallbackMatch ? parseFloat(fallbackMatch[1]) : null;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
public parseAddress(address: string): ParsedAddress {
|
|
512
|
+
const parts = address.split('.');
|
|
513
|
+
if (parts.length < 2) throw new Error("Invalid Address Format");
|
|
514
|
+
return { mode: parts[0], domain: parts.slice(1).join('.').toLowerCase(), route: '/' };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private async solvePoW(nonce: string, difficultyBits: number): Promise<string> {
|
|
518
|
+
let counter = 0;
|
|
519
|
+
const CHUNK_SIZE = 10000;
|
|
520
|
+
while (true) {
|
|
521
|
+
for (let i = 0; i < CHUNK_SIZE; i++) {
|
|
522
|
+
const attempt = counter.toString();
|
|
523
|
+
const hashArray = sha256.create().update(nonce + this.identityPubKey + attempt).array();
|
|
524
|
+
if (this.hasLeadingZeroBits(hashArray, difficultyBits)) return attempt;
|
|
525
|
+
counter++;
|
|
526
|
+
}
|
|
527
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private hasLeadingZeroBits(hash: number[], bits: number): boolean {
|
|
532
|
+
let zeroBits = 0;
|
|
533
|
+
for (const byte of hash) {
|
|
534
|
+
if (byte === 0) zeroBits += 8;
|
|
535
|
+
else { zeroBits += Math.clz32(byte) - 24; break; }
|
|
536
|
+
}
|
|
537
|
+
return zeroBits >= bits;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ============================================================================
|
|
542
|
+
// THE CLINCH SELLER LIBRARY (Server-Side)
|
|
543
|
+
// ============================================================================
|
|
544
|
+
export interface SellerRecord {
|
|
545
|
+
agent_id: string;
|
|
546
|
+
display_name: string;
|
|
547
|
+
endpoint: string;
|
|
548
|
+
supported_modes: string[];
|
|
549
|
+
categories: string[];
|
|
550
|
+
capabilities: string[];
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export class ClinchSeller extends EventEmitter {
|
|
554
|
+
private config: ClinchConfig;
|
|
555
|
+
private cachedRegistryUrl: string | null = null;
|
|
556
|
+
public sellerAuthToken: string | null = null;
|
|
557
|
+
private identityPrivKey: Uint8Array;
|
|
558
|
+
public identityPubKey: string;
|
|
559
|
+
|
|
560
|
+
constructor(config: ClinchConfig = {}) {
|
|
561
|
+
super();
|
|
562
|
+
this.config = { timeoutMs: 5000, ...config };
|
|
563
|
+
const keyPair = nacl.sign.keyPair();
|
|
564
|
+
this.identityPrivKey = keyPair.secretKey;
|
|
565
|
+
this.identityPubKey = toHex(keyPair.publicKey);
|
|
566
|
+
if (this.config.registryUrl) this.cachedRegistryUrl = this.config.registryUrl;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async authenticate(authToken: string): Promise<void> {
|
|
570
|
+
this.sellerAuthToken = authToken;
|
|
571
|
+
if (!this.cachedRegistryUrl) {
|
|
572
|
+
const res = await fetch(FIREBASE_CONFIG_URL);
|
|
573
|
+
const config = JSON.parse(await res.text());
|
|
574
|
+
this.cachedRegistryUrl = config.registry_nodes[PROTOCOL_VERSION];
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async registerEndpoint(record: SellerRecord): Promise<any> {
|
|
579
|
+
if (!this.sellerAuthToken) throw new Error("Must call authenticate() first.");
|
|
580
|
+
const payload = {
|
|
581
|
+
...record,
|
|
582
|
+
public_key: this.identityPubKey,
|
|
583
|
+
record_sig: this.signData(record)
|
|
584
|
+
};
|
|
585
|
+
const res = await fetch(`${this.cachedRegistryUrl}/api/dashboard/sellers/register`, {
|
|
586
|
+
method: 'POST',
|
|
587
|
+
headers: {
|
|
588
|
+
'Content-Type': 'application/json',
|
|
589
|
+
'Authorization': `Bearer ${this.sellerAuthToken}`
|
|
590
|
+
},
|
|
591
|
+
body: JSON.stringify(payload)
|
|
592
|
+
});
|
|
593
|
+
if (!res.ok) throw new Error(`Registration failed: ${await res.text()}`);
|
|
594
|
+
return await res.json();
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
verifyBuyerSignature(payload: any, signatureHex: string, buyerSessionPubKeyHex: string): boolean {
|
|
598
|
+
try {
|
|
599
|
+
const msgUint8 = new TextEncoder().encode(JSON.stringify(payload));
|
|
600
|
+
const sigUint8 = new Uint8Array(signatureHex.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)));
|
|
601
|
+
const pubKeyUint8 = new Uint8Array(buyerSessionPubKeyHex.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)));
|
|
602
|
+
return nacl.sign.detached.verify(msgUint8, sigUint8, pubKeyUint8);
|
|
603
|
+
} catch (e) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
private signData(data: any): string {
|
|
609
|
+
const msgUint8 = new TextEncoder().encode(JSON.stringify(data));
|
|
610
|
+
return toHex(nacl.sign.detached(msgUint8, this.identityPrivKey));
|
|
611
|
+
}
|
|
612
|
+
}
|
package/test.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const { ClinchCore } = require('./index.js'); // Or wherever your compiled output is
|
|
2
|
+
|
|
3
|
+
const core = new ClinchCore();
|
|
4
|
+
|
|
5
|
+
// Bind all the beautiful new logs directly to your terminal
|
|
6
|
+
core.on('log', (msg) => console.log(msg));
|
|
7
|
+
core.on('error', (err) => console.error(`🚨 [CRITICAL ERROR]:`, err.message));
|
|
8
|
+
|
|
9
|
+
async function run() {
|
|
10
|
+
console.log("=== CLINCH CORE CLIENT STARTING ===\n");
|
|
11
|
+
|
|
12
|
+
// Pass undefined to force PoW, or pass your cached string
|
|
13
|
+
await core.initialize(undefined);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
run();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|