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/cjs/schema.cjs
CHANGED
|
@@ -155,6 +155,79 @@ function isRecord(value) {
|
|
|
155
155
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
// src/workflows/v3/artifact-contract.ts
|
|
159
|
+
var MANIFEST_FILE_KINDS = [
|
|
160
|
+
"markdown",
|
|
161
|
+
"json",
|
|
162
|
+
"text",
|
|
163
|
+
"code",
|
|
164
|
+
"log",
|
|
165
|
+
"binary",
|
|
166
|
+
"directory"
|
|
167
|
+
];
|
|
168
|
+
var MANIFEST_SUMMARY_MAX_BYTES = 4 * 1024;
|
|
169
|
+
var MANIFEST_PREVIEW_MAX_BYTES = 4 * 1024;
|
|
170
|
+
|
|
171
|
+
// src/workflows/v3/artifact-contract-declarations.ts
|
|
172
|
+
var V3_ARTIFACT_OUTPUT_KEY_RE = /^[A-Za-z0-9._-]+$/;
|
|
173
|
+
var V3_ARTIFACT_OUTPUT_MAX_COUNT = 32;
|
|
174
|
+
var V3_ARTIFACT_OUTPUT_MAX_BYTES = 4 * 1024;
|
|
175
|
+
function normalizeArtifactOutputs(value, where, problems) {
|
|
176
|
+
if (value === void 0) return void 0;
|
|
177
|
+
if (!isRecord2(value) || Object.keys(value).length === 0) {
|
|
178
|
+
problems.push(`${where}.outputs must be a non-empty object when present`);
|
|
179
|
+
return void 0;
|
|
180
|
+
}
|
|
181
|
+
const entries = Object.entries(value);
|
|
182
|
+
if (entries.length > V3_ARTIFACT_OUTPUT_MAX_COUNT) {
|
|
183
|
+
problems.push(`${where}.outputs has ${entries.length} entries (max ${V3_ARTIFACT_OUTPUT_MAX_COUNT})`);
|
|
184
|
+
}
|
|
185
|
+
if (Buffer.byteLength(JSON.stringify(value), "utf-8") > V3_ARTIFACT_OUTPUT_MAX_BYTES) {
|
|
186
|
+
problems.push(`${where}.outputs exceeds ${V3_ARTIFACT_OUTPUT_MAX_BYTES} serialized bytes`);
|
|
187
|
+
}
|
|
188
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
189
|
+
const paths = /* @__PURE__ */ new Set();
|
|
190
|
+
for (const [key, raw] of entries) {
|
|
191
|
+
const itemWhere = `${where}.outputs.${JSON.stringify(key)}`;
|
|
192
|
+
if (!V3_ARTIFACT_OUTPUT_KEY_RE.test(key)) {
|
|
193
|
+
problems.push(`${itemWhere} key must match ${V3_ARTIFACT_OUTPUT_KEY_RE}`);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (!isRecord2(raw)) {
|
|
197
|
+
problems.push(`${itemWhere} must be { path, kind }`);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const extra = Object.keys(raw).filter((field) => field !== "path" && field !== "kind");
|
|
201
|
+
if (extra.length > 0) {
|
|
202
|
+
problems.push(`${itemWhere} has unsupported key(s): ${extra.join(", ")} (allowed: path, kind)`);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (!isPortableRelativeArtifactPath(raw.path)) {
|
|
206
|
+
problems.push(`${itemWhere}.path must be a portable relative path without '.', '..', or empty segments`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (typeof raw.kind !== "string" || !MANIFEST_FILE_KINDS.includes(raw.kind)) {
|
|
210
|
+
problems.push(`${itemWhere}.kind must be one of ${MANIFEST_FILE_KINDS.join(" | ")}`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (paths.has(raw.path)) {
|
|
214
|
+
problems.push(`${where}.outputs contains duplicate path ${JSON.stringify(raw.path)}`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
paths.add(raw.path);
|
|
218
|
+
out[key] = { path: raw.path, kind: raw.kind };
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
function isPortableRelativeArtifactPath(value) {
|
|
223
|
+
if (typeof value !== "string" || value.length === 0 || value.startsWith("/") || value.includes("\\") || value.includes("\0") || /^[A-Za-z]:/.test(value)) return false;
|
|
224
|
+
const segments = value.split("/");
|
|
225
|
+
return segments.every((segment) => segment !== "" && segment !== "." && segment !== "..");
|
|
226
|
+
}
|
|
227
|
+
function isRecord2(value) {
|
|
228
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
229
|
+
}
|
|
230
|
+
|
|
158
231
|
// src/workflows/v3/dag.ts
|
|
159
232
|
var NODE_KINDS = ["goal", "host", "loop"];
|
|
160
233
|
var V3_HOST_EXECUTORS = ["feishu-send", "feishu-reply", "botmux-schedule"];
|
|
@@ -205,6 +278,10 @@ function validateDag(raw) {
|
|
|
205
278
|
if (!isObject(raw)) {
|
|
206
279
|
throw new DagValidationError(["root must be a JSON object"]);
|
|
207
280
|
}
|
|
281
|
+
const schemaVersion = raw.schemaVersion === void 0 ? 1 : raw.schemaVersion;
|
|
282
|
+
if (schemaVersion !== 1 && schemaVersion !== 2) {
|
|
283
|
+
problems.push(`schemaVersion must be 1 or 2 (got ${JSON.stringify(raw.schemaVersion)})`);
|
|
284
|
+
}
|
|
208
285
|
if (typeof raw.runId !== "string" || !SEGMENT_RE.test(raw.runId)) {
|
|
209
286
|
problems.push(`runId must be a path-safe string matching ${SEGMENT_RE} (got ${JSON.stringify(raw.runId)})`);
|
|
210
287
|
}
|
|
@@ -241,9 +318,9 @@ function validateDag(raw) {
|
|
|
241
318
|
if (fromList.includes(id)) problems.push(`node "${id}" depends on itself`);
|
|
242
319
|
if (new Set(fromList).size !== fromList.length) problems.push(`node "${id}".depends has duplicates`);
|
|
243
320
|
const triggerRule = normTriggerRule(n.triggerRule, depends.length, `node "${id}"`, problems);
|
|
244
|
-
const inputs = normInputs(n.inputs, id, problems);
|
|
321
|
+
const inputs = normInputs(n.inputs, id, problems, schemaVersion === 2 ? 2 : 1);
|
|
245
322
|
if (type === "loop") {
|
|
246
|
-
const loopFields = normLoopFields(n, id, problems);
|
|
323
|
+
const loopFields = normLoopFields(n, id, problems, schemaVersion === 2 ? 2 : 1);
|
|
247
324
|
if (loopFields) {
|
|
248
325
|
nodes.push({
|
|
249
326
|
id,
|
|
@@ -260,6 +337,7 @@ function validateDag(raw) {
|
|
|
260
337
|
continue;
|
|
261
338
|
}
|
|
262
339
|
if (type === "host") {
|
|
340
|
+
if (n.outputs !== void 0) problems.push(`host node "${id}".outputs is not supported`);
|
|
263
341
|
if (n.goal !== void 0) problems.push(`host node "${id}".goal is not supported`);
|
|
264
342
|
if (n.bot !== void 0) problems.push(`host node "${id}".bot is not supported \u2014 host nodes do not spawn a CLI`);
|
|
265
343
|
if (n.override !== void 0) problems.push(`host node "${id}".override is not supported`);
|
|
@@ -335,6 +413,10 @@ function validateDag(raw) {
|
|
|
335
413
|
const humanGate = normHumanGate(n.humanGate, `node "${id}"`, problems);
|
|
336
414
|
const override = normOverride(n.override, `node "${id}"`, problems);
|
|
337
415
|
const revisitTo = normRevisitTo(n.revisitTo, id, problems);
|
|
416
|
+
if (schemaVersion !== 2 && n.outputs !== void 0) {
|
|
417
|
+
problems.push(`goal node "${id}".outputs requires schemaVersion 2`);
|
|
418
|
+
}
|
|
419
|
+
const outputs = schemaVersion === 2 ? normalizeArtifactOutputs(n.outputs, `goal node "${id}"`, problems) : void 0;
|
|
338
420
|
nodes.push({
|
|
339
421
|
id,
|
|
340
422
|
type,
|
|
@@ -344,6 +426,7 @@ function validateDag(raw) {
|
|
|
344
426
|
triggerRule,
|
|
345
427
|
override,
|
|
346
428
|
inputs,
|
|
429
|
+
outputs,
|
|
347
430
|
timeoutSec,
|
|
348
431
|
humanGate,
|
|
349
432
|
resultSchema,
|
|
@@ -359,6 +442,14 @@ function validateDag(raw) {
|
|
|
359
442
|
problems.push(`node "${node.id}".inputs references unknown node "${inp.from}"`);
|
|
360
443
|
} else if (!node.depends.some((d) => d.from === inp.from)) {
|
|
361
444
|
problems.push(`node "${node.id}".inputs.from "${inp.from}" must also be in depends`);
|
|
445
|
+
} else if (inp.output !== void 0) {
|
|
446
|
+
const source = nodes.find((candidate) => candidate.id === inp.from);
|
|
447
|
+
const outputs = source?.type === "loop" ? source.body?.nodes.find((bodyNode) => bodyNode.id === source.output?.from)?.outputs : source?.outputs;
|
|
448
|
+
if (!outputs || !Object.prototype.hasOwnProperty.call(outputs, inp.output)) {
|
|
449
|
+
problems.push(
|
|
450
|
+
`node "${node.id}".inputs output ${JSON.stringify(inp.output)} is not declared by source "${inp.from}"`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
362
453
|
}
|
|
363
454
|
}
|
|
364
455
|
if (node.type === "host") {
|
|
@@ -437,7 +528,11 @@ function validateDag(raw) {
|
|
|
437
528
|
}
|
|
438
529
|
}
|
|
439
530
|
if (problems.length > 0) throw new DagValidationError(problems);
|
|
440
|
-
const dag = {
|
|
531
|
+
const dag = {
|
|
532
|
+
...raw.schemaVersion !== void 0 ? { schemaVersion } : {},
|
|
533
|
+
runId: raw.runId,
|
|
534
|
+
nodes
|
|
535
|
+
};
|
|
441
536
|
topologicalOrder(dag);
|
|
442
537
|
return dag;
|
|
443
538
|
}
|
|
@@ -751,7 +846,7 @@ function downstreamCone(nodeId, nodes) {
|
|
|
751
846
|
function nodeByIdUnsafe(nodes, nodeId) {
|
|
752
847
|
return nodes.find((node) => node.id === nodeId);
|
|
753
848
|
}
|
|
754
|
-
function normLoopFields(n, id, problems) {
|
|
849
|
+
function normLoopFields(n, id, problems, schemaVersion) {
|
|
755
850
|
const where = `loop node "${id}"`;
|
|
756
851
|
const before = problems.length;
|
|
757
852
|
if (n.timeoutSec !== void 0) {
|
|
@@ -822,10 +917,14 @@ function normLoopFields(n, id, problems) {
|
|
|
822
917
|
const bFromList = bdepends.map((d) => d.from);
|
|
823
918
|
if (bFromList.includes(bid)) problems.push(`${where}.body node "${bid}" depends on itself`);
|
|
824
919
|
if (new Set(bFromList).size !== bFromList.length) problems.push(`${where}.body node "${bid}".depends has duplicates`);
|
|
825
|
-
const binputs = normInputs(b.inputs, `${id}.body.${bid}`, problems);
|
|
920
|
+
const binputs = normInputs(b.inputs, `${id}.body.${bid}`, problems, schemaVersion);
|
|
826
921
|
const btimeout = normTimeoutSec(b.timeoutSec, `${where}.body node "${bid}"`, problems);
|
|
827
922
|
const bschema = normResultSchema(b.resultSchema, `${id}.body.${bid}`, problems);
|
|
828
923
|
const boverride = normOverride(b.override, `${where}.body node "${bid}"`, problems);
|
|
924
|
+
if (schemaVersion !== 2 && b.outputs !== void 0) {
|
|
925
|
+
problems.push(`${where}.body node "${bid}".outputs requires schemaVersion 2`);
|
|
926
|
+
}
|
|
927
|
+
const boutputs = schemaVersion === 2 ? normalizeArtifactOutputs(b.outputs, `${where}.body node "${bid}"`, problems) : void 0;
|
|
829
928
|
bodyNodes.push({
|
|
830
929
|
id: bid,
|
|
831
930
|
type: "goal",
|
|
@@ -834,6 +933,7 @@ function normLoopFields(n, id, problems) {
|
|
|
834
933
|
depends: bdepends,
|
|
835
934
|
override: boverride,
|
|
836
935
|
inputs: binputs,
|
|
936
|
+
outputs: boutputs,
|
|
837
937
|
timeoutSec: btimeout,
|
|
838
938
|
humanGate: null,
|
|
839
939
|
resultSchema: bschema
|
|
@@ -848,6 +948,13 @@ function normLoopFields(n, id, problems) {
|
|
|
848
948
|
problems.push(`${where}.body node "${bn.id}".inputs references unknown body node "${inp.from}"`);
|
|
849
949
|
} else if (!bn.depends.some((d) => d.from === inp.from)) {
|
|
850
950
|
problems.push(`${where}.body node "${bn.id}".inputs.from "${inp.from}" must also be in depends`);
|
|
951
|
+
} else if (inp.output !== void 0) {
|
|
952
|
+
const source = bodyNodes.find((candidate) => candidate.id === inp.from);
|
|
953
|
+
if (!source?.outputs || !Object.prototype.hasOwnProperty.call(source.outputs, inp.output)) {
|
|
954
|
+
problems.push(
|
|
955
|
+
`${where}.body node "${bn.id}".inputs output ${JSON.stringify(inp.output)} is not declared by source "${inp.from}"`
|
|
956
|
+
);
|
|
957
|
+
}
|
|
851
958
|
}
|
|
852
959
|
}
|
|
853
960
|
}
|
|
@@ -1073,7 +1180,7 @@ function normResultSchema(v, id, problems) {
|
|
|
1073
1180
|
}
|
|
1074
1181
|
return schema;
|
|
1075
1182
|
}
|
|
1076
|
-
function normInputs(v, id, problems) {
|
|
1183
|
+
function normInputs(v, id, problems, schemaVersion) {
|
|
1077
1184
|
if (v === void 0) return [];
|
|
1078
1185
|
if (!Array.isArray(v)) {
|
|
1079
1186
|
problems.push(`node "${id}".inputs must be an array`);
|
|
@@ -1086,9 +1193,29 @@ function normInputs(v, id, problems) {
|
|
|
1086
1193
|
problems.push(`node "${id}".inputs[${j}] must be { from: <nodeId>, select? }`);
|
|
1087
1194
|
continue;
|
|
1088
1195
|
}
|
|
1089
|
-
const extra = Object.keys(inp).filter((k) => k !== "from" && k !== "select");
|
|
1196
|
+
const extra = Object.keys(inp).filter((k) => k !== "from" && k !== "select" && k !== "output");
|
|
1090
1197
|
if (extra.length > 0) {
|
|
1091
|
-
problems.push(`node "${id}".inputs[${j}] has unsupported key(s): ${extra.join(", ")} (allowed: from, select)`);
|
|
1198
|
+
problems.push(`node "${id}".inputs[${j}] has unsupported key(s): ${extra.join(", ")} (allowed: from, select/output)`);
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
if (schemaVersion === 2) {
|
|
1202
|
+
if (inp.select !== void 0) {
|
|
1203
|
+
problems.push(`node "${id}".inputs[${j}].select is legacy-only; schemaVersion 2 must use output`);
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
if (inp.output === void 0) {
|
|
1207
|
+
out.push({ from: inp.from });
|
|
1208
|
+
continue;
|
|
1209
|
+
}
|
|
1210
|
+
if (typeof inp.output !== "string" || !SEGMENT_RE.test(inp.output)) {
|
|
1211
|
+
problems.push(`node "${id}".inputs[${j}].output must be a stable key matching ${SEGMENT_RE}`);
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
out.push({ from: inp.from, output: inp.output });
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
if (inp.output !== void 0) {
|
|
1218
|
+
problems.push(`node "${id}".inputs[${j}].output requires schemaVersion 2`);
|
|
1092
1219
|
continue;
|
|
1093
1220
|
}
|
|
1094
1221
|
if (inp.select === void 0) {
|