clinch-core 0.4.0 → 0.6.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/README.md +16 -7
- package/dist/index.js +20 -25
- package/package.json +1 -1
- package/src/index.ts +19 -29
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ npm install node-llama-cpp
|
|
|
40
40
|
|
|
41
41
|
### Core Statuses (`CoreStatus`)
|
|
42
42
|
* `OFFLINE`: Client is disconnected.
|
|
43
|
-
* `CONNECTING`: Resolving registry
|
|
43
|
+
* `CONNECTING`: Resolving registry configuration and performing Proof-of-Work auth.
|
|
44
44
|
* `IDLE`: Connected and authenticated. Waiting for handshakes or callbacks.
|
|
45
45
|
* `RECONNECTING`: Socket connection lost; executing exponential backoff.
|
|
46
46
|
* `NEGOTIATING`: Active turn-based negotiation sequence in progress.
|
|
@@ -86,6 +86,7 @@ async function startLocalAgent() {
|
|
|
86
86
|
await core.sandbox({ downloadLLM: true });
|
|
87
87
|
|
|
88
88
|
// 2. Initiate Negotiation: Session automatically transitions to 'NEGOTIATING'
|
|
89
|
+
// Format: PROTOCOL_MODE.domain.anp (e.g., ANP/C.amazon.anp)
|
|
89
90
|
const sessionId = await core.negotiate('ANP/C.amazon.anp', {
|
|
90
91
|
intent: 'purchase',
|
|
91
92
|
category: 'electronics',
|
|
@@ -147,6 +148,7 @@ webhookCore.on('callback_received', async (event) => {
|
|
|
147
148
|
console.log("Deal reached!");
|
|
148
149
|
} else {
|
|
149
150
|
await webhookCore.sendCounter(event.sessionId, aiDecision.price, aiDecision.message);
|
|
151
|
+
// Reserialize and update DB after turn to maintain state sync
|
|
150
152
|
await db.update(event.sessionId, webhookCore.exportSessionState(event.sessionId));
|
|
151
153
|
}
|
|
152
154
|
});
|
|
@@ -168,7 +170,10 @@ const app = express();
|
|
|
168
170
|
app.use(express.json());
|
|
169
171
|
|
|
170
172
|
// Initialize with your permanent Dashboard-generated Private Key
|
|
171
|
-
const seller = new ClinchSeller({
|
|
173
|
+
const seller = new ClinchSeller({
|
|
174
|
+
privateKeyHex: process.env.SELLER_PRIVATE_KEY
|
|
175
|
+
// registryUrl: 'http://localhost:7860' // Optional: Override for local dev
|
|
176
|
+
});
|
|
172
177
|
|
|
173
178
|
// Publish your routing endpoint to the network on boot
|
|
174
179
|
await seller.registerEndpoint({
|
|
@@ -206,19 +211,20 @@ app.listen(8080);
|
|
|
206
211
|
### Buyer Client (`ClinchCore`)
|
|
207
212
|
|
|
208
213
|
#### `new ClinchCore(config)`
|
|
209
|
-
* `config.registryUrl` *(string)*: Override
|
|
214
|
+
* `config.registryUrl` *(string)*: Override default dynamic configuration resolution for local testing.
|
|
210
215
|
* `config.timeoutMs` *(number)*: Connection timeout limit (Default: `5000`ms).
|
|
211
216
|
|
|
212
217
|
#### `async initialize(cachedToken?)`
|
|
213
|
-
Authenticates the node, completes Identity-Bound PoW, and connects the WebSocket.
|
|
218
|
+
Authenticates the node on the network, completes Identity-Bound PoW, and connects the WebSocket.
|
|
219
|
+
* `cachedToken` *(string)*: Optional. Re-use an existing network token to bypass PoW calculations on restart.
|
|
214
220
|
|
|
215
221
|
#### `async negotiate(address, constraints)`
|
|
216
222
|
Launches a cryptographic session handshake. Returns `sessionId`.
|
|
217
|
-
* `address` *(string)*: e.g
|
|
218
|
-
* `constraints` *(ConstraintVector)*: Must include `max_budget` (number).
|
|
223
|
+
* `address` *(string)*: Target seller address. Must include the protocol mode prefix (e.g., `ANP/C.amazon.anp`).
|
|
224
|
+
* `constraints` *(ConstraintVector)*: Must include `max_budget` (number) and `item` (string).
|
|
219
225
|
|
|
220
226
|
#### `exportSessionState(sessionId)` / `importSessionState(serializedData)`
|
|
221
|
-
Serializes the active session—including the ephemeral cryptographic keys, current turn, and LLM context parameters—to a JSON string. Used to scale instances horizontally
|
|
227
|
+
Serializes the active session—including the ephemeral cryptographic keys, current turn, and LLM context parameters—to a JSON string. Used to scale instances horizontally, survive pod restarts, or pick up negotiations across async callback windows.
|
|
222
228
|
|
|
223
229
|
#### `buildAgentPrompt(sessionId, incomingMessage)`
|
|
224
230
|
Returns a highly-optimized, state-aware string to pass to an external LLM as a System Prompt. Ensures the LLM outputs strict JSON matching the protocol rules.
|
|
@@ -232,10 +238,13 @@ Closes the active connection and generates a single-use re-engagement Callback t
|
|
|
232
238
|
#### `async sandbox(config)`
|
|
233
239
|
Initializes the edge-AI execution context, downloads the GGUF, and auto-listens.
|
|
234
240
|
|
|
241
|
+
---
|
|
242
|
+
|
|
235
243
|
### Seller Client (`ClinchSeller`)
|
|
236
244
|
|
|
237
245
|
#### `new ClinchSeller(config)`
|
|
238
246
|
* `config.privateKeyHex` *(string)*: The Ed25519 private key generated from the Clinch Dashboard. Used to cryptographically authenticate endpoint updates.
|
|
247
|
+
* `config.registryUrl` *(string)*: Optional. Overrides Registry configuration resolution for local testing.
|
|
239
248
|
|
|
240
249
|
#### `async registerEndpoint(record)`
|
|
241
250
|
Publishes the seller's DNS-style record to the Registry so buyers can discover and route to it. Signed locally via the Ed25519 identity key.
|
package/dist/index.js
CHANGED
|
@@ -50,7 +50,12 @@ function toHex(arr) {
|
|
|
50
50
|
return Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
51
51
|
}
|
|
52
52
|
function fromHex(hex) {
|
|
53
|
-
|
|
53
|
+
// Strips all spaces, newlines, and rogue quotes from .env strings
|
|
54
|
+
const clean = hex.replace(/[^0-9a-fA-F]/g, '');
|
|
55
|
+
const match = clean.match(/.{1,2}/g);
|
|
56
|
+
if (!match)
|
|
57
|
+
return new Uint8Array(0);
|
|
58
|
+
return new Uint8Array(match.map(byte => parseInt(byte, 16)));
|
|
54
59
|
}
|
|
55
60
|
// ============================================================================
|
|
56
61
|
// THE CLINCH CORE LIBRARY (Buyer)
|
|
@@ -66,11 +71,9 @@ class ClinchCore extends events_1.EventEmitter {
|
|
|
66
71
|
identityPubKey;
|
|
67
72
|
activeSessions = new Map();
|
|
68
73
|
ws = null;
|
|
69
|
-
// Sandbox Engine Base (Model/Context are global, Sequences are per-session)
|
|
70
74
|
isSandboxMode = false;
|
|
71
75
|
sandboxModelContext = null;
|
|
72
76
|
sandboxMaxTurns = 6;
|
|
73
|
-
// Legacy support for single-tenant applications checking the last ID
|
|
74
77
|
get activeNegotiationId() {
|
|
75
78
|
return Array.from(this.activeSessions.keys()).pop() || null;
|
|
76
79
|
}
|
|
@@ -228,9 +231,6 @@ class ClinchCore extends events_1.EventEmitter {
|
|
|
228
231
|
this.ws = null;
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
|
-
// --------------------------------------------------------------------------
|
|
232
|
-
// SESSION STATE MANAGEMENT (For Enterprise Horizontal Scaling & Reconnects)
|
|
233
|
-
// --------------------------------------------------------------------------
|
|
234
234
|
exportSessionState(sessionId) {
|
|
235
235
|
const session = this.activeSessions.get(sessionId);
|
|
236
236
|
if (!session)
|
|
@@ -266,9 +266,6 @@ class ClinchCore extends events_1.EventEmitter {
|
|
|
266
266
|
getSession(sessionId) {
|
|
267
267
|
return this.activeSessions.get(sessionId);
|
|
268
268
|
}
|
|
269
|
-
// --------------------------------------------------------------------------
|
|
270
|
-
// UNIVERSAL PROMPT BUILDER
|
|
271
|
-
// --------------------------------------------------------------------------
|
|
272
269
|
buildAgentPrompt(sessionId, incomingMessage) {
|
|
273
270
|
const session = this.activeSessions.get(sessionId);
|
|
274
271
|
if (!session)
|
|
@@ -303,9 +300,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
303
300
|
"message": "<One concise sentence of negotiation dialogue>"
|
|
304
301
|
}`;
|
|
305
302
|
}
|
|
306
|
-
// --------------------------------------------------------------------------
|
|
307
|
-
// PROTOCOL OPERATIONS
|
|
308
|
-
// --------------------------------------------------------------------------
|
|
309
303
|
async search(query, mode) {
|
|
310
304
|
let url = `/api/discover?category=${encodeURIComponent(query)}`;
|
|
311
305
|
if (mode)
|
|
@@ -355,7 +349,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
355
349
|
headers: { 'Content-Type': 'application/json' },
|
|
356
350
|
body: JSON.stringify({ ...payload, buyer_sig })
|
|
357
351
|
});
|
|
358
|
-
// Sync state if deal reached
|
|
359
352
|
if (response.msg_type === 'accept' || response.status === 'COMMITTED') {
|
|
360
353
|
session.status = 'CLOSED';
|
|
361
354
|
session.lastKnownPrice = response.price || price;
|
|
@@ -375,9 +368,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
375
368
|
session.exitTokenHash = res.token_hash;
|
|
376
369
|
return res.token_hash;
|
|
377
370
|
}
|
|
378
|
-
// --------------------------------------------------------------------------
|
|
379
|
-
// OUT-OF-THE-BOX SANDBOX (AUTO-TURN ENGINE)
|
|
380
|
-
// --------------------------------------------------------------------------
|
|
381
371
|
async sandbox(config = {}) {
|
|
382
372
|
this.isSandboxMode = true;
|
|
383
373
|
this.sandboxMaxTurns = config.maxTurns || 6;
|
|
@@ -392,7 +382,7 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
392
382
|
}
|
|
393
383
|
async setupSandbox(config = {}) {
|
|
394
384
|
if (this.sandboxModelContext)
|
|
395
|
-
return;
|
|
385
|
+
return;
|
|
396
386
|
const settings = {
|
|
397
387
|
downloadLLM: true,
|
|
398
388
|
modelUrl: "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf",
|
|
@@ -475,7 +465,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
475
465
|
async sandboxEvaluate(session, incomingOffer) {
|
|
476
466
|
const { LlamaChatSession, ChatMLChatWrapper } = await Promise.resolve().then(() => __importStar(require('node-llama-cpp')));
|
|
477
467
|
const systemPrompt = this.buildAgentPrompt(session.sessionId, incomingOffer);
|
|
478
|
-
// State Isolation: Bind sequence to the session, not the global class!
|
|
479
468
|
if (!session.sandboxSequence) {
|
|
480
469
|
session.sandboxSequence = this.sandboxModelContext.getSequence();
|
|
481
470
|
session.sandboxSession = new LlamaChatSession({
|
|
@@ -491,9 +480,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
491
480
|
});
|
|
492
481
|
return responseText;
|
|
493
482
|
}
|
|
494
|
-
// --------------------------------------------------------------------------
|
|
495
|
-
// UTILITIES
|
|
496
|
-
// --------------------------------------------------------------------------
|
|
497
483
|
extractPrice(text, prefix) {
|
|
498
484
|
const regex = new RegExp(`${prefix}\\s*\\:?\\s*\\$?(\\d+(?:\\.\\d{2})?)`, 'i');
|
|
499
485
|
const match = text.match(regex);
|
|
@@ -550,10 +536,19 @@ class ClinchSeller extends events_1.EventEmitter {
|
|
|
550
536
|
super();
|
|
551
537
|
this.config = { timeoutMs: 8000, ...config };
|
|
552
538
|
if (config.privateKeyHex) {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
539
|
+
try {
|
|
540
|
+
const cleanHex = config.privateKeyHex.replace(/[^0-9a-fA-F]/g, '');
|
|
541
|
+
if (cleanHex.length !== 128) {
|
|
542
|
+
throw new Error(`Expected 128 hex chars (64 bytes), got ${cleanHex.length}`);
|
|
543
|
+
}
|
|
544
|
+
this.identityPrivKey = fromHex(cleanHex);
|
|
545
|
+
const kp = tweetnacl_1.default.sign.keyPair.fromSecretKey(this.identityPrivKey);
|
|
546
|
+
this.identityPubKey = toHex(kp.publicKey);
|
|
547
|
+
this.emit('log', `[Seller] Loaded permanent identity. PubKey: ${this.identityPubKey.substring(0, 12)}...`);
|
|
548
|
+
}
|
|
549
|
+
catch (e) {
|
|
550
|
+
throw new Error(`[Seller] Invalid privateKeyHex in constructor: ${e.message}`);
|
|
551
|
+
}
|
|
557
552
|
}
|
|
558
553
|
else {
|
|
559
554
|
const kp = tweetnacl_1.default.sign.keyPair();
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -14,7 +14,11 @@ function toHex(arr: Uint8Array | number[]): string {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function fromHex(hex: string): Uint8Array {
|
|
17
|
-
|
|
17
|
+
// Strips all spaces, newlines, and rogue quotes from .env strings
|
|
18
|
+
const clean = hex.replace(/[^0-9a-fA-F]/g, '');
|
|
19
|
+
const match = clean.match(/.{1,2}/g);
|
|
20
|
+
if (!match) return new Uint8Array(0);
|
|
21
|
+
return new Uint8Array(match.map(byte => parseInt(byte, 16)));
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
// ============================================================================
|
|
@@ -56,11 +60,9 @@ export interface SessionState {
|
|
|
56
60
|
exitTokenHash?: string;
|
|
57
61
|
constraints: ConstraintVector;
|
|
58
62
|
|
|
59
|
-
// Isolated State Tracking (Crucial for concurrency)
|
|
60
63
|
currentTurn: number;
|
|
61
64
|
lastKnownPrice: number;
|
|
62
65
|
|
|
63
|
-
// Local LLM Context Tracking
|
|
64
66
|
sandboxSequence?: any;
|
|
65
67
|
sandboxSession?: any;
|
|
66
68
|
}
|
|
@@ -95,12 +97,10 @@ export class ClinchCore extends EventEmitter {
|
|
|
95
97
|
private activeSessions = new Map<string, SessionState>();
|
|
96
98
|
private ws: WebSocket | null = null;
|
|
97
99
|
|
|
98
|
-
// Sandbox Engine Base (Model/Context are global, Sequences are per-session)
|
|
99
100
|
private isSandboxMode = false;
|
|
100
101
|
private sandboxModelContext: any = null;
|
|
101
102
|
private sandboxMaxTurns = 6;
|
|
102
103
|
|
|
103
|
-
// Legacy support for single-tenant applications checking the last ID
|
|
104
104
|
public get activeNegotiationId(): string | null {
|
|
105
105
|
return Array.from(this.activeSessions.keys()).pop() || null;
|
|
106
106
|
}
|
|
@@ -266,9 +266,6 @@ export class ClinchCore extends EventEmitter {
|
|
|
266
266
|
if (this.ws) { this.ws.close(); this.ws = null; }
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
// --------------------------------------------------------------------------
|
|
270
|
-
// SESSION STATE MANAGEMENT (For Enterprise Horizontal Scaling & Reconnects)
|
|
271
|
-
// --------------------------------------------------------------------------
|
|
272
269
|
public exportSessionState(sessionId: string): string {
|
|
273
270
|
const session = this.activeSessions.get(sessionId);
|
|
274
271
|
if (!session) throw new Error("Session not found");
|
|
@@ -289,7 +286,6 @@ export class ClinchCore extends EventEmitter {
|
|
|
289
286
|
|
|
290
287
|
public importSessionState(serializedData: string): void {
|
|
291
288
|
const data = JSON.parse(serializedData);
|
|
292
|
-
|
|
293
289
|
const secretKey = fromHex(data.ephemeralSecretKeyHex);
|
|
294
290
|
const keyPair = nacl.sign.keyPair.fromSecretKey(secretKey);
|
|
295
291
|
|
|
@@ -311,9 +307,6 @@ export class ClinchCore extends EventEmitter {
|
|
|
311
307
|
return this.activeSessions.get(sessionId);
|
|
312
308
|
}
|
|
313
309
|
|
|
314
|
-
// --------------------------------------------------------------------------
|
|
315
|
-
// UNIVERSAL PROMPT BUILDER
|
|
316
|
-
// --------------------------------------------------------------------------
|
|
317
310
|
public buildAgentPrompt(sessionId: string, incomingMessage: string): string {
|
|
318
311
|
const session = this.activeSessions.get(sessionId);
|
|
319
312
|
if (!session) throw new Error("Cannot build prompt: Session not found.");
|
|
@@ -350,9 +343,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
350
343
|
}`;
|
|
351
344
|
}
|
|
352
345
|
|
|
353
|
-
// --------------------------------------------------------------------------
|
|
354
|
-
// PROTOCOL OPERATIONS
|
|
355
|
-
// --------------------------------------------------------------------------
|
|
356
346
|
async search(query: string, mode?: string): Promise<any> {
|
|
357
347
|
let url = `/api/discover?category=${encodeURIComponent(query)}`;
|
|
358
348
|
if (mode) url += `&mode=${encodeURIComponent(mode)}`;
|
|
@@ -414,7 +404,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
414
404
|
body: JSON.stringify({ ...payload, buyer_sig })
|
|
415
405
|
});
|
|
416
406
|
|
|
417
|
-
// Sync state if deal reached
|
|
418
407
|
if (response.msg_type === 'accept' || response.status === 'COMMITTED') {
|
|
419
408
|
session.status = 'CLOSED';
|
|
420
409
|
session.lastKnownPrice = response.price || price;
|
|
@@ -437,9 +426,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
437
426
|
return res.token_hash;
|
|
438
427
|
}
|
|
439
428
|
|
|
440
|
-
// --------------------------------------------------------------------------
|
|
441
|
-
// OUT-OF-THE-BOX SANDBOX (AUTO-TURN ENGINE)
|
|
442
|
-
// --------------------------------------------------------------------------
|
|
443
429
|
async sandbox(config: SandboxConfig = {}): Promise<void> {
|
|
444
430
|
this.isSandboxMode = true;
|
|
445
431
|
this.sandboxMaxTurns = config.maxTurns || 6;
|
|
@@ -455,7 +441,7 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
455
441
|
}
|
|
456
442
|
|
|
457
443
|
private async setupSandbox(config: SandboxConfig = {}): Promise<void> {
|
|
458
|
-
if (this.sandboxModelContext) return;
|
|
444
|
+
if (this.sandboxModelContext) return;
|
|
459
445
|
|
|
460
446
|
const settings = {
|
|
461
447
|
downloadLLM: true,
|
|
@@ -546,7 +532,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
546
532
|
|
|
547
533
|
const systemPrompt = this.buildAgentPrompt(session.sessionId, incomingOffer);
|
|
548
534
|
|
|
549
|
-
// State Isolation: Bind sequence to the session, not the global class!
|
|
550
535
|
if (!session.sandboxSequence) {
|
|
551
536
|
session.sandboxSequence = this.sandboxModelContext.getSequence();
|
|
552
537
|
session.sandboxSession = new LlamaChatSession({
|
|
@@ -565,9 +550,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
|
|
|
565
550
|
return responseText;
|
|
566
551
|
}
|
|
567
552
|
|
|
568
|
-
// --------------------------------------------------------------------------
|
|
569
|
-
// UTILITIES
|
|
570
|
-
// --------------------------------------------------------------------------
|
|
571
553
|
private extractPrice(text: string, prefix: string): number | null {
|
|
572
554
|
const regex = new RegExp(`${prefix}\\s*\\:?\\s*\\$?(\\d+(?:\\.\\d{2})?)`, 'i');
|
|
573
555
|
const match = text.match(regex);
|
|
@@ -623,7 +605,7 @@ export interface SellerRecord {
|
|
|
623
605
|
supported_modes: string[];
|
|
624
606
|
categories: string[];
|
|
625
607
|
capabilities: string[];
|
|
626
|
-
display_name?: string;
|
|
608
|
+
display_name?: string;
|
|
627
609
|
}
|
|
628
610
|
|
|
629
611
|
export class ClinchSeller extends EventEmitter {
|
|
@@ -637,10 +619,18 @@ export class ClinchSeller extends EventEmitter {
|
|
|
637
619
|
this.config = { timeoutMs: 8000, ...config };
|
|
638
620
|
|
|
639
621
|
if (config.privateKeyHex) {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
622
|
+
try {
|
|
623
|
+
const cleanHex = config.privateKeyHex.replace(/[^0-9a-fA-F]/g, '');
|
|
624
|
+
if (cleanHex.length !== 128) {
|
|
625
|
+
throw new Error(`Expected 128 hex chars (64 bytes), got ${cleanHex.length}`);
|
|
626
|
+
}
|
|
627
|
+
this.identityPrivKey = fromHex(cleanHex);
|
|
628
|
+
const kp = nacl.sign.keyPair.fromSecretKey(this.identityPrivKey);
|
|
629
|
+
this.identityPubKey = toHex(kp.publicKey);
|
|
630
|
+
this.emit('log', `[Seller] Loaded permanent identity. PubKey: ${this.identityPubKey.substring(0, 12)}...`);
|
|
631
|
+
} catch (e: any) {
|
|
632
|
+
throw new Error(`[Seller] Invalid privateKeyHex in constructor: ${e.message}`);
|
|
633
|
+
}
|
|
644
634
|
} else {
|
|
645
635
|
const kp = nacl.sign.keyPair();
|
|
646
636
|
this.identityPrivKey = kp.secretKey;
|