@wenathlan/extension 1.1.50 → 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.
@@ -2191,6 +2191,26 @@ var sessionmemory = class {
2191
2191
  async getsteptemplates() {
2192
2192
  return await this.adapter.get("steptemplates") ?? [];
2193
2193
  }
2194
+ /** 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. */
2195
+ async addcontroldecision(runid, decision) {
2196
+ const decisions = await this.listcontroldecisions(runid);
2197
+ await this.adapter.set(`controldecisions${runid}`, [...decisions, { ...decision, runid }]);
2198
+ }
2199
+ /** Returns every stored control flow decision of one run, oldest first. */
2200
+ async listcontroldecisions(runid) {
2201
+ return await this.adapter.get(`controldecisions${runid}`) ?? [];
2202
+ }
2203
+ /** Returns the past branch decisions of one workflow across every stored run, oldest first, so review can compare branch paths over time. */
2204
+ async getbranchhistory(workflowid) {
2205
+ const runs = await this.listworkflowruns();
2206
+ const ordered = [...runs].reverse().filter((run) => run.workflowid === workflowid);
2207
+ const history2 = [];
2208
+ for (const run of ordered) {
2209
+ const decisions = await this.listcontroldecisions(run.id);
2210
+ for (const decision of decisions) if (decision.kind === "branch" && decision.branch !== void 0) history2.push(decision.branch);
2211
+ }
2212
+ return history2;
2213
+ }
2194
2214
  };
2195
2215
  function mediakindof(record2) {
2196
2216
  if ("pages" in record2) return "pdf";
@@ -3177,6 +3197,678 @@ function teardowncdpsession(input) {
3177
3197
  };
3178
3198
  }
3179
3199
 
