agent-clinch 0.7.7 ā 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 +81 -59
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -202,10 +202,13 @@ 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 {
|
|
208
|
-
console.log(c.dim(`\n[Agent Q] Dispatching request to local Ollama (${cfg.ollamaModel || 'llama3'})...`));
|
|
209
212
|
const res = await fetch('http://127.0.0.1:11434/api/chat', {
|
|
210
213
|
method: 'POST',
|
|
211
214
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -234,8 +237,6 @@ async function promptAI(systemPrompt, userText, cfg) {
|
|
|
234
237
|
await downloadFile(cfg.ggufUrl || "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf", resolvedPath);
|
|
235
238
|
}
|
|
236
239
|
|
|
237
|
-
console.log(c.dim(`\n[Agent Q] Loading GGUF model into memory (this may take a few seconds)...`));
|
|
238
|
-
|
|
239
240
|
let nodeLlama;
|
|
240
241
|
try { nodeLlama = await import('node-llama-cpp'); }
|
|
241
242
|
catch (e) {
|
|
@@ -244,17 +245,23 @@ async function promptAI(systemPrompt, userText, cfg) {
|
|
|
244
245
|
process.exit(1);
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
+
|
|
250
259
|
const session = new nodeLlama.LlamaChatSession({
|
|
251
|
-
contextSequence:
|
|
260
|
+
contextSequence: cachedLlamaContext.getSequence(),
|
|
252
261
|
systemPrompt: systemPrompt,
|
|
253
262
|
chatWrapper: new nodeLlama.ChatMLChatWrapper()
|
|
254
263
|
});
|
|
255
264
|
|
|
256
|
-
console.log(c.dim(`[Agent Q] Model loaded. Analyzing intent...`));
|
|
257
|
-
|
|
258
265
|
let responseText = "";
|
|
259
266
|
await session.prompt(userText, { maxTokens: 1500, onTextChunk: (chunk) => { responseText += chunk; } });
|
|
260
267
|
return responseText;
|
|
@@ -262,18 +269,18 @@ async function promptAI(systemPrompt, userText, cfg) {
|
|
|
262
269
|
}
|
|
263
270
|
|
|
264
271
|
async function parseIntentWithLLM(userInput, cfg) {
|
|
265
|
-
const systemPrompt = `You are a structured data extractor for a
|
|
266
|
-
Analyze the user's input.
|
|
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.
|
|
267
274
|
|
|
268
|
-
If the user says a greeting (like "hi" or "hello") or
|
|
269
|
-
{"error": "Please specify what you want to
|
|
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')."}
|
|
270
277
|
|
|
271
|
-
If they DO specify a
|
|
278
|
+
If they DO specify a clear intent (purchase, scheduling, booking, data retrieval), output EXACTLY this JSON schema:
|
|
272
279
|
{
|
|
273
|
-
"intent": "purchase",
|
|
274
|
-
"category": "string (e.g. electronics,
|
|
275
|
-
"item": "string (the actual item requested)",
|
|
276
|
-
"max_budget": number (integer representing
|
|
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)
|
|
277
284
|
}
|
|
278
285
|
Your response MUST be ONLY valid JSON. Do not include conversational text.`;
|
|
279
286
|
|
|
@@ -283,22 +290,18 @@ Your response MUST be ONLY valid JSON. Do not include conversational text.`;
|
|
|
283
290
|
|
|
284
291
|
const parsed = JSON.parse(cleanJson);
|
|
285
292
|
|
|
286
|
-
//
|
|
293
|
+
// Allow the loop to catch errors and reprompt the user interactively
|
|
287
294
|
if (parsed.error) {
|
|
288
|
-
|
|
289
|
-
return null;
|
|
295
|
+
return { error: parsed.error };
|
|
290
296
|
}
|
|
291
297
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
console.log(c.yellow(`\n[Agent Q] I couldn't quite figure out the item or budget from your request. Please be specific!`));
|
|
295
|
-
return null;
|
|
298
|
+
if (!parsed.item) {
|
|
299
|
+
return { error: "I couldn't figure out the exact item or service you want. Please be specific!" };
|
|
296
300
|
}
|
|
297
301
|
|
|
298
302
|
return parsed;
|
|
299
303
|
} catch (e) {
|
|
300
|
-
|
|
301
|
-
return null;
|
|
304
|
+
return { error: "Failed to parse intent correctly. Please try formatting your request more simply." };
|
|
302
305
|
}
|
|
303
306
|
}
|
|
304
307
|
|
|
@@ -424,37 +427,51 @@ program
|
|
|
424
427
|
|
|
425
428
|
cfg = await ensureAIEngine(cfg);
|
|
426
429
|
|
|
427
|
-
|
|
430
|
+
let naturalIntent = await prompt("š Describe what you want to negotiate\n" +
|
|
428
431
|
c.dim(" (e.g., 'Get me the domain cartpost.shop under 80 dollars')\n\nš¬: "));
|
|
429
432
|
|
|
430
|
-
|
|
433
|
+
// Conversational loop: keeps asking until a valid intent is parsed
|
|
434
|
+
while (true) {
|
|
435
|
+
if (!naturalIntent) process.exit(1);
|
|
431
436
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
+
}
|
|
437
443
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
444
|
+
if (parsed.error) {
|
|
445
|
+
naturalIntent = await prompt(c.yellow(`\n[Agent Q] ${parsed.error}\nš¬: `));
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
442
448
|
|
|
443
|
-
|
|
444
|
-
|
|
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`);
|
|
445
454
|
|
|
446
|
-
|
|
447
|
-
|
|
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
|
+
}
|
|
448
460
|
|
|
449
|
-
|
|
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}"...`));
|
|
450
467
|
const coreDiscovery = getClinchCore(cfg);
|
|
451
468
|
await coreDiscovery.initialize(cfg.token);
|
|
452
|
-
const results = await coreDiscovery.search(
|
|
469
|
+
const results = await coreDiscovery.search(constraints.category);
|
|
453
470
|
coreDiscovery.disconnect();
|
|
454
471
|
|
|
455
472
|
const sellers = results.results || [];
|
|
456
473
|
if (sellers.length === 0) {
|
|
457
|
-
console.log(c.yellow(`\nNo sellers found for "${
|
|
474
|
+
console.log(c.yellow(`\nNo sellers found for "${constraints.category}".`));
|
|
458
475
|
targetAddress = await prompt("š Enter address manually (e.g. ANP/C.amazon.anp): ");
|
|
459
476
|
} else {
|
|
460
477
|
console.log(c.bold(`\nAvailable sellers:`));
|
|
@@ -490,12 +507,11 @@ program
|
|
|
490
507
|
|
|
491
508
|
const incomingMessage = payload.message || JSON.stringify(payload);
|
|
492
509
|
|
|
493
|
-
// Extract basic price to check constraints
|
|
494
510
|
const priceMatch = incomingMessage.match(/price\s*:\s*\$?(\d+(?:\.\d{2})?)/i);
|
|
495
511
|
if (priceMatch) session.lastKnownPrice = parseFloat(priceMatch[1]);
|
|
496
512
|
|
|
497
513
|
if (session.lastKnownPrice > 0 && session.lastKnownPrice <= session.constraints.max_budget) {
|
|
498
|
-
console.log(c.green(`\nš [Agent Q]
|
|
514
|
+
console.log(c.green(`\nš [Agent Q] Target met constraints! Securing deal.`));
|
|
499
515
|
await core.sendCounter(sessionId, session.lastKnownPrice, "I accept this offer.");
|
|
500
516
|
return;
|
|
501
517
|
}
|
|
@@ -507,10 +523,12 @@ program
|
|
|
507
523
|
}
|
|
508
524
|
|
|
509
525
|
const promptStr = core.buildAgentPrompt(sessionId, incomingMessage);
|
|
526
|
+
|
|
527
|
+
console.log(c.dim(`\n[Agent Q] Evaluating turn ${session.currentTurn}...`));
|
|
510
528
|
const aiResponse = await promptAI(promptStr, incomingMessage, cfg);
|
|
511
529
|
|
|
512
530
|
let price = null;
|
|
513
|
-
let msg = "Counter offer";
|
|
531
|
+
let msg = "Counter offer / Clarification requested";
|
|
514
532
|
|
|
515
533
|
try {
|
|
516
534
|
const clean = aiResponse.replace(/```json|```/g, "").trim();
|
|
@@ -525,8 +543,8 @@ program
|
|
|
525
543
|
if (price) {
|
|
526
544
|
await core.sendCounter(sessionId, Math.min(price, session.constraints.max_budget), msg);
|
|
527
545
|
} else {
|
|
528
|
-
console.log(c.yellow(`[Agent Q]
|
|
529
|
-
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?");
|
|
530
548
|
}
|
|
531
549
|
});
|
|
532
550
|
}
|
|
@@ -539,20 +557,20 @@ program
|
|
|
539
557
|
if (opts.parallel && !opts.squeeze) {
|
|
540
558
|
maxSellers = parseInt(opts.parallel);
|
|
541
559
|
strategy = 'parallel';
|
|
542
|
-
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`));
|
|
543
561
|
} else {
|
|
544
562
|
maxSellers = parseInt(opts.squeeze || '3');
|
|
545
563
|
strategy = 'sequential';
|
|
546
|
-
console.log(c.yellow(`š¤ Squeeze Mode: Sequentially
|
|
564
|
+
console.log(c.yellow(`š¤ Squeeze Mode: Sequentially communicating across top ${maxSellers} nodes for "${opts.category}"...\n`));
|
|
547
565
|
}
|
|
548
566
|
|
|
549
567
|
await core.initialize(cfg.token);
|
|
550
568
|
const bestDeal = await core.negotiateCascade(opts.category, constraints, maxSellers, strategy);
|
|
551
569
|
|
|
552
570
|
if (bestDeal) {
|
|
553
|
-
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}!`)));
|
|
554
572
|
} else {
|
|
555
|
-
console.log(c.red(`\nā Cascade completed without any successful
|
|
573
|
+
console.log(c.red(`\nā Cascade completed without any successful agreements.`));
|
|
556
574
|
}
|
|
557
575
|
process.exit(0);
|
|
558
576
|
}
|
|
@@ -575,15 +593,15 @@ program
|
|
|
575
593
|
if (!runAuto) {
|
|
576
594
|
core.on('callback_received', ({ sessionId, payload }) => {
|
|
577
595
|
saveSessionState(sessionId, core);
|
|
578
|
-
console.log(c.cyan(`\nš¬
|
|
579
|
-
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":`));
|
|
580
598
|
});
|
|
581
599
|
}
|
|
582
600
|
|
|
583
601
|
core.on('session_closed', ({ sessionId, outcome, finalPrice }) => {
|
|
584
602
|
saveSessionState(sessionId, core);
|
|
585
603
|
if (outcome === 'deal') {
|
|
586
|
-
console.log(c.green(c.bold(`\nš
|
|
604
|
+
console.log(c.green(c.bold(`\nš AGREEMENT SECURED at $${finalPrice}`)));
|
|
587
605
|
process.exit(0);
|
|
588
606
|
}
|
|
589
607
|
});
|
|
@@ -598,7 +616,7 @@ program
|
|
|
598
616
|
const sessionId = await core.negotiate(targetAddress, constraints);
|
|
599
617
|
|
|
600
618
|
if (!runAuto) {
|
|
601
|
-
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'));
|
|
602
620
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
603
621
|
rl.on('line', async (cmd) => {
|
|
604
622
|
if (cmd === 'exit') {
|
|
@@ -612,6 +630,10 @@ program
|
|
|
612
630
|
if (!isNaN(price)) {
|
|
613
631
|
await core.sendCounter(sessionId, price, 'Counter offer');
|
|
614
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);
|
|
615
637
|
}
|
|
616
638
|
}
|
|
617
639
|
});
|
|
@@ -665,7 +687,7 @@ program
|
|
|
665
687
|
core.on('callback_received', ({ id }) => saveSessionState(id, core));
|
|
666
688
|
core.on('session_closed', ({ outcome, finalPrice }) => {
|
|
667
689
|
saveSessionState(sessionId, core);
|
|
668
|
-
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}`)));
|
|
669
691
|
process.exit(0);
|
|
670
692
|
});
|
|
671
693
|
|