@elisym/mcp 0.8.11 → 0.8.13

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.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy, makeCensor, DEFAULT_REDACT_PATHS, validateAgentName, RELAYS, toDTag, formatAssetAmount, USDC_SOLANA_DEVNET, estimateSolFeeLamports, formatFeeBreakdown, resolveAssetFromPaymentRequest as resolveAssetFromPaymentRequest$1, parseAssetAmount, ElisymIdentity, ElisymClient, NATIVE_SOL, resolveKnownAsset, estimateNetworkBaseline, formatNetworkBaseline, getProtocolProgramId, getProtocolConfig, assetKey, assetByKey, KNOWN_ASSETS } from '@elisym/sdk';
2
+ import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy, makeCensor, DEFAULT_REDACT_PATHS, validateAgentName, RELAYS, toDTag, formatAssetAmount, estimateNetworkBaseline, formatSol as formatSol$1, USDC_SOLANA_DEVNET, estimateSolFeeLamports, formatFeeBreakdown, resolveAssetFromPaymentRequest as resolveAssetFromPaymentRequest$1, parseAssetAmount, ElisymIdentity, ElisymClient, NATIVE_SOL, resolveKnownAsset, formatNetworkBaseline, getProtocolProgramId, getProtocolConfig, assetKey, assetByKey, KNOWN_ASSETS } from '@elisym/sdk';
3
3
  import { listAgents, createAgentDir, writeYaml, writeSecrets, resolveAgent, loadAgent, globalConfigPath, writeFileAtomic as writeFileAtomic$1 } from '@elisym/sdk/agent-store';
4
4
  import { loadGlobalConfig, writeGlobalConfig } from '@elisym/sdk/node';
5
- import { getBase58Encoder, getBase58Decoder, generateKeyPairSigner, address, createSolanaRpcSubscriptions, sendAndConfirmTransactionFactory, getSignatureFromTransaction, pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, signTransactionMessageWithSigners, createKeyPairSignerFromBytes, createSolanaRpc, isAddress } from '@solana/kit';
5
+ import { getBase58Encoder, getBase58Decoder, generateKeyPairSigner, createSolanaRpc, address, createSolanaRpcSubscriptions, sendAndConfirmTransactionFactory, getSignatureFromTransaction, pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, signTransactionMessageWithSigners, createKeyPairSignerFromBytes, isAddress } from '@solana/kit';
6
6
  import { Command } from 'commander';
7
7
  import { generateSecretKey, nip19, getPublicKey } from 'nostr-tools';
8
8
  import { readFile, writeFile, rename, unlink } from 'node:fs/promises';
@@ -2095,14 +2095,13 @@ var ContactSchema = z.object({
2095
2095
  name: z.string().max(200).optional(),
2096
2096
  addedAt: z.number().int().nonnegative(),
2097
2097
  lastJobAt: z.number().int().nonnegative().optional(),
2098
- jobCount: z.number().int().nonnegative(),
2099
2098
  lastCapability: z.string().max(200).optional(),
2100
2099
  note: z.string().max(500).optional()
2101
- }).strict();
2100
+ });
2102
2101
  var ContactsSchema = z.object({
2103
2102
  version: z.literal(1),
2104
2103
  contacts: z.array(ContactSchema)
2105
- }).strict();
2104
+ });
2106
2105
  var EMPTY2 = { version: 1, contacts: [] };
2107
2106
  var writeLocks2 = /* @__PURE__ */ new Map();
2108
2107
  function withLock2(path, fn) {
@@ -2157,8 +2156,7 @@ async function upsertContact(agentDir, input) {
2157
2156
  name: input.name ?? existing.name,
2158
2157
  note: input.note ?? existing.note,
2159
2158
  lastJobAt: input.lastJobAt ?? existing.lastJobAt,
2160
- lastCapability: input.lastCapability ?? existing.lastCapability,
2161
- jobCount: input.jobCount ?? existing.jobCount + 1
2159
+ lastCapability: input.lastCapability ?? existing.lastCapability
2162
2160
  });