3200
+ // controlflow.ts
3201
+ var controlflowkinds = ["condition", "branch", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"];
3202
+ var defaultloopbound = 1e3;
3203
+ function iscontrolflowkind(kind) {
3204
+ return controlflowkinds.includes(kind);
3205
+ }
3206
+ var cancellederror = class extends Error {
3207
+ constructor(message) {
3208
+ super(message);
3209
+ this.name = "cancellederror";
3210
+ }
3211
+ };
3212
+ function controlname(value) {
3213
+ return typeof value === "string" && /^[a-z][a-z0-9]*$/.test(value) ? value : void 0;
3214
+ }
3215
+ function controlstepslist(value) {
3216
+ if (!Array.isArray(value) || value.length === 0) return void 0;
3217
+ const steps = [];
3218
+ for (const entry of value) {
3219
+ const parsed = workflowstepof(entry);
3220
+ if (!parsed) return void 0;
3221
+ steps.push(parsed);
3222
+ }
3223
+ return steps;
3224
+ }
3225
+ function controlbound(value) {
3226
+ if (value === void 0) return void 0;
3227
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
3228
+ }
3229
+ function conditionof(value) {
3230
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3231
+ const candidate = value;
3232
+ const expression = expressionof(candidate.expression);
3233
+ if (!expression) return void 0;
3234
+ if (expression.resultkind !== "boolean") return void 0;
3235
+ return { expression };
3236
+ }
3237
+ function elseof(value) {
3238
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3239
+ const candidate = value;
3240
+ const name = controlname(candidate.name);
3241
+ if (!name) return void 0;
3242
+ if (candidate.when !== void 0) return void 0;
3243
+ if (!Array.isArray(candidate.steps)) return void 0;
3244
+ const steps = [];
3245
+ for (const entry of candidate.steps) {
3246
+ const parsed = workflowstepof(entry);
3247
+ if (!parsed) return void 0;
3248
+ steps.push(parsed);
3249
+ }
3250
+ return { name, steps };
3251
+ }
3252
+ function branchof(value) {
3253
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3254
+ const candidate = value;
3255
+ if (!Array.isArray(candidate.paths) || candidate.paths.length === 0) return void 0;
3256
+ const paths = [];
3257
+ for (const entry of candidate.paths) {
3258
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return void 0;
3259
+ const path = entry;
3260
+ const name = controlname(path.name);
3261
+ if (!name) return void 0;
3262
+ const when = path.when === void 0 ? void 0 : expressionof(path.when);
3263
+ if (path.when !== void 0 && when === void 0) return void 0;
3264
+ if (when !== void 0 && when.resultkind !== "boolean") return void 0;
3265
+ const steps = controlstepslist(path.steps);
3266
+ if (!steps) return void 0;
3267
+ paths.push({ name, ...when !== void 0 ? { when } : {}, steps });
3268
+ }
3269
+ const names = paths.map((path) => path.name);
3270
+ if (new Set(names).size !== names.length) return void 0;
3271
+ const elsepath = elseof(candidate.else);
3272
+ if (!elsepath) return void 0;
3273
+ if (names.includes(elsepath.name)) return void 0;
3274
+ return { paths, else: elsepath };
3275
+ }
3276
+ function loopof(value) {
3277
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3278
+ const candidate = value;
3279
+ const list = controlname(candidate.list);
3280
+ const item = controlname(candidate.item);
3281
+ const index = controlname(candidate.index);
3282
+ if (!list || !item || !index) return void 0;
3283
+ if (item === list || index === list || item === index) return void 0;
3284
+ const bound = controlbound(candidate.bound);
3285
+ if (candidate.bound !== void 0 && bound === void 0) return void 0;
3286
+ const steps = controlstepslist(candidate.steps);
3287
+ if (!steps) return void 0;
3288
+ return { list, item, index, ...bound !== void 0 ? { bound } : {}, steps };
3289
+ }
3290
+ function repeatuntilof(value) {
3291
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3292
+ const candidate = value;
3293
+ const until = expressionof(candidate.until);
3294
+ if (!until || until.resultkind !== "boolean") return void 0;
3295
+ const bound = controlbound(candidate.bound);
3296
+ if (candidate.bound !== void 0 && bound === void 0) return void 0;
3297
+ const steps = controlstepslist(candidate.steps);
3298
+ if (!steps) return void 0;
3299
+ return { until, ...bound !== void 0 ? { bound } : {}, steps };
3300
+ }
3301
+ function whileof(value) {
3302
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3303
+ const candidate = value;
3304
+ const condition = expressionof(candidate.while);
3305
+ if (!condition || condition.resultkind !== "boolean") return void 0;
3306
+ const bound = controlbound(candidate.bound);
3307
+ if (bound === void 0) return void 0;
3308
+ const steps = controlstepslist(candidate.steps);
3309
+ if (!steps) return void 0;
3310
+ return { while: condition, bound, steps };
3311
+ }
3312
+ function foreachof(value) {
3313
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3314
+ const candidate = value;
3315
+ if (typeof candidate.selector !== "string" || !candidate.selector.trim()) return void 0;
3316
+ const item = controlname(candidate.item);
3317
+ const index = controlname(candidate.index);
3318
+ if (!item || !index || item === index) return void 0;
3319
+ const steps = controlstepslist(candidate.steps);
3320
+ if (!steps) return void 0;
3321
+ return { selector: candidate.selector, item, index, steps };
3322
+ }
3323
+ function parallelof(value) {
3324
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3325
+ const candidate = value;
3326
+ if (!Array.isArray(candidate.branches) || candidate.branches.length === 0) return void 0;
3327
+ const branches = [];
3328
+ for (const entry of candidate.branches) {
3329
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return void 0;
3330
+ const branch = entry;
3331
+ const id = controlname(branch.id);
3332
+ if (!id) return void 0;
3333
+ const steps = controlstepslist(branch.steps);
3334
+ if (!steps) return void 0;
3335
+ branches.push({ id, steps });
3336
+ }
3337
+ if (new Set(branches.map((branch) => branch.id)).size !== branches.length) return void 0;
3338
+ const join = candidate.join && typeof candidate.join === "object" && !Array.isArray(candidate.join) ? candidate.join : void 0;
3339
+ if (!join) return void 0;
3340
+ if (join.strategy !== "first" && join.strategy !== "last" && join.strategy !== "fail") return void 0;
3341
+ if (join.onfail !== "cancel" && join.onfail !== "continue") return void 0;
3342
+ return { branches, join: { strategy: join.strategy, onfail: join.onfail } };
3343
+ }
3344
+ function tryof(value) {
3345
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3346
+ const candidate = value;
3347
+ const steps = controlstepslist(candidate.steps);
3348
+ if (!steps) return void 0;
3349
+ const catchcandidate = candidate.catch && typeof candidate.catch === "object" && !Array.isArray(candidate.catch) ? candidate.catch : void 0;
3350
+ if (!catchcandidate) return void 0;
3351
+ const catchsteps = controlstepslist(catchcandidate.steps);
3352
+ if (!catchsteps) return void 0;
3353
+ if (catchcandidate.rerun !== void 0 && typeof catchcandidate.rerun !== "boolean") return void 0;
3354
+ const catchvalue = { steps: catchsteps, ...catchcandidate.rerun === true ? { rerun: true } : {} };
3355
+ let retry;
3356
+ if (candidate.retry !== void 0) {
3357
+ const retrycandidate = candidate.retry && typeof candidate.retry === "object" && !Array.isArray(candidate.retry) ? candidate.retry : void 0;
3358
+ if (!retrycandidate) return void 0;
3359
+ if (typeof retrycandidate.attempts !== "number" || !Number.isInteger(retrycandidate.attempts) || retrycandidate.attempts < 1) return void 0;
3360
+ const backoff = retrycandidate.backoff && typeof retrycandidate.backoff === "object" && !Array.isArray(retrycandidate.backoff) ? retrycandidate.backoff : void 0;
3361
+ if (!backoff) return void 0;
3362
+ if (backoff.shape !== "fixed" && backoff.shape !== "exponential") return void 0;
3363
+ if (typeof backoff.base !== "number" || !Number.isFinite(backoff.base) || backoff.base < 0) return void 0;
3364
+ if (typeof backoff.jitter !== "number" || !Number.isFinite(backoff.jitter) || backoff.jitter < 0) return void 0;
3365
+ if (!Array.isArray(retrycandidate.retryable) || !retrycandidate.retryable.every((entry) => typeof entry === "string" && entry.trim())) return void 0;
3366
+ retry = { attempts: retrycandidate.attempts, backoff: { shape: backoff.shape, base: backoff.base, jitter: backoff.jitter }, retryable: retrycandidate.retryable };
3367
+ }
3368
+ let timeout;
3369
+ if (candidate.timeout !== void 0) {
3370
+ const timeoutcandidate = candidate.timeout && typeof candidate.timeout === "object" && !Array.isArray(candidate.timeout) ? candidate.timeout : void 0;
3371
+ if (!timeoutcandidate) return void 0;
3372
+ const stepms = timeoutcandidate.stepms === void 0 ? void 0 : typeof timeoutcandidate.stepms === "number" && Number.isFinite(timeoutcandidate.stepms) && timeoutcandidate.stepms > 0 ? timeoutcandidate.stepms : void 0;
3373
+ const runms = timeoutcandidate.runms === void 0 ? void 0 : typeof timeoutcandidate.runms === "number" && Number.isFinite(timeoutcandidate.runms) && timeoutcandidate.runms > 0 ? timeoutcandidate.runms : void 0;
3374
+ if (stepms === void 0 && runms === void 0) return void 0;
3375
+ if (timeoutcandidate.stepms !== void 0 && stepms === void 0) return void 0;
3376
+ if (timeoutcandidate.runms !== void 0 && runms === void 0) return void 0;
3377
+ timeout = { ...stepms !== void 0 ? { stepms } : {}, ...runms !== void 0 ? { runms } : {} };
3378
+ }
3379
+ return { steps, catch: catchvalue, ...retry !== void 0 ? { retry } : {}, ...timeout !== void 0 ? { timeout } : {} };
3380
+ }
3381
+ function controloptions(step) {
3382
+ if (step.options === void 0) throw new Error(`The ${step.kind} step needs its reviewed control payload in options.`);
3383
+ let parsed;
3384
+ try {
3385
+ parsed = JSON.parse(step.options);
3386
+ } catch {
3387
+ throw new Error(`The ${step.kind} control payload must be a JSON object.`);
3388
+ }
3389
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`The ${step.kind} control payload must be a JSON object.`);
3390
+ return parsed;
3391
+ }
3392
+ function validatecontrolpayload(step) {
3393
+ if (!iscontrolflowkind(step.kind)) return;
3394
+ const payload = controloptions(step);
3395
+ if (step.kind === "condition" && conditionof(payload.condition) === void 0) throw new Error("The condition step needs a reviewed boolean expression in its options.");
3396
+ 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.");
3397
+ 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.");
3398
+ 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.");
3399
+ 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.");
3400
+ 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.");
3401
+ 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.");
3402
+ 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.");
3403
+ }
3404
+ function controlsteps(step) {
3405
+ if (!iscontrolflowkind(step.kind)) return [];
3406
+ let payload;
3407
+ try {
3408
+ payload = controloptions(step);
3409
+ } catch {
3410
+ return [];
3411
+ }
3412
+ const children = [];
3413
+ const collect = (steps) => {
3414
+ for (const child of steps) {
3415
+ children.push(child);
3416
+ collect(controlsteps(child));
3417
+ }
3418
+ };
3419
+ if (step.kind === "condition") return children;
3420
+ if (step.kind === "branch") {
3421
+ const branch = branchof(payload.branch);
3422
+ if (!branch) return children;
3423
+ for (const path of branch.paths) collect(path.steps);
3424
+ collect(branch.else.steps);
3425
+ return children;
3426
+ }
3427
+ if (step.kind === "loop") {
3428
+ const loop = loopof(payload.loop);
3429
+ if (loop) collect(loop.steps);
3430
+ return children;
3431
+ }
3432
+ if (step.kind === "repeatuntil") {
3433
+ const repeat = repeatuntilof(payload.repeatuntil);
3434
+ if (repeat) collect(repeat.steps);
3435
+ return children;
3436
+ }
3437
+ if (step.kind === "whileloop") {
3438
+ const condition = whileof(payload.while);
3439
+ if (condition) collect(condition.steps);
3440
+ return children;
3441
+ }
3442
+ if (step.kind === "foreach") {
3443
+ const foreach = foreachof(payload.foreach);
3444
+ if (foreach) collect(foreach.steps);
3445
+ return children;
3446
+ }
3447
+ if (step.kind === "parallel") {
3448
+ const parallel = parallelof(payload.parallel);
3449
+ if (parallel) for (const branch of parallel.branches) collect(branch.steps);
3450
+ return children;
3451
+ }
3452
+ const fragile = tryof(payload.try);
3453
+ if (fragile) {
3454
+ collect(fragile.steps);
3455
+ collect(fragile.catch.steps);
3456
+ }
3457
+ return children;
3458
+ }
3459
+ function controlsummary(step) {
3460
+ if (!iscontrolflowkind(step.kind)) return void 0;
3461
+ let payload;
3462
+ try {
3463
+ payload = controloptions(step);
3464
+ } catch {
3465
+ return { kind: step.kind };
3466
+ }
3467
+ if (step.kind === "condition") {
3468
+ const condition = conditionof(payload.condition);
3469
+ return { kind: step.kind, ...condition ? { expression: `${condition.expression.operator} into ${condition.expression.result}` } : {} };
3470
+ }
3471
+ if (step.kind === "branch") {
3472
+ const branch = branchof(payload.branch);
3473
+ return { kind: step.kind, ...branch ? { paths: branch.paths.map((path) => path.name), elsepath: branch.else.name } : {} };
3474
+ }
3475
+ if (step.kind === "loop") {
3476
+ const loop = loopof(payload.loop);
3477
+ return { kind: step.kind, ...loop ? { list: loop.list, item: loop.item, index: loop.index, ...loop.bound !== void 0 ? { bound: loop.bound } : { bound: defaultloopbound } } : {} };
3478
+ }
3479
+ if (step.kind === "repeatuntil") {
3480
+ const repeat = repeatuntilof(payload.repeatuntil);
3481
+ return { kind: step.kind, ...repeat ? { bound: repeat.bound ?? defaultloopbound } : {} };
3482
+ }
3483
+ if (step.kind === "whileloop") {
3484
+ const condition = whileof(payload.while);
3485
+ return { kind: step.kind, ...condition ? { bound: condition.bound } : {} };
3486
+ }
3487
+ if (step.kind === "foreach") {
3488
+ const foreach = foreachof(payload.foreach);
3489
+ return { kind: step.kind, ...foreach ? { selector: foreach.selector, item: foreach.item, index: foreach.index } : {} };
3490
+ }
3491
+ if (step.kind === "parallel") {
3492
+ const parallel = parallelof(payload.parallel);
3493
+ return { kind: step.kind, ...parallel ? { branches: parallel.branches.map((branch) => branch.id), strategy: parallel.join.strategy, onfail: parallel.join.onfail } : {} };
3494
+ }
3495
+ const fragile = tryof(payload.try);
3496
+ 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 } : {} } : {} };
3497
+ }
3498
+ function evaluatecondition(condition, scopes) {
3499
+ const value = expressioneval(condition.expression, scopes);
3500
+ if (typeof value !== "boolean") throw new Error("The condition expression must resolve to a boolean.");
3501
+ return value;
3502
+ }
3503
+ function choosebranch(input) {
3504
+ let scopes = input.scopes;
3505
+ if (input.pagestate !== void 0) {
3506
+ const parent = scopes.length > 0 ? scopes[scopes.length - 1].name : void 0;
3507
+ scopes = pushscope(scopes, `pagestate${input.stepid}`, parent);
3508
+ if (input.pagestate.url !== void 0) scopes = setvariable(scopes, "pageurl", "string", input.pagestate.url, input.now);
3509
+ if (input.pagestate.title !== void 0) scopes = setvariable(scopes, "pagetitle", "string", input.pagestate.title, input.now);
3510
+ if (input.pagestate.ready !== void 0) scopes = setvariable(scopes, "pageready", "boolean", input.pagestate.ready, input.now);
3511
+ }
3512
+ for (const path of input.branch.paths) {
3513
+ 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 };
3514
+ const value = expressioneval(path.when, scopes);
3515
+ if (typeof value !== "boolean") throw new Error(`The branch path ${path.name} needs a boolean expression.`);
3516
+ 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 };
3517
+ }
3518
+ 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 };
3519
+ }
3520
+ async function runbody(input) {
3521
+ let scopes = input.scopes;
3522
+ const outputs = { ...input.outputs };
3523
+ const log = [];
3524
+ for (const child of input.steps) {
3525
+ if (iscontrolflowkind(child.kind)) {
3526
+ const result = await runcontrolstep({ step: child, scopes, outputs, execute: input.execute, now: input.now, ...input.path !== void 0 ? { path: `${input.path}.${child.id}` } : {} });
3527
+ scopes = result.scopes;
3528
+ log.push(...result.log);
3529
+ 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 };
3530
+ if (!result.output.ok) return { ok: false, scopes, log, outputs, failure: result.output };
3531
+ continue;
3532
+ }
3533
+ const executed = await runstep({ step: child, scopes, outputs, execute: input.execute, now: input.now });
3534
+ scopes = executed.scopes;
3535
+ if (executed.childlog !== void 0) log.push(...executed.childlog);
3536
+ log.push(executed.log);
3537
+ 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 };
3538
+ if (!executed.output.ok) return { ok: false, scopes, log, outputs, failure: executed.output };
3539
+ }
3540
+ return { ok: true, scopes, log, outputs };
3541
+ }
3542
+ function deepcopy(value) {
3543
+ if (Array.isArray(value)) return value.map(deepcopy);
3544
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, deepcopy(entry)]));
3545
+ return value;
3546
+ }
3547
+ function iterationentry(step, iteration, total, ok, now) {
3548
+ 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 } };
3549
+ }
3550
+ async function runloop(input) {
3551
+ const list = resolvevariable(input.scopes, input.loop.list);
3552
+ if (!list) throw new Error(`The loop references the undefined list variable ${input.loop.list}.`);
3553
+ if (list.kind !== "list") throw new Error(`The loop variable ${input.loop.list} is not a list.`);
3554
+ const items = list.value;
3555
+ const bound = input.loop.bound ?? defaultloopbound;
3556
+ const loops = [];
3557
+ const decision = { runid: "", stepid: input.step.id, kind: "loop", at: input.now, loops };
3558
+ if (items.length > bound) {
3559
+ 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 };
3560
+ }
3561
+ let scopes = input.scopes;
3562
+ const log = [];
3563
+ for (let index = 0; index < items.length; index += 1) {
3564
+ scopes = setvariable(scopes, input.loop.item, "string", deepcopy(items[index]), input.now);
3565
+ scopes = setvariable(scopes, input.loop.index, "number", index, input.now);
3566
+ 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}]` });
3567
+ scopes = body.scopes;
3568
+ const ok = body.ok;
3569
+ loops.push({ stepid: input.step.id, path: `${input.path ?? input.step.id}[${index}]`, iteration: index, ok, at: input.now });
3570
+ log.push(...body.log, iterationentry(input.step, index, items.length, ok, input.now));
3571
+ 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 };
3572
+ }
3573
+ 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 };
3574
+ }
3575
+ async function runrepeatuntil(input) {
3576
+ const bound = input.repeat.bound ?? defaultloopbound;
3577
+ let scopes = input.scopes;
3578
+ const log = [];
3579
+ const loops = [];
3580
+ const decision = { runid: "", stepid: input.step.id, kind: "loop", at: input.now, loops };
3581
+ for (let iteration = 0; iteration < bound; iteration += 1) {
3582
+ 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}]` });
3583
+ scopes = body.scopes;
3584
+ log.push(...body.log);
3585
+ const converged = evaluatecondition({ expression: input.repeat.until }, scopes);
3586
+ loops.push({ stepid: input.step.id, path: `${input.path ?? input.step.id}[${iteration}]`, iteration, ok: body.ok, at: input.now });
3587
+ 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 };
3588
+ log.push(iterationentry(input.step, iteration, bound, true, input.now));
3589
+ 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 };
3590
+ }
3591
+ return { ok: false, scopes, log, summary: `The repeat until never converged within the reviewed safety bound of ${bound} iterations.`, decision };
3592
+ }
3593
+ async function runwhile(input) {
3594
+ let scopes = input.scopes;
3595
+ const log = [];
3596
+ const loops = [];
3597
+ const decision = { runid: "", stepid: input.step.id, kind: "loop", at: input.now, loops };
3598
+ for (let iteration = 0; iteration < input.condition.bound; iteration += 1) {
3599
+ if (!evaluatecondition({ expression: input.condition.while }, scopes)) {
3600
+ 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 };
3601
+ }
3602
+ 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}]` });
3603
+ scopes = body.scopes;
3604
+ log.push(...body.log);
3605
+ loops.push({ stepid: input.step.id, path: `${input.path ?? input.step.id}[${iteration}]`, iteration, ok: body.ok, at: input.now });
3606
+ 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 };
3607
+ log.push(iterationentry(input.step, iteration, input.condition.bound, true, input.now));
3608
+ }
3609
+ if (evaluatecondition({ expression: input.condition.while }, scopes)) {
3610
+ 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 };
3611
+ }
3612
+ 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 };
3613
+ }
3614
+ async function runforeach(input) {
3615
+ if (!input.resolveelements) throw new Error("The foreach step needs the element resolver of the executor seam.");
3616
+ const elements = await input.resolveelements(input.foreach.selector);
3617
+ const loops = [];
3618
+ const decision = { runid: "", stepid: input.step.id, kind: "loop", at: input.now, loops };
3619
+ 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 };
3620
+ let scopes = input.scopes;
3621
+ const log = [];
3622
+ for (let index = 0; index < elements.length; index += 1) {
3623
+ scopes = setvariable(scopes, input.foreach.item, "element", deepcopy(elements[index]), input.now);
3624
+ scopes = setvariable(scopes, input.foreach.index, "number", index, input.now);
3625
+ 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}]` });
3626
+ scopes = body.scopes;
3627
+ const ok = body.ok;
3628
+ loops.push({ stepid: input.step.id, path: `${input.path ?? input.step.id}[${index}]`, iteration: index, ok, at: input.now });
3629
+ log.push(...body.log, iterationentry(input.step, index, elements.length, ok, input.now));
3630
+ 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 };
3631
+ }
3632
+ return { ok: true, scopes, log, summary: `The foreach ran ${elements.length} iteration${elements.length === 1 ? "" : "s"} over the elements of ${input.foreach.selector}.`, decision };
3633
+ }
3634
+ function joinbranches(input) {
3635
+ const contributing = input.branches.filter((branch) => !branch.cancelled);
3636
+ const byname = /* @__PURE__ */ new Map();
3637
+ for (const branch of contributing) for (const variable of branch.variables) {
3638
+ const entries = byname.get(variable.name) ?? [];
3639
+ entries.push({ order: branch.order, value: variable });
3640
+ byname.set(variable.name, entries);
3641
+ }
3642
+ const conflicts = [...byname.entries()].filter(([, entries]) => entries.length > 1).map(([name]) => name);
3643
+ const record2 = { stepid: input.stepid, strategy: input.strategy, conflicts, merged: [], at: input.now };
3644
+ if (conflicts.length > 0 && input.strategy === "fail") {
3645
+ return { ok: false, conflicts, merged: [], record: record2, summary: `The join refused the conflicting writes of ${conflicts.join(", ")} under the fail strategy.` };
3646
+ }
3647
+ const merged = [];
3648
+ for (const [name, entries] of byname) {
3649
+ void name;
3650
+ 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);
3651
+ merged.push({ ...winner.value, setat: input.now });
3652
+ }
3653
+ record2.merged = merged.map((variable) => variable.name);
3654
+ 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"}.` };
3655
+ }
3656
+ async function runparallel(input) {
3657
+ let finished = 0;
3658
+ let firstfailure = Number.POSITIVE_INFINITY;
3659
+ const log = [];
3660
+ const launches = input.parallel.branches.map((branch, order) => (async () => {
3661
+ const parent = input.scopes.length > 0 ? input.scopes[input.scopes.length - 1].name : void 0;
3662
+ const isolated = pushscope(input.scopes, `branch${branch.id}`, parent);
3663
+ const body = await runbody({ step: input.step, scopes: isolated, outputs: input.outputs, execute: input.execute, now: input.now, steps: branch.steps });
3664
+ const finishedat = finished;
3665
+ finished += 1;
3666
+ if (!body.ok && finishedat < firstfailure) firstfailure = finishedat;
3667
+ const scope = body.scopes[body.scopes.length - 1];
3668
+ return { branch, order, finishedat, ok: body.ok, scopes: body.scopes, variables: scope.name === `branch${branch.id}` ? scope.variables : [], log: body.log, failure: body.failure };
3669
+ })());
3670
+ const settled = await Promise.all(launches);
3671
+ for (const entry of settled) log.push(...entry.log);
3672
+ const cancelmode = input.parallel.join.onfail === "cancel";
3673
+ 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 } : {} }));
3674
+ 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 });
3675
+ const decision = { runid: "", stepid: input.step.id, kind: "join", at: input.now, join: join.record, branches: outcomes };
3676
+ if (!join.ok) return { ok: false, scopes: input.scopes, log, summary: join.summary, decision };
3677
+ let scopes = input.scopes;
3678
+ for (const variable of join.merged) scopes = setvariable(scopes, variable.name, variable.kind, variable.value, input.now);
3679
+ const failedbranches = settled.filter((entry, order) => !entry.ok && outcomes[order]?.cancelled !== true).map((entry) => entry.branch.id);
3680
+ if (cancelmode && failedbranches.length > 0) {
3681
+ 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 };
3682
+ }
3683
+ 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 };
3684
+ }
3685
+ function errorclassof(output) {
3686
+ const errorclass = output.details?.errorclass;
3687
+ return typeof errorclass === "string" && errorclass.trim() ? errorclass : "stepfailed";
3688
+ }
3689
+ function backoffdelay(policy, attempt, seed) {
3690
+ const base = policy.backoff.shape === "exponential" ? policy.backoff.base * 2 ** (attempt - 1) : policy.backoff.base;
3691
+ if (policy.backoff.jitter <= 0) return Math.max(0, base);
3692
+ return Math.max(0, base - policy.backoff.jitter / 2 + seededrandom(seed + attempt) * policy.backoff.jitter);
3693
+ }
3694
+ function waitsome(milliseconds) {
3695
+ return new Promise((resolve) => setTimeout(resolve, Math.max(0, milliseconds)));
3696
+ }
3697
+ async function applyretry(input) {
3698
+ const seed = input.seed ?? 0;
3699
+ let value = await input.run();
3700
+ const attempts = [];
3701
+ let attempt = 1;
3702
+ while (!value.ok && attempt < input.policy.attempts) {
3703
+ const errorclass = input.errorclass(value);
3704
+ if (!input.policy.retryable.includes(errorclass)) break;
3705
+ const delay = backoffdelay(input.policy, attempt, seed);
3706
+ attempts.push({ stepid: input.stepid, attempt: attempt + 1, delay, errorclass, at: input.now });
3707
+ if (delay > 0) await waitsome(delay);
3708
+ attempt += 1;
3709
+ value = await input.run();
3710
+ }
3711
+ return { value, attempts, exhausted: !value.ok && attempts.length > 0 && attempt >= input.policy.attempts };
3712
+ }
3713
+ async function applytimeout(input) {
3714
+ let timer;
3715
+ const guard = new Promise((resolve) => {
3716
+ timer = setTimeout(() => resolve("cancelled"), Math.max(0, input.budgetms));
3717
+ });
3718
+ const raced = await Promise.race([input.run().then((value) => ({ kind: "done", value })), guard.then((marker) => ({ kind: "cancelled", marker }))]);
3719
+ if (timer !== void 0) clearTimeout(timer);
3720
+ if (raced.kind === "done") return { aborted: false, value: raced.value };
3721
+ 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() } };
3722
+ }
3723
+ async function applyruntimeout(input) {
3724
+ let timer;
3725
+ const guard = new Promise((resolve) => {
3726
+ timer = setTimeout(() => resolve("cancelled"), Math.max(0, input.budgetms));
3727
+ });
3728
+ const raced = await Promise.race([input.run().then((value) => ({ kind: "done", value })), guard.then((marker) => ({ kind: "cancelled", marker }))]);
3729
+ if (timer !== void 0) clearTimeout(timer);
3730
+ if (raced.kind === "done") return { cancelled: false, value: raced.value };
3731
+ return { cancelled: true, error: new cancellederror(`The run exceeded its reviewed budget of ${input.budgetms} milliseconds and was cancelled.`) };
3732
+ }
3733
+ async function runcatch(input) {
3734
+ 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 });
3735
+ return { ok: body.ok, scopes: body.scopes, log: body.log };
3736
+ }
3737
+ async function runtry(input) {
3738
+ const timeouts = [];
3739
+ const retries = [];
3740
+ const runonce = async (child, scopes) => {
3741
+ if (iscontrolflowkind(child.kind)) {
3742
+ const result = await runcontrolstep({ step: child, scopes, outputs: input.outputs, execute: input.execute, now: input.now });
3743
+ return { ok: result.output.ok, scopes: result.scopes, log: result.log, output: result.output };
3744
+ }
3745
+ const executed = await runstep({ step: child, scopes, outputs: input.outputs, execute: input.execute, now: input.now });
3746
+ return { ok: executed.output.ok, scopes: executed.scopes, log: [...executed.childlog ?? [], executed.log], output: executed.output };
3747
+ };
3748
+ const runchild = async (child, scopes) => {
3749
+ const attempt = async () => {
3750
+ if (input.fragile.retry === void 0) return await runonce(child, scopes);
3751
+ 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) });
3752
+ retries.push(...retried.attempts);
3753
+ return retried.value;
3754
+ };
3755
+ if (input.fragile.timeout?.stepms === void 0) return await attempt();
3756
+ const guarded = await applytimeout({ stepid: child.id, budgetms: input.fragile.timeout.stepms, run: attempt });
3757
+ if (!guarded.aborted) return guarded.value;
3758
+ if (guarded.abort) timeouts.push(guarded.abort);
3759
+ return { ok: false, scopes, log: [], output: guarded.output };
3760
+ };
3761
+ const runbodyof = async (scopes) => {
3762
+ let current = scopes;
3763
+ const log = [];
3764
+ for (const child of input.fragile.steps) {
3765
+ const executed = await runchild(child, current);
3766
+ current = executed.scopes;
3767
+ log.push(...executed.log);
3768
+ if (!executed.ok) return { ok: false, scopes: current, log, failure: executed.output };
3769
+ }
3770
+ return { ok: true, scopes: current, log };
3771
+ };
3772
+ let body;
3773
+ if (input.fragile.timeout?.runms !== void 0) {
3774
+ const guarded = await applytimeout({ stepid: input.step.id, budgetms: input.fragile.timeout.runms, run: () => runbodyof(input.scopes) });
3775
+ if (guarded.aborted) {
3776
+ if (guarded.abort) timeouts.push({ ...guarded.abort, scope: "run" });
3777
+ 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 } } };
3778
+ } else {
3779
+ body = guarded.value;
3780
+ }
3781
+ } else {
3782
+ body = await runbodyof(input.scopes);
3783
+ }
3784
+ if (body.ok) {
3785
+ 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"}` : ""}.`;
3786
+ if (retries.length === 0 && timeouts.length === 0) return { ok: true, scopes: body.scopes, log: body.log, summary };
3787
+ 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 } : {} } };
3788
+ }
3789
+ const handler = await runcatch({ handler: input.fragile.catch, scopes: body.scopes, outputs: input.outputs, execute: input.execute, now: input.now });
3790
+ const errorclass = errorclassof(body.failure ?? { ok: false, summary: "" });
3791
+ 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 } };
3792
+ 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 };
3793
+ if (input.fragile.catch.rerun === true) {
3794
+ const rerun = await runbodyof(handler.scopes);
3795
+ 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 };
3796
+ 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 };
3797
+ }
3798
+ 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 };
3799
+ }
3800
+ function seedof(text2) {
3801
+ let hash = 2166136261;
3802
+ for (let index = 0; index < text2.length; index += 1) {
3803
+ hash ^= text2.charCodeAt(index);
3804
+ hash = Math.imul(hash, 16777619) >>> 0;
3805
+ }
3806
+ return hash >>> 0;
3807
+ }
3808
+ async function runcontrolstep(input) {
3809
+ const payload = controloptions(input.step);
3810
+ const base = { step: input.step, scopes: input.scopes, outputs: input.outputs, execute: input.execute, now: input.now, ...input.path !== void 0 ? { path: input.path } : {} };
3811
+ let result;
3812
+ switch (input.step.kind) {
3813
+ case "condition": {
3814
+ const condition = conditionof(payload.condition);
3815
+ if (!condition) throw new Error("The condition step needs a reviewed boolean expression in its options.");
3816
+ const value = evaluatecondition(condition, input.scopes);
3817
+ const scopes = setvariable(input.scopes, condition.expression.result, condition.expression.resultkind, value, input.now);
3818
+ const summary = `The condition ${condition.expression.result} ${value ? "holds" : "does not hold"} over the extracted values.`;
3819
+ return { scopes, log: [], output: { ok: true, summary, details: { condition: { result: condition.expression.result, value } } } };
3820
+ }
3821
+ case "branch": {
3822
+ const branch = branchof(payload.branch);
3823
+ if (!branch) throw new Error("The branch step needs reviewed unique paths with boolean match expressions and an else path in its options.");
3824
+ const chosen = choosebranch({ stepid: input.step.id, branch, scopes: input.scopes, ...input.pagestate !== void 0 ? { pagestate: input.pagestate } : {}, now: input.now });
3825
+ const body = await runbody({ ...base, steps: chosen.steps });
3826
+ 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 } };
3827
+ break;
3828
+ }
3829
+ case "loop": {
3830
+ const loop = loopof(payload.loop);
3831
+ 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.");
3832
+ result = await runloop({ ...base, loop });
3833
+ break;
3834
+ }
3835
+ case "repeatuntil": {
3836
+ const repeat = repeatuntilof(payload.repeatuntil);
3837
+ 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.");
3838
+ result = await runrepeatuntil({ ...base, repeat });
3839
+ break;
3840
+ }
3841
+ case "whileloop": {
3842
+ const condition = whileof(payload.while);
3843
+ 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.");
3844
+ result = await runwhile({ ...base, condition });
3845
+ break;
3846
+ }
3847
+ case "foreach": {
3848
+ const foreach = foreachof(payload.foreach);
3849
+ 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.");
3850
+ result = await runforeach({ ...base, foreach, ...input.resolveelements !== void 0 ? { resolveelements: input.resolveelements } : {} });
3851
+ break;
3852
+ }
3853
+ case "parallel": {
3854
+ const parallel = parallelof(payload.parallel);
3855
+ 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.");
3856
+ result = await runparallel({ ...base, parallel });
3857
+ break;
3858
+ }
3859
+ case "trycatch": {
3860
+ const fragile = tryof(payload.try);
3861
+ if (!fragile) throw new Error("The try step needs a fragile body, a catch handler and optional retry and timeout policies in its options.");
3862
+ result = await runtry({ ...base, fragile });
3863
+ break;
3864
+ }
3865
+ default:
3866
+ throw new Error(`The ${input.step.kind} step is not a control flow kind.`);
3867
+ }
3868
+ if (result.decision !== void 0 && input.runid !== void 0) result.decision.runid = input.runid;
3869
+ return { scopes: result.scopes, log: result.log, output: { ok: result.ok, summary: result.summary, ...result.decision !== void 0 ? { details: { control: result.decision } } : {} } };
3870
+ }
3871
+
3180
3872
  // workflow.ts
