agent-clinch 0.7.6 → 0.7.8

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.
Files changed (2) hide show
  1. package/cli.js +92 -51
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -202,6 +202,10 @@ function downloadFile(url, dest) {
202
202
  });
203
203
  }
204
204
 
205
+ // Global cache to prevent reloading the 1.1GB model on every chat turn
206
+ let cachedLlamaModel = null;
207
+ let cachedLlamaContext = null;
208
+
205
209
  async function promptAI(systemPrompt, userText, cfg) {
206
210
  if (cfg.engine === 'ollama') {
207
211
  try {
@@ -241,11 +245,19 @@ async function promptAI(systemPrompt, userText, cfg) {
241
245
  process.exit(1);
242
246
  }
243
247
 
244
- const llama = await nodeLlama.getLlama();
245
- const model = await llama.loadModel({ modelPath: resolvedPath });
246
- const context = await model.createContext({ contextSize: 2048, threads: Math.max(1, os.cpus().length - 1) });
248
+ if (!cachedLlamaModel) {
249
+ const threads = Math.max(2, os.cpus().length - 1);
250
+ console.log(c.dim(`\n[Agent Q] Loading GGUF model into memory using ${threads} threads (this may take a few seconds)...`));
251
+
252
+ const llama = await nodeLlama.getLlama();
253
+ cachedLlamaModel = await llama.loadModel({ modelPath: resolvedPath });
254
+ cachedLlamaContext = await cachedLlamaModel.createContext({ contextSize: 2048, threads: threads });
255
+
256
+ console.log(c.dim(`[Agent Q] Model loaded. Analyzing...`));
257
+ }
258
+
247
259
  const session = new nodeLlama.LlamaChatSession({
248
- contextSequence: context.getSequence(),
260
+ contextSequence: cachedLlamaContext.getSequence(),
249
261
  systemPrompt: systemPrompt,
250
262
  chatWrapper: new nodeLlama.ChatMLChatWrapper()
251
263
  });
@@ -257,25 +269,39 @@ async function promptAI(systemPrompt, userText, cfg) {
257
269
  }
258
270
 
259
271
  async function parseIntentWithLLM(userInput, cfg) {
260
- console.log(c.dim(`\n[Agent Q] Booting local parser (${cfg.engine}) to analyze your request...`));
261
- const systemPrompt = `You are a structured data extractor. Convert the user's conversational intent into a strict JSON schema.
262
- Your response MUST be ONLY valid JSON matching this schema exactly. Do not output conversational text.
272
+ const systemPrompt = `You are a structured data extractor for a smart network agent.
273
+ Analyze the user's input. They might want to purchase an item, schedule a P2P service, book something, or query a node.
274
+
275
+ If the user says a greeting (like "hi" or "hello") or their request is too vague to act on, output EXACTLY this JSON:
276
+ {"error": "Please specify what you want to do (e.g. 'Get me a laptop under $500', or 'Schedule a call with @algeru on ginger')."}
263
277
 
264
- JSON Schema:
278
+ If they DO specify a clear intent (purchase, scheduling, booking, data retrieval), output EXACTLY this JSON schema:
265
279
  {
266
- "intent": "purchase",
267
- "category": "string (e.g. domain_name, electronics, kitchen_appliance)",
268
- "item": "string (the exact item, website, or product they want)",
269
- "max_budget": number (extract budget numeric value)
270
- }`;
280
+ "intent": "string (e.g. purchase, schedule, booking)",
281
+ "category": "string (e.g. electronics, scheduling, p2p_services, domain_names)",
282
+ "item": "string (the actual item, target, or service requested)",
283
+ "max_budget": number (integer representing max budget. If none mentioned, use 0)
284
+ }
285
+ Your response MUST be ONLY valid JSON. Do not include conversational text.`;
271
286
 
272
287
  try {
273
288
  const rawRes = await promptAI(systemPrompt, userInput, cfg);
274
289
  const cleanJson = rawRes.replace(/```json|```/g, "").trim();
275
- return JSON.parse(cleanJson);
290
+
291
+ const parsed = JSON.parse(cleanJson);
292
+
293
+ // Allow the loop to catch errors and reprompt the user interactively
294
+ if (parsed.error) {
295
+ return { error: parsed.error };
296
+ }
297
+
298
+ if (!parsed.item) {
299
+ return { error: "I couldn't figure out the exact item or service you want. Please be specific!" };
300
+ }
301
+
302
+ return parsed;
276
303
  } catch (e) {
277
- console.error(c.red("Failed to parse intent. Falling back to manual entry."));
278
- return null;
304
+ return { error: "Failed to parse intent correctly. Please try formatting your request more simply." };
279
305
  }
280
306
  }
281
307
 
@@ -401,37 +427,51 @@ program
401
427
 
402
428
  cfg = await ensureAIEngine(cfg);
403
429
 
404
- const naturalIntent = await prompt("šŸ‘‰ Describe what you want to negotiate\n" +
430
+ let naturalIntent = await prompt("šŸ‘‰ Describe what you want to negotiate\n" +
405
431
  c.dim(" (e.g., 'Get me the domain cartpost.shop under 80 dollars')\n\nšŸ’¬: "));
406
432
 
407
- if (!naturalIntent) process.exit(1);
433
+ // Conversational loop: keeps asking until a valid intent is parsed
434
+ while (true) {
435
+ if (!naturalIntent) process.exit(1);
408
436
 
409
- const parsed = await parseIntentWithLLM(naturalIntent, cfg);
410
- if (!parsed) {
411
- console.error(c.red("āœ— Error parsing constraints. Please try manual entry."));
412
- process.exit(1);
413
- }
437
+ const parsed = await parseIntentWithLLM(naturalIntent, cfg);
438
+
439
+ if (!parsed) {
440
+ naturalIntent = await prompt(c.yellow("\n[Agent Q] Something went wrong. Let's try again. What are you looking for?\nšŸ’¬: "));
441
+ continue;
442
+ }
414
443
 
415
- console.log(c.bold("\nšŸ“Š Extracted Intention Context:"));
416
- console.log(` - Category: ${c.cyan(parsed.category)}`);
417
- console.log(` - Target Item: ${c.cyan(parsed.item)}`);
418
- console.log(` - Max Budget: ${c.green("$" + parsed.max_budget)}\n`);
444
+ if (parsed.error) {
445
+ naturalIntent = await prompt(c.yellow(`\n[Agent Q] ${parsed.error}\nšŸ’¬: `));
446
+ continue;
447
+ }
419
448
 
420
- const confirm = await prompt("šŸ‘‰ Is this correct? (Y/n): ");
421
- if (confirm.toLowerCase() === 'n') process.exit(0);
449
+ console.log(c.bold("\nšŸ“Š Extracted Intention Context:"));
450
+ console.log(` - Intent: ${c.cyan(parsed.intent || 'purchase')}`);
451
+ console.log(` - Category: ${c.cyan(parsed.category)}`);
452
+ console.log(` - Target Item: ${c.cyan(parsed.item)}`);
453
+ console.log(` - Max Budget: ${c.green("$" + parsed.max_budget)}\n`);
422
454
 
423
- constraints = parsed;
424
- budget = parsed.max_budget;
455
+ const confirm = await prompt("šŸ‘‰ Is this correct? (Y/n): ");
456
+ if (confirm.toLowerCase() === 'n') {
457
+ naturalIntent = await prompt(c.yellow("\n[Agent Q] Got it. Let's try again. What are you looking for?\nšŸ’¬: "));
458
+ continue;
459
+ }
425
460
 
426
- console.log(c.dim(`\nQuerying registry for category "${parsed.category}"...`));
461
+ constraints = parsed;
462
+ budget = parsed.max_budget;
463
+ break; // Exit loop on confirmation
464
+ }
465
+
466
+ console.log(c.dim(`\n[Network] Querying registry for category "${constraints.category}"...`));
427
467
  const coreDiscovery = getClinchCore(cfg);
428
468
  await coreDiscovery.initialize(cfg.token);
429
- const results = await coreDiscovery.search(parsed.category);
469
+ const results = await coreDiscovery.search(constraints.category);
430
470
  coreDiscovery.disconnect();
431
471
 
432
472
  const sellers = results.results || [];
433
473
  if (sellers.length === 0) {
434
- console.log(c.yellow(`\nNo sellers found for "${parsed.category}".`));
474
+ console.log(c.yellow(`\nNo sellers found for "${constraints.category}".`));
435
475
  targetAddress = await prompt("šŸ‘‰ Enter address manually (e.g. ANP/C.amazon.anp): ");
436
476
  } else {
437
477
  console.log(c.bold(`\nAvailable sellers:`));
@@ -467,12 +507,11 @@ program
467
507
 
468
508
  const incomingMessage = payload.message || JSON.stringify(payload);
469
509
 
470
- // Extract basic price to check constraints
471
510
  const priceMatch = incomingMessage.match(/price\s*:\s*\$?(\d+(?:\.\d{2})?)/i);
472
511
  if (priceMatch) session.lastKnownPrice = parseFloat(priceMatch[1]);
473
512
 
474
513
  if (session.lastKnownPrice > 0 && session.lastKnownPrice <= session.constraints.max_budget) {
475
- console.log(c.green(`\nšŸŽ‰ [Agent Q] Seller met budget conditions! Securing deal.`));
514
+ console.log(c.green(`\nšŸŽ‰ [Agent Q] Target met constraints! Securing deal.`));
476
515
  await core.sendCounter(sessionId, session.lastKnownPrice, "I accept this offer.");
477
516
  return;
478
517
  }
@@ -483,14 +522,13 @@ program
483
522
  return;
484
523
  }
485
524
 
486
- // Ask the core to build the context prompt, but evaluate it HERE in the CLI
487
525
  const promptStr = core.buildAgentPrompt(sessionId, incomingMessage);
488
- console.log(c.dim(`\n[Agent Q] Evaluating turn ${session.currentTurn} with ${cfg.engine}...`));
489
526
 
527
+ console.log(c.dim(`\n[Agent Q] Evaluating turn ${session.currentTurn}...`));
490
528
  const aiResponse = await promptAI(promptStr, incomingMessage, cfg);
491
529
 
492
530
  let price = null;
493
- let msg = "Counter offer";
531
+ let msg = "Counter offer / Clarification requested";
494
532
 
495
533
  try {
496
534
  const clean = aiResponse.replace(/```json|```/g, "").trim();
@@ -505,8 +543,8 @@ program
505
543
  if (price) {
506
544
  await core.sendCounter(sessionId, Math.min(price, session.constraints.max_budget), msg);
507
545
  } else {
508
- console.log(c.yellow(`[Agent Q] Failed to parse price constraint. Sending safe fallback.`));
509
- await core.sendCounter(sessionId, session.lastKnownPrice * 0.9, "Can you do slightly better?");
546
+ console.log(c.yellow(`[Agent Q] Sending safe fallback response.`));
547
+ await core.sendCounter(sessionId, session.lastKnownPrice * 0.9 || 0, "Can you provide more details?");
510
548
  }
511
549
  });
512
550
  }
@@ -519,20 +557,20 @@ program
519
557
  if (opts.parallel && !opts.squeeze) {
520
558
  maxSellers = parseInt(opts.parallel);
521
559
  strategy = 'parallel';
522
- console.log(c.yellow(`šŸ¤– Parallel Mode: Handshaking concurrently with top ${maxSellers} sellers for "${opts.category}"...\n`));
560
+ console.log(c.yellow(`šŸ¤– Parallel Mode: Handshaking concurrently with top ${maxSellers} nodes for "${opts.category}"...\n`));
523
561
  } else {
524
562
  maxSellers = parseInt(opts.squeeze || '3');
525
563
  strategy = 'sequential';
526
- console.log(c.yellow(`šŸ¤– Squeeze Mode: Sequentially bargaining across top ${maxSellers} sellers for "${opts.category}"...\n`));
564
+ console.log(c.yellow(`šŸ¤– Squeeze Mode: Sequentially communicating across top ${maxSellers} nodes for "${opts.category}"...\n`));
527
565
  }
528
566
 
529
567
  await core.initialize(cfg.token);
530
568
  const bestDeal = await core.negotiateCascade(opts.category, constraints, maxSellers, strategy);
531
569
 
532
570
  if (bestDeal) {
533
- console.log(c.green(c.bold(`\nšŸ† CASCADE COMPLETE: Secured optimal deal with ${bestDeal.sellerId} at $${bestDeal.finalPrice}!`)));
571
+ console.log(c.green(c.bold(`\nšŸ† CASCADE COMPLETE: Secured optimal agreement with ${bestDeal.sellerId} at $${bestDeal.finalPrice}!`)));
534
572
  } else {
535
- console.log(c.red(`\nāœ— Cascade completed without any successful deals.`));
573
+ console.log(c.red(`\nāœ— Cascade completed without any successful agreements.`));
536
574
  }
537
575
  process.exit(0);
538
576
  }
@@ -555,15 +593,15 @@ program
555
593
  if (!runAuto) {
556
594
  core.on('callback_received', ({ sessionId, payload }) => {
557
595
  saveSessionState(sessionId, core);
558
- console.log(c.cyan(`\nšŸ’¬ Seller says:`), payload);
559
- console.log(c.dim(`\nType a price to counter, or "exit" / "accept":`));
596
+ console.log(c.cyan(`\nšŸ’¬ Node says:`), payload);
597
+ console.log(c.dim(`\nType a price/response to counter, or "exit" / "accept":`));
560
598
  });
561
599
  }
562
600
 
563
601
  core.on('session_closed', ({ sessionId, outcome, finalPrice }) => {
564
602
  saveSessionState(sessionId, core);
565
603
  if (outcome === 'deal') {
566
- console.log(c.green(c.bold(`\nšŸŽ‰ DEAL SECURED at $${finalPrice}`)));
604
+ console.log(c.green(c.bold(`\nšŸŽ‰ AGREEMENT SECURED at $${finalPrice}`)));
567
605
  process.exit(0);
568
606
  }
569
607
  });
@@ -578,7 +616,7 @@ program
578
616
  const sessionId = await core.negotiate(targetAddress, constraints);
579
617
 
580
618
  if (!runAuto) {
581
- console.log(c.bold('\nManual mode — await seller response, then type a price to counter, or "exit" / "accept".\n'));
619
+ console.log(c.bold('\nManual mode — await response, then type a counter-offer, or "exit" / "accept".\n'));
582
620
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
583
621
  rl.on('line', async (cmd) => {
584
622
  if (cmd === 'exit') {
@@ -592,6 +630,10 @@ program
592
630
  if (!isNaN(price)) {
593
631
  await core.sendCounter(sessionId, price, 'Counter offer');
594
632
  saveSessionState(sessionId, core);
633
+ } else {
634
+ // Allows sending non-numeric replies if needed
635
+ await core.sendCounter(sessionId, 0, cmd);
636
+ saveSessionState(sessionId, core);
595
637
  }
596
638
  }
597
639
  });
@@ -635,7 +677,6 @@ program
635
677
  console.log(c.yellow(`\nRehydrating Session ${c.bold(sessionId)}...\n`));
636
678
  if (opts.auto) {
637
679
  cfg = await ensureAIEngine(cfg);
638
- // ... hook auto logic here if needed (omitted for brevity, handled heavily in negotiate)
639
680
  }
640
681
 
641
682
  const core = getClinchCore(cfg);
@@ -646,7 +687,7 @@ program
646
687
  core.on('callback_received', ({ id }) => saveSessionState(id, core));
647
688
  core.on('session_closed', ({ outcome, finalPrice }) => {
648
689
  saveSessionState(sessionId, core);
649
- if (outcome === 'deal') console.log(c.green(c.bold(`\nšŸŽ‰ DEAL SECURED at $${finalPrice}`)));
690
+ if (outcome === 'deal') console.log(c.green(c.bold(`\nšŸŽ‰ AGREEMENT SECURED at $${finalPrice}`)));
650
691
  process.exit(0);
651
692
  });
652
693
 
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
 
8
8
  "name": "agent-clinch",
9
- "version": "0.7.6",
9
+ "version": "0.7.8",
10
10
  "description": "Clinch Protocol CLI — agent negotiation from your terminal",
11
11
  "main": "cli.js",
12
12
  "bin": {