@elisym/cli 0.22.3 → 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 +126 -62
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2115,6 +2115,12 @@ var SeedFailedError = class extends Error {
|
|
|
2115
2115
|
this.name = "SeedFailedError";
|
|
2116
2116
|
}
|
|
2117
2117
|
};
|
|
2118
|
+
var PaymentTimeoutError = class extends Error {
|
|
2119
|
+
constructor() {
|
|
2120
|
+
super("Payment verification timed out; awaiting late confirmation.");
|
|
2121
|
+
this.name = "PaymentTimeoutError";
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2118
2124
|
var MIME_EXTENSIONS = {
|
|
2119
2125
|
"image/png": ".png",
|
|
2120
2126
|
"image/jpeg": ".jpg",
|
|
@@ -2544,7 +2550,7 @@ var AgentRuntime = class {
|
|
|
2544
2550
|
const log = this.callbacks.onLog ?? console.log;
|
|
2545
2551
|
log(`[${job.jobId.slice(0, 8)}] Error: ${e.message}`);
|
|
2546
2552
|
const currentStatus = this.ledger.getStatus(job.jobId);
|
|
2547
|
-
const keepPaidForRecovery = (e instanceof AgentUnavailableError || e instanceof SeedFailedError) && currentStatus === "paid";
|
|
2553
|
+
const keepPaidForRecovery = (e instanceof AgentUnavailableError || e instanceof SeedFailedError || e instanceof PaymentTimeoutError) && currentStatus === "paid";
|
|
2548
2554
|
if (currentStatus !== "executed" && !keepPaidForRecovery) {
|
|
2549
2555
|
this.ledger.markFailed(job.jobId);
|
|
2550
2556
|
}
|
|
@@ -2617,7 +2623,7 @@ var AgentRuntime = class {
|
|
|
2617
2623
|
);
|
|
2618
2624
|
this.callbacks.onPaymentReceived?.(job.jobId, netAmount);
|
|
2619
2625
|
}
|
|
2620
|
-
const inputFile = await this.resolveInputFile(job.attachment, job.customerId);
|
|
2626
|
+
const inputFile = await this.resolveInputFile(job.attachment, job.customerId, signal);
|
|
2621
2627
|
await this.transport.sendFeedback(job, { type: "processing" }).catch(() => {
|
|
2622
2628
|
});
|
|
2623
2629
|
const skill = this.skills.route(job.tags);
|
|
@@ -2760,40 +2766,55 @@ var AgentRuntime = class {
|
|
|
2760
2766
|
)
|
|
2761
2767
|
);
|
|
2762
2768
|
}
|
|
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)
|
|
2773
|
+
);
|
|
2774
|
+
deliveredContent = "";
|
|
2775
|
+
}
|
|
2763
2776
|
this.ledger.recordAttachment(jobId, {
|
|
2764
2777
|
resultAttachments: attachments.map((a) => JSON.stringify(a))
|
|
2765
2778
|
});
|
|
2766
|
-
return { attachments, deliveredContent
|
|
2779
|
+
return { attachments, deliveredContent };
|
|
2767
2780
|
} finally {
|
|
2768
2781
|
await output.cleanup?.().catch(() => {
|
|
2769
2782
|
});
|
|
2770
2783
|
}
|
|
2771
2784
|
}
|
|
2772
2785
|
if (utf8ByteLength(output.data) > LIMITS.MAX_ENCRYPTED_INLINE_BYTES) {
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
const transports = [{ kind: "iroh", ticket: seeded.ticket }];
|
|
2780
|
-
if (wantBlossom) {
|
|
2781
|
-
const blossomMember = await this.seedBlossomMember(dataBytes, customerPubkey);
|
|
2782
|
-
if (blossomMember !== void 0) {
|
|
2783
|
-
transports.unshift(blossomMember);
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
const attachment = {
|
|
2787
|
-
name: "result.txt",
|
|
2788
|
-
size: seeded.size,
|
|
2789
|
-
mime: "text/plain",
|
|
2790
|
-
transports
|
|
2791
|
-
};
|
|
2786
|
+
const attachment = await this.spillTextAttachment(
|
|
2787
|
+
jobId,
|
|
2788
|
+
output.data,
|
|
2789
|
+
customerPubkey,
|
|
2790
|
+
wantBlossom
|
|
2791
|
+
);
|
|
2792
2792
|
this.ledger.recordAttachment(jobId, { resultAttachments: [JSON.stringify(attachment)] });
|
|
2793
2793
|
return { attachments: [attachment], deliveredContent: "" };
|
|
2794
2794
|
}
|
|
2795
2795
|
return { attachments: [], deliveredContent: output.data };
|
|
2796
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);
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
return { name: "result.txt", size: seeded.size, mime: "text/plain", transports };
|
|
2817
|
+
}
|
|
2797
2818
|
/**
|
|
2798
2819
|
* Seed ONE result file to iroh (+ encrypted Blossom when wanted) and build its
|
|
2799
2820
|
* FileAttachment. A seed timeout throws SeedFailedError (job stays `paid` for
|
|
@@ -2906,51 +2927,77 @@ var AgentRuntime = class {
|
|
|
2906
2927
|
* `fetchToBytes`/`fetchToPath` enforce the real cap on the BLAKE3-verified size, so
|
|
2907
2928
|
* an incorrect declared `size` cannot exceed the in-memory ceiling.
|
|
2908
2929
|
*/
|
|
2909
|
-
async resolveInputFile(attachment, senderPubkey) {
|
|
2930
|
+
async resolveInputFile(attachment, senderPubkey, signal) {
|
|
2910
2931
|
if (attachment === void 0) {
|
|
2911
2932
|
return void 0;
|
|
2912
2933
|
}
|
|
2913
|
-
const
|
|
2914
|
-
|
|
2915
|
-
);
|
|
2916
|
-
|
|
2917
|
-
(
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
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
|
|
2923
2989
|
});
|
|
2924
2990
|
return this.materializeBytesInput(bytes, attachment.mime);
|
|
2925
2991
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
} catch (error) {
|
|
2931
|
-
await rm(dir, { recursive: true, force: true }).catch(() => {
|
|
2932
|
-
});
|
|
2933
|
-
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);
|
|
2934
2996
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
cleanup: async () => {
|
|
2938
|
-
await rm(dir, { recursive: true, force: true });
|
|
2939
|
-
}
|
|
2940
|
-
};
|
|
2941
|
-
}
|
|
2942
|
-
if (blossomMember !== void 0 && this.blossomTransport !== void 0 && this.identity !== void 0) {
|
|
2943
|
-
if (attachment.size > LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES) {
|
|
2944
|
-
throw new Error("Blossom file input exceeds the encrypted size cap.");
|
|
2997
|
+
if (signal) {
|
|
2998
|
+
signal.removeEventListener("abort", onParentAbort);
|
|
2945
2999
|
}
|
|
2946
|
-
const bytes = await this.blossomTransport.fetchToBytes({
|
|
2947
|
-
transport: blossomMember,
|
|
2948
|
-
senderPubkey,
|
|
2949
|
-
maxBytes: LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES
|
|
2950
|
-
});
|
|
2951
|
-
return this.materializeBytesInput(bytes, attachment.mime);
|
|
2952
3000
|
}
|
|
2953
|
-
throw new Error("Job carries a file input but no supported transport is available.");
|
|
2954
3001
|
}
|
|
2955
3002
|
/**
|
|
2956
3003
|
* Turn fetched input bytes into a SkillInput: text within the re-inline ceiling becomes an
|
|
@@ -3147,7 +3194,10 @@ var AgentRuntime = class {
|
|
|
3147
3194
|
}
|
|
3148
3195
|
await this.transport.sendFeedback(job, { type: "error", message: "payment timeout" }).catch(() => {
|
|
3149
3196
|
});
|
|
3150
|
-
|
|
3197
|
+
if (customerAbandoned) {
|
|
3198
|
+
throw new Error("Payment timeout");
|
|
3199
|
+
}
|
|
3200
|
+
throw new PaymentTimeoutError();
|
|
3151
3201
|
}
|
|
3152
3202
|
async recoverPendingJobs() {
|
|
3153
3203
|
const pending = this.ledger.pendingJobs().filter((e) => !this.inFlight.has(e.job_id));
|
|
@@ -3303,9 +3353,15 @@ var AgentRuntime = class {
|
|
|
3303
3353
|
}
|
|
3304
3354
|
let recoveryInputFile;
|
|
3305
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;
|
|
3306
3361
|
recoveryInputFile = await this.resolveInputFile(
|
|
3307
|
-
decodeJobPayload(
|
|
3308
|
-
entry.customer_id
|
|
3362
|
+
decodeJobPayload(rawInput).attachment,
|
|
3363
|
+
entry.customer_id,
|
|
3364
|
+
recoveryAbort.signal
|
|
3309
3365
|
);
|
|
3310
3366
|
} catch {
|
|
3311
3367
|
log(`[${entry.job_id.slice(0, 8)}] Recovery: input file unavailable, marking failed`);
|
|
@@ -3341,6 +3397,9 @@ var AgentRuntime = class {
|
|
|
3341
3397
|
} else {
|
|
3342
3398
|
output = await execPromise;
|
|
3343
3399
|
}
|
|
3400
|
+
} catch (err) {
|
|
3401
|
+
this.markHealthFromExecuteError(skill, err, log, entry.job_id);
|
|
3402
|
+
throw err;
|
|
3344
3403
|
} finally {
|
|
3345
3404
|
if (budgetTimer) {
|
|
3346
3405
|
clearTimeout(budgetTimer);
|
|
@@ -4145,11 +4204,13 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
4145
4204
|
}
|
|
4146
4205
|
console.log();
|
|
4147
4206
|
} catch (e) {
|
|
4148
|
-
|
|
4207
|
+
const message = typeof e?.message === "string" ? e.message : String(e);
|
|
4208
|
+
console.warn(` ! Wallet error: ${redactRpcUrlsInText(message)}
|
|
4149
4209
|
`);
|
|
4150
4210
|
}
|
|
4151
4211
|
}
|
|
4152
4212
|
const scriptEnv = { ...process.env };
|
|
4213
|
+
delete scriptEnv.ELISYM_PASSPHRASE;
|
|
4153
4214
|
const llmKeys = loaded.secrets.llm_api_keys ?? {};
|
|
4154
4215
|
for (const descriptor of listLlmProviders()) {
|
|
4155
4216
|
const secretValue = llmKeys[descriptor.id];
|
|
@@ -4703,6 +4764,9 @@ function stripRpcSecrets(raw) {
|
|
|
4703
4764
|
return "[unparseable RPC URL]";
|
|
4704
4765
|
}
|
|
4705
4766
|
}
|
|
4767
|
+
function redactRpcUrlsInText(text) {
|
|
4768
|
+
return text.replace(/https?:\/\/[^\s)'"]+/g, (url) => stripRpcSecrets(url));
|
|
4769
|
+
}
|
|
4706
4770
|
async function resolveMediaField(value, agentDir, cache, blossom, identity, onCacheUpdate) {
|
|
4707
4771
|
if (!value) {
|
|
4708
4772
|
return void 0;
|