@themoltnet/node-red-contrib-core 0.11.0 → 0.12.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/README.md +20 -10
- package/dist/nodes/runtime-session-download.js +1 -1
- package/dist/nodes/runtime-session-upload.js +1 -1
- package/dist/nodes/src.js +257 -53
- package/dist/nodes/task-artifact-download.html +7 -2
- package/dist/nodes/task-artifact-download.js +10 -4
- package/dist/nodes/task-artifact-stage.html +93 -0
- package/dist/nodes/task-artifact-stage.js +54 -0
- package/dist/nodes/task-artifact-upload.js +1 -1
- package/dist/nodes/task-artifact-utils.js +35 -7
- package/dist/nodes/task-builder.js +2 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -15,10 +15,12 @@ Empirically validated against **Node-RED 5.0.0** (Node 22):
|
|
|
15
15
|
package carries no private-package runtime dependency. `@themoltnet/sdk` is
|
|
16
16
|
therefore a **devDependency** (bundled, not installed at runtime).
|
|
17
17
|
- The `.html` editor files are copied to `dist/nodes/` as assets (not compiled).
|
|
18
|
-
- Two **config nodes** (`moltnet-agent`, `moltnet-runtime-profile`) and
|
|
19
|
-
**action nodes** (`moltnet-tasks-create`, `moltnet-task-get`,
|
|
20
|
-
`moltnet-task-wait`, `moltnet-task-artifacts-list`,
|
|
21
|
-
`moltnet-task-artifact-
|
|
18
|
+
- Two **config nodes** (`moltnet-agent`, `moltnet-runtime-profile`) and the
|
|
19
|
+
MoltNet **action nodes** (`moltnet-tasks-create`, `moltnet-task-get`,
|
|
20
|
+
`moltnet-task-cancel`, `moltnet-task-wait`, `moltnet-task-artifacts-list`,
|
|
21
|
+
`moltnet-task-artifact-stage`, `moltnet-task-artifact-upload`,
|
|
22
|
+
`moltnet-task-artifact-download`, `moltnet-runtime-session-get`,
|
|
23
|
+
`moltnet-runtime-session-upload`, `moltnet-runtime-session-download`,
|
|
22
24
|
`moltnet-workflow-status`, `moltnet-task-builder`, `moltnet-task-reader`,
|
|
23
25
|
`moltnet-tasks-list`, `moltnet-entries-search`)
|
|
24
26
|
register and appear in the palette.
|
|
@@ -80,10 +82,17 @@ attempts, error, task }`. `state` is the accepted attempt's output artifact
|
|
|
80
82
|
`content`, `body`, or base64 `contentBase64`. Team context is the configured
|
|
81
83
|
node/agent by default; message team overrides require an explicit checkbox.
|
|
82
84
|
Emits artifact metadata on `msg.payload` and `msg.artifact`.
|
|
85
|
+
- **`moltnet-task-artifact-stage`** (palette: _task artifact: stage_) — stages
|
|
86
|
+
bytes for later binding as a task input artifact. It accepts the same payload
|
|
87
|
+
shapes and team controls as upload, but needs no task or attempt. The returned
|
|
88
|
+
metadata is emitted on `msg.payload` and `msg.artifact`; pass it to a task
|
|
89
|
+
builder's **References from** field.
|
|
83
90
|
- **`moltnet-task-artifact-download`** (palette: _task artifact: download_) —
|
|
84
|
-
downloads an artifact by task id
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
downloads an artifact by task id and CID. Set an attempt number for an exact
|
|
92
|
+
attempt artifact, or omit it to resolve the CID across the task, including a
|
|
93
|
+
bound input artifact. Emits the bytes as a Buffer on `msg.payload` and
|
|
94
|
+
metadata on `msg.artifact`. Stage, upload, and download nodes enforce a local
|
|
95
|
+
25 MiB byte limit by default.
|
|
87
96
|
- **`moltnet-workflow-status`** (palette: _workflow: status_) — reads the tasks of
|
|
88
97
|
one workflow run (by `correlationId`) and emits a table-shaped `msg.payload`
|
|
89
98
|
(array of `{ taskId, type, title, status, queuedAt, completedAt }`) plus
|
|
@@ -92,9 +101,10 @@ attempts, error, task }`. `state` is the accepted attempt's output artifact
|
|
|
92
101
|
`tasks.create` body from the SDK fluent builder (`buildFreeform`). Pure offline
|
|
93
102
|
transform: reads `teamId`/`diaryId` from the referenced `agent` (with optional
|
|
94
103
|
typedInput overrides), maps **context rows** (slug ← msg/flow/global/str/json),
|
|
95
|
-
binds a prior task's output via **References from**
|
|
96
|
-
`outputRef` from `task: read`
|
|
97
|
-
|
|
104
|
+
binds a prior task's output or staged input artifact via **References from**
|
|
105
|
+
(a `msg`-path to an `outputRef` from `task: read` or metadata from
|
|
106
|
+
`task artifact: stage`), and toggles the **submit-output** / schema gates.
|
|
107
|
+
Emits the flat body on `msg.payload` for a downstream `tasks: create`
|
|
98
108
|
(the SDK's `{ body, teamId }` envelope is flattened to `{ ...body, teamId }`).
|
|
99
109
|
Validation errors surface on the node (red ring).
|
|
100
110
|
- **`moltnet-task-reader`** (palette: _task: read_) — parses a completed snapshot
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as withAgent } from "./agent-call.js";
|
|
2
2
|
import { t as bool } from "./query-utils.js";
|
|
3
|
-
import { a as requireAttemptContext,
|
|
3
|
+
import { a as requireAttemptContext, c as resolveMaxBytes, t as collectArtifactBody } from "./task-artifact-utils.js";
|
|
4
4
|
//#region src/nodes/runtime-session-download.ts
|
|
5
5
|
var init = (RED) => {
|
|
6
6
|
function RuntimeSessionDownloadNode(def) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as withAgent } from "./agent-call.js";
|
|
2
2
|
import { n as compact, t as bool } from "./query-utils.js";
|
|
3
|
-
import { a as requireAttemptContext, c as
|
|
3
|
+
import { a as requireAttemptContext, c as resolveMaxBytes, s as resolveField, u as resolveUploadBody } from "./task-artifact-utils.js";
|
|
4
4
|
//#region src/nodes/runtime-session-upload.ts
|
|
5
5
|
var init = (RED) => {
|
|
6
6
|
function RuntimeSessionUploadNode(def) {
|
package/dist/nodes/src.js
CHANGED
|
@@ -1927,7 +1927,34 @@ var findLatestRuntimeSlotForAttempt = (options) => (options.client ?? client).ge
|
|
|
1927
1927
|
...options
|
|
1928
1928
|
});
|
|
1929
1929
|
/**
|
|
1930
|
-
*
|
|
1930
|
+
* Stage immutable content-addressed artifact bytes for later binding as task input artifacts via task creation references. Creates no metadata row; staged bytes are not downloadable until bound to a task, and unbound objects are garbage-collected after a grace window.
|
|
1931
|
+
*/
|
|
1932
|
+
var stageTaskArtifact = (options) => (options.client ?? client).put({
|
|
1933
|
+
bodySerializer: null,
|
|
1934
|
+
security: [
|
|
1935
|
+
{
|
|
1936
|
+
scheme: "bearer",
|
|
1937
|
+
type: "http"
|
|
1938
|
+
},
|
|
1939
|
+
{
|
|
1940
|
+
name: "X-Moltnet-Session-Token",
|
|
1941
|
+
type: "apiKey"
|
|
1942
|
+
},
|
|
1943
|
+
{
|
|
1944
|
+
in: "cookie",
|
|
1945
|
+
name: "ory_kratos_session",
|
|
1946
|
+
type: "apiKey"
|
|
1947
|
+
}
|
|
1948
|
+
],
|
|
1949
|
+
url: "/task-artifacts/staged",
|
|
1950
|
+
...options,
|
|
1951
|
+
headers: {
|
|
1952
|
+
"Content-Type": "application/octet-stream",
|
|
1953
|
+
...options.headers
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
/**
|
|
1957
|
+
* Queue asynchronous deletion of waiting, queued, and terminal tasks in bulk. By default, dispatched, running, unauthorized, missing, and protected tasks are skipped. Set force: true with a reason to delete protected terminal tasks.
|
|
1931
1958
|
*/
|
|
1932
1959
|
var batchDeleteTasks = (options) => (options.client ?? client).delete({
|
|
1933
1960
|
security: [
|
|
@@ -9379,12 +9406,15 @@ function Evaluate(type, options = {}) {
|
|
|
9379
9406
|
* V1 bindings only; Tier-2 (reference_file, mcp_resource, imported_file,
|
|
9380
9407
|
* tool_response_seed, additional_context_hook) ship in a later slice.
|
|
9381
9408
|
*/
|
|
9382
|
-
var
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
]
|
|
9409
|
+
var CONTEXT_BINDINGS = [
|
|
9410
|
+
"skill",
|
|
9411
|
+
"context_inline",
|
|
9412
|
+
"prompt_prefix",
|
|
9413
|
+
"user_inline"
|
|
9414
|
+
];
|
|
9415
|
+
/** Maximum UTF-16 code units accepted in one ContextRef content field. */
|
|
9416
|
+
var CONTEXT_REF_MAX_CONTENT_LENGTH = 65536;
|
|
9417
|
+
var ContextBinding = Unsafe(Union(CONTEXT_BINDINGS.map((binding) => Literal(binding)), { $id: "ContextBinding" }));
|
|
9388
9418
|
/** Reusable input fragment for any task type. Soft cap at 5 items. */
|
|
9389
9419
|
var TaskContext = _Array_(_Object_({
|
|
9390
9420
|
slug: String$1({
|
|
@@ -9395,7 +9425,7 @@ var TaskContext = _Array_(_Object_({
|
|
|
9395
9425
|
binding: ContextBinding,
|
|
9396
9426
|
content: String$1({
|
|
9397
9427
|
minLength: 1,
|
|
9398
|
-
maxLength:
|
|
9428
|
+
maxLength: CONTEXT_REF_MAX_CONTENT_LENGTH
|
|
9399
9429
|
})
|
|
9400
9430
|
}, {
|
|
9401
9431
|
$id: "ContextRef",
|
|
@@ -9633,6 +9663,11 @@ var RuntimeProfileSandboxResumeCommand = Union([String$1({
|
|
|
9633
9663
|
maximum: 6e4
|
|
9634
9664
|
}))
|
|
9635
9665
|
}, { additionalProperties: false })]);
|
|
9666
|
+
var RuntimeProfileAllowedHost = String$1({
|
|
9667
|
+
minLength: 1,
|
|
9668
|
+
maxLength: 255,
|
|
9669
|
+
pattern: "^(?:\\*\\.)?(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\\.(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))*$"
|
|
9670
|
+
});
|
|
9636
9671
|
var RuntimeProfileSandbox = _Object_({
|
|
9637
9672
|
snapshot: Optional(_Object_({
|
|
9638
9673
|
setupCommands: Optional(_Array_(String$1({
|
|
@@ -9649,6 +9684,10 @@ var RuntimeProfileSandbox = _Object_({
|
|
|
9649
9684
|
pattern: "^[0-9]+[KMGTP]?$"
|
|
9650
9685
|
}))
|
|
9651
9686
|
}, { additionalProperties: false })),
|
|
9687
|
+
network: Optional(_Object_({
|
|
9688
|
+
allowedHosts: Optional(_Array_(RuntimeProfileAllowedHost, { maxItems: 50 })),
|
|
9689
|
+
allowedInternalHosts: Optional(_Array_(RuntimeProfileAllowedHost, { maxItems: 50 }))
|
|
9690
|
+
}, { additionalProperties: false })),
|
|
9652
9691
|
resumeCommands: Optional(_Array_(RuntimeProfileSandboxResumeCommand, { maxItems: 30 })),
|
|
9653
9692
|
vfs: Optional(_Object_({
|
|
9654
9693
|
shadow: Optional(_Array_(String$1({
|
|
@@ -9992,7 +10031,7 @@ _Object_({
|
|
|
9992
10031
|
* (server-side schema check). Self-assessment is a truthful self-rating,
|
|
9993
10032
|
* NOT enforcement — `verification.passed=false` does not block /complete
|
|
9994
10033
|
* and does not affect `acceptedAttemptN`. See
|
|
9995
|
-
* `docs/
|
|
10034
|
+
* `docs/use/tasks-and-runtime.md` for the full producer/judge flow.
|
|
9996
10035
|
*
|
|
9997
10036
|
* **Binding evaluation** (judgment tasks: `assess_brief`, `judge_pack`).
|
|
9998
10037
|
* A separate task whose IS the application of `successCriteria` to
|
|
@@ -10108,7 +10147,7 @@ _Object_({
|
|
|
10108
10147
|
id: String$1({ format: "uuid" }),
|
|
10109
10148
|
teamId: String$1({ format: "uuid" }),
|
|
10110
10149
|
taskId: String$1({ format: "uuid" }),
|
|
10111
|
-
attemptN: Integer({ minimum: 1 }),
|
|
10150
|
+
attemptN: Union([Integer({ minimum: 1 }), Null()]),
|
|
10112
10151
|
kind: String$1({
|
|
10113
10152
|
minLength: 1,
|
|
10114
10153
|
maxLength: 100
|
|
@@ -10130,7 +10169,7 @@ _Object_({
|
|
|
10130
10169
|
minLength: 1,
|
|
10131
10170
|
maxLength: 100
|
|
10132
10171
|
}),
|
|
10133
|
-
createdByAgentId: String$1({ format: "uuid" }),
|
|
10172
|
+
createdByAgentId: Union([String$1({ format: "uuid" }), Null()]),
|
|
10134
10173
|
expiresAt: Union([String$1({ format: "date-time" }), Null()]),
|
|
10135
10174
|
createdAt: String$1({ format: "date-time" })
|
|
10136
10175
|
}, { $id: "TaskArtifact" })),
|
|
@@ -10146,6 +10185,16 @@ _Object_({
|
|
|
10146
10185
|
$id: "ListTaskArtifactsQuery",
|
|
10147
10186
|
additionalProperties: false
|
|
10148
10187
|
});
|
|
10188
|
+
var HeaderSafeContentType = String$1({
|
|
10189
|
+
minLength: 1,
|
|
10190
|
+
maxLength: 200,
|
|
10191
|
+
pattern: "^[\\x21-\\x7e][\\x20-\\x7e]*$"
|
|
10192
|
+
});
|
|
10193
|
+
var HeaderSafeContentEncoding = String$1({
|
|
10194
|
+
minLength: 1,
|
|
10195
|
+
maxLength: 100,
|
|
10196
|
+
pattern: "^[\\x21-\\x7e][\\x20-\\x7e]*$"
|
|
10197
|
+
});
|
|
10149
10198
|
_Object_({
|
|
10150
10199
|
kind: String$1({
|
|
10151
10200
|
minLength: 1,
|
|
@@ -10155,14 +10204,8 @@ _Object_({
|
|
|
10155
10204
|
minLength: 1,
|
|
10156
10205
|
maxLength: 255
|
|
10157
10206
|
}),
|
|
10158
|
-
contentType: Optional(
|
|
10159
|
-
|
|
10160
|
-
maxLength: 200
|
|
10161
|
-
})),
|
|
10162
|
-
contentEncoding: Optional(String$1({
|
|
10163
|
-
minLength: 1,
|
|
10164
|
-
maxLength: 100
|
|
10165
|
-
}))
|
|
10207
|
+
contentType: Optional(HeaderSafeContentType),
|
|
10208
|
+
contentEncoding: Optional(HeaderSafeContentEncoding)
|
|
10166
10209
|
}, {
|
|
10167
10210
|
$id: "UploadTaskArtifactQuery",
|
|
10168
10211
|
additionalProperties: false
|
|
@@ -10194,6 +10237,34 @@ _Object_({
|
|
|
10194
10237
|
$id: "TaskArtifactContentParams",
|
|
10195
10238
|
additionalProperties: false
|
|
10196
10239
|
});
|
|
10240
|
+
_Object_({
|
|
10241
|
+
contentType: Optional(HeaderSafeContentType),
|
|
10242
|
+
contentEncoding: Optional(HeaderSafeContentEncoding)
|
|
10243
|
+
}, {
|
|
10244
|
+
$id: "StageTaskArtifactQuery",
|
|
10245
|
+
additionalProperties: false
|
|
10246
|
+
});
|
|
10247
|
+
_Object_({
|
|
10248
|
+
cid: String$1({
|
|
10249
|
+
minLength: 1,
|
|
10250
|
+
maxLength: 100
|
|
10251
|
+
}),
|
|
10252
|
+
sizeBytes: Integer({ minimum: 0 }),
|
|
10253
|
+
contentType: String$1({
|
|
10254
|
+
minLength: 1,
|
|
10255
|
+
maxLength: 200
|
|
10256
|
+
})
|
|
10257
|
+
}, { $id: "StagedTaskArtifact" });
|
|
10258
|
+
_Object_({
|
|
10259
|
+
taskId: String$1({ format: "uuid" }),
|
|
10260
|
+
cid: String$1({
|
|
10261
|
+
minLength: 1,
|
|
10262
|
+
maxLength: 100
|
|
10263
|
+
})
|
|
10264
|
+
}, {
|
|
10265
|
+
$id: "TaskArtifactTaskContentParams",
|
|
10266
|
+
additionalProperties: false
|
|
10267
|
+
});
|
|
10197
10268
|
new TextEncoder();
|
|
10198
10269
|
new TextDecoder();
|
|
10199
10270
|
//#endregion
|
|
@@ -10707,6 +10778,27 @@ var JudgeEvalAttemptInput = _Object_({
|
|
|
10707
10778
|
$id: "JudgeEvalAttemptInput",
|
|
10708
10779
|
additionalProperties: false
|
|
10709
10780
|
});
|
|
10781
|
+
/** Agent-authored part of a judge attempt's output. */
|
|
10782
|
+
var JudgeEvalAttemptSubmission = _Object_({
|
|
10783
|
+
targetTaskId: String$1({ format: "uuid" }),
|
|
10784
|
+
targetAttemptN: Integer({ minimum: 1 }),
|
|
10785
|
+
variantLabel: String$1({
|
|
10786
|
+
minLength: 1,
|
|
10787
|
+
maxLength: 64,
|
|
10788
|
+
pattern: "^(?!.* - ).*$"
|
|
10789
|
+
}),
|
|
10790
|
+
scores: _Array_(JudgePackScore, { minItems: 1 }),
|
|
10791
|
+
composite: Number$1({
|
|
10792
|
+
minimum: 0,
|
|
10793
|
+
maximum: 1
|
|
10794
|
+
}),
|
|
10795
|
+
verdict: String$1({ minLength: 1 }),
|
|
10796
|
+
judgeModel: Optional(String$1({ minLength: 1 }))
|
|
10797
|
+
}, {
|
|
10798
|
+
$id: "JudgeEvalAttemptSubmission",
|
|
10799
|
+
additionalProperties: false
|
|
10800
|
+
});
|
|
10801
|
+
/** Durable output after the executor stamps the claim trace context. */
|
|
10710
10802
|
var JudgeEvalAttemptOutput = _Object_({
|
|
10711
10803
|
targetTaskId: String$1({ format: "uuid" }),
|
|
10712
10804
|
targetAttemptN: Integer({ minimum: 1 }),
|
|
@@ -10722,7 +10814,7 @@ var JudgeEvalAttemptOutput = _Object_({
|
|
|
10722
10814
|
}),
|
|
10723
10815
|
verdict: String$1({ minLength: 1 }),
|
|
10724
10816
|
judgeModel: Optional(String$1({ minLength: 1 })),
|
|
10725
|
-
traceparent: String$1({ minLength: 1 })
|
|
10817
|
+
traceparent: Optional(String$1({ minLength: 1 }))
|
|
10726
10818
|
}, {
|
|
10727
10819
|
$id: "JudgeEvalAttemptOutput",
|
|
10728
10820
|
additionalProperties: false
|
|
@@ -11014,15 +11106,33 @@ var RunEvalInput = _Object_({
|
|
|
11014
11106
|
$id: "RunEvalInput",
|
|
11015
11107
|
additionalProperties: false
|
|
11016
11108
|
});
|
|
11109
|
+
var RunEvalArtifact = _Object_({
|
|
11110
|
+
path: String$1({ minLength: 1 }),
|
|
11111
|
+
cid: String$1({ minLength: 1 })
|
|
11112
|
+
}, { additionalProperties: false });
|
|
11113
|
+
/**
|
|
11114
|
+
* Fields the eval agent authors through its submit-output tool. Runtime
|
|
11115
|
+
* telemetry deliberately does not live here: an agent cannot truthfully
|
|
11116
|
+
* measure provider token usage, wall-clock duration, or the claim trace.
|
|
11117
|
+
*/
|
|
11118
|
+
var RunEvalSubmission = _Object_({
|
|
11119
|
+
response: String$1({ minLength: 1 }),
|
|
11120
|
+
artifacts: Optional(_Array_(RunEvalArtifact)),
|
|
11121
|
+
verification: Optional(VerificationRecord)
|
|
11122
|
+
}, {
|
|
11123
|
+
$id: "RunEvalSubmission",
|
|
11124
|
+
additionalProperties: false
|
|
11125
|
+
});
|
|
11126
|
+
/**
|
|
11127
|
+
* Durable eval output. The daemon materializes this from RunEvalSubmission
|
|
11128
|
+
* and observed execution metadata before the task service accepts it.
|
|
11129
|
+
*/
|
|
11017
11130
|
var RunEvalOutput = _Object_({
|
|
11018
11131
|
response: String$1({ minLength: 1 }),
|
|
11019
|
-
artifacts: Optional(_Array_(
|
|
11020
|
-
path: String$1({ minLength: 1 }),
|
|
11021
|
-
cid: String$1({ minLength: 1 })
|
|
11022
|
-
}, { additionalProperties: false }))),
|
|
11132
|
+
artifacts: Optional(_Array_(RunEvalArtifact)),
|
|
11023
11133
|
totalTokens: Integer({ minimum: 0 }),
|
|
11024
11134
|
durationMs: Integer({ minimum: 0 }),
|
|
11025
|
-
traceparent: String$1({ minLength: 1 }),
|
|
11135
|
+
traceparent: Optional(String$1({ minLength: 1 })),
|
|
11026
11136
|
verification: Optional(VerificationRecord)
|
|
11027
11137
|
}, {
|
|
11028
11138
|
$id: "RunEvalOutput",
|
|
@@ -11167,6 +11277,7 @@ var BUILT_IN_TASK_TYPES = {
|
|
|
11167
11277
|
name: RUN_EVAL_TYPE,
|
|
11168
11278
|
inputSchema: RunEvalInput,
|
|
11169
11279
|
outputSchema: RunEvalOutput,
|
|
11280
|
+
submissionSchema: RunEvalSubmission,
|
|
11170
11281
|
outputKind: "artifact",
|
|
11171
11282
|
resumable: true,
|
|
11172
11283
|
workspaceScope: "session",
|
|
@@ -11179,6 +11290,7 @@ var BUILT_IN_TASK_TYPES = {
|
|
|
11179
11290
|
name: JUDGE_EVAL_ATTEMPT_TYPE,
|
|
11180
11291
|
inputSchema: JudgeEvalAttemptInput,
|
|
11181
11292
|
outputSchema: JudgeEvalAttemptOutput,
|
|
11293
|
+
submissionSchema: JudgeEvalAttemptSubmission,
|
|
11182
11294
|
outputKind: "judgment",
|
|
11183
11295
|
workspaceScope: "attempt",
|
|
11184
11296
|
sessionScope: "none",
|
|
@@ -13755,22 +13867,33 @@ function validateTaskInput(taskType, input) {
|
|
|
13755
13867
|
}
|
|
13756
13868
|
return [];
|
|
13757
13869
|
}
|
|
13758
|
-
function
|
|
13870
|
+
function checkVerificationInputCid(value, runtime) {
|
|
13871
|
+
const verification = value !== null && typeof value === "object" ? value.verification : void 0;
|
|
13872
|
+
if (runtime?.inputCid && verification !== void 0 && verification.inputCid !== runtime.inputCid) return [{
|
|
13873
|
+
field: "output/verification/inputCid",
|
|
13874
|
+
message: "must match the task input CID"
|
|
13875
|
+
}];
|
|
13876
|
+
return [];
|
|
13877
|
+
}
|
|
13878
|
+
function validateTaskResult(taskType, value, input, runtime, submission = false) {
|
|
13759
13879
|
const entry = getTaskTypeEntry(taskType);
|
|
13760
13880
|
if (!entry) return [{
|
|
13761
13881
|
field: "taskType",
|
|
13762
13882
|
message: `Unknown task type: ${taskType}`
|
|
13763
13883
|
}];
|
|
13764
|
-
const errors = schemaErrors("output", entry.outputSchema,
|
|
13884
|
+
const errors = schemaErrors("output", submission ? entry.submissionSchema ?? entry.outputSchema : entry.outputSchema, value);
|
|
13765
13885
|
if (errors.length > 0) return errors;
|
|
13766
13886
|
if (entry.validateOutput) {
|
|
13767
|
-
const validationError = entry.validateOutput(
|
|
13887
|
+
const validationError = entry.validateOutput(value, input);
|
|
13768
13888
|
if (validationError) return [{
|
|
13769
13889
|
field: "output",
|
|
13770
13890
|
message: validationError
|
|
13771
13891
|
}];
|
|
13772
13892
|
}
|
|
13773
|
-
return
|
|
13893
|
+
return checkVerificationInputCid(value, runtime);
|
|
13894
|
+
}
|
|
13895
|
+
function validateTaskOutput(taskType, output, input, runtime) {
|
|
13896
|
+
return validateTaskResult(taskType, output, input, runtime);
|
|
13774
13897
|
}
|
|
13775
13898
|
/**
|
|
13776
13899
|
* Resolve the TypeBox output schema registered for `taskType`. Returns
|
|
@@ -13793,6 +13916,42 @@ function validateTaskCreateRequest(args) {
|
|
|
13793
13916
|
field: "references",
|
|
13794
13917
|
message: `At least one reference is required for task type: ${args.taskType}`
|
|
13795
13918
|
});
|
|
13919
|
+
errors.push(...validateTaskReferences(args.references));
|
|
13920
|
+
return errors;
|
|
13921
|
+
}
|
|
13922
|
+
/**
|
|
13923
|
+
* Cross-field rules for the reference shapes the schema alone cannot
|
|
13924
|
+
* express. Every reference must be exactly one of:
|
|
13925
|
+
*
|
|
13926
|
+
* - **task output ref**: taskId set, outputCid required; an optional
|
|
13927
|
+
* artifact must name its producing attempt (attemptN).
|
|
13928
|
+
* - **input artifact ref**: taskId null, artifact without attemptN;
|
|
13929
|
+
* outputCid omitted (or equal to artifact.cid when sent) -- the bytes
|
|
13930
|
+
* were staged before any task existed, so there is no output to name.
|
|
13931
|
+
* - **external ref**: taskId null, external present, no artifact.
|
|
13932
|
+
*
|
|
13933
|
+
* Anything else used to be silently persisted into taskRefs and never
|
|
13934
|
+
* materialized; now it fails fast with a field error.
|
|
13935
|
+
*/
|
|
13936
|
+
function validateTaskReferences(references) {
|
|
13937
|
+
const errors = [];
|
|
13938
|
+
(references ?? []).forEach((ref, index) => {
|
|
13939
|
+
const invalid = (message) => errors.push({
|
|
13940
|
+
field: `references[${index}]`,
|
|
13941
|
+
message
|
|
13942
|
+
});
|
|
13943
|
+
if (ref.taskId !== null) {
|
|
13944
|
+
if (ref.outputCid === void 0) invalid("outputCid is required when referencing a task output");
|
|
13945
|
+
if (ref.artifact && ref.artifact.attemptN === void 0) invalid("artifact references to another task must include attemptN; input artifacts are referenced with taskId null");
|
|
13946
|
+
return;
|
|
13947
|
+
}
|
|
13948
|
+
if (ref.artifact) {
|
|
13949
|
+
if (ref.artifact.attemptN !== void 0) invalid("input artifact references (taskId null) must not include attemptN; reference the producing task by id instead");
|
|
13950
|
+
if (ref.outputCid !== void 0 && ref.outputCid !== ref.artifact.cid) invalid("outputCid on an input artifact reference must be omitted or equal artifact.cid");
|
|
13951
|
+
return;
|
|
13952
|
+
}
|
|
13953
|
+
if (!ref.external) invalid("references with taskId null must carry either an artifact (input artifact) or an external descriptor");
|
|
13954
|
+
});
|
|
13796
13955
|
return errors;
|
|
13797
13956
|
}
|
|
13798
13957
|
//#endregion
|
|
@@ -13909,7 +14068,7 @@ Unsafe(Cyclic({ ClaimCondition: Unsafe(Union([
|
|
|
13909
14068
|
*/
|
|
13910
14069
|
var TaskRef = _Object_({
|
|
13911
14070
|
taskId: Union([Uuid, Null()]),
|
|
13912
|
-
outputCid: Cid,
|
|
14071
|
+
outputCid: Optional(Cid),
|
|
13913
14072
|
role: Union([
|
|
13914
14073
|
Literal("judged_work"),
|
|
13915
14074
|
Literal("reviewed_diff"),
|
|
@@ -13930,7 +14089,7 @@ var TaskRef = _Object_({
|
|
|
13930
14089
|
})),
|
|
13931
14090
|
artifact: Optional(_Object_({
|
|
13932
14091
|
cid: Cid,
|
|
13933
|
-
attemptN: Integer({ minimum: 1 }),
|
|
14092
|
+
attemptN: Optional(Integer({ minimum: 1 })),
|
|
13934
14093
|
kind: Optional(String$1({
|
|
13935
14094
|
minLength: 1,
|
|
13936
14095
|
maxLength: 100
|
|
@@ -14400,30 +14559,53 @@ var TaskBuilder = class {
|
|
|
14400
14559
|
return this;
|
|
14401
14560
|
}
|
|
14402
14561
|
/**
|
|
14403
|
-
* Add a
|
|
14404
|
-
*
|
|
14562
|
+
* Add either a staged input artifact or a persistent attempt artifact.
|
|
14563
|
+
* Staged metadata returned by `tasks.artifacts.stage()` carries
|
|
14564
|
+
* `artifactSource: 'staged'` and produces a reference with `taskId: null`, no
|
|
14565
|
+
* `outputCid`, and no `attemptN`. Persistent artifacts require the producing
|
|
14566
|
+
* task, accepted output CID, artifact CID, and positive attempt number so
|
|
14567
|
+
* their provenance remains explicit.
|
|
14405
14568
|
*
|
|
14406
|
-
* @param source -
|
|
14569
|
+
* @param source - Staged SDK metadata, a result reader, raw artifact reference, or `TaskRef`.
|
|
14407
14570
|
* @param role - The role the referenced artifact plays.
|
|
14408
14571
|
* @returns This builder, for chaining.
|
|
14409
|
-
* @throws {TaskBuildError} when
|
|
14572
|
+
* @throws {TaskBuildError} when the source is ambiguous or required provenance is missing.
|
|
14410
14573
|
*/
|
|
14411
14574
|
artifactReference(source, role) {
|
|
14412
14575
|
let ref;
|
|
14413
14576
|
if ("artifactRef" in source && typeof source.artifactRef === "function") ref = source.artifactRef(role);
|
|
14414
|
-
else if ("
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
...source,
|
|
14421
|
-
|
|
14422
|
-
}
|
|
14423
|
-
}
|
|
14577
|
+
else if ("artifactSource" in source && source.artifactSource === "staged" && source.cid) ref = {
|
|
14578
|
+
taskId: null,
|
|
14579
|
+
role,
|
|
14580
|
+
artifact: {
|
|
14581
|
+
cid: source.cid,
|
|
14582
|
+
...source.kind ? { kind: source.kind } : {},
|
|
14583
|
+
...source.title ? { title: source.title } : {},
|
|
14584
|
+
...source.contentType ? { contentType: source.contentType } : {}
|
|
14585
|
+
}
|
|
14586
|
+
};
|
|
14587
|
+
else if ("cid" in source) throw new TaskBuildError([{
|
|
14588
|
+
field: "references/artifactSource",
|
|
14589
|
+
message: "top-level artifact CID is ambiguous; use metadata returned by tasks.artifacts.stage()"
|
|
14590
|
+
}]);
|
|
14591
|
+
else if ("artifact" in source && source.artifact?.cid) if (source.taskId === null && source.artifact.attemptN === void 0) ref = {
|
|
14592
|
+
taskId: null,
|
|
14593
|
+
role,
|
|
14594
|
+
artifact: { ...source.artifact }
|
|
14595
|
+
};
|
|
14596
|
+
else if (typeof source.artifact.attemptN !== "number" || !Number.isInteger(source.artifact.attemptN) || source.artifact.attemptN < 1) throw new TaskBuildError([{
|
|
14597
|
+
field: "references/artifact/attemptN",
|
|
14598
|
+
message: "artifact reference is missing required attemptN"
|
|
14599
|
+
}]);
|
|
14600
|
+
else ref = {
|
|
14601
|
+
...source,
|
|
14602
|
+
role
|
|
14603
|
+
};
|
|
14604
|
+
else {
|
|
14424
14605
|
const s = source;
|
|
14425
14606
|
const errors = [];
|
|
14426
|
-
|
|
14607
|
+
const inputArtifact = s.taskId === null && s.attemptN === void 0;
|
|
14608
|
+
if (!inputArtifact && !s.outputCid) errors.push({
|
|
14427
14609
|
field: "references/outputCid",
|
|
14428
14610
|
message: "reference is missing required outputCid"
|
|
14429
14611
|
});
|
|
@@ -14431,19 +14613,18 @@ var TaskBuilder = class {
|
|
|
14431
14613
|
field: "references/artifact/cid",
|
|
14432
14614
|
message: "artifact reference is missing required cid"
|
|
14433
14615
|
});
|
|
14434
|
-
if (typeof s.attemptN !== "number" || !Number.isInteger(s.attemptN) || s.attemptN < 1) errors.push({
|
|
14616
|
+
if (!inputArtifact && (typeof s.attemptN !== "number" || !Number.isInteger(s.attemptN) || s.attemptN < 1)) errors.push({
|
|
14435
14617
|
field: "references/artifact/attemptN",
|
|
14436
14618
|
message: "artifact reference is missing required attemptN"
|
|
14437
14619
|
});
|
|
14438
14620
|
if (errors.length > 0) throw new TaskBuildError(errors);
|
|
14439
|
-
const attemptN = s.attemptN;
|
|
14440
14621
|
ref = {
|
|
14441
14622
|
taskId: s.taskId ?? null,
|
|
14442
|
-
outputCid: s.outputCid,
|
|
14623
|
+
...!inputArtifact && s.outputCid ? { outputCid: s.outputCid } : {},
|
|
14443
14624
|
role,
|
|
14444
14625
|
artifact: {
|
|
14445
14626
|
cid: s.artifactCid,
|
|
14446
|
-
attemptN,
|
|
14627
|
+
...s.attemptN !== void 0 ? { attemptN: s.attemptN } : {},
|
|
14447
14628
|
...s.kind ? { kind: s.kind } : {},
|
|
14448
14629
|
...s.title ? { title: s.title } : {},
|
|
14449
14630
|
...s.contentType ? { contentType: s.contentType } : {}
|
|
@@ -14913,6 +15094,22 @@ function createTasksNamespace(context) {
|
|
|
14913
15094
|
}));
|
|
14914
15095
|
},
|
|
14915
15096
|
artifacts: {
|
|
15097
|
+
async stage(body, query, options) {
|
|
15098
|
+
return {
|
|
15099
|
+
...unwrapResult(await stageTaskArtifact({
|
|
15100
|
+
auth,
|
|
15101
|
+
body,
|
|
15102
|
+
client,
|
|
15103
|
+
duplex: "half",
|
|
15104
|
+
headers: {
|
|
15105
|
+
...requiredTeamHeaders(options),
|
|
15106
|
+
"content-type": "application/octet-stream"
|
|
15107
|
+
},
|
|
15108
|
+
query
|
|
15109
|
+
})),
|
|
15110
|
+
artifactSource: "staged"
|
|
15111
|
+
};
|
|
15112
|
+
},
|
|
14916
15113
|
async upload(path, body, query, options) {
|
|
14917
15114
|
return unwrapResult(await uploadTaskArtifact({
|
|
14918
15115
|
auth,
|
|
@@ -14946,17 +15143,24 @@ function createTasksNamespace(context) {
|
|
|
14946
15143
|
}));
|
|
14947
15144
|
},
|
|
14948
15145
|
async download(path, options) {
|
|
14949
|
-
const
|
|
15146
|
+
const request = {
|
|
14950
15147
|
auth,
|
|
14951
15148
|
headers: requiredTeamHeaders(options),
|
|
14952
15149
|
method: "GET",
|
|
14953
15150
|
parseAs: "stream",
|
|
14954
|
-
path,
|
|
14955
15151
|
security: [{
|
|
14956
15152
|
scheme: "bearer",
|
|
14957
15153
|
type: "http"
|
|
14958
|
-
}]
|
|
15154
|
+
}]
|
|
15155
|
+
};
|
|
15156
|
+
const result = "attemptN" in path ? await client.request({
|
|
15157
|
+
...request,
|
|
15158
|
+
path,
|
|
14959
15159
|
url: "/tasks/{taskId}/attempts/{attemptN}/artifacts/{cid}/content"
|
|
15160
|
+
}) : await client.request({
|
|
15161
|
+
...request,
|
|
15162
|
+
path,
|
|
15163
|
+
url: "/tasks/{taskId}/artifacts/{cid}/content"
|
|
14960
15164
|
});
|
|
14961
15165
|
const normalizedStream = normalizeDownloadStream(unwrapResult(result));
|
|
14962
15166
|
if (normalizedStream) return {
|
|
@@ -72,12 +72,17 @@
|
|
|
72
72
|
|
|
73
73
|
<script type="text/html" data-help-name="moltnet-task-artifact-download">
|
|
74
74
|
<p>
|
|
75
|
-
Downloads a task artifact by task id
|
|
76
|
-
|
|
75
|
+
Downloads a task artifact by task id and CID. CID is taken from
|
|
76
|
+
<code>msg.cid</code>, <code>msg.payload.cid</code>,
|
|
77
77
|
<code>msg.payload.artifact.cid</code>, or the configured CID. Team ID is
|
|
78
78
|
taken from this node or the configured agent unless Team override is
|
|
79
79
|
enabled.
|
|
80
80
|
</p>
|
|
81
|
+
<p>
|
|
82
|
+
Attempt is optional. Omit it to resolve the CID across the task, including
|
|
83
|
+
input artifacts bound when the task was created. Set it to require an exact
|
|
84
|
+
attempt artifact.
|
|
85
|
+
</p>
|
|
81
86
|
<p>
|
|
82
87
|
Emits the artifact bytes as a Buffer on <code>msg.payload</code>. Download
|
|
83
88
|
metadata is placed on <code>msg.artifact</code>. The local byte limit
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as withAgent } from "./agent-call.js";
|
|
2
2
|
import { a as nonEmpty, t as bool } from "./query-utils.js";
|
|
3
|
-
import {
|
|
3
|
+
import { c as resolveMaxBytes, i as requireArtifactContext, n as payloadRecord, o as resolveAttemptSelection, r as recordField, t as collectArtifactBody } from "./task-artifact-utils.js";
|
|
4
4
|
//#region src/nodes/task-artifact-download.ts
|
|
5
5
|
var init = (RED) => {
|
|
6
6
|
function TaskArtifactDownloadNode(def) {
|
|
@@ -10,7 +10,10 @@ var init = (RED) => {
|
|
|
10
10
|
const run = async () => {
|
|
11
11
|
try {
|
|
12
12
|
if (!agentNode || typeof agentNode.getAgent !== "function") throw new Error("task-artifact-download: no moltnet-agent configured");
|
|
13
|
-
const { taskId, teamId
|
|
13
|
+
const { taskId, teamId } = requireArtifactContext("task-artifact-download", msg, def.taskId, def.teamId, agentNode, bool(def.allowMsgTeamOverride) ?? false);
|
|
14
|
+
const attempt = resolveAttemptSelection(msg, def.attemptN);
|
|
15
|
+
if (attempt.supplied && attempt.attemptN === void 0) throw new Error("task-artifact-download: attemptN must be a positive integer");
|
|
16
|
+
const attemptN = attempt.attemptN;
|
|
14
17
|
const cid = resolveCid(msg, def.cid);
|
|
15
18
|
if (!cid) throw new Error("task-artifact-download: cid is required");
|
|
16
19
|
this.status({
|
|
@@ -18,7 +21,10 @@ var init = (RED) => {
|
|
|
18
21
|
shape: "dot",
|
|
19
22
|
text: "downloading…"
|
|
20
23
|
});
|
|
21
|
-
const result = await withAgent(agentNode, (agent) => agent.tasks.artifacts.download({
|
|
24
|
+
const result = await withAgent(agentNode, (agent) => agent.tasks.artifacts.download(attemptN === void 0 ? {
|
|
25
|
+
taskId,
|
|
26
|
+
cid
|
|
27
|
+
} : {
|
|
22
28
|
taskId,
|
|
23
29
|
attemptN,
|
|
24
30
|
cid
|
|
@@ -33,7 +39,7 @@ var init = (RED) => {
|
|
|
33
39
|
out.artifact = {
|
|
34
40
|
taskId,
|
|
35
41
|
teamId,
|
|
36
|
-
attemptN,
|
|
42
|
+
...attemptN === void 0 ? {} : { attemptN },
|
|
37
43
|
cid,
|
|
38
44
|
...metadataFromResult(result)
|
|
39
45
|
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
var moltnetTaskArtifactStageMaxBytes = 25 * 1024 * 1024;
|
|
3
|
+
RED.nodes.registerType('moltnet-task-artifact-stage', {
|
|
4
|
+
category: 'moltnet',
|
|
5
|
+
color: '#00d4c8',
|
|
6
|
+
paletteLabel: 'task artifact: stage',
|
|
7
|
+
defaults: {
|
|
8
|
+
name: { value: '' },
|
|
9
|
+
agent: { value: '', type: 'moltnet-agent', required: true },
|
|
10
|
+
teamId: { value: '' },
|
|
11
|
+
allowMsgTeamOverride: { value: false },
|
|
12
|
+
maxBytes: {
|
|
13
|
+
value: moltnetTaskArtifactStageMaxBytes,
|
|
14
|
+
validate: RED.validators.number(),
|
|
15
|
+
},
|
|
16
|
+
contentType: { value: '' },
|
|
17
|
+
contentEncoding: { value: '' },
|
|
18
|
+
},
|
|
19
|
+
inputs: 1,
|
|
20
|
+
outputs: 1,
|
|
21
|
+
icon: 'font-awesome/fa-upload',
|
|
22
|
+
label: function () {
|
|
23
|
+
return this.name || 'task artifact: stage';
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<script type="text/html" data-template-name="moltnet-task-artifact-stage">
|
|
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-teamId"><i class="fa fa-users"></i> Team ID</label>
|
|
39
|
+
<input type="text" id="node-input-teamId" placeholder="agent teamId" />
|
|
40
|
+
</div>
|
|
41
|
+
<div class="form-row">
|
|
42
|
+
<label for="node-input-allowMsgTeamOverride"
|
|
43
|
+
><i class="fa fa-random"></i> Team override</label
|
|
44
|
+
>
|
|
45
|
+
<input
|
|
46
|
+
type="checkbox"
|
|
47
|
+
id="node-input-allowMsgTeamOverride"
|
|
48
|
+
style="display:inline-block;width:auto;vertical-align:top"
|
|
49
|
+
/>
|
|
50
|
+
<span>Allow <code>msg.teamId</code></span>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="form-row">
|
|
53
|
+
<label for="node-input-maxBytes"
|
|
54
|
+
><i class="fa fa-database"></i> Max bytes</label
|
|
55
|
+
>
|
|
56
|
+
<input type="number" id="node-input-maxBytes" />
|
|
57
|
+
</div>
|
|
58
|
+
<div class="form-row">
|
|
59
|
+
<label for="node-input-contentType"
|
|
60
|
+
><i class="fa fa-file-o"></i> Type</label
|
|
61
|
+
>
|
|
62
|
+
<input
|
|
63
|
+
type="text"
|
|
64
|
+
id="node-input-contentType"
|
|
65
|
+
placeholder="application/pdf"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="form-row">
|
|
69
|
+
<label for="node-input-contentEncoding"
|
|
70
|
+
><i class="fa fa-compress"></i> Encoding</label
|
|
71
|
+
>
|
|
72
|
+
<input type="text" id="node-input-contentEncoding" placeholder="gzip" />
|
|
73
|
+
</div>
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<script type="text/html" data-help-name="moltnet-task-artifact-stage">
|
|
77
|
+
<p>
|
|
78
|
+
Stages content-addressed bytes for later binding to a task through an input
|
|
79
|
+
artifact reference. Staging creates no visible artifact row; the returned
|
|
80
|
+
CID becomes readable only after a task binds it.
|
|
81
|
+
</p>
|
|
82
|
+
<p>
|
|
83
|
+
Reads bytes from the same payload shapes as task artifact upload and emits
|
|
84
|
+
<code>{ artifactSource: "staged", cid, sizeBytes, contentType }</code> on
|
|
85
|
+
<code>msg.payload</code> and <code>msg.artifact</code>. Wire
|
|
86
|
+
<code>msg.artifact</code> into a task builder's References from field.
|
|
87
|
+
</p>
|
|
88
|
+
<p>
|
|
89
|
+
Buffer and Uint8Array payloads avoid encoding overhead. The
|
|
90
|
+
<code>{ contentBase64 }</code> form is available for JSON-only sources. The
|
|
91
|
+
local byte limit defaults to 25 MiB.
|
|
92
|
+
</p>
|
|
93
|
+
</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 { c as resolveMaxBytes, l as resolveTeamId, s as resolveField, u as resolveUploadBody } from "./task-artifact-utils.js";
|
|
4
|
+
//#region src/nodes/task-artifact-stage.ts
|
|
5
|
+
var init = (RED) => {
|
|
6
|
+
function TaskArtifactStageNode(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("task-artifact-stage: no moltnet-agent configured");
|
|
13
|
+
const teamId = resolveTeamId(msg, def.teamId, agentNode, bool(def.allowMsgTeamOverride) ?? false);
|
|
14
|
+
if (!teamId) throw new Error("task-artifact-stage: teamId is required");
|
|
15
|
+
const body = resolveUploadBody(msg, resolveMaxBytes(def.maxBytes), "task-artifact-stage");
|
|
16
|
+
const query = {
|
|
17
|
+
contentType: resolveField(msg, "contentType", def.contentType),
|
|
18
|
+
contentEncoding: resolveField(msg, "contentEncoding", def.contentEncoding)
|
|
19
|
+
};
|
|
20
|
+
this.status({
|
|
21
|
+
fill: "blue",
|
|
22
|
+
shape: "dot",
|
|
23
|
+
text: "staging..."
|
|
24
|
+
});
|
|
25
|
+
const artifact = await withAgent(agentNode, (agent) => agent.tasks.artifacts.stage(body, query, { teamId }));
|
|
26
|
+
const out = RED.util.cloneMessage({
|
|
27
|
+
...msg,
|
|
28
|
+
payload: void 0
|
|
29
|
+
});
|
|
30
|
+
out.payload = artifact;
|
|
31
|
+
out.artifact = artifact;
|
|
32
|
+
this.status({
|
|
33
|
+
fill: "green",
|
|
34
|
+
shape: "dot",
|
|
35
|
+
text: artifact.cid
|
|
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-task-artifact-stage", TaskArtifactStageNode);
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { init as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as withAgent } from "./agent-call.js";
|
|
2
2
|
import { t as bool } from "./query-utils.js";
|
|
3
|
-
import { a as requireAttemptContext, c as
|
|
3
|
+
import { a as requireAttemptContext, c as resolveMaxBytes, s as resolveField, u as resolveUploadBody } from "./task-artifact-utils.js";
|
|
4
4
|
//#region src/nodes/task-artifact-upload.ts
|
|
5
5
|
var init = (RED) => {
|
|
6
6
|
function TaskArtifactUploadNode(def) {
|
|
@@ -17,8 +17,22 @@ function resolveTeamId(msg, configured, agentNode, allowMsgTeamOverride) {
|
|
|
17
17
|
return nonEmpty(payloadRecord(msg).teamId) ?? nonEmpty(configured) ?? agentNode.teamId;
|
|
18
18
|
}
|
|
19
19
|
function resolveAttemptN(msg, configured) {
|
|
20
|
+
return resolveAttemptSelection(msg, configured).attemptN;
|
|
21
|
+
}
|
|
22
|
+
function resolveAttemptSelection(msg, configured) {
|
|
20
23
|
const payload = payloadRecord(msg);
|
|
21
|
-
|
|
24
|
+
const candidate = [
|
|
25
|
+
msg.attemptN,
|
|
26
|
+
payload.attemptN,
|
|
27
|
+
recordField(payload.attempt, "attemptN"),
|
|
28
|
+
recordField(payload.artifact, "attemptN"),
|
|
29
|
+
configured
|
|
30
|
+
].find(isSupplied);
|
|
31
|
+
if (candidate === void 0) return { supplied: false };
|
|
32
|
+
return {
|
|
33
|
+
supplied: true,
|
|
34
|
+
attemptN: positiveInt(candidate)
|
|
35
|
+
};
|
|
22
36
|
}
|
|
23
37
|
function requireArtifactContext(nodeName, msg, configuredTaskId, configuredTeamId, agentNode, allowMsgTeamOverride) {
|
|
24
38
|
const taskId = resolveTaskId(msg, configuredTaskId);
|
|
@@ -59,9 +73,8 @@ function resolveUploadBody(msg, maxBytes, nodeName = "task-artifact-upload") {
|
|
|
59
73
|
}
|
|
60
74
|
const record = payloadRecord(msg);
|
|
61
75
|
if (typeof record.contentBase64 === "string") {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return Buffer.from(normalized, "base64");
|
|
76
|
+
if (decodedBase64Length(record.contentBase64) > maxBytes) throw tooLarge("upload", maxBytes, nodeName);
|
|
77
|
+
return Buffer.from(record.contentBase64, "base64");
|
|
65
78
|
}
|
|
66
79
|
const content = record.content ?? record.body;
|
|
67
80
|
if (Buffer.isBuffer(content)) return enforceMaxBytes(content, maxBytes, nodeName);
|
|
@@ -139,11 +152,26 @@ function pushChunk(chunks, chunk, bytes, maxBytes, nodeName) {
|
|
|
139
152
|
}
|
|
140
153
|
function decodedBase64Length(value) {
|
|
141
154
|
if (!value) return 0;
|
|
142
|
-
|
|
143
|
-
|
|
155
|
+
let characters = 0;
|
|
156
|
+
let previous = "";
|
|
157
|
+
let last = "";
|
|
158
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
159
|
+
if (isBase64Whitespace(value.charCodeAt(index))) continue;
|
|
160
|
+
characters += 1;
|
|
161
|
+
previous = last;
|
|
162
|
+
last = value[index];
|
|
163
|
+
}
|
|
164
|
+
const padding = last === "=" ? previous === "=" ? 2 : 1 : 0;
|
|
165
|
+
return Math.max(0, Math.floor(characters * 3 / 4) - padding);
|
|
166
|
+
}
|
|
167
|
+
function isSupplied(value) {
|
|
168
|
+
return value !== void 0 && value !== null && value !== "";
|
|
169
|
+
}
|
|
170
|
+
function isBase64Whitespace(code) {
|
|
171
|
+
return code === 32 || code >= 9 && code <= 13;
|
|
144
172
|
}
|
|
145
173
|
function tooLarge(operation, maxBytes, nodeName) {
|
|
146
174
|
return /* @__PURE__ */ new Error(`${nodeName}: ${operation} body exceeds ${maxBytes} bytes`);
|
|
147
175
|
}
|
|
148
176
|
//#endregion
|
|
149
|
-
export { requireAttemptContext as a,
|
|
177
|
+
export { requireAttemptContext as a, resolveMaxBytes as c, requireArtifactContext as i, resolveTeamId as l, payloadRecord as n, resolveAttemptSelection as o, recordField as r, resolveField as s, collectArtifactBody as t, resolveUploadBody as u };
|
|
@@ -75,7 +75,8 @@ var init = (RED) => {
|
|
|
75
75
|
}
|
|
76
76
|
if (def.referencesFrom) {
|
|
77
77
|
const ref = RED.util.getMessageProperty(msg, def.referencesFrom);
|
|
78
|
-
if (ref
|
|
78
|
+
if (ref?.artifact || ref?.artifactSource === "staged" || ref?.cid) builder.artifactReference(ref, def.referencesRole ?? "context");
|
|
79
|
+
else if (ref?.outputCid) builder.references(ref, def.referencesRole ?? "context");
|
|
79
80
|
}
|
|
80
81
|
if (def.submitOutputGate) builder.requireSubmitOutput();
|
|
81
82
|
if (def.schemaCid) builder.requireSchema(def.schemaCid);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/node-red-contrib-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Node-RED nodes for the MoltNet API",
|
|
6
6
|
"keywords": [
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"moltnet-task-wait": "dist/nodes/task-wait.js",
|
|
31
31
|
"moltnet-task-artifacts-list": "dist/nodes/task-artifacts-list.js",
|
|
32
32
|
"moltnet-task-artifact-upload": "dist/nodes/task-artifact-upload.js",
|
|
33
|
+
"moltnet-task-artifact-stage": "dist/nodes/task-artifact-stage.js",
|
|
33
34
|
"moltnet-task-artifact-download": "dist/nodes/task-artifact-download.js",
|
|
34
35
|
"moltnet-runtime-session-get": "dist/nodes/runtime-session-get.js",
|
|
35
36
|
"moltnet-runtime-session-upload": "dist/nodes/runtime-session-upload.js",
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
},
|
|
46
47
|
"main": "dist/nodes/agent.js",
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"@themoltnet/sdk": "0.
|
|
49
|
+
"@themoltnet/sdk": "0.121.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/node": "^22.19.0",
|