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/index.cjs
CHANGED
|
@@ -147,6 +147,79 @@ function isRecord(value) {
|
|
|
147
147
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// src/workflows/v3/artifact-contract.ts
|
|
151
|
+
var MANIFEST_FILE_KINDS = [
|
|
152
|
+
"markdown",
|
|
153
|
+
"json",
|
|
154
|
+
"text",
|
|
155
|
+
"code",
|
|
156
|
+
"log",
|
|
157
|
+
"binary",
|
|
158
|
+
"directory"
|
|
159
|
+
];
|
|
160
|
+
var MANIFEST_SUMMARY_MAX_BYTES = 4 * 1024;
|
|
161
|
+
var MANIFEST_PREVIEW_MAX_BYTES = 4 * 1024;
|
|
162
|
+
|
|
163
|
+
// src/workflows/v3/artifact-contract-declarations.ts
|
|
164
|
+
var V3_ARTIFACT_OUTPUT_KEY_RE = /^[A-Za-z0-9._-]+$/;
|
|
165
|
+
var V3_ARTIFACT_OUTPUT_MAX_COUNT = 32;
|
|
166
|
+
var V3_ARTIFACT_OUTPUT_MAX_BYTES = 4 * 1024;
|
|
167
|
+
function normalizeArtifactOutputs(value, where, problems) {
|
|
168
|
+
if (value === void 0) return void 0;
|
|
169
|
+
if (!isRecord2(value) || Object.keys(value).length === 0) {
|
|
170
|
+
problems.push(`${where}.outputs must be a non-empty object when present`);
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
const entries = Object.entries(value);
|
|
174
|
+
if (entries.length > V3_ARTIFACT_OUTPUT_MAX_COUNT) {
|
|
175
|
+
problems.push(`${where}.outputs has ${entries.length} entries (max ${V3_ARTIFACT_OUTPUT_MAX_COUNT})`);
|
|
176
|
+
}
|
|
177
|
+
if (Buffer.byteLength(JSON.stringify(value), "utf-8") > V3_ARTIFACT_OUTPUT_MAX_BYTES) {
|
|
178
|
+
problems.push(`${where}.outputs exceeds ${V3_ARTIFACT_OUTPUT_MAX_BYTES} serialized bytes`);
|
|
179
|
+
}
|
|
180
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
181
|
+
const paths = /* @__PURE__ */ new Set();
|
|
182
|
+
for (const [key, raw] of entries) {
|
|
183
|
+
const itemWhere = `${where}.outputs.${JSON.stringify(key)}`;
|
|
184
|
+
if (!V3_ARTIFACT_OUTPUT_KEY_RE.test(key)) {
|
|
185
|
+
problems.push(`${itemWhere} key must match ${V3_ARTIFACT_OUTPUT_KEY_RE}`);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (!isRecord2(raw)) {
|
|
189
|
+
problems.push(`${itemWhere} must be { path, kind }`);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const extra = Object.keys(raw).filter((field) => field !== "path" && field !== "kind");
|
|
193
|
+
if (extra.length > 0) {
|
|
194
|
+
problems.push(`${itemWhere} has unsupported key(s): ${extra.join(", ")} (allowed: path, kind)`);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!isPortableRelativeArtifactPath(raw.path)) {
|
|
198
|
+
problems.push(`${itemWhere}.path must be a portable relative path without '.', '..', or empty segments`);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (typeof raw.kind !== "string" || !MANIFEST_FILE_KINDS.includes(raw.kind)) {
|
|
202
|
+
problems.push(`${itemWhere}.kind must be one of ${MANIFEST_FILE_KINDS.join(" | ")}`);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (paths.has(raw.path)) {
|
|
206
|
+
problems.push(`${where}.outputs contains duplicate path ${JSON.stringify(raw.path)}`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
paths.add(raw.path);
|
|
210
|
+
out[key] = { path: raw.path, kind: raw.kind };
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
function isPortableRelativeArtifactPath(value) {
|
|
215
|
+
if (typeof value !== "string" || value.length === 0 || value.startsWith("/") || value.includes("\\") || value.includes("\0") || /^[A-Za-z]:/.test(value)) return false;
|
|
216
|
+
const segments = value.split("/");
|
|
217
|
+
return segments.every((segment) => segment !== "" && segment !== "." && segment !== "..");
|
|
218
|
+
}
|
|
219
|
+
function isRecord2(value) {
|
|
220
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
221
|
+
}
|
|
222
|
+
|
|
150
223
|
// src/workflows/v3/dag.ts
|
|
151
224
|
var NODE_KINDS = ["goal", "host", "loop"];
|
|
152
225
|
var V3_HOST_EXECUTORS = ["feishu-send", "feishu-reply", "botmux-schedule"];
|
|
@@ -196,6 +269,10 @@ function validateDag(raw) {
|
|
|
196
269
|
if (!isObject(raw)) {
|
|
197
270
|
throw new DagValidationError(["root must be a JSON object"]);
|
|
198
271
|
}
|
|
272
|
+
const schemaVersion = raw.schemaVersion === void 0 ? 1 : raw.schemaVersion;
|
|
273
|
+
if (schemaVersion !== 1 && schemaVersion !== 2) {
|
|
274
|
+
problems.push(`schemaVersion must be 1 or 2 (got ${JSON.stringify(raw.schemaVersion)})`);
|
|
275
|
+
}
|
|
199
276
|
if (typeof raw.runId !== "string" || !SEGMENT_RE.test(raw.runId)) {
|
|
200
277
|
problems.push(`runId must be a path-safe string matching ${SEGMENT_RE} (got ${JSON.stringify(raw.runId)})`);
|
|
201
278
|
}
|
|
@@ -232,9 +309,9 @@ function validateDag(raw) {
|
|
|
232
309
|
if (fromList.includes(id)) problems.push(`node "${id}" depends on itself`);
|
|
233
310
|
if (new Set(fromList).size !== fromList.length) problems.push(`node "${id}".depends has duplicates`);
|
|
234
311
|
const triggerRule = normTriggerRule(n.triggerRule, depends.length, `node "${id}"`, problems);
|
|
235
|
-
const inputs = normInputs(n.inputs, id, problems);
|
|
312
|
+
const inputs = normInputs(n.inputs, id, problems, schemaVersion === 2 ? 2 : 1);
|
|
236
313
|
if (type === "loop") {
|
|
237
|
-
const loopFields = normLoopFields(n, id, problems);
|
|
314
|
+
const loopFields = normLoopFields(n, id, problems, schemaVersion === 2 ? 2 : 1);
|
|
238
315
|
if (loopFields) {
|
|
239
316
|
nodes.push({
|
|
240
317
|
id,
|
|
@@ -251,6 +328,7 @@ function validateDag(raw) {
|
|
|
251
328
|
continue;
|
|
252
329
|
}
|
|
253
330
|
if (type === "host") {
|
|
331
|
+
if (n.outputs !== void 0) problems.push(`host node "${id}".outputs is not supported`);
|
|
254
332
|
if (n.goal !== void 0) problems.push(`host node "${id}".goal is not supported`);
|
|
255
333
|
if (n.bot !== void 0) problems.push(`host node "${id}".bot is not supported \u2014 host nodes do not spawn a CLI`);
|
|
256
334
|
if (n.override !== void 0) problems.push(`host node "${id}".override is not supported`);
|
|
@@ -326,6 +404,10 @@ function validateDag(raw) {
|
|
|
326
404
|
const humanGate = normHumanGate(n.humanGate, `node "${id}"`, problems);
|
|
327
405
|
const override = normOverride(n.override, `node "${id}"`, problems);
|
|
328
406
|
const revisitTo = normRevisitTo(n.revisitTo, id, problems);
|
|
407
|
+
if (schemaVersion !== 2 && n.outputs !== void 0) {
|
|
408
|
+
problems.push(`goal node "${id}".outputs requires schemaVersion 2`);
|
|
409
|
+
}
|
|
410
|
+
const outputs = schemaVersion === 2 ? normalizeArtifactOutputs(n.outputs, `goal node "${id}"`, problems) : void 0;
|
|
329
411
|
nodes.push({
|
|
330
412
|
id,
|
|
331
413
|
type,
|
|
@@ -335,6 +417,7 @@ function validateDag(raw) {
|
|
|
335
417
|
triggerRule,
|
|
336
418
|
override,
|
|
337
419
|
inputs,
|
|
420
|
+
outputs,
|
|
338
421
|
timeoutSec,
|
|
339
422
|
humanGate,
|
|
340
423
|
resultSchema,
|
|
@@ -350,6 +433,14 @@ function validateDag(raw) {
|
|
|
350
433
|
problems.push(`node "${node.id}".inputs references unknown node "${inp.from}"`);
|
|
351
434
|
} else if (!node.depends.some((d) => d.from === inp.from)) {
|
|
352
435
|
problems.push(`node "${node.id}".inputs.from "${inp.from}" must also be in depends`);
|
|
436
|
+
} else if (inp.output !== void 0) {
|
|
437
|
+
const source = nodes.find((candidate) => candidate.id === inp.from);
|
|
438
|
+
const outputs = source?.type === "loop" ? source.body?.nodes.find((bodyNode) => bodyNode.id === source.output?.from)?.outputs : source?.outputs;
|
|
439
|
+
if (!outputs || !Object.prototype.hasOwnProperty.call(outputs, inp.output)) {
|
|
440
|
+
problems.push(
|
|
441
|
+
`node "${node.id}".inputs output ${JSON.stringify(inp.output)} is not declared by source "${inp.from}"`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
353
444
|
}
|
|
354
445
|
}
|
|
355
446
|
if (node.type === "host") {
|
|
@@ -428,7 +519,11 @@ function validateDag(raw) {
|
|
|
428
519
|
}
|
|
429
520
|
}
|
|
430
521
|
if (problems.length > 0) throw new DagValidationError(problems);
|
|
431
|
-
const dag = {
|
|
522
|
+
const dag = {
|
|
523
|
+
...raw.schemaVersion !== void 0 ? { schemaVersion } : {},
|
|
524
|
+
runId: raw.runId,
|
|
525
|
+
nodes
|
|
526
|
+
};
|
|
432
527
|
topologicalOrder(dag);
|
|
433
528
|
return dag;
|
|
434
529
|
}
|
|
@@ -742,7 +837,7 @@ function downstreamCone(nodeId, nodes) {
|
|
|
742
837
|
function nodeByIdUnsafe(nodes, nodeId) {
|
|
743
838
|
return nodes.find((node) => node.id === nodeId);
|
|
744
839
|
}
|
|
745
|
-
function normLoopFields(n, id, problems) {
|
|
840
|
+
function normLoopFields(n, id, problems, schemaVersion) {
|
|
746
841
|
const where = `loop node "${id}"`;
|
|
747
842
|
const before = problems.length;
|
|
748
843
|
if (n.timeoutSec !== void 0) {
|
|
@@ -813,10 +908,14 @@ function normLoopFields(n, id, problems) {
|
|
|
813
908
|
const bFromList = bdepends.map((d) => d.from);
|
|
814
909
|
if (bFromList.includes(bid)) problems.push(`${where}.body node "${bid}" depends on itself`);
|
|
815
910
|
if (new Set(bFromList).size !== bFromList.length) problems.push(`${where}.body node "${bid}".depends has duplicates`);
|
|
816
|
-
const binputs = normInputs(b.inputs, `${id}.body.${bid}`, problems);
|
|
911
|
+
const binputs = normInputs(b.inputs, `${id}.body.${bid}`, problems, schemaVersion);
|
|
817
912
|
const btimeout = normTimeoutSec(b.timeoutSec, `${where}.body node "${bid}"`, problems);
|
|
818
913
|
const bschema = normResultSchema(b.resultSchema, `${id}.body.${bid}`, problems);
|
|
819
914
|
const boverride = normOverride(b.override, `${where}.body node "${bid}"`, problems);
|
|
915
|
+
if (schemaVersion !== 2 && b.outputs !== void 0) {
|
|
916
|
+
problems.push(`${where}.body node "${bid}".outputs requires schemaVersion 2`);
|
|
917
|
+
}
|
|
918
|
+
const boutputs = schemaVersion === 2 ? normalizeArtifactOutputs(b.outputs, `${where}.body node "${bid}"`, problems) : void 0;
|
|
820
919
|
bodyNodes.push({
|
|
821
920
|
id: bid,
|
|
822
921
|
type: "goal",
|
|
@@ -825,6 +924,7 @@ function normLoopFields(n, id, problems) {
|
|
|
825
924
|
depends: bdepends,
|
|
826
925
|
override: boverride,
|
|
827
926
|
inputs: binputs,
|
|
927
|
+
outputs: boutputs,
|
|
828
928
|
timeoutSec: btimeout,
|
|
829
929
|
humanGate: null,
|
|
830
930
|
resultSchema: bschema
|
|
@@ -839,6 +939,13 @@ function normLoopFields(n, id, problems) {
|
|
|
839
939
|
problems.push(`${where}.body node "${bn.id}".inputs references unknown body node "${inp.from}"`);
|
|
840
940
|
} else if (!bn.depends.some((d) => d.from === inp.from)) {
|
|
841
941
|
problems.push(`${where}.body node "${bn.id}".inputs.from "${inp.from}" must also be in depends`);
|
|
942
|
+
} else if (inp.output !== void 0) {
|
|
943
|
+
const source = bodyNodes.find((candidate) => candidate.id === inp.from);
|
|
944
|
+
if (!source?.outputs || !Object.prototype.hasOwnProperty.call(source.outputs, inp.output)) {
|
|
945
|
+
problems.push(
|
|
946
|
+
`${where}.body node "${bn.id}".inputs output ${JSON.stringify(inp.output)} is not declared by source "${inp.from}"`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
842
949
|
}
|
|
843
950
|
}
|
|
844
951
|
}
|
|
@@ -1064,7 +1171,7 @@ function normResultSchema(v, id, problems) {
|
|
|
1064
1171
|
}
|
|
1065
1172
|
return schema;
|
|
1066
1173
|
}
|
|
1067
|
-
function normInputs(v, id, problems) {
|
|
1174
|
+
function normInputs(v, id, problems, schemaVersion) {
|
|
1068
1175
|
if (v === void 0) return [];
|
|
1069
1176
|
if (!Array.isArray(v)) {
|
|
1070
1177
|
problems.push(`node "${id}".inputs must be an array`);
|
|
@@ -1077,9 +1184,29 @@ function normInputs(v, id, problems) {
|
|
|
1077
1184
|
problems.push(`node "${id}".inputs[${j}] must be { from: <nodeId>, select? }`);
|
|
1078
1185
|
continue;
|
|
1079
1186
|
}
|
|
1080
|
-
const extra = Object.keys(inp).filter((k) => k !== "from" && k !== "select");
|
|
1187
|
+
const extra = Object.keys(inp).filter((k) => k !== "from" && k !== "select" && k !== "output");
|
|
1081
1188
|
if (extra.length > 0) {
|
|
1082
|
-
problems.push(`node "${id}".inputs[${j}] has unsupported key(s): ${extra.join(", ")} (allowed: from, select)`);
|
|
1189
|
+
problems.push(`node "${id}".inputs[${j}] has unsupported key(s): ${extra.join(", ")} (allowed: from, select/output)`);
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
if (schemaVersion === 2) {
|
|
1193
|
+
if (inp.select !== void 0) {
|
|
1194
|
+
problems.push(`node "${id}".inputs[${j}].select is legacy-only; schemaVersion 2 must use output`);
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
if (inp.output === void 0) {
|
|
1198
|
+
out.push({ from: inp.from });
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
if (typeof inp.output !== "string" || !SEGMENT_RE.test(inp.output)) {
|
|
1202
|
+
problems.push(`node "${id}".inputs[${j}].output must be a stable key matching ${SEGMENT_RE}`);
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
out.push({ from: inp.from, output: inp.output });
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
if (inp.output !== void 0) {
|
|
1209
|
+
problems.push(`node "${id}".inputs[${j}].output requires schemaVersion 2`);
|
|
1083
1210
|
continue;
|
|
1084
1211
|
}
|
|
1085
1212
|
if (inp.select === void 0) {
|