@elisym/cli 0.21.3 → 0.22.1
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 +160 -41
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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 } from 'node:path';
|
|
5
|
-
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, USDC_SOLANA_DEVNET, ElisymClient,
|
|
4
|
+
import { dirname, join, resolve, basename, relative, sep, extname } from 'node:path';
|
|
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';
|
|
8
8
|
import { generateSecretKey, getPublicKey, nip19, verifyEvent } from 'nostr-tools';
|
|
@@ -14,7 +14,7 @@ import { createIrohTransport } from '@elisym/sdk/node';
|
|
|
14
14
|
import { lookup } from 'node:dns/promises';
|
|
15
15
|
import { Socket } from 'node:net';
|
|
16
16
|
import pino from 'pino';
|
|
17
|
-
import { mkdtemp, rm } from 'node:fs/promises';
|
|
17
|
+
import { readFile, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
18
18
|
import { tmpdir } from 'node:os';
|
|
19
19
|
import pLimit from 'p-limit';
|
|
20
20
|
import { parseSkillMd, validateSkillFrontmatter, resolveInsidePath, DEFAULT_SCRIPT_TIMEOUT_MS, DynamicScriptSkill as DynamicScriptSkill$1, StaticScriptSkill as StaticScriptSkill$1, StaticFileSkill as StaticFileSkill$1, ScriptSkill as ScriptSkill$1 } from '@elisym/sdk/skills';
|
|
@@ -2051,6 +2051,18 @@ function createLogger(options = {}) {
|
|
|
2051
2051
|
bannerLog: logWithIndent
|
|
2052
2052
|
};
|
|
2053
2053
|
}
|
|
2054
|
+
var MIME_BY_EXT = {
|
|
2055
|
+
".png": "image/png",
|
|
2056
|
+
".jpg": "image/jpeg",
|
|
2057
|
+
".jpeg": "image/jpeg",
|
|
2058
|
+
".gif": "image/gif",
|
|
2059
|
+
".webp": "image/webp",
|
|
2060
|
+
".svg": "image/svg+xml",
|
|
2061
|
+
".avif": "image/avif"
|
|
2062
|
+
};
|
|
2063
|
+
function mimeFromPath(path) {
|
|
2064
|
+
return MIME_BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
2065
|
+
}
|
|
2054
2066
|
var payment = new SolanaPaymentStrategy();
|
|
2055
2067
|
var LEDGER_GC_INTERVAL_MS = 60 * 60 * 1e3;
|
|
2056
2068
|
var LEDGER_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -2140,7 +2152,7 @@ var PAID_GLOBAL_MAX_JOBS_PER_WINDOW = 2e3;
|
|
|
2140
2152
|
var MAX_TRACKED_CUSTOMERS = 1e3;
|
|
2141
2153
|
var GLOBAL_LIMITER_KEY = "__global__";
|
|
2142
2154
|
var AgentRuntime = class {
|
|
2143
|
-
constructor(transport, skills, skillCtx, config, ledger, callbacks = {}, healthMonitor, irohTransport) {
|
|
2155
|
+
constructor(transport, skills, skillCtx, config, ledger, callbacks = {}, healthMonitor, irohTransport, identity, blossomTransport) {
|
|
2144
2156
|
this.transport = transport;
|
|
2145
2157
|
this.skills = skills;
|
|
2146
2158
|
this.skillCtx = skillCtx;
|
|
@@ -2149,6 +2161,8 @@ var AgentRuntime = class {
|
|
|
2149
2161
|
this.callbacks = callbacks;
|
|
2150
2162
|
this.healthMonitor = healthMonitor;
|
|
2151
2163
|
this.irohTransport = irohTransport;
|
|
2164
|
+
this.identity = identity;
|
|
2165
|
+
this.blossomTransport = blossomTransport;
|
|
2152
2166
|
this.limit = pLimit(config.maxConcurrentJobs);
|
|
2153
2167
|
this.maxQueueSize = config.maxQueueSize ?? config.maxConcurrentJobs * 10;
|
|
2154
2168
|
}
|
|
@@ -2576,7 +2590,7 @@ var AgentRuntime = class {
|
|
|
2576
2590
|
);
|
|
2577
2591
|
this.callbacks.onPaymentReceived?.(job.jobId, netAmount);
|
|
2578
2592
|
}
|
|
2579
|
-
const inputFile = await this.resolveInputFile(job.attachment);
|
|
2593
|
+
const inputFile = await this.resolveInputFile(job.attachment, job.customerId);
|
|
2580
2594
|
await this.transport.sendFeedback(job, { type: "processing" }).catch(() => {
|
|
2581
2595
|
});
|
|
2582
2596
|
const skill = this.skills.route(job.tags);
|
|
@@ -2646,7 +2660,12 @@ var AgentRuntime = class {
|
|
|
2646
2660
|
});
|
|
2647
2661
|
}
|
|
2648
2662
|
}
|
|
2649
|
-
const { attachment, deliveredContent } = await this.buildResultAttachment(
|
|
2663
|
+
const { attachment, deliveredContent } = await this.buildResultAttachment(
|
|
2664
|
+
job.jobId,
|
|
2665
|
+
output,
|
|
2666
|
+
job.customerId,
|
|
2667
|
+
readAcceptedTransports(job.rawEvent.tags)
|
|
2668
|
+
);
|
|
2650
2669
|
this.ledger.markExecuted(job.jobId, deliveredContent);
|
|
2651
2670
|
log(`[${job.jobId.slice(0, 8)}] Skill completed, delivering result`);
|
|
2652
2671
|
const eventId = await this.transport.deliverResult(
|
|
@@ -2673,18 +2692,30 @@ var AgentRuntime = class {
|
|
|
2673
2692
|
* recovery re-executes (rather than re-delivering a dead ticket). Runs after the
|
|
2674
2693
|
* `skill.execute` budget window, so a slow seed never trips `max_execution_secs`.
|
|
2675
2694
|
*/
|
|
2676
|
-
async buildResultAttachment(jobId, output) {
|
|
2695
|
+
async buildResultAttachment(jobId, output, customerPubkey, acceptedTransports) {
|
|
2696
|
+
const wantBlossom = acceptedTransports === void 0 || acceptedTransports.includes("blossom");
|
|
2677
2697
|
if (output.filePath !== void 0) {
|
|
2678
2698
|
try {
|
|
2679
2699
|
if (!this.irohTransport) {
|
|
2680
2700
|
throw new Error("Skill produced a file result but iroh transport is unavailable.");
|
|
2681
2701
|
}
|
|
2682
2702
|
const seeded = await this.irohTransport.seedPath(output.filePath);
|
|
2703
|
+
const transports = [{ kind: "iroh", ticket: seeded.ticket }];
|
|
2704
|
+
if (wantBlossom) {
|
|
2705
|
+
const blossomMember = await this.seedBlossomFromPath(
|
|
2706
|
+
output.filePath,
|
|
2707
|
+
seeded.size,
|
|
2708
|
+
customerPubkey
|
|
2709
|
+
);
|
|
2710
|
+
if (blossomMember !== void 0) {
|
|
2711
|
+
transports.unshift(blossomMember);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2683
2714
|
const attachment = {
|
|
2684
2715
|
name: basename(output.filePath),
|
|
2685
2716
|
size: seeded.size,
|
|
2686
2717
|
mime: output.outputMime ?? "application/octet-stream",
|
|
2687
|
-
transports
|
|
2718
|
+
transports
|
|
2688
2719
|
};
|
|
2689
2720
|
this.ledger.recordAttachment(jobId, { resultAttachment: JSON.stringify(attachment) });
|
|
2690
2721
|
return { attachment, deliveredContent: output.data };
|
|
@@ -2697,18 +2728,60 @@ var AgentRuntime = class {
|
|
|
2697
2728
|
if (!this.irohTransport) {
|
|
2698
2729
|
throw new Error("Result is too large to deliver inline and iroh transport is unavailable.");
|
|
2699
2730
|
}
|
|
2700
|
-
const
|
|
2731
|
+
const dataBytes = Buffer.from(output.data, "utf8");
|
|
2732
|
+
const seeded = await this.irohTransport.seedBytes(dataBytes);
|
|
2733
|
+
const transports = [{ kind: "iroh", ticket: seeded.ticket }];
|
|
2734
|
+
if (wantBlossom) {
|
|
2735
|
+
const blossomMember = await this.seedBlossomMember(dataBytes, customerPubkey);
|
|
2736
|
+
if (blossomMember !== void 0) {
|
|
2737
|
+
transports.unshift(blossomMember);
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2701
2740
|
const attachment = {
|
|
2702
2741
|
name: "result.txt",
|
|
2703
2742
|
size: seeded.size,
|
|
2704
2743
|
mime: "text/plain",
|
|
2705
|
-
transports
|
|
2744
|
+
transports
|
|
2706
2745
|
};
|
|
2707
2746
|
this.ledger.recordAttachment(jobId, { resultAttachment: JSON.stringify(attachment) });
|
|
2708
2747
|
return { attachment, deliveredContent: "" };
|
|
2709
2748
|
}
|
|
2710
2749
|
return { attachment: void 0, deliveredContent: output.data };
|
|
2711
2750
|
}
|
|
2751
|
+
/**
|
|
2752
|
+
* Encrypt `bytes` to `recipientPubkey` and seed them to Blossom, returning a `blossom` transport
|
|
2753
|
+
* member - or `undefined` when blossom isn't configured, the bytes exceed the encrypted cap, or the
|
|
2754
|
+
* upload fails. Returning undefined (never throwing) keeps iroh as the guaranteed transport: an
|
|
2755
|
+
* optional second path must never fail the job.
|
|
2756
|
+
*/
|
|
2757
|
+
async seedBlossomMember(bytes, recipientPubkey) {
|
|
2758
|
+
if (this.blossomTransport === void 0 || this.identity === void 0) {
|
|
2759
|
+
return void 0;
|
|
2760
|
+
}
|
|
2761
|
+
if (bytes.byteLength > LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES) {
|
|
2762
|
+
return void 0;
|
|
2763
|
+
}
|
|
2764
|
+
try {
|
|
2765
|
+
return await this.blossomTransport.seedBytes({ bytes, recipientPubkey });
|
|
2766
|
+
} catch {
|
|
2767
|
+
return void 0;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
/** Read a file (only when within the encrypted cap, to bound memory) and seed it to Blossom. */
|
|
2771
|
+
async seedBlossomFromPath(filePath, size, recipientPubkey) {
|
|
2772
|
+
if (this.blossomTransport === void 0 || this.identity === void 0) {
|
|
2773
|
+
return void 0;
|
|
2774
|
+
}
|
|
2775
|
+
if (size > LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES) {
|
|
2776
|
+
return void 0;
|
|
2777
|
+
}
|
|
2778
|
+
try {
|
|
2779
|
+
const bytes = await readFile(filePath);
|
|
2780
|
+
return await this.seedBlossomMember(bytes, recipientPubkey);
|
|
2781
|
+
} catch {
|
|
2782
|
+
return void 0;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2712
2785
|
/**
|
|
2713
2786
|
* Rebuild a file-result attachment for crash-recovery re-delivery: re-share the
|
|
2714
2787
|
* blob from the persistent store to mint a fresh ticket (the original ticket's
|
|
@@ -2723,12 +2796,17 @@ var AgentRuntime = class {
|
|
|
2723
2796
|
throw new Error("Cannot recover a file result: iroh transport is unavailable.");
|
|
2724
2797
|
}
|
|
2725
2798
|
const stored = JSON.parse(resultAttachmentJson);
|
|
2726
|
-
const irohTransport = stored.transports.find(
|
|
2799
|
+
const irohTransport = stored.transports.find(
|
|
2800
|
+
(transport) => transport.kind === "iroh"
|
|
2801
|
+
);
|
|
2727
2802
|
if (!irohTransport) {
|
|
2728
2803
|
throw new Error("Stored result attachment has no iroh transport.");
|
|
2729
2804
|
}
|
|
2730
2805
|
const freshTicket = await this.irohTransport.reShare(irohTransport.ticket);
|
|
2731
|
-
|
|
2806
|
+
const transports = stored.transports.map(
|
|
2807
|
+
(transport) => transport.kind === "iroh" ? { kind: "iroh", ticket: freshTicket } : transport
|
|
2808
|
+
);
|
|
2809
|
+
return { ...stored, transports };
|
|
2732
2810
|
}
|
|
2733
2811
|
/**
|
|
2734
2812
|
* Resolve a job's file/large-text input (when it carries an attachment) via iroh.
|
|
@@ -2743,31 +2821,67 @@ var AgentRuntime = class {
|
|
|
2743
2821
|
* `fetchToBytes`/`fetchToPath` enforce the real cap on the BLAKE3-verified size, so
|
|
2744
2822
|
* an incorrect declared `size` cannot exceed the in-memory ceiling.
|
|
2745
2823
|
*/
|
|
2746
|
-
async resolveInputFile(attachment) {
|
|
2824
|
+
async resolveInputFile(attachment, senderPubkey) {
|
|
2747
2825
|
if (attachment === void 0) {
|
|
2748
2826
|
return void 0;
|
|
2749
2827
|
}
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
const
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2828
|
+
const irohMember = attachment.transports.find(
|
|
2829
|
+
(t) => t.kind === "iroh"
|
|
2830
|
+
);
|
|
2831
|
+
const blossomMember = attachment.transports.find(
|
|
2832
|
+
(t) => t.kind === "blossom"
|
|
2833
|
+
);
|
|
2834
|
+
if (irohMember !== void 0 && this.irohTransport !== void 0) {
|
|
2835
|
+
if (attachment.mime.startsWith("text/") && attachment.size <= LIMITS.MAX_REINLINE_TEXT_BYTES) {
|
|
2836
|
+
const bytes = await this.irohTransport.fetchToBytes(irohMember.ticket, {
|
|
2837
|
+
maxBytes: LIMITS.MAX_REINLINE_TEXT_BYTES
|
|
2838
|
+
});
|
|
2839
|
+
return this.materializeBytesInput(bytes, attachment.mime);
|
|
2840
|
+
}
|
|
2841
|
+
const dir = await mkdtemp(join(tmpdir(), "elisym-job-"));
|
|
2842
|
+
const filePath = join(dir, "input");
|
|
2843
|
+
try {
|
|
2844
|
+
await this.irohTransport.fetchToPath(irohMember.ticket, filePath);
|
|
2845
|
+
} catch (error) {
|
|
2846
|
+
await rm(dir, { recursive: true, force: true }).catch(() => {
|
|
2847
|
+
});
|
|
2848
|
+
throw error;
|
|
2849
|
+
}
|
|
2761
2850
|
return {
|
|
2762
|
-
|
|
2851
|
+
filePath,
|
|
2763
2852
|
cleanup: async () => {
|
|
2853
|
+
await rm(dir, { recursive: true, force: true });
|
|
2764
2854
|
}
|
|
2765
2855
|
};
|
|
2766
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.");
|
|
2860
|
+
}
|
|
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
|
+
}
|
|
2868
|
+
throw new Error("Job carries a file input but no supported transport is available.");
|
|
2869
|
+
}
|
|
2870
|
+
/**
|
|
2871
|
+
* Turn fetched input bytes into a SkillInput: text within the re-inline ceiling becomes an
|
|
2872
|
+
* in-memory string; anything else is written to a fixed `input` name inside a unique mkdtemp dir
|
|
2873
|
+
* (the untrusted attachment `name` never touches the path). Shared by the iroh-text and blossom
|
|
2874
|
+
* paths (both have the bytes in hand); the iroh-binary path streams to disk separately.
|
|
2875
|
+
*/
|
|
2876
|
+
async materializeBytesInput(bytes, mime) {
|
|
2877
|
+
if (mime.startsWith("text/") && bytes.byteLength <= LIMITS.MAX_REINLINE_TEXT_BYTES) {
|
|
2878
|
+
return { inlineText: Buffer.from(bytes).toString("utf8"), cleanup: async () => {
|
|
2879
|
+
} };
|
|
2880
|
+
}
|
|
2767
2881
|
const dir = await mkdtemp(join(tmpdir(), "elisym-job-"));
|
|
2768
2882
|
const filePath = join(dir, "input");
|
|
2769
2883
|
try {
|
|
2770
|
-
await
|
|
2884
|
+
await writeFile(filePath, bytes);
|
|
2771
2885
|
} catch (error) {
|
|
2772
2886
|
await rm(dir, { recursive: true, force: true }).catch(() => {
|
|
2773
2887
|
});
|
|
@@ -3100,7 +3214,8 @@ var AgentRuntime = class {
|
|
|
3100
3214
|
let recoveryInputFile;
|
|
3101
3215
|
try {
|
|
3102
3216
|
recoveryInputFile = await this.resolveInputFile(
|
|
3103
|
-
decodeJobPayload(rawEvent.content).attachment
|
|
3217
|
+
decodeJobPayload(rawEvent.content).attachment,
|
|
3218
|
+
entry.customer_id
|
|
3104
3219
|
);
|
|
3105
3220
|
} catch {
|
|
3106
3221
|
log(`[${entry.job_id.slice(0, 8)}] Recovery: input file unavailable, marking failed`);
|
|
@@ -3147,7 +3262,9 @@ var AgentRuntime = class {
|
|
|
3147
3262
|
}
|
|
3148
3263
|
const { attachment: resultAttachment, deliveredContent } = await this.buildResultAttachment(
|
|
3149
3264
|
entry.job_id,
|
|
3150
|
-
output
|
|
3265
|
+
output,
|
|
3266
|
+
entry.customer_id,
|
|
3267
|
+
readAcceptedTransports(rawEvent.tags)
|
|
3151
3268
|
);
|
|
3152
3269
|
this.ledger.markExecuted(entry.job_id, deliveredContent);
|
|
3153
3270
|
await this.transport.deliverResult(
|
|
@@ -4163,14 +4280,13 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
4163
4280
|
}
|
|
4164
4281
|
}
|
|
4165
4282
|
}
|
|
4166
|
-
const media = new MediaService();
|
|
4167
4283
|
const mediaCache = await readMediaCache(loaded.dir);
|
|
4168
4284
|
let mediaCacheDirty = false;
|
|
4169
4285
|
const pictureUrl = await resolveMediaField(
|
|
4170
4286
|
loaded.yaml.picture,
|
|
4171
4287
|
loaded.dir,
|
|
4172
4288
|
mediaCache,
|
|
4173
|
-
|
|
4289
|
+
client.blossom,
|
|
4174
4290
|
identity,
|
|
4175
4291
|
(updated) => mediaCacheDirty = mediaCacheDirty || updated
|
|
4176
4292
|
);
|
|
@@ -4178,7 +4294,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
4178
4294
|
loaded.yaml.banner,
|
|
4179
4295
|
loaded.dir,
|
|
4180
4296
|
mediaCache,
|
|
4181
|
-
|
|
4297
|
+
client.blossom,
|
|
4182
4298
|
identity,
|
|
4183
4299
|
(updated) => mediaCacheDirty = mediaCacheDirty || updated
|
|
4184
4300
|
);
|
|
@@ -4194,7 +4310,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
4194
4310
|
cacheKey,
|
|
4195
4311
|
absPath,
|
|
4196
4312
|
mediaCache,
|
|
4197
|
-
|
|
4313
|
+
client.blossom,
|
|
4198
4314
|
identity,
|
|
4199
4315
|
() => mediaCacheDirty = true
|
|
4200
4316
|
);
|
|
@@ -4434,6 +4550,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
4434
4550
|
});
|
|
4435
4551
|
diagLog("LLM health monitor armed (lazy recovery, 5min interval).");
|
|
4436
4552
|
}
|
|
4553
|
+
const blossomTransport = createBlossomTransport({ blossom: client.blossom, identity });
|
|
4437
4554
|
const runtime = new AgentRuntime(
|
|
4438
4555
|
transport,
|
|
4439
4556
|
registry,
|
|
@@ -4465,7 +4582,9 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
4465
4582
|
}
|
|
4466
4583
|
},
|
|
4467
4584
|
healthMonitor,
|
|
4468
|
-
irohTransport
|
|
4585
|
+
irohTransport,
|
|
4586
|
+
identity,
|
|
4587
|
+
blossomTransport
|
|
4469
4588
|
);
|
|
4470
4589
|
console.log(" * Running. Press Ctrl+C to stop.\n");
|
|
4471
4590
|
await runtime.run();
|
|
@@ -4490,7 +4609,7 @@ function stripRpcSecrets(raw) {
|
|
|
4490
4609
|
return "[unparseable RPC URL]";
|
|
4491
4610
|
}
|
|
4492
4611
|
}
|
|
4493
|
-
async function resolveMediaField(value, agentDir, cache,
|
|
4612
|
+
async function resolveMediaField(value, agentDir, cache, blossom, identity, onCacheUpdate) {
|
|
4494
4613
|
if (!value) {
|
|
4495
4614
|
return void 0;
|
|
4496
4615
|
}
|
|
@@ -4502,7 +4621,7 @@ async function resolveMediaField(value, agentDir, cache, media, identity, onCach
|
|
|
4502
4621
|
console.warn(` ! Skipping media field "${value}": path must stay inside the agent directory.`);
|
|
4503
4622
|
return void 0;
|
|
4504
4623
|
}
|
|
4505
|
-
return uploadOrReuse(value, absPath, cache,
|
|
4624
|
+
return uploadOrReuse(value, absPath, cache, blossom, identity, () => onCacheUpdate(true));
|
|
4506
4625
|
}
|
|
4507
4626
|
function resolveInsideAgentDir(value, agentDir) {
|
|
4508
4627
|
const agentRoot = resolve(agentDir);
|
|
@@ -4513,7 +4632,7 @@ function resolveInsideAgentDir(value, agentDir) {
|
|
|
4513
4632
|
}
|
|
4514
4633
|
return candidate;
|
|
4515
4634
|
}
|
|
4516
|
-
async function uploadOrReuse(cacheKey, absPath, cache,
|
|
4635
|
+
async function uploadOrReuse(cacheKey, absPath, cache, blossom, identity, onCacheUpdate) {
|
|
4517
4636
|
try {
|
|
4518
4637
|
const cached = await lookupCachedUrl(cache, cacheKey, absPath);
|
|
4519
4638
|
if (cached) {
|
|
@@ -4522,12 +4641,12 @@ async function uploadOrReuse(cacheKey, absPath, cache, media, identity, onCacheU
|
|
|
4522
4641
|
console.log(` Uploading ${basename(absPath)}...`);
|
|
4523
4642
|
const data = readFileSync(absPath);
|
|
4524
4643
|
const sha256 = createHash("sha256").update(data).digest("hex");
|
|
4525
|
-
const blob = new Blob([data]);
|
|
4526
|
-
const
|
|
4527
|
-
cache[cacheKey] = newCacheEntry(url, sha256);
|
|
4644
|
+
const blob = new Blob([data], { type: mimeFromPath(absPath) });
|
|
4645
|
+
const descriptor = await blossom.upload(identity, blob);
|
|
4646
|
+
cache[cacheKey] = newCacheEntry(descriptor.url, sha256);
|
|
4528
4647
|
onCacheUpdate();
|
|
4529
|
-
console.log(` Uploaded: ${url}`);
|
|
4530
|
-
return url;
|
|
4648
|
+
console.log(` Uploaded: ${descriptor.url}`);
|
|
4649
|
+
return descriptor.url;
|
|
4531
4650
|
} catch (e) {
|
|
4532
4651
|
console.warn(` ! Failed to upload ${basename(absPath)}: ${e.message}`);
|
|
4533
4652
|
return void 0;
|