@themoltnet/node-red-contrib-core 0.9.0 → 0.10.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.
@@ -0,0 +1,74 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('moltnet-runtime-session-download', {
3
+ category: 'moltnet',
4
+ color: '#00d4c8',
5
+ paletteLabel: 'runtime session: download',
6
+ defaults: {
7
+ name: { value: '' },
8
+ agent: { value: '', type: 'moltnet-agent', required: true },
9
+ taskId: { value: '' },
10
+ teamId: { value: '' },
11
+ allowMsgTeamOverride: { value: false },
12
+ attemptN: { value: '', validate: RED.validators.number() },
13
+ maxBytes: { value: 26214400, validate: RED.validators.number() },
14
+ },
15
+ inputs: 1,
16
+ outputs: 1,
17
+ icon: 'font-awesome/fa-download',
18
+ label: function () {
19
+ return this.name || 'runtime session: download';
20
+ },
21
+ });
22
+ </script>
23
+
24
+ <script type="text/html" data-template-name="moltnet-runtime-session-download">
25
+ <div class="form-row">
26
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
27
+ <input type="text" id="node-input-name" />
28
+ </div>
29
+ <div class="form-row">
30
+ <label for="node-input-agent"><i class="fa fa-user"></i> Agent</label>
31
+ <input type="text" id="node-input-agent" />
32
+ </div>
33
+ <div class="form-row">
34
+ <label for="node-input-taskId"><i class="fa fa-hashtag"></i> Task ID</label>
35
+ <input
36
+ type="text"
37
+ id="node-input-taskId"
38
+ placeholder="msg.payload.taskId"
39
+ />
40
+ </div>
41
+ <div class="form-row">
42
+ <label for="node-input-teamId"><i class="fa fa-users"></i> Team ID</label>
43
+ <input type="text" id="node-input-teamId" placeholder="agent teamId" />
44
+ </div>
45
+ <div class="form-row">
46
+ <label for="node-input-allowMsgTeamOverride"
47
+ ><i class="fa fa-random"></i> Team override</label
48
+ >
49
+ <input
50
+ type="checkbox"
51
+ id="node-input-allowMsgTeamOverride"
52
+ style="display: inline-block; width: auto; vertical-align: top"
53
+ />
54
+ <span>Allow <code>msg.teamId</code></span>
55
+ </div>
56
+ <div class="form-row">
57
+ <label for="node-input-attemptN"
58
+ ><i class="fa fa-history"></i> Attempt</label
59
+ >
60
+ <input type="number" id="node-input-attemptN" placeholder="msg.attemptN" />
61
+ </div>
62
+ <div class="form-row">
63
+ <label for="node-input-maxBytes"><i class="fa fa-database"></i> Max</label>
64
+ <input type="number" id="node-input-maxBytes" />
65
+ </div>
66
+ </script>
67
+
68
+ <script type="text/html" data-help-name="moltnet-runtime-session-download">
69
+ <p>
70
+ Downloads durable runtime session content for a task attempt. Emits session
71
+ bytes as a Buffer on <code>msg.payload</code> and context metadata on
72
+ <code>msg.runtimeSession</code>. The local byte limit defaults to 25 MiB.
73
+ </p>
74
+ </script>
@@ -0,0 +1,57 @@
1
+ import { t as withAgent } from "./agent-call.js";
2
+ import { t as bool } from "./query-utils.js";
3
+ import { a as requireAttemptContext, s as resolveMaxBytes, t as collectArtifactBody } from "./task-artifact-utils.js";
4
+ //#region src/nodes/runtime-session-download.ts
5
+ var init = (RED) => {
6
+ function RuntimeSessionDownloadNode(def) {
7
+ RED.nodes.createNode(this, def);
8
+ const agentNode = def.agent ? RED.nodes.getNode(def.agent) : null;
9
+ this.on("input", (msg, send, done) => {
10
+ const run = async () => {
11
+ try {
12
+ if (!agentNode || typeof agentNode.getAgent !== "function") throw new Error("runtime-session-download: no moltnet-agent configured");
13
+ const { taskId, teamId, attemptN } = requireAttemptContext("runtime-session-download", msg, def.taskId, def.teamId, def.attemptN, agentNode, bool(def.allowMsgTeamOverride) ?? false);
14
+ this.status({
15
+ fill: "blue",
16
+ shape: "dot",
17
+ text: "downloading…"
18
+ });
19
+ const body = await collectArtifactBody(await withAgent(agentNode, (agent) => agent.runtimeSessions.download({
20
+ taskId,
21
+ attemptN
22
+ }, { teamId })), resolveMaxBytes(def.maxBytes), "runtime-session-download");
23
+ const out = RED.util.cloneMessage({
24
+ ...msg,
25
+ payload: void 0
26
+ });
27
+ out.payload = body;
28
+ out.taskId = taskId;
29
+ out.runtimeSession = {
30
+ taskId,
31
+ teamId,
32
+ attemptN,
33
+ sizeBytes: body.byteLength
34
+ };
35
+ this.status({
36
+ fill: "green",
37
+ shape: "dot",
38
+ text: `${body.byteLength} byte(s)`
39
+ });
40
+ send(out);
41
+ done();
42
+ } catch (err) {
43
+ this.status({
44
+ fill: "red",
45
+ shape: "ring",
46
+ text: "error"
47
+ });
48
+ done(err instanceof Error ? err : new Error(String(err)));
49
+ }
50
+ };
51
+ run();
52
+ });
53
+ }
54
+ RED.nodes.registerType("moltnet-runtime-session-download", RuntimeSessionDownloadNode);
55
+ };
56
+ //#endregion
57
+ export { init as default };
@@ -0,0 +1,73 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('moltnet-runtime-session-get', {
3
+ category: 'moltnet',
4
+ color: '#00d4c8',
5
+ paletteLabel: 'runtime session: get',
6
+ defaults: {
7
+ name: { value: '' },
8
+ agent: { value: '', type: 'moltnet-agent', required: true },
9
+ taskId: { value: '' },
10
+ teamId: { value: '' },
11
+ allowMsgTeamOverride: { value: false },
12
+ attemptN: { value: '', validate: RED.validators.number() },
13
+ },
14
+ inputs: 1,
15
+ outputs: 1,
16
+ icon: 'font-awesome/fa-info-circle',
17
+ label: function () {
18
+ return this.name || 'runtime session: get';
19
+ },
20
+ });
21
+ </script>
22
+
23
+ <script type="text/html" data-template-name="moltnet-runtime-session-get">
24
+ <div class="form-row">
25
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
26
+ <input type="text" id="node-input-name" />
27
+ </div>
28
+ <div class="form-row">
29
+ <label for="node-input-agent"><i class="fa fa-user"></i> Agent</label>
30
+ <input type="text" id="node-input-agent" />
31
+ </div>
32
+ <div class="form-row">
33
+ <label for="node-input-taskId"><i class="fa fa-hashtag"></i> Task ID</label>
34
+ <input
35
+ type="text"
36
+ id="node-input-taskId"
37
+ placeholder="msg.payload.taskId"
38
+ />
39
+ </div>
40
+ <div class="form-row">
41
+ <label for="node-input-teamId"><i class="fa fa-users"></i> Team ID</label>
42
+ <input type="text" id="node-input-teamId" placeholder="agent teamId" />
43
+ </div>
44
+ <div class="form-row">
45
+ <label for="node-input-allowMsgTeamOverride"
46
+ ><i class="fa fa-random"></i> Team override</label
47
+ >
48
+ <input
49
+ type="checkbox"
50
+ id="node-input-allowMsgTeamOverride"
51
+ style="display: inline-block; width: auto; vertical-align: top"
52
+ />
53
+ <span>Allow <code>msg.teamId</code></span>
54
+ </div>
55
+ <div class="form-row">
56
+ <label for="node-input-attemptN"
57
+ ><i class="fa fa-history"></i> Attempt</label
58
+ >
59
+ <input type="number" id="node-input-attemptN" placeholder="msg.attemptN" />
60
+ </div>
61
+ </script>
62
+
63
+ <script type="text/html" data-help-name="moltnet-runtime-session-get">
64
+ <p>
65
+ Fetches durable runtime session metadata for a task attempt. Team ID is
66
+ taken from this node or the configured agent unless Team override is
67
+ enabled.
68
+ </p>
69
+ <p>
70
+ Emits the metadata or <code>null</code> on <code>msg.payload</code> and a
71
+ context envelope on <code>msg.runtimeSession</code>.
72
+ </p>
73
+ </script>
@@ -0,0 +1,54 @@
1
+ import { t as withAgent } from "./agent-call.js";
2
+ import { t as bool } from "./query-utils.js";
3
+ import { a as requireAttemptContext } from "./task-artifact-utils.js";
4
+ //#region src/nodes/runtime-session-get.ts
5
+ var init = (RED) => {
6
+ function RuntimeSessionGetNode(def) {
7
+ RED.nodes.createNode(this, def);
8
+ const agentNode = def.agent ? RED.nodes.getNode(def.agent) : null;
9
+ this.on("input", (msg, send, done) => {
10
+ const run = async () => {
11
+ try {
12
+ if (!agentNode || typeof agentNode.getAgent !== "function") throw new Error("runtime-session-get: no moltnet-agent configured");
13
+ const { taskId, teamId, attemptN } = requireAttemptContext("runtime-session-get", msg, def.taskId, def.teamId, def.attemptN, agentNode, bool(def.allowMsgTeamOverride) ?? false);
14
+ this.status({
15
+ fill: "blue",
16
+ shape: "dot",
17
+ text: "loading…"
18
+ });
19
+ const session = await withAgent(agentNode, (agent) => agent.runtimeSessions.getForAttempt({
20
+ taskId,
21
+ attemptN
22
+ }, { teamId }));
23
+ const out = RED.util.cloneMessage(msg);
24
+ out.payload = session;
25
+ out.taskId = taskId;
26
+ out.runtimeSession = {
27
+ taskId,
28
+ teamId,
29
+ attemptN,
30
+ session
31
+ };
32
+ this.status({
33
+ fill: session ? "green" : "yellow",
34
+ shape: session ? "dot" : "ring",
35
+ text: session ? "found" : "not found"
36
+ });
37
+ send(out);
38
+ done();
39
+ } catch (err) {
40
+ this.status({
41
+ fill: "red",
42
+ shape: "ring",
43
+ text: "error"
44
+ });
45
+ done(err instanceof Error ? err : new Error(String(err)));
46
+ }
47
+ };
48
+ run();
49
+ });
50
+ }
51
+ RED.nodes.registerType("moltnet-runtime-session-get", RuntimeSessionGetNode);
52
+ };
53
+ //#endregion
54
+ export { init as default };
@@ -0,0 +1,120 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('moltnet-runtime-session-upload', {
3
+ category: 'moltnet',
4
+ color: '#00d4c8',
5
+ paletteLabel: 'runtime session: upload',
6
+ defaults: {
7
+ name: { value: '' },
8
+ agent: { value: '', type: 'moltnet-agent', required: true },
9
+ taskId: { value: '' },
10
+ teamId: { value: '' },
11
+ allowMsgTeamOverride: { value: false },
12
+ attemptN: { value: '', validate: RED.validators.number() },
13
+ maxBytes: { value: 26214400, validate: RED.validators.number() },
14
+ sessionKind: { value: 'root' },
15
+ parentSessionId: { value: '' },
16
+ sourceSlotId: { value: '' },
17
+ sourceRuntimeProfileId: { value: '' },
18
+ },
19
+ inputs: 1,
20
+ outputs: 1,
21
+ icon: 'font-awesome/fa-upload',
22
+ label: function () {
23
+ return this.name || 'runtime session: upload';
24
+ },
25
+ });
26
+ </script>
27
+
28
+ <script type="text/html" data-template-name="moltnet-runtime-session-upload">
29
+ <div class="form-row">
30
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
31
+ <input type="text" id="node-input-name" />
32
+ </div>
33
+ <div class="form-row">
34
+ <label for="node-input-agent"><i class="fa fa-user"></i> Agent</label>
35
+ <input type="text" id="node-input-agent" />
36
+ </div>
37
+ <div class="form-row">
38
+ <label for="node-input-taskId"><i class="fa fa-hashtag"></i> Task ID</label>
39
+ <input
40
+ type="text"
41
+ id="node-input-taskId"
42
+ placeholder="msg.payload.taskId"
43
+ />
44
+ </div>
45
+ <div class="form-row">
46
+ <label for="node-input-teamId"><i class="fa fa-users"></i> Team ID</label>
47
+ <input type="text" id="node-input-teamId" placeholder="agent teamId" />
48
+ </div>
49
+ <div class="form-row">
50
+ <label for="node-input-allowMsgTeamOverride"
51
+ ><i class="fa fa-random"></i> Team override</label
52
+ >
53
+ <input
54
+ type="checkbox"
55
+ id="node-input-allowMsgTeamOverride"
56
+ style="display: inline-block; width: auto; vertical-align: top"
57
+ />
58
+ <span>Allow <code>msg.teamId</code></span>
59
+ </div>
60
+ <div class="form-row">
61
+ <label for="node-input-attemptN"
62
+ ><i class="fa fa-history"></i> Attempt</label
63
+ >
64
+ <input type="number" id="node-input-attemptN" placeholder="msg.attemptN" />
65
+ </div>
66
+ <div class="form-row">
67
+ <label for="node-input-sessionKind"
68
+ ><i class="fa fa-code-fork"></i> Kind</label
69
+ >
70
+ <select id="node-input-sessionKind">
71
+ <option value="root">Root</option>
72
+ <option value="extend">Extend</option>
73
+ <option value="fork">Fork</option>
74
+ </select>
75
+ </div>
76
+ <div class="form-row">
77
+ <label for="node-input-parentSessionId"
78
+ ><i class="fa fa-link"></i> Parent</label
79
+ >
80
+ <input
81
+ type="text"
82
+ id="node-input-parentSessionId"
83
+ placeholder="parent runtime session UUID"
84
+ />
85
+ </div>
86
+ <div class="form-row">
87
+ <label for="node-input-sourceSlotId"
88
+ ><i class="fa fa-map-pin"></i> Slot</label
89
+ >
90
+ <input type="text" id="node-input-sourceSlotId" placeholder="slot UUID" />
91
+ </div>
92
+ <div class="form-row">
93
+ <label for="node-input-sourceRuntimeProfileId"
94
+ ><i class="fa fa-cog"></i> Profile</label
95
+ >
96
+ <input
97
+ type="text"
98
+ id="node-input-sourceRuntimeProfileId"
99
+ placeholder="runtime profile UUID"
100
+ />
101
+ </div>
102
+ <div class="form-row">
103
+ <label for="node-input-maxBytes"><i class="fa fa-database"></i> Max</label>
104
+ <input type="number" id="node-input-maxBytes" />
105
+ </div>
106
+ </script>
107
+
108
+ <script type="text/html" data-help-name="moltnet-runtime-session-upload">
109
+ <p>
110
+ Uploads durable runtime session bytes for a task attempt. The body is read
111
+ from <code>msg.payload</code> when it is a string, Buffer, Uint8Array, or
112
+ ArrayBuffer. Object payloads may provide <code>content</code>,
113
+ <code>body</code>, or base64 <code>contentBase64</code>.
114
+ </p>
115
+ <p>
116
+ Session kind and optional parent/source ids can be supplied by node fields
117
+ or matching fields on <code>msg.payload</code>. Emits session metadata on
118
+ <code>msg.payload</code> and <code>msg.runtimeSession</code>.
119
+ </p>
120
+ </script>
@@ -0,0 +1,62 @@
1
+ import { t as withAgent } from "./agent-call.js";
2
+ import { n as compact, t as bool } from "./query-utils.js";
3
+ import { a as requireAttemptContext, c as resolveUploadBody, o as resolveField, s as resolveMaxBytes } from "./task-artifact-utils.js";
4
+ //#region src/nodes/runtime-session-upload.ts
5
+ var init = (RED) => {
6
+ function RuntimeSessionUploadNode(def) {
7
+ RED.nodes.createNode(this, def);
8
+ const agentNode = def.agent ? RED.nodes.getNode(def.agent) : null;
9
+ this.on("input", (msg, send, done) => {
10
+ const run = async () => {
11
+ try {
12
+ if (!agentNode || typeof agentNode.getAgent !== "function") throw new Error("runtime-session-upload: no moltnet-agent configured");
13
+ const { taskId, teamId, attemptN } = requireAttemptContext("runtime-session-upload", msg, def.taskId, def.teamId, def.attemptN, agentNode, bool(def.allowMsgTeamOverride) ?? false);
14
+ const body = resolveUploadBody(msg, resolveMaxBytes(def.maxBytes), "runtime-session-upload");
15
+ const query = buildUploadQuery(def, msg);
16
+ this.status({
17
+ fill: "blue",
18
+ shape: "dot",
19
+ text: "uploading…"
20
+ });
21
+ const session = await withAgent(agentNode, (agent) => agent.runtimeSessions.upload({
22
+ taskId,
23
+ attemptN
24
+ }, body, query, { teamId }));
25
+ const out = RED.util.cloneMessage({
26
+ ...msg,
27
+ payload: void 0
28
+ });
29
+ out.payload = session;
30
+ out.taskId = taskId;
31
+ out.runtimeSession = session;
32
+ this.status({
33
+ fill: "green",
34
+ shape: "dot",
35
+ text: session.sha256 ?? "uploaded"
36
+ });
37
+ send(out);
38
+ done();
39
+ } catch (err) {
40
+ this.status({
41
+ fill: "red",
42
+ shape: "ring",
43
+ text: "error"
44
+ });
45
+ done(err instanceof Error ? err : new Error(String(err)));
46
+ }
47
+ };
48
+ run();
49
+ });
50
+ }
51
+ RED.nodes.registerType("moltnet-runtime-session-upload", RuntimeSessionUploadNode);
52
+ };
53
+ function buildUploadQuery(def, msg) {
54
+ return compact({
55
+ sessionKind: resolveField(msg, "sessionKind", def.sessionKind) ?? resolveField(msg, "session_kind", void 0) ?? "root",
56
+ parentSessionId: resolveField(msg, "parentSessionId", def.parentSessionId) ?? resolveField(msg, "parent_session_id", void 0),
57
+ sourceSlotId: resolveField(msg, "sourceSlotId", def.sourceSlotId) ?? resolveField(msg, "source_slot_id", void 0),
58
+ sourceRuntimeProfileId: resolveField(msg, "sourceRuntimeProfileId", def.sourceRuntimeProfileId) ?? resolveField(msg, "source_runtime_profile_id", void 0)
59
+ });
60
+ }
61
+ //#endregion
62
+ export { init as default };
@@ -45,75 +45,75 @@ function resolveField(msg, name, configured) {
45
45
  function resolveMaxBytes(configured) {
46
46
  return positiveInt(configured) ?? 26214400;
47
47
  }
48
- function resolveUploadBody(msg, maxBytes) {
48
+ function resolveUploadBody(msg, maxBytes, nodeName = "task-artifact-upload") {
49
49
  const payload = msg.payload;
50
- if (Buffer.isBuffer(payload)) return enforceMaxBytes(payload, maxBytes);
51
- if (payload instanceof Uint8Array) return enforceMaxBytes(payload, maxBytes);
50
+ if (Buffer.isBuffer(payload)) return enforceMaxBytes(payload, maxBytes, nodeName);
51
+ if (payload instanceof Uint8Array) return enforceMaxBytes(payload, maxBytes, nodeName);
52
52
  if (payload instanceof ArrayBuffer) {
53
- if (payload.byteLength > maxBytes) throw tooLarge("upload", maxBytes);
53
+ if (payload.byteLength > maxBytes) throw tooLarge("upload", maxBytes, nodeName);
54
54
  return new Uint8Array(payload);
55
55
  }
56
56
  if (typeof payload === "string") {
57
- if (Buffer.byteLength(payload) > maxBytes) throw tooLarge("upload", maxBytes);
57
+ if (Buffer.byteLength(payload) > maxBytes) throw tooLarge("upload", maxBytes, nodeName);
58
58
  return new TextEncoder().encode(payload);
59
59
  }
60
60
  const record = payloadRecord(msg);
61
61
  if (typeof record.contentBase64 === "string") {
62
62
  const normalized = record.contentBase64.replace(/\s/g, "");
63
- if (decodedBase64Length(normalized) > maxBytes) throw tooLarge("upload", maxBytes);
63
+ if (decodedBase64Length(normalized) > maxBytes) throw tooLarge("upload", maxBytes, nodeName);
64
64
  return Buffer.from(normalized, "base64");
65
65
  }
66
66
  const content = record.content ?? record.body;
67
- if (Buffer.isBuffer(content)) return enforceMaxBytes(content, maxBytes);
68
- if (content instanceof Uint8Array) return enforceMaxBytes(content, maxBytes);
67
+ if (Buffer.isBuffer(content)) return enforceMaxBytes(content, maxBytes, nodeName);
68
+ if (content instanceof Uint8Array) return enforceMaxBytes(content, maxBytes, nodeName);
69
69
  if (content instanceof ArrayBuffer) {
70
- if (content.byteLength > maxBytes) throw tooLarge("upload", maxBytes);
70
+ if (content.byteLength > maxBytes) throw tooLarge("upload", maxBytes, nodeName);
71
71
  return new Uint8Array(content);
72
72
  }
73
73
  if (typeof content === "string") {
74
- if (Buffer.byteLength(content) > maxBytes) throw tooLarge("upload", maxBytes);
74
+ if (Buffer.byteLength(content) > maxBytes) throw tooLarge("upload", maxBytes, nodeName);
75
75
  return new TextEncoder().encode(content);
76
76
  }
77
- throw new Error("task-artifact-upload: payload content is required");
77
+ throw new Error(`${nodeName}: payload content is required`);
78
78
  }
79
- async function collectArtifactBody(value, maxBytes) {
79
+ async function collectArtifactBody(value, maxBytes, nodeName = "task-artifact-download") {
80
80
  const source = value && typeof value === "object" && "stream" in value ? value.stream : value;
81
81
  if (Buffer.isBuffer(source)) {
82
- if (source.byteLength > maxBytes) throw tooLarge("download", maxBytes);
82
+ if (source.byteLength > maxBytes) throw tooLarge("download", maxBytes, nodeName);
83
83
  return source;
84
84
  }
85
85
  if (source instanceof Uint8Array) {
86
- if (source.byteLength > maxBytes) throw tooLarge("download", maxBytes);
86
+ if (source.byteLength > maxBytes) throw tooLarge("download", maxBytes, nodeName);
87
87
  return Buffer.from(source);
88
88
  }
89
89
  if (source instanceof ArrayBuffer) {
90
- if (source.byteLength > maxBytes) throw tooLarge("download", maxBytes);
90
+ if (source.byteLength > maxBytes) throw tooLarge("download", maxBytes, nodeName);
91
91
  return Buffer.from(source);
92
92
  }
93
93
  if (typeof source === "string") {
94
- if (Buffer.byteLength(source) > maxBytes) throw tooLarge("download", maxBytes);
94
+ if (Buffer.byteLength(source) > maxBytes) throw tooLarge("download", maxBytes, nodeName);
95
95
  return Buffer.from(source);
96
96
  }
97
97
  if (source instanceof Readable) {
98
98
  const chunks = [];
99
99
  let bytes = 0;
100
- for await (const chunk of source) bytes = pushChunk(chunks, chunk, bytes, maxBytes);
100
+ for await (const chunk of source) bytes = pushChunk(chunks, chunk, bytes, maxBytes, nodeName);
101
101
  return Buffer.concat(chunks);
102
102
  }
103
103
  if (source && typeof source === "object" && Symbol.asyncIterator in source) {
104
104
  const chunks = [];
105
105
  let bytes = 0;
106
- for await (const chunk of source) bytes = pushChunk(chunks, chunk, bytes, maxBytes);
106
+ for await (const chunk of source) bytes = pushChunk(chunks, chunk, bytes, maxBytes, nodeName);
107
107
  return Buffer.concat(chunks);
108
108
  }
109
109
  if (source && typeof source === "object" && "arrayBuffer" in source) {
110
110
  const size = source.size;
111
- if (typeof size === "number" && size > maxBytes) throw tooLarge("download", maxBytes);
111
+ if (typeof size === "number" && size > maxBytes) throw tooLarge("download", maxBytes, nodeName);
112
112
  const arrayBuffer = await source.arrayBuffer();
113
- if (arrayBuffer.byteLength > maxBytes) throw tooLarge("download", maxBytes);
113
+ if (arrayBuffer.byteLength > maxBytes) throw tooLarge("download", maxBytes, nodeName);
114
114
  return Buffer.from(arrayBuffer);
115
115
  }
116
- throw new Error("task-artifact-download: unsupported artifact body");
116
+ throw new Error(`${nodeName}: unsupported downloaded body`);
117
117
  }
118
118
  function toBuffer(value) {
119
119
  if (Buffer.isBuffer(value)) return value;
@@ -126,14 +126,14 @@ function recordField(value, key) {
126
126
  if (!value || typeof value !== "object") return void 0;
127
127
  return value[key];
128
128
  }
129
- function enforceMaxBytes(value, maxBytes) {
130
- if (value.byteLength > maxBytes) throw tooLarge("upload", maxBytes);
129
+ function enforceMaxBytes(value, maxBytes, nodeName) {
130
+ if (value.byteLength > maxBytes) throw tooLarge("upload", maxBytes, nodeName);
131
131
  return value;
132
132
  }
133
- function pushChunk(chunks, chunk, bytes, maxBytes) {
133
+ function pushChunk(chunks, chunk, bytes, maxBytes, nodeName) {
134
134
  const next = toBuffer(chunk);
135
135
  const total = bytes + next.byteLength;
136
- if (total > maxBytes) throw tooLarge("download", maxBytes);
136
+ if (total > maxBytes) throw tooLarge("download", maxBytes, nodeName);
137
137
  chunks.push(next);
138
138
  return total;
139
139
  }
@@ -142,8 +142,8 @@ function decodedBase64Length(value) {
142
142
  const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
143
143
  return Math.floor(value.length * 3 / 4) - padding;
144
144
  }
145
- function tooLarge(operation, maxBytes) {
146
- return /* @__PURE__ */ new Error(`task-artifact-${operation}: artifact body exceeds ${maxBytes} bytes`);
145
+ function tooLarge(operation, maxBytes, nodeName) {
146
+ return /* @__PURE__ */ new Error(`${nodeName}: ${operation} body exceeds ${maxBytes} bytes`);
147
147
  }
148
148
  //#endregion
149
149
  export { requireAttemptContext as a, resolveUploadBody as c, requireArtifactContext as i, payloadRecord as n, resolveField as o, recordField as r, resolveMaxBytes as s, collectArtifactBody as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/node-red-contrib-core",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "description": "Node-RED nodes for the MoltNet API",
6
6
  "keywords": [
@@ -30,6 +30,9 @@
30
30
  "moltnet-task-artifacts-list": "dist/nodes/task-artifacts-list.js",
31
31
  "moltnet-task-artifact-upload": "dist/nodes/task-artifact-upload.js",
32
32
  "moltnet-task-artifact-download": "dist/nodes/task-artifact-download.js",
33
+ "moltnet-runtime-session-get": "dist/nodes/runtime-session-get.js",
34
+ "moltnet-runtime-session-upload": "dist/nodes/runtime-session-upload.js",
35
+ "moltnet-runtime-session-download": "dist/nodes/runtime-session-download.js",
33
36
  "moltnet-workflow-status": "dist/nodes/workflow-status.js",
34
37
  "moltnet-task-builder": "dist/nodes/task-builder.js",
35
38
  "moltnet-task-reader": "dist/nodes/task-reader.js",