@elisym/cli 0.27.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
|
@@ -9,7 +9,7 @@ import { generateSecretKey, getPublicKey, nip19, verifyEvent } from 'nostr-tools
|
|
|
9
9
|
import YAML2 from 'yaml';
|
|
10
10
|
import { Command } from 'commander';
|
|
11
11
|
import { mkdir, writeFile, readFile, mkdtemp, rm, rename, stat } from 'node:fs/promises';
|
|
12
|
-
import { parseSkillMd, validateSkillFrontmatter, DEFAULT_X402_MAX_INPUT_BYTES, resolveInsidePathReal, DEFAULT_SCRIPT_TIMEOUT_MS, DynamicScriptSkill as DynamicScriptSkill$1, StaticScriptSkill as StaticScriptSkill$1, StaticFileSkill as StaticFileSkill$1, ScriptSkill as ScriptSkill$1 } from '@elisym/sdk/skills';
|
|
12
|
+
import { parseSkillMd, validateSkillFrontmatter, DEFAULT_X402_MAX_INPUT_BYTES, resolveInsidePathReal, DEFAULT_SCRIPT_TIMEOUT_MS, OnchainCallSkill, DynamicScriptSkill as DynamicScriptSkill$1, StaticScriptSkill as StaticScriptSkill$1, StaticFileSkill as StaticFileSkill$1, ScriptSkill as ScriptSkill$1 } from '@elisym/sdk/skills';
|
|
13
13
|
import chalk from 'chalk';
|
|
14
14
|
import Decimal from 'decimal.js-light';
|
|
15
15
|
import { decodePaymentRequiredHeader } from '@x402/core/http';
|
|
@@ -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
|
|
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(
|
|
5586
|
+
Number(amount)
|
|
5525
5587
|
);
|
|
5526
5588
|
ctx.pullSignature = pull.signature;
|
|
5527
5589
|
const outcome = await sendConfirmToTerminal(ctx.rpc, pull, {});
|
|
5528
|
-
log(
|
|
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
|
/**
|
|
@@ -6704,6 +6768,51 @@ var DynamicScriptSkill = class {
|
|
|
6704
6768
|
return this.inner.execute(input, ctx);
|
|
6705
6769
|
}
|
|
6706
6770
|
};
|
|
6771
|
+
var OnchainSkill = class {
|
|
6772
|
+
name;
|
|
6773
|
+
description;
|
|
6774
|
+
capabilities;
|
|
6775
|
+
priceSubunits;
|
|
6776
|
+
asset;
|
|
6777
|
+
mode = "onchain";
|
|
6778
|
+
image;
|
|
6779
|
+
imageFile;
|
|
6780
|
+
dir;
|
|
6781
|
+
llmOverride;
|
|
6782
|
+
onchain;
|
|
6783
|
+
inner;
|
|
6784
|
+
constructor(params) {
|
|
6785
|
+
this.name = params.name;
|
|
6786
|
+
this.description = params.description;
|
|
6787
|
+
this.capabilities = params.capabilities;
|
|
6788
|
+
this.priceSubunits = params.priceSubunits;
|
|
6789
|
+
this.asset = params.asset;
|
|
6790
|
+
this.image = params.image;
|
|
6791
|
+
this.imageFile = params.imageFile;
|
|
6792
|
+
this.dir = params.dir;
|
|
6793
|
+
this.llmOverride = params.llmOverride;
|
|
6794
|
+
this.onchain = params.onchain;
|
|
6795
|
+
this.inner = new OnchainCallSkill({
|
|
6796
|
+
name: params.name,
|
|
6797
|
+
description: params.description,
|
|
6798
|
+
capabilities: params.capabilities,
|
|
6799
|
+
priceSubunits: BigInt(Math.round(params.priceSubunits)),
|
|
6800
|
+
asset: params.asset,
|
|
6801
|
+
scriptPath: params.scriptPath,
|
|
6802
|
+
scriptArgs: params.scriptArgs,
|
|
6803
|
+
scriptTimeoutMs: params.scriptTimeoutMs,
|
|
6804
|
+
scriptEnv: params.scriptEnv,
|
|
6805
|
+
image: params.image,
|
|
6806
|
+
imageFile: params.imageFile,
|
|
6807
|
+
llmOverride: params.llmOverride,
|
|
6808
|
+
onchain: params.onchain,
|
|
6809
|
+
network: params.network
|
|
6810
|
+
});
|
|
6811
|
+
}
|
|
6812
|
+
async execute(input, ctx) {
|
|
6813
|
+
return this.inner.execute(input, ctx);
|
|
6814
|
+
}
|
|
6815
|
+
};
|
|
6707
6816
|
var ScriptSkill = class {
|
|
6708
6817
|
name;
|
|
6709
6818
|
description;
|
|
@@ -6836,7 +6945,7 @@ function scopeLlmKeys(scriptEnv, declaredProvider) {
|
|
|
6836
6945
|
}
|
|
6837
6946
|
return env;
|
|
6838
6947
|
}
|
|
6839
|
-
function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
6948
|
+
function buildCliSkill(parsed, entryPath, scriptEnv, network) {
|
|
6840
6949
|
let safeImageFile = parsed.imageFile;
|
|
6841
6950
|
if (safeImageFile !== void 0 && resolveInsidePathReal(entryPath, safeImageFile) === null) {
|
|
6842
6951
|
console.warn(
|
|
@@ -6924,6 +7033,35 @@ function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
|
6924
7033
|
}) : new StaticScriptSkill(scriptParams);
|
|
6925
7034
|
break;
|
|
6926
7035
|
}
|
|
7036
|
+
case "onchain": {
|
|
7037
|
+
if (parsed.script === void 0 || parsed.onchain === void 0) {
|
|
7038
|
+
throw new Error(
|
|
7039
|
+
`SKILL.md "${parsed.name}": internal error - script or onchain block missing for mode 'onchain'`
|
|
7040
|
+
);
|
|
7041
|
+
}
|
|
7042
|
+
const scriptPath = resolveInsidePathReal(entryPath, parsed.script);
|
|
7043
|
+
if (!scriptPath) {
|
|
7044
|
+
throw new Error(`SKILL.md "${parsed.name}": "script" must stay inside the skill directory`);
|
|
7045
|
+
}
|
|
7046
|
+
skill = new OnchainSkill({
|
|
7047
|
+
name: parsed.name,
|
|
7048
|
+
description: parsed.description,
|
|
7049
|
+
capabilities: parsed.capabilities,
|
|
7050
|
+
priceSubunits: Number(parsed.priceSubunits),
|
|
7051
|
+
asset: parsed.asset,
|
|
7052
|
+
scriptPath,
|
|
7053
|
+
scriptArgs: parsed.scriptArgs,
|
|
7054
|
+
scriptTimeoutMs: parsed.scriptTimeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS,
|
|
7055
|
+
scriptEnv: scopeLlmKeys(scriptEnv, parsed.llmOverride?.provider),
|
|
7056
|
+
image: parsed.image,
|
|
7057
|
+
imageFile: safeImageFile,
|
|
7058
|
+
dir: entryPath,
|
|
7059
|
+
llmOverride: parsed.llmOverride,
|
|
7060
|
+
onchain: parsed.onchain,
|
|
7061
|
+
network
|
|
7062
|
+
});
|
|
7063
|
+
break;
|
|
7064
|
+
}
|
|
6927
7065
|
case "x402": {
|
|
6928
7066
|
if (parsed.x402 === void 0) {
|
|
6929
7067
|
throw new Error(
|
|
@@ -6951,6 +7089,9 @@ function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
|
6951
7089
|
if (parsed.executionTimeoutSecs !== void 0) {
|
|
6952
7090
|
skill.executionTimeoutSecs = parsed.executionTimeoutSecs;
|
|
6953
7091
|
}
|
|
7092
|
+
if (parsed.meteredMinSubunits !== void 0) {
|
|
7093
|
+
skill.meteredMinSubunits = parsed.meteredMinSubunits;
|
|
7094
|
+
}
|
|
6954
7095
|
if (parsed.delegation) {
|
|
6955
7096
|
skill.delegation = parsed.delegation;
|
|
6956
7097
|
}
|
|
@@ -6989,7 +7130,7 @@ function loadSkillsFromDir(skillsDir, options) {
|
|
|
6989
7130
|
warn: (_obj, msg) => console.warn(` ! ${msg ?? ""}`)
|
|
6990
7131
|
}
|
|
6991
7132
|
});
|
|
6992
|
-
skills.push(buildCliSkill(parsed, entryPath, options.scriptEnv));
|
|
7133
|
+
skills.push(buildCliSkill(parsed, entryPath, options.scriptEnv, options.network));
|
|
6993
7134
|
} catch (e) {
|
|
6994
7135
|
const message = e instanceof Error ? e.message : String(e);
|
|
6995
7136
|
console.warn(` ! Skipping skill "${entry}": ${message}`);
|
|
@@ -8368,6 +8509,12 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8368
8509
|
agentName,
|
|
8369
8510
|
agentDescription: loaded.yaml.description ?? ""
|
|
8370
8511
|
};
|
|
8512
|
+
const onchainSkillCount = allSkills.filter((skill) => skill.onchain !== void 0).length;
|
|
8513
|
+
if (solanaAddress === void 0 && onchainSkillCount > 0) {
|
|
8514
|
+
console.warn(
|
|
8515
|
+
` ! ${onchainSkillCount} onchain capability(ies) declare a block but this agent has no Solana payment address, so the network their cards would promise is a guess. Add a payments entry to elisym.yaml.`
|
|
8516
|
+
);
|
|
8517
|
+
}
|
|
8371
8518
|
const x402Skills = allSkills.filter((skill) => skill.mode === "x402");
|
|
8372
8519
|
let x402InvariantBroken;
|
|
8373
8520
|
if (x402Skills.length > 0) {
|
|
@@ -8377,9 +8524,13 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8377
8524
|
} else if (!solanaAddress) {
|
|
8378
8525
|
x402InvariantBroken = "no payments[] entry in elisym.yaml";
|
|
8379
8526
|
} else {
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8527
|
+
try {
|
|
8528
|
+
const bridgeSigner = await signerFromSecretKeyBase58(solanaSecretKey);
|
|
8529
|
+
if (bridgeSigner.address !== solanaAddress) {
|
|
8530
|
+
x402InvariantBroken = `payments[].address (${solanaAddress}) differs from the solana_secret_key address (${bridgeSigner.address})`;
|
|
8531
|
+
}
|
|
8532
|
+
} catch (error) {
|
|
8533
|
+
x402InvariantBroken = `solana_secret_key is present but unreadable - ${error instanceof Error ? error.message : String(error)}`;
|
|
8383
8534
|
}
|
|
8384
8535
|
}
|
|
8385
8536
|
for (const skill of x402Skills) {
|
|
@@ -8655,6 +8806,16 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8655
8806
|
);
|
|
8656
8807
|
continue;
|
|
8657
8808
|
}
|
|
8809
|
+
if (withholdsOnchainCard(skill, solanaAddress)) {
|
|
8810
|
+
console.warn(
|
|
8811
|
+
` ! Not republishing "${skill.name}" - no Solana payments entry. An earlier card for it, if any, stays on the relays until it expires.`
|
|
8812
|
+
);
|
|
8813
|
+
logger.warn(
|
|
8814
|
+
{ event: "publish_skipped_onchain_no_wallet", kind: 31990, skill: skill.name },
|
|
8815
|
+
"capability not advertised - onchain block with no Solana payment address"
|
|
8816
|
+
);
|
|
8817
|
+
continue;
|
|
8818
|
+
}
|
|
8658
8819
|
try {
|
|
8659
8820
|
await client.discovery.publishCapability(identity2, buildCard(skill), kinds);
|
|
8660
8821
|
cardsPublished += 1;
|
|
@@ -8832,6 +8993,26 @@ function buildCapabilityCard(skill, inputs) {
|
|
|
8832
8993
|
// exists (delegatePubkey resolved). The pubkey is injected here, never
|
|
8833
8994
|
// taken from the SKILL.md - the operator declares only the cap/mechanism.
|
|
8834
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() } } : {},
|
|
9009
|
+
// On-chain promise (`mode: onchain`): the network is stamped from the
|
|
9010
|
+
// agent's wallet here, never from SKILL.md, so a capability copied between
|
|
9011
|
+
// a devnet and a mainnet agent cannot advertise the wrong chain.
|
|
9012
|
+
// Only stamped when the agent actually has a Solana payment entry: without
|
|
9013
|
+
// one `walletNetwork` is a default, and a card must never promise a chain
|
|
9014
|
+
// the operator never chose. `cmdStart` warns for such a skill at step 7a.
|
|
9015
|
+
...skill.onchain && solanaAddress ? { onchain: { ...skill.onchain, network: walletNetwork } } : {},
|
|
8835
9016
|
payment: solanaAddress ? {
|
|
8836
9017
|
chain: "solana",
|
|
8837
9018
|
network: walletNetwork,
|
|
@@ -8844,6 +9025,9 @@ function buildCapabilityCard(skill, inputs) {
|
|
|
8844
9025
|
} : void 0
|
|
8845
9026
|
};
|
|
8846
9027
|
}
|
|
9028
|
+
function withholdsOnchainCard(skill, solanaAddress) {
|
|
9029
|
+
return skill.onchain !== void 0 && solanaAddress === void 0;
|
|
9030
|
+
}
|
|
8847
9031
|
function buildScriptEnv(secrets) {
|
|
8848
9032
|
const scriptEnv = { ...process.env };
|
|
8849
9033
|
delete scriptEnv.ELISYM_PASSPHRASE;
|