@elisym/cli 0.22.1 → 0.22.4
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 +259 -101
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --no-deprecation
|
|
2
2
|
import { ReadableStream } from 'node:stream/web';
|
|
3
3
|
import { readFileSync, existsSync, readdirSync, statSync, renameSync, chmodSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { dirname, join, resolve, basename, relative, sep
|
|
4
|
+
import { dirname, join, resolve, basename, extname, relative, sep } from 'node:path';
|
|
5
5
|
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, USDC_SOLANA_DEVNET, ElisymClient, POLICY_D_TAG_PREFIX, KIND_LONG_FORM_ARTICLE, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, createBlossomTransport, makeCensor, DEFAULT_REDACT_PATHS, POLICY_T_TAG, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, utf8ByteLength, LIMITS, readAcceptedTransports, calculateProtocolFee, decodeJobPayload, BoundedSet, KIND_JOB_FEEDBACK, NATIVE_SOL } from '@elisym/sdk';
|
|
6
6
|
import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYamlInitial, writeExampleSkillTemplate, writeSecrets, listAgents, loadAgent, writeYaml, agentPaths, readMediaCache, loadPoliciesFromDir, ensureGitignoreHasIrohEntry, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
|
|
7
7
|
import { isAddress, createSolanaRpc, address } from '@solana/kit';
|
|
@@ -1865,6 +1865,9 @@ var JobLedger = class {
|
|
|
1865
1865
|
if (fields.resultAttachment !== void 0) {
|
|
1866
1866
|
entry.result_attachment = fields.resultAttachment;
|
|
1867
1867
|
}
|
|
1868
|
+
if (fields.resultAttachments !== void 0) {
|
|
1869
|
+
entry.result_attachments = fields.resultAttachments;
|
|
1870
|
+
}
|
|
1868
1871
|
this.flush();
|
|
1869
1872
|
}
|
|
1870
1873
|
markDelivered(jobId) {
|
|
@@ -2106,9 +2109,41 @@ var ExecutionBudgetExceededError = class extends Error {
|
|
|
2106
2109
|
this.name = "ExecutionBudgetExceededError";
|
|
2107
2110
|
}
|
|
2108
2111
|
};
|
|
2112
|
+
var SeedFailedError = class extends Error {
|
|
2113
|
+
constructor(cause) {
|
|
2114
|
+
super("Result is ready - delivery is retrying and will arrive shortly.", { cause });
|
|
2115
|
+
this.name = "SeedFailedError";
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
var PaymentTimeoutError = class extends Error {
|
|
2119
|
+
constructor() {
|
|
2120
|
+
super("Payment verification timed out; awaiting late confirmation.");
|
|
2121
|
+
this.name = "PaymentTimeoutError";
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
var MIME_EXTENSIONS = {
|
|
2125
|
+
"image/png": ".png",
|
|
2126
|
+
"image/jpeg": ".jpg",
|
|
2127
|
+
"image/webp": ".webp",
|
|
2128
|
+
"image/gif": ".gif",
|
|
2129
|
+
"image/svg+xml": ".svg",
|
|
2130
|
+
"application/pdf": ".pdf",
|
|
2131
|
+
"text/plain": ".txt",
|
|
2132
|
+
"text/markdown": ".md",
|
|
2133
|
+
"application/json": ".json",
|
|
2134
|
+
"audio/mpeg": ".mp3",
|
|
2135
|
+
"audio/wav": ".wav",
|
|
2136
|
+
"video/mp4": ".mp4",
|
|
2137
|
+
"video/webm": ".webm",
|
|
2138
|
+
"application/zip": ".zip"
|
|
2139
|
+
};
|
|
2140
|
+
function extensionForMime(mime) {
|
|
2141
|
+
return mime ? MIME_EXTENSIONS[mime] ?? "" : "";
|
|
2142
|
+
}
|
|
2143
|
+
var SLOW_SEED_LOG_MS = 5e3;
|
|
2109
2144
|
var CUSTOMER_SAFE_MESSAGE_PREFIXES = ["Input too long", "No skill matched", "Payment timeout"];
|
|
2110
2145
|
function customerSafeMessage(error) {
|
|
2111
|
-
if (error instanceof AgentUnavailableError || error instanceof ExecutionBudgetExceededError) {
|
|
2146
|
+
if (error instanceof AgentUnavailableError || error instanceof ExecutionBudgetExceededError || error instanceof SeedFailedError) {
|
|
2112
2147
|
return error.message;
|
|
2113
2148
|
}
|
|
2114
2149
|
if (error instanceof ScriptExecutionError) {
|
|
@@ -2515,14 +2550,12 @@ var AgentRuntime = class {
|
|
|
2515
2550
|
const log = this.callbacks.onLog ?? console.log;
|
|
2516
2551
|
log(`[${job.jobId.slice(0, 8)}] Error: ${e.message}`);
|
|
2517
2552
|
const currentStatus = this.ledger.getStatus(job.jobId);
|
|
2518
|
-
const keepPaidForRecovery = e instanceof AgentUnavailableError && currentStatus === "paid";
|
|
2553
|
+
const keepPaidForRecovery = (e instanceof AgentUnavailableError || e instanceof SeedFailedError || e instanceof PaymentTimeoutError) && currentStatus === "paid";
|
|
2519
2554
|
if (currentStatus !== "executed" && !keepPaidForRecovery) {
|
|
2520
2555
|
this.ledger.markFailed(job.jobId);
|
|
2521
2556
|
}
|
|
2522
2557
|
if (keepPaidForRecovery) {
|
|
2523
|
-
log(
|
|
2524
|
-
`[${job.jobId.slice(0, 8)}] Keeping status=paid; recovery will re-execute when LLM pair recovers (24h cutoff).`
|
|
2525
|
-
);
|
|
2558
|
+
log(`[${job.jobId.slice(0, 8)}] Keeping status=paid; recovery will retry (24h cutoff).`);
|
|
2526
2559
|
}
|
|
2527
2560
|
const operatorMessage = e instanceof ScriptExecutionError ? `${e.message}: ${e.detail}` : e.message ?? "Unknown error";
|
|
2528
2561
|
this.callbacks.onJobError?.(job.jobId, operatorMessage);
|
|
@@ -2590,7 +2623,7 @@ var AgentRuntime = class {
|
|
|
2590
2623
|
);
|
|
2591
2624
|
this.callbacks.onPaymentReceived?.(job.jobId, netAmount);
|
|
2592
2625
|
}
|
|
2593
|
-
const inputFile = await this.resolveInputFile(job.attachment, job.customerId);
|
|
2626
|
+
const inputFile = await this.resolveInputFile(job.attachment, job.customerId, signal);
|
|
2594
2627
|
await this.transport.sendFeedback(job, { type: "processing" }).catch(() => {
|
|
2595
2628
|
});
|
|
2596
2629
|
const skill = this.skills.route(job.tags);
|
|
@@ -2660,7 +2693,7 @@ var AgentRuntime = class {
|
|
|
2660
2693
|
});
|
|
2661
2694
|
}
|
|
2662
2695
|
}
|
|
2663
|
-
const {
|
|
2696
|
+
const { attachments, deliveredContent } = await this.buildResultAttachment(
|
|
2664
2697
|
job.jobId,
|
|
2665
2698
|
output,
|
|
2666
2699
|
job.customerId,
|
|
@@ -2672,12 +2705,36 @@ var AgentRuntime = class {
|
|
|
2672
2705
|
job,
|
|
2673
2706
|
deliveredContent,
|
|
2674
2707
|
netAmount,
|
|
2675
|
-
|
|
2708
|
+
attachments.length > 0 ? attachments : void 0
|
|
2676
2709
|
);
|
|
2677
2710
|
this.ledger.markDelivered(job.jobId);
|
|
2678
2711
|
log(`[${job.jobId.slice(0, 8)}] Delivered: ${eventId.slice(0, 16)}...`);
|
|
2679
2712
|
this.callbacks.onJobCompleted?.(job.jobId, output.data);
|
|
2680
2713
|
}
|
|
2714
|
+
/**
|
|
2715
|
+
* Seed a result blob through iroh, timing it and converting any failure (incl. the
|
|
2716
|
+
* transport's seed timeout, which also resets the wedged node) into a
|
|
2717
|
+
* `SeedFailedError` so the post-execute catch keeps the job `paid` for recovery
|
|
2718
|
+
* rather than losing the customer's paid job.
|
|
2719
|
+
*/
|
|
2720
|
+
async seedGuarded(jobId, kind, run) {
|
|
2721
|
+
const log = this.callbacks.onLog ?? console.log;
|
|
2722
|
+
const started = Date.now();
|
|
2723
|
+
try {
|
|
2724
|
+
const seeded = await run();
|
|
2725
|
+
const elapsed = Date.now() - started;
|
|
2726
|
+
if (elapsed > SLOW_SEED_LOG_MS) {
|
|
2727
|
+
log(`[${jobId.slice(0, 8)}] iroh seed (${kind}) slow: ${elapsed}ms`);
|
|
2728
|
+
}
|
|
2729
|
+
return seeded;
|
|
2730
|
+
} catch (error) {
|
|
2731
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2732
|
+
log(
|
|
2733
|
+
`[${jobId.slice(0, 8)}] iroh seed (${kind}) failed after ${Date.now() - started}ms: ${detail}`
|
|
2734
|
+
);
|
|
2735
|
+
throw new SeedFailedError(error);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2681
2738
|
/**
|
|
2682
2739
|
* Decide how a skill's result travels: inline text, a seeded file, or seeded
|
|
2683
2740
|
* large text. Returns the attachment descriptor (if any) PLUS the content to
|
|
@@ -2694,59 +2751,91 @@ var AgentRuntime = class {
|
|
|
2694
2751
|
*/
|
|
2695
2752
|
async buildResultAttachment(jobId, output, customerPubkey, acceptedTransports) {
|
|
2696
2753
|
const wantBlossom = acceptedTransports === void 0 || acceptedTransports.includes("blossom");
|
|
2697
|
-
|
|
2754
|
+
const filePaths = output.filePaths ?? (output.filePath !== void 0 ? [output.filePath] : []);
|
|
2755
|
+
if (filePaths.length > 0) {
|
|
2698
2756
|
try {
|
|
2699
|
-
|
|
2700
|
-
|
|
2757
|
+
const attachments = [];
|
|
2758
|
+
for (const filePath of filePaths) {
|
|
2759
|
+
attachments.push(
|
|
2760
|
+
await this.seedFileToAttachment(
|
|
2761
|
+
jobId,
|
|
2762
|
+
filePath,
|
|
2763
|
+
output.outputMime,
|
|
2764
|
+
customerPubkey,
|
|
2765
|
+
wantBlossom
|
|
2766
|
+
)
|
|
2767
|
+
);
|
|
2701
2768
|
}
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
output.filePath,
|
|
2707
|
-
seeded.size,
|
|
2708
|
-
customerPubkey
|
|
2769
|
+
let deliveredContent = output.data;
|
|
2770
|
+
if (utf8ByteLength(output.data) > LIMITS.MAX_ENCRYPTED_INLINE_BYTES) {
|
|
2771
|
+
attachments.push(
|
|
2772
|
+
await this.spillTextAttachment(jobId, output.data, customerPubkey, wantBlossom)
|
|
2709
2773
|
);
|
|
2710
|
-
|
|
2711
|
-
transports.unshift(blossomMember);
|
|
2712
|
-
}
|
|
2774
|
+
deliveredContent = "";
|
|
2713
2775
|
}
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
transports
|
|
2719
|
-
};
|
|
2720
|
-
this.ledger.recordAttachment(jobId, { resultAttachment: JSON.stringify(attachment) });
|
|
2721
|
-
return { attachment, deliveredContent: output.data };
|
|
2776
|
+
this.ledger.recordAttachment(jobId, {
|
|
2777
|
+
resultAttachments: attachments.map((a) => JSON.stringify(a))
|
|
2778
|
+
});
|
|
2779
|
+
return { attachments, deliveredContent };
|
|
2722
2780
|
} finally {
|
|
2723
2781
|
await output.cleanup?.().catch(() => {
|
|
2724
2782
|
});
|
|
2725
2783
|
}
|
|
2726
2784
|
}
|
|
2727
2785
|
if (utf8ByteLength(output.data) > LIMITS.MAX_ENCRYPTED_INLINE_BYTES) {
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2786
|
+
const attachment = await this.spillTextAttachment(
|
|
2787
|
+
jobId,
|
|
2788
|
+
output.data,
|
|
2789
|
+
customerPubkey,
|
|
2790
|
+
wantBlossom
|
|
2791
|
+
);
|
|
2792
|
+
this.ledger.recordAttachment(jobId, { resultAttachments: [JSON.stringify(attachment)] });
|
|
2793
|
+
return { attachments: [attachment], deliveredContent: "" };
|
|
2794
|
+
}
|
|
2795
|
+
return { attachments: [], deliveredContent: output.data };
|
|
2796
|
+
}
|
|
2797
|
+
/**
|
|
2798
|
+
* Seed a text note too large for inline delivery to iroh (+ encrypted Blossom when
|
|
2799
|
+
* wanted) and return it as a `result.txt` FileAttachment. Shared by the large-text
|
|
2800
|
+
* result path and the file-result path (a file result may carry an oversized note).
|
|
2801
|
+
*/
|
|
2802
|
+
async spillTextAttachment(jobId, text, customerPubkey, wantBlossom) {
|
|
2803
|
+
if (!this.irohTransport) {
|
|
2804
|
+
throw new Error("Result is too large to deliver inline and iroh transport is unavailable.");
|
|
2805
|
+
}
|
|
2806
|
+
const iroh = this.irohTransport;
|
|
2807
|
+
const dataBytes = Buffer.from(text, "utf8");
|
|
2808
|
+
const seeded = await this.seedGuarded(jobId, "large-text", () => iroh.seedBytes(dataBytes));
|
|
2809
|
+
const transports = [{ kind: "iroh", ticket: seeded.ticket }];
|
|
2810
|
+
if (wantBlossom) {
|
|
2811
|
+
const blossomMember = await this.seedBlossomMember(dataBytes, customerPubkey);
|
|
2812
|
+
if (blossomMember !== void 0) {
|
|
2813
|
+
transports.unshift(blossomMember);
|
|
2739
2814
|
}
|
|
2740
|
-
const attachment = {
|
|
2741
|
-
name: "result.txt",
|
|
2742
|
-
size: seeded.size,
|
|
2743
|
-
mime: "text/plain",
|
|
2744
|
-
transports
|
|
2745
|
-
};
|
|
2746
|
-
this.ledger.recordAttachment(jobId, { resultAttachment: JSON.stringify(attachment) });
|
|
2747
|
-
return { attachment, deliveredContent: "" };
|
|
2748
2815
|
}
|
|
2749
|
-
return {
|
|
2816
|
+
return { name: "result.txt", size: seeded.size, mime: "text/plain", transports };
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* Seed ONE result file to iroh (+ encrypted Blossom when wanted) and build its
|
|
2820
|
+
* FileAttachment. A seed timeout throws SeedFailedError (job stays `paid` for
|
|
2821
|
+
* recovery). Shared by the single- and multi-file result paths.
|
|
2822
|
+
*/
|
|
2823
|
+
async seedFileToAttachment(jobId, filePath, outputMime, customerPubkey, wantBlossom) {
|
|
2824
|
+
if (!this.irohTransport) {
|
|
2825
|
+
throw new Error("Skill produced a file result but iroh transport is unavailable.");
|
|
2826
|
+
}
|
|
2827
|
+
const iroh = this.irohTransport;
|
|
2828
|
+
const seeded = await this.seedGuarded(jobId, "file", () => iroh.seedPath(filePath));
|
|
2829
|
+
const transports = [{ kind: "iroh", ticket: seeded.ticket }];
|
|
2830
|
+
if (wantBlossom) {
|
|
2831
|
+
const blossomMember = await this.seedBlossomFromPath(filePath, seeded.size, customerPubkey);
|
|
2832
|
+
if (blossomMember !== void 0) {
|
|
2833
|
+
transports.unshift(blossomMember);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
const producedName = basename(filePath);
|
|
2837
|
+
const name = extname(producedName) ? producedName : `${producedName}${extensionForMime(outputMime)}`;
|
|
2838
|
+
return { name, size: seeded.size, mime: outputMime ?? "application/octet-stream", transports };
|
|
2750
2839
|
}
|
|
2751
2840
|
/**
|
|
2752
2841
|
* Encrypt `bytes` to `recipientPubkey` and seed them to Blossom, returning a `blossom` transport
|
|
@@ -2808,6 +2897,23 @@ var AgentRuntime = class {
|
|
|
2808
2897
|
);
|
|
2809
2898
|
return { ...stored, transports };
|
|
2810
2899
|
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Re-share ALL of a recovered job's result attachments (multi-file aware). Prefers
|
|
2902
|
+
* the `result_attachments` array; falls back to the legacy single `result_attachment`.
|
|
2903
|
+
* A failure on ANY attachment throws (the caller marks the whole job failed - a
|
|
2904
|
+
* partial multi-file delivery is not a valid paid result).
|
|
2905
|
+
*/
|
|
2906
|
+
async reShareResultAttachments(entry) {
|
|
2907
|
+
const stored = entry.result_attachments ?? (entry.result_attachment !== void 0 ? [entry.result_attachment] : []);
|
|
2908
|
+
const out = [];
|
|
2909
|
+
for (const serialized of stored) {
|
|
2910
|
+
const attachment = await this.reShareResultAttachment(serialized);
|
|
2911
|
+
if (attachment !== void 0) {
|
|
2912
|
+
out.push(attachment);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
return out;
|
|
2916
|
+
}
|
|
2811
2917
|
/**
|
|
2812
2918
|
* Resolve a job's file/large-text input (when it carries an attachment) via iroh.
|
|
2813
2919
|
* Only called post-payment - free + attachment is rejected in the `executeJob`
|
|
@@ -2821,51 +2927,77 @@ var AgentRuntime = class {
|
|
|
2821
2927
|
* `fetchToBytes`/`fetchToPath` enforce the real cap on the BLAKE3-verified size, so
|
|
2822
2928
|
* an incorrect declared `size` cannot exceed the in-memory ceiling.
|
|
2823
2929
|
*/
|
|
2824
|
-
async resolveInputFile(attachment, senderPubkey) {
|
|
2930
|
+
async resolveInputFile(attachment, senderPubkey, signal) {
|
|
2825
2931
|
if (attachment === void 0) {
|
|
2826
2932
|
return void 0;
|
|
2827
2933
|
}
|
|
2828
|
-
const
|
|
2829
|
-
|
|
2830
|
-
);
|
|
2831
|
-
|
|
2832
|
-
(
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2934
|
+
const fetchTimeoutMs = this.config.executionTimeoutSecs && this.config.executionTimeoutSecs > 0 ? this.config.executionTimeoutSecs * 1e3 : void 0;
|
|
2935
|
+
const fetchAbort = new AbortController();
|
|
2936
|
+
const onParentAbort = () => fetchAbort.abort();
|
|
2937
|
+
if (signal) {
|
|
2938
|
+
if (signal.aborted) {
|
|
2939
|
+
fetchAbort.abort();
|
|
2940
|
+
} else {
|
|
2941
|
+
signal.addEventListener("abort", onParentAbort);
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
const budgetTimer = fetchTimeoutMs !== void 0 ? setTimeout(() => fetchAbort.abort(), fetchTimeoutMs) : void 0;
|
|
2945
|
+
try {
|
|
2946
|
+
const irohMember = attachment.transports.find(
|
|
2947
|
+
(t) => t.kind === "iroh"
|
|
2948
|
+
);
|
|
2949
|
+
const blossomMember = attachment.transports.find(
|
|
2950
|
+
(t) => t.kind === "blossom"
|
|
2951
|
+
);
|
|
2952
|
+
if (irohMember !== void 0 && this.irohTransport !== void 0) {
|
|
2953
|
+
if (attachment.mime.startsWith("text/") && attachment.size <= LIMITS.MAX_REINLINE_TEXT_BYTES) {
|
|
2954
|
+
const bytes = await this.irohTransport.fetchToBytes(irohMember.ticket, {
|
|
2955
|
+
maxBytes: LIMITS.MAX_REINLINE_TEXT_BYTES,
|
|
2956
|
+
timeoutMs: fetchTimeoutMs,
|
|
2957
|
+
signal: fetchAbort.signal
|
|
2958
|
+
});
|
|
2959
|
+
return this.materializeBytesInput(bytes, attachment.mime);
|
|
2960
|
+
}
|
|
2961
|
+
const dir = await mkdtemp(join(tmpdir(), "elisym-job-"));
|
|
2962
|
+
const filePath = join(dir, "input");
|
|
2963
|
+
try {
|
|
2964
|
+
await this.irohTransport.fetchToPath(irohMember.ticket, filePath, {
|
|
2965
|
+
timeoutMs: fetchTimeoutMs,
|
|
2966
|
+
signal: fetchAbort.signal
|
|
2967
|
+
});
|
|
2968
|
+
} catch (error) {
|
|
2969
|
+
await rm(dir, { recursive: true, force: true }).catch(() => {
|
|
2970
|
+
});
|
|
2971
|
+
throw error;
|
|
2972
|
+
}
|
|
2973
|
+
return {
|
|
2974
|
+
filePath,
|
|
2975
|
+
cleanup: async () => {
|
|
2976
|
+
await rm(dir, { recursive: true, force: true });
|
|
2977
|
+
}
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
if (blossomMember !== void 0 && this.blossomTransport !== void 0 && this.identity !== void 0) {
|
|
2981
|
+
if (attachment.size > LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES) {
|
|
2982
|
+
throw new Error("Blossom file input exceeds the encrypted size cap.");
|
|
2983
|
+
}
|
|
2984
|
+
const bytes = await this.blossomTransport.fetchToBytes({
|
|
2985
|
+
transport: blossomMember,
|
|
2986
|
+
senderPubkey,
|
|
2987
|
+
maxBytes: LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES,
|
|
2988
|
+
signal: fetchAbort.signal
|
|
2838
2989
|
});
|
|
2839
2990
|
return this.materializeBytesInput(bytes, attachment.mime);
|
|
2840
2991
|
}
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
} catch (error) {
|
|
2846
|
-
await rm(dir, { recursive: true, force: true }).catch(() => {
|
|
2847
|
-
});
|
|
2848
|
-
throw error;
|
|
2992
|
+
throw new Error("Job carries a file input but no supported transport is available.");
|
|
2993
|
+
} finally {
|
|
2994
|
+
if (budgetTimer !== void 0) {
|
|
2995
|
+
clearTimeout(budgetTimer);
|
|
2849
2996
|
}
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
cleanup: async () => {
|
|
2853
|
-
await rm(dir, { recursive: true, force: true });
|
|
2854
|
-
}
|
|
2855
|
-
};
|
|
2856
|
-
}
|
|
2857
|
-
if (blossomMember !== void 0 && this.blossomTransport !== void 0 && this.identity !== void 0) {
|
|
2858
|
-
if (attachment.size > LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES) {
|
|
2859
|
-
throw new Error("Blossom file input exceeds the encrypted size cap.");
|
|
2997
|
+
if (signal) {
|
|
2998
|
+
signal.removeEventListener("abort", onParentAbort);
|
|
2860
2999
|
}
|
|
2861
|
-
const bytes = await this.blossomTransport.fetchToBytes({
|
|
2862
|
-
transport: blossomMember,
|
|
2863
|
-
senderPubkey,
|
|
2864
|
-
maxBytes: LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES
|
|
2865
|
-
});
|
|
2866
|
-
return this.materializeBytesInput(bytes, attachment.mime);
|
|
2867
3000
|
}
|
|
2868
|
-
throw new Error("Job carries a file input but no supported transport is available.");
|
|
2869
3001
|
}
|
|
2870
3002
|
/**
|
|
2871
3003
|
* Turn fetched input bytes into a SkillInput: text within the re-inline ceiling becomes an
|
|
@@ -3062,7 +3194,10 @@ var AgentRuntime = class {
|
|
|
3062
3194
|
}
|
|
3063
3195
|
await this.transport.sendFeedback(job, { type: "error", message: "payment timeout" }).catch(() => {
|
|
3064
3196
|
});
|
|
3065
|
-
|
|
3197
|
+
if (customerAbandoned) {
|
|
3198
|
+
throw new Error("Payment timeout");
|
|
3199
|
+
}
|
|
3200
|
+
throw new PaymentTimeoutError();
|
|
3066
3201
|
}
|
|
3067
3202
|
async recoverPendingJobs() {
|
|
3068
3203
|
const pending = this.ledger.pendingJobs().filter((e) => !this.inFlight.has(e.job_id));
|
|
@@ -3160,15 +3295,20 @@ var AgentRuntime = class {
|
|
|
3160
3295
|
};
|
|
3161
3296
|
if (entry.status === "executed" && entry.result !== void 0) {
|
|
3162
3297
|
this.ledger.incrementRetry(entry.job_id);
|
|
3163
|
-
let
|
|
3298
|
+
let attachments;
|
|
3164
3299
|
try {
|
|
3165
|
-
|
|
3300
|
+
attachments = await this.reShareResultAttachments(entry);
|
|
3166
3301
|
} catch {
|
|
3167
3302
|
log(`[${entry.job_id.slice(0, 8)}] Recovery: result blob unavailable, marking failed`);
|
|
3168
3303
|
this.ledger.markFailed(entry.job_id);
|
|
3169
3304
|
return;
|
|
3170
3305
|
}
|
|
3171
|
-
await this.transport.deliverResult(
|
|
3306
|
+
await this.transport.deliverResult(
|
|
3307
|
+
fakeJob,
|
|
3308
|
+
entry.result,
|
|
3309
|
+
entry.net_amount,
|
|
3310
|
+
attachments.length > 0 ? attachments : void 0
|
|
3311
|
+
);
|
|
3172
3312
|
this.ledger.markDelivered(entry.job_id);
|
|
3173
3313
|
log(`[${entry.job_id.slice(0, 8)}] Recovery: re-delivered`);
|
|
3174
3314
|
} else if (entry.status === "paid") {
|
|
@@ -3213,9 +3353,15 @@ var AgentRuntime = class {
|
|
|
3213
3353
|
}
|
|
3214
3354
|
let recoveryInputFile;
|
|
3215
3355
|
try {
|
|
3356
|
+
const encrypted = rawEvent.tags?.some(
|
|
3357
|
+
(tag) => tag[0] === "encrypted" && tag[1] === "nip44"
|
|
3358
|
+
);
|
|
3359
|
+
const iTag = rawEvent.tags?.find((tag) => tag[0] === "i");
|
|
3360
|
+
const rawInput = encrypted ? rawEvent.content : iTag?.[1] ?? rawEvent.content;
|
|
3216
3361
|
recoveryInputFile = await this.resolveInputFile(
|
|
3217
|
-
decodeJobPayload(
|
|
3218
|
-
entry.customer_id
|
|
3362
|
+
decodeJobPayload(rawInput).attachment,
|
|
3363
|
+
entry.customer_id,
|
|
3364
|
+
recoveryAbort.signal
|
|
3219
3365
|
);
|
|
3220
3366
|
} catch {
|
|
3221
3367
|
log(`[${entry.job_id.slice(0, 8)}] Recovery: input file unavailable, marking failed`);
|
|
@@ -3251,6 +3397,9 @@ var AgentRuntime = class {
|
|
|
3251
3397
|
} else {
|
|
3252
3398
|
output = await execPromise;
|
|
3253
3399
|
}
|
|
3400
|
+
} catch (err) {
|
|
3401
|
+
this.markHealthFromExecuteError(skill, err, log, entry.job_id);
|
|
3402
|
+
throw err;
|
|
3254
3403
|
} finally {
|
|
3255
3404
|
if (budgetTimer) {
|
|
3256
3405
|
clearTimeout(budgetTimer);
|
|
@@ -3260,7 +3409,7 @@ var AgentRuntime = class {
|
|
|
3260
3409
|
});
|
|
3261
3410
|
}
|
|
3262
3411
|
}
|
|
3263
|
-
const {
|
|
3412
|
+
const { attachments: resultAttachments, deliveredContent } = await this.buildResultAttachment(
|
|
3264
3413
|
entry.job_id,
|
|
3265
3414
|
output,
|
|
3266
3415
|
entry.customer_id,
|
|
@@ -3271,7 +3420,7 @@ var AgentRuntime = class {
|
|
|
3271
3420
|
fakeJob,
|
|
3272
3421
|
deliveredContent,
|
|
3273
3422
|
entry.net_amount,
|
|
3274
|
-
|
|
3423
|
+
resultAttachments.length > 0 ? resultAttachments : void 0
|
|
3275
3424
|
);
|
|
3276
3425
|
this.ledger.markDelivered(entry.job_id);
|
|
3277
3426
|
log(`[${entry.job_id.slice(0, 8)}] Recovery: re-executed and delivered`);
|
|
@@ -3464,8 +3613,9 @@ var DynamicScriptSkill = class {
|
|
|
3464
3613
|
llmOverride;
|
|
3465
3614
|
// Discovery hints surfaced in the published capability card (buildCard).
|
|
3466
3615
|
// `outputMime` is also forwarded to the inner SDK runner (it labels the file
|
|
3467
|
-
// result); `inputMime`
|
|
3616
|
+
// result); `inputMime`/`inputText` are publish-time metadata only.
|
|
3468
3617
|
inputMime;
|
|
3618
|
+
inputText;
|
|
3469
3619
|
outputMime;
|
|
3470
3620
|
inner;
|
|
3471
3621
|
constructor(params) {
|
|
@@ -3479,6 +3629,7 @@ var DynamicScriptSkill = class {
|
|
|
3479
3629
|
this.dir = params.dir;
|
|
3480
3630
|
this.llmOverride = params.llmOverride;
|
|
3481
3631
|
this.inputMime = params.inputMime;
|
|
3632
|
+
this.inputText = params.inputText;
|
|
3482
3633
|
this.outputMime = params.outputMime;
|
|
3483
3634
|
this.inner = new DynamicScriptSkill$1({
|
|
3484
3635
|
name: params.name,
|
|
@@ -3647,7 +3798,8 @@ function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
|
3647
3798
|
skill = parsed.mode === "dynamic-script" ? new DynamicScriptSkill({
|
|
3648
3799
|
...scriptParams,
|
|
3649
3800
|
outputMime: parsed.outputMime,
|
|
3650
|
-
inputMime: parsed.inputMime
|
|
3801
|
+
inputMime: parsed.inputMime,
|
|
3802
|
+
inputText: parsed.inputText
|
|
3651
3803
|
}) : new StaticScriptSkill(scriptParams);
|
|
3652
3804
|
break;
|
|
3653
3805
|
}
|
|
@@ -3856,7 +4008,7 @@ var NostrTransport = class {
|
|
|
3856
4008
|
}
|
|
3857
4009
|
}
|
|
3858
4010
|
/** Deliver result to customer. Retries with exponential backoff via SDK. */
|
|
3859
|
-
async deliverResult(job, content, amount,
|
|
4011
|
+
async deliverResult(job, content, amount, attachments, retries = 3) {
|
|
3860
4012
|
return this.client.marketplace.submitJobResultWithRetry(
|
|
3861
4013
|
this.identity,
|
|
3862
4014
|
job.rawEvent,
|
|
@@ -3864,7 +4016,7 @@ var NostrTransport = class {
|
|
|
3864
4016
|
amount,
|
|
3865
4017
|
retries,
|
|
3866
4018
|
void 0,
|
|
3867
|
-
|
|
4019
|
+
attachments
|
|
3868
4020
|
);
|
|
3869
4021
|
}
|
|
3870
4022
|
/** Returns true if an event was received within the given idle window. */
|
|
@@ -4052,11 +4204,13 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
4052
4204
|
}
|
|
4053
4205
|
console.log();
|
|
4054
4206
|
} catch (e) {
|
|
4055
|
-
|
|
4207
|
+
const message = typeof e?.message === "string" ? e.message : String(e);
|
|
4208
|
+
console.warn(` ! Wallet error: ${redactRpcUrlsInText(message)}
|
|
4056
4209
|
`);
|
|
4057
4210
|
}
|
|
4058
4211
|
}
|
|
4059
4212
|
const scriptEnv = { ...process.env };
|
|
4213
|
+
delete scriptEnv.ELISYM_PASSPHRASE;
|
|
4060
4214
|
const llmKeys = loaded.secrets.llm_api_keys ?? {};
|
|
4061
4215
|
for (const descriptor of listLlmProviders()) {
|
|
4062
4216
|
const secretValue = llmKeys[descriptor.id];
|
|
@@ -4417,9 +4571,10 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
4417
4571
|
capabilities: skill.capabilities,
|
|
4418
4572
|
image: skill.image,
|
|
4419
4573
|
...isStatic ? { static: true } : {},
|
|
4420
|
-
// File-exchange hints (dynamic-script only). `inputMime`
|
|
4421
|
-
//
|
|
4574
|
+
// File-exchange hints (dynamic-script only). `inputMime` flags a file input;
|
|
4575
|
+
// `inputText` tells the web whether to also show its text box for that file job.
|
|
4422
4576
|
...skill.inputMime ? { inputMime: skill.inputMime } : {},
|
|
4577
|
+
...skill.inputText ? { inputText: skill.inputText } : {},
|
|
4423
4578
|
...skill.outputMime ? { outputMime: skill.outputMime } : {},
|
|
4424
4579
|
payment: solanaAddress ? {
|
|
4425
4580
|
chain: "solana",
|
|
@@ -4609,6 +4764,9 @@ function stripRpcSecrets(raw) {
|
|
|
4609
4764
|
return "[unparseable RPC URL]";
|
|
4610
4765
|
}
|
|
4611
4766
|
}
|
|
4767
|
+
function redactRpcUrlsInText(text) {
|
|
4768
|
+
return text.replace(/https?:\/\/[^\s)'"]+/g, (url) => stripRpcSecrets(url));
|
|
4769
|
+
}
|
|
4612
4770
|
async function resolveMediaField(value, agentDir, cache, blossom, identity, onCacheUpdate) {
|
|
4613
4771
|
if (!value) {
|
|
4614
4772
|
return void 0;
|