2163
2161
  data.contacts[index] = merged;
2164
2162
  } else {
@@ -2169,8 +2167,7 @@ async function upsertContact(agentDir, input) {
2169
2167
  note: input.note,
2170
2168
  addedAt: Date.now(),
2171
2169
  lastJobAt: input.lastJobAt,
2172
- lastCapability: input.lastCapability,
2173
- jobCount: input.jobCount ?? 0
2170
+ lastCapability: input.lastCapability
2174
2171
  });
2175
2172
  data.contacts.push(merged);
2176
2173
  }
@@ -2339,7 +2336,7 @@ var GetIdentitySchema = z.object({});
2339
2336
  var discoveryTools = [
2340
2337
  defineTool({
2341
2338
  name: "search_agents",
2342
- description: "Search AI agents currently online on elisym. `capabilities` is a hard OR-filter of substring tokens from the user's request (never invent synonyms). `query` is optional re-ranking; omit if not needed. Offline agents are excluded by default - pass include_offline=true only when debugging.",
2339
+ description: "Search AI agents currently online on elisym. `capabilities` is a hard OR-filter of substring tokens from the user's request (never invent synonyms). `query` is optional re-ranking; omit if not needed. Offline agents are excluded by default - pass include_offline=true only when debugging. Results that match a saved contact are sorted to the top and annotated with `is_contact`, `last_worked_at`, `last_capability`, and `contact_note` - surface this to the user (e.g. \"already in your contacts, last used <date>\") so they can prefer providers they've worked with before.",
2343
2340
  schema: SearchAgentsSchema,
2344
2341
  async handler(ctx, input) {
2345
2342
  const { capabilities, query, max_price_lamports, include_offline, contacts_only } = input;
@@ -2347,25 +2344,27 @@ var discoveryTools = [
2347
2344
  return errorResult(`Too many capabilities (max ${MAX_CAPABILITIES})`);
2348
2345
  }
2349
2346
  const agent = ctx.active();
2350
- let lastWorkedAtByPubkey = /* @__PURE__ */ new Map();
2347
+ const contactByPubkey = /* @__PURE__ */ new Map();
2348
+ if (agent.agentDir) {
2349
+ const data = await readContacts(agent.agentDir);
2350
+ for (const contact of data.contacts) {
2351
+ contactByPubkey.set(contact.pubkey, contact);
2352
+ }
2353
+ }
2351
2354
  if (contacts_only) {
2352
2355
  if (!agent.agentDir) {
2353
2356
  return textResult(
2354
2357
  "contacts_only=true requires a persistent agent (no on-disk directory for the active agent)."
2355
2358
  );
2356
2359
  }
2357
- const data = await readContacts(agent.agentDir);
2358
- if (data.contacts.length === 0) {
2360
+ if (contactByPubkey.size === 0) {
2359
2361
  return textResult(
2360
2362
  "No contacts saved yet. Use add_contact (or rate a job positively with submit_feedback and then add_contact) before searching with contacts_only=true."
2361
2363
  );
2362
2364
  }
2363
- lastWorkedAtByPubkey = new Map(
2364
- data.contacts.map((contact) => [contact.pubkey, contact.lastJobAt ?? contact.addedAt])
2365
- );
2366
2365
  }
2367
2366
  const agents = await agent.client.discovery.fetchAgents(agent.network);
2368
- let filtered = contacts_only ? agents.filter((a) => lastWorkedAtByPubkey.has(a.pubkey)) : agents;
2367
+ let filtered = contacts_only ? agents.filter((a) => contactByPubkey.has(a.pubkey)) : agents;
2369
2368
  filtered = filtered.filter(
2370
2369
  (a) => a.cards.some(
2371
2370
  (card) => capabilities.some(
@@ -2408,38 +2407,89 @@ var discoveryTools = [
2408
2407
  hits++;
2409
2408
  }
2410
2409
  }
2411
- return { agent: a, score: hits };
2410
+ const contactBoost = contactByPubkey.has(a.pubkey) ? 0.5 : 0;
2411
+ return { agent: a, score: hits + contactBoost };
2412
2412
  });
2413
2413
  filtered = scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).map((s) => s.agent);
2414
2414
  }
2415
+ } else if (contactByPubkey.size > 0) {
2416
+ filtered = [...filtered].sort((left, right) => {
2417
+ const leftContact = contactByPubkey.get(left.pubkey);
2418
+ const rightContact = contactByPubkey.get(right.pubkey);
2419
+ if (leftContact && !rightContact) {
2420
+ return -1;
2421
+ }
2422
+ if (rightContact && !leftContact) {
2423
+ return 1;
2424
+ }
2425
+ if (leftContact && rightContact) {
2426
+ const leftTs = leftContact.lastJobAt ?? leftContact.addedAt;
2427
+ const rightTs = rightContact.lastJobAt ?? rightContact.addedAt;
2428
+ return rightTs - leftTs;
2429
+ }
2430
+ return 0;
2431
+ });
2415
2432
  }
2416
2433
  if (filtered.length === 0) {
2417
2434
  return textResult(
2418
2435
  include_offline ? "No agents found matching those capabilities." : "No online agents found matching those capabilities. Retry shortly or pass include_offline=true to see unreachable matches."
2419
2436
  );
2420
2437
  }
2421
- const results = filtered.map((a) => ({
2422
- npub: a.npub,
2423
- name: sanitizeField(a.name || "", 200),
2424
- cards: a.cards.map((card) => {
2425
- const asset = assetFromCardPayment(card.payment);
2426
- const price = card.payment?.job_price;
2427
- return {
2428
- name: sanitizeField(card.name || "", 200),
2429
- description: sanitizeField(card.description || "", 500),
2430
- capabilities: card.capabilities,
2431
- job_price_subunits: price,
2432
- price_display: price ? formatAssetAmount(asset, BigInt(price)) : "free",
2433
- asset_token: asset.token,
2434
- asset_symbol: asset.symbol,
2435
- asset_mint: asset.mint,
2436
- chain: card.payment?.chain,
2437
- network: card.payment?.network
2438
- };
2439
- }),
2440
- supported_kinds: a.supportedKinds,
2441
- last_worked_at: contacts_only ? lastWorkedAtByPubkey.get(a.pubkey) : void 0
2442
- }));
2438
+ const needsAtaSeen = /* @__PURE__ */ new Set();
2439
+ for (const a of filtered) {
2440
+ for (const card of a.cards) {
2441
+ if ((card.payment?.chain ?? "solana") !== "solana") {
2442
+ continue;
2443
+ }
2444
+ if (!card.payment?.job_price) {
2445
+ continue;
2446
+ }
2447
+ needsAtaSeen.add(assetFromCardPayment(card.payment).mint !== void 0);
2448
+ }
2449
+ }
2450
+ const gasByAtaNeed = /* @__PURE__ */ new Map();
2451
+ if (needsAtaSeen.size > 0) {
2452
+ try {
2453
+ const rpc = createSolanaRpc(rpcUrlFor(agent.network));
2454
+ await Promise.all(
2455
+ Array.from(needsAtaSeen).map(async (needsAta) => {
2456
+ const baseline = await estimateNetworkBaseline(rpc, { includeAtaRent: needsAta });
2457
+ gasByAtaNeed.set(needsAta, formatSol$1(Number(baseline.totalLamports)));
2458
+ })
2459
+ );
2460
+ } catch {
2461
+ }
2462
+ }
2463
+ const results = filtered.map((a) => {
2464
+ const contact = contactByPubkey.get(a.pubkey);
2465
+ return {
2466
+ npub: a.npub,
2467
+ name: sanitizeField(a.name || "", 200),
2468
+ cards: a.cards.map((card) => {
2469
+ const asset = assetFromCardPayment(card.payment);
2470
+ const price = card.payment?.job_price;
2471
+ const gasEstimate = price ? gasByAtaNeed.get(asset.mint !== void 0) : void 0;
2472
+ return {
2473
+ name: sanitizeField(card.name || "", 200),
2474
+ description: sanitizeField(card.description || "", 500),
2475
+ capabilities: card.capabilities,
2476
+ job_price_subunits: price,
2477
+ price_display: price ? formatAssetAmount(asset, BigInt(price)) : "free",
2478
+ asset_token: asset.token,
2479
+ asset_symbol: asset.symbol,
2480
+ asset_mint: asset.mint,
2481
+ chain: card.payment?.chain,
2482
+ network: card.payment?.network,
2483
+ network_fee_estimate_sol: gasEstimate
2484
+ };
2485
+ }),
2486
+ supported_kinds: a.supportedKinds,
2487
+ is_contact: contact ? true : void 0,
2488
+ last_worked_at: contact ? contact.lastJobAt ?? contact.addedAt : void 0,
2489
+ last_capability: contact?.lastCapability,
2490
+ contact_note: contact?.note ? sanitizeField(contact.note, 500) : void 0
2491
+ };
2492
+ });
2443
2493
  const { text } = sanitizeUntrusted(JSON.stringify(results, null, 2), "structured");
2444
2494
  return textResult(text);
2445
2495
  }
@@ -2571,7 +2621,7 @@ var feedbackContactsTools = [
2571
2621
  }),
