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.
- package/cli.js +92 -51
- 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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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:
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
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.
|
|
268
|
-
"item": "string (the
|
|
269
|
-
"max_budget": number (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
433
|
+
// Conversational loop: keeps asking until a valid intent is parsed
|
|
434
|
+
while (true) {
|
|
435
|
+
if (!naturalIntent) process.exit(1);
|
|
408
436
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
444
|
+
if (parsed.error) {
|
|
445
|
+
naturalIntent = await prompt(c.yellow(`\n[Agent Q] ${parsed.error}\nš¬: `));
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
419
448
|
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
424
|
-
|
|
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
|
-
|
|
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(
|
|
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 "${
|
|
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]
|
|
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]
|
|
509
|
-
await core.sendCounter(sessionId, session.lastKnownPrice * 0.9, "Can you
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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š¬
|
|
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š
|
|
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
|
|
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š
|
|
690
|
+
if (outcome === 'deal') console.log(c.green(c.bold(`\nš AGREEMENT SECURED at $${finalPrice}`)));
|
|
650
691
|
process.exit(0);
|
|
651
692
|
});
|
|
652
693
|
|