@elisym/cli 0.28.0 → 0.29.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.js CHANGED
@@ -5318,7 +5318,15 @@ var AgentRuntime = class {
5318
5318
  return;
5319
5319
  }
5320
5320
  this.ledger.recordDeliveredContent(job.jobId, deliveredContent);
5321
+ delegatedPull.chargedSubunits = this.resolveMeteredCharge(
5322
+ job,
5323
+ matched,
5324
+ delegatedPull.priceSubunits,
5325
+ output.chargeSubunits,
5326
+ log
5327
+ );
5321
5328
  const outcome = await this.executeDelegatedPull(job, delegatedPull, log);
5329
+ netAmount = Number(delegatedPull.chargedSubunits ?? delegatedPull.priceSubunits);
5322
5330
  if (outcome === "dead") {
5323
5331
  this.ledger.markFailed(job.jobId);
5324
5332
  await this.transport.sendFeedback(job, {
@@ -5353,6 +5361,59 @@ var AgentRuntime = class {
5353
5361
  }
5354
5362
  }
5355
5363
  }
5364
+ /**
5365
+ * Decide what a delegated pull should move, for a skill that opted into
5366
+ * metered pricing.
5367
+ *
5368
+ * The skill reports a figure through `ELISYM_CHARGE_FILE`; we clamp it into
5369
+ * the range the CARD published, so the buyer can never be billed above the
5370
+ * `price` they approved nor below the `min` they were quoted. A skill that is
5371
+ * not metered, or one that reported nothing usable, charges the ceiling -
5372
+ * that is exactly today's behaviour and exactly what the buyer consented to,
5373
+ * so a provider-side bug costs the provider nothing and surprises no one.
5374
+ * The "reported nothing" case is logged loudly because on a metered skill it
5375
+ * means the script is broken, not that the job was cheap.
5376
+ *
5377
+ * Returns `undefined` to mean "charge the ceiling", which is what every
5378
+ * caller already falls back to.
5379
+ */
5380
+ resolveMeteredCharge(job, skill, ceiling, reported, log) {
5381
+ const floor = skill?.meteredMinSubunits;
5382
+ if (floor === void 0) {
5383
+ return void 0;
5384
+ }
5385
+ if (typeof floor !== "bigint" || floor <= 0n) {
5386
+ log(
5387
+ `[${job.jobId.slice(0, 8)}] Metered skill has an unusable floor (${String(floor)}) - billing the ${ceiling} ceiling.`
5388
+ );
5389
+ return void 0;
5390
+ }
5391
+ if (reported !== void 0 && typeof reported !== "bigint") {
5392
+ log(
5393
+ `[${job.jobId.slice(0, 8)}] Metered skill reported a non-bigint charge - billing the ${ceiling} ceiling.`
5394
+ );
5395
+ return void 0;
5396
+ }
5397
+ if (reported === void 0) {
5398
+ log(
5399
+ `[${job.jobId.slice(0, 8)}] Metered skill reported no charge - billing the ${ceiling} ceiling. The script should write its cost in subunits to ELISYM_CHARGE_FILE.`
5400
+ );
5401
+ return void 0;
5402
+ }
5403
+ let charged = reported;
5404
+ if (charged < floor) {
5405
+ charged = floor;
5406
+ }
5407
+ if (charged > ceiling) {
5408
+ charged = ceiling;
5409
+ }
5410
+ if (charged !== reported) {
5411
+ log(
5412
+ `[${job.jobId.slice(0, 8)}] Metered charge ${reported} clamped to ${charged} (published range ${floor}..${ceiling}).`
5413
+ );
5414
+ }
5415
+ return charged;
5416
+ }
5356
5417
  reserveDelegation(owner, amount) {
5357
5418
  this.delegationInFlight.set(owner, (this.delegationInFlight.get(owner) ?? 0n) + amount);
5358
5419
  }
@@ -5484,13 +5545,13 @@ var AgentRuntime = class {
5484
5545
  "the delegation account balance is below this skill price."
5485
5546
  );
5486
5547
  }
5487
- this.reserveDelegation(request.owner, priceSubunits);
5488
5548
  log(
5489
5549
  `[${job.jobId.slice(0, 8)}] Delegated pre-check ok: ${formatAssetAmount(
5490
5550
  delegationAsset,
5491
5551
  priceSubunits
5492
5552
  )} from ${request.owner.slice(0, 8)}... (pull after work)`
5493
5553
  );