3181
3873
  var workflowkinds = ["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars"];
3182
3874
  function workflowstepof(value) {
@@ -3324,12 +4016,19 @@ function composeworkflow(input) {
3324
4016
  const steps = expandblocks(input.steps, blocks);
3325
4017
  for (const step of steps) {
3326
4018
  if (input.kindallowed && !input.kindallowed(step.kind)) throw new Error(`The workflow step kind ${step.kind} is not a reviewed action kind.`);
4019
+ if (iscontrolflowkind(step.kind)) {
4020
+ validatecontrolpayload(step);
4021
+ for (const child of controlsteps(step)) {
4022
+ 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.`);
4023
+ }
4024
+ }
3327
4025
  if (step.bindings) for (const binding of step.bindings) {
3328
4026
  if (!steps.some((other) => other.id === binding.stepid)) throw new Error(`The binding of ${binding.variable} references the unknown step ${binding.stepid}.`);
3329
4027
  }
3330
4028
  }
3331
4029
  const riskof = input.riskof ?? (() => "sensitive");
3332
- const risk = steps.some((step) => riskof(step.kind) === "sensitive") ? "sensitive" : steps.some((step) => riskof(step.kind) === "interaction") ? "interaction" : "read";
4030
+ const gradedkinds = steps.flatMap((step) => [step.kind, ...controlsteps(step).map((child) => child.kind)]);
4031
+ const risk = gradedkinds.some((kind) => riskof(kind) === "sensitive") ? "sensitive" : gradedkinds.some((kind) => riskof(kind) === "interaction") ? "interaction" : "read";
3333
4032
  const record2 = { id: input.id ?? crypto.randomUUID(), name: input.name, version: input.version, origins: [...new Set(origins)], steps, blocks, risk, createdat: input.now };
3334
4033
  return deepfreeze(record2);
3335
4034
  }
@@ -3610,21 +4309,24 @@ async function runstep(input) {
3610
4309
  }
3611
4310
  stepvalue = interpolated.text;
3612
4311
  }
3613
- const target = input.step.target !== void 0 ? interpolate(input.step.target, scopes) : void 0;
4312
+ const controlled = iscontrolflowkind(input.step.kind);
4313
+ const target = !controlled && input.step.target !== void 0 ? interpolate(input.step.target, scopes) : void 0;
3614
4314
  if (target) consumed.push(...target.consumed);
3615
- const value = stepvalue !== void 0 ? interpolate(stepvalue, scopes) : void 0;
4315
+ const value = !controlled && stepvalue !== void 0 ? interpolate(stepvalue, scopes) : void 0;
3616
4316
  if (value) consumed.push(...value.consumed);
3617
- const options = input.step.options !== void 0 ? interpolate(input.step.options, scopes) : void 0;
4317
+ const options = !controlled && input.step.options !== void 0 ? interpolate(input.step.options, scopes) : void 0;
3618
4318
  if (options) consumed.push(...options.consumed);
3619
4319
  const dispatchable = { ...input.step, ...target !== void 0 ? { target: target.text } : {}, ...value !== void 0 ? { value: value.text } : {}, ...options !== void 0 ? { options: options.text } : {} };
3620
- const output = await input.execute(dispatchable, { scopes, ...input.block !== void 0 ? { block: input.block } : {} });
4320
+ const output = await input.execute(dispatchable, { scopes, outputs: input.outputs, ...input.block !== void 0 ? { block: input.block } : {} });
4321
+ if (output.scopes !== void 0) scopes = output.scopes;
4322
+ const childlog = output.log;
3621
4323
  if (input.step.bindings) {
3622
4324
  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);
3623
4325
  scopes = bound.scopes;
3624
4326
  produced = [.../* @__PURE__ */ new Set([...produced, ...bound.produced])];
3625
4327
  }
3626
4328
  const duration = Date.now() - startedat;
3627
- 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 } : {} }), output };
4329
+ 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 };
3628
4330
  } catch (error) {
3629
4331
  const duration = Date.now() - startedat;
3630
4332
  const summary = error instanceof Error ? error.message : String(error);
@@ -3656,6 +4358,7 @@ async function runworkflow(input) {
3656
4358
  }
3657
4359
  const executed = await runstep({ step, scopes, outputs, execute: input.execute, now: Date.now(), ...step.block !== void 0 ? { block: step.block } : {} });
3658
4360
  scopes = executed.scopes;
4361
+ if (executed.childlog !== void 0) log.push(...executed.childlog);
3659
4362
  log.push(executed.log);
3660
4363
  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() };
3661
4364
  if (!executed.output.ok) {
@@ -3916,8 +4619,8 @@ function consolediff(input) {
3916
4619
 
3917
4620
  // policy.ts
3918
4621
  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"]);
3919
- 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"]);
3920
- 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"]);
4622
+ 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"]);
4623
+ 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"]);
3921
4624
  var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
3922
4625
  var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
3923
4626
  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"]);
@@ -3938,7 +4641,7 @@ var cdpactions = /* @__PURE__ */ new Set(["attachcdp", "detachcdp", "cdpcmd", "w
3938
4641
  var profileractions = /* @__PURE__ */ new Set(["measureflow", "heapshot", "trackmemory", "profilecpu", "watchshifts", "traceload", "annotatetrace", "replaytrace", "capturesourcemaps"]);
3939
4642
  var emulationactions = /* @__PURE__ */ new Set(["emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "blackboxscripts"]);
3940
4643
  var sessionactions = /* @__PURE__ */ new Set(["persiststate", "capturesession", "restoresession", "namedsessions", "diffsessions", "searchsessions", "exportsessions", "importsessions"]);
3941
- var workflowactions = /* @__PURE__ */ new Set(["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars"]);
4644
+ var workflowactions = /* @__PURE__ */ new Set(["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars", "condition", "branch", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"]);
3942
4645
  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"]);
3943
4646
  var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
3944
4647
  var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
@@ -5583,6 +6286,68 @@ function validateworkflowgrammar(step, options) {
5583
6286
  if (typeof options.text !== "string") return { allowed: false, reason: "The variable extraction needs the reviewed text the regex rule applies to." };
5584
6287
  return { allowed: true };
5585
6288
  }
6289
+ if (kind === "condition") {
6290
+ const condition = conditionof(options.condition);
6291
+ if (!condition) return { allowed: false, reason: "The condition step needs a reviewed boolean expression in its options." };
6292
+ const operatorcheck = validatexpressionoperators(condition.expression);
6293
+ if (!operatorcheck.allowed) return operatorcheck;
6294
+ return { allowed: true };
6295
+ }
6296
+ if (kind === "branch") {
6297
+ const branch = branchof(options.branch);
6298
+ 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." };
6299
+ for (const path of [...branch.paths, branch.else]) {
6300
+ if (path.when === void 0) continue;
6301
+ const operatorcheck = validatexpressionoperators(path.when);
6302
+ if (!operatorcheck.allowed) return operatorcheck;
6303
+ }
6304
+ return controlchildkinds(step);
6305
+ }
6306
+ if (kind === "loop") {
6307
+ const loop = loopof(options.loop);
6308
+ 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." };
6309
+ return controlchildkinds(step);
6310
+ }
6311
+ if (kind === "repeatuntil") {
6312
+ const repeat = repeatuntilof(options.repeatuntil);
6313
+ 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." };
6314
+ const operatorcheck = validatexpressionoperators(repeat.until);
6315
+ if (!operatorcheck.allowed) return operatorcheck;
6316
+ return controlchildkinds(step);
6317
+ }
6318
+ if (kind === "whileloop") {
6319
+ const condition = whileof(options.while);
6320
+ 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." };
6321
+ const operatorcheck = validatexpressionoperators(condition.while);
6322
+ if (!operatorcheck.allowed) return operatorcheck;
6323
+ return controlchildkinds(step);
6324
+ }
6325
+ if (kind === "foreach") {
6326
+ const foreach = foreachof(options.foreach);
6327
+ 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." };
6328
+ return controlchildkinds(step);
6329
+ }
6330
+ if (kind === "parallel") {
6331
+ const parallel = parallelof(options.parallel);
6332
+ 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." };
6333
+ return controlchildkinds(step);
6334
+ }
6335
+ if (kind === "trycatch") {
6336
+ const fragile = tryof(options.try);
6337
+ 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." };
6338
+ return controlchildkinds(step);
6339
+ }
6340
+ return { allowed: true };
6341
+ }
6342
+ function controlchildkinds(step) {
6343
+ const children = controlsteps({ id: step.id, kind: step.kind, label: step.summary, ...step.options !== void 0 ? { options: step.options } : {} });
6344
+ for (const child of children) {
6345
+ try {
6346
+ actionrisk(child.kind);
6347
+ } catch {
6348
+ return { allowed: false, reason: `The ${child.kind} step inside the control payload of the ${step.kind} step is not a reviewed action kind.` };
6349
+ }
6350
+ }
5586
6351
  return { allowed: true };
5587
6352
  }
5588
6353
  function validateregexrule(pattern) {
@@ -5644,6 +6409,20 @@ function workflowgate(input) {
5644
6409
  return { allowed: true };
5645
6410
  }
5646
6411
  function dryrunprojection(step) {
6412
+ if (iscontrolflowkind(step.kind)) {
6413
+ for (const child of controlsteps(step)) {
6414
+ 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 } : {} });
6415
+ if (childrisk !== "read") return void 0;
6416
+ }
6417
+ if (step.kind === "condition") return "The condition step would evaluate its reviewed expression over the extracted values with no page side effect.";
6418
+ if (step.kind === "branch") return "The branch step would choose one reviewed path by page state and only the chosen path would run.";
6419
+ 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.";
6420
+ if (step.kind === "repeatuntil") return "The repeat until step would rerun its body until the convergence expression holds inside the safety bound.";
6421
+ if (step.kind === "whileloop") return "The while step would loop while its condition holds inside the reviewed safety bound.";
6422
+ if (step.kind === "foreach") return "The foreach step would iterate the elements of its reviewed selector binding the item and index variables per iteration.";
6423
+ if (step.kind === "parallel") return "The parallel step would run its branches concurrently and join their outcomes under the reviewed strategy.";
6424
+ return "The try step would run its fragile body and only the catch handler on failure.";
6425
+ }
5647
6426
  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 } : {} });
5648
6427
  if (risk !== "read") return void 0;
5649
6428
  if (step.kind === "delay") return `The delay step would sleep its reviewed base inside the jitter window.`;
@@ -6422,30 +7201,30 @@ function canexecute(input) {
6422
7201
  if (iscontrolkind(input.step.kind)) {
6423
7202
  const controlgate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: "control the network" });
6424
7203
  if (!controlgate.allowed) return controlgate;
6425
- let controloptions = {};
7204
+ let controloptions2 = {};
6426
7205
  try {
6427
- controloptions = parseoptions(input.step);
7206
+ controloptions2 = parseoptions(input.step);
6428
7207
  } catch {
6429
- controloptions = {};
7208
+ controloptions2 = {};
6430
7209
  }
6431
7210
  if (input.step.kind === "blockrequest") {
6432
7211
  const blockgatecheck = blockgate(input.session, input.step, now);
6433
7212
  if (!blockgatecheck.allowed) return blockgatecheck;
6434
- const rule = blockruleof(controloptions.block);
7213
+ const rule = blockruleof(controloptions2.block);
6435
7214
  if (rule) {
6436
7215
  const blockorigin = origincheck(input.session, rule.urlpattern);
6437
7216
  if (!blockorigin.allowed) return blockorigin;
6438
7217
  }
6439
7218
  }
6440
7219
  if (input.step.kind === "mockresponse" || input.step.kind === "rewriteheaders") {
6441
- const patterns = input.step.kind === "mockresponse" ? [mockspecof(controloptions.mock)?.urlpattern ?? ""] : Array.isArray(controloptions.rules) ? controloptions.rules.map((item) => item && typeof item === "object" && !Array.isArray(item) ? String(item.urlpattern ?? "") : "") : [];
7220
+ 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 ?? "") : "") : [];
6442
7221
  for (const pattern of patterns) {
6443
7222
  const patterngate = origincheck(input.session, pattern);
6444
7223
  if (!patterngate.allowed) return patterngate;
6445
7224
  }
6446
7225
  }
6447
7226
  if (input.step.kind === "setcookies" || input.step.kind === "readcookies" || input.step.kind === "clearcookies") {
6448
- const domain = typeof controloptions.domain === "string" && controloptions.domain.trim() ? controloptions.domain : Array.isArray(controloptions.cookies) ? String(controloptions.cookies[0]?.domain ?? "") : "";
7227
+ const domain = typeof controloptions2.domain === "string" && controloptions2.domain.trim() ? controloptions2.domain : Array.isArray(controloptions2.cookies) ? String(controloptions2.cookies[0]?.domain ?? "") : "";
6449
7228
  if (!domain) return { allowed: false, reason: "A reviewed cookie domain is required before cookie control runs." };
6450
7229
  const cookiegatecheck = cookiegate(input.session, domain, now);
6451
7230
  if (!cookiegatecheck.allowed) return cookiegatecheck;
@@ -6677,13 +7456,13 @@ function recordsession(progress, planid, stepid, entry, now) {
6677
7456
  }
6678
7457
  function recordworkflow(progress, planid, stepid, entry, now) {
6679
7458
  const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
6680
- const counts = `${entry.executed !== void 0 ? `${entry.executed} executed step${entry.executed === 1 ? "" : "s"}, ` : ""}${entry.refused !== void 0 ? `${entry.refused} refused step${entry.refused === 1 ? "" : "s"}, ` : ""}${entry.total !== void 0 ? `${entry.total} total step${entry.total === 1 ? "" : "s"}, ` : ""}`.replace(/, $/, "");
7459
+ const counts = `${entry.executed !== void 0 ? `${entry.executed} executed step${entry.executed === 1 ? "" : "s"}, ` : ""}${entry.refused !== void 0 ? `${entry.refused} refused step${entry.refused === 1 ? "" : "s"}, ` : ""}${entry.total !== void 0 ? `${entry.total} total step${entry.total === 1 ? "" : "s"}, ` : ""}${entry.iterations !== void 0 ? `${entry.iterations} iteration${entry.iterations === 1 ? "" : "s"}, ` : ""}${entry.denominator !== void 0 ? `denominator ${entry.denominator}, ` : ""}`.replace(/, $/, "");
6681
7460
  const outcome = { stepid, ok: true, summary: `${entry.detail}${counts.length > 0 ? ` with ${counts}` : ""}.`, details: { workflow: entry }, at: now };
6682
7461
  return recordoutcome(base, planid, outcome, now);
6683
7462
  }
6684
7463
 
6685
7464
  // version.ts
6686
- var packageversion = "1.1.50";
7465
+ var packageversion = "1.1.51";
6687
7466
 
6688
7467
  // types.ts
6689
7468
  var protocolversion = packageversion;
@@ -6983,7 +7762,7 @@ function parseproposal(value, origin, grants) {
6983
7762
  }
6984
7763
  function workflowoutcome(input) {
6985
7764
  const selected = input.stepid !== void 0 ? input.entries.filter((entry) => entry.stepid === input.stepid) : input.entries;
6986
- 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 } : {} }));
7765
+ 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 } : {} }));
6987
7766
  return { version: protocolversion, runid: input.run.id, workflowid: input.run.workflowid, state: input.run.state, ...input.run.dryrun === true ? { dryrun: true } : {}, steps };
6988
7767
  }
6989
7768
  function stepof(kind, candidate, index) {
@@ -7001,7 +7780,7 @@ function requestbody(input) {
7001
7780
  return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
7002
7781
  }
7003
7782
  function outcomeresponse(input) {
7004
- 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 } } : {} });
7783
+ 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 } : {} } } : {} });
7005
7784
  }
7006
7785
  function mapresponse(input) {
7007
7786
  return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
@@ -7136,7 +7915,7 @@ function sessionreport(input) {
7136
7915
  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 } : {} };
7137
7916
  }
7138
7917
  function workflowreport(input) {
7139
- return { version: protocolversion, workflows: input.workflows, runs: input.runs, templates: input.templates, log: input.log ?? [], scopes: input.scopes ?? [], provenance: input.provenance ?? [] };
7918
+ return { version: protocolversion, workflows: input.workflows, runs: input.runs, templates: input.templates, log: input.log ?? [], scopes: input.scopes ?? [], provenance: input.provenance ?? [], control: input.control ?? [] };
7140
7919
  }
7141
7920
 
7142
7921
  // capture.ts
@@ -8866,7 +9645,7 @@ function stepoptions2(step) {
8866
9645
  }
8867
9646
  async function refreshcapabilities() {
8868
9647
  const report = await readcapabilities();
8869
- const withmedia = { ...report, captures: [...capturekinds], media: [...mediakinds], http: [...httpkinds], netwatch: [...socketkinds, ...netwatchkinds], control: [...controlkinds], debug: [...timelinekinds, ...cdpkinds], profile: [...profilerkinds], emulation: [...emulationkinds], sessions: [...sessionkinds], workflow: [...workflowkinds] };
9648
+ const withmedia = { ...report, captures: [...capturekinds], media: [...mediakinds], http: [...httpkinds], netwatch: [...socketkinds, ...netwatchkinds], control: [...controlkinds], debug: [...timelinekinds, ...cdpkinds], profile: [...profilerkinds], emulation: [...emulationkinds], sessions: [...sessionkinds], workflow: [...workflowkinds, ...controlflowkinds] };
8870
9649
  await memory.setcapabilities(withmedia);
8871
9650
  return withmedia;
8872
9651
  }
@@ -11566,7 +12345,7 @@ async function executehttpstep(step, session, plan, tabid2, origin) {
11566
12345
  var activesockets = /* @__PURE__ */ new Map();
11567
12346
  var channelbuses = /* @__PURE__ */ new Map();
11568
12347
  var netpoll = 100;
11569
- function waitsome(milliseconds) {
12348
+ function waitsome2(milliseconds) {
11570
12349
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, milliseconds)));
11571
12350
  }
11572
12351
  async function queueinboundmessage(channelid, payload) {
@@ -11654,7 +12433,7 @@ async function runsubscription(subscription, controller) {
11654
12433
  }
11655
12434
  if (controller.signal.aborted) break;
11656
12435
  if (subscription.lifetime !== void 0 && Date.now() - startedat >= subscription.lifetime) break;
11657
- await waitsome(netpoll * 10);
12436
+ await waitsome2(netpoll * 10);
11658
12437
  }
11659
12438
  const closed = { ...current, state: "closed", closedat: Date.now() };
11660
12439
  await memory.setsubscription(closed);
@@ -11748,7 +12527,7 @@ async function executesocketstep(step, session, plan, tabid2, origin) {
11748
12527
  const queued = await memory.getmessages(channelid);
11749
12528
  matched = queued.filter((envelope) => matchmessage(filter, envelope)).slice(0, filter.limit ?? (matched.length || void 0));
11750
12529
  if (matched.length >= (filter.limit ?? 1) || Date.now() >= deadline) break;
11751
- await waitsome(netpoll);
12530
+ await waitsome2(netpoll);
11752
12531
  }
11753
12532
  await memory.drainmessages(matched.map((envelope) => ({ channelid: envelope.channelid, sequence: envelope.sequence })));
11754
12533
  await audit("socket", `The message wait on channel ${channelid} matched ${matched.length} envelope${matched.length === 1 ? "" : "s"} of the reviewed filter within the ${budget} millisecond budget; payload values stay out of the audit trail.`, extra);
@@ -11809,7 +12588,7 @@ async function executesocketstep(step, session, plan, tabid2, origin) {
11809
12588
  break;
11810
12589
  }
11811
12590
  stopcursor = decision.cursor;
11812
- await waitsome(decision.next?.wait ?? cursor.interval);
12591
+ await waitsome2(decision.next?.wait ?? cursor.interval);
11813
12592
  next = { url: decision.next?.url ?? next.url, ...decision.next?.body !== void 0 ? { body: decision.next.body } : {} };
11814
12593
  }
11815
12594
  } finally {
@@ -11831,7 +12610,7 @@ async function executenetwatchstep(step, session, plan, tabid2, origin) {
11831
12610
  const limit = typeof options.limit === "number" && Number.isInteger(options.limit) && options.limit >= 1 ? options.limit : void 0;
11832
12611
  const before = await bridgecall(tabid2, "resourcerecords");
11833
12612
  const known = new Set(resourcefacts(before ?? []).map((fact) => `${fact.url}@${fact.start}`));
11834
- if (window2 > 0) await waitsome(window2);
12613
+ if (window2 > 0) await waitsome2(window2);
11835
12614
  const after = await bridgecall(tabid2, "resourcerecords");
11836
12615
  const fresh = resourcefacts(after ?? []).filter((fact) => !known.has(`${fact.url}@${fact.start}`));
11837
12616
  const chosen = limit !== void 0 ? fresh.slice(0, limit) : fresh;
@@ -13342,7 +14121,7 @@ async function sleepreviewed(sampled, stepid) {
13342
14121
  } catch {
13343
14122
  }
13344
14123
  }
13345
- await waitsome(sampled);
14124
+ await waitsome2(sampled);
13346
14125
  return "timer";
13347
14126
  }
13348
14127
  async function executewaitelement(step, tabid2) {
@@ -13360,7 +14139,7 @@ async function executewaitelement(step, tabid2) {
13360
14139
  const probe = await bridgecall(tabid2, "elementrect", wait.selector).catch(() => void 0);
13361
14140
  if (probe?.ok) return { ok: true, summary: `Selector ${wait.selector} appeared after ${Date.now() - startedat} milliseconds of polling every ${wait.poll} milliseconds.`, details: { selector: wait.selector, waited: Date.now() - startedat, poll: wait.poll } };
13362
14141
  if (singlepass || Date.now() >= deadline) break;
13363
- await waitsome(pass + 1 < plan.probes ? wait.poll : plan.lastwait);
14142
+ await waitsome2(pass + 1 < plan.probes ? wait.poll : plan.lastwait);
13364
14143
  }
13365
14144
  return { ok: false, summary: `Selector ${wait.selector} did not appear within the reviewed timeout of ${wait.timeout} milliseconds.`, details: { selector: wait.selector, waited: Date.now() - startedat } };
13366
14145
  }
@@ -13475,13 +14254,26 @@ async function executeworkflowrun(step, session, plan, tabid2, origin, dry) {
13475
14254
  activeworkflowruns.set(run.id, guards);
13476
14255
  await audit("workflow", `Started the run ${run.id} of the workflow ${record2.name} version ${record2.version} with ${record2.steps.length} reviewed steps; every step passes the session, plan review and origin gates.`, { sessionid: session.id, planid: plan.id, stepid: step.id });
13477
14256
  const context = { session, plan, tabid: tabid2, origin };
13478
- const execute = async (workflowstep) => {
14257
+ const executesteprouted = async (workflowstep, stepcontext) => {
13479
14258
  if (guards.cancelled) return { ok: false, summary: `The run was cancelled before the ${workflowstep.label} step dispatched.` };
13480
- return await dispatchworkflowstep(workflowstep, context);
14259
+ if (!iscontrolflowkind(workflowstep.kind)) return await dispatchworkflowstep(workflowstep, context);
14260
+ const controlled = await runcontrolstep({
14261
+ step: workflowstep,
14262
+ scopes: stepcontext.scopes,
14263
+ outputs: stepcontext.outputs ?? {},
14264
+ execute: async (dispatched, inner) => await executesteprouted(dispatched, inner),
14265
+ now: Date.now(),
14266
+ runid: run.id,
14267
+ pagestate: await pagestateof(tabid2),
14268
+ resolveelements: async (selector) => await resolveelements(tabid2, selector)
14269
+ });
14270
+ return { ...controlled.output, scopes: controlled.scopes, log: controlled.log };
13481
14271
  };
14272
+ const execute = async (workflowstep, stepcontext) => await executesteprouted(workflowstep, stepcontext);
13482
14273
  let result;
14274
+ const runbudget = options.timeout && typeof options.timeout === "object" && !Array.isArray(options.timeout) && typeof options.timeout.runms === "number" && Number.isFinite(options.timeout.runms) && options.timeout.runms > 0 ? options.timeout.runms : void 0;
13483
14275
  try {
13484
- result = await runworkflow({
14276
+ const launch = () => runworkflow({
13485
14277
  record: record2,
13486
14278
  run,
13487
14279
  scopes: runscopes(options.variables),
@@ -13497,6 +14289,18 @@ async function executeworkflowrun(step, session, plan, tabid2, origin, dry) {
13497
14289
  await refreshbadge();
13498
14290
  }
13499
14291
  });
14292
+ if (runbudget === void 0) {
14293
+ result = await launch();
14294
+ } else {
14295
+ const raced = await applyruntimeout({ budgetms: runbudget, run: launch });
14296
+ if (raced.cancelled) {
14297
+ guards.cancelled = true;
14298
+ const aborted = await storetimeoutabort(run, step, raced.error.message, runbudget);
14299
+ result = { run: aborted.run, scopes: [], log: aborted.log, outputs: {} };
14300
+ } else {
14301
+ result = raced.value;
14302
+ }
14303
+ }
13500
14304
  } finally {
13501
14305
  activeworkflowruns.delete(run.id);
13502
14306
  }
@@ -13506,12 +14310,59 @@ async function executeworkflowrun(step, session, plan, tabid2, origin, dry) {
13506
14310
  if (!stored.some((candidate) => candidate.stepid === entry.stepid && candidate.startedat === entry.startedat)) await memory.addrunlogentry(run.id, entry);
13507
14311
  }
13508
14312
  await memory.setrunscopes(run.id, result.scopes);
14313
+ const decisions = [];
14314
+ for (const entry of result.log) {
14315
+ const control = entry.details?.control;
14316
+ if (control && typeof control === "object" && !Array.isArray(control)) {
14317
+ const decision = control;
14318
+ decisions.push(decision);
14319
+ await memory.addcontroldecision(run.id, decision);
14320
+ }
14321
+ }
14322
+ for (const decision of decisions) await auditcontroldecision(run.id, decision, session.id, plan.id);
14323
+ const loopbounds = record2.steps.flatMap((controlstep) => {
14324
+ const summary = controlsummary(controlstep);
14325
+ return summary !== void 0 && summary.bound !== void 0 && (controlstep.kind === "loop" || controlstep.kind === "repeatuntil" || controlstep.kind === "whileloop") ? [summary.bound] : [];
14326
+ });
14327
+ if (loopbounds.length > 0) {
14328
+ const iterations = decisions.filter((decision) => decision.kind === "loop").reduce((total, decision) => total + (decision.loops?.length ?? 0), 0);
14329
+ const denominator = loopbounds.reduce((total, bound) => total + bound, 0);
14330
+ await memory.setprogress(recordworkflow(await memory.getprogress(), plan.id, step.id, { family: "control", detail: `The control flow steps of the workflow ${record2.name} ran their loops toward the reviewed bounds`, runid: run.id, iterations, denominator }, Date.now()));
14331
+ }
13509
14332
  const executed = result.run.cursor;
13510
14333
  await memory.setprogress(recordworkflow(await memory.getprogress(), plan.id, step.id, { family: "run", detail: `Ran the workflow ${record2.name}`, runid: run.id, executed, total: record2.steps.length }, Date.now()));
13511
14334
  await audit("workflow", `The run ${run.id} of the workflow ${record2.name} ended ${result.run.state} after ${executed} of ${record2.steps.length} steps${result.run.failreason !== void 0 ? ` with the failure ${result.run.failreason}` : ""}.`, { sessionid: session.id, planid: plan.id, stepid: step.id });
13512
14335
  await refreshbadge();
13513
14336
  return { ok: result.run.state === "done", summary: `The workflow run ended ${result.run.state} after ${executed} of ${record2.steps.length} steps.`, details: { runid: run.id, state: result.run.state, executed, total: record2.steps.length, ...result.run.failreason !== void 0 ? { failreason: result.run.failreason } : {} } };
13514
14337
  }
14338
+ async function pagestateof(tabid2) {
14339
+ const tab = await chrome.tabs.get(tabid2).catch(() => void 0);
14340
+ if (!tab) return {};
14341
+ return { ...tab.url !== void 0 ? { url: tab.url } : {}, ...tab.title !== void 0 ? { title: tab.title } : {}, ...tab.status !== void 0 ? { ready: tab.status === "complete" } : {} };
14342
+ }
14343
+ async function resolveelements(tabid2, selector) {
14344
+ const resolved = await bridgecall(tabid2, "queryelements", selector).catch(() => void 0);
14345
+ if (!resolved?.ok) return [];
14346
+ return resolved.selectors ?? [];
14347
+ }
14348
+ async function storetimeoutabort(run, step, message, budget) {
14349
+ const at = Date.now();
14350
+ const aborted = { ...run, state: "failed", endedat: at, failreason: message };
14351
+ const entry = { stepid: step.id, label: `Run budget of ${step.id}`, state: "failed", startedat: at, duration: 0, summary: message, details: { errorclass: "timeout", budget, cancelled: true, control: { runid: run.id, stepid: step.id, kind: "timeout", at, timeouts: [{ stepid: step.id, budget, scope: "run", at }] } } };
14352
+ const decision = entry.details?.control;
14353
+ await memory.addcontroldecision(run.id, decision);
14354
+ await memory.addrunlogentry(run.id, entry);
14355
+ await audit("workflow", `The run ${run.id} exceeded its reviewed budget of ${budget} milliseconds and was cancelled with the cancelled error class.`, { planid: step.id });
14356
+ return { run: aborted, log: [entry] };
14357
+ }
14358
+ async function auditcontroldecision(runid, decision, sessionid, planid) {
14359
+ if (decision.kind === "branch" && decision.branch !== void 0) await audit("workflow", `The branch step ${decision.stepid} of the run ${runid} chose the path ${decision.branch.path}: ${decision.branch.reason}`, { sessionid, planid });
14360
+ else if (decision.kind === "loop" && decision.loops !== void 0) await audit("workflow", `The loop step ${decision.stepid} of the run ${runid} recorded ${decision.loops.length} iteration counter${decision.loops.length === 1 ? "" : "s"} with the paths ${decision.loops.map((counter) => counter.path).join(", ")}.`, { sessionid, planid });
14361
+ else if (decision.kind === "join" && decision.join !== void 0) await audit("workflow", `The join of the parallel step ${decision.stepid} of the run ${runid} merged ${decision.join.merged.length} variable${decision.join.merged.length === 1 ? "" : "s"} under the ${decision.join.strategy} strategy${decision.join.conflicts.length > 0 ? ` with the conflicts ${decision.join.conflicts.join(", ")}` : " with no conflict"}.`, { sessionid, planid });
14362
+ else if (decision.kind === "catch" && decision.catch !== void 0) await audit("workflow", `The catch handler of the try step ${decision.stepid} of the run ${runid} ran after the ${decision.catch.errorclass} failure${decision.catch.rerun ? " and reran the fragile body" : ""}.`, { sessionid, planid });
14363
+ else if (decision.kind === "retry" && decision.retries !== void 0) await audit("workflow", `The retry policy of the try step ${decision.stepid} of the run ${runid} ran ${decision.retries.length} retry attempt${decision.retries.length === 1 ? "" : "s"} with the backoff durations ${decision.retries.map((attempt) => `${attempt.delay} ms`).join(", ")}.`, { sessionid, planid });
14364
+ else if (decision.kind === "timeout" && decision.timeouts !== void 0) await audit("workflow", `The timeout policy of the try step ${decision.stepid} of the run ${runid} aborted ${decision.timeouts.length} step${decision.timeouts.length === 1 ? "" : "s"} that exceeded their reviewed budgets.`, { sessionid, planid });
14365
+ }
13515
14366
  async function executestep(stepid) {
13516
14367
  const session = await memory.getsession();
13517
14368
  const plan = await memory.getplan();
@@ -13845,7 +14696,7 @@ async function handlerequest(message, sender) {
13845
14696
  const livetab = session ? await chrome.tabs.get(session.tabid).catch(() => void 0) : void 0;
13846
14697
  const waitprofile = session ? waitprofiles.find((record2) => record2.origin === session.origin) : void 0;
13847
14698
  const livestate = { phase: livetab?.status === "loading" ? "loading" : "complete", ...navrecords[0] ? { finalurl: navrecords[0].finalurl, redirects: navrecords[0].chain } : {} };
13848
- return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, profileretention: runsettings?.profileretention, traceceiling: runsettings?.traceceiling, profile: profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() }), profileactive: activememorytrackers.size + activeprofiletargets.size, profiletargets: [...activeprofiletargets.values()].flatMap((entry) => entry.targets), socketsactive: activesockets.size, emulation: emulationreport({ ...plan && await loademulationstate(plan.id) !== void 0 ? { state: await loademulationstate(plan.id) } : {}, devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), blackbox: await memory.getblackboxrules(), permissions: await memory.getpermissionoverrides(), consents: await memory.getlocationconsents() }), emulatedlayers: plan ? layernames(await loademulationstate(plan.id)) : [], emulationretention: runsettings?.emulationretention, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), sessionmemory: sessionreport({ records: sessionrecords, events: await memory.getsessionevents(), folders: await memory.getsessionfolders(), diffs: await memory.getsessiondiffs(), ...autosnapshot !== void 0 ? { auto: autosnapshot.interval } : {}, ...crashed ? { crashed: true } : {} }), autosnapshotstate: autosnapshot, sessionretention: runsettings?.sessionretention, workflow: workflowreport({ workflows: await memory.listworkflows(), runs: await memory.listworkflowruns(), templates: await memory.getsteptemplates(), ...newestworkflowrun !== void 0 ? { log: await memory.getrunlog(newestworkflowrun.id), scopes: await memory.getrunscopes(newestworkflowrun.id), provenance: await memory.getworkflowprovenance(newestworkflowrun.id) } : {} }), runlogretention: runsettings?.runlogretention, ...taskstate !== void 0 ? { taskstate } : {}, ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {} };
14699
+ return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, profileretention: runsettings?.profileretention, traceceiling: runsettings?.traceceiling, profile: profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() }), profileactive: activememorytrackers.size + activeprofiletargets.size, profiletargets: [...activeprofiletargets.values()].flatMap((entry) => entry.targets), socketsactive: activesockets.size, emulation: emulationreport({ ...plan && await loademulationstate(plan.id) !== void 0 ? { state: await loademulationstate(plan.id) } : {}, devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), blackbox: await memory.getblackboxrules(), permissions: await memory.getpermissionoverrides(), consents: await memory.getlocationconsents() }), emulatedlayers: plan ? layernames(await loademulationstate(plan.id)) : [], emulationretention: runsettings?.emulationretention, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), sessionmemory: sessionreport({ records: sessionrecords, events: await memory.getsessionevents(), folders: await memory.getsessionfolders(), diffs: await memory.getsessiondiffs(), ...autosnapshot !== void 0 ? { auto: autosnapshot.interval } : {}, ...crashed ? { crashed: true } : {} }), autosnapshotstate: autosnapshot, sessionretention: runsettings?.sessionretention, workflow: workflowreport({ workflows: await memory.listworkflows(), runs: await memory.listworkflowruns(), templates: await memory.getsteptemplates(), ...newestworkflowrun !== void 0 ? { log: await memory.getrunlog(newestworkflowrun.id), scopes: await memory.getrunscopes(newestworkflowrun.id), provenance: await memory.getworkflowprovenance(newestworkflowrun.id), control: await memory.listcontroldecisions(newestworkflowrun.id) } : {} }), runlogretention: runsettings?.runlogretention, ...taskstate !== void 0 ? { taskstate } : {}, ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {} };
13849
14700
  }
13850
14701
  case "capabilities":
13851
14702
  return refreshcapabilities();
@@ -15016,7 +15867,49 @@ async function handlerequest(message, sender) {
15016
15867
  const inputreview = message;
15017
15868
  const record2 = await memory.getworkflowrecord(inputreview.workflowid?.trim() ?? "");
15018
15869
  if (!record2) throw new Error(`No composed workflow matches ${inputreview.workflowid ?? ""}.`);
15019
- return { record: record2, steps: record2.steps.map((entry) => ({ id: entry.id, kind: entry.kind, label: entry.label, ...entry.target !== void 0 ? { target: entry.target } : {}, ...entry.value !== void 0 ? { value: entry.value } : {}, ...entry.block !== void 0 ? { block: entry.block } : {}, ...entry.bindings !== void 0 ? { bindings: entry.bindings } : {}, ...entry.expression !== void 0 ? { expression: entry.expression } : {}, ...entry.extract !== void 0 ? { extract: entry.extract } : {} })), blocks: record2.blocks, risk: record2.risk, origins: record2.origins };
15870
+ return { record: record2, steps: record2.steps.map((entry) => ({ id: entry.id, kind: entry.kind, label: entry.label, ...entry.target !== void 0 ? { target: entry.target } : {}, ...entry.value !== void 0 ? { value: entry.value } : {}, ...entry.block !== void 0 ? { block: entry.block } : {}, ...entry.bindings !== void 0 ? { bindings: entry.bindings } : {}, ...entry.expression !== void 0 ? { expression: entry.expression } : {}, ...entry.extract !== void 0 ? { extract: entry.extract } : {}, ...controlsummary(entry) !== void 0 ? { control: controlsummary(entry) } : {} })), blocks: record2.blocks, risk: record2.risk, origins: record2.origins };
15871
+ }
15872
+ case "setloopbound": {
15873
+ const inputbound = message;
15874
+ const session = await memory.getsession();
15875
+ if (!session || session.stoppedat || session.expiresat <= Date.now()) throw new Error("Editing a loop safety bound needs an active browser session behind the consent gates.");
15876
+ const record2 = await memory.getworkflowrecord(inputbound.workflowid?.trim() ?? "");
15877
+ if (!record2) throw new Error(`No composed workflow matches ${inputbound.workflowid ?? ""}.`);
15878
+ if (typeof inputbound.bound !== "number" || !Number.isInteger(inputbound.bound) || inputbound.bound < 1) throw new Error("The loop safety bound must be a positive integer with no code ceiling.");
15879
+ const target = record2.steps.find((entry) => entry.id === (inputbound.stepid ?? ""));
15880
+ if (!target || target.kind !== "loop" && target.kind !== "repeatuntil") throw new Error(`No loop or repeat until step of the workflow matches ${inputbound.stepid ?? ""}.`);
15881
+ let payload;
15882
+ try {
15883
+ payload = JSON.parse(target.options ?? "{}");
15884
+ } catch {
15885
+ payload = {};
15886
+ }
15887
+ const key = target.kind === "loop" ? "loop" : "repeatuntil";
15888
+ const body = payload[key] && typeof payload[key] === "object" && !Array.isArray(payload[key]) ? payload[key] : {};
15889
+ body.bound = inputbound.bound;
15890
+ payload[key] = body;
15891
+ const restep = { ...target, options: JSON.stringify(payload) };
15892
+ const recomposed = composeworkflow({
15893
+ id: record2.id,
15894
+ name: record2.name,
15895
+ version: record2.version + 1,
15896
+ origins: [...record2.origins],
15897
+ steps: record2.steps.map((entry) => entry.id === target.id ? restep : entry),
15898
+ blocks: [...record2.blocks],
15899
+ now: Date.now(),
15900
+ kindallowed: (kind) => {
15901
+ try {
15902
+ actionrisk(kind);
15903
+ return true;
15904
+ } catch {
15905
+ return false;
15906
+ }
15907
+ },
15908
+ riskof: (kind) => actionrisk(kind)
15909
+ });
15910
+ await memory.addworkflowrecord(recomposed);
15911
+ await audit("workflow", `The user edited the loop safety bound of the step ${target.id} of the workflow ${record2.name} to ${inputbound.bound} iterations; version ${recomposed.version} was recomposed and the older version survives for the audit trail.`, { sessionid: session.id });
15912
+ return { workflowid: recomposed.id, stepid: target.id, bound: inputbound.bound, version: recomposed.version };
15020
15913
  }
15021
15914
  case "approveworkflowrun": {
15022
15915
  const inputapprove = message;
@@ -15039,8 +15932,24 @@ async function handlerequest(message, sender) {
15039
15932
  const step = record2.steps.find((entry) => entry.id === (inputsingle.stepid ?? ""));
15040
15933
  if (!step) throw new Error(`No step of the workflow matches ${inputsingle.stepid ?? ""}.`);
15041
15934
  const { tab, origin } = await activecontext();
15042
- const executed = await runstep({ step, scopes: await memory.getrunscopes(stored.run.id), outputs: {}, execute: async (dispatched) => dispatchworkflowstep(dispatched, { session, plan, tabid: tab.id, origin }), now: Date.now(), ...step.block !== void 0 ? { block: step.block } : {} });
15935
+ const executestepsingle = async (dispatched, stepcontext) => {
15936
+ if (iscontrolflowkind(dispatched.kind)) {
15937
+ const controlled = await runcontrolstep({ step: dispatched, scopes: stepcontext.scopes, outputs: stepcontext.outputs ?? {}, execute: executestepsingle, now: Date.now(), runid: stored.run.id, pagestate: await pagestateof(tab.id), resolveelements: async (selector) => await resolveelements(tab.id, selector) });
15938
+ return { ...controlled.output, scopes: controlled.scopes, log: controlled.log };
15939
+ }
15940
+ return await dispatchworkflowstep(dispatched, { session, plan, tabid: tab.id, origin });
15941
+ };
15942
+ const executed = await runstep({ step, scopes: await memory.getrunscopes(stored.run.id), outputs: {}, execute: executestepsingle, now: Date.now(), ...step.block !== void 0 ? { block: step.block } : {} });
15043
15943
  await memory.addrunlogentry(stored.run.id, executed.log);
15944
+ if (executed.childlog !== void 0) for (const entry of executed.childlog) await memory.addrunlogentry(stored.run.id, entry);
15945
+ for (const entry of [executed.log, ...executed.childlog ?? []]) {
15946
+ const control = entry.details?.control;
15947
+ if (control && typeof control === "object" && !Array.isArray(control)) {
15948
+ const decision = control;
15949
+ await memory.addcontroldecision(stored.run.id, decision);
15950
+ await auditcontroldecision(stored.run.id, decision, session.id, plan.id);
15951
+ }
15952
+ }
15044
15953
  await memory.setrunscopes(stored.run.id, executed.scopes);
15045
15954
  const stepindex = record2.steps.findIndex((entry) => entry.id === step.id);
15046
15955
  const isthenext = stepindex === stored.run.cursor && executed.output.ok;
@@ -15073,15 +15982,32 @@ async function handlerequest(message, sender) {
15073
15982
  const scopes = await memory.getrunscopes(stored.run.id);
15074
15983
  const log = await memory.getrunlog(stored.run.id);
15075
15984
  const { tab, origin } = await activecontext();
15076
- const resumed = await runworkflow({ record: record2, run: stored.run, scopes, log, execute: async (dispatched) => dispatchworkflowstep(dispatched, { session, plan, tabid: tab.id, origin }), now: Date.now(), gates: { sessionactive: Boolean(session && !session.stoppedat && !session.pausedat && session.expiresat > Date.now()), planapproved: plan.state === "approved", origingranted: (workfloworigin) => origingranted(session, workfloworigin) }, oncheckpoint: async (state) => {
15985
+ let storedentries = log.length;
15986
+ const executeresume = async (dispatched, stepcontext) => {
15987
+ if (iscontrolflowkind(dispatched.kind)) {
15988
+ const controlled = await runcontrolstep({ step: dispatched, scopes: stepcontext.scopes, outputs: stepcontext.outputs ?? {}, execute: executeresume, now: Date.now(), runid: stored.run.id, pagestate: await pagestateof(tab.id), resolveelements: async (selector) => await resolveelements(tab.id, selector) });
15989
+ return { ...controlled.output, scopes: controlled.scopes, log: controlled.log };
15990
+ }
15991
+ return await dispatchworkflowstep(dispatched, { session, plan, tabid: tab.id, origin });
15992
+ };
15993
+ const resumed = await runworkflow({ record: record2, run: stored.run, scopes, log, execute: executeresume, now: Date.now(), gates: { sessionactive: Boolean(session && !session.stoppedat && !session.pausedat && session.expiresat > Date.now()), planapproved: plan.state === "approved", origingranted: (workfloworigin) => origingranted(session, workfloworigin) }, oncheckpoint: async (state) => {
15077
15994
  await memory.setworkflowrun(state.run);
15078
- const last = state.log[state.log.length - 1];
15079
- if (last) await memory.addrunlogentry(state.run.id, last);
15995
+ for (const entry of state.log.slice(storedentries)) await memory.addrunlogentry(state.run.id, entry);
15996
+ storedentries = state.log.length;
15080
15997
  await memory.setrunscopes(state.run.id, state.scopes);
15081
15998
  } });
15999
+ for (const entry of resumed.log.slice(storedentries)) await memory.addrunlogentry(stored.run.id, entry);
16000
+ for (const entry of resumed.log.slice(log.length)) {
16001
+ const control = entry.details?.control;
16002
+ if (control && typeof control === "object" && !Array.isArray(control)) {
16003
+ const decision = control;
16004
+ await memory.addcontroldecision(stored.run.id, decision);
16005
+ await auditcontroldecision(stored.run.id, decision, session.id, plan.id);
16006
+ }
16007
+ }
15082
16008
  await memory.setworkflowrun(resumed.run);
15083
16009
  await memory.setrunscopes(stored.run.id, resumed.scopes);
15084
- await audit("workflow", `Resumed the workflow run ${stored.run.id} from the checkpoint at step cursor ${stored.run.cursor}; the run ended ${resumed.run.state} at cursor ${resumed.run.cursor}.`, { sessionid: session.id, planid: plan.id });
16010
+ await audit("workflow", `Resumed the workflow run ${stored.run.id} from the checkpoint at step cursor ${stored.run.cursor}; the run ended ${resumed.run.state} at cursor ${resumed.run.cursor} with ${resumed.log.length - log.length} new runlog entries.`, { sessionid: session.id, planid: plan.id });
15085
16011
  await refreshbadge();
15086
16012
  return { runid: resumed.run.id, state: resumed.run.state, cursor: resumed.run.cursor };
15087
16013
  }