botmux-workflow-core 3.16.0 → 3.17.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/dist/cjs/control.cjs +7 -0
- package/dist/cjs/control.cjs.map +3 -3
- package/dist/cjs/engine.cjs +7 -0
- package/dist/cjs/engine.cjs.map +3 -3
- package/dist/cjs/gate-policy.cjs +7 -0
- package/dist/cjs/gate-policy.cjs.map +3 -3
- package/dist/cjs/index.cjs +135 -8
- package/dist/cjs/index.cjs.map +4 -4
- package/dist/cjs/runtime.cjs +205 -33
- package/dist/cjs/runtime.cjs.map +4 -4
- package/dist/cjs/schema.cjs +135 -8
- package/dist/cjs/schema.cjs.map +4 -4
- package/dist/esm/control.js +7 -0
- package/dist/esm/control.js.map +3 -3
- package/dist/esm/engine.js +7 -0
- package/dist/esm/engine.js.map +3 -3
- package/dist/esm/gate-policy.js +7 -0
- package/dist/esm/gate-policy.js.map +3 -3
- package/dist/esm/index.js +135 -8
- package/dist/esm/index.js.map +4 -4
- package/dist/esm/runtime.js +205 -33
- package/dist/esm/runtime.js.map +4 -4
- package/dist/esm/schema.js +135 -8
- package/dist/esm/schema.js.map +4 -4
- package/dist/types/src/workflows/v3/artifact-contract-declarations.d.ts +21 -0
- package/dist/types/src/workflows/v3/dag.d.ts +7 -0
- package/dist/types/src/workflows/v3/event-contract.d.ts +3 -1
- package/package.json +1 -1
package/dist/esm/schema.js
CHANGED
|
@@ -106,6 +106,79 @@ function isRecord(value) {
|
|
|
106
106
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
// src/workflows/v3/artifact-contract.ts
|
|
110
|
+
var MANIFEST_FILE_KINDS = [
|
|
111
|
+
"markdown",
|
|
112
|
+
"json",
|
|
113
|
+
"text",
|
|
114
|
+
"code",
|
|
115
|
+
"log",
|
|
116
|
+
"binary",
|
|
117
|
+
"directory"
|
|
118
|
+
];
|
|
119
|
+
var MANIFEST_SUMMARY_MAX_BYTES = 4 * 1024;
|
|
120
|
+
var MANIFEST_PREVIEW_MAX_BYTES = 4 * 1024;
|
|
121
|
+
|
|
122
|
+
// src/workflows/v3/artifact-contract-declarations.ts
|
|
123
|
+
var V3_ARTIFACT_OUTPUT_KEY_RE = /^[A-Za-z0-9._-]+$/;
|
|
124
|
+
var V3_ARTIFACT_OUTPUT_MAX_COUNT = 32;
|
|
125
|
+
var V3_ARTIFACT_OUTPUT_MAX_BYTES = 4 * 1024;
|
|
126
|
+
function normalizeArtifactOutputs(value, where, problems) {
|
|
127
|
+
if (value === void 0) return void 0;
|
|
128
|
+
if (!isRecord2(value) || Object.keys(value).length === 0) {
|
|
129
|
+
problems.push(`${where}.outputs must be a non-empty object when present`);
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
const entries = Object.entries(value);
|
|
133
|
+
if (entries.length > V3_ARTIFACT_OUTPUT_MAX_COUNT) {
|
|
134
|
+
problems.push(`${where}.outputs has ${entries.length} entries (max ${V3_ARTIFACT_OUTPUT_MAX_COUNT})`);
|
|
135
|
+
}
|
|
136
|
+
if (Buffer.byteLength(JSON.stringify(value), "utf-8") > V3_ARTIFACT_OUTPUT_MAX_BYTES) {
|
|
137
|
+
problems.push(`${where}.outputs exceeds ${V3_ARTIFACT_OUTPUT_MAX_BYTES} serialized bytes`);
|
|
138
|
+
}
|
|
139
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
140
|
+
const paths = /* @__PURE__ */ new Set();
|
|
141
|
+
for (const [key, raw] of entries) {
|
|
142
|
+
const itemWhere = `${where}.outputs.${JSON.stringify(key)}`;
|
|
143
|
+
if (!V3_ARTIFACT_OUTPUT_KEY_RE.test(key)) {
|
|
144
|
+
problems.push(`${itemWhere} key must match ${V3_ARTIFACT_OUTPUT_KEY_RE}`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!isRecord2(raw)) {
|
|
148
|
+
problems.push(`${itemWhere} must be { path, kind }`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const extra = Object.keys(raw).filter((field) => field !== "path" && field !== "kind");
|
|
152
|
+
if (extra.length > 0) {
|
|
153
|
+
problems.push(`${itemWhere} has unsupported key(s): ${extra.join(", ")} (allowed: path, kind)`);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!isPortableRelativeArtifactPath(raw.path)) {
|
|
157
|
+
problems.push(`${itemWhere}.path must be a portable relative path without '.', '..', or empty segments`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (typeof raw.kind !== "string" || !MANIFEST_FILE_KINDS.includes(raw.kind)) {
|
|
161
|
+
problems.push(`${itemWhere}.kind must be one of ${MANIFEST_FILE_KINDS.join(" | ")}`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (paths.has(raw.path)) {
|
|
165
|
+
problems.push(`${where}.outputs contains duplicate path ${JSON.stringify(raw.path)}`);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
paths.add(raw.path);
|
|
169
|
+
out[key] = { path: raw.path, kind: raw.kind };
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
function isPortableRelativeArtifactPath(value) {
|
|
174
|
+
if (typeof value !== "string" || value.length === 0 || value.startsWith("/") || value.includes("\\") || value.includes("\0") || /^[A-Za-z]:/.test(value)) return false;
|
|
175
|
+
const segments = value.split("/");
|
|
176
|
+
return segments.every((segment) => segment !== "" && segment !== "." && segment !== "..");
|
|
177
|
+
}
|
|
178
|
+
function isRecord2(value) {
|
|
179
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
180
|
+
}
|
|
181
|
+
|
|
109
182
|
// src/workflows/v3/dag.ts
|
|
110
183
|
var NODE_KINDS = ["goal", "host", "loop"];
|
|
111
184
|
var V3_HOST_EXECUTORS = ["feishu-send", "feishu-reply", "botmux-schedule"];
|
|
@@ -156,6 +229,10 @@ function validateDag(raw) {
|
|
|
156
229
|
if (!isObject(raw)) {
|
|
157
230
|
throw new DagValidationError(["root must be a JSON object"]);
|
|
158
231
|
}
|
|
232
|
+
const schemaVersion = raw.schemaVersion === void 0 ? 1 : raw.schemaVersion;
|
|
233
|
+
if (schemaVersion !== 1 && schemaVersion !== 2) {
|
|
234
|
+
problems.push(`schemaVersion must be 1 or 2 (got ${JSON.stringify(raw.schemaVersion)})`);
|
|
235
|
+
}
|
|
159
236
|
if (typeof raw.runId !== "string" || !SEGMENT_RE.test(raw.runId)) {
|
|
160
237
|
problems.push(`runId must be a path-safe string matching ${SEGMENT_RE} (got ${JSON.stringify(raw.runId)})`);
|
|
161
238
|
}
|
|
@@ -192,9 +269,9 @@ function validateDag(raw) {
|
|
|
192
269
|
if (fromList.includes(id)) problems.push(`node "${id}" depends on itself`);
|
|
193
270
|
if (new Set(fromList).size !== fromList.length) problems.push(`node "${id}".depends has duplicates`);
|
|
194
271
|
const triggerRule = normTriggerRule(n.triggerRule, depends.length, `node "${id}"`, problems);
|
|
195
|
-
const inputs = normInputs(n.inputs, id, problems);
|
|
272
|
+
const inputs = normInputs(n.inputs, id, problems, schemaVersion === 2 ? 2 : 1);
|
|
196
273
|
if (type === "loop") {
|
|
197
|
-
const loopFields = normLoopFields(n, id, problems);
|
|
274
|
+
const loopFields = normLoopFields(n, id, problems, schemaVersion === 2 ? 2 : 1);
|
|
198
275
|
if (loopFields) {
|
|
199
276
|
nodes.push({
|
|
200
277
|
id,
|
|
@@ -211,6 +288,7 @@ function validateDag(raw) {
|
|
|
211
288
|
continue;
|
|
212
289
|
}
|
|
213
290
|
if (type === "host") {
|
|
291
|
+
if (n.outputs !== void 0) problems.push(`host node "${id}".outputs is not supported`);
|
|
214
292
|
if (n.goal !== void 0) problems.push(`host node "${id}".goal is not supported`);
|
|
215
293
|
if (n.bot !== void 0) problems.push(`host node "${id}".bot is not supported \u2014 host nodes do not spawn a CLI`);
|
|
216
294
|
if (n.override !== void 0) problems.push(`host node "${id}".override is not supported`);
|
|
@@ -286,6 +364,10 @@ function validateDag(raw) {
|
|
|
286
364
|
const humanGate = normHumanGate(n.humanGate, `node "${id}"`, problems);
|
|
287
365
|
const override = normOverride(n.override, `node "${id}"`, problems);
|
|
288
366
|
const revisitTo = normRevisitTo(n.revisitTo, id, problems);
|
|
367
|
+
if (schemaVersion !== 2 && n.outputs !== void 0) {
|
|
368
|
+
problems.push(`goal node "${id}".outputs requires schemaVersion 2`);
|
|
369
|
+
}
|
|
370
|
+
const outputs = schemaVersion === 2 ? normalizeArtifactOutputs(n.outputs, `goal node "${id}"`, problems) : void 0;
|
|
289
371
|
nodes.push({
|
|
290
372
|
id,
|
|
291
373
|
type,
|
|
@@ -295,6 +377,7 @@ function validateDag(raw) {
|
|
|
295
377
|
triggerRule,
|
|
296
378
|
override,
|
|
297
379
|
inputs,
|
|
380
|
+
outputs,
|
|
298
381
|
timeoutSec,
|
|
299
382
|
humanGate,
|
|
300
383
|
resultSchema,
|
|
@@ -310,6 +393,14 @@ function validateDag(raw) {
|
|
|
310
393
|
problems.push(`node "${node.id}".inputs references unknown node "${inp.from}"`);
|
|
311
394
|
} else if (!node.depends.some((d) => d.from === inp.from)) {
|
|
312
395
|
problems.push(`node "${node.id}".inputs.from "${inp.from}" must also be in depends`);
|
|
396
|
+
} else if (inp.output !== void 0) {
|
|
397
|
+
const source = nodes.find((candidate) => candidate.id === inp.from);
|
|
398
|
+
const outputs = source?.type === "loop" ? source.body?.nodes.find((bodyNode) => bodyNode.id === source.output?.from)?.outputs : source?.outputs;
|
|
399
|
+
if (!outputs || !Object.prototype.hasOwnProperty.call(outputs, inp.output)) {
|
|
400
|
+
problems.push(
|
|
401
|
+
`node "${node.id}".inputs output ${JSON.stringify(inp.output)} is not declared by source "${inp.from}"`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
313
404
|
}
|
|
314
405
|
}
|
|
315
406
|
if (node.type === "host") {
|
|
@@ -388,7 +479,11 @@ function validateDag(raw) {
|
|
|
388
479
|
}
|
|
389
480
|
}
|
|
390
481
|
if (problems.length > 0) throw new DagValidationError(problems);
|
|
391
|
-
const dag = {
|
|
482
|
+
const dag = {
|
|
483
|
+
...raw.schemaVersion !== void 0 ? { schemaVersion } : {},
|
|
484
|
+
runId: raw.runId,
|
|
485
|
+
nodes
|
|
486
|
+
};
|
|
392
487
|
topologicalOrder(dag);
|
|
393
488
|
return dag;
|
|
394
489
|
}
|
|
@@ -702,7 +797,7 @@ function downstreamCone(nodeId, nodes) {
|
|
|
702
797
|
function nodeByIdUnsafe(nodes, nodeId) {
|
|
703
798
|
return nodes.find((node) => node.id === nodeId);
|
|
704
799
|
}
|
|
705
|
-
function normLoopFields(n, id, problems) {
|
|
800
|
+
function normLoopFields(n, id, problems, schemaVersion) {
|
|
706
801
|
const where = `loop node "${id}"`;
|
|
707
802
|
const before = problems.length;
|
|
708
803
|
if (n.timeoutSec !== void 0) {
|
|
@@ -773,10 +868,14 @@ function normLoopFields(n, id, problems) {
|
|
|
773
868
|
const bFromList = bdepends.map((d) => d.from);
|
|
774
869
|
if (bFromList.includes(bid)) problems.push(`${where}.body node "${bid}" depends on itself`);
|
|
775
870
|
if (new Set(bFromList).size !== bFromList.length) problems.push(`${where}.body node "${bid}".depends has duplicates`);
|
|
776
|
-
const binputs = normInputs(b.inputs, `${id}.body.${bid}`, problems);
|
|
871
|
+
const binputs = normInputs(b.inputs, `${id}.body.${bid}`, problems, schemaVersion);
|
|
777
872
|
const btimeout = normTimeoutSec(b.timeoutSec, `${where}.body node "${bid}"`, problems);
|
|
778
873
|
const bschema = normResultSchema(b.resultSchema, `${id}.body.${bid}`, problems);
|
|
779
874
|
const boverride = normOverride(b.override, `${where}.body node "${bid}"`, problems);
|
|
875
|
+
if (schemaVersion !== 2 && b.outputs !== void 0) {
|
|
876
|
+
problems.push(`${where}.body node "${bid}".outputs requires schemaVersion 2`);
|
|
877
|
+
}
|
|
878
|
+
const boutputs = schemaVersion === 2 ? normalizeArtifactOutputs(b.outputs, `${where}.body node "${bid}"`, problems) : void 0;
|
|
780
879
|
bodyNodes.push({
|
|
781
880
|
id: bid,
|
|
782
881
|
type: "goal",
|
|
@@ -785,6 +884,7 @@ function normLoopFields(n, id, problems) {
|
|
|
785
884
|
depends: bdepends,
|
|
786
885
|
override: boverride,
|
|
787
886
|
inputs: binputs,
|
|
887
|
+
outputs: boutputs,
|
|
788
888
|
timeoutSec: btimeout,
|
|
789
889
|
humanGate: null,
|
|
790
890
|
resultSchema: bschema
|
|
@@ -799,6 +899,13 @@ function normLoopFields(n, id, problems) {
|
|
|
799
899
|
problems.push(`${where}.body node "${bn.id}".inputs references unknown body node "${inp.from}"`);
|
|
800
900
|
} else if (!bn.depends.some((d) => d.from === inp.from)) {
|
|
801
901
|
problems.push(`${where}.body node "${bn.id}".inputs.from "${inp.from}" must also be in depends`);
|
|
902
|
+
} else if (inp.output !== void 0) {
|
|
903
|
+
const source = bodyNodes.find((candidate) => candidate.id === inp.from);
|
|
904
|
+
if (!source?.outputs || !Object.prototype.hasOwnProperty.call(source.outputs, inp.output)) {
|
|
905
|
+
problems.push(
|
|
906
|
+
`${where}.body node "${bn.id}".inputs output ${JSON.stringify(inp.output)} is not declared by source "${inp.from}"`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
802
909
|
}
|
|
803
910
|
}
|
|
804
911
|
}
|
|
@@ -1024,7 +1131,7 @@ function normResultSchema(v, id, problems) {
|
|
|
1024
1131
|
}
|
|
1025
1132
|
return schema;
|
|
1026
1133
|
}
|
|
1027
|
-
function normInputs(v, id, problems) {
|
|
1134
|
+
function normInputs(v, id, problems, schemaVersion) {
|
|
1028
1135
|
if (v === void 0) return [];
|
|
1029
1136
|
if (!Array.isArray(v)) {
|
|
1030
1137
|
problems.push(`node "${id}".inputs must be an array`);
|
|
@@ -1037,9 +1144,29 @@ function normInputs(v, id, problems) {
|
|
|
1037
1144
|
problems.push(`node "${id}".inputs[${j}] must be { from: <nodeId>, select? }`);
|
|
1038
1145
|
continue;
|
|
1039
1146
|
}
|
|
1040
|
-
const extra = Object.keys(inp).filter((k) => k !== "from" && k !== "select");
|
|
1147
|
+
const extra = Object.keys(inp).filter((k) => k !== "from" && k !== "select" && k !== "output");
|
|
1041
1148
|
if (extra.length > 0) {
|
|
1042
|
-
problems.push(`node "${id}".inputs[${j}] has unsupported key(s): ${extra.join(", ")} (allowed: from, select)`);
|
|
1149
|
+
problems.push(`node "${id}".inputs[${j}] has unsupported key(s): ${extra.join(", ")} (allowed: from, select/output)`);
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (schemaVersion === 2) {
|
|
1153
|
+
if (inp.select !== void 0) {
|
|
1154
|
+
problems.push(`node "${id}".inputs[${j}].select is legacy-only; schemaVersion 2 must use output`);
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
if (inp.output === void 0) {
|
|
1158
|
+
out.push({ from: inp.from });
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
if (typeof inp.output !== "string" || !SEGMENT_RE.test(inp.output)) {
|
|
1162
|
+
problems.push(`node "${id}".inputs[${j}].output must be a stable key matching ${SEGMENT_RE}`);
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
out.push({ from: inp.from, output: inp.output });
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
if (inp.output !== void 0) {
|
|
1169
|
+
problems.push(`node "${id}".inputs[${j}].output requires schemaVersion 2`);
|
|
1043
1170
|
continue;
|
|
1044
1171
|
}
|
|
1045
1172
|
if (inp.select === void 0) {
|