@wenathlan/extension 1.1.49 → 1.1.51
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 +6 -5
- package/dist/controlflow.d.ts +220 -0
- package/dist/controlflow.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1708 -12
- package/dist/index.js.map +4 -4
- package/dist/memory.d.ts +40 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +16 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +85 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +309 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/workflow.d.ts +157 -0
- package/dist/workflow.d.ts.map +1 -0
- package/extension/dist/background.js +2127 -60
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +9 -4
- package/extension/dist/pagebridge.js.map +2 -2
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +48 -3
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +287 -3
- package/extension/dist/sidepanel.js.map +2 -2
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -333,6 +333,1201 @@ function teardowncdpsession(input) {
|
|
|
333
333
|
};
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
// workflow.ts
|
|
337
|
+
var workflowkinds = ["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars"];
|
|
338
|
+
function workflowstepof(value) {
|
|
339
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
340
|
+
const candidate = value;
|
|
341
|
+
if (typeof candidate.id !== "string" || !candidate.id.trim()) return void 0;
|
|
342
|
+
if (typeof candidate.kind !== "string" || !/^[a-z]+$/.test(candidate.kind)) return void 0;
|
|
343
|
+
if (typeof candidate.label !== "string" || !candidate.label.trim()) return void 0;
|
|
344
|
+
if (candidate.target !== void 0 && (typeof candidate.target !== "string" || !candidate.target)) return void 0;
|
|
345
|
+
if (candidate.value !== void 0 && typeof candidate.value !== "string") return void 0;
|
|
346
|
+
if (candidate.options !== void 0 && typeof candidate.options !== "string") return void 0;
|
|
347
|
+
const bindings = Array.isArray(candidate.bindings) ? candidate.bindings.flatMap((binding) => bindingof(binding) !== void 0 ? [bindingof(binding)] : []) : void 0;
|
|
348
|
+
if (candidate.bindings !== void 0 && bindings === void 0) return void 0;
|
|
349
|
+
if (Array.isArray(candidate.bindings) && bindings !== void 0 && bindings.length !== candidate.bindings.length) return void 0;
|
|
350
|
+
const expression = candidate.expression === void 0 ? void 0 : expressionof(candidate.expression);
|
|
351
|
+
if (candidate.expression !== void 0 && expression === void 0) return void 0;
|
|
352
|
+
const extract = candidate.extract === void 0 ? void 0 : regexruleof(candidate.extract);
|
|
353
|
+
if (candidate.extract !== void 0 && extract === void 0) return void 0;
|
|
354
|
+
return { id: candidate.id, kind: candidate.kind, label: candidate.label, ...candidate.target !== void 0 ? { target: candidate.target } : {}, ...candidate.value !== void 0 ? { value: candidate.value } : {}, ...candidate.options !== void 0 ? { options: candidate.options } : {}, ...bindings !== void 0 && bindings.length > 0 ? { bindings } : {}, ...expression !== void 0 ? { expression } : {}, ...extract !== void 0 ? { extract } : {} };
|
|
355
|
+
}
|
|
356
|
+
function blockinvocationof(value) {
|
|
357
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
358
|
+
const candidate = value;
|
|
359
|
+
if (typeof candidate.block !== "string" || !candidate.block.trim()) return void 0;
|
|
360
|
+
if (typeof candidate.label !== "string" || !candidate.label.trim()) return void 0;
|
|
361
|
+
return { block: candidate.block, label: candidate.label };
|
|
362
|
+
}
|
|
363
|
+
function workflowblockof(value) {
|
|
364
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
365
|
+
const candidate = value;
|
|
366
|
+
if (typeof candidate.name !== "string" || !/^[a-z][a-z0-9]*$/.test(candidate.name)) return void 0;
|
|
367
|
+
if (typeof candidate.label !== "string" || !candidate.label.trim()) return void 0;
|
|
368
|
+
if (!Array.isArray(candidate.steps)) return void 0;
|
|
369
|
+
const steps = [];
|
|
370
|
+
for (const entry of candidate.steps) {
|
|
371
|
+
const step = workflowstepof(entry);
|
|
372
|
+
if (step) {
|
|
373
|
+
steps.push(step);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const invocation = blockinvocationof(entry);
|
|
377
|
+
if (invocation) {
|
|
378
|
+
steps.push(invocation);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
return void 0;
|
|
382
|
+
}
|
|
383
|
+
return { name: candidate.name, label: candidate.label, steps };
|
|
384
|
+
}
|
|
385
|
+
function steptemplateof(value) {
|
|
386
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
387
|
+
const candidate = value;
|
|
388
|
+
if (typeof candidate.id !== "string" || !candidate.id.trim()) return void 0;
|
|
389
|
+
if (typeof candidate.name !== "string" || !candidate.name.trim()) return void 0;
|
|
390
|
+
if (typeof candidate.origin !== "string" || !candidate.origin.trim()) return void 0;
|
|
391
|
+
const step = workflowstepof(candidate.step);
|
|
392
|
+
if (!step) return void 0;
|
|
393
|
+
if (typeof candidate.sharedat !== "number" || !Number.isFinite(candidate.sharedat)) return void 0;
|
|
394
|
+
return { id: candidate.id, name: candidate.name, origin: candidate.origin, step, sharedat: candidate.sharedat };
|
|
395
|
+
}
|
|
396
|
+
function bindingof(value) {
|
|
397
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
398
|
+
const candidate = value;
|
|
399
|
+
if (typeof candidate.variable !== "string" || !/^[a-z][a-z0-9]*$/.test(candidate.variable)) return void 0;
|
|
400
|
+
if (!variablekinds.includes(candidate.kind)) return void 0;
|
|
401
|
+
if (typeof candidate.stepid !== "string" || !candidate.stepid.trim()) return void 0;
|
|
402
|
+
if (candidate.path !== void 0 && (typeof candidate.path !== "string" || !candidate.path.trim())) return void 0;
|
|
403
|
+
return { variable: candidate.variable, kind: candidate.kind, stepid: candidate.stepid, ...candidate.path !== void 0 ? { path: candidate.path } : {} };
|
|
404
|
+
}
|
|
405
|
+
var variablekinds = ["string", "number", "boolean", "list", "element"];
|
|
406
|
+
var expressionoperators = ["add", "subtract", "multiply", "divide", "modulo", "equal", "notequal", "less", "greater", "lessequal", "greaterequal", "and", "or", "not", "concat", "contains", "length"];
|
|
407
|
+
function expressionof(value) {
|
|
408
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
409
|
+
const candidate = value;
|
|
410
|
+
const left = operandof(candidate.left);
|
|
411
|
+
if (!left) return void 0;
|
|
412
|
+
const right = candidate.right === void 0 ? void 0 : operandof(candidate.right);
|
|
413
|
+
if (candidate.right !== void 0 && right === void 0) return void 0;
|
|
414
|
+
if (typeof candidate.operator !== "string" || !expressionoperators.includes(candidate.operator)) return void 0;
|
|
415
|
+
if (typeof candidate.result !== "string" || !/^[a-z][a-z0-9]*$/.test(candidate.result)) return void 0;
|
|
416
|
+
if (!variablekinds.includes(candidate.resultkind)) return void 0;
|
|
417
|
+
return { left, ...right !== void 0 ? { right } : {}, operator: candidate.operator, result: candidate.result, resultkind: candidate.resultkind };
|
|
418
|
+
}
|
|
419
|
+
function operandof(value) {
|
|
420
|
+
if (value === void 0) return void 0;
|
|
421
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return { literal: value };
|
|
422
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
423
|
+
const candidate = value;
|
|
424
|
+
if (typeof candidate.ref === "string" && /^[a-z][a-z0-9]*$/.test(candidate.ref)) return { ref: candidate.ref };
|
|
425
|
+
if (typeof candidate.literal === "string" || typeof candidate.literal === "number" || typeof candidate.literal === "boolean") return { literal: candidate.literal };
|
|
426
|
+
return void 0;
|
|
427
|
+
}
|
|
428
|
+
function regexruleof(value) {
|
|
429
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
430
|
+
const candidate = value;
|
|
431
|
+
if (typeof candidate.pattern !== "string" || !candidate.pattern.trim()) return void 0;
|
|
432
|
+
if (typeof candidate.flags !== "string" || !/^[dgimsuvy]*$/.test(candidate.flags)) return void 0;
|
|
433
|
+
const groups = Array.isArray(candidate.groups) ? candidate.groups.flatMap((group) => typeof group === "string" && /^[a-z][a-z0-9]*$/.test(group) ? [group] : []) : [];
|
|
434
|
+
if (candidate.groups !== void 0 && groups.length !== candidate.groups.length) return void 0;
|
|
435
|
+
return { pattern: candidate.pattern, flags: candidate.flags, groups };
|
|
436
|
+
}
|
|
437
|
+
function expandblocks(steps, blocks) {
|
|
438
|
+
const byname = new Map(blocks.map((block) => [block.name, block]));
|
|
439
|
+
const expanded = [];
|
|
440
|
+
const visit = (entries, path, inside) => {
|
|
441
|
+
for (const entry of entries) {
|
|
442
|
+
if ("kind" in entry && "label" in entry && !("block" in entry)) {
|
|
443
|
+
expanded.push(inside === void 0 ? entry : { ...entry, block: inside });
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
const invocation = blockinvocationof(entry);
|
|
447
|
+
if (!invocation) throw new Error("The step list entry is neither a reviewed step nor a block invocation.");
|
|
448
|
+
if (path.includes(invocation.block)) throw new Error(`The block ${invocation.block} recurs inside itself and cannot expand.`);
|
|
449
|
+
const block = byname.get(invocation.block);
|
|
450
|
+
if (!block) throw new Error(`The block ${invocation.block} is not defined in the workflow.`);
|
|
451
|
+
visit(block.steps, [...path, invocation.block], invocation.block);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
visit(steps, [], void 0);
|
|
455
|
+
if (expanded.length === 0) throw new Error("A workflow needs at least one executable step after block expansion.");
|
|
456
|
+
return expanded;
|
|
457
|
+
}
|
|
458
|
+
function composeworkflow(input) {
|
|
459
|
+
if (typeof input.name !== "string" || !input.name.trim()) throw new Error("The workflow name must be a non-empty string.");
|
|
460
|
+
if (typeof input.version !== "number" || !Number.isInteger(input.version) || input.version < 1) throw new Error("The workflow version must be a positive integer.");
|
|
461
|
+
if (!Array.isArray(input.origins) || input.origins.length === 0) throw new Error("A workflow needs at least one granted HTTPS origin.");
|
|
462
|
+
const origins = input.origins.map((origin) => {
|
|
463
|
+
try {
|
|
464
|
+
return new URL(origin).origin;
|
|
465
|
+
} catch {
|
|
466
|
+
throw new Error(`The workflow origin ${origin} is not a valid url.`);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
if (origins.some((origin) => !origin.startsWith("https://"))) throw new Error("Workflow origins must use HTTPS.");
|
|
470
|
+
const blocks = input.blocks ?? [];
|
|
471
|
+
if (blocks.some((block, index) => blocks.findIndex((other) => other.name === block.name) !== index)) throw new Error("Workflow block names must stay unique.");
|
|
472
|
+
for (const entry of input.steps) {
|
|
473
|
+
if ("kind" in entry && "label" in entry && !("block" in entry)) {
|
|
474
|
+
if (input.kindallowed && !input.kindallowed(entry.kind)) throw new Error(`The workflow step kind ${entry.kind} is not a reviewed action kind.`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
for (const block of blocks) for (const entry of block.steps) {
|
|
478
|
+
if ("kind" in entry && "label" in entry && !("block" in entry) && input.kindallowed && !input.kindallowed(entry.kind)) throw new Error(`The workflow step kind ${entry.kind} inside block ${block.name} is not a reviewed action kind.`);
|
|
479
|
+
}
|
|
480
|
+
const steps = expandblocks(input.steps, blocks);
|
|
481
|
+
for (const step of steps) {
|
|
482
|
+
if (input.kindallowed && !input.kindallowed(step.kind)) throw new Error(`The workflow step kind ${step.kind} is not a reviewed action kind.`);
|
|
483
|
+
if (iscontrolflowkind(step.kind)) {
|
|
484
|
+
validatecontrolpayload(step);
|
|
485
|
+
for (const child of controlsteps(step)) {
|
|
486
|
+
if (input.kindallowed && !input.kindallowed(child.kind)) throw new Error(`The workflow step kind ${child.kind} inside the control payload of ${step.id} is not a reviewed action kind.`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (step.bindings) for (const binding of step.bindings) {
|
|
490
|
+
if (!steps.some((other) => other.id === binding.stepid)) throw new Error(`The binding of ${binding.variable} references the unknown step ${binding.stepid}.`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const riskof = input.riskof ?? (() => "sensitive");
|
|
494
|
+
const gradedkinds = steps.flatMap((step) => [step.kind, ...controlsteps(step).map((child) => child.kind)]);
|
|
495
|
+
const risk = gradedkinds.some((kind) => riskof(kind) === "sensitive") ? "sensitive" : gradedkinds.some((kind) => riskof(kind) === "interaction") ? "interaction" : "read";
|
|
496
|
+
const record2 = { id: input.id ?? crypto.randomUUID(), name: input.name, version: input.version, origins: [...new Set(origins)], steps, blocks, risk, createdat: input.now };
|
|
497
|
+
return deepfreeze(record2);
|
|
498
|
+
}
|
|
499
|
+
function deepfreeze(record2) {
|
|
500
|
+
for (const step of record2.steps) Object.freeze(step);
|
|
501
|
+
for (const block of record2.blocks) for (const entry of block.steps) if ("kind" in entry && "label" in entry && !("block" in entry)) Object.freeze(entry);
|
|
502
|
+
Object.freeze(record2.blocks);
|
|
503
|
+
Object.freeze(record2.steps);
|
|
504
|
+
return Object.freeze(record2);
|
|
505
|
+
}
|
|
506
|
+
function validateworkflow(record2, options) {
|
|
507
|
+
if (record2.steps.length === 0) return { allowed: false, reason: "A workflow needs at least one reviewed step." };
|
|
508
|
+
const defined = new Set(options?.inputs ?? []);
|
|
509
|
+
const byid = new Map(record2.steps.map((step, index) => [step.id, { step, index }]));
|
|
510
|
+
for (let index = 0; index < record2.steps.length; index += 1) {
|
|
511
|
+
const step = record2.steps[index];
|
|
512
|
+
if (options?.kindallowed && !options.kindallowed(step.kind)) return { allowed: false, reason: `The workflow step kind ${step.kind} is not a reviewed action kind.` };
|
|
513
|
+
if (step.bindings) for (const binding of step.bindings) {
|
|
514
|
+
const source = byid.get(binding.stepid);
|
|
515
|
+
if (!source) return { allowed: false, reason: `The binding of ${binding.variable} references the unknown step ${binding.stepid}.` };
|
|
516
|
+
if (source.index >= index) return { allowed: false, reason: `The binding of ${binding.variable} must link an earlier step than ${step.id}.` };
|
|
517
|
+
defined.add(binding.variable);
|
|
518
|
+
}
|
|
519
|
+
if (step.expression) {
|
|
520
|
+
for (const operand of [step.expression.left, step.expression.right]) {
|
|
521
|
+
if (operand?.ref && !defined.has(operand.ref)) return { allowed: false, reason: `The expression of step ${step.id} references the undefined variable ${operand.ref}.` };
|
|
522
|
+
}
|
|
523
|
+
defined.add(step.expression.result);
|
|
524
|
+
}
|
|
525
|
+
if (step.extract) for (const group of step.extract.groups) defined.add(group);
|
|
526
|
+
}
|
|
527
|
+
return { allowed: true };
|
|
528
|
+
}
|
|
529
|
+
function pushscope(scopes, name, parent) {
|
|
530
|
+
return [...scopes, { name, variables: [], ...parent !== void 0 ? { parent } : {} }];
|
|
531
|
+
}
|
|
532
|
+
function popscope(scopes) {
|
|
533
|
+
if (scopes.length === 0) return scopes;
|
|
534
|
+
return scopes.slice(0, -1);
|
|
535
|
+
}
|
|
536
|
+
function resolvevariable(scopes, name) {
|
|
537
|
+
for (let index = scopes.length - 1; index >= 0; index -= 1) {
|
|
538
|
+
const scope = scopes[index];
|
|
539
|
+
const found = scope.variables.find((variable) => variable.name === name);
|
|
540
|
+
if (found) return found;
|
|
541
|
+
if (scope.parent === void 0) continue;
|
|
542
|
+
const parentindex = scopes.findIndex((candidate) => candidate.name === scope.parent);
|
|
543
|
+
if (parentindex >= 0 && parentindex < index) {
|
|
544
|
+
const inherited = resolvevariable([scopes[parentindex]], name);
|
|
545
|
+
if (inherited) return inherited;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return void 0;
|
|
549
|
+
}
|
|
550
|
+
function setvariable(scopes, name, kind, value, now) {
|
|
551
|
+
if (scopes.length === 0) scopes = [{ name: "root", variables: [] }];
|
|
552
|
+
const target = scopes[scopes.length - 1];
|
|
553
|
+
const variables = [...target.variables.filter((variable) => variable.name !== name), { name, kind, value, setat: now }];
|
|
554
|
+
return [...scopes.slice(0, -1), { ...target, variables }];
|
|
555
|
+
}
|
|
556
|
+
function coercevariable(value, kind) {
|
|
557
|
+
if (kind === "number") {
|
|
558
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
|
|
559
|
+
if (!Number.isFinite(parsed)) throw new Error("The bound value is not a finite number.");
|
|
560
|
+
return parsed;
|
|
561
|
+
}
|
|
562
|
+
if (kind === "boolean") {
|
|
563
|
+
if (typeof value === "boolean") return value;
|
|
564
|
+
if (value === "true") return true;
|
|
565
|
+
if (value === "false") return false;
|
|
566
|
+
throw new Error("The bound value is not a boolean.");
|
|
567
|
+
}
|
|
568
|
+
if (kind === "list") {
|
|
569
|
+
if (Array.isArray(value)) return value.map((item) => String(item));
|
|
570
|
+
if (typeof value === "string") return value.length === 0 ? [] : value.split(",");
|
|
571
|
+
throw new Error("The bound value is not a list.");
|
|
572
|
+
}
|
|
573
|
+
if (kind === "element") {
|
|
574
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
575
|
+
throw new Error("The bound value is not an element reference.");
|
|
576
|
+
}
|
|
577
|
+
if (typeof value === "string") return value;
|
|
578
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
579
|
+
throw new Error("The bound value is not a string.");
|
|
580
|
+
}
|
|
581
|
+
function outcomedetail(outcome, path) {
|
|
582
|
+
if (!path) return outcome.summary;
|
|
583
|
+
let current = outcome.details ?? {};
|
|
584
|
+
for (const segment of path.split(".")) {
|
|
585
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) return void 0;
|
|
586
|
+
current = current[segment];
|
|
587
|
+
}
|
|
588
|
+
return current;
|
|
589
|
+
}
|
|
590
|
+
function bindvariables(scopes, bindings, outputs, now) {
|
|
591
|
+
let current = scopes;
|
|
592
|
+
const produced = [];
|
|
593
|
+
for (const binding of bindings) {
|
|
594
|
+
const outcome = outputs[binding.stepid];
|
|
595
|
+
if (!outcome) continue;
|
|
596
|
+
const raw = outcomedetail(outcome, binding.path);
|
|
597
|
+
if (raw === void 0) throw new Error(`The binding of ${binding.variable} found no value at ${binding.path ?? "the summary"} of step ${binding.stepid}.`);
|
|
598
|
+
current = setvariable(current, binding.variable, binding.kind, coercevariable(raw, binding.kind), now);
|
|
599
|
+
produced.push(binding.variable);
|
|
600
|
+
}
|
|
601
|
+
return { scopes: current, produced };
|
|
602
|
+
}
|
|
603
|
+
function operandvalue(operand, scopes) {
|
|
604
|
+
if (operand.ref !== void 0) {
|
|
605
|
+
const resolved = resolvevariable(scopes, operand.ref);
|
|
606
|
+
if (!resolved) throw new Error(`The expression references the undefined variable ${operand.ref}.`);
|
|
607
|
+
return resolved.value;
|
|
608
|
+
}
|
|
609
|
+
if (operand.literal === void 0) throw new Error("The expression operand needs a variable reference or a literal.");
|
|
610
|
+
return operand.literal;
|
|
611
|
+
}
|
|
612
|
+
function expressioneval(expression, scopes) {
|
|
613
|
+
const left = operandvalue(expression.left, scopes);
|
|
614
|
+
const right = expression.right === void 0 ? void 0 : operandvalue(expression.right, scopes);
|
|
615
|
+
const operand = (value) => {
|
|
616
|
+
if (Array.isArray(value)) throw new Error("The expression operand is a list and needs the contains or length operator.");
|
|
617
|
+
if (value === void 0) throw new Error("The expression operand is missing.");
|
|
618
|
+
return value;
|
|
619
|
+
};
|
|
620
|
+
const numbervalue = (value) => {
|
|
621
|
+
const primitive = operand(value);
|
|
622
|
+
if (typeof primitive === "number") return primitive;
|
|
623
|
+
if (typeof primitive === "string" && primitive.trim() !== "") {
|
|
624
|
+
const parsed = Number(primitive);
|
|
625
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
626
|
+
}
|
|
627
|
+
throw new Error("The arithmetic operand is not a number.");
|
|
628
|
+
};
|
|
629
|
+
const booleanvalue = (value) => {
|
|
630
|
+
const primitive = operand(value);
|
|
631
|
+
if (typeof primitive === "boolean") return primitive;
|
|
632
|
+
throw new Error("The logic operand is not a boolean.");
|
|
633
|
+
};
|
|
634
|
+
const stringvalue = (value) => {
|
|
635
|
+
const primitive = operand(value);
|
|
636
|
+
if (typeof primitive === "string") return primitive;
|
|
637
|
+
if (typeof primitive === "number" || typeof primitive === "boolean") return String(primitive);
|
|
638
|
+
throw new Error("The text operand is not a string.");
|
|
639
|
+
};
|
|
640
|
+
switch (expression.operator) {
|
|
641
|
+
case "add":
|
|
642
|
+
return numbervalue(left) + numbervalue(right);
|
|
643
|
+
case "subtract":
|
|
644
|
+
return numbervalue(left) - numbervalue(right);
|
|
645
|
+
case "multiply":
|
|
646
|
+
return numbervalue(left) * numbervalue(right);
|
|
647
|
+
case "divide": {
|
|
648
|
+
const divisor = numbervalue(right);
|
|
649
|
+
if (divisor === 0) throw new Error("The expression divides by zero.");
|
|
650
|
+
return numbervalue(left) / divisor;
|
|
651
|
+
}
|
|
652
|
+
case "modulo": {
|
|
653
|
+
const divisor = numbervalue(right);
|
|
654
|
+
if (divisor === 0) throw new Error("The expression divides by zero.");
|
|
655
|
+
return numbervalue(left) % divisor;
|
|
656
|
+
}
|
|
657
|
+
case "equal":
|
|
658
|
+
return left === right;
|
|
659
|
+
case "notequal":
|
|
660
|
+
return left !== right;
|
|
661
|
+
case "less":
|
|
662
|
+
return numbervalue(left) < numbervalue(right);
|
|
663
|
+
case "greater":
|
|
664
|
+
return numbervalue(left) > numbervalue(right);
|
|
665
|
+
case "lessequal":
|
|
666
|
+
return numbervalue(left) <= numbervalue(right);
|
|
667
|
+
case "greaterequal":
|
|
668
|
+
return numbervalue(left) >= numbervalue(right);
|
|
669
|
+
case "and":
|
|
670
|
+
return booleanvalue(left) && booleanvalue(right);
|
|
671
|
+
case "or":
|
|
672
|
+
return booleanvalue(left) || booleanvalue(right);
|
|
673
|
+
case "not":
|
|
674
|
+
return !booleanvalue(left);
|
|
675
|
+
case "concat":
|
|
676
|
+
return `${stringvalue(left)}${stringvalue(right)}`;
|
|
677
|
+
case "contains": {
|
|
678
|
+
if (Array.isArray(left)) return left.includes(stringvalue(right));
|
|
679
|
+
return stringvalue(left).includes(stringvalue(right));
|
|
680
|
+
}
|
|
681
|
+
case "length": {
|
|
682
|
+
if (Array.isArray(left)) return left.length;
|
|
683
|
+
return stringvalue(left).length;
|
|
684
|
+
}
|
|
685
|
+
default:
|
|
686
|
+
throw new Error("The reviewed expression operator is unknown.");
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function regexextract(rule, text2, now) {
|
|
690
|
+
const pattern = new RegExp(rule.pattern, rule.flags);
|
|
691
|
+
const match = pattern.exec(text2);
|
|
692
|
+
if (!match) return { matched: false, variables: [] };
|
|
693
|
+
const variables = [];
|
|
694
|
+
for (const group of rule.groups) {
|
|
695
|
+
const value = match.groups?.[group];
|
|
696
|
+
variables.push({ name: group, kind: "string", value: typeof value === "string" ? value : "", setat: now });
|
|
697
|
+
}
|
|
698
|
+
return { matched: true, variables };
|
|
699
|
+
}
|
|
700
|
+
function waitelementplan(wait) {
|
|
701
|
+
if (wait.timeout <= 0 || wait.poll <= 0) return { probes: 1, lastwait: 0 };
|
|
702
|
+
const probes = Math.floor(wait.timeout / wait.poll) + 1;
|
|
703
|
+
return { probes, lastwait: wait.timeout % wait.poll };
|
|
704
|
+
}
|
|
705
|
+
function delayjitter(delay, seed) {
|
|
706
|
+
if (delay.jitter <= 0) return Math.max(0, delay.base);
|
|
707
|
+
const sample = seededrandom(seed);
|
|
708
|
+
return Math.max(0, delay.base - delay.jitter / 2 + sample * delay.jitter);
|
|
709
|
+
}
|
|
710
|
+
function seededrandom(seed) {
|
|
711
|
+
let state = seed >>> 0;
|
|
712
|
+
state ^= state >>> 16;
|
|
713
|
+
state = Math.imul(state, 2246822507);
|
|
714
|
+
state ^= state >>> 13;
|
|
715
|
+
state = Math.imul(state, 3266489909);
|
|
716
|
+
state ^= state >>> 16;
|
|
717
|
+
state = state >>> 0 || 1;
|
|
718
|
+
state ^= state << 13;
|
|
719
|
+
state >>>= 0;
|
|
720
|
+
state ^= state >> 17;
|
|
721
|
+
state ^= state << 5;
|
|
722
|
+
state >>>= 0;
|
|
723
|
+
return state / 4294967296;
|
|
724
|
+
}
|
|
725
|
+
function newworkflowrun(input) {
|
|
726
|
+
return { id: input.id ?? crypto.randomUUID(), workflowid: input.workflowid, state: "pending", cursor: 0, startedat: input.now, ...input.dryrun === true ? { dryrun: true } : {} };
|
|
727
|
+
}
|
|
728
|
+
function pauserun(run, now) {
|
|
729
|
+
if (run.state !== "running") throw new Error("Only a running workflow can pause.");
|
|
730
|
+
return { ...run, state: "paused", pausedat: now };
|
|
731
|
+
}
|
|
732
|
+
function cancelrun(run, reason, now) {
|
|
733
|
+
if (run.state === "done" || run.state === "cancelled") return run;
|
|
734
|
+
return { ...run, state: "cancelled", cancelreason: reason, endedat: now };
|
|
735
|
+
}
|
|
736
|
+
function interpolate(text2, scopes) {
|
|
737
|
+
const consumed = [];
|
|
738
|
+
const resolved = text2.replace(/\$\{([a-z][a-z0-9]*)\}/g, (_whole, name) => {
|
|
739
|
+
const variable = resolvevariable(scopes, name);
|
|
740
|
+
if (!variable) throw new Error(`The step references the undefined variable ${name}.`);
|
|
741
|
+
consumed.push(name);
|
|
742
|
+
return Array.isArray(variable.value) ? variable.value.join(",") : String(variable.value);
|
|
743
|
+
});
|
|
744
|
+
return { text: resolved, consumed };
|
|
745
|
+
}
|
|
746
|
+
function runlogof(step, state, startedat, duration, summary, extra) {
|
|
747
|
+
return { stepid: step.id, label: step.label, state, startedat, duration, summary, ...extra.block !== void 0 ? { block: extra.block } : {}, ...extra.consumed !== void 0 && extra.consumed.length > 0 ? { consumed: extra.consumed } : {}, ...extra.produced !== void 0 && extra.produced.length > 0 ? { produced: extra.produced } : {}, ...extra.checkpoint === true ? { checkpoint: true } : {}, ...extra.details !== void 0 ? { details: extra.details } : {} };
|
|
748
|
+
}
|
|
749
|
+
async function runstep(input) {
|
|
750
|
+
const startedat = input.now;
|
|
751
|
+
let scopes = input.scopes;
|
|
752
|
+
const consumed = [];
|
|
753
|
+
if (input.step.bindings) {
|
|
754
|
+
const bound = bindvariables(scopes, input.step.bindings.filter((binding) => input.outputs[binding.stepid] !== void 0), input.outputs, input.now);
|
|
755
|
+
scopes = bound.scopes;
|
|
756
|
+
}
|
|
757
|
+
let produced = [];
|
|
758
|
+
try {
|
|
759
|
+
if (input.step.expression) {
|
|
760
|
+
const value2 = expressioneval(input.step.expression, scopes);
|
|
761
|
+
scopes = setvariable(scopes, input.step.expression.result, input.step.expression.resultkind, coercevariable(value2, input.step.expression.resultkind), input.now);
|
|
762
|
+
produced = [...produced, input.step.expression.result];
|
|
763
|
+
}
|
|
764
|
+
let stepvalue = input.step.value;
|
|
765
|
+
if (input.step.extract) {
|
|
766
|
+
const text2 = stepvalue ?? "";
|
|
767
|
+
const interpolated = interpolate(text2, scopes);
|
|
768
|
+
consumed.push(...interpolated.consumed);
|
|
769
|
+
const extraction = regexextract(input.step.extract, interpolated.text, input.now);
|
|
770
|
+
if (extraction.matched) {
|
|
771
|
+
for (const variable of extraction.variables) scopes = setvariable(scopes, variable.name, "string", variable.value, input.now);
|
|
772
|
+
produced = [...produced, ...extraction.variables.map((variable) => variable.name)];
|
|
773
|
+
}
|
|
774
|
+
stepvalue = interpolated.text;
|
|
775
|
+
}
|
|
776
|
+
const controlled = iscontrolflowkind(input.step.kind);
|
|
777
|
+
const target = !controlled && input.step.target !== void 0 ? interpolate(input.step.target, scopes) : void 0;
|
|
778
|
+
if (target) consumed.push(...target.consumed);
|
|
779
|
+
const value = !controlled && stepvalue !== void 0 ? interpolate(stepvalue, scopes) : void 0;
|
|
780
|
+
if (value) consumed.push(...value.consumed);
|
|
781
|
+
const options = !controlled && input.step.options !== void 0 ? interpolate(input.step.options, scopes) : void 0;
|
|
782
|
+
if (options) consumed.push(...options.consumed);
|
|
783
|
+
const dispatchable = { ...input.step, ...target !== void 0 ? { target: target.text } : {}, ...value !== void 0 ? { value: value.text } : {}, ...options !== void 0 ? { options: options.text } : {} };
|
|
784
|
+
const output = await input.execute(dispatchable, { scopes, outputs: input.outputs, ...input.block !== void 0 ? { block: input.block } : {} });
|
|
785
|
+
if (output.scopes !== void 0) scopes = output.scopes;
|
|
786
|
+
const childlog = output.log;
|
|
787
|
+
if (input.step.bindings) {
|
|
788
|
+
const bound = bindvariables(scopes, input.step.bindings, { ...input.outputs, [input.step.id]: { stepid: input.step.id, ok: output.ok, summary: output.summary, ...output.details !== void 0 ? { details: output.details } : {}, at: input.now } }, input.now);
|
|
789
|
+
scopes = bound.scopes;
|
|
790
|
+
produced = [.../* @__PURE__ */ new Set([...produced, ...bound.produced])];
|
|
791
|
+
}
|
|
792
|
+
const duration = Date.now() - startedat;
|
|
793
|
+
return { scopes, log: runlogof(input.step, output.ok ? "done" : "failed", startedat, duration, output.summary, { ...input.block !== void 0 ? { block: input.block } : {}, ...consumed.length > 0 ? { consumed } : {}, ...produced.length > 0 ? { produced } : {}, ...output.details !== void 0 ? { details: output.details } : {}, ...output.ok ? { checkpoint: true } : {} }), ...childlog !== void 0 ? { childlog } : {}, output };
|
|
794
|
+
} catch (error) {
|
|
795
|
+
const duration = Date.now() - startedat;
|
|
796
|
+
const summary = error instanceof Error ? error.message : String(error);
|
|
797
|
+
return { scopes, log: runlogof(input.step, "failed", startedat, duration, summary, { ...input.block !== void 0 ? { block: input.block } : {}, ...consumed.length > 0 ? { consumed } : {} }), output: { ok: false, summary } };
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async function runworkflow(input) {
|
|
801
|
+
if (input.gates && !input.gates.sessionactive) throw new Error("The workflow refuses to run outside an approved session.");
|
|
802
|
+
if (input.gates && !input.gates.planapproved) throw new Error("The workflow refuses to run without the approved plan review.");
|
|
803
|
+
if (input.gates) for (const origin of input.record.origins) {
|
|
804
|
+
if (!input.gates.origingranted(origin)) throw new Error(`The workflow origin ${origin} falls outside the session grants.`);
|
|
805
|
+
}
|
|
806
|
+
if (input.run.state === "done" || input.run.state === "failed" || input.run.state === "cancelled") throw new Error(`The workflow run is already ${input.run.state}.`);
|
|
807
|
+
const { pausedat, ...resumed } = input.run;
|
|
808
|
+
void pausedat;
|
|
809
|
+
let run = input.run.state === "paused" ? { ...resumed, state: "running" } : { ...input.run, state: "running" };
|
|
810
|
+
let scopes = input.scopes ?? [{ name: "root", variables: [] }];
|
|
811
|
+
const log = [...input.log ?? []];
|
|
812
|
+
const outputs = { ...input.outputs ?? {} };
|
|
813
|
+
let activeblock;
|
|
814
|
+
for (let index = run.cursor; index < input.record.steps.length; index += 1) {
|
|
815
|
+
const step = input.record.steps[index];
|
|
816
|
+
if (step.block !== void 0 && step.block !== activeblock) {
|
|
817
|
+
scopes = pushscope(scopes, step.block, scopes[scopes.length - 1].name);
|
|
818
|
+
activeblock = step.block;
|
|
819
|
+
} else if (step.block === void 0 && activeblock !== void 0) {
|
|
820
|
+
while (scopes.length > 1) scopes = popscope(scopes);
|
|
821
|
+
activeblock = void 0;
|
|
822
|
+
}
|
|
823
|
+
const executed = await runstep({ step, scopes, outputs, execute: input.execute, now: Date.now(), ...step.block !== void 0 ? { block: step.block } : {} });
|
|
824
|
+
scopes = executed.scopes;
|
|
825
|
+
if (executed.childlog !== void 0) log.push(...executed.childlog);
|
|
826
|
+
log.push(executed.log);
|
|
827
|
+
outputs[step.id] = { stepid: step.id, ok: executed.output.ok, summary: executed.output.summary, ...executed.output.details !== void 0 ? { details: executed.output.details } : {}, at: Date.now() };
|
|
828
|
+
if (!executed.output.ok) {
|
|
829
|
+
run = { ...run, state: "failed", endedat: Date.now(), failreason: executed.output.summary };
|
|
830
|
+
return { run, scopes, log, outputs };
|
|
831
|
+
}
|
|
832
|
+
run = { ...run, cursor: index + 1 };
|
|
833
|
+
if (input.oncheckpoint) await input.oncheckpoint({ run, scopes, log });
|
|
834
|
+
}
|
|
835
|
+
run = { ...run, state: "done", endedat: Date.now() };
|
|
836
|
+
return { run, scopes, log, outputs };
|
|
837
|
+
}
|
|
838
|
+
function dryrunworkflow(input) {
|
|
839
|
+
const run = { ...input.run, state: "running", ...input.run.dryrun === true ? { dryrun: true } : { dryrun: true } };
|
|
840
|
+
let scopes = input.scopes ?? [{ name: "root", variables: [] }];
|
|
841
|
+
const log = [...input.log ?? []];
|
|
842
|
+
for (let index = run.cursor; index < input.record.steps.length; index += 1) {
|
|
843
|
+
const step = input.record.steps[index];
|
|
844
|
+
const summary = input.projection(step);
|
|
845
|
+
const entry = summary === void 0 ? runlogof(step, "refused", input.now, 0, `The ${step.kind} step has no read only projection and the dry run refuses it.`, { ...step.block !== void 0 ? { block: step.block } : {} }) : runlogof(step, "done", input.now, 0, summary, { ...step.block !== void 0 ? { block: step.block } : {} });
|
|
846
|
+
log.push(entry);
|
|
847
|
+
scopes = setvariable(scopes, `${step.id}outcome`, "boolean", entry.state === "done", input.now);
|
|
848
|
+
}
|
|
849
|
+
return { run: { ...run, state: "done", cursor: input.record.steps.length, endedat: input.now }, scopes, log };
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// controlflow.ts
|
|
853
|
+
var controlflowkinds = ["condition", "branch", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"];
|
|
854
|
+
var defaultloopbound = 1e3;
|
|
855
|
+
function iscontrolflowkind(kind) {
|
|
856
|
+
return controlflowkinds.includes(kind);
|
|
857
|
+
}
|
|
858
|
+
var cancellederror = class extends Error {
|
|
859
|
+
constructor(message) {
|
|
860
|
+
super(message);
|
|
861
|
+
this.name = "cancellederror";
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
function controlname(value) {
|
|
865
|
+
return typeof value === "string" && /^[a-z][a-z0-9]*$/.test(value) ? value : void 0;
|
|
866
|
+
}
|
|
867
|
+
function controlstepslist(value) {
|
|
868
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
869
|
+
const steps = [];
|
|
870
|
+
for (const entry of value) {
|
|
871
|
+
const parsed = workflowstepof(entry);
|
|
872
|
+
if (!parsed) return void 0;
|
|
873
|
+
steps.push(parsed);
|
|
874
|
+
}
|
|
875
|
+
return steps;
|
|
876
|
+
}
|
|
877
|
+
function controlbound(value) {
|
|
878
|
+
if (value === void 0) return void 0;
|
|
879
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
880
|
+
}
|
|
881
|
+
function conditionof(value) {
|
|
882
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
883
|
+
const candidate = value;
|
|
884
|
+
const expression = expressionof(candidate.expression);
|
|
885
|
+
if (!expression) return void 0;
|
|
886
|
+
if (expression.resultkind !== "boolean") return void 0;
|
|
887
|
+
return { expression };
|
|
888
|
+
}
|
|
889
|
+
function elseof(value) {
|
|
890
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
891
|
+
const candidate = value;
|
|
892
|
+
const name = controlname(candidate.name);
|
|
893
|
+
if (!name) return void 0;
|
|
894
|
+
if (candidate.when !== void 0) return void 0;
|
|
895
|
+
if (!Array.isArray(candidate.steps)) return void 0;
|
|
896
|
+
const steps = [];
|
|
897
|
+
for (const entry of candidate.steps) {
|
|
898
|
+
const parsed = workflowstepof(entry);
|
|
899
|
+
if (!parsed) return void 0;
|
|
900
|
+
steps.push(parsed);
|
|
901
|
+
}
|
|
902
|
+
return { name, steps };
|
|
903
|
+
}
|
|
904
|
+
function branchof(value) {
|
|
905
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
906
|
+
const candidate = value;
|
|
907
|
+
if (!Array.isArray(candidate.paths) || candidate.paths.length === 0) return void 0;
|
|
908
|
+
const paths = [];
|
|
909
|
+
for (const entry of candidate.paths) {
|
|
910
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return void 0;
|
|
911
|
+
const path = entry;
|
|
912
|
+
const name = controlname(path.name);
|
|
913
|
+
if (!name) return void 0;
|
|
914
|
+
const when = path.when === void 0 ? void 0 : expressionof(path.when);
|
|
915
|
+
if (path.when !== void 0 && when === void 0) return void 0;
|
|
916
|
+
if (when !== void 0 && when.resultkind !== "boolean") return void 0;
|
|
917
|
+
const steps = controlstepslist(path.steps);
|
|
918
|
+
if (!steps) return void 0;
|
|
919
|
+
paths.push({ name, ...when !== void 0 ? { when } : {}, steps });
|
|
920
|
+
}
|
|
921
|
+
const names = paths.map((path) => path.name);
|
|
922
|
+
if (new Set(names).size !== names.length) return void 0;
|
|
923
|
+
const elsepath = elseof(candidate.else);
|
|
924
|
+
if (!elsepath) return void 0;
|
|
925
|
+
if (names.includes(elsepath.name)) return void 0;
|
|
926
|
+
return { paths, else: elsepath };
|
|
927
|
+
}
|
|
928
|
+
function loopof(value) {
|
|
929
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
930
|
+
const candidate = value;
|
|
931
|
+
const list = controlname(candidate.list);
|
|
932
|
+
const item = controlname(candidate.item);
|
|
933
|
+
const index = controlname(candidate.index);
|
|
934
|
+
if (!list || !item || !index) return void 0;
|
|
935
|
+
if (item === list || index === list || item === index) return void 0;
|
|
936
|
+
const bound = controlbound(candidate.bound);
|
|
937
|
+
if (candidate.bound !== void 0 && bound === void 0) return void 0;
|
|
938
|
+
const steps = controlstepslist(candidate.steps);
|
|
939
|
+
if (!steps) return void 0;
|
|
940
|
+
return { list, item, index, ...bound !== void 0 ? { bound } : {}, steps };
|
|
941
|
+
}
|
|
942
|
+
function repeatuntilof(value) {
|
|
943
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
944
|
+
const candidate = value;
|
|
945
|
+
const until = expressionof(candidate.until);
|
|
946
|
+
if (!until || until.resultkind !== "boolean") return void 0;
|
|
947
|
+
const bound = controlbound(candidate.bound);
|
|
948
|
+
if (candidate.bound !== void 0 && bound === void 0) return void 0;
|
|
949
|
+
const steps = controlstepslist(candidate.steps);
|
|
950
|
+
if (!steps) return void 0;
|
|
951
|
+
return { until, ...bound !== void 0 ? { bound } : {}, steps };
|
|
952
|
+
}
|
|
953
|
+
function whileof(value) {
|
|
954
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
955
|
+
const candidate = value;
|
|
956
|
+
const condition = expressionof(candidate.while);
|
|
957
|
+
if (!condition || condition.resultkind !== "boolean") return void 0;
|
|
958
|
+
const bound = controlbound(candidate.bound);
|
|
959
|
+
if (bound === void 0) return void 0;
|
|
960
|
+
const steps = controlstepslist(candidate.steps);
|
|
961
|
+
if (!steps) return void 0;
|
|
962
|
+
return { while: condition, bound, steps };
|
|
963
|
+
}
|
|
964
|
+
function foreachof(value) {
|
|
965
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
966
|
+
const candidate = value;
|
|
967
|
+
if (typeof candidate.selector !== "string" || !candidate.selector.trim()) return void 0;
|
|
968
|
+
const item = controlname(candidate.item);
|
|
969
|
+
const index = controlname(candidate.index);
|
|
970
|
+
if (!item || !index || item === index) return void 0;
|
|
971
|
+
const steps = controlstepslist(candidate.steps);
|
|
972
|
+
if (!steps) return void 0;
|
|
973
|
+
return { selector: candidate.selector, item, index, steps };
|
|
974
|
+
}
|
|
975
|
+
function parallelof(value) {
|
|
976
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
977
|
+
const candidate = value;
|
|
978
|
+
if (!Array.isArray(candidate.branches) || candidate.branches.length === 0) return void 0;
|
|
979
|
+
const branches = [];
|
|
980
|
+
for (const entry of candidate.branches) {
|
|
981
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return void 0;
|
|
982
|
+
const branch = entry;
|
|
983
|
+
const id = controlname(branch.id);
|
|
984
|
+
if (!id) return void 0;
|
|
985
|
+
const steps = controlstepslist(branch.steps);
|
|
986
|
+
if (!steps) return void 0;
|
|
987
|
+
branches.push({ id, steps });
|
|
988
|
+
}
|
|
989
|
+
if (new Set(branches.map((branch) => branch.id)).size !== branches.length) return void 0;
|
|
990
|
+
const join = candidate.join && typeof candidate.join === "object" && !Array.isArray(candidate.join) ? candidate.join : void 0;
|
|
991
|
+
if (!join) return void 0;
|
|
992
|
+
if (join.strategy !== "first" && join.strategy !== "last" && join.strategy !== "fail") return void 0;
|
|
993
|
+
if (join.onfail !== "cancel" && join.onfail !== "continue") return void 0;
|
|
994
|
+
return { branches, join: { strategy: join.strategy, onfail: join.onfail } };
|
|
995
|
+
}
|
|
996
|
+
function tryof(value) {
|
|
997
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
998
|
+
const candidate = value;
|
|
999
|
+
const steps = controlstepslist(candidate.steps);
|
|
1000
|
+
if (!steps) return void 0;
|
|
1001
|
+
const catchcandidate = candidate.catch && typeof candidate.catch === "object" && !Array.isArray(candidate.catch) ? candidate.catch : void 0;
|
|
1002
|
+
if (!catchcandidate) return void 0;
|
|
1003
|
+
const catchsteps = controlstepslist(catchcandidate.steps);
|
|
1004
|
+
if (!catchsteps) return void 0;
|
|
1005
|
+
if (catchcandidate.rerun !== void 0 && typeof catchcandidate.rerun !== "boolean") return void 0;
|
|
1006
|
+
const catchvalue = { steps: catchsteps, ...catchcandidate.rerun === true ? { rerun: true } : {} };
|
|
1007
|
+
let retry;
|
|
1008
|
+
if (candidate.retry !== void 0) {
|
|
1009
|
+
const retrycandidate = candidate.retry && typeof candidate.retry === "object" && !Array.isArray(candidate.retry) ? candidate.retry : void 0;
|
|
1010
|
+
if (!retrycandidate) return void 0;
|
|
1011
|
+
if (typeof retrycandidate.attempts !== "number" || !Number.isInteger(retrycandidate.attempts) || retrycandidate.attempts < 1) return void 0;
|
|
1012
|
+
const backoff = retrycandidate.backoff && typeof retrycandidate.backoff === "object" && !Array.isArray(retrycandidate.backoff) ? retrycandidate.backoff : void 0;
|
|
1013
|
+
if (!backoff) return void 0;
|
|
1014
|
+
if (backoff.shape !== "fixed" && backoff.shape !== "exponential") return void 0;
|
|
1015
|
+
if (typeof backoff.base !== "number" || !Number.isFinite(backoff.base) || backoff.base < 0) return void 0;
|
|
1016
|
+
if (typeof backoff.jitter !== "number" || !Number.isFinite(backoff.jitter) || backoff.jitter < 0) return void 0;
|
|
1017
|
+
if (!Array.isArray(retrycandidate.retryable) || !retrycandidate.retryable.every((entry) => typeof entry === "string" && entry.trim())) return void 0;
|
|
1018
|
+
retry = { attempts: retrycandidate.attempts, backoff: { shape: backoff.shape, base: backoff.base, jitter: backoff.jitter }, retryable: retrycandidate.retryable };
|
|
1019
|
+
}
|
|
1020
|
+
let timeout;
|
|
1021
|
+
if (candidate.timeout !== void 0) {
|
|
1022
|
+
const timeoutcandidate = candidate.timeout && typeof candidate.timeout === "object" && !Array.isArray(candidate.timeout) ? candidate.timeout : void 0;
|
|
1023
|
+
if (!timeoutcandidate) return void 0;
|
|
1024
|
+
const stepms = timeoutcandidate.stepms === void 0 ? void 0 : typeof timeoutcandidate.stepms === "number" && Number.isFinite(timeoutcandidate.stepms) && timeoutcandidate.stepms > 0 ? timeoutcandidate.stepms : void 0;
|
|
1025
|
+
const runms = timeoutcandidate.runms === void 0 ? void 0 : typeof timeoutcandidate.runms === "number" && Number.isFinite(timeoutcandidate.runms) && timeoutcandidate.runms > 0 ? timeoutcandidate.runms : void 0;
|
|
1026
|
+
if (stepms === void 0 && runms === void 0) return void 0;
|
|
1027
|
+
if (timeoutcandidate.stepms !== void 0 && stepms === void 0) return void 0;
|
|
1028
|
+
if (timeoutcandidate.runms !== void 0 && runms === void 0) return void 0;
|
|
1029
|
+
timeout = { ...stepms !== void 0 ? { stepms } : {}, ...runms !== void 0 ? { runms } : {} };
|
|
1030
|
+
}
|
|
1031
|
+
return { steps, catch: catchvalue, ...retry !== void 0 ? { retry } : {}, ...timeout !== void 0 ? { timeout } : {} };
|
|
1032
|
+
}
|
|
1033
|
+
function controloptions(step) {
|
|
1034
|
+
if (step.options === void 0) throw new Error(`The ${step.kind} step needs its reviewed control payload in options.`);
|
|
1035
|
+
let parsed;
|
|
1036
|
+
try {
|
|
1037
|
+
parsed = JSON.parse(step.options);
|
|
1038
|
+
} catch {
|
|
1039
|
+
throw new Error(`The ${step.kind} control payload must be a JSON object.`);
|
|
1040
|
+
}
|
|
1041
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`The ${step.kind} control payload must be a JSON object.`);
|
|
1042
|
+
return parsed;
|
|
1043
|
+
}
|
|
1044
|
+
function validatecontrolpayload(step) {
|
|
1045
|
+
if (!iscontrolflowkind(step.kind)) return;
|
|
1046
|
+
const payload = controloptions(step);
|
|
1047
|
+
if (step.kind === "condition" && conditionof(payload.condition) === void 0) throw new Error("The condition step needs a reviewed boolean expression in its options.");
|
|
1048
|
+
if (step.kind === "branch" && branchof(payload.branch) === void 0) throw new Error("The branch step needs reviewed unique paths with boolean match expressions and an else path in its options.");
|
|
1049
|
+
if (step.kind === "loop" && loopof(payload.loop) === void 0) throw new Error("The loop step needs a reviewed list variable, distinct item and index variables, an optional positive safety bound and a non-empty body in its options.");
|
|
1050
|
+
if (step.kind === "repeatuntil" && repeatuntilof(payload.repeatuntil) === void 0) throw new Error("The repeat until step needs a reviewed convergence expression, an optional positive safety bound and a non-empty body in its options.");
|
|
1051
|
+
if (step.kind === "whileloop" && whileof(payload.while) === void 0) throw new Error("The while step needs a reviewed condition, a mandatory positive safety bound and a non-empty body in its options.");
|
|
1052
|
+
if (step.kind === "foreach" && foreachof(payload.foreach) === void 0) throw new Error("The foreach step needs a reviewed non-empty selector, distinct item and index variables and a non-empty body in its options.");
|
|
1053
|
+
if (step.kind === "parallel" && parallelof(payload.parallel) === void 0) throw new Error("The parallel step needs uniquely identified branches with bodies and a join policy of the first, last or fail strategy with cancel or continue on branch failure in its options.");
|
|
1054
|
+
if (step.kind === "trycatch" && tryof(payload.try) === void 0) throw new Error("The try step needs a fragile body, a catch handler and optional retry and timeout policies in its options.");
|
|
1055
|
+
}
|
|
1056
|
+
function controlsteps(step) {
|
|
1057
|
+
if (!iscontrolflowkind(step.kind)) return [];
|
|
1058
|
+
let payload;
|
|
1059
|
+
try {
|
|
1060
|
+
payload = controloptions(step);
|
|
1061
|
+
} catch {
|
|
1062
|
+
return [];
|
|
1063
|
+
}
|
|
1064
|
+
const children = [];
|
|
1065
|
+
const collect = (steps) => {
|
|
1066
|
+
for (const child of steps) {
|
|
1067
|
+
children.push(child);
|
|
1068
|
+
collect(controlsteps(child));
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
if (step.kind === "condition") return children;
|
|
1072
|
+
if (step.kind === "branch") {
|
|
1073
|
+
const branch = branchof(payload.branch);
|
|
1074
|
+
if (!branch) return children;
|
|
1075
|
+
for (const path of branch.paths) collect(path.steps);
|
|
1076
|
+
collect(branch.else.steps);
|
|
1077
|
+
return children;
|
|
1078
|
+
}
|
|
1079
|
+
if (step.kind === "loop") {
|
|
1080
|
+
const loop = loopof(payload.loop);
|
|
1081
|
+
if (loop) collect(loop.steps);
|
|
1082
|
+
return children;
|
|
1083
|
+
}
|
|
1084
|
+
if (step.kind === "repeatuntil") {
|
|
1085
|
+
const repeat = repeatuntilof(payload.repeatuntil);
|
|
1086
|
+
if (repeat) collect(repeat.steps);
|
|
1087
|
+
return children;
|
|
1088
|
+
}
|
|
1089
|
+
if (step.kind === "whileloop") {
|
|
1090
|
+
const condition = whileof(payload.while);
|
|
1091
|
+
if (condition) collect(condition.steps);
|
|
1092
|
+
return children;
|
|
1093
|
+
}
|
|
1094
|
+
if (step.kind === "foreach") {
|
|
1095
|
+
const foreach = foreachof(payload.foreach);
|
|
1096
|
+
if (foreach) collect(foreach.steps);
|
|
1097
|
+
return children;
|
|
1098
|
+
}
|
|
1099
|
+
if (step.kind === "parallel") {
|
|
1100
|
+
const parallel = parallelof(payload.parallel);
|
|
1101
|
+
if (parallel) for (const branch of parallel.branches) collect(branch.steps);
|
|
1102
|
+
return children;
|
|
1103
|
+
}
|
|
1104
|
+
const fragile = tryof(payload.try);
|
|
1105
|
+
if (fragile) {
|
|
1106
|
+
collect(fragile.steps);
|
|
1107
|
+
collect(fragile.catch.steps);
|
|
1108
|
+
}
|
|
1109
|
+
return children;
|
|
1110
|
+
}
|
|
1111
|
+
function controlsummary(step) {
|
|
1112
|
+
if (!iscontrolflowkind(step.kind)) return void 0;
|
|
1113
|
+
let payload;
|
|
1114
|
+
try {
|
|
1115
|
+
payload = controloptions(step);
|
|
1116
|
+
} catch {
|
|
1117
|
+
return { kind: step.kind };
|
|
1118
|
+
}
|
|
1119
|
+
if (step.kind === "condition") {
|
|
1120
|
+
const condition = conditionof(payload.condition);
|
|
1121
|
+
return { kind: step.kind, ...condition ? { expression: `${condition.expression.operator} into ${condition.expression.result}` } : {} };
|
|
1122
|
+
}
|
|
1123
|
+
if (step.kind === "branch") {
|
|
1124
|
+
const branch = branchof(payload.branch);
|
|
1125
|
+
return { kind: step.kind, ...branch ? { paths: branch.paths.map((path) => path.name), elsepath: branch.else.name } : {} };
|
|
1126
|
+
}
|
|
1127
|
+
if (step.kind === "loop") {
|
|
1128
|
+
const loop = loopof(payload.loop);
|
|
1129
|
+
return { kind: step.kind, ...loop ? { list: loop.list, item: loop.item, index: loop.index, ...loop.bound !== void 0 ? { bound: loop.bound } : { bound: defaultloopbound } } : {} };
|
|
1130
|
+
}
|
|
1131
|
+
if (step.kind === "repeatuntil") {
|
|
1132
|
+
const repeat = repeatuntilof(payload.repeatuntil);
|
|
1133
|
+
return { kind: step.kind, ...repeat ? { bound: repeat.bound ?? defaultloopbound } : {} };
|
|
1134
|
+
}
|
|
1135
|
+
if (step.kind === "whileloop") {
|
|
1136
|
+
const condition = whileof(payload.while);
|
|
1137
|
+
return { kind: step.kind, ...condition ? { bound: condition.bound } : {} };
|
|
1138
|
+
}
|
|
1139
|
+
if (step.kind === "foreach") {
|
|
1140
|
+
const foreach = foreachof(payload.foreach);
|
|
1141
|
+
return { kind: step.kind, ...foreach ? { selector: foreach.selector, item: foreach.item, index: foreach.index } : {} };
|
|
1142
|
+
}
|
|
1143
|
+
if (step.kind === "parallel") {
|
|
1144
|
+
const parallel = parallelof(payload.parallel);
|
|
1145
|
+
return { kind: step.kind, ...parallel ? { branches: parallel.branches.map((branch) => branch.id), strategy: parallel.join.strategy, onfail: parallel.join.onfail } : {} };
|
|
1146
|
+
}
|
|
1147
|
+
const fragile = tryof(payload.try);
|
|
1148
|
+
return { kind: step.kind, ...fragile ? { ...fragile.retry !== void 0 ? { attempts: fragile.retry.attempts, backoff: `${fragile.retry.backoff.shape} base ${fragile.retry.backoff.base} jitter ${fragile.retry.backoff.jitter}` } : {}, ...fragile.catch.rerun === true ? { rerun: true } : {}, ...fragile.timeout?.stepms !== void 0 ? { stepms: fragile.timeout.stepms } : {}, ...fragile.timeout?.runms !== void 0 ? { runms: fragile.timeout.runms } : {} } : {} };
|
|
1149
|
+
}
|
|
1150
|
+
function evaluatecondition(condition, scopes) {
|
|
1151
|
+
const value = expressioneval(condition.expression, scopes);
|
|
1152
|
+
if (typeof value !== "boolean") throw new Error("The condition expression must resolve to a boolean.");
|
|
1153
|
+
return value;
|
|
1154
|
+
}
|
|
1155
|
+
function choosebranch(input) {
|
|
1156
|
+
let scopes = input.scopes;
|
|
1157
|
+
if (input.pagestate !== void 0) {
|
|
1158
|
+
const parent = scopes.length > 0 ? scopes[scopes.length - 1].name : void 0;
|
|
1159
|
+
scopes = pushscope(scopes, `pagestate${input.stepid}`, parent);
|
|
1160
|
+
if (input.pagestate.url !== void 0) scopes = setvariable(scopes, "pageurl", "string", input.pagestate.url, input.now);
|
|
1161
|
+
if (input.pagestate.title !== void 0) scopes = setvariable(scopes, "pagetitle", "string", input.pagestate.title, input.now);
|
|
1162
|
+
if (input.pagestate.ready !== void 0) scopes = setvariable(scopes, "pageready", "boolean", input.pagestate.ready, input.now);
|
|
1163
|
+
}
|
|
1164
|
+
for (const path of input.branch.paths) {
|
|
1165
|
+
if (path.when === void 0) return { outcome: { stepid: input.stepid, path: path.name, reason: `The path ${path.name} matches unconditionally.`, at: input.now }, steps: path.steps };
|
|
1166
|
+
const value = expressioneval(path.when, scopes);
|
|
1167
|
+
if (typeof value !== "boolean") throw new Error(`The branch path ${path.name} needs a boolean expression.`);
|
|
1168
|
+
if (value) return { outcome: { stepid: input.stepid, path: path.name, reason: `The condition of the path ${path.name} holds.`, at: input.now }, steps: path.steps };
|
|
1169
|
+
}
|
|
1170
|
+
return { outcome: { stepid: input.stepid, path: input.branch.else.name, reason: "No path condition held and the else path ran.", at: input.now }, steps: input.branch.else.steps };
|
|
1171
|
+
}
|
|
1172
|
+
async function runbody(input) {
|
|
1173
|
+
let scopes = input.scopes;
|
|
1174
|
+
const outputs = { ...input.outputs };
|
|
1175
|
+
const log = [];
|
|
1176
|
+
for (const child of input.steps) {
|
|
1177
|
+
if (iscontrolflowkind(child.kind)) {
|
|
1178
|
+
const result = await runcontrolstep({ step: child, scopes, outputs, execute: input.execute, now: input.now, ...input.path !== void 0 ? { path: `${input.path}.${child.id}` } : {} });
|
|
1179
|
+
scopes = result.scopes;
|
|
1180
|
+
log.push(...result.log);
|
|
1181
|
+
outputs[child.id] = { stepid: child.id, ok: result.output.ok, summary: result.output.summary, ...result.output.details !== void 0 ? { details: result.output.details } : {}, at: input.now };
|
|
1182
|
+
if (!result.output.ok) return { ok: false, scopes, log, outputs, failure: result.output };
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
const executed = await runstep({ step: child, scopes, outputs, execute: input.execute, now: input.now });
|
|
1186
|
+
scopes = executed.scopes;
|
|
1187
|
+
if (executed.childlog !== void 0) log.push(...executed.childlog);
|
|
1188
|
+
log.push(executed.log);
|
|
1189
|
+
outputs[child.id] = { stepid: child.id, ok: executed.output.ok, summary: executed.output.summary, ...executed.output.details !== void 0 ? { details: executed.output.details } : {}, at: input.now };
|
|
1190
|
+
if (!executed.output.ok) return { ok: false, scopes, log, outputs, failure: executed.output };
|
|
1191
|
+
}
|
|
1192
|
+
return { ok: true, scopes, log, outputs };
|
|
1193
|
+
}
|
|
1194
|
+
function deepcopy(value) {
|
|
1195
|
+
if (Array.isArray(value)) return value.map(deepcopy);
|
|
1196
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, deepcopy(entry)]));
|
|
1197
|
+
return value;
|
|
1198
|
+
}
|
|
1199
|
+
function iterationentry(step, iteration, total, ok, now) {
|
|
1200
|
+
return { stepid: step.id, label: `${step.label} iteration ${iteration + 1}`, state: ok ? "done" : "failed", startedat: now, duration: 0, summary: `Iteration ${iteration + 1} of ${total}.`, details: { iteration, total } };
|
|
1201
|
+
}
|
|
1202
|
+
async function runloop(input) {
|
|
1203
|
+
const list = resolvevariable(input.scopes, input.loop.list);
|
|
1204
|
+
if (!list) throw new Error(`The loop references the undefined list variable ${input.loop.list}.`);
|
|
1205
|
+
if (list.kind !== "list") throw new Error(`The loop variable ${input.loop.list} is not a list.`);
|
|
1206
|
+
const items = list.value;
|
|
1207
|
+
const bound = input.loop.bound ?? defaultloopbound;
|
|
1208
|
+
const loops = [];
|
|
1209
|
+
const decision = { runid: "", stepid: input.step.id, kind: "loop", at: input.now, loops };
|
|
1210
|
+
if (items.length > bound) {
|
|
1211
|
+
return { ok: false, scopes: input.scopes, log: [], summary: `The loop list holds ${items.length} items and exceeds the reviewed safety bound of ${bound} iterations; nothing ran.`, decision };
|
|
1212
|
+
}
|
|
1213
|
+
let scopes = input.scopes;
|
|
1214
|
+
const log = [];
|
|
1215
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
1216
|
+
scopes = setvariable(scopes, input.loop.item, "string", deepcopy(items[index]), input.now);
|
|
1217
|
+
scopes = setvariable(scopes, input.loop.index, "number", index, input.now);
|
|
1218
|
+
const body = await runbody({ step: input.step, scopes, outputs: input.outputs, execute: input.execute, now: input.now, steps: input.loop.steps, path: `${input.path ?? input.step.id}[${index}]` });
|
|
1219
|
+
scopes = body.scopes;
|
|
1220
|
+
const ok = body.ok;
|
|
1221
|
+
loops.push({ stepid: input.step.id, path: `${input.path ?? input.step.id}[${index}]`, iteration: index, ok, at: input.now });
|
|
1222
|
+
log.push(...body.log, iterationentry(input.step, index, items.length, ok, input.now));
|
|
1223
|
+
if (!ok) return { ok: false, scopes, log, summary: `The loop failed at iteration ${index + 1} of ${items.length}: ${body.failure?.summary ?? "the body step failed."}`, decision };
|
|
1224
|
+
}
|
|
1225
|
+
return { ok: true, scopes, log, summary: `The loop ran ${items.length} iteration${items.length === 1 ? "" : "s"} over ${input.loop.list} inside the reviewed safety bound of ${bound}.`, decision };
|
|
1226
|
+
}
|
|
1227
|
+
async function runrepeatuntil(input) {
|
|
1228
|
+
const bound = input.repeat.bound ?? defaultloopbound;
|
|
1229
|
+
let scopes = input.scopes;
|
|
1230
|
+
const log = [];
|
|
1231
|
+
const loops = [];
|
|
1232
|
+
const decision = { runid: "", stepid: input.step.id, kind: "loop", at: input.now, loops };
|
|
1233
|
+
for (let iteration = 0; iteration < bound; iteration += 1) {
|
|
1234
|
+
const body = await runbody({ step: input.step, scopes, outputs: input.outputs, execute: input.execute, now: input.now, steps: input.repeat.steps, path: `${input.path ?? input.step.id}[${iteration}]` });
|
|
1235
|
+
scopes = body.scopes;
|
|
1236
|
+
log.push(...body.log);
|
|
1237
|
+
const converged = evaluatecondition({ expression: input.repeat.until }, scopes);
|
|
1238
|
+
loops.push({ stepid: input.step.id, path: `${input.path ?? input.step.id}[${iteration}]`, iteration, ok: body.ok, at: input.now });
|
|
1239
|
+
if (!body.ok) return { ok: false, scopes, log, summary: `The repeat until failed at iteration ${iteration + 1}: ${body.failure?.summary ?? "the body step failed."}`, decision };
|
|
1240
|
+
log.push(iterationentry(input.step, iteration, bound, true, input.now));
|
|
1241
|
+
if (converged) return { ok: true, scopes, log, summary: `The repeat until converged after ${iteration + 1} iteration${iteration === 0 ? "" : "s"} inside the reviewed safety bound of ${bound}.`, decision };
|
|
1242
|
+
}
|
|
1243
|
+
return { ok: false, scopes, log, summary: `The repeat until never converged within the reviewed safety bound of ${bound} iterations.`, decision };
|
|
1244
|
+
}
|
|
1245
|
+
async function runwhile(input) {
|
|
1246
|
+
let scopes = input.scopes;
|
|
1247
|
+
const log = [];
|
|
1248
|
+
const loops = [];
|
|
1249
|
+
const decision = { runid: "", stepid: input.step.id, kind: "loop", at: input.now, loops };
|
|
1250
|
+
for (let iteration = 0; iteration < input.condition.bound; iteration += 1) {
|
|
1251
|
+
if (!evaluatecondition({ expression: input.condition.while }, scopes)) {
|
|
1252
|
+
return { ok: true, scopes, log, summary: `The while loop ended after ${iteration} iteration${iteration === 1 ? "" : "s"} because its condition stopped holding inside the reviewed safety bound of ${input.condition.bound}.`, decision };
|
|
1253
|
+
}
|
|
1254
|
+
const body = await runbody({ step: input.step, scopes, outputs: input.outputs, execute: input.execute, now: input.now, steps: input.condition.steps, path: `${input.path ?? input.step.id}[${iteration}]` });
|
|
1255
|
+
scopes = body.scopes;
|
|
1256
|
+
log.push(...body.log);
|
|
1257
|
+
loops.push({ stepid: input.step.id, path: `${input.path ?? input.step.id}[${iteration}]`, iteration, ok: body.ok, at: input.now });
|
|
1258
|
+
if (!body.ok) return { ok: false, scopes, log, summary: `The while loop failed at iteration ${iteration + 1}: ${body.failure?.summary ?? "the body step failed."}`, decision };
|
|
1259
|
+
log.push(iterationentry(input.step, iteration, input.condition.bound, true, input.now));
|
|
1260
|
+
}
|
|
1261
|
+
if (evaluatecondition({ expression: input.condition.while }, scopes)) {
|
|
1262
|
+
return { ok: false, scopes, log, summary: `The while loop hit its reviewed safety bound of ${input.condition.bound} iterations while its condition still held; the overflow is reported instead of looping forever.`, decision };
|
|
1263
|
+
}
|
|
1264
|
+
return { ok: true, scopes, log, summary: `The while loop ended after ${input.condition.bound} iteration${input.condition.bound === 1 ? "" : "s"} inside the reviewed safety bound.`, decision };
|
|
1265
|
+
}
|
|
1266
|
+
async function runforeach(input) {
|
|
1267
|
+
if (!input.resolveelements) throw new Error("The foreach step needs the element resolver of the executor seam.");
|
|
1268
|
+
const elements = await input.resolveelements(input.foreach.selector);
|
|
1269
|
+
const loops = [];
|
|
1270
|
+
const decision = { runid: "", stepid: input.step.id, kind: "loop", at: input.now, loops };
|
|
1271
|
+
if (elements.length === 0) return { ok: true, scopes: input.scopes, log: [], summary: `The selector ${input.foreach.selector} matched no element and the foreach ran zero iterations.`, decision };
|
|
1272
|
+
let scopes = input.scopes;
|
|
1273
|
+
const log = [];
|
|
1274
|
+
for (let index = 0; index < elements.length; index += 1) {
|
|
1275
|
+
scopes = setvariable(scopes, input.foreach.item, "element", deepcopy(elements[index]), input.now);
|
|
1276
|
+
scopes = setvariable(scopes, input.foreach.index, "number", index, input.now);
|
|
1277
|
+
const body = await runbody({ step: input.step, scopes, outputs: input.outputs, execute: input.execute, now: input.now, steps: input.foreach.steps, path: `${input.path ?? input.step.id}[${index}]` });
|
|
1278
|
+
scopes = body.scopes;
|
|
1279
|
+
const ok = body.ok;
|
|
1280
|
+
loops.push({ stepid: input.step.id, path: `${input.path ?? input.step.id}[${index}]`, iteration: index, ok, at: input.now });
|
|
1281
|
+
log.push(...body.log, iterationentry(input.step, index, elements.length, ok, input.now));
|
|
1282
|
+
if (!ok) return { ok: false, scopes, log, summary: `The foreach failed at iteration ${index + 1} of ${elements.length}: ${body.failure?.summary ?? "the body step failed."}`, decision };
|
|
1283
|
+
}
|
|
1284
|
+
return { ok: true, scopes, log, summary: `The foreach ran ${elements.length} iteration${elements.length === 1 ? "" : "s"} over the elements of ${input.foreach.selector}.`, decision };
|
|
1285
|
+
}
|
|
1286
|
+
function joinbranches(input) {
|
|
1287
|
+
const contributing = input.branches.filter((branch) => !branch.cancelled);
|
|
1288
|
+
const byname = /* @__PURE__ */ new Map();
|
|
1289
|
+
for (const branch of contributing) for (const variable of branch.variables) {
|
|
1290
|
+
const entries = byname.get(variable.name) ?? [];
|
|
1291
|
+
entries.push({ order: branch.order, value: variable });
|
|
1292
|
+
byname.set(variable.name, entries);
|
|
1293
|
+
}
|
|
1294
|
+
const conflicts = [...byname.entries()].filter(([, entries]) => entries.length > 1).map(([name]) => name);
|
|
1295
|
+
const record2 = { stepid: input.stepid, strategy: input.strategy, conflicts, merged: [], at: input.now };
|
|
1296
|
+
if (conflicts.length > 0 && input.strategy === "fail") {
|
|
1297
|
+
return { ok: false, conflicts, merged: [], record: record2, summary: `The join refused the conflicting writes of ${conflicts.join(", ")} under the fail strategy.` };
|
|
1298
|
+
}
|
|
1299
|
+
const merged = [];
|
|
1300
|
+
for (const [name, entries] of byname) {
|
|
1301
|
+
void name;
|
|
1302
|
+
const winner = input.strategy === "first" ? entries.reduce((left, right) => left.order <= right.order ? left : right) : entries.reduce((left, right) => left.order >= right.order ? left : right);
|
|
1303
|
+
merged.push({ ...winner.value, setat: input.now });
|
|
1304
|
+
}
|
|
1305
|
+
record2.merged = merged.map((variable) => variable.name);
|
|
1306
|
+
return { ok: true, conflicts, merged, record: record2, summary: `The join merged ${merged.length} variable${merged.length === 1 ? "" : "s"} under the ${input.strategy} strategy${conflicts.length > 0 ? ` with the conflicts ${conflicts.join(", ")} resolved by the strategy` : " with no conflict"}.` };
|
|
1307
|
+
}
|
|
1308
|
+
async function runparallel(input) {
|
|
1309
|
+
let finished = 0;
|
|
1310
|
+
let firstfailure = Number.POSITIVE_INFINITY;
|
|
1311
|
+
const log = [];
|
|
1312
|
+
const launches = input.parallel.branches.map((branch, order) => (async () => {
|
|
1313
|
+
const parent = input.scopes.length > 0 ? input.scopes[input.scopes.length - 1].name : void 0;
|
|
1314
|
+
const isolated = pushscope(input.scopes, `branch${branch.id}`, parent);
|
|
1315
|
+
const body = await runbody({ step: input.step, scopes: isolated, outputs: input.outputs, execute: input.execute, now: input.now, steps: branch.steps });
|
|
1316
|
+
const finishedat = finished;
|
|
1317
|
+
finished += 1;
|
|
1318
|
+
if (!body.ok && finishedat < firstfailure) firstfailure = finishedat;
|
|
1319
|
+
const scope = body.scopes[body.scopes.length - 1];
|
|
1320
|
+
return { branch, order, finishedat, ok: body.ok, scopes: body.scopes, variables: scope.name === `branch${branch.id}` ? scope.variables : [], log: body.log, failure: body.failure };
|
|
1321
|
+
})());
|
|
1322
|
+
const settled = await Promise.all(launches);
|
|
1323
|
+
for (const entry of settled) log.push(...entry.log);
|
|
1324
|
+
const cancelmode = input.parallel.join.onfail === "cancel";
|
|
1325
|
+
const outcomes = settled.map((entry) => ({ branchid: entry.branch.id, ok: entry.ok, summary: entry.ok ? `The branch ${entry.branch.id} completed.` : entry.failure?.summary ?? `The branch ${entry.branch.id} failed.`, ...cancelmode && firstfailure !== Number.POSITIVE_INFINITY && entry.finishedat > firstfailure ? { cancelled: true } : {} }));
|
|
1326
|
+
const join = joinbranches({ stepid: input.step.id, branches: settled.map((entry, order) => ({ id: entry.branch.id, order, ok: entry.ok, cancelled: outcomes[order]?.cancelled === true, variables: entry.variables })), strategy: input.parallel.join.strategy, now: input.now });
|
|
1327
|
+
const decision = { runid: "", stepid: input.step.id, kind: "join", at: input.now, join: join.record, branches: outcomes };
|
|
1328
|
+
if (!join.ok) return { ok: false, scopes: input.scopes, log, summary: join.summary, decision };
|
|
1329
|
+
let scopes = input.scopes;
|
|
1330
|
+
for (const variable of join.merged) scopes = setvariable(scopes, variable.name, variable.kind, variable.value, input.now);
|
|
1331
|
+
const failedbranches = settled.filter((entry, order) => !entry.ok && outcomes[order]?.cancelled !== true).map((entry) => entry.branch.id);
|
|
1332
|
+
if (cancelmode && failedbranches.length > 0) {
|
|
1333
|
+
return { ok: false, scopes, log, summary: `The parallel block failed on branch ${failedbranches.join(", ")} and the join policy cancelled the siblings still running; ${join.summary}`, decision };
|
|
1334
|
+
}
|
|
1335
|
+
return { ok: true, scopes, log, summary: `The parallel block ran ${input.parallel.branches.length} concurrent branch${input.parallel.branches.length === 1 ? "" : "es"}; ${join.summary}`, decision };
|
|
1336
|
+
}
|
|
1337
|
+
function errorclassof(output) {
|
|
1338
|
+
const errorclass = output.details?.errorclass;
|
|
1339
|
+
return typeof errorclass === "string" && errorclass.trim() ? errorclass : "stepfailed";
|
|
1340
|
+
}
|
|
1341
|
+
function backoffdelay(policy, attempt, seed) {
|
|
1342
|
+
const base = policy.backoff.shape === "exponential" ? policy.backoff.base * 2 ** (attempt - 1) : policy.backoff.base;
|
|
1343
|
+
if (policy.backoff.jitter <= 0) return Math.max(0, base);
|
|
1344
|
+
return Math.max(0, base - policy.backoff.jitter / 2 + seededrandom(seed + attempt) * policy.backoff.jitter);
|
|
1345
|
+
}
|
|
1346
|
+
function waitsome(milliseconds) {
|
|
1347
|
+
return new Promise((resolve) => setTimeout(resolve, Math.max(0, milliseconds)));
|
|
1348
|
+
}
|
|
1349
|
+
async function applyretry(input) {
|
|
1350
|
+
const seed = input.seed ?? 0;
|
|
1351
|
+
let value = await input.run();
|
|
1352
|
+
const attempts = [];
|
|
1353
|
+
let attempt = 1;
|
|
1354
|
+
while (!value.ok && attempt < input.policy.attempts) {
|
|
1355
|
+
const errorclass = input.errorclass(value);
|
|
1356
|
+
if (!input.policy.retryable.includes(errorclass)) break;
|
|
1357
|
+
const delay = backoffdelay(input.policy, attempt, seed);
|
|
1358
|
+
attempts.push({ stepid: input.stepid, attempt: attempt + 1, delay, errorclass, at: input.now });
|
|
1359
|
+
if (delay > 0) await waitsome(delay);
|
|
1360
|
+
attempt += 1;
|
|
1361
|
+
value = await input.run();
|
|
1362
|
+
}
|
|
1363
|
+
return { value, attempts, exhausted: !value.ok && attempts.length > 0 && attempt >= input.policy.attempts };
|
|
1364
|
+
}
|
|
1365
|
+
async function applytimeout(input) {
|
|
1366
|
+
let timer;
|
|
1367
|
+
const guard = new Promise((resolve) => {
|
|
1368
|
+
timer = setTimeout(() => resolve("cancelled"), Math.max(0, input.budgetms));
|
|
1369
|
+
});
|
|
1370
|
+
const raced = await Promise.race([input.run().then((value) => ({ kind: "done", value })), guard.then((marker) => ({ kind: "cancelled", marker }))]);
|
|
1371
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1372
|
+
if (raced.kind === "done") return { aborted: false, value: raced.value };
|
|
1373
|
+
return { aborted: true, output: { ok: false, summary: `The ${input.stepid} step exceeded its reviewed budget of ${input.budgetms} milliseconds and was cancelled.`, details: { errorclass: "timeout", budget: input.budgetms, cancelled: true } }, abort: { stepid: input.stepid, budget: input.budgetms, scope: "step", at: Date.now() } };
|
|
1374
|
+
}
|
|
1375
|
+
async function applyruntimeout(input) {
|
|
1376
|
+
let timer;
|
|
1377
|
+
const guard = new Promise((resolve) => {
|
|
1378
|
+
timer = setTimeout(() => resolve("cancelled"), Math.max(0, input.budgetms));
|
|
1379
|
+
});
|
|
1380
|
+
const raced = await Promise.race([input.run().then((value) => ({ kind: "done", value })), guard.then((marker) => ({ kind: "cancelled", marker }))]);
|
|
1381
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1382
|
+
if (raced.kind === "done") return { cancelled: false, value: raced.value };
|
|
1383
|
+
return { cancelled: true, error: new cancellederror(`The run exceeded its reviewed budget of ${input.budgetms} milliseconds and was cancelled.`) };
|
|
1384
|
+
}
|
|
1385
|
+
async function runcatch(input) {
|
|
1386
|
+
const body = await runbody({ step: { id: "catch", kind: "trycatch", label: "catch handler" }, scopes: input.scopes, outputs: input.outputs, execute: input.execute, now: input.now, steps: input.handler.steps });
|
|
1387
|
+
return { ok: body.ok, scopes: body.scopes, log: body.log };
|
|
1388
|
+
}
|
|
1389
|
+
async function runtry(input) {
|
|
1390
|
+
const timeouts = [];
|
|
1391
|
+
const retries = [];
|
|
1392
|
+
const runonce = async (child, scopes) => {
|
|
1393
|
+
if (iscontrolflowkind(child.kind)) {
|
|
1394
|
+
const result = await runcontrolstep({ step: child, scopes, outputs: input.outputs, execute: input.execute, now: input.now });
|
|
1395
|
+
return { ok: result.output.ok, scopes: result.scopes, log: result.log, output: result.output };
|
|
1396
|
+
}
|
|
1397
|
+
const executed = await runstep({ step: child, scopes, outputs: input.outputs, execute: input.execute, now: input.now });
|
|
1398
|
+
return { ok: executed.output.ok, scopes: executed.scopes, log: [...executed.childlog ?? [], executed.log], output: executed.output };
|
|
1399
|
+
};
|
|
1400
|
+
const runchild = async (child, scopes) => {
|
|
1401
|
+
const attempt = async () => {
|
|
1402
|
+
if (input.fragile.retry === void 0) return await runonce(child, scopes);
|
|
1403
|
+
const retried = await applyretry({ stepid: child.id, policy: input.fragile.retry, run: () => runonce(child, scopes), errorclass: (value) => errorclassof(value.output), now: input.now, seed: seedof(child.id) });
|
|
1404
|
+
retries.push(...retried.attempts);
|
|
1405
|
+
return retried.value;
|
|
1406
|
+
};
|
|
1407
|
+
if (input.fragile.timeout?.stepms === void 0) return await attempt();
|
|
1408
|
+
const guarded = await applytimeout({ stepid: child.id, budgetms: input.fragile.timeout.stepms, run: attempt });
|
|
1409
|
+
if (!guarded.aborted) return guarded.value;
|
|
1410
|
+
if (guarded.abort) timeouts.push(guarded.abort);
|
|
1411
|
+
return { ok: false, scopes, log: [], output: guarded.output };
|
|
1412
|
+
};
|
|
1413
|
+
const runbodyof = async (scopes) => {
|
|
1414
|
+
let current = scopes;
|
|
1415
|
+
const log = [];
|
|
1416
|
+
for (const child of input.fragile.steps) {
|
|
1417
|
+
const executed = await runchild(child, current);
|
|
1418
|
+
current = executed.scopes;
|
|
1419
|
+
log.push(...executed.log);
|
|
1420
|
+
if (!executed.ok) return { ok: false, scopes: current, log, failure: executed.output };
|
|
1421
|
+
}
|
|
1422
|
+
return { ok: true, scopes: current, log };
|
|
1423
|
+
};
|
|
1424
|
+
let body;
|
|
1425
|
+
if (input.fragile.timeout?.runms !== void 0) {
|
|
1426
|
+
const guarded = await applytimeout({ stepid: input.step.id, budgetms: input.fragile.timeout.runms, run: () => runbodyof(input.scopes) });
|
|
1427
|
+
if (guarded.aborted) {
|
|
1428
|
+
if (guarded.abort) timeouts.push({ ...guarded.abort, scope: "run" });
|
|
1429
|
+
body = { ok: false, scopes: input.scopes, log: [], failure: guarded.output ?? { ok: false, summary: "The try block exceeded its reviewed run budget and was cancelled.", details: { errorclass: "timeout", cancelled: true } } };
|
|
1430
|
+
} else {
|
|
1431
|
+
body = guarded.value;
|
|
1432
|
+
}
|
|
1433
|
+
} else {
|
|
1434
|
+
body = await runbodyof(input.scopes);
|
|
1435
|
+
}
|
|
1436
|
+
if (body.ok) {
|
|
1437
|
+
const summary = `The try block completed its ${input.fragile.steps.length} step${input.fragile.steps.length === 1 ? "" : "s"}${retries.length > 0 ? ` after ${retries.length} retry attempt${retries.length === 1 ? "" : "s"}` : ""}.`;
|
|
1438
|
+
if (retries.length === 0 && timeouts.length === 0) return { ok: true, scopes: body.scopes, log: body.log, summary };
|
|
1439
|
+
return { ok: true, scopes: body.scopes, log: body.log, summary, decision: { runid: "", stepid: input.step.id, kind: "retry", at: input.now, ...retries.length > 0 ? { retries } : {}, ...timeouts.length > 0 ? { timeouts } : {} } };
|
|
1440
|
+
}
|
|
1441
|
+
const handler = await runcatch({ handler: input.fragile.catch, scopes: body.scopes, outputs: input.outputs, execute: input.execute, now: input.now });
|
|
1442
|
+
const errorclass = errorclassof(body.failure ?? { ok: false, summary: "" });
|
|
1443
|
+
const decision = { runid: "", stepid: input.step.id, kind: "catch", at: input.now, ...retries.length > 0 ? { retries } : {}, ...timeouts.length > 0 ? { timeouts } : {}, catch: { errorclass, message: body.failure?.summary ?? "The fragile body step failed.", rerun: input.fragile.catch.rerun === true } };
|
|
1444
|
+
if (!handler.ok) return { ok: false, scopes: handler.scopes, log: [...body.log, ...handler.log], summary: `The catch handler of the try block failed after the ${errorclass} failure.`, decision };
|
|
1445
|
+
if (input.fragile.catch.rerun === true) {
|
|
1446
|
+
const rerun = await runbodyof(handler.scopes);
|
|
1447
|
+
if (rerun.ok) return { ok: true, scopes: rerun.scopes, log: [...body.log, ...handler.log, ...rerun.log], summary: `The catch handler ran after the ${errorclass} failure and the rerun of the try body succeeded.`, decision };
|
|
1448
|
+
return { ok: false, scopes: rerun.scopes, log: [...body.log, ...handler.log, ...rerun.log], summary: `The catch handler ran and the rerun of the try body failed again with ${errorclassof(rerun.failure ?? { ok: false, summary: "" })}.`, decision };
|
|
1449
|
+
}
|
|
1450
|
+
return { ok: true, scopes: handler.scopes, log: [...body.log, ...handler.log], summary: `The catch handler ran ${input.fragile.catch.steps.length} step${input.fragile.catch.steps.length === 1 ? "" : "s"} after the ${errorclass} failure.`, decision };
|
|
1451
|
+
}
|
|
1452
|
+
function seedof(text2) {
|
|
1453
|
+
let hash = 2166136261;
|
|
1454
|
+
for (let index = 0; index < text2.length; index += 1) {
|
|
1455
|
+
hash ^= text2.charCodeAt(index);
|
|
1456
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
1457
|
+
}
|
|
1458
|
+
return hash >>> 0;
|
|
1459
|
+
}
|
|
1460
|
+
async function runcontrolstep(input) {
|
|
1461
|
+
const payload = controloptions(input.step);
|
|
1462
|
+
const base = { step: input.step, scopes: input.scopes, outputs: input.outputs, execute: input.execute, now: input.now, ...input.path !== void 0 ? { path: input.path } : {} };
|
|
1463
|
+
let result;
|
|
1464
|
+
switch (input.step.kind) {
|
|
1465
|
+
case "condition": {
|
|
1466
|
+
const condition = conditionof(payload.condition);
|
|
1467
|
+
if (!condition) throw new Error("The condition step needs a reviewed boolean expression in its options.");
|
|
1468
|
+
const value = evaluatecondition(condition, input.scopes);
|
|
1469
|
+
const scopes = setvariable(input.scopes, condition.expression.result, condition.expression.resultkind, value, input.now);
|
|
1470
|
+
const summary = `The condition ${condition.expression.result} ${value ? "holds" : "does not hold"} over the extracted values.`;
|
|
1471
|
+
return { scopes, log: [], output: { ok: true, summary, details: { condition: { result: condition.expression.result, value } } } };
|
|
1472
|
+
}
|
|
1473
|
+
case "branch": {
|
|
1474
|
+
const branch = branchof(payload.branch);
|
|
1475
|
+
if (!branch) throw new Error("The branch step needs reviewed unique paths with boolean match expressions and an else path in its options.");
|
|
1476
|
+
const chosen = choosebranch({ stepid: input.step.id, branch, scopes: input.scopes, ...input.pagestate !== void 0 ? { pagestate: input.pagestate } : {}, now: input.now });
|
|
1477
|
+
const body = await runbody({ ...base, steps: chosen.steps });
|
|
1478
|
+
result = body.ok ? { ok: true, scopes: body.scopes, log: body.log, summary: `The branch chose the path ${chosen.outcome.path}: ${chosen.outcome.reason}`, decision: { runid: input.runid ?? "", stepid: input.step.id, kind: "branch", at: input.now, branch: chosen.outcome } } : { ok: false, scopes: body.scopes, log: body.log, summary: `The branch chose the path ${chosen.outcome.path} and its body failed: ${body.failure?.summary ?? "the body step failed."}`, decision: { runid: input.runid ?? "", stepid: input.step.id, kind: "branch", at: input.now, branch: chosen.outcome } };
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1481
|
+
case "loop": {
|
|
1482
|
+
const loop = loopof(payload.loop);
|
|
1483
|
+
if (!loop) throw new Error("The loop step needs a reviewed list variable, distinct item and index variables, an optional positive safety bound and a non-empty body in its options.");
|
|
1484
|
+
result = await runloop({ ...base, loop });
|
|
1485
|
+
break;
|
|
1486
|
+
}
|
|
1487
|
+
case "repeatuntil": {
|
|
1488
|
+
const repeat = repeatuntilof(payload.repeatuntil);
|
|
1489
|
+
if (!repeat) throw new Error("The repeat until step needs a reviewed convergence expression, an optional positive safety bound and a non-empty body in its options.");
|
|
1490
|
+
result = await runrepeatuntil({ ...base, repeat });
|
|
1491
|
+
break;
|
|
1492
|
+
}
|
|
1493
|
+
case "whileloop": {
|
|
1494
|
+
const condition = whileof(payload.while);
|
|
1495
|
+
if (!condition) throw new Error("The while step needs a reviewed condition, a mandatory positive safety bound and a non-empty body in its options.");
|
|
1496
|
+
result = await runwhile({ ...base, condition });
|
|
1497
|
+
break;
|
|
1498
|
+
}
|
|
1499
|
+
case "foreach": {
|
|
1500
|
+
const foreach = foreachof(payload.foreach);
|
|
1501
|
+
if (!foreach) throw new Error("The foreach step needs a reviewed non-empty selector, distinct item and index variables and a non-empty body in its options.");
|
|
1502
|
+
result = await runforeach({ ...base, foreach, ...input.resolveelements !== void 0 ? { resolveelements: input.resolveelements } : {} });
|
|
1503
|
+
break;
|
|
1504
|
+
}
|
|
1505
|
+
case "parallel": {
|
|
1506
|
+
const parallel = parallelof(payload.parallel);
|
|
1507
|
+
if (!parallel) throw new Error("The parallel step needs uniquely identified branches with bodies and a join policy of the first, last or fail strategy with cancel or continue on branch failure in its options.");
|
|
1508
|
+
result = await runparallel({ ...base, parallel });
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
case "trycatch": {
|
|
1512
|
+
const fragile = tryof(payload.try);
|
|
1513
|
+
if (!fragile) throw new Error("The try step needs a fragile body, a catch handler and optional retry and timeout policies in its options.");
|
|
1514
|
+
result = await runtry({ ...base, fragile });
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
default:
|
|
1518
|
+
throw new Error(`The ${input.step.kind} step is not a control flow kind.`);
|
|
1519
|
+
}
|
|
1520
|
+
if (result.decision !== void 0 && input.runid !== void 0) result.decision.runid = input.runid;
|
|
1521
|
+
return { scopes: result.scopes, log: result.log, output: { ok: result.ok, summary: result.summary, ...result.decision !== void 0 ? { details: { control: result.decision } } : {} } };
|
|
1522
|
+
}
|
|
1523
|
+
function controlexecutor(execute, extras = {}) {
|
|
1524
|
+
return async (step, context) => {
|
|
1525
|
+
if (!iscontrolflowkind(step.kind)) return execute(step, context);
|
|
1526
|
+
const result = await runcontrolstep({ step, scopes: context.scopes, outputs: context.outputs ?? {}, execute, now: Date.now(), ...extras.runid !== void 0 ? { runid: extras.runid } : {}, ...extras.pagestate !== void 0 ? { pagestate: extras.pagestate } : {}, ...extras.resolveelements !== void 0 ? { resolveelements: extras.resolveelements } : {} });
|
|
1527
|
+
return { ...result.output, scopes: result.scopes, log: result.log };
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
|
|
336
1531
|
// emulation.ts
|
|
337
1532
|
var emulationkinds = ["emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "blackboxscripts"];
|
|
338
1533
|
var browserpermissions = ["geolocation", "notifications", "camera", "microphone", "clipboard-read", "clipboard-write", "midi", "persistent-storage"];
|
|
@@ -3070,6 +4265,104 @@ var sessionmemory = class {
|
|
|
3070
4265
|
async setcrashflag(value) {
|
|
3071
4266
|
return this.adapter.set("crashed", value);
|
|
3072
4267
|
}
|
|
4268
|
+
/** Stores one composed workflow record version with its timestamp; re-composing the same version replaces it while older versions survive for the audit trail. */
|
|
4269
|
+
async addworkflowrecord(record2) {
|
|
4270
|
+
const records = await this.getworkflowrecordversions();
|
|
4271
|
+
const remaining = records.filter((entry) => !(entry.id === record2.id && entry.version === record2.version));
|
|
4272
|
+
await this.adapter.set("workflowrecords", [record2, ...remaining]);
|
|
4273
|
+
}
|
|
4274
|
+
/** Returns every stored workflow record version, newest first. */
|
|
4275
|
+
async getworkflowrecordversions() {
|
|
4276
|
+
return await this.adapter.get("workflowrecords") ?? [];
|
|
4277
|
+
}
|
|
4278
|
+
/** Returns the latest stored version of one workflow record. */
|
|
4279
|
+
async getworkflowrecord(id) {
|
|
4280
|
+
return (await this.getworkflowrecordversions()).find((entry) => entry.id === id);
|
|
4281
|
+
}
|
|
4282
|
+
/** Lists the saved workflow records, the latest version of each, newest first. */
|
|
4283
|
+
async listworkflows() {
|
|
4284
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4285
|
+
const latest = [];
|
|
4286
|
+
for (const entry of await this.getworkflowrecordversions()) {
|
|
4287
|
+
if (seen.has(entry.id)) continue;
|
|
4288
|
+
seen.add(entry.id);
|
|
4289
|
+
latest.push(entry);
|
|
4290
|
+
}
|
|
4291
|
+
return latest;
|
|
4292
|
+
}
|
|
4293
|
+
/** Stores one workflow run with its state transition; a run replace keeps the full runlog of the same id. */
|
|
4294
|
+
async setworkflowrun(run) {
|
|
4295
|
+
const runs = await this.listworkflowruns();
|
|
4296
|
+
const remaining = runs.filter((entry) => entry.id !== run.id);
|
|
4297
|
+
await this.adapter.set("workflowruns", [run, ...remaining]);
|
|
4298
|
+
}
|
|
4299
|
+
/** Returns every stored workflow run, newest first. */
|
|
4300
|
+
async listworkflowruns() {
|
|
4301
|
+
return await this.adapter.get("workflowruns") ?? [];
|
|
4302
|
+
}
|
|
4303
|
+
/** Returns one run with its full step outcome list so the panel shows the timeline after and during a run. */
|
|
4304
|
+
async getrun(id) {
|
|
4305
|
+
const run = (await this.listworkflowruns()).find((entry) => entry.id === id);
|
|
4306
|
+
if (!run) return void 0;
|
|
4307
|
+
return { run, log: await this.getrunlog(id) };
|
|
4308
|
+
}
|
|
4309
|
+
/** Records one runlog entry of a run; the runlog retention window is a user setting and an absent window keeps every entry. */
|
|
4310
|
+
async addrunlogentry(runid, entry) {
|
|
4311
|
+
const entries = await this.getrunlog(runid);
|
|
4312
|
+
const combined = [...entries, entry];
|
|
4313
|
+
const retention = (await this.getsettings())?.runlogretention;
|
|
4314
|
+
await this.adapter.set(`runlog${runid}`, retention === void 0 ? combined : combined.slice(-retention));
|
|
4315
|
+
}
|
|
4316
|
+
/** Returns the runlog of one run, oldest first. */
|
|
4317
|
+
async getrunlog(runid) {
|
|
4318
|
+
return await this.adapter.get(`runlog${runid}`) ?? [];
|
|
4319
|
+
}
|
|
4320
|
+
/** Stores the variable values per scope of one run for inspection after the run. */
|
|
4321
|
+
async setrunscopes(runid, scopes) {
|
|
4322
|
+
return this.adapter.set(`runscopes${runid}`, scopes);
|
|
4323
|
+
}
|
|
4324
|
+
/** Returns the variable scopes of one run, oldest first. */
|
|
4325
|
+
async getrunscopes(runid) {
|
|
4326
|
+
return await this.adapter.get(`runscopes${runid}`) ?? [];
|
|
4327
|
+
}
|
|
4328
|
+
/** Records one provenance entry of a run: an expression result or a regex capture with its name, value and time. */
|
|
4329
|
+
async addworkflowprovenance(runid, entry) {
|
|
4330
|
+
const entries = await this.getworkflowprovenance(runid);
|
|
4331
|
+
await this.adapter.set(`workflowprovenance${runid}`, [...entries, entry]);
|
|
4332
|
+
}
|
|
4333
|
+
/** Returns every provenance entry of one run, oldest first. */
|
|
4334
|
+
async getworkflowprovenance(runid) {
|
|
4335
|
+
return await this.adapter.get(`workflowprovenance${runid}`) ?? [];
|
|
4336
|
+
}
|
|
4337
|
+
/** Stores one shareable step template under its unique name. */
|
|
4338
|
+
async addsteptemplate(template) {
|
|
4339
|
+
const templates = (await this.getsteptemplates()).filter((entry) => entry.name !== template.name);
|
|
4340
|
+
await this.adapter.set("steptemplates", [template, ...templates]);
|
|
4341
|
+
}
|
|
4342
|
+
/** Returns every stored step template, newest first. */
|
|
4343
|
+
async getsteptemplates() {
|
|
4344
|
+
return await this.adapter.get("steptemplates") ?? [];
|
|
4345
|
+
}
|
|
4346
|
+
/** Records one control flow decision of a run — a branch choice with its reason, the loop counters of an iteration trail, the retry attempts with their backoff durations, the timeout aborts with the exceeded budget, the join record with its strategy and conflicts or the catch handler execution — so the audit trail keeps every control flow turn. */
|
|
4347
|
+
async addcontroldecision(runid, decision) {
|
|
4348
|
+
const decisions = await this.listcontroldecisions(runid);
|
|
4349
|
+
await this.adapter.set(`controldecisions${runid}`, [...decisions, { ...decision, runid }]);
|
|
4350
|
+
}
|
|
4351
|
+
/** Returns every stored control flow decision of one run, oldest first. */
|
|
4352
|
+
async listcontroldecisions(runid) {
|
|
4353
|
+
return await this.adapter.get(`controldecisions${runid}`) ?? [];
|
|
4354
|
+
}
|
|
4355
|
+
/** Returns the past branch decisions of one workflow across every stored run, oldest first, so review can compare branch paths over time. */
|
|
4356
|
+
async getbranchhistory(workflowid) {
|
|
4357
|
+
const runs = await this.listworkflowruns();
|
|
4358
|
+
const ordered = [...runs].reverse().filter((run) => run.workflowid === workflowid);
|
|
4359
|
+
const history = [];
|
|
4360
|
+
for (const run of ordered) {
|
|
4361
|
+
const decisions = await this.listcontroldecisions(run.id);
|
|
4362
|
+
for (const decision of decisions) if (decision.kind === "branch" && decision.branch !== void 0) history.push(decision.branch);
|
|
4363
|
+
}
|
|
4364
|
+
return history;
|
|
4365
|
+
}
|
|
3073
4366
|
};
|
|
3074
4367
|
function mediakindof(record2) {
|
|
3075
4368
|
if ("pages" in record2) return "pdf";
|
|
@@ -4016,9 +5309,9 @@ function polldecision(input) {
|
|
|
4016
5309
|
}
|
|
4017
5310
|
|
|
4018
5311
|
// policy.ts
|
|
4019
|
-
var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript", "heapshot", "profilecpu", "capturesourcemaps", "emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "restoresession", "exportsessions", "importsessions"]);
|
|
4020
|
-
var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction", "capturebodies", "setbreakpoint", "stepcode", "watchexpr"]);
|
|
4021
|
-
var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet", "capturepdf", "captureframe", "readmedia", "readassets", "probestream", "timelapse", "shotcanvas", "convertimage", "makethumbs", "fetchurl", "parsejson", "parsehtml", "opensocket", "waitmessage", "watchrequests", "readheaders", "mapapi", "subscribesse", "longpoll", "extractapi", "readcookies", "watchconsole", "watcherrors", "watchtasks", "watchcdp", "measureflow", "trackmemory", "watchshifts", "traceload", "annotatetrace", "replaytrace", "blackboxscripts", "persiststate", "capturesession", "namedsessions", "diffsessions", "searchsessions"]);
|
|
5312
|
+
var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript", "heapshot", "profilecpu", "capturesourcemaps", "emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "restoresession", "exportsessions", "importsessions", "runworkflow"]);
|
|
5313
|
+
var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction", "capturebodies", "setbreakpoint", "stepcode", "watchexpr", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"]);
|
|
5314
|
+
var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet", "capturepdf", "captureframe", "readmedia", "readassets", "probestream", "timelapse", "shotcanvas", "convertimage", "makethumbs", "fetchurl", "parsejson", "parsehtml", "opensocket", "waitmessage", "watchrequests", "readheaders", "mapapi", "subscribesse", "longpoll", "extractapi", "readcookies", "watchconsole", "watcherrors", "watchtasks", "watchcdp", "measureflow", "trackmemory", "watchshifts", "traceload", "annotatetrace", "replaytrace", "blackboxscripts", "persiststate", "capturesession", "namedsessions", "diffsessions", "searchsessions", "composeworkflow", "savetemplate", "dryrun", "delay", "waitelement", "compute", "extractvars", "condition", "branch"]);
|
|
4022
5315
|
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
4023
5316
|
var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
|
|
4024
5317
|
var targetactions = /* @__PURE__ */ new Set(["inspect", "focus", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "deriveselector", "fingerprintsection", "submitform", "retryform", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "scrapetable", "paginateextract", "shotelement", "captureframe", "shotcanvas"]);
|
|
@@ -4039,6 +5332,7 @@ var cdpactions = /* @__PURE__ */ new Set(["attachcdp", "detachcdp", "cdpcmd", "w
|
|
|
4039
5332
|
var profileractions = /* @__PURE__ */ new Set(["measureflow", "heapshot", "trackmemory", "profilecpu", "watchshifts", "traceload", "annotatetrace", "replaytrace", "capturesourcemaps"]);
|
|
4040
5333
|
var emulationactions = /* @__PURE__ */ new Set(["emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "blackboxscripts"]);
|
|
4041
5334
|
var sessionactions = /* @__PURE__ */ new Set(["persiststate", "capturesession", "restoresession", "namedsessions", "diffsessions", "searchsessions", "exportsessions", "importsessions"]);
|
|
5335
|
+
var workflowactions = /* @__PURE__ */ new Set(["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars", "condition", "branch", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"]);
|
|
4042
5336
|
var credentialheaders = /* @__PURE__ */ new Set(["authorization", "proxy-authorization", "cookie", "cookie2", "set-cookie", "api-key", "x-api-key", "x-auth-token", "x-session-token", "proxy-authorization"]);
|
|
4043
5337
|
var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
|
|
4044
5338
|
var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
|
|
@@ -4057,6 +5351,9 @@ function hostpattern(origin) {
|
|
|
4057
5351
|
function issessionkind(kind) {
|
|
4058
5352
|
return sessionactions.has(kind);
|
|
4059
5353
|
}
|
|
5354
|
+
function isworkflowkind(kind) {
|
|
5355
|
+
return workflowactions.has(kind);
|
|
5356
|
+
}
|
|
4060
5357
|
function iswatchkind(kind) {
|
|
4061
5358
|
return watchactions.has(kind);
|
|
4062
5359
|
}
|
|
@@ -5522,6 +6819,238 @@ function sessionfolderunique(name, folders) {
|
|
|
5522
6819
|
function snapshotretentionwindow(settings) {
|
|
5523
6820
|
return settings?.sessionretention;
|
|
5524
6821
|
}
|
|
6822
|
+
function validateworkflowgrammar(step, options) {
|
|
6823
|
+
const kind = step.kind;
|
|
6824
|
+
if (kind === "composeworkflow") {
|
|
6825
|
+
const payload = options.workflow;
|
|
6826
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { allowed: false, reason: "The workflow composition needs the reviewed workflow payload with its name, version, origins, steps and blocks." };
|
|
6827
|
+
const candidate = payload;
|
|
6828
|
+
if (typeof candidate.name !== "string" || !candidate.name.trim()) return { allowed: false, reason: "The workflow composition needs a reviewed non-empty name." };
|
|
6829
|
+
if (typeof candidate.version !== "number" || !Number.isInteger(candidate.version) || candidate.version < 1) return { allowed: false, reason: "The workflow version must be a positive integer." };
|
|
6830
|
+
if (!Array.isArray(candidate.origins) || candidate.origins.length === 0 || !candidate.origins.every((origin) => typeof origin === "string" && origin.startsWith("https://"))) return { allowed: false, reason: "The workflow needs at least one granted HTTPS origin so every step stays inside the grants." };
|
|
6831
|
+
if (!Array.isArray(candidate.steps) || candidate.steps.length === 0 || !candidate.steps.every((entry) => workflowstepof(entry) !== void 0 || entry && typeof entry === "object" && typeof entry.block === "string")) return { allowed: false, reason: "The workflow needs a non-empty reviewed step list of the workflow step grammar or block invocations." };
|
|
6832
|
+
const blocks = Array.isArray(candidate.blocks) ? candidate.blocks.flatMap((block) => {
|
|
6833
|
+
const parsed = workflowblockof(block);
|
|
6834
|
+
return parsed !== void 0 ? [parsed] : [];
|
|
6835
|
+
}) : [];
|
|
6836
|
+
if (Array.isArray(candidate.blocks) && blocks.length !== candidate.blocks.length) return { allowed: false, reason: "The reviewed block list must carry unique lowercase names, labels and valid child steps." };
|
|
6837
|
+
try {
|
|
6838
|
+
const record2 = composeworkflow({ name: candidate.name, version: candidate.version, origins: candidate.origins, steps: candidate.steps.map((entry) => "block" in entry ? { block: entry.block, label: typeof entry.label === "string" ? entry.label : entry.block } : workflowstepof(entry)), blocks, now: 0, kindallowed: (candidatekind) => {
|
|
6839
|
+
try {
|
|
6840
|
+
actionrisk(candidatekind);
|
|
6841
|
+
return true;
|
|
6842
|
+
} catch {
|
|
6843
|
+
return false;
|
|
6844
|
+
}
|
|
6845
|
+
}, riskof: (candidatekind) => actionrisk(candidatekind) });
|
|
6846
|
+
const inputs = Array.isArray(candidate.inputs) ? candidate.inputs.flatMap((name) => typeof name === "string" ? [name] : []) : void 0;
|
|
6847
|
+
const checked = validateworkflow(record2, { kindallowed: (workflowkind) => {
|
|
6848
|
+
try {
|
|
6849
|
+
actionrisk(workflowkind);
|
|
6850
|
+
return true;
|
|
6851
|
+
} catch {
|
|
6852
|
+
return false;
|
|
6853
|
+
}
|
|
6854
|
+
}, ...inputs !== void 0 ? { inputs } : {} });
|
|
6855
|
+
if (!checked.allowed) return checked;
|
|
6856
|
+
} catch (error) {
|
|
6857
|
+
return { allowed: false, reason: error instanceof Error ? error.message : "The workflow payload failed its composition validation." };
|
|
6858
|
+
}
|
|
6859
|
+
return { allowed: true };
|
|
6860
|
+
}
|
|
6861
|
+
if (kind === "savetemplate") {
|
|
6862
|
+
const payload = options.template && typeof options.template === "object" && !Array.isArray(options.template) ? options.template : {};
|
|
6863
|
+
const template = steptemplateof({ id: "templatereview", origin: "https://example.com", sharedat: 0, ...payload });
|
|
6864
|
+
if (!template) return { allowed: false, reason: "The step template needs a reviewed name and a valid workflow step it shares across workflows." };
|
|
6865
|
+
return { allowed: true };
|
|
6866
|
+
}
|
|
6867
|
+
if (kind === "runworkflow") {
|
|
6868
|
+
if (typeof options.workflowid !== "string" || !options.workflowid.trim()) return { allowed: false, reason: "The workflow run needs the reviewed id of the composed workflow." };
|
|
6869
|
+
if (options.reviewed !== true) return { allowed: false, reason: "Every real workflow run needs the explicit run review with its expanded step list shown before the first step executes." };
|
|
6870
|
+
if (options.variables !== void 0 && (!options.variables || typeof options.variables !== "object" || Array.isArray(options.variables) || !Object.values(options.variables).every((value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean"))) return { allowed: false, reason: "The reviewed run variables must be an object of string, number or boolean values." };
|
|
6871
|
+
return { allowed: true };
|
|
6872
|
+
}
|
|
6873
|
+
if (kind === "dryrun") {
|
|
6874
|
+
if (typeof options.workflowid !== "string" || !options.workflowid.trim()) return { allowed: false, reason: "The dry run needs the reviewed id of the composed workflow." };
|
|
6875
|
+
return { allowed: true };
|
|
6876
|
+
}
|
|
6877
|
+
if (kind === "delay") {
|
|
6878
|
+
const delay = options.delay;
|
|
6879
|
+
if (!delay || typeof delay !== "object" || Array.isArray(delay)) return { allowed: false, reason: "The delay needs a reviewed base and jitter window in options." };
|
|
6880
|
+
const reviewed = delay;
|
|
6881
|
+
if (typeof reviewed.base !== "number" || !Number.isFinite(reviewed.base) || reviewed.base < 0) return { allowed: false, reason: "The reviewed delay base must be zero or a positive number of milliseconds." };
|
|
6882
|
+
if (typeof reviewed.jitter !== "number" || !Number.isFinite(reviewed.jitter) || reviewed.jitter < 0) return { allowed: false, reason: "The reviewed delay jitter window must be zero or a positive number of milliseconds with no code ceiling." };
|
|
6883
|
+
return { allowed: true };
|
|
6884
|
+
}
|
|
6885
|
+
if (kind === "waitelement") {
|
|
6886
|
+
const wait = options.wait;
|
|
6887
|
+
if (!wait || typeof wait !== "object" || Array.isArray(wait)) return { allowed: false, reason: "The element wait needs a reviewed selector, timeout and poll interval in options." };
|
|
6888
|
+
const reviewed = wait;
|
|
6889
|
+
if (typeof reviewed.selector !== "string" || !reviewed.selector.trim()) return { allowed: false, reason: "The element wait needs a reviewed non-empty selector." };
|
|
6890
|
+
if (typeof reviewed.timeout !== "number" || !Number.isFinite(reviewed.timeout) || reviewed.timeout < 0) return { allowed: false, reason: "The reviewed element wait timeout must be zero or a positive number of milliseconds with no code ceiling." };
|
|
6891
|
+
if (typeof reviewed.poll !== "number" || !Number.isFinite(reviewed.poll) || reviewed.poll < 0) return { allowed: false, reason: "The reviewed element wait poll interval must be zero or a positive number of milliseconds with no code ceiling." };
|
|
6892
|
+
return { allowed: true };
|
|
6893
|
+
}
|
|
6894
|
+
if (kind === "compute") {
|
|
6895
|
+
const expression = expressionof(options.expression);
|
|
6896
|
+
if (!expression) return { allowed: false, reason: `The expression step needs a reviewed expression with operands, an operator of the reviewed set (${expressionoperators.join(", ")}) and a result variable of a reviewed kind.` };
|
|
6897
|
+
const operatorcheck = validatexpressionoperators(expression);
|
|
6898
|
+
if (!operatorcheck.allowed) return operatorcheck;
|
|
6899
|
+
return { allowed: true };
|
|
6900
|
+
}
|
|
6901
|
+
if (kind === "extractvars") {
|
|
6902
|
+
const rule = regexruleof(options.rule);
|
|
6903
|
+
if (!rule) return { allowed: false, reason: "The variable extraction needs a reviewed regex rule with its pattern, flags and named capture groups." };
|
|
6904
|
+
const shapecheck = validateregexrule(rule.pattern);
|
|
6905
|
+
if (!shapecheck.allowed) return shapecheck;
|
|
6906
|
+
if (typeof options.text !== "string") return { allowed: false, reason: "The variable extraction needs the reviewed text the regex rule applies to." };
|
|
6907
|
+
return { allowed: true };
|
|
6908
|
+
}
|
|
6909
|
+
if (kind === "condition") {
|
|
6910
|
+
const condition = conditionof(options.condition);
|
|
6911
|
+
if (!condition) return { allowed: false, reason: "The condition step needs a reviewed boolean expression in its options." };
|
|
6912
|
+
const operatorcheck = validatexpressionoperators(condition.expression);
|
|
6913
|
+
if (!operatorcheck.allowed) return operatorcheck;
|
|
6914
|
+
return { allowed: true };
|
|
6915
|
+
}
|
|
6916
|
+
if (kind === "branch") {
|
|
6917
|
+
const branch = branchof(options.branch);
|
|
6918
|
+
if (!branch) return { allowed: false, reason: "The branch step needs reviewed unique paths with boolean match expressions and an else path in its options so every branch terminates." };
|
|
6919
|
+
for (const path of [...branch.paths, branch.else]) {
|
|
6920
|
+
if (path.when === void 0) continue;
|
|
6921
|
+
const operatorcheck = validatexpressionoperators(path.when);
|
|
6922
|
+
if (!operatorcheck.allowed) return operatorcheck;
|
|
6923
|
+
}
|
|
6924
|
+
return controlchildkinds(step);
|
|
6925
|
+
}
|
|
6926
|
+
if (kind === "loop") {
|
|
6927
|
+
const loop = loopof(options.loop);
|
|
6928
|
+
if (!loop) return { allowed: false, reason: "The loop step needs a reviewed list variable, distinct item and index variables, an optional positive safety bound and a non-empty body in its options; an absent bound keeps the documented default." };
|
|
6929
|
+
return controlchildkinds(step);
|
|
6930
|
+
}
|
|
6931
|
+
if (kind === "repeatuntil") {
|
|
6932
|
+
const repeat = repeatuntilof(options.repeatuntil);
|
|
6933
|
+
if (!repeat) return { allowed: false, reason: "The repeat until step needs a reviewed convergence expression, an optional positive safety bound and a non-empty body in its options." };
|
|
6934
|
+
const operatorcheck = validatexpressionoperators(repeat.until);
|
|
6935
|
+
if (!operatorcheck.allowed) return operatorcheck;
|
|
6936
|
+
return controlchildkinds(step);
|
|
6937
|
+
}
|
|
6938
|
+
if (kind === "whileloop") {
|
|
6939
|
+
const condition = whileof(options.while);
|
|
6940
|
+
if (!condition) return { allowed: false, reason: "The while step needs a reviewed condition, a mandatory positive safety bound and a non-empty body in its options; a while loop without a safety bound is refused." };
|
|
6941
|
+
const operatorcheck = validatexpressionoperators(condition.while);
|
|
6942
|
+
if (!operatorcheck.allowed) return operatorcheck;
|
|
6943
|
+
return controlchildkinds(step);
|
|
6944
|
+
}
|
|
6945
|
+
if (kind === "foreach") {
|
|
6946
|
+
const foreach = foreachof(options.foreach);
|
|
6947
|
+
if (!foreach) return { allowed: false, reason: "The foreach step needs a reviewed non-empty selector, distinct item and index variables and a non-empty body in its options." };
|
|
6948
|
+
return controlchildkinds(step);
|
|
6949
|
+
}
|
|
6950
|
+
if (kind === "parallel") {
|
|
6951
|
+
const parallel = parallelof(options.parallel);
|
|
6952
|
+
if (!parallel) return { allowed: false, reason: "The parallel step needs uniquely identified branches with bodies and a join policy of the first, last or fail strategy with cancel or continue on branch failure in its options." };
|
|
6953
|
+
return controlchildkinds(step);
|
|
6954
|
+
}
|
|
6955
|
+
if (kind === "trycatch") {
|
|
6956
|
+
const fragile = tryof(options.try);
|
|
6957
|
+
if (!fragile) return { allowed: false, reason: "The try step needs a fragile body, a catch handler and optional retry and timeout policies in its options: attempts stay user configured with no code ceiling, backoff is fixed or exponential and budgets are positive." };
|
|
6958
|
+
return controlchildkinds(step);
|
|
6959
|
+
}
|
|
6960
|
+
return { allowed: true };
|
|
6961
|
+
}
|
|
6962
|
+
function controlchildkinds(step) {
|
|
6963
|
+
const children = controlsteps({ id: step.id, kind: step.kind, label: step.summary, ...step.options !== void 0 ? { options: step.options } : {} });
|
|
6964
|
+
for (const child of children) {
|
|
6965
|
+
try {
|
|
6966
|
+
actionrisk(child.kind);
|
|
6967
|
+
} catch {
|
|
6968
|
+
return { allowed: false, reason: `The ${child.kind} step inside the control payload of the ${step.kind} step is not a reviewed action kind.` };
|
|
6969
|
+
}
|
|
6970
|
+
}
|
|
6971
|
+
return { allowed: true };
|
|
6972
|
+
}
|
|
6973
|
+
function validateregexrule(pattern) {
|
|
6974
|
+
try {
|
|
6975
|
+
new RegExp(pattern);
|
|
6976
|
+
} catch {
|
|
6977
|
+
return { allowed: false, reason: "The reviewed regex pattern does not compile." };
|
|
6978
|
+
}
|
|
6979
|
+
const nestedquantifier = /\((?:[^()\\]|\\.)*[+*}]\)[+*{]/.test(pattern) || /\(\)[+*{]/.test(pattern);
|
|
6980
|
+
if (nestedquantifier) return { allowed: false, reason: "The reviewed regex pattern nests an unbounded quantifier inside a quantified group and is refused because adversarial text could explode the backtracking." };
|
|
6981
|
+
const unboundedrepeat = /\{\d+,\}/.test(pattern);
|
|
6982
|
+
if (unboundedrepeat && /\([^)]*\{\d+,\}[^)]*\)[+*{]/.test(pattern)) return { allowed: false, reason: "The reviewed regex pattern repeats an unbounded group and is refused because adversarial text could explode the backtracking." };
|
|
6983
|
+
return { allowed: true };
|
|
6984
|
+
}
|
|
6985
|
+
function validatexpressionoperators(expression) {
|
|
6986
|
+
const numeric = /* @__PURE__ */ new Set(["add", "subtract", "multiply", "divide", "modulo"]);
|
|
6987
|
+
const logic = /* @__PURE__ */ new Set(["and", "or", "not"]);
|
|
6988
|
+
const comparison = /* @__PURE__ */ new Set(["less", "greater", "lessequal", "greaterequal"]);
|
|
6989
|
+
const text2 = /* @__PURE__ */ new Set(["concat", "contains"]);
|
|
6990
|
+
const operator = expression.operator;
|
|
6991
|
+
if (numeric.has(operator)) {
|
|
6992
|
+
for (const operand of [expression.left, expression.right]) {
|
|
6993
|
+
if (operand === void 0) continue;
|
|
6994
|
+
if (operand.literal !== void 0 && typeof operand.literal === "boolean") return { allowed: false, reason: `The ${operator} operator needs numeric operands; boolean literals are refused.` };
|
|
6995
|
+
}
|
|
6996
|
+
if (expression.resultkind !== "number" && expression.resultkind !== "string") return { allowed: false, reason: `The ${operator} operator needs a number result kind.` };
|
|
6997
|
+
}
|
|
6998
|
+
if (logic.has(operator)) {
|
|
6999
|
+
for (const operand of [expression.left, expression.right]) {
|
|
7000
|
+
if (operand === void 0) continue;
|
|
7001
|
+
if (operand.literal !== void 0 && typeof operand.literal !== "boolean") return { allowed: false, reason: `The ${operator} operator needs boolean operands; non boolean literals are refused.` };
|
|
7002
|
+
}
|
|
7003
|
+
if (expression.resultkind !== "boolean") return { allowed: false, reason: `The ${operator} operator needs a boolean result kind.` };
|
|
7004
|
+
if (operator === "not" && expression.right !== void 0) return { allowed: false, reason: "The not operator takes one operand only." };
|
|
7005
|
+
}
|
|
7006
|
+
if (comparison.has(operator) && expression.resultkind !== "boolean") return { allowed: false, reason: `The ${operator} operator needs a boolean result kind.` };
|
|
7007
|
+
if (text2.has(operator) && expression.resultkind !== "boolean" && expression.resultkind !== "string") return { allowed: false, reason: `The ${operator} operator needs a string or boolean result kind.` };
|
|
7008
|
+
if (operator === "contains" && expression.resultkind !== "boolean") return { allowed: false, reason: "The contains operator needs a boolean result kind." };
|
|
7009
|
+
if (operator === "length") {
|
|
7010
|
+
if (expression.right !== void 0) return { allowed: false, reason: "The length operator takes one operand only." };
|
|
7011
|
+
if (expression.resultkind !== "number") return { allowed: false, reason: "The length operator needs a number result kind." };
|
|
7012
|
+
}
|
|
7013
|
+
if ((operator === "equal" || operator === "notequal") && !(/* @__PURE__ */ new Set(["boolean", "string", "number"])).has(expression.resultkind)) return { allowed: false, reason: "The equality operator needs a primitive result kind." };
|
|
7014
|
+
return { allowed: true };
|
|
7015
|
+
}
|
|
7016
|
+
function workflowgate(input) {
|
|
7017
|
+
const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: "run the workflow step" });
|
|
7018
|
+
if (!gate.allowed) return gate;
|
|
7019
|
+
if (!input.plan || input.plan.state !== "approved") return { allowed: false, reason: "Workflow steps need the approved plan review before they run." };
|
|
7020
|
+
if (input.step.kind === "runworkflow") {
|
|
7021
|
+
let runoptions = {};
|
|
7022
|
+
try {
|
|
7023
|
+
runoptions = parseoptions(input.step);
|
|
7024
|
+
} catch {
|
|
7025
|
+
runoptions = {};
|
|
7026
|
+
}
|
|
7027
|
+
if (runoptions.reviewed !== true) return { allowed: false, reason: "Every real workflow run needs the explicit run review with its expanded step list shown before the first step executes." };
|
|
7028
|
+
}
|
|
7029
|
+
return { allowed: true };
|
|
7030
|
+
}
|
|
7031
|
+
function dryrunprojection(step) {
|
|
7032
|
+
if (iscontrolflowkind(step.kind)) {
|
|
7033
|
+
for (const child of controlsteps(step)) {
|
|
7034
|
+
const childrisk = resolvedrisk({ id: child.id, kind: child.kind, summary: child.label, risk: "read", ...child.target !== void 0 ? { target: child.target } : {}, ...child.value !== void 0 ? { value: child.value } : {}, ...child.options !== void 0 ? { options: child.options } : {} });
|
|
7035
|
+
if (childrisk !== "read") return void 0;
|
|
7036
|
+
}
|
|
7037
|
+
if (step.kind === "condition") return "The condition step would evaluate its reviewed expression over the extracted values with no page side effect.";
|
|
7038
|
+
if (step.kind === "branch") return "The branch step would choose one reviewed path by page state and only the chosen path would run.";
|
|
7039
|
+
if (step.kind === "loop") return "The loop step would iterate its reviewed list binding the item and index variables per iteration inside the safety bound.";
|
|
7040
|
+
if (step.kind === "repeatuntil") return "The repeat until step would rerun its body until the convergence expression holds inside the safety bound.";
|
|
7041
|
+
if (step.kind === "whileloop") return "The while step would loop while its condition holds inside the reviewed safety bound.";
|
|
7042
|
+
if (step.kind === "foreach") return "The foreach step would iterate the elements of its reviewed selector binding the item and index variables per iteration.";
|
|
7043
|
+
if (step.kind === "parallel") return "The parallel step would run its branches concurrently and join their outcomes under the reviewed strategy.";
|
|
7044
|
+
return "The try step would run its fragile body and only the catch handler on failure.";
|
|
7045
|
+
}
|
|
7046
|
+
const risk = resolvedrisk({ id: step.id, kind: step.kind, summary: step.label, risk: "read", ...step.target !== void 0 ? { target: step.target } : {}, ...step.value !== void 0 ? { value: step.value } : {}, ...step.options !== void 0 ? { options: step.options } : {} });
|
|
7047
|
+
if (risk !== "read") return void 0;
|
|
7048
|
+
if (step.kind === "delay") return `The delay step would sleep its reviewed base inside the jitter window.`;
|
|
7049
|
+
if (step.kind === "waitelement") return `The element wait step would poll ${step.target ?? "the reviewed selector"} until appearance or the reviewed timeout.`;
|
|
7050
|
+
if (step.kind === "compute") return `The compute step would evaluate its reviewed expression into the result variable.`;
|
|
7051
|
+
if (step.kind === "extractvars") return `The variable extraction step would apply its reviewed regex rule and store the named captures.`;
|
|
7052
|
+
return `The ${step.kind} step would run read only and mutate nothing.`;
|
|
7053
|
+
}
|
|
5525
7054
|
function permissionstatevalid(state) {
|
|
5526
7055
|
if (!permissionstates.includes(state)) return { allowed: false, reason: `The reviewed permission state must be one of ${permissionstates.join(", ")}.` };
|
|
5527
7056
|
return { allowed: true };
|
|
@@ -6091,6 +7620,10 @@ function validatestep(step, origin) {
|
|
|
6091
7620
|
const sessioncheck = validatesessiongrammar(step, options);
|
|
6092
7621
|
if (!sessioncheck.allowed) return sessioncheck;
|
|
6093
7622
|
}
|
|
7623
|
+
if (isworkflowkind(step.kind)) {
|
|
7624
|
+
const workflowcheck = validateworkflowgrammar(step, options);
|
|
7625
|
+
if (!workflowcheck.allowed) return workflowcheck;
|
|
7626
|
+
}
|
|
6094
7627
|
if (step.kind === "tabcreate") {
|
|
6095
7628
|
if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
|
|
6096
7629
|
if (options.window !== void 0 && (typeof options.window !== "number" || !Number.isInteger(options.window) || options.window < 0)) return { allowed: false, reason: "The reviewed target window id must be a non-negative integer." };
|
|
@@ -6281,33 +7814,37 @@ function canexecute(input) {
|
|
|
6281
7814
|
}
|
|
6282
7815
|
}
|
|
6283
7816
|
}
|
|
7817
|
+
if (isworkflowkind(input.step.kind)) {
|
|
7818
|
+
const workflowgatecheck = workflowgate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });
|
|
7819
|
+
if (!workflowgatecheck.allowed) return workflowgatecheck;
|
|
7820
|
+
}
|
|
6284
7821
|
if (iscontrolkind(input.step.kind)) {
|
|
6285
7822
|
const controlgate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: "control the network" });
|
|
6286
7823
|
if (!controlgate.allowed) return controlgate;
|
|
6287
|
-
let
|
|
7824
|
+
let controloptions2 = {};
|
|
6288
7825
|
try {
|
|
6289
|
-
|
|
7826
|
+
controloptions2 = parseoptions(input.step);
|
|
6290
7827
|
} catch {
|
|
6291
|
-
|
|
7828
|
+
controloptions2 = {};
|
|
6292
7829
|
}
|
|
6293
7830
|
if (input.step.kind === "blockrequest") {
|
|
6294
7831
|
const blockgatecheck = blockgate(input.session, input.step, now);
|
|
6295
7832
|
if (!blockgatecheck.allowed) return blockgatecheck;
|
|
6296
|
-
const rule = blockruleof(
|
|
7833
|
+
const rule = blockruleof(controloptions2.block);
|
|
6297
7834
|
if (rule) {
|
|
6298
7835
|
const blockorigin = origincheck(input.session, rule.urlpattern);
|
|
6299
7836
|
if (!blockorigin.allowed) return blockorigin;
|
|
6300
7837
|
}
|
|
6301
7838
|
}
|
|
6302
7839
|
if (input.step.kind === "mockresponse" || input.step.kind === "rewriteheaders") {
|
|
6303
|
-
const patterns = input.step.kind === "mockresponse" ? [mockspecof(
|
|
7840
|
+
const patterns = input.step.kind === "mockresponse" ? [mockspecof(controloptions2.mock)?.urlpattern ?? ""] : Array.isArray(controloptions2.rules) ? controloptions2.rules.map((item) => item && typeof item === "object" && !Array.isArray(item) ? String(item.urlpattern ?? "") : "") : [];
|
|
6304
7841
|
for (const pattern of patterns) {
|
|
6305
7842
|
const patterngate = origincheck(input.session, pattern);
|
|
6306
7843
|
if (!patterngate.allowed) return patterngate;
|
|
6307
7844
|
}
|
|
6308
7845
|
}
|
|
6309
7846
|
if (input.step.kind === "setcookies" || input.step.kind === "readcookies" || input.step.kind === "clearcookies") {
|
|
6310
|
-
const domain = typeof
|
|
7847
|
+
const domain = typeof controloptions2.domain === "string" && controloptions2.domain.trim() ? controloptions2.domain : Array.isArray(controloptions2.cookies) ? String(controloptions2.cookies[0]?.domain ?? "") : "";
|
|
6311
7848
|
if (!domain) return { allowed: false, reason: "A reviewed cookie domain is required before cookie control runs." };
|
|
6312
7849
|
const cookiegatecheck = cookiegate(input.session, domain, now);
|
|
6313
7850
|
if (!cookiegatecheck.allowed) return cookiegatecheck;
|
|
@@ -6364,7 +7901,7 @@ function canexecute(input) {
|
|
|
6364
7901
|
}
|
|
6365
7902
|
|
|
6366
7903
|
// version.ts
|
|
6367
|
-
var packageversion = "1.1.
|
|
7904
|
+
var packageversion = "1.1.51";
|
|
6368
7905
|
|
|
6369
7906
|
// types.ts
|
|
6370
7907
|
var protocolversion = packageversion;
|
|
@@ -6580,6 +8117,29 @@ function parseproposal(value, origin, grants) {
|
|
|
6580
8117
|
}
|
|
6581
8118
|
if (step.kind === "importsessions" && importsessionfile(sessionoptions.file) === void 0) throw new Error("Session import files of unknown format versions are refused.");
|
|
6582
8119
|
}
|
|
8120
|
+
if (isworkflowkind(step.kind)) {
|
|
8121
|
+
let workflowoptions = {};
|
|
8122
|
+
try {
|
|
8123
|
+
workflowoptions = parseoptions(step);
|
|
8124
|
+
} catch {
|
|
8125
|
+
workflowoptions = {};
|
|
8126
|
+
}
|
|
8127
|
+
if (step.kind === "composeworkflow") {
|
|
8128
|
+
const payload = workflowoptions.workflow && typeof workflowoptions.workflow === "object" && !Array.isArray(workflowoptions.workflow) ? workflowoptions.workflow : void 0;
|
|
8129
|
+
const origins = payload && Array.isArray(payload.origins) ? payload.origins.filter((originvalue) => typeof originvalue === "string") : [];
|
|
8130
|
+
for (const workfloworigin of origins) {
|
|
8131
|
+
const granted = covered.some((pattern) => {
|
|
8132
|
+
try {
|
|
8133
|
+
return new URL(workfloworigin).origin === new URL(pattern).origin;
|
|
8134
|
+
} catch {
|
|
8135
|
+
return false;
|
|
8136
|
+
}
|
|
8137
|
+
});
|
|
8138
|
+
if (!granted) throw new Error(`The workflow origin ${workfloworigin} stays outside the grants.`);
|
|
8139
|
+
}
|
|
8140
|
+
}
|
|
8141
|
+
if (step.kind === "runworkflow" && workflowoptions.reviewed !== true) throw new Error("Workflow runs without the explicit run review of the expanded step list are refused.");
|
|
8142
|
+
}
|
|
6583
8143
|
const evaluation = validatestep(step, origin);
|
|
6584
8144
|
if (!evaluation.allowed) throw new Error(evaluation.reason);
|
|
6585
8145
|
const target = outboundtarget(step);
|
|
@@ -6639,6 +8199,74 @@ function parseproposal(value, origin, grants) {
|
|
|
6639
8199
|
};
|
|
6640
8200
|
return { version: protocolversion, plan };
|
|
6641
8201
|
}
|
|
8202
|
+
function parseworkflowproposal(value, origin, grants, dryrun) {
|
|
8203
|
+
const root = record(value);
|
|
8204
|
+
if (root.version !== protocolversion) throw new Error("Unsupported protocol version.");
|
|
8205
|
+
const covered = grants !== void 0 && grants.length > 0 ? grants : [origin];
|
|
8206
|
+
const candidate = record(root.workflow);
|
|
8207
|
+
const name = text(candidate.name, "workflow name");
|
|
8208
|
+
const version = typeof candidate.version === "number" && Number.isInteger(candidate.version) && candidate.version >= 1 ? candidate.version : void 0;
|
|
8209
|
+
if (version === void 0) throw new Error("The workflow version must be a positive integer.");
|
|
8210
|
+
const origins = Array.isArray(candidate.origins) ? candidate.origins : [];
|
|
8211
|
+
if (origins.length === 0 || !origins.every((workfloworigin) => typeof workfloworigin === "string" && workfloworigin.startsWith("https://"))) throw new Error("The workflow needs at least one granted HTTPS origin.");
|
|
8212
|
+
for (const workfloworigin of origins) {
|
|
8213
|
+
const granted = covered.some((pattern) => {
|
|
8214
|
+
try {
|
|
8215
|
+
return new URL(workfloworigin).origin === new URL(pattern).origin;
|
|
8216
|
+
} catch {
|
|
8217
|
+
return false;
|
|
8218
|
+
}
|
|
8219
|
+
});
|
|
8220
|
+
if (!granted) throw new Error(`The workflow origin ${workfloworigin} stays outside the grants.`);
|
|
8221
|
+
}
|
|
8222
|
+
const steps = Array.isArray(candidate.steps) ? candidate.steps : [];
|
|
8223
|
+
if (steps.length === 0) throw new Error("A workflow proposal needs at least one step or block invocation.");
|
|
8224
|
+
const blocks = Array.isArray(candidate.blocks) ? candidate.blocks.flatMap((block) => workflowblockof(block) !== void 0 ? [workflowblockof(block)] : []) : [];
|
|
8225
|
+
if (Array.isArray(candidate.blocks) && blocks.length !== candidate.blocks.length) throw new Error("The reviewed block list must carry unique lowercase names, labels and valid child steps.");
|
|
8226
|
+
const composed = composeworkflow({
|
|
8227
|
+
name,
|
|
8228
|
+
version,
|
|
8229
|
+
origins,
|
|
8230
|
+
steps: steps.map((entry) => {
|
|
8231
|
+
const step = workflowstepof(entry);
|
|
8232
|
+
if (step) return step;
|
|
8233
|
+
const invocation = blockinvocationof(entry);
|
|
8234
|
+
if (invocation) return invocation;
|
|
8235
|
+
throw new Error("Every workflow entry must be a reviewed step or a block invocation.");
|
|
8236
|
+
}),
|
|
8237
|
+
blocks,
|
|
8238
|
+
now: Date.now(),
|
|
8239
|
+
kindallowed: (kind) => {
|
|
8240
|
+
try {
|
|
8241
|
+
actionrisk(kind);
|
|
8242
|
+
return true;
|
|
8243
|
+
} catch {
|
|
8244
|
+
return false;
|
|
8245
|
+
}
|
|
8246
|
+
},
|
|
8247
|
+
riskof: (kind) => actionrisk(kind)
|
|
8248
|
+
});
|
|
8249
|
+
const inputs = Array.isArray(candidate.inputs) ? candidate.inputs.flatMap((inputname) => typeof inputname === "string" ? [inputname] : []) : void 0;
|
|
8250
|
+
const checked = validateworkflow(composed, { kindallowed: (kind) => {
|
|
8251
|
+
try {
|
|
8252
|
+
actionrisk(kind);
|
|
8253
|
+
return true;
|
|
8254
|
+
} catch {
|
|
8255
|
+
return false;
|
|
8256
|
+
}
|
|
8257
|
+
}, ...inputs !== void 0 ? { inputs } : {} });
|
|
8258
|
+
if (!checked.allowed) throw new Error(checked.reason ?? "The workflow proposal failed its validation.");
|
|
8259
|
+
const control = composed.steps.flatMap((step) => {
|
|
8260
|
+
const summary = controlsummary(step);
|
|
8261
|
+
return summary !== void 0 ? [{ stepid: step.id, summary }] : [];
|
|
8262
|
+
});
|
|
8263
|
+
return { version: protocolversion, workflow: composed, ...dryrun === true ? { dryrun: true } : {}, ...control.length > 0 ? { control } : {} };
|
|
8264
|
+
}
|
|
8265
|
+
function workflowoutcome(input) {
|
|
8266
|
+
const selected = input.stepid !== void 0 ? input.entries.filter((entry) => entry.stepid === input.stepid) : input.entries;
|
|
8267
|
+
const steps = selected.map((entry) => ({ stepid: entry.stepid, label: entry.label, state: entry.state, duration: entry.duration, summary: entry.summary, ...entry.block !== void 0 ? { block: entry.block } : {}, ...entry.produced !== void 0 ? { produced: entry.produced } : {}, ...entry.consumed !== void 0 ? { consumed: entry.consumed } : {}, ...entry.checkpoint === true ? { checkpoint: true } : {}, ...entry.details !== void 0 && entry.details.control !== void 0 ? { control: entry.details.control } : {} }));
|
|
8268
|
+
return { version: protocolversion, runid: input.run.id, workflowid: input.run.workflowid, state: input.run.state, ...input.run.dryrun === true ? { dryrun: true } : {}, steps };
|
|
8269
|
+
}
|
|
6642
8270
|
function stepof(kind, candidate, index) {
|
|
6643
8271
|
return { id: typeof candidate.id === "string" ? candidate.id : `candidate${index + 1}`, kind, summary: typeof candidate.summary === "string" ? candidate.summary : "", risk: "read", ...typeof candidate.options === "string" ? { options: candidate.options } : {} };
|
|
6644
8272
|
}
|
|
@@ -6654,7 +8282,7 @@ function requestbody(input) {
|
|
|
6654
8282
|
return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
|
|
6655
8283
|
}
|
|
6656
8284
|
function outcomeresponse(input) {
|
|
6657
|
-
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome, ...input.resolvedtarget ? { resolvedtarget: input.resolvedtarget } : {}, ...input.capture ? { capture: input.capture } : {}, ...input.media ? { media: input.media } : {}, ...input.transport ? { transport: input.transport } : {}, ...input.network ? { network: input.network } : {}, ...input.control ? { control: input.control } : {}, ...input.timeline ? { timeline: input.timeline } : {}, ...input.cdp ? { cdp: input.cdp } : {}, ...input.profile ? { profile: input.profile } : {}, ...input.emulation ? { emulation: input.emulation } : {}, ...input.session ? { session: input.session } : {} });
|
|
8285
|
+
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome, ...input.resolvedtarget ? { resolvedtarget: input.resolvedtarget } : {}, ...input.capture ? { capture: input.capture } : {}, ...input.media ? { media: input.media } : {}, ...input.transport ? { transport: input.transport } : {}, ...input.network ? { network: input.network } : {}, ...input.control ? { control: input.control } : {}, ...input.timeline ? { timeline: input.timeline } : {}, ...input.cdp ? { cdp: input.cdp } : {}, ...input.profile ? { profile: input.profile } : {}, ...input.emulation ? { emulation: input.emulation } : {}, ...input.session ? { session: input.session } : {}, ...input.workflow ? { workflow: { runid: input.workflow.runid, state: input.workflow.state, ...input.workflow.dryrun === true ? { dryrun: true } : {}, produced: input.workflow.produced, consumed: input.workflow.consumed, ...input.workflow.timeout !== void 0 ? { timeout: input.workflow.timeout } : {}, ...input.workflow.retry !== void 0 ? { retry: input.workflow.retry } : {} } } : {} });
|
|
6658
8286
|
}
|
|
6659
8287
|
function mapresponse(input) {
|
|
6660
8288
|
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
|
|
@@ -6803,6 +8431,9 @@ function emulationreport(input) {
|
|
|
6803
8431
|
function sessionreport(input) {
|
|
6804
8432
|
return { version: protocolversion, records: input.records, events: input.events, folders: input.folders, diffs: input.diffs, ...input.auto !== void 0 ? { auto: input.auto } : {}, ...input.crashed === true ? { crashed: true } : {} };
|
|
6805
8433
|
}
|
|
8434
|
+
function workflowreport(input) {
|
|
8435
|
+
return { version: protocolversion, workflows: input.workflows, runs: input.runs, templates: input.templates, log: input.log ?? [], scopes: input.scopes ?? [], provenance: input.provenance ?? [], control: input.control ?? [] };
|
|
8436
|
+
}
|
|
6806
8437
|
export {
|
|
6807
8438
|
activelayers,
|
|
6808
8439
|
agentgrammarvalid,
|
|
@@ -6816,6 +8447,9 @@ export {
|
|
|
6816
8447
|
apireplayspecof,
|
|
6817
8448
|
applyheaderules,
|
|
6818
8449
|
applylayer,
|
|
8450
|
+
applyretry,
|
|
8451
|
+
applyruntimeout,
|
|
8452
|
+
applytimeout,
|
|
6819
8453
|
argkind,
|
|
6820
8454
|
assetentries,
|
|
6821
8455
|
attachcdpsession,
|
|
@@ -6825,15 +8459,19 @@ export {
|
|
|
6825
8459
|
authorizeurl,
|
|
6826
8460
|
authreport,
|
|
6827
8461
|
autointervalof,
|
|
8462
|
+
backoffdelay,
|
|
8463
|
+
bindvariables,
|
|
6828
8464
|
blackboxedurls,
|
|
6829
8465
|
blackboxmatches,
|
|
6830
8466
|
blackboxruleof,
|
|
6831
8467
|
blendrows,
|
|
6832
8468
|
blockgate,
|
|
6833
8469
|
blockingduration,
|
|
8470
|
+
blockinvocationof,
|
|
6834
8471
|
blockruleof,
|
|
6835
8472
|
bodyfilterof,
|
|
6836
8473
|
bodymatches,
|
|
8474
|
+
branchof,
|
|
6837
8475
|
breakpointbudgetallowed,
|
|
6838
8476
|
breakpointceilingof,
|
|
6839
8477
|
breakpointinputof,
|
|
@@ -6845,6 +8483,8 @@ export {
|
|
|
6845
8483
|
callgraphql,
|
|
6846
8484
|
callrest,
|
|
6847
8485
|
callsreport,
|
|
8486
|
+
cancellederror,
|
|
8487
|
+
cancelrun,
|
|
6848
8488
|
canexecute,
|
|
6849
8489
|
capturebody,
|
|
6850
8490
|
capturecode,
|
|
@@ -6868,14 +8508,21 @@ export {
|
|
|
6868
8508
|
cdpreport,
|
|
6869
8509
|
channeloptionsof,
|
|
6870
8510
|
channelorigin,
|
|
8511
|
+
choosebranch,
|
|
6871
8512
|
closechannel,
|
|
6872
8513
|
collectmessages,
|
|
8514
|
+
composeworkflow,
|
|
8515
|
+
conditionof,
|
|
6873
8516
|
consolecapture,
|
|
6874
8517
|
consoleconsentcovers,
|
|
6875
8518
|
consolediff,
|
|
6876
8519
|
consolediffreport,
|
|
8520
|
+
controlexecutor,
|
|
8521
|
+
controlflowkinds,
|
|
6877
8522
|
controlkinds,
|
|
6878
8523
|
controlreport,
|
|
8524
|
+
controlsteps,
|
|
8525
|
+
controlsummary,
|
|
6879
8526
|
controltarget,
|
|
6880
8527
|
convertdirectiveof,
|
|
6881
8528
|
cookiedomaingranted,
|
|
@@ -6892,6 +8539,8 @@ export {
|
|
|
6892
8539
|
debuggerconsentcovers,
|
|
6893
8540
|
debugwaitbudgetallowed,
|
|
6894
8541
|
dedupeimages,
|
|
8542
|
+
defaultloopbound,
|
|
8543
|
+
delayjitter,
|
|
6895
8544
|
actionrisk as deriveactionrisk,
|
|
6896
8545
|
detachcdpsession,
|
|
6897
8546
|
devicepresetof,
|
|
@@ -6899,6 +8548,8 @@ export {
|
|
|
6899
8548
|
diffreviewgrade,
|
|
6900
8549
|
diffsessionrecords,
|
|
6901
8550
|
downloadreport,
|
|
8551
|
+
dryrunprojection,
|
|
8552
|
+
dryrunworkflow,
|
|
6902
8553
|
emugate,
|
|
6903
8554
|
emulationkinds,
|
|
6904
8555
|
emulationreport,
|
|
@@ -6907,13 +8558,18 @@ export {
|
|
|
6907
8558
|
emulationstateof,
|
|
6908
8559
|
errorcapture,
|
|
6909
8560
|
errorreportresponse,
|
|
8561
|
+
evaluatecondition,
|
|
6910
8562
|
eventresponse,
|
|
6911
8563
|
exchangesreport,
|
|
8564
|
+
expandblocks,
|
|
6912
8565
|
expirelayers,
|
|
6913
8566
|
expireprofilerecords,
|
|
6914
8567
|
expiresessions,
|
|
6915
8568
|
exportpresetlibrary,
|
|
6916
8569
|
exportsessionfile,
|
|
8570
|
+
expressioneval,
|
|
8571
|
+
expressionof,
|
|
8572
|
+
expressionoperators,
|
|
6917
8573
|
extractionreport,
|
|
6918
8574
|
extractvalues,
|
|
6919
8575
|
failureclass,
|
|
@@ -6927,6 +8583,7 @@ export {
|
|
|
6927
8583
|
fixedheadermatch,
|
|
6928
8584
|
flowmetricnames,
|
|
6929
8585
|
flowspecof,
|
|
8586
|
+
foreachof,
|
|
6930
8587
|
formpayloadof,
|
|
6931
8588
|
formreportresponse,
|
|
6932
8589
|
frameinterval,
|
|
@@ -6950,6 +8607,7 @@ export {
|
|
|
6950
8607
|
importpresetlibrary,
|
|
6951
8608
|
importsessionfile,
|
|
6952
8609
|
iscdpkind,
|
|
8610
|
+
iscontrolflowkind,
|
|
6953
8611
|
iscontrolkind,
|
|
6954
8612
|
isdebugkind,
|
|
6955
8613
|
isemulationkind,
|
|
@@ -6959,6 +8617,8 @@ export {
|
|
|
6959
8617
|
issessionkind,
|
|
6960
8618
|
issocketkind,
|
|
6961
8619
|
iswatchkind,
|
|
8620
|
+
isworkflowkind,
|
|
8621
|
+
joinbranches,
|
|
6962
8622
|
jsonpathrulesof,
|
|
6963
8623
|
lapseframes,
|
|
6964
8624
|
lapseplanof,
|
|
@@ -6971,6 +8631,7 @@ export {
|
|
|
6971
8631
|
locationrangevalid,
|
|
6972
8632
|
loglevels,
|
|
6973
8633
|
longtaskcapture,
|
|
8634
|
+
loopof,
|
|
6974
8635
|
mapresponse,
|
|
6975
8636
|
mapurlof,
|
|
6976
8637
|
matchmessage,
|
|
@@ -6999,6 +8660,7 @@ export {
|
|
|
6999
8660
|
newrecording,
|
|
7000
8661
|
newsessiondiff,
|
|
7001
8662
|
newsessionrecord,
|
|
8663
|
+
newworkflowrun,
|
|
7002
8664
|
normalizeendpoint,
|
|
7003
8665
|
oauthflowof,
|
|
7004
8666
|
observationmodeof,
|
|
@@ -7009,13 +8671,16 @@ export {
|
|
|
7009
8671
|
overridematches,
|
|
7010
8672
|
pairexchange,
|
|
7011
8673
|
pairstates,
|
|
8674
|
+
parallelof,
|
|
7012
8675
|
parsehtmlbody,
|
|
7013
8676
|
parseproposal,
|
|
7014
8677
|
parsessetext,
|
|
7015
8678
|
parsetokens,
|
|
8679
|
+
parseworkflowproposal,
|
|
7016
8680
|
passwordconsentgranted,
|
|
7017
8681
|
patternorigin,
|
|
7018
8682
|
pauseretentionwindow,
|
|
8683
|
+
pauserun,
|
|
7019
8684
|
payloadshapeof,
|
|
7020
8685
|
payloadvalid,
|
|
7021
8686
|
payloadwithdefaults,
|
|
@@ -7032,6 +8697,7 @@ export {
|
|
|
7032
8697
|
pollcursorof,
|
|
7033
8698
|
polldecision,
|
|
7034
8699
|
pollurl,
|
|
8700
|
+
popscope,
|
|
7035
8701
|
privatemime,
|
|
7036
8702
|
profilegrantgranted,
|
|
7037
8703
|
profilereport,
|
|
@@ -7042,6 +8708,7 @@ export {
|
|
|
7042
8708
|
proxygate,
|
|
7043
8709
|
proxyrouteof,
|
|
7044
8710
|
publishmessage,
|
|
8711
|
+
pushscope,
|
|
7045
8712
|
quarantinereport,
|
|
7046
8713
|
randomid,
|
|
7047
8714
|
rankapis,
|
|
@@ -7056,13 +8723,17 @@ export {
|
|
|
7056
8723
|
recordwatchvalue,
|
|
7057
8724
|
redactconsoletext,
|
|
7058
8725
|
redactedcookies,
|
|
8726
|
+
regexextract,
|
|
8727
|
+
regexruleof,
|
|
7059
8728
|
regionsteps,
|
|
7060
8729
|
rejectioncapture,
|
|
8730
|
+
repeatuntilof,
|
|
7061
8731
|
replaytrace,
|
|
7062
8732
|
replayurl,
|
|
7063
8733
|
requestbody,
|
|
7064
8734
|
resolutionverdict,
|
|
7065
8735
|
resolvedrisk,
|
|
8736
|
+
resolvevariable,
|
|
7066
8737
|
resourcefacts,
|
|
7067
8738
|
restoreoriginsgranted,
|
|
7068
8739
|
restoreplanof,
|
|
@@ -7076,12 +8747,23 @@ export {
|
|
|
7076
8747
|
rewritesourcelocation,
|
|
7077
8748
|
rotatelogs,
|
|
7078
8749
|
rotationruleof,
|
|
8750
|
+
runcatch,
|
|
8751
|
+
runcontrolstep,
|
|
8752
|
+
runforeach,
|
|
8753
|
+
runloop,
|
|
8754
|
+
runparallel,
|
|
8755
|
+
runrepeatuntil,
|
|
8756
|
+
runstep,
|
|
8757
|
+
runtry,
|
|
8758
|
+
runwhile,
|
|
8759
|
+
runworkflow,
|
|
7079
8760
|
safetyresponse,
|
|
7080
8761
|
scaledrect,
|
|
7081
8762
|
seamweights,
|
|
7082
8763
|
searchfields,
|
|
7083
8764
|
searchqueryof,
|
|
7084
8765
|
searchsessionrecords,
|
|
8766
|
+
seededrandom,
|
|
7085
8767
|
selectorresponse,
|
|
7086
8768
|
sendcdpcommand,
|
|
7087
8769
|
sendfetch,
|
|
@@ -7097,6 +8779,7 @@ export {
|
|
|
7097
8779
|
sessionreport,
|
|
7098
8780
|
sessionrestoregate,
|
|
7099
8781
|
sessiontabof,
|
|
8782
|
+
setvariable,
|
|
7100
8783
|
shiftentryof,
|
|
7101
8784
|
signalsreport,
|
|
7102
8785
|
snapshotplanof,
|
|
@@ -7113,6 +8796,7 @@ export {
|
|
|
7113
8796
|
stackgate,
|
|
7114
8797
|
statusclassof,
|
|
7115
8798
|
stepmodeof,
|
|
8799
|
+
steptemplateof,
|
|
7116
8800
|
stepwindows,
|
|
7117
8801
|
streamsummaries,
|
|
7118
8802
|
streamwindowof,
|
|
@@ -7141,18 +8825,30 @@ export {
|
|
|
7141
8825
|
tracetofile,
|
|
7142
8826
|
trailreport,
|
|
7143
8827
|
transformgrammar,
|
|
8828
|
+
tryof,
|
|
7144
8829
|
unwrapgraphql,
|
|
7145
8830
|
urlencodeform,
|
|
7146
8831
|
validatebreakpointcondition,
|
|
8832
|
+
validatecontrolpayload,
|
|
7147
8833
|
validatefieldmatch,
|
|
7148
8834
|
validateformrecord,
|
|
8835
|
+
validateregexrule,
|
|
7149
8836
|
validatestep,
|
|
7150
8837
|
validatetargetref,
|
|
7151
8838
|
validatevaluegen,
|
|
8839
|
+
validateworkflow,
|
|
8840
|
+
waitelementplan,
|
|
7152
8841
|
watchcdpevents,
|
|
7153
8842
|
watcherdetached,
|
|
7154
8843
|
watchexpressionof,
|
|
7155
8844
|
watchgate,
|
|
7156
|
-
|
|
8845
|
+
whileof,
|
|
8846
|
+
wizardreport,
|
|
8847
|
+
workflowblockof,
|
|
8848
|
+
workflowgate,
|
|
8849
|
+
workflowkinds,
|
|
8850
|
+
workflowoutcome,
|
|
8851
|
+
workflowreport,
|
|
8852
|
+
workflowstepof
|
|
7157
8853
|
};
|
|
7158
8854
|
//# sourceMappingURL=index.js.map
|