clinch-core 0.3.0 → 0.5.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/index.d.ts CHANGED
@@ -51,6 +51,7 @@ export declare class ClinchCore extends EventEmitter {
51
51
  private isSandboxMode;
52
52
  private sandboxModelContext;
53
53
  private sandboxMaxTurns;
54
+ get activeNegotiationId(): string | null;
54
55
  constructor(config?: ClinchConfig);
55
56
  private setStatus;
56
57
  initialize(cachedToken?: string): Promise<void>;
@@ -83,6 +84,7 @@ export interface SellerRecord {
83
84
  supported_modes: string[];
84
85
  categories: string[];
85
86
  capabilities: string[];
87
+ display_name?: string;
86
88
  }
87
89
  export declare class ClinchSeller extends EventEmitter {
88
90
  private config;
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
- return new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
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,10 +71,12 @@ 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;
77
+ get activeNegotiationId() {
78
+ return Array.from(this.activeSessions.keys()).pop() || null;
79
+ }
73
80
  constructor(config = {}) {
74
81
  super();
75
82
  this.config = { timeoutMs: 5000, ...config };
@@ -224,9 +231,6 @@ class ClinchCore extends events_1.EventEmitter {
224
231
  this.ws = null;
225
232
  }
226
233
  }
227
- // --------------------------------------------------------------------------
228
- // SESSION STATE MANAGEMENT (For Enterprise Horizontal Scaling & Reconnects)
229
- // --------------------------------------------------------------------------
230
234
  exportSessionState(sessionId) {
231
235
  const session = this.activeSessions.get(sessionId);
232
236
  if (!session)
@@ -262,9 +266,6 @@ class ClinchCore extends events_1.EventEmitter {
262
266
  getSession(sessionId) {
263
267
  return this.activeSessions.get(sessionId);
264
268
  }
265
- // --------------------------------------------------------------------------
266
- // UNIVERSAL PROMPT BUILDER
267
- // --------------------------------------------------------------------------
268
269
  buildAgentPrompt(sessionId, incomingMessage) {
269
270
  const session = this.activeSessions.get(sessionId);
270
271
  if (!session)
@@ -299,9 +300,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
299
300
  "message": "<One concise sentence of negotiation dialogue>"
300
301
  }`;
301
302
  }
302
- // --------------------------------------------------------------------------
303
- // PROTOCOL OPERATIONS
304
- // --------------------------------------------------------------------------
305
303
  async search(query, mode) {
306
304
  let url = `/api/discover?category=${encodeURIComponent(query)}`;
307
305
  if (mode)
@@ -351,7 +349,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
351
349
  headers: { 'Content-Type': 'application/json' },
352
350
  body: JSON.stringify({ ...payload, buyer_sig })
353
351
  });
354
- // Sync state if deal reached
355
352
  if (response.msg_type === 'accept' || response.status === 'COMMITTED') {
356
353
  session.status = 'CLOSED';
357
354
  session.lastKnownPrice = response.price || price;
@@ -371,9 +368,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
371
368
  session.exitTokenHash = res.token_hash;
372
369
  return res.token_hash;
373
370
  }
374
- // --------------------------------------------------------------------------
375
- // OUT-OF-THE-BOX SANDBOX (AUTO-TURN ENGINE)
376
- // --------------------------------------------------------------------------
377
371
  async sandbox(config = {}) {
378
372
  this.isSandboxMode = true;
379
373
  this.sandboxMaxTurns = config.maxTurns || 6;
@@ -388,7 +382,7 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
388
382
  }
389
383
  async setupSandbox(config = {}) {
390
384
  if (this.sandboxModelContext)
391
- return; // Already initialized
385
+ return;
392
386
  const settings = {
393
387
  downloadLLM: true,
394
388
  modelUrl: "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf",
@@ -471,7 +465,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
471
465
  async sandboxEvaluate(session, incomingOffer) {
472
466
  const { LlamaChatSession, ChatMLChatWrapper } = await Promise.resolve().then(() => __importStar(require('node-llama-cpp')));
473
467
  const systemPrompt = this.buildAgentPrompt(session.sessionId, incomingOffer);
474
- // State Isolation: Bind sequence to the session, not the global class!
475
468
  if (!session.sandboxSequence) {
476
469
  session.sandboxSequence = this.sandboxModelContext.getSequence();
477
470
  session.sandboxSession = new LlamaChatSession({
@@ -487,9 +480,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
487
480
  });
488
481
  return responseText;
489
482
  }
490
- // --------------------------------------------------------------------------
491
- // UTILITIES
492
- // --------------------------------------------------------------------------
493
483
  extractPrice(text, prefix) {
494
484
  const regex = new RegExp(`${prefix}\\s*\\:?\\s*\\$?(\\d+(?:\\.\\d{2})?)`, 'i');
495
485
  const match = text.match(regex);
@@ -546,10 +536,19 @@ class ClinchSeller extends events_1.EventEmitter {
546
536
  super();
547
537
  this.config = { timeoutMs: 8000, ...config };
548
538
  if (config.privateKeyHex) {
549
- this.identityPrivKey = fromHex(config.privateKeyHex);
550
- const kp = tweetnacl_1.default.sign.keyPair.fromSecretKey(this.identityPrivKey);
551
- this.identityPubKey = toHex(kp.publicKey);
552
- this.emit('log', `[Seller] Loaded permanent identity. PubKey: ${this.identityPubKey.substring(0, 12)}...`);
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
+ }
553
552
  }
554
553
  else {
555
554
  const kp = tweetnacl_1.default.sign.keyPair();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clinch-core",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
 
5
5
  "description": "Clinch Protocol Edge Client",
6
6
  "main": "dist/index.js",
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
- return new Uint8Array(hex.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)));
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,11 +97,14 @@ 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
 
104
+ public get activeNegotiationId(): string | null {
105
+ return Array.from(this.activeSessions.keys()).pop() || null;
106
+ }
107
+
103
108
  constructor(config: ClinchConfig = {}) {
104
109
  super();
105
110
  this.config = { timeoutMs: 5000, ...config };
@@ -261,9 +266,6 @@ export class ClinchCore extends EventEmitter {
261
266
  if (this.ws) { this.ws.close(); this.ws = null; }
262
267
  }
263
268
 
264
- // --------------------------------------------------------------------------
265
- // SESSION STATE MANAGEMENT (For Enterprise Horizontal Scaling & Reconnects)
266
- // --------------------------------------------------------------------------
267
269
  public exportSessionState(sessionId: string): string {
268
270
  const session = this.activeSessions.get(sessionId);
269
271
  if (!session) throw new Error("Session not found");
@@ -284,7 +286,6 @@ export class ClinchCore extends EventEmitter {
284
286
 
285
287
  public importSessionState(serializedData: string): void {
286
288
  const data = JSON.parse(serializedData);
287
-
288
289
  const secretKey = fromHex(data.ephemeralSecretKeyHex);
289
290
  const keyPair = nacl.sign.keyPair.fromSecretKey(secretKey);
290
291
 
@@ -306,9 +307,6 @@ export class ClinchCore extends EventEmitter {
306
307
  return this.activeSessions.get(sessionId);
307
308
  }
308
309
 
309
- // --------------------------------------------------------------------------
310
- // UNIVERSAL PROMPT BUILDER
311
- // --------------------------------------------------------------------------
312
310
  public buildAgentPrompt(sessionId: string, incomingMessage: string): string {
313
311
  const session = this.activeSessions.get(sessionId);
314
312
  if (!session) throw new Error("Cannot build prompt: Session not found.");
@@ -345,9 +343,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
345
343
  }`;
346
344
  }
347
345
 
348
- // --------------------------------------------------------------------------
349
- // PROTOCOL OPERATIONS
350
- // --------------------------------------------------------------------------
351
346
  async search(query: string, mode?: string): Promise<any> {
352
347
  let url = `/api/discover?category=${encodeURIComponent(query)}`;
353
348
  if (mode) url += `&mode=${encodeURIComponent(mode)}`;
@@ -409,7 +404,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
409
404
  body: JSON.stringify({ ...payload, buyer_sig })
410
405
  });
411
406
 
412
- // Sync state if deal reached
413
407
  if (response.msg_type === 'accept' || response.status === 'COMMITTED') {
414
408
  session.status = 'CLOSED';
415
409
  session.lastKnownPrice = response.price || price;
@@ -432,9 +426,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
432
426
  return res.token_hash;
433
427
  }
434
428
 
435
- // --------------------------------------------------------------------------
436
- // OUT-OF-THE-BOX SANDBOX (AUTO-TURN ENGINE)
437
- // --------------------------------------------------------------------------
438
429
  async sandbox(config: SandboxConfig = {}): Promise<void> {
439
430
  this.isSandboxMode = true;
440
431
  this.sandboxMaxTurns = config.maxTurns || 6;
@@ -450,7 +441,7 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
450
441
  }
451
442
 
452
443
  private async setupSandbox(config: SandboxConfig = {}): Promise<void> {
453
- if (this.sandboxModelContext) return; // Already initialized
444
+ if (this.sandboxModelContext) return;
454
445
 
455
446
  const settings = {
456
447
  downloadLLM: true,
@@ -541,7 +532,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
541
532
 
542
533
  const systemPrompt = this.buildAgentPrompt(session.sessionId, incomingOffer);
543
534
 
544
- // State Isolation: Bind sequence to the session, not the global class!
545
535
  if (!session.sandboxSequence) {
546
536
  session.sandboxSequence = this.sandboxModelContext.getSequence();
547
537
  session.sandboxSession = new LlamaChatSession({
@@ -560,9 +550,6 @@ You MUST respond ONLY in valid JSON matching this exact schema. Do not include m
560
550
  return responseText;
561
551
  }
562
552
 
563
- // --------------------------------------------------------------------------
564
- // UTILITIES
565
- // --------------------------------------------------------------------------
566
553
  private extractPrice(text: string, prefix: string): number | null {
567
554
  const regex = new RegExp(`${prefix}\\s*\\:?\\s*\\$?(\\d+(?:\\.\\d{2})?)`, 'i');
568
555
  const match = text.match(regex);
@@ -618,6 +605,7 @@ export interface SellerRecord {
618
605
  supported_modes: string[];
619
606
  categories: string[];
620
607
  capabilities: string[];
608
+ display_name?: string;
621
609
  }
622
610
 
623
611
  export class ClinchSeller extends EventEmitter {
@@ -631,10 +619,18 @@ export class ClinchSeller extends EventEmitter {
631
619
  this.config = { timeoutMs: 8000, ...config };
632
620
 
633
621
  if (config.privateKeyHex) {
634
- this.identityPrivKey = fromHex(config.privateKeyHex);
635
- const kp = nacl.sign.keyPair.fromSecretKey(this.identityPrivKey);
636
- this.identityPubKey = toHex(kp.publicKey);
637
- this.emit('log', `[Seller] Loaded permanent identity. PubKey: ${this.identityPubKey.substring(0, 12)}...`);
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
+ }
638
634
  } else {
639
635
  const kp = nacl.sign.keyPair();
640
636
  this.identityPrivKey = kp.secretKey;