@themoltnet/node-red-contrib-core 0.4.0 → 0.7.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.
- package/README.md +122 -11
- package/dist/nodes/agent-call.js +19 -0
- package/dist/nodes/agent.js +3 -0
- package/dist/nodes/entries-search.js +2 -1
- package/dist/nodes/runtime-profile.js +2 -1
- package/dist/nodes/src.js +18 -0
- package/dist/nodes/task-artifact-download.html +86 -0
- package/dist/nodes/task-artifact-download.js +76 -0
- package/dist/nodes/task-artifact-upload.html +118 -0
- package/dist/nodes/task-artifact-upload.js +62 -0
- package/dist/nodes/task-artifact-utils.js +149 -0
- package/dist/nodes/task-artifacts-list.html +78 -0
- package/dist/nodes/task-artifacts-list.js +61 -0
- package/dist/nodes/task-get.js +2 -2
- package/dist/nodes/task-wait.js +65 -11
- package/dist/nodes/tasks-create.js +46 -5
- package/dist/nodes/tasks-list.js +2 -2
- package/dist/nodes/workflow-status.js +2 -2
- package/examples/deep-review-freeform.flow.json +780 -0
- package/package.json +29 -3
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { a as nonEmpty, c as positiveInt } from "./query-utils.js";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
function payloadRecord(msg) {
|
|
5
|
+
if (!msg.payload || typeof msg.payload !== "object") return {};
|
|
6
|
+
if (Buffer.isBuffer(msg.payload)) return {};
|
|
7
|
+
return msg.payload;
|
|
8
|
+
}
|
|
9
|
+
function resolveTaskId(msg, configured) {
|
|
10
|
+
if (typeof msg.taskId === "string" && msg.taskId) return msg.taskId;
|
|
11
|
+
const payload = payloadRecord(msg);
|
|
12
|
+
return nonEmpty(payload.taskId) ?? nonEmpty(payload.id) ?? nonEmpty(recordField(payload.task, "id")) ?? nonEmpty(configured);
|
|
13
|
+
}
|
|
14
|
+
function resolveTeamId(msg, configured, agentNode, allowMsgTeamOverride) {
|
|
15
|
+
if (!allowMsgTeamOverride) return nonEmpty(configured) ?? agentNode.teamId;
|
|
16
|
+
if (typeof msg.teamId === "string" && msg.teamId) return msg.teamId;
|
|
17
|
+
return nonEmpty(payloadRecord(msg).teamId) ?? nonEmpty(configured) ?? agentNode.teamId;
|
|
18
|
+
}
|
|
19
|
+
function resolveAttemptN(msg, configured) {
|
|
20
|
+
const payload = payloadRecord(msg);
|
|
21
|
+
return positiveInt(msg.attemptN) ?? positiveInt(payload.attemptN) ?? positiveInt(recordField(payload.attempt, "attemptN")) ?? positiveInt(recordField(payload.artifact, "attemptN")) ?? positiveInt(configured);
|
|
22
|
+
}
|
|
23
|
+
function requireArtifactContext(nodeName, msg, configuredTaskId, configuredTeamId, agentNode, allowMsgTeamOverride) {
|
|
24
|
+
const taskId = resolveTaskId(msg, configuredTaskId);
|
|
25
|
+
if (!taskId) throw new Error(`${nodeName}: taskId is required`);
|
|
26
|
+
const teamId = resolveTeamId(msg, configuredTeamId, agentNode, allowMsgTeamOverride);
|
|
27
|
+
if (!teamId) throw new Error(`${nodeName}: teamId is required`);
|
|
28
|
+
return {
|
|
29
|
+
taskId,
|
|
30
|
+
teamId
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function requireAttemptContext(nodeName, msg, configuredTaskId, configuredTeamId, configuredAttemptN, agentNode, allowMsgTeamOverride) {
|
|
34
|
+
const context = requireArtifactContext(nodeName, msg, configuredTaskId, configuredTeamId, agentNode, allowMsgTeamOverride);
|
|
35
|
+
const attemptN = resolveAttemptN(msg, configuredAttemptN);
|
|
36
|
+
if (!attemptN) throw new Error(`${nodeName}: attemptN is required`);
|
|
37
|
+
return {
|
|
38
|
+
...context,
|
|
39
|
+
attemptN
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function resolveField(msg, name, configured) {
|
|
43
|
+
return nonEmpty(payloadRecord(msg)[name]) ?? nonEmpty(configured);
|
|
44
|
+
}
|
|
45
|
+
function resolveMaxBytes(configured) {
|
|
46
|
+
return positiveInt(configured) ?? 26214400;
|
|
47
|
+
}
|
|
48
|
+
function resolveUploadBody(msg, maxBytes) {
|
|
49
|
+
const payload = msg.payload;
|
|
50
|
+
if (Buffer.isBuffer(payload)) return enforceMaxBytes(payload, maxBytes);
|
|
51
|
+
if (payload instanceof Uint8Array) return enforceMaxBytes(payload, maxBytes);
|
|
52
|
+
if (payload instanceof ArrayBuffer) {
|
|
53
|
+
if (payload.byteLength > maxBytes) throw tooLarge("upload", maxBytes);
|
|
54
|
+
return new Uint8Array(payload);
|
|
55
|
+
}
|
|
56
|
+
if (typeof payload === "string") {
|
|
57
|
+
if (Buffer.byteLength(payload) > maxBytes) throw tooLarge("upload", maxBytes);
|
|
58
|
+
return new TextEncoder().encode(payload);
|
|
59
|
+
}
|
|
60
|
+
const record = payloadRecord(msg);
|
|
61
|
+
if (typeof record.contentBase64 === "string") {
|
|
62
|
+
const normalized = record.contentBase64.replace(/\s/g, "");
|
|
63
|
+
if (decodedBase64Length(normalized) > maxBytes) throw tooLarge("upload", maxBytes);
|
|
64
|
+
return Buffer.from(normalized, "base64");
|
|
65
|
+
}
|
|
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);
|
|
69
|
+
if (content instanceof ArrayBuffer) {
|
|
70
|
+
if (content.byteLength > maxBytes) throw tooLarge("upload", maxBytes);
|
|
71
|
+
return new Uint8Array(content);
|
|
72
|
+
}
|
|
73
|
+
if (typeof content === "string") {
|
|
74
|
+
if (Buffer.byteLength(content) > maxBytes) throw tooLarge("upload", maxBytes);
|
|
75
|
+
return new TextEncoder().encode(content);
|
|
76
|
+
}
|
|
77
|
+
throw new Error("task-artifact-upload: payload content is required");
|
|
78
|
+
}
|
|
79
|
+
async function collectArtifactBody(value, maxBytes) {
|
|
80
|
+
const source = value && typeof value === "object" && "stream" in value ? value.stream : value;
|
|
81
|
+
if (Buffer.isBuffer(source)) {
|
|
82
|
+
if (source.byteLength > maxBytes) throw tooLarge("download", maxBytes);
|
|
83
|
+
return source;
|
|
84
|
+
}
|
|
85
|
+
if (source instanceof Uint8Array) {
|
|
86
|
+
if (source.byteLength > maxBytes) throw tooLarge("download", maxBytes);
|
|
87
|
+
return Buffer.from(source);
|
|
88
|
+
}
|
|
89
|
+
if (source instanceof ArrayBuffer) {
|
|
90
|
+
if (source.byteLength > maxBytes) throw tooLarge("download", maxBytes);
|
|
91
|
+
return Buffer.from(source);
|
|
92
|
+
}
|
|
93
|
+
if (typeof source === "string") {
|
|
94
|
+
if (Buffer.byteLength(source) > maxBytes) throw tooLarge("download", maxBytes);
|
|
95
|
+
return Buffer.from(source);
|
|
96
|
+
}
|
|
97
|
+
if (source instanceof Readable) {
|
|
98
|
+
const chunks = [];
|
|
99
|
+
let bytes = 0;
|
|
100
|
+
for await (const chunk of source) bytes = pushChunk(chunks, chunk, bytes, maxBytes);
|
|
101
|
+
return Buffer.concat(chunks);
|
|
102
|
+
}
|
|
103
|
+
if (source && typeof source === "object" && Symbol.asyncIterator in source) {
|
|
104
|
+
const chunks = [];
|
|
105
|
+
let bytes = 0;
|
|
106
|
+
for await (const chunk of source) bytes = pushChunk(chunks, chunk, bytes, maxBytes);
|
|
107
|
+
return Buffer.concat(chunks);
|
|
108
|
+
}
|
|
109
|
+
if (source && typeof source === "object" && "arrayBuffer" in source) {
|
|
110
|
+
const size = source.size;
|
|
111
|
+
if (typeof size === "number" && size > maxBytes) throw tooLarge("download", maxBytes);
|
|
112
|
+
const arrayBuffer = await source.arrayBuffer();
|
|
113
|
+
if (arrayBuffer.byteLength > maxBytes) throw tooLarge("download", maxBytes);
|
|
114
|
+
return Buffer.from(arrayBuffer);
|
|
115
|
+
}
|
|
116
|
+
throw new Error("task-artifact-download: unsupported artifact body");
|
|
117
|
+
}
|
|
118
|
+
function toBuffer(value) {
|
|
119
|
+
if (Buffer.isBuffer(value)) return value;
|
|
120
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
121
|
+
if (value instanceof ArrayBuffer) return Buffer.from(value);
|
|
122
|
+
if (typeof value === "string") return Buffer.from(value);
|
|
123
|
+
return Buffer.from(String(value));
|
|
124
|
+
}
|
|
125
|
+
function recordField(value, key) {
|
|
126
|
+
if (!value || typeof value !== "object") return void 0;
|
|
127
|
+
return value[key];
|
|
128
|
+
}
|
|
129
|
+
function enforceMaxBytes(value, maxBytes) {
|
|
130
|
+
if (value.byteLength > maxBytes) throw tooLarge("upload", maxBytes);
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
function pushChunk(chunks, chunk, bytes, maxBytes) {
|
|
134
|
+
const next = toBuffer(chunk);
|
|
135
|
+
const total = bytes + next.byteLength;
|
|
136
|
+
if (total > maxBytes) throw tooLarge("download", maxBytes);
|
|
137
|
+
chunks.push(next);
|
|
138
|
+
return total;
|
|
139
|
+
}
|
|
140
|
+
function decodedBase64Length(value) {
|
|
141
|
+
if (!value) return 0;
|
|
142
|
+
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
|
|
143
|
+
return Math.floor(value.length * 3 / 4) - padding;
|
|
144
|
+
}
|
|
145
|
+
function tooLarge(operation, maxBytes) {
|
|
146
|
+
return /* @__PURE__ */ new Error(`task-artifact-${operation}: artifact body exceeds ${maxBytes} bytes`);
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
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 };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('moltnet-task-artifacts-list', {
|
|
3
|
+
category: 'moltnet',
|
|
4
|
+
color: '#00d4c8',
|
|
5
|
+
paletteLabel: 'task artifacts: list',
|
|
6
|
+
defaults: {
|
|
7
|
+
name: { value: '' },
|
|
8
|
+
agent: { value: '', type: 'moltnet-agent', required: true },
|
|
9
|
+
taskId: { value: '' },
|
|
10
|
+
teamId: { value: '' },
|
|
11
|
+
allowMsgTeamOverride: { value: false },
|
|
12
|
+
limit: { value: 20, validate: RED.validators.number() },
|
|
13
|
+
cursor: { value: '' },
|
|
14
|
+
},
|
|
15
|
+
inputs: 1,
|
|
16
|
+
outputs: 1,
|
|
17
|
+
icon: 'font-awesome/fa-list',
|
|
18
|
+
label: function () {
|
|
19
|
+
return this.name || 'task artifacts: list';
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<script type="text/html" data-template-name="moltnet-task-artifacts-list">
|
|
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-limit"><i class="fa fa-hashtag"></i> Limit</label>
|
|
58
|
+
<input type="number" id="node-input-limit" />
|
|
59
|
+
</div>
|
|
60
|
+
<div class="form-row">
|
|
61
|
+
<label for="node-input-cursor"><i class="fa fa-forward"></i> Cursor</label>
|
|
62
|
+
<input type="text" id="node-input-cursor" />
|
|
63
|
+
</div>
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<script type="text/html" data-help-name="moltnet-task-artifacts-list">
|
|
67
|
+
<p>
|
|
68
|
+
Lists artifacts for a MoltNet task. The task id is taken from
|
|
69
|
+
<code>msg.taskId</code>, <code>msg.payload.taskId</code>,
|
|
70
|
+
<code>msg.payload.id</code>, or the configured Task ID. Team ID is taken
|
|
71
|
+
from this node or the configured agent. Enable Team override to allow
|
|
72
|
+
<code>msg.teamId</code> or <code>msg.payload.teamId</code>.
|
|
73
|
+
</p>
|
|
74
|
+
<p>
|
|
75
|
+
Emits artifact rows on <code>msg.payload</code>. Pagination metadata, the
|
|
76
|
+
final query, and the full page are on <code>msg.artifacts</code>.
|
|
77
|
+
</p>
|
|
78
|
+
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { t as withAgent } from "./agent-call.js";
|
|
2
|
+
import { a as nonEmpty, c as positiveInt, n as compact, t as bool } from "./query-utils.js";
|
|
3
|
+
import { i as requireArtifactContext, n as payloadRecord } from "./task-artifact-utils.js";
|
|
4
|
+
//#region src/nodes/task-artifacts-list.ts
|
|
5
|
+
var init = (RED) => {
|
|
6
|
+
function TaskArtifactsListNode(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-artifacts-list: no moltnet-agent configured");
|
|
13
|
+
const { taskId, teamId } = requireArtifactContext("task-artifacts-list", msg, def.taskId, def.teamId, agentNode, bool(def.allowMsgTeamOverride) ?? false);
|
|
14
|
+
this.status({
|
|
15
|
+
fill: "blue",
|
|
16
|
+
shape: "dot",
|
|
17
|
+
text: "loading…"
|
|
18
|
+
});
|
|
19
|
+
const query = buildQuery(def, msg);
|
|
20
|
+
const page = await withAgent(agentNode, (agent) => agent.tasks.artifacts.listPage(taskId, query, { teamId }));
|
|
21
|
+
const out = RED.util.cloneMessage(msg);
|
|
22
|
+
out.payload = page.artifacts;
|
|
23
|
+
out.taskId = taskId;
|
|
24
|
+
out.artifacts = {
|
|
25
|
+
taskId,
|
|
26
|
+
teamId,
|
|
27
|
+
query,
|
|
28
|
+
count: page.artifacts.length,
|
|
29
|
+
nextCursor: page.nextCursor,
|
|
30
|
+
page
|
|
31
|
+
};
|
|
32
|
+
this.status({
|
|
33
|
+
fill: "green",
|
|
34
|
+
shape: "dot",
|
|
35
|
+
text: `${page.artifacts.length} artifact(s)`
|
|
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-artifacts-list", TaskArtifactsListNode);
|
|
52
|
+
};
|
|
53
|
+
function buildQuery(def, msg) {
|
|
54
|
+
const payload = payloadRecord(msg);
|
|
55
|
+
return compact({
|
|
56
|
+
limit: positiveInt(payload.limit) ?? positiveInt(def.limit),
|
|
57
|
+
cursor: nonEmpty(payload.cursor) ?? nonEmpty(def.cursor)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { init as default };
|
package/dist/nodes/task-get.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as withAgent } from "./agent-call.js";
|
|
1
2
|
import { t as buildTaskSnapshot } from "./task-snapshot.js";
|
|
2
3
|
//#region src/nodes/task-get.ts
|
|
3
4
|
var init = (RED) => {
|
|
@@ -15,8 +16,7 @@ var init = (RED) => {
|
|
|
15
16
|
shape: "dot",
|
|
16
17
|
text: "loading…"
|
|
17
18
|
});
|
|
18
|
-
const
|
|
19
|
-
const [task, attempts] = await Promise.all([agent.tasks.get(taskId), agent.tasks.listAttempts(taskId)]);
|
|
19
|
+
const [task, attempts] = await withAgent(agentNode, (agent) => Promise.all([agent.tasks.get(taskId), agent.tasks.listAttempts(taskId)]));
|
|
20
20
|
const snapshot = buildTaskSnapshot(task, attempts);
|
|
21
21
|
const out = RED.util.cloneMessage(msg);
|
|
22
22
|
out.payload = snapshot;
|
package/dist/nodes/task-wait.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as withAgent } from "./agent-call.js";
|
|
1
2
|
import { n as isTerminalTaskStatus, t as buildTaskSnapshot } from "./task-snapshot.js";
|
|
2
3
|
//#region src/nodes/task-wait.ts
|
|
3
4
|
var DEFAULT_POLL_SEC = 5;
|
|
@@ -9,48 +10,63 @@ var init = (RED) => {
|
|
|
9
10
|
const pollMs = Math.max(1, def.pollIntervalSec || DEFAULT_POLL_SEC) * 1e3;
|
|
10
11
|
const timeoutMs = (def.timeoutSec ?? DEFAULT_TIMEOUT_SEC) > 0 ? (def.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3 : 0;
|
|
11
12
|
const kindAllow = parseKinds(def.kinds);
|
|
13
|
+
const active = /* @__PURE__ */ new Map();
|
|
12
14
|
const pending = /* @__PURE__ */ new Set();
|
|
13
15
|
this.on("close", () => {
|
|
14
16
|
for (const t of pending) clearTimeout(t);
|
|
15
17
|
pending.clear();
|
|
16
18
|
});
|
|
17
19
|
this.on("input", (msg, send, done) => {
|
|
20
|
+
let taskIdForStatus;
|
|
21
|
+
let label = describeMessage(msg);
|
|
18
22
|
const run = async () => {
|
|
19
23
|
try {
|
|
20
24
|
if (!agentNode || typeof agentNode.getAgent !== "function") throw new Error("task-wait: no moltnet-agent configured");
|
|
21
25
|
const taskId = resolveTaskId(msg, def.taskId);
|
|
22
26
|
if (!taskId) throw new Error("task-wait: taskId is required");
|
|
23
|
-
|
|
27
|
+
taskIdForStatus = taskId;
|
|
28
|
+
label = describeWait(taskId, msg, label);
|
|
29
|
+
active.set(taskId, label);
|
|
30
|
+
const correlationId = resolveCorrelationId(msg);
|
|
24
31
|
const startedAt = Date.now();
|
|
25
32
|
let afterSeq;
|
|
26
33
|
let polls = 0;
|
|
27
34
|
this.status({
|
|
28
35
|
fill: "blue",
|
|
29
36
|
shape: "dot",
|
|
30
|
-
text: "waiting
|
|
37
|
+
text: statusText("waiting", label, active.size)
|
|
31
38
|
});
|
|
32
39
|
for (;;) {
|
|
33
|
-
if (def.tail) afterSeq = await drainMessages({
|
|
40
|
+
if (def.tail) afterSeq = await withAgent(agentNode, (agent) => drainMessages({
|
|
34
41
|
agent,
|
|
35
42
|
taskId,
|
|
36
43
|
afterSeq,
|
|
37
44
|
kindAllow,
|
|
38
45
|
emit: (m) => {
|
|
39
46
|
const tailMsg = RED.util.cloneMessage(msg);
|
|
40
|
-
tailMsg.payload =
|
|
47
|
+
tailMsg.payload = correlationId ? {
|
|
48
|
+
...m,
|
|
49
|
+
correlationId
|
|
50
|
+
} : m;
|
|
41
51
|
tailMsg.taskId = taskId;
|
|
52
|
+
if (correlationId) tailMsg.correlationId = correlationId;
|
|
42
53
|
send([tailMsg, null]);
|
|
43
54
|
}
|
|
44
|
-
});
|
|
45
|
-
const task = await agent.tasks.get(taskId);
|
|
55
|
+
}));
|
|
56
|
+
const task = await withAgent(agentNode, (agent) => agent.tasks.get(taskId));
|
|
46
57
|
if (isTerminalTaskStatus(task.status)) {
|
|
47
|
-
const snapshot = buildTaskSnapshot(task, await agent.tasks.listAttempts(taskId));
|
|
58
|
+
const snapshot = buildTaskSnapshot(task, await withAgent(agentNode, (agent) => agent.tasks.listAttempts(taskId)));
|
|
48
59
|
const resultMsg = RED.util.cloneMessage(msg);
|
|
49
|
-
resultMsg.payload =
|
|
60
|
+
resultMsg.payload = correlationId ? {
|
|
61
|
+
...snapshot,
|
|
62
|
+
correlationId
|
|
63
|
+
} : snapshot;
|
|
64
|
+
if (correlationId) resultMsg.correlationId = correlationId;
|
|
65
|
+
active.delete(taskId);
|
|
50
66
|
this.status({
|
|
51
67
|
fill: snapshot.accepted ? "green" : "red",
|
|
52
68
|
shape: "dot",
|
|
53
|
-
text: `${snapshot.status}${snapshot.accepted ? "
|
|
69
|
+
text: statusText(`${snapshot.status}${snapshot.accepted ? " ok" : ""}`, label, active.size)
|
|
54
70
|
});
|
|
55
71
|
send([null, resultMsg]);
|
|
56
72
|
done();
|
|
@@ -61,15 +77,16 @@ var init = (RED) => {
|
|
|
61
77
|
this.status({
|
|
62
78
|
fill: "blue",
|
|
63
79
|
shape: "ring",
|
|
64
|
-
text: `${task.status}
|
|
80
|
+
text: statusText(`${task.status} ${polls}x`, label, active.size)
|
|
65
81
|
});
|
|
66
82
|
await sleep(pollMs, pending);
|
|
67
83
|
}
|
|
68
84
|
} catch (err) {
|
|
85
|
+
if (taskIdForStatus) active.delete(taskIdForStatus);
|
|
69
86
|
this.status({
|
|
70
87
|
fill: "red",
|
|
71
88
|
shape: "ring",
|
|
72
|
-
text: "error"
|
|
89
|
+
text: statusText("error", label, active.size)
|
|
73
90
|
});
|
|
74
91
|
done(err instanceof Error ? err : new Error(String(err)));
|
|
75
92
|
}
|
|
@@ -120,6 +137,43 @@ function resolveTaskId(msg, configured) {
|
|
|
120
137
|
}
|
|
121
138
|
return configured && configured.length > 0 ? configured : void 0;
|
|
122
139
|
}
|
|
140
|
+
function resolveCorrelationId(msg) {
|
|
141
|
+
if (typeof msg.correlationId === "string" && msg.correlationId) return msg.correlationId;
|
|
142
|
+
const payload = msg.payload;
|
|
143
|
+
if (payload && typeof payload === "object") {
|
|
144
|
+
const p = payload;
|
|
145
|
+
if (typeof p.correlationId === "string" && p.correlationId) return p.correlationId;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function describeMessage(msg) {
|
|
149
|
+
if (typeof msg.reviewDimension === "string" && msg.reviewDimension) return msg.reviewDimension;
|
|
150
|
+
const payload = msg.payload;
|
|
151
|
+
if (payload && typeof payload === "object") {
|
|
152
|
+
const p = payload;
|
|
153
|
+
if (typeof p.dimension === "string" && p.dimension) return p.dimension;
|
|
154
|
+
if (typeof p.title === "string" && p.title) return p.title;
|
|
155
|
+
}
|
|
156
|
+
return "task";
|
|
157
|
+
}
|
|
158
|
+
function describeWait(taskId, msg, fallback) {
|
|
159
|
+
if (fallback !== "task") return fallback;
|
|
160
|
+
const payload = msg.payload;
|
|
161
|
+
if (payload && typeof payload === "object") {
|
|
162
|
+
const p = payload;
|
|
163
|
+
if (typeof p.title === "string" && p.title) return p.title;
|
|
164
|
+
}
|
|
165
|
+
return shortId(taskId);
|
|
166
|
+
}
|
|
167
|
+
function statusText(action, label, activeCount) {
|
|
168
|
+
const suffix = activeCount > 0 ? ` · ${activeCount} active` : "";
|
|
169
|
+
return `${action} · ${truncate(label, 34)}${suffix}`;
|
|
170
|
+
}
|
|
171
|
+
function shortId(id) {
|
|
172
|
+
return id.slice(0, 8);
|
|
173
|
+
}
|
|
174
|
+
function truncate(value, max) {
|
|
175
|
+
return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
|
|
176
|
+
}
|
|
123
177
|
function sleep(ms, pending) {
|
|
124
178
|
return new Promise((resolve) => {
|
|
125
179
|
const t = setTimeout(() => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as withAgent } from "./agent-call.js";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
//#region src/nodes/tasks-create.ts
|
|
3
4
|
var init = (RED) => {
|
|
@@ -5,18 +6,24 @@ var init = (RED) => {
|
|
|
5
6
|
RED.nodes.createNode(this, def);
|
|
6
7
|
const agentNode = def.agent ? RED.nodes.getNode(def.agent) : null;
|
|
7
8
|
const profileNode = def.runtimeProfile ? RED.nodes.getNode(def.runtimeProfile) : null;
|
|
9
|
+
const active = /* @__PURE__ */ new Map();
|
|
10
|
+
let nextInvocationId = 0;
|
|
8
11
|
this.on("input", (msg, send, done) => {
|
|
12
|
+
const invocationId = ++nextInvocationId;
|
|
13
|
+
let label = describeMessage(msg);
|
|
9
14
|
const run = async () => {
|
|
10
15
|
try {
|
|
11
16
|
if (!agentNode || typeof agentNode.getAgent !== "function") throw new Error("tasks-create: no moltnet-agent configured");
|
|
17
|
+
active.set(invocationId, label);
|
|
12
18
|
this.status({
|
|
13
19
|
fill: "blue",
|
|
14
20
|
shape: "dot",
|
|
15
|
-
text: "creating
|
|
21
|
+
text: statusText("creating", label, active.size)
|
|
16
22
|
});
|
|
17
|
-
const agent = await agentNode.getAgent();
|
|
18
23
|
const base = msg.payload && typeof msg.payload === "object" ? { ...msg.payload } : {};
|
|
19
24
|
if (!base.taskType) base.taskType = "freeform";
|
|
25
|
+
label = describeTaskBody(base, label);
|
|
26
|
+
active.set(invocationId, label);
|
|
20
27
|
if (!base.allowedProfiles && profileNode?.profileId) base.allowedProfiles = [{ profileId: profileNode.profileId }];
|
|
21
28
|
if (base.maxAttempts === void 0 && typeof def.maxAttempts === "number") base.maxAttempts = def.maxAttempts;
|
|
22
29
|
if (!base.teamId && agentNode.teamId) base.teamId = agentNode.teamId;
|
|
@@ -24,22 +31,24 @@ var init = (RED) => {
|
|
|
24
31
|
const correlationId = resolveCorrelationId(msg, base.correlationId, def.generateCorrelationId === true);
|
|
25
32
|
if (correlationId) base.correlationId = correlationId;
|
|
26
33
|
const { teamId, ...createBody } = base;
|
|
27
|
-
const task = await agent.tasks.create(createBody, { teamId });
|
|
34
|
+
const task = await withAgent(agentNode, (agent) => agent.tasks.create(createBody, { teamId }));
|
|
28
35
|
const out = RED.util.cloneMessage(msg);
|
|
29
36
|
if (correlationId) out.correlationId = correlationId;
|
|
30
37
|
out.payload = task;
|
|
38
|
+
active.delete(invocationId);
|
|
31
39
|
this.status({
|
|
32
40
|
fill: "green",
|
|
33
41
|
shape: "dot",
|
|
34
|
-
text: `
|
|
42
|
+
text: statusText(`created ${shortId(task.id)}`, label, active.size)
|
|
35
43
|
});
|
|
36
44
|
send(out);
|
|
37
45
|
done();
|
|
38
46
|
} catch (err) {
|
|
47
|
+
active.delete(invocationId);
|
|
39
48
|
this.status({
|
|
40
49
|
fill: "red",
|
|
41
50
|
shape: "ring",
|
|
42
|
-
text: "error"
|
|
51
|
+
text: statusText("error", label, active.size)
|
|
43
52
|
});
|
|
44
53
|
done(err instanceof Error ? err : new Error(String(err)));
|
|
45
54
|
}
|
|
@@ -60,5 +69,37 @@ function resolveCorrelationId(msg, fromPayload, generate) {
|
|
|
60
69
|
if (typeof msg.correlationId === "string" && msg.correlationId) return msg.correlationId;
|
|
61
70
|
return generate ? randomUUID() : void 0;
|
|
62
71
|
}
|
|
72
|
+
function describeMessage(msg) {
|
|
73
|
+
if (typeof msg.reviewDimension === "string" && msg.reviewDimension) return msg.reviewDimension;
|
|
74
|
+
const payload = msg.payload;
|
|
75
|
+
if (payload && typeof payload === "object") {
|
|
76
|
+
const p = payload;
|
|
77
|
+
if (typeof p.dimension === "string" && p.dimension) return p.dimension;
|
|
78
|
+
if (typeof p.title === "string" && p.title) return p.title;
|
|
79
|
+
}
|
|
80
|
+
return "task";
|
|
81
|
+
}
|
|
82
|
+
function describeTaskBody(body, fallback) {
|
|
83
|
+
if (fallback !== "task") return fallback;
|
|
84
|
+
const input = body.input;
|
|
85
|
+
if (input && typeof input === "object") {
|
|
86
|
+
const execution = input.execution;
|
|
87
|
+
if (execution && typeof execution === "object") {
|
|
88
|
+
const dimension = execution.dimension;
|
|
89
|
+
if (typeof dimension === "string" && dimension) return dimension;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return typeof body.title === "string" && body.title ? body.title : fallback;
|
|
93
|
+
}
|
|
94
|
+
function statusText(action, label, activeCount) {
|
|
95
|
+
const suffix = activeCount > 0 ? ` · ${activeCount} active` : "";
|
|
96
|
+
return `${action} · ${truncate(label, 34)}${suffix}`;
|
|
97
|
+
}
|
|
98
|
+
function shortId(id) {
|
|
99
|
+
return typeof id === "string" && id ? id.slice(0, 8) : "task";
|
|
100
|
+
}
|
|
101
|
+
function truncate(value, max) {
|
|
102
|
+
return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
|
|
103
|
+
}
|
|
63
104
|
//#endregion
|
|
64
105
|
export { init as default };
|
package/dist/nodes/tasks-list.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as withAgent } from "./agent-call.js";
|
|
1
2
|
import { a as nonEmpty, c as positiveInt, n as compact, r as csv, t as bool } from "./query-utils.js";
|
|
2
3
|
//#region src/nodes/tasks-list.ts
|
|
3
4
|
var init = (RED) => {
|
|
@@ -15,9 +16,8 @@ var init = (RED) => {
|
|
|
15
16
|
shape: "dot",
|
|
16
17
|
text: "loading…"
|
|
17
18
|
});
|
|
18
|
-
const agent = await agentNode.getAgent();
|
|
19
19
|
const query = buildTasksQuery(def, msg);
|
|
20
|
-
const result = await agent.tasks.list(query, { teamId });
|
|
20
|
+
const result = await withAgent(agentNode, (agent) => agent.tasks.list(query, { teamId }));
|
|
21
21
|
const out = RED.util.cloneMessage(msg);
|
|
22
22
|
out.payload = result.items;
|
|
23
23
|
out.tasks = {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as withAgent } from "./agent-call.js";
|
|
1
2
|
//#region src/nodes/workflow-status.ts
|
|
2
3
|
var init = (RED) => {
|
|
3
4
|
function WorkflowStatusNode(def) {
|
|
@@ -17,12 +18,11 @@ var init = (RED) => {
|
|
|
17
18
|
});
|
|
18
19
|
const teamId = agentNode.teamId;
|
|
19
20
|
if (!teamId) throw new Error("workflow-status: agent teamId is required");
|
|
20
|
-
const agent = await agentNode.getAgent();
|
|
21
21
|
const query = {
|
|
22
22
|
correlationId,
|
|
23
23
|
limit: def.limit && def.limit > 0 ? def.limit : 50
|
|
24
24
|
};
|
|
25
|
-
const res = await agent.tasks.list(query, { teamId });
|
|
25
|
+
const res = await withAgent(agentNode, (agent) => agent.tasks.list(query, { teamId }));
|
|
26
26
|
const rows = res.items.map((t) => ({
|
|
27
27
|
taskId: t.id,
|
|
28
28
|
type: t.taskType,
|