5554
+ this.reserveDelegation(request.owner, priceSubunits);
5494
5555
  return { request, delegateSigner, ownerAta, priceSubunits, rpc, network };
5495
5556
  }
5496
5557
  /**
@@ -5504,11 +5565,12 @@ var AgentRuntime = class {
5504
5565
  throw new Error("Solana address not configured");
5505
5566
  }
5506
5567
  const destinationAta = await deriveOwnerDelegationAta(this.config.solanaAddress, ctx.network);
5568
+ const amount = ctx.chargedSubunits ?? ctx.priceSubunits;
5507
5569
  const instructions = await buildDelegatedTransfer({
5508
5570
  delegate: ctx.delegateSigner,
5509
5571
  source: ctx.ownerAta,
5510
5572
  destination: destinationAta,
5511
- amount: ctx.priceSubunits,
5573
+ amount,
5512
5574
  network: ctx.network,
5513
5575
  // Idempotent create (delegate-paid rent) so a fresh provider wallet's
5514
5576
  // first delegated job does not dead-letter on a missing ATA.
@@ -5521,11 +5583,13 @@ var AgentRuntime = class {
5521
5583
  job.jobId,
5522
5584
  pull.signature,
5523
5585
  Number(pull.lastValidBlockHeight),
5524
- Number(ctx.priceSubunits)
5586
+ Number(amount)
5525
5587
  );
5526
5588
  ctx.pullSignature = pull.signature;
5527
5589
  const outcome = await sendConfirmToTerminal(ctx.rpc, pull, {});
5528
- log(`[${job.jobId.slice(0, 8)}] Delegated pull ${outcome}: ${pull.signature.slice(0, 16)}...`);
5590
+ log(
5591
+ `[${job.jobId.slice(0, 8)}] Delegated pull ${outcome}: ${amount} subunits, ${pull.signature.slice(0, 16)}...`
5592
+ );
5529
5593
  return outcome;
5530
5594
  }
5531
5595
  /**
@@ -7025,6 +7089,9 @@ function buildCliSkill(parsed, entryPath, scriptEnv, network) {
7025
7089
  if (parsed.executionTimeoutSecs !== void 0) {
7026
7090
  skill.executionTimeoutSecs = parsed.executionTimeoutSecs;
7027
7091
  }
7092
+ if (parsed.meteredMinSubunits !== void 0) {
7093
+ skill.meteredMinSubunits = parsed.meteredMinSubunits;
7094
+ }
7028
7095
  if (parsed.delegation) {
7029
7096
  skill.delegation = parsed.delegation;
7030
7097
  }
@@ -8926,6 +8993,19 @@ function buildCapabilityCard(skill, inputs) {
8926
8993
  // exists (delegatePubkey resolved). The pubkey is injected here, never
8927
8994
  // taken from the SKILL.md - the operator declares only the cap/mechanism.
8928
8995
  ...skill.delegation && delegatePubkey ? { delegation: { ...skill.delegation, delegate_pubkey: delegatePubkey } } : {},
8996
+ // Metered descriptor. The gate is COMPOUND on purpose and both halves
8997
+ // matter: `delegatePubkey` is agent-wide (hoisted when ANY skill declares
8998
+ // delegation), while `skill.delegation` is per-skill. Stamping on the agent
8999
+ // half alone would advertise pay-per-use on a card that ships no delegation
9000
+ // block - the buyer would be told "you pay for what you use" while the only
9001
+ // rail they can reach charges the ceiling. The loader already refuses
9002
+ // `metered` without `delegation`, so this is defence in depth.
9003
+ // `solanaAddress` is in the gate too: without it the `payment` block below
9004
+ // is omitted, and a metered descriptor with no price to clamp against is
9005
+ // incoherent by construction - the write-side mirror would then reject the
9006
+ // card with a misleading "malformed metered descriptor" instead of the real
9007
+ // reason, which is that the agent has no payment address.
9008
+ ...skill.meteredMinSubunits !== void 0 && skill.delegation && delegatePubkey && solanaAddress ? { metered: { min_subunits: skill.meteredMinSubunits.toString() } } : {},
8929
9009
  // On-chain promise (`mode: onchain`): the network is stamped from the
8930
9010
  // agent's wallet here, never from SKILL.md, so a capability copied between
8931
9011
  // a devnet and a mainnet agent cannot advertise the wrong chain.