2572
2622
  defineTool({
2573
2623
  name: "add_contact",
2574
- description: "Add a provider to the active agent's contacts list (.contacts.json). When the provider has prior jobs in the local history, the contact is enriched with jobCount, lastJobAt, and lastCapability. Idempotent: re-calling with the same npub updates name/note in place without duplicating the entry.",
2624
+ description: "Add a provider to the active agent's contacts list (.contacts.json). When the provider has prior jobs in the local history, the contact is enriched with lastJobAt and lastCapability. Idempotent: re-calling with the same npub updates name/note in place without duplicating the entry.",
2575
2625
  schema: AddContactSchema,
2576
2626
  async handler(ctx, input) {
2577
2627
  ctx.toolRateLimiter.check();
@@ -2599,14 +2649,12 @@ var feedbackContactsTools = [
2599
2649
  name: cleanName ?? fallbackProviderName,
2600
2650
  note: cleanNote,
2601
2651
  lastJobAt: last?.completedAt,
2602
- lastCapability: last?.capability,
2603
- jobCount: history.length
2652
+ lastCapability: last?.capability
2604
2653
  });
2605
2654
  const lines = [
2606
2655
  `Saved contact ${contact.npub}.`,
2607
2656
  contact.name ? ` name: ${contact.name}` : null,
2608
- contact.lastCapability ? ` last capability: ${contact.lastCapability}` : null,
2609
- contact.jobCount > 0 ? ` prior jobs: ${contact.jobCount}` : null
2657
+ contact.lastCapability ? ` last capability: ${contact.lastCapability}` : null
2610
2658
  ].filter((line) => line !== null);
2611
2659
  return textResult(lines.join("\n"));
2612
2660
  }
@@ -2656,8 +2704,7 @@ var feedbackContactsTools = [
2656
2704
  note: contact.note !== void 0 ? sanitizeField(contact.note, 500) : void 0,
2657
2705
  added_at: contact.addedAt,
2658
2706
  last_job_at: contact.lastJobAt,
2659
- last_capability: contact.lastCapability !== void 0 ? sanitizeField(contact.lastCapability, 200) : void 0,
2660
- job_count: contact.jobCount
2707
+ last_capability: contact.lastCapability !== void 0 ? sanitizeField(contact.lastCapability, 200) : void 0
2661
2708
  }));
2662
2709
  const { text: wrapped } = sanitizeUntrusted(JSON.stringify(limited, null, 2), "structured");
2663
2710
  return textResult(`${limited.length} contact(s):