@wenathlan/extension 1.1.49 → 1.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2113,6 +2113,104 @@ var sessionmemory = class {
2113
2113
  async setcrashflag(value) {
2114
2114
  return this.adapter.set("crashed", value);
2115
2115
  }
2116
+ /** Stores one composed workflow record version with its timestamp; re-composing the same version replaces it while older versions survive for the audit trail. */
2117
+ async addworkflowrecord(record2) {
2118
+ const records = await this.getworkflowrecordversions();
2119
+ const remaining = records.filter((entry) => !(entry.id === record2.id && entry.version === record2.version));
2120
+ await this.adapter.set("workflowrecords", [record2, ...remaining]);
2121
+ }
2122
+ /** Returns every stored workflow record version, newest first. */
2123
+ async getworkflowrecordversions() {
2124
+ return await this.adapter.get("workflowrecords") ?? [];
2125
+ }
2126
+ /** Returns the latest stored version of one workflow record. */
2127
+ async getworkflowrecord(id) {
2128
+ return (await this.getworkflowrecordversions()).find((entry) => entry.id === id);
2129
+ }
2130
+ /** Lists the saved workflow records, the latest version of each, newest first. */
2131
+ async listworkflows() {
2132
+ const seen = /* @__PURE__ */ new Set();
2133
+ const latest = [];
2134
+ for (const entry of await this.getworkflowrecordversions()) {
2135
+ if (seen.has(entry.id)) continue;
2136
+ seen.add(entry.id);
2137
+ latest.push(entry);
2138
+ }
2139
+ return latest;
2140
+ }
2141
+ /** Stores one workflow run with its state transition; a run replace keeps the full runlog of the same id. */
2142
+ async setworkflowrun(run) {
2143
+ const runs = await this.listworkflowruns();
2144
+ const remaining = runs.filter((entry) => entry.id !== run.id);
2145
+ await this.adapter.set("workflowruns", [run, ...remaining]);
2146
+ }
2147
+ /** Returns every stored workflow run, newest first. */
2148
+ async listworkflowruns() {
2149
+ return await this.adapter.get("workflowruns") ?? [];
2150
+ }
2151
+ /** Returns one run with its full step outcome list so the panel shows the timeline after and during a run. */
2152
+ async getrun(id) {
2153
+ const run = (await this.listworkflowruns()).find((entry) => entry.id === id);
2154
+ if (!run) return void 0;
2155
+ return { run, log: await this.getrunlog(id) };
2156
+ }
2157
+ /** Records one runlog entry of a run; the runlog retention window is a user setting and an absent window keeps every entry. */
2158
+ async addrunlogentry(runid, entry) {
2159
+ const entries = await this.getrunlog(runid);
2160
+ const combined = [...entries, entry];
2161
+ const retention = (await this.getsettings())?.runlogretention;
2162
+ await this.adapter.set(`runlog${runid}`, retention === void 0 ? combined : combined.slice(-retention));
2163
+ }
2164
+ /** Returns the runlog of one run, oldest first. */
2165
+ async getrunlog(runid) {
2166
+ return await this.adapter.get(`runlog${runid}`) ?? [];
2167
+ }
2168
+ /** Stores the variable values per scope of one run for inspection after the run. */
2169
+ async setrunscopes(runid, scopes) {
2170
+ return this.adapter.set(`runscopes${runid}`, scopes);
2171
+ }
2172
+ /** Returns the variable scopes of one run, oldest first. */
2173
+ async getrunscopes(runid) {
2174
+ return await this.adapter.get(`runscopes${runid}`) ?? [];
2175
+ }
2176
+ /** Records one provenance entry of a run: an expression result or a regex capture with its name, value and time. */
2177
+ async addworkflowprovenance(runid, entry) {
2178
+ const entries = await this.getworkflowprovenance(runid);
2179
+ await this.adapter.set(`workflowprovenance${runid}`, [...entries, entry]);
2180
+ }
2181
+ /** Returns every provenance entry of one run, oldest first. */
2182
+ async getworkflowprovenance(runid) {
2183
+ return await this.adapter.get(`workflowprovenance${runid}`) ?? [];
2184
+ }
2185
+ /** Stores one shareable step template under its unique name. */
2186
+ async addsteptemplate(template) {
2187
+ const templates = (await this.getsteptemplates()).filter((entry) => entry.name !== template.name);
2188
+ await this.adapter.set("steptemplates", [template, ...templates]);
2189
+ }
2190
+ /** Returns every stored step template, newest first. */
2191
+ async getsteptemplates() {
2192
+ return await this.adapter.get("steptemplates") ?? [];
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
+ }
2116
2214
  };
2117
2215
  function mediakindof(record2) {
2118
2216
  if ("pages" in record2) return "pdf";
@@ -3099,6 +3197,1194 @@ function teardowncdpsession(input) {
3099
3197
  };
3100
3198
  }
3101
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
+
3872
+ // workflow.ts
3873
+ var workflowkinds = ["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars"];
3874
+ function workflowstepof(value) {
3875
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3876
+ const candidate = value;
3877
+ if (typeof candidate.id !== "string" || !candidate.id.trim()) return void 0;
3878
+ if (typeof candidate.kind !== "string" || !/^[a-z]+$/.test(candidate.kind)) return void 0;
3879
+ if (typeof candidate.label !== "string" || !candidate.label.trim()) return void 0;
3880
+ if (candidate.target !== void 0 && (typeof candidate.target !== "string" || !candidate.target)) return void 0;
3881
+ if (candidate.value !== void 0 && typeof candidate.value !== "string") return void 0;
3882
+ if (candidate.options !== void 0 && typeof candidate.options !== "string") return void 0;
3883
+ const bindings = Array.isArray(candidate.bindings) ? candidate.bindings.flatMap((binding) => bindingof(binding) !== void 0 ? [bindingof(binding)] : []) : void 0;
3884
+ if (candidate.bindings !== void 0 && bindings === void 0) return void 0;
3885
+ if (Array.isArray(candidate.bindings) && bindings !== void 0 && bindings.length !== candidate.bindings.length) return void 0;
3886
+ const expression = candidate.expression === void 0 ? void 0 : expressionof(candidate.expression);
3887
+ if (candidate.expression !== void 0 && expression === void 0) return void 0;
3888
+ const extract = candidate.extract === void 0 ? void 0 : regexruleof(candidate.extract);
3889
+ if (candidate.extract !== void 0 && extract === void 0) return void 0;
3890
+ return { id: candidate.id, kind: candidate.kind, label: candidate.label, ...candidate.target !== void 0 ? { target: candidate.target } : {}, ...candidate.value !== void 0 ? { value: candidate.value } : {}, ...candidate.options !== void 0 ? { options: candidate.options } : {}, ...bindings !== void 0 && bindings.length > 0 ? { bindings } : {}, ...expression !== void 0 ? { expression } : {}, ...extract !== void 0 ? { extract } : {} };
3891
+ }
3892
+ function blockinvocationof(value) {
3893
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3894
+ const candidate = value;
3895
+ if (typeof candidate.block !== "string" || !candidate.block.trim()) return void 0;
3896
+ if (typeof candidate.label !== "string" || !candidate.label.trim()) return void 0;
3897
+ return { block: candidate.block, label: candidate.label };
3898
+ }
3899
+ function workflowblockof(value) {
3900
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3901
+ const candidate = value;
3902
+ if (typeof candidate.name !== "string" || !/^[a-z][a-z0-9]*$/.test(candidate.name)) return void 0;
3903
+ if (typeof candidate.label !== "string" || !candidate.label.trim()) return void 0;
3904
+ if (!Array.isArray(candidate.steps)) return void 0;
3905
+ const steps = [];
3906
+ for (const entry of candidate.steps) {
3907
+ const step = workflowstepof(entry);
3908
+ if (step) {
3909
+ steps.push(step);
3910
+ continue;
3911
+ }
3912
+ const invocation = blockinvocationof(entry);
3913
+ if (invocation) {
3914
+ steps.push(invocation);
3915
+ continue;
3916
+ }
3917
+ return void 0;
3918
+ }
3919
+ return { name: candidate.name, label: candidate.label, steps };
3920
+ }
3921
+ function steptemplateof(value) {
3922
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3923
+ const candidate = value;
3924
+ if (typeof candidate.id !== "string" || !candidate.id.trim()) return void 0;
3925
+ if (typeof candidate.name !== "string" || !candidate.name.trim()) return void 0;
3926
+ if (typeof candidate.origin !== "string" || !candidate.origin.trim()) return void 0;
3927
+ const step = workflowstepof(candidate.step);
3928
+ if (!step) return void 0;
3929
+ if (typeof candidate.sharedat !== "number" || !Number.isFinite(candidate.sharedat)) return void 0;
3930
+ return { id: candidate.id, name: candidate.name, origin: candidate.origin, step, sharedat: candidate.sharedat };
3931
+ }
3932
+ function bindingof(value) {
3933
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3934
+ const candidate = value;
3935
+ if (typeof candidate.variable !== "string" || !/^[a-z][a-z0-9]*$/.test(candidate.variable)) return void 0;
3936
+ if (!variablekinds.includes(candidate.kind)) return void 0;
3937
+ if (typeof candidate.stepid !== "string" || !candidate.stepid.trim()) return void 0;
3938
+ if (candidate.path !== void 0 && (typeof candidate.path !== "string" || !candidate.path.trim())) return void 0;
3939
+ return { variable: candidate.variable, kind: candidate.kind, stepid: candidate.stepid, ...candidate.path !== void 0 ? { path: candidate.path } : {} };
3940
+ }
3941
+ var variablekinds = ["string", "number", "boolean", "list", "element"];
3942
+ var expressionoperators = ["add", "subtract", "multiply", "divide", "modulo", "equal", "notequal", "less", "greater", "lessequal", "greaterequal", "and", "or", "not", "concat", "contains", "length"];
3943
+ function expressionof(value) {
3944
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3945
+ const candidate = value;
3946
+ const left = operandof(candidate.left);
3947
+ if (!left) return void 0;
3948
+ const right = candidate.right === void 0 ? void 0 : operandof(candidate.right);
3949
+ if (candidate.right !== void 0 && right === void 0) return void 0;
3950
+ if (typeof candidate.operator !== "string" || !expressionoperators.includes(candidate.operator)) return void 0;
3951
+ if (typeof candidate.result !== "string" || !/^[a-z][a-z0-9]*$/.test(candidate.result)) return void 0;
3952
+ if (!variablekinds.includes(candidate.resultkind)) return void 0;
3953
+ return { left, ...right !== void 0 ? { right } : {}, operator: candidate.operator, result: candidate.result, resultkind: candidate.resultkind };
3954
+ }
3955
+ function operandof(value) {
3956
+ if (value === void 0) return void 0;
3957
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return { literal: value };
3958
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3959
+ const candidate = value;
3960
+ if (typeof candidate.ref === "string" && /^[a-z][a-z0-9]*$/.test(candidate.ref)) return { ref: candidate.ref };
3961
+ if (typeof candidate.literal === "string" || typeof candidate.literal === "number" || typeof candidate.literal === "boolean") return { literal: candidate.literal };
3962
+ return void 0;
3963
+ }
3964
+ function regexruleof(value) {
3965
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3966
+ const candidate = value;
3967
+ if (typeof candidate.pattern !== "string" || !candidate.pattern.trim()) return void 0;
3968
+ if (typeof candidate.flags !== "string" || !/^[dgimsuvy]*$/.test(candidate.flags)) return void 0;
3969
+ const groups = Array.isArray(candidate.groups) ? candidate.groups.flatMap((group) => typeof group === "string" && /^[a-z][a-z0-9]*$/.test(group) ? [group] : []) : [];
3970
+ if (candidate.groups !== void 0 && groups.length !== candidate.groups.length) return void 0;
3971
+ return { pattern: candidate.pattern, flags: candidate.flags, groups };
3972
+ }
3973
+ function expandblocks(steps, blocks) {
3974
+ const byname = new Map(blocks.map((block) => [block.name, block]));
3975
+ const expanded = [];
3976
+ const visit = (entries, path, inside) => {
3977
+ for (const entry of entries) {
3978
+ if ("kind" in entry && "label" in entry && !("block" in entry)) {
3979
+ expanded.push(inside === void 0 ? entry : { ...entry, block: inside });
3980
+ continue;
3981
+ }
3982
+ const invocation = blockinvocationof(entry);
3983
+ if (!invocation) throw new Error("The step list entry is neither a reviewed step nor a block invocation.");
3984
+ if (path.includes(invocation.block)) throw new Error(`The block ${invocation.block} recurs inside itself and cannot expand.`);
3985
+ const block = byname.get(invocation.block);
3986
+ if (!block) throw new Error(`The block ${invocation.block} is not defined in the workflow.`);
3987
+ visit(block.steps, [...path, invocation.block], invocation.block);
3988
+ }
3989
+ };
3990
+ visit(steps, [], void 0);
3991
+ if (expanded.length === 0) throw new Error("A workflow needs at least one executable step after block expansion.");
3992
+ return expanded;
3993
+ }
3994
+ function composeworkflow(input) {
3995
+ if (typeof input.name !== "string" || !input.name.trim()) throw new Error("The workflow name must be a non-empty string.");
3996
+ if (typeof input.version !== "number" || !Number.isInteger(input.version) || input.version < 1) throw new Error("The workflow version must be a positive integer.");
3997
+ if (!Array.isArray(input.origins) || input.origins.length === 0) throw new Error("A workflow needs at least one granted HTTPS origin.");
3998
+ const origins = input.origins.map((origin) => {
3999
+ try {
4000
+ return new URL(origin).origin;
4001
+ } catch {
4002
+ throw new Error(`The workflow origin ${origin} is not a valid url.`);
4003
+ }
4004
+ });
4005
+ if (origins.some((origin) => !origin.startsWith("https://"))) throw new Error("Workflow origins must use HTTPS.");
4006
+ const blocks = input.blocks ?? [];
4007
+ if (blocks.some((block, index) => blocks.findIndex((other) => other.name === block.name) !== index)) throw new Error("Workflow block names must stay unique.");
4008
+ for (const entry of input.steps) {
4009
+ if ("kind" in entry && "label" in entry && !("block" in entry)) {
4010
+ if (input.kindallowed && !input.kindallowed(entry.kind)) throw new Error(`The workflow step kind ${entry.kind} is not a reviewed action kind.`);
4011
+ }
4012
+ }
4013
+ for (const block of blocks) for (const entry of block.steps) {
4014
+ if ("kind" in entry && "label" in entry && !("block" in entry) && input.kindallowed && !input.kindallowed(entry.kind)) throw new Error(`The workflow step kind ${entry.kind} inside block ${block.name} is not a reviewed action kind.`);
4015
+ }
4016
+ const steps = expandblocks(input.steps, blocks);
4017
+ for (const step of steps) {
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
+ }
4025
+ if (step.bindings) for (const binding of step.bindings) {
4026
+ if (!steps.some((other) => other.id === binding.stepid)) throw new Error(`The binding of ${binding.variable} references the unknown step ${binding.stepid}.`);
4027
+ }
4028
+ }
4029
+ const riskof = input.riskof ?? (() => "sensitive");
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";
4032
+ const record2 = { id: input.id ?? crypto.randomUUID(), name: input.name, version: input.version, origins: [...new Set(origins)], steps, blocks, risk, createdat: input.now };
4033
+ return deepfreeze(record2);
4034
+ }
4035
+ function deepfreeze(record2) {
4036
+ for (const step of record2.steps) Object.freeze(step);
4037
+ for (const block of record2.blocks) for (const entry of block.steps) if ("kind" in entry && "label" in entry && !("block" in entry)) Object.freeze(entry);
4038
+ Object.freeze(record2.blocks);
4039
+ Object.freeze(record2.steps);
4040
+ return Object.freeze(record2);
4041
+ }
4042
+ function validateworkflow(record2, options) {
4043
+ if (record2.steps.length === 0) return { allowed: false, reason: "A workflow needs at least one reviewed step." };
4044
+ const defined = new Set(options?.inputs ?? []);
4045
+ const byid = new Map(record2.steps.map((step, index) => [step.id, { step, index }]));
4046
+ for (let index = 0; index < record2.steps.length; index += 1) {
4047
+ const step = record2.steps[index];
4048
+ if (options?.kindallowed && !options.kindallowed(step.kind)) return { allowed: false, reason: `The workflow step kind ${step.kind} is not a reviewed action kind.` };
4049
+ if (step.bindings) for (const binding of step.bindings) {
4050
+ const source = byid.get(binding.stepid);
4051
+ if (!source) return { allowed: false, reason: `The binding of ${binding.variable} references the unknown step ${binding.stepid}.` };
4052
+ if (source.index >= index) return { allowed: false, reason: `The binding of ${binding.variable} must link an earlier step than ${step.id}.` };
4053
+ defined.add(binding.variable);
4054
+ }
4055
+ if (step.expression) {
4056
+ for (const operand of [step.expression.left, step.expression.right]) {
4057
+ if (operand?.ref && !defined.has(operand.ref)) return { allowed: false, reason: `The expression of step ${step.id} references the undefined variable ${operand.ref}.` };
4058
+ }
4059
+ defined.add(step.expression.result);
4060
+ }
4061
+ if (step.extract) for (const group of step.extract.groups) defined.add(group);
4062
+ }
4063
+ return { allowed: true };
4064
+ }
4065
+ function pushscope(scopes, name, parent) {
4066
+ return [...scopes, { name, variables: [], ...parent !== void 0 ? { parent } : {} }];
4067
+ }
4068
+ function popscope(scopes) {
4069
+ if (scopes.length === 0) return scopes;
4070
+ return scopes.slice(0, -1);
4071
+ }
4072
+ function resolvevariable(scopes, name) {
4073
+ for (let index = scopes.length - 1; index >= 0; index -= 1) {
4074
+ const scope = scopes[index];
4075
+ const found = scope.variables.find((variable) => variable.name === name);
4076
+ if (found) return found;
4077
+ if (scope.parent === void 0) continue;
4078
+ const parentindex = scopes.findIndex((candidate) => candidate.name === scope.parent);
4079
+ if (parentindex >= 0 && parentindex < index) {
4080
+ const inherited = resolvevariable([scopes[parentindex]], name);
4081
+ if (inherited) return inherited;
4082
+ }
4083
+ }
4084
+ return void 0;
4085
+ }
4086
+ function setvariable(scopes, name, kind, value, now) {
4087
+ if (scopes.length === 0) scopes = [{ name: "root", variables: [] }];
4088
+ const target = scopes[scopes.length - 1];
4089
+ const variables = [...target.variables.filter((variable) => variable.name !== name), { name, kind, value, setat: now }];
4090
+ return [...scopes.slice(0, -1), { ...target, variables }];
4091
+ }
4092
+ function coercevariable(value, kind) {
4093
+ if (kind === "number") {
4094
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
4095
+ if (!Number.isFinite(parsed)) throw new Error("The bound value is not a finite number.");
4096
+ return parsed;
4097
+ }
4098
+ if (kind === "boolean") {
4099
+ if (typeof value === "boolean") return value;
4100
+ if (value === "true") return true;
4101
+ if (value === "false") return false;
4102
+ throw new Error("The bound value is not a boolean.");
4103
+ }
4104
+ if (kind === "list") {
4105
+ if (Array.isArray(value)) return value.map((item) => String(item));
4106
+ if (typeof value === "string") return value.length === 0 ? [] : value.split(",");
4107
+ throw new Error("The bound value is not a list.");
4108
+ }
4109
+ if (kind === "element") {
4110
+ if (typeof value === "string" && value.trim()) return value;
4111
+ throw new Error("The bound value is not an element reference.");
4112
+ }
4113
+ if (typeof value === "string") return value;
4114
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
4115
+ throw new Error("The bound value is not a string.");
4116
+ }
4117
+ function outcomedetail(outcome, path) {
4118
+ if (!path) return outcome.summary;
4119
+ let current = outcome.details ?? {};
4120
+ for (const segment of path.split(".")) {
4121
+ if (!current || typeof current !== "object" || Array.isArray(current)) return void 0;
4122
+ current = current[segment];
4123
+ }
4124
+ return current;
4125
+ }
4126
+ function bindvariables(scopes, bindings, outputs, now) {
4127
+ let current = scopes;
4128
+ const produced = [];
4129
+ for (const binding of bindings) {
4130
+ const outcome = outputs[binding.stepid];
4131
+ if (!outcome) continue;
4132
+ const raw = outcomedetail(outcome, binding.path);
4133
+ if (raw === void 0) throw new Error(`The binding of ${binding.variable} found no value at ${binding.path ?? "the summary"} of step ${binding.stepid}.`);
4134
+ current = setvariable(current, binding.variable, binding.kind, coercevariable(raw, binding.kind), now);
4135
+ produced.push(binding.variable);
4136
+ }
4137
+ return { scopes: current, produced };
4138
+ }
4139
+ function operandvalue(operand, scopes) {
4140
+ if (operand.ref !== void 0) {
4141
+ const resolved = resolvevariable(scopes, operand.ref);
4142
+ if (!resolved) throw new Error(`The expression references the undefined variable ${operand.ref}.`);
4143
+ return resolved.value;
4144
+ }
4145
+ if (operand.literal === void 0) throw new Error("The expression operand needs a variable reference or a literal.");
4146
+ return operand.literal;
4147
+ }
4148
+ function expressioneval(expression, scopes) {
4149
+ const left = operandvalue(expression.left, scopes);
4150
+ const right = expression.right === void 0 ? void 0 : operandvalue(expression.right, scopes);
4151
+ const operand = (value) => {
4152
+ if (Array.isArray(value)) throw new Error("The expression operand is a list and needs the contains or length operator.");
4153
+ if (value === void 0) throw new Error("The expression operand is missing.");
4154
+ return value;
4155
+ };
4156
+ const numbervalue = (value) => {
4157
+ const primitive = operand(value);
4158
+ if (typeof primitive === "number") return primitive;
4159
+ if (typeof primitive === "string" && primitive.trim() !== "") {
4160
+ const parsed = Number(primitive);
4161
+ if (Number.isFinite(parsed)) return parsed;
4162
+ }
4163
+ throw new Error("The arithmetic operand is not a number.");
4164
+ };
4165
+ const booleanvalue = (value) => {
4166
+ const primitive = operand(value);
4167
+ if (typeof primitive === "boolean") return primitive;
4168
+ throw new Error("The logic operand is not a boolean.");
4169
+ };
4170
+ const stringvalue = (value) => {
4171
+ const primitive = operand(value);
4172
+ if (typeof primitive === "string") return primitive;
4173
+ if (typeof primitive === "number" || typeof primitive === "boolean") return String(primitive);
4174
+ throw new Error("The text operand is not a string.");
4175
+ };
4176
+ switch (expression.operator) {
4177
+ case "add":
4178
+ return numbervalue(left) + numbervalue(right);
4179
+ case "subtract":
4180
+ return numbervalue(left) - numbervalue(right);
4181
+ case "multiply":
4182
+ return numbervalue(left) * numbervalue(right);
4183
+ case "divide": {
4184
+ const divisor = numbervalue(right);
4185
+ if (divisor === 0) throw new Error("The expression divides by zero.");
4186
+ return numbervalue(left) / divisor;
4187
+ }
4188
+ case "modulo": {
4189
+ const divisor = numbervalue(right);
4190
+ if (divisor === 0) throw new Error("The expression divides by zero.");
4191
+ return numbervalue(left) % divisor;
4192
+ }
4193
+ case "equal":
4194
+ return left === right;
4195
+ case "notequal":
4196
+ return left !== right;
4197
+ case "less":
4198
+ return numbervalue(left) < numbervalue(right);
4199
+ case "greater":
4200
+ return numbervalue(left) > numbervalue(right);
4201
+ case "lessequal":
4202
+ return numbervalue(left) <= numbervalue(right);
4203
+ case "greaterequal":
4204
+ return numbervalue(left) >= numbervalue(right);
4205
+ case "and":
4206
+ return booleanvalue(left) && booleanvalue(right);
4207
+ case "or":
4208
+ return booleanvalue(left) || booleanvalue(right);
4209
+ case "not":
4210
+ return !booleanvalue(left);
4211
+ case "concat":
4212
+ return `${stringvalue(left)}${stringvalue(right)}`;
4213
+ case "contains": {
4214
+ if (Array.isArray(left)) return left.includes(stringvalue(right));
4215
+ return stringvalue(left).includes(stringvalue(right));
4216
+ }
4217
+ case "length": {
4218
+ if (Array.isArray(left)) return left.length;
4219
+ return stringvalue(left).length;
4220
+ }
4221
+ default:
4222
+ throw new Error("The reviewed expression operator is unknown.");
4223
+ }
4224
+ }
4225
+ function regexextract(rule, text2, now) {
4226
+ const pattern = new RegExp(rule.pattern, rule.flags);
4227
+ const match = pattern.exec(text2);
4228
+ if (!match) return { matched: false, variables: [] };
4229
+ const variables = [];
4230
+ for (const group of rule.groups) {
4231
+ const value = match.groups?.[group];
4232
+ variables.push({ name: group, kind: "string", value: typeof value === "string" ? value : "", setat: now });
4233
+ }
4234
+ return { matched: true, variables };
4235
+ }
4236
+ function waitelementplan(wait) {
4237
+ if (wait.timeout <= 0 || wait.poll <= 0) return { probes: 1, lastwait: 0 };
4238
+ const probes = Math.floor(wait.timeout / wait.poll) + 1;
4239
+ return { probes, lastwait: wait.timeout % wait.poll };
4240
+ }
4241
+ function delayjitter(delay, seed) {
4242
+ if (delay.jitter <= 0) return Math.max(0, delay.base);
4243
+ const sample = seededrandom(seed);
4244
+ return Math.max(0, delay.base - delay.jitter / 2 + sample * delay.jitter);
4245
+ }
4246
+ function seededrandom(seed) {
4247
+ let state = seed >>> 0;
4248
+ state ^= state >>> 16;
4249
+ state = Math.imul(state, 2246822507);
4250
+ state ^= state >>> 13;
4251
+ state = Math.imul(state, 3266489909);
4252
+ state ^= state >>> 16;
4253
+ state = state >>> 0 || 1;
4254
+ state ^= state << 13;
4255
+ state >>>= 0;
4256
+ state ^= state >> 17;
4257
+ state ^= state << 5;
4258
+ state >>>= 0;
4259
+ return state / 4294967296;
4260
+ }
4261
+ function newworkflowrun(input) {
4262
+ return { id: input.id ?? crypto.randomUUID(), workflowid: input.workflowid, state: "pending", cursor: 0, startedat: input.now, ...input.dryrun === true ? { dryrun: true } : {} };
4263
+ }
4264
+ function pauserun(run, now) {
4265
+ if (run.state !== "running") throw new Error("Only a running workflow can pause.");
4266
+ return { ...run, state: "paused", pausedat: now };
4267
+ }
4268
+ function cancelrun(run, reason, now) {
4269
+ if (run.state === "done" || run.state === "cancelled") return run;
4270
+ return { ...run, state: "cancelled", cancelreason: reason, endedat: now };
4271
+ }
4272
+ function interpolate(text2, scopes) {
4273
+ const consumed = [];
4274
+ const resolved = text2.replace(/\$\{([a-z][a-z0-9]*)\}/g, (_whole, name) => {
4275
+ const variable = resolvevariable(scopes, name);
4276
+ if (!variable) throw new Error(`The step references the undefined variable ${name}.`);
4277
+ consumed.push(name);
4278
+ return Array.isArray(variable.value) ? variable.value.join(",") : String(variable.value);
4279
+ });
4280
+ return { text: resolved, consumed };
4281
+ }
4282
+ function runlogof(step, state, startedat, duration, summary, extra) {
4283
+ return { stepid: step.id, label: step.label, state, startedat, duration, summary, ...extra.block !== void 0 ? { block: extra.block } : {}, ...extra.consumed !== void 0 && extra.consumed.length > 0 ? { consumed: extra.consumed } : {}, ...extra.produced !== void 0 && extra.produced.length > 0 ? { produced: extra.produced } : {}, ...extra.checkpoint === true ? { checkpoint: true } : {}, ...extra.details !== void 0 ? { details: extra.details } : {} };
4284
+ }
4285
+ async function runstep(input) {
4286
+ const startedat = input.now;
4287
+ let scopes = input.scopes;
4288
+ const consumed = [];
4289
+ if (input.step.bindings) {
4290
+ const bound = bindvariables(scopes, input.step.bindings.filter((binding) => input.outputs[binding.stepid] !== void 0), input.outputs, input.now);
4291
+ scopes = bound.scopes;
4292
+ }
4293
+ let produced = [];
4294
+ try {
4295
+ if (input.step.expression) {
4296
+ const value2 = expressioneval(input.step.expression, scopes);
4297
+ scopes = setvariable(scopes, input.step.expression.result, input.step.expression.resultkind, coercevariable(value2, input.step.expression.resultkind), input.now);
4298
+ produced = [...produced, input.step.expression.result];
4299
+ }
4300
+ let stepvalue = input.step.value;
4301
+ if (input.step.extract) {
4302
+ const text2 = stepvalue ?? "";
4303
+ const interpolated = interpolate(text2, scopes);
4304
+ consumed.push(...interpolated.consumed);
4305
+ const extraction = regexextract(input.step.extract, interpolated.text, input.now);
4306
+ if (extraction.matched) {
4307
+ for (const variable of extraction.variables) scopes = setvariable(scopes, variable.name, "string", variable.value, input.now);
4308
+ produced = [...produced, ...extraction.variables.map((variable) => variable.name)];
4309
+ }
4310
+ stepvalue = interpolated.text;
4311
+ }
4312
+ const controlled = iscontrolflowkind(input.step.kind);
4313
+ const target = !controlled && input.step.target !== void 0 ? interpolate(input.step.target, scopes) : void 0;
4314
+ if (target) consumed.push(...target.consumed);
4315
+ const value = !controlled && stepvalue !== void 0 ? interpolate(stepvalue, scopes) : void 0;
4316
+ if (value) consumed.push(...value.consumed);
4317
+ const options = !controlled && input.step.options !== void 0 ? interpolate(input.step.options, scopes) : void 0;
4318
+ if (options) consumed.push(...options.consumed);
4319
+ const dispatchable = { ...input.step, ...target !== void 0 ? { target: target.text } : {}, ...value !== void 0 ? { value: value.text } : {}, ...options !== void 0 ? { options: options.text } : {} };
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;
4323
+ if (input.step.bindings) {
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);
4325
+ scopes = bound.scopes;
4326
+ produced = [.../* @__PURE__ */ new Set([...produced, ...bound.produced])];
4327
+ }
4328
+ const duration = Date.now() - startedat;
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 };
4330
+ } catch (error) {
4331
+ const duration = Date.now() - startedat;
4332
+ const summary = error instanceof Error ? error.message : String(error);
4333
+ return { scopes, log: runlogof(input.step, "failed", startedat, duration, summary, { ...input.block !== void 0 ? { block: input.block } : {}, ...consumed.length > 0 ? { consumed } : {} }), output: { ok: false, summary } };
4334
+ }
4335
+ }
4336
+ async function runworkflow(input) {
4337
+ if (input.gates && !input.gates.sessionactive) throw new Error("The workflow refuses to run outside an approved session.");
4338
+ if (input.gates && !input.gates.planapproved) throw new Error("The workflow refuses to run without the approved plan review.");
4339
+ if (input.gates) for (const origin of input.record.origins) {
4340
+ if (!input.gates.origingranted(origin)) throw new Error(`The workflow origin ${origin} falls outside the session grants.`);
4341
+ }
4342
+ if (input.run.state === "done" || input.run.state === "failed" || input.run.state === "cancelled") throw new Error(`The workflow run is already ${input.run.state}.`);
4343
+ const { pausedat, ...resumed } = input.run;
4344
+ void pausedat;
4345
+ let run = input.run.state === "paused" ? { ...resumed, state: "running" } : { ...input.run, state: "running" };
4346
+ let scopes = input.scopes ?? [{ name: "root", variables: [] }];
4347
+ const log = [...input.log ?? []];
4348
+ const outputs = { ...input.outputs ?? {} };
4349
+ let activeblock;
4350
+ for (let index = run.cursor; index < input.record.steps.length; index += 1) {
4351
+ const step = input.record.steps[index];
4352
+ if (step.block !== void 0 && step.block !== activeblock) {
4353
+ scopes = pushscope(scopes, step.block, scopes[scopes.length - 1].name);
4354
+ activeblock = step.block;
4355
+ } else if (step.block === void 0 && activeblock !== void 0) {
4356
+ while (scopes.length > 1) scopes = popscope(scopes);
4357
+ activeblock = void 0;
4358
+ }
4359
+ const executed = await runstep({ step, scopes, outputs, execute: input.execute, now: Date.now(), ...step.block !== void 0 ? { block: step.block } : {} });
4360
+ scopes = executed.scopes;
4361
+ if (executed.childlog !== void 0) log.push(...executed.childlog);
4362
+ log.push(executed.log);
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() };
4364
+ if (!executed.output.ok) {
4365
+ run = { ...run, state: "failed", endedat: Date.now(), failreason: executed.output.summary };
4366
+ return { run, scopes, log, outputs };
4367
+ }
4368
+ run = { ...run, cursor: index + 1 };
4369
+ if (input.oncheckpoint) await input.oncheckpoint({ run, scopes, log });
4370
+ }
4371
+ run = { ...run, state: "done", endedat: Date.now() };
4372
+ return { run, scopes, log, outputs };
4373
+ }
4374
+ function dryrunworkflow(input) {
4375
+ const run = { ...input.run, state: "running", ...input.run.dryrun === true ? { dryrun: true } : { dryrun: true } };
4376
+ let scopes = input.scopes ?? [{ name: "root", variables: [] }];
4377
+ const log = [...input.log ?? []];
4378
+ for (let index = run.cursor; index < input.record.steps.length; index += 1) {
4379
+ const step = input.record.steps[index];
4380
+ const summary = input.projection(step);
4381
+ const entry = summary === void 0 ? runlogof(step, "refused", input.now, 0, `The ${step.kind} step has no read only projection and the dry run refuses it.`, { ...step.block !== void 0 ? { block: step.block } : {} }) : runlogof(step, "done", input.now, 0, summary, { ...step.block !== void 0 ? { block: step.block } : {} });
4382
+ log.push(entry);
4383
+ scopes = setvariable(scopes, `${step.id}outcome`, "boolean", entry.state === "done", input.now);
4384
+ }
4385
+ return { run: { ...run, state: "done", cursor: input.record.steps.length, endedat: input.now }, scopes, log };
4386
+ }
4387
+
3102
4388
  // netauth.ts
3103
4389
  function oauthflowof(value) {
3104
4390
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
@@ -3332,9 +4618,9 @@ function consolediff(input) {
3332
4618
  }
3333
4619
 
3334
4620
  // policy.ts
3335
- 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"]);
3336
- 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"]);
3337
- 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"]);
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"]);
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"]);
3338
4624
  var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
3339
4625
  var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
3340
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"]);
@@ -3355,6 +4641,7 @@ var cdpactions = /* @__PURE__ */ new Set(["attachcdp", "detachcdp", "cdpcmd", "w
3355
4641
  var profileractions = /* @__PURE__ */ new Set(["measureflow", "heapshot", "trackmemory", "profilecpu", "watchshifts", "traceload", "annotatetrace", "replaytrace", "capturesourcemaps"]);
3356
4642
  var emulationactions = /* @__PURE__ */ new Set(["emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "blackboxscripts"]);
3357
4643
  var sessionactions = /* @__PURE__ */ new Set(["persiststate", "capturesession", "restoresession", "namedsessions", "diffsessions", "searchsessions", "exportsessions", "importsessions"]);
4644
+ var workflowactions = /* @__PURE__ */ new Set(["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars", "condition", "branch", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"]);
3358
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"]);
3359
4646
  var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
3360
4647
  var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
@@ -3373,6 +4660,9 @@ function hostpattern(origin) {
3373
4660
  function issessionkind(kind) {
3374
4661
  return sessionactions.has(kind);
3375
4662
  }
4663
+ function isworkflowkind(kind) {
4664
+ return workflowactions.has(kind);
4665
+ }
3376
4666
  function isdebugkind(kind) {
3377
4667
  return debugactions.has(kind);
3378
4668
  }
@@ -4909,6 +6199,238 @@ function sessionfolderunique(name, folders) {
4909
6199
  function snapshotretentionwindow(settings) {
4910
6200
  return settings?.sessionretention;
4911
6201
  }
6202
+ function validateworkflowgrammar(step, options) {
6203
+ const kind = step.kind;
6204
+ if (kind === "composeworkflow") {
6205
+ const payload = options.workflow;
6206
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { allowed: false, reason: "The workflow composition needs the reviewed workflow payload with its name, version, origins, steps and blocks." };
6207
+ const candidate = payload;
6208
+ if (typeof candidate.name !== "string" || !candidate.name.trim()) return { allowed: false, reason: "The workflow composition needs a reviewed non-empty name." };
6209
+ if (typeof candidate.version !== "number" || !Number.isInteger(candidate.version) || candidate.version < 1) return { allowed: false, reason: "The workflow version must be a positive integer." };
6210
+ if (!Array.isArray(candidate.origins) || candidate.origins.length === 0 || !candidate.origins.every((origin) => typeof origin === "string" && origin.startsWith("https://"))) return { allowed: false, reason: "The workflow needs at least one granted HTTPS origin so every step stays inside the grants." };
6211
+ if (!Array.isArray(candidate.steps) || candidate.steps.length === 0 || !candidate.steps.every((entry) => workflowstepof(entry) !== void 0 || entry && typeof entry === "object" && typeof entry.block === "string")) return { allowed: false, reason: "The workflow needs a non-empty reviewed step list of the workflow step grammar or block invocations." };
6212
+ const blocks = Array.isArray(candidate.blocks) ? candidate.blocks.flatMap((block) => {
6213
+ const parsed = workflowblockof(block);
6214
+ return parsed !== void 0 ? [parsed] : [];
6215
+ }) : [];
6216
+ if (Array.isArray(candidate.blocks) && blocks.length !== candidate.blocks.length) return { allowed: false, reason: "The reviewed block list must carry unique lowercase names, labels and valid child steps." };
6217
+ try {
6218
+ const record2 = composeworkflow({ name: candidate.name, version: candidate.version, origins: candidate.origins, steps: candidate.steps.map((entry) => "block" in entry ? { block: entry.block, label: typeof entry.label === "string" ? entry.label : entry.block } : workflowstepof(entry)), blocks, now: 0, kindallowed: (candidatekind) => {
6219
+ try {
6220
+ actionrisk(candidatekind);
6221
+ return true;
6222
+ } catch {
6223
+ return false;
6224
+ }
6225
+ }, riskof: (candidatekind) => actionrisk(candidatekind) });
6226
+ const inputs = Array.isArray(candidate.inputs) ? candidate.inputs.flatMap((name) => typeof name === "string" ? [name] : []) : void 0;
6227
+ const checked = validateworkflow(record2, { kindallowed: (workflowkind) => {
6228
+ try {
6229
+ actionrisk(workflowkind);
6230
+ return true;
6231
+ } catch {
6232
+ return false;
6233
+ }
6234
+ }, ...inputs !== void 0 ? { inputs } : {} });
6235
+ if (!checked.allowed) return checked;
6236
+ } catch (error) {
6237
+ return { allowed: false, reason: error instanceof Error ? error.message : "The workflow payload failed its composition validation." };
6238
+ }
6239
+ return { allowed: true };
6240
+ }
6241
+ if (kind === "savetemplate") {
6242
+ const payload = options.template && typeof options.template === "object" && !Array.isArray(options.template) ? options.template : {};
6243
+ const template = steptemplateof({ id: "templatereview", origin: "https://example.com", sharedat: 0, ...payload });
6244
+ if (!template) return { allowed: false, reason: "The step template needs a reviewed name and a valid workflow step it shares across workflows." };
6245
+ return { allowed: true };
6246
+ }
6247
+ if (kind === "runworkflow") {
6248
+ if (typeof options.workflowid !== "string" || !options.workflowid.trim()) return { allowed: false, reason: "The workflow run needs the reviewed id of the composed workflow." };
6249
+ if (options.reviewed !== true) return { allowed: false, reason: "Every real workflow run needs the explicit run review with its expanded step list shown before the first step executes." };
6250
+ if (options.variables !== void 0 && (!options.variables || typeof options.variables !== "object" || Array.isArray(options.variables) || !Object.values(options.variables).every((value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean"))) return { allowed: false, reason: "The reviewed run variables must be an object of string, number or boolean values." };
6251
+ return { allowed: true };
6252
+ }
6253
+ if (kind === "dryrun") {
6254
+ if (typeof options.workflowid !== "string" || !options.workflowid.trim()) return { allowed: false, reason: "The dry run needs the reviewed id of the composed workflow." };
6255
+ return { allowed: true };
6256
+ }
6257
+ if (kind === "delay") {
6258
+ const delay = options.delay;
6259
+ if (!delay || typeof delay !== "object" || Array.isArray(delay)) return { allowed: false, reason: "The delay needs a reviewed base and jitter window in options." };
6260
+ const reviewed = delay;
6261
+ if (typeof reviewed.base !== "number" || !Number.isFinite(reviewed.base) || reviewed.base < 0) return { allowed: false, reason: "The reviewed delay base must be zero or a positive number of milliseconds." };
6262
+ if (typeof reviewed.jitter !== "number" || !Number.isFinite(reviewed.jitter) || reviewed.jitter < 0) return { allowed: false, reason: "The reviewed delay jitter window must be zero or a positive number of milliseconds with no code ceiling." };
6263
+ return { allowed: true };
6264
+ }
6265
+ if (kind === "waitelement") {
6266
+ const wait = options.wait;
6267
+ if (!wait || typeof wait !== "object" || Array.isArray(wait)) return { allowed: false, reason: "The element wait needs a reviewed selector, timeout and poll interval in options." };
6268
+ const reviewed = wait;
6269
+ if (typeof reviewed.selector !== "string" || !reviewed.selector.trim()) return { allowed: false, reason: "The element wait needs a reviewed non-empty selector." };
6270
+ if (typeof reviewed.timeout !== "number" || !Number.isFinite(reviewed.timeout) || reviewed.timeout < 0) return { allowed: false, reason: "The reviewed element wait timeout must be zero or a positive number of milliseconds with no code ceiling." };
6271
+ if (typeof reviewed.poll !== "number" || !Number.isFinite(reviewed.poll) || reviewed.poll < 0) return { allowed: false, reason: "The reviewed element wait poll interval must be zero or a positive number of milliseconds with no code ceiling." };
6272
+ return { allowed: true };
6273
+ }
6274
+ if (kind === "compute") {
6275
+ const expression = expressionof(options.expression);
6276
+ if (!expression) return { allowed: false, reason: `The expression step needs a reviewed expression with operands, an operator of the reviewed set (${expressionoperators.join(", ")}) and a result variable of a reviewed kind.` };
6277
+ const operatorcheck = validatexpressionoperators(expression);
6278
+ if (!operatorcheck.allowed) return operatorcheck;
6279
+ return { allowed: true };
6280
+ }
6281
+ if (kind === "extractvars") {
6282
+ const rule = regexruleof(options.rule);
6283
+ if (!rule) return { allowed: false, reason: "The variable extraction needs a reviewed regex rule with its pattern, flags and named capture groups." };
6284
+ const shapecheck = validateregexrule(rule.pattern);
6285
+ if (!shapecheck.allowed) return shapecheck;
6286
+ if (typeof options.text !== "string") return { allowed: false, reason: "The variable extraction needs the reviewed text the regex rule applies to." };
6287
+ return { allowed: true };
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
+ }
6351
+ return { allowed: true };
6352
+ }
6353
+ function validateregexrule(pattern) {
6354
+ try {
6355
+ new RegExp(pattern);
6356
+ } catch {
6357
+ return { allowed: false, reason: "The reviewed regex pattern does not compile." };
6358
+ }
6359
+ const nestedquantifier = /\((?:[^()\\]|\\.)*[+*}]\)[+*{]/.test(pattern) || /\(\)[+*{]/.test(pattern);
6360
+ if (nestedquantifier) return { allowed: false, reason: "The reviewed regex pattern nests an unbounded quantifier inside a quantified group and is refused because adversarial text could explode the backtracking." };
6361
+ const unboundedrepeat = /\{\d+,\}/.test(pattern);
6362
+ if (unboundedrepeat && /\([^)]*\{\d+,\}[^)]*\)[+*{]/.test(pattern)) return { allowed: false, reason: "The reviewed regex pattern repeats an unbounded group and is refused because adversarial text could explode the backtracking." };
6363
+ return { allowed: true };
6364
+ }
6365
+ function validatexpressionoperators(expression) {
6366
+ const numeric = /* @__PURE__ */ new Set(["add", "subtract", "multiply", "divide", "modulo"]);
6367
+ const logic = /* @__PURE__ */ new Set(["and", "or", "not"]);
6368
+ const comparison = /* @__PURE__ */ new Set(["less", "greater", "lessequal", "greaterequal"]);
6369
+ const text2 = /* @__PURE__ */ new Set(["concat", "contains"]);
6370
+ const operator = expression.operator;
6371
+ if (numeric.has(operator)) {
6372
+ for (const operand of [expression.left, expression.right]) {
6373
+ if (operand === void 0) continue;
6374
+ if (operand.literal !== void 0 && typeof operand.literal === "boolean") return { allowed: false, reason: `The ${operator} operator needs numeric operands; boolean literals are refused.` };
6375
+ }
6376
+ if (expression.resultkind !== "number" && expression.resultkind !== "string") return { allowed: false, reason: `The ${operator} operator needs a number result kind.` };
6377
+ }
6378
+ if (logic.has(operator)) {
6379
+ for (const operand of [expression.left, expression.right]) {
6380
+ if (operand === void 0) continue;
6381
+ if (operand.literal !== void 0 && typeof operand.literal !== "boolean") return { allowed: false, reason: `The ${operator} operator needs boolean operands; non boolean literals are refused.` };
6382
+ }
6383
+ if (expression.resultkind !== "boolean") return { allowed: false, reason: `The ${operator} operator needs a boolean result kind.` };
6384
+ if (operator === "not" && expression.right !== void 0) return { allowed: false, reason: "The not operator takes one operand only." };
6385
+ }
6386
+ if (comparison.has(operator) && expression.resultkind !== "boolean") return { allowed: false, reason: `The ${operator} operator needs a boolean result kind.` };
6387
+ if (text2.has(operator) && expression.resultkind !== "boolean" && expression.resultkind !== "string") return { allowed: false, reason: `The ${operator} operator needs a string or boolean result kind.` };
6388
+ if (operator === "contains" && expression.resultkind !== "boolean") return { allowed: false, reason: "The contains operator needs a boolean result kind." };
6389
+ if (operator === "length") {
6390
+ if (expression.right !== void 0) return { allowed: false, reason: "The length operator takes one operand only." };
6391
+ if (expression.resultkind !== "number") return { allowed: false, reason: "The length operator needs a number result kind." };
6392
+ }
6393
+ if ((operator === "equal" || operator === "notequal") && !(/* @__PURE__ */ new Set(["boolean", "string", "number"])).has(expression.resultkind)) return { allowed: false, reason: "The equality operator needs a primitive result kind." };
6394
+ return { allowed: true };
6395
+ }
6396
+ function workflowgate(input) {
6397
+ const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: "run the workflow step" });
6398
+ if (!gate.allowed) return gate;
6399
+ if (!input.plan || input.plan.state !== "approved") return { allowed: false, reason: "Workflow steps need the approved plan review before they run." };
6400
+ if (input.step.kind === "runworkflow") {
6401
+ let runoptions = {};
6402
+ try {
6403
+ runoptions = parseoptions(input.step);
6404
+ } catch {
6405
+ runoptions = {};
6406
+ }
6407
+ if (runoptions.reviewed !== true) return { allowed: false, reason: "Every real workflow run needs the explicit run review with its expanded step list shown before the first step executes." };
6408
+ }
6409
+ return { allowed: true };
6410
+ }
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
+ }
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 } : {} });
6427
+ if (risk !== "read") return void 0;
6428
+ if (step.kind === "delay") return `The delay step would sleep its reviewed base inside the jitter window.`;
6429
+ if (step.kind === "waitelement") return `The element wait step would poll ${step.target ?? "the reviewed selector"} until appearance or the reviewed timeout.`;
6430
+ if (step.kind === "compute") return `The compute step would evaluate its reviewed expression into the result variable.`;
6431
+ if (step.kind === "extractvars") return `The variable extraction step would apply its reviewed regex rule and store the named captures.`;
6432
+ return `The ${step.kind} step would run read only and mutate nothing.`;
6433
+ }
4912
6434
  function validatecdpgrammar(step, options) {
4913
6435
  const kind = step.kind;
4914
6436
  if (kind === "attachcdp") {
@@ -5478,6 +7000,10 @@ function validatestep(step, origin) {
5478
7000
  const sessioncheck = validatesessiongrammar(step, options);
5479
7001
  if (!sessioncheck.allowed) return sessioncheck;
5480
7002
  }
7003
+ if (isworkflowkind(step.kind)) {
7004
+ const workflowcheck = validateworkflowgrammar(step, options);
7005
+ if (!workflowcheck.allowed) return workflowcheck;
7006
+ }
5481
7007
  if (step.kind === "tabcreate") {
5482
7008
  if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
5483
7009
  if (options.window !== void 0 && (typeof options.window !== "number" || !Number.isInteger(options.window) || options.window < 0)) return { allowed: false, reason: "The reviewed target window id must be a non-negative integer." };
@@ -5668,33 +7194,37 @@ function canexecute(input) {
5668
7194
  }
5669
7195
  }
5670
7196
  }
7197
+ if (isworkflowkind(input.step.kind)) {
7198
+ const workflowgatecheck = workflowgate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });
7199
+ if (!workflowgatecheck.allowed) return workflowgatecheck;
7200
+ }
5671
7201
  if (iscontrolkind(input.step.kind)) {
5672
7202
  const controlgate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: "control the network" });
5673
7203
  if (!controlgate.allowed) return controlgate;
5674
- let controloptions = {};
7204
+ let controloptions2 = {};
5675
7205
  try {
5676
- controloptions = parseoptions(input.step);
7206
+ controloptions2 = parseoptions(input.step);
5677
7207
  } catch {
5678
- controloptions = {};
7208
+ controloptions2 = {};
5679
7209
  }
5680
7210
  if (input.step.kind === "blockrequest") {
5681
7211
  const blockgatecheck = blockgate(input.session, input.step, now);
5682
7212
  if (!blockgatecheck.allowed) return blockgatecheck;
5683
- const rule = blockruleof(controloptions.block);
7213
+ const rule = blockruleof(controloptions2.block);
5684
7214
  if (rule) {
5685
7215
  const blockorigin = origincheck(input.session, rule.urlpattern);
5686
7216
  if (!blockorigin.allowed) return blockorigin;
5687
7217
  }
5688
7218
  }
5689
7219
  if (input.step.kind === "mockresponse" || input.step.kind === "rewriteheaders") {
5690
- 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 ?? "") : "") : [];
5691
7221
  for (const pattern of patterns) {
5692
7222
  const patterngate = origincheck(input.session, pattern);
5693
7223
  if (!patterngate.allowed) return patterngate;
5694
7224
  }
5695
7225
  }
5696
7226
  if (input.step.kind === "setcookies" || input.step.kind === "readcookies" || input.step.kind === "clearcookies") {
5697
- 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 ?? "") : "";
5698
7228
  if (!domain) return { allowed: false, reason: "A reviewed cookie domain is required before cookie control runs." };
5699
7229
  const cookiegatecheck = cookiegate(input.session, domain, now);
5700
7230
  if (!cookiegatecheck.allowed) return cookiegatecheck;
@@ -5924,9 +7454,15 @@ function recordsession(progress, planid, stepid, entry, now) {
5924
7454
  const outcome = { stepid, ok: true, summary: `${entry.detail}${counts.length > 0 ? ` with ${counts}` : ""}.`, details: { session: entry }, at: now };
5925
7455
  return recordoutcome(base, planid, outcome, now);
5926
7456
  }
7457
+ function recordworkflow(progress, planid, stepid, entry, now) {
7458
+ const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
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(/, $/, "");
7460
+ const outcome = { stepid, ok: true, summary: `${entry.detail}${counts.length > 0 ? ` with ${counts}` : ""}.`, details: { workflow: entry }, at: now };
7461
+ return recordoutcome(base, planid, outcome, now);
7462
+ }
5927
7463
 
5928
7464
  // version.ts
5929
- var packageversion = "1.1.49";
7465
+ var packageversion = "1.1.51";
5930
7466
 
5931
7467
  // types.ts
5932
7468
  var protocolversion = packageversion;
@@ -6142,6 +7678,29 @@ function parseproposal(value, origin, grants) {
6142
7678
  }
6143
7679
  if (step.kind === "importsessions" && importsessionfile(sessionoptions.file) === void 0) throw new Error("Session import files of unknown format versions are refused.");
6144
7680
  }
7681
+ if (isworkflowkind(step.kind)) {
7682
+ let workflowoptions = {};
7683
+ try {
7684
+ workflowoptions = parseoptions(step);
7685
+ } catch {
7686
+ workflowoptions = {};
7687
+ }
7688
+ if (step.kind === "composeworkflow") {
7689
+ const payload = workflowoptions.workflow && typeof workflowoptions.workflow === "object" && !Array.isArray(workflowoptions.workflow) ? workflowoptions.workflow : void 0;
7690
+ const origins = payload && Array.isArray(payload.origins) ? payload.origins.filter((originvalue) => typeof originvalue === "string") : [];
7691
+ for (const workfloworigin of origins) {
7692
+ const granted = covered.some((pattern) => {
7693
+ try {
7694
+ return new URL(workfloworigin).origin === new URL(pattern).origin;
7695
+ } catch {
7696
+ return false;
7697
+ }
7698
+ });
7699
+ if (!granted) throw new Error(`The workflow origin ${workfloworigin} stays outside the grants.`);
7700
+ }
7701
+ }
7702
+ if (step.kind === "runworkflow" && workflowoptions.reviewed !== true) throw new Error("Workflow runs without the explicit run review of the expanded step list are refused.");
7703
+ }
6145
7704
  const evaluation = validatestep(step, origin);
6146
7705
  if (!evaluation.allowed) throw new Error(evaluation.reason);
6147
7706
  const target = outboundtarget(step);
@@ -6201,6 +7760,11 @@ function parseproposal(value, origin, grants) {
6201
7760
  };
6202
7761
  return { version: protocolversion, plan };
6203
7762
  }
7763
+ function workflowoutcome(input) {
7764
+ const selected = input.stepid !== void 0 ? input.entries.filter((entry) => entry.stepid === input.stepid) : input.entries;
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 } : {} }));
7766
+ return { version: protocolversion, runid: input.run.id, workflowid: input.run.workflowid, state: input.run.state, ...input.run.dryrun === true ? { dryrun: true } : {}, steps };
7767
+ }
6204
7768
  function stepof(kind, candidate, index) {
6205
7769
  return { id: typeof candidate.id === "string" ? candidate.id : `candidate${index + 1}`, kind, summary: typeof candidate.summary === "string" ? candidate.summary : "", risk: "read", ...typeof candidate.options === "string" ? { options: candidate.options } : {} };
6206
7770
  }
@@ -6216,7 +7780,7 @@ function requestbody(input) {
6216
7780
  return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
6217
7781
  }
6218
7782
  function outcomeresponse(input) {
6219
- 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 } : {} });
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 } : {} } } : {} });
6220
7784
  }
6221
7785
  function mapresponse(input) {
6222
7786
  return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
@@ -6350,6 +7914,9 @@ function emulationreport(input) {
6350
7914
  function sessionreport(input) {
6351
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 } : {} };
6352
7916
  }
7917
+ function workflowreport(input) {
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 ?? [] };
7919
+ }
6353
7920
 
6354
7921
  // capture.ts
6355
7922
  var capturekinds = ["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"];
@@ -7866,15 +9433,15 @@ function remainingpages(sessionvalue, planned) {
7866
9433
  function provenancefor(artifact, url, stepid, at) {
7867
9434
  return { artifact: artifact.id, name: artifact.name, url, stepid, rowstart: artifact.rowcount > 0 ? 1 : 0, rowend: artifact.rowcount, checksum: artifact.checksum, at };
7868
9435
  }
7869
- function interpolate(text2, row) {
9436
+ function interpolate2(text2, row) {
7870
9437
  return text2.replace(/\{\{([^}]+)\}\}/g, (_, key) => row[key.trim()] ?? "");
7871
9438
  }
7872
9439
  function loopstep(step, row) {
7873
9440
  return {
7874
9441
  ...step,
7875
- ...step.target !== void 0 ? { target: interpolate(step.target, row) } : {},
7876
- ...step.value !== void 0 ? { value: interpolate(step.value, row) } : {},
7877
- ...step.options !== void 0 ? { options: interpolate(step.options, row) } : {}
9442
+ ...step.target !== void 0 ? { target: interpolate2(step.target, row) } : {},
9443
+ ...step.value !== void 0 ? { value: interpolate2(step.value, row) } : {},
9444
+ ...step.options !== void 0 ? { options: interpolate2(step.options, row) } : {}
7878
9445
  };
7879
9446
  }
7880
9447
  function loopvariables(row) {
@@ -8078,7 +9645,7 @@ function stepoptions2(step) {
8078
9645
  }
8079
9646
  async function refreshcapabilities() {
8080
9647
  const report = await readcapabilities();
8081
- const withmedia = { ...report, captures: [...capturekinds], media: [...mediakinds], http: [...httpkinds], netwatch: [...socketkinds, ...netwatchkinds], control: [...controlkinds], debug: [...timelinekinds, ...cdpkinds], profile: [...profilerkinds], emulation: [...emulationkinds], sessions: [...sessionkinds] };
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] };
8082
9649
  await memory.setcapabilities(withmedia);
8083
9650
  return withmedia;
8084
9651
  }
@@ -10778,7 +12345,7 @@ async function executehttpstep(step, session, plan, tabid2, origin) {
10778
12345
  var activesockets = /* @__PURE__ */ new Map();
10779
12346
  var channelbuses = /* @__PURE__ */ new Map();
10780
12347
  var netpoll = 100;
10781
- function waitsome(milliseconds) {
12348
+ function waitsome2(milliseconds) {
10782
12349
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, milliseconds)));
10783
12350
  }
10784
12351
  async function queueinboundmessage(channelid, payload) {
@@ -10866,7 +12433,7 @@ async function runsubscription(subscription, controller) {
10866
12433
  }
10867
12434
  if (controller.signal.aborted) break;
10868
12435
  if (subscription.lifetime !== void 0 && Date.now() - startedat >= subscription.lifetime) break;
10869
- await waitsome(netpoll * 10);
12436
+ await waitsome2(netpoll * 10);
10870
12437
  }
10871
12438
  const closed = { ...current, state: "closed", closedat: Date.now() };
10872
12439
  await memory.setsubscription(closed);
@@ -10960,7 +12527,7 @@ async function executesocketstep(step, session, plan, tabid2, origin) {
10960
12527
  const queued = await memory.getmessages(channelid);
10961
12528
  matched = queued.filter((envelope) => matchmessage(filter, envelope)).slice(0, filter.limit ?? (matched.length || void 0));
10962
12529
  if (matched.length >= (filter.limit ?? 1) || Date.now() >= deadline) break;
10963
- await waitsome(netpoll);
12530
+ await waitsome2(netpoll);
10964
12531
  }
10965
12532
  await memory.drainmessages(matched.map((envelope) => ({ channelid: envelope.channelid, sequence: envelope.sequence })));
10966
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);
@@ -11021,7 +12588,7 @@ async function executesocketstep(step, session, plan, tabid2, origin) {
11021
12588
  break;
11022
12589
  }
11023
12590
  stopcursor = decision.cursor;
11024
- await waitsome(decision.next?.wait ?? cursor.interval);
12591
+ await waitsome2(decision.next?.wait ?? cursor.interval);
11025
12592
  next = { url: decision.next?.url ?? next.url, ...decision.next?.body !== void 0 ? { body: decision.next.body } : {} };
11026
12593
  }
11027
12594
  } finally {
@@ -11043,7 +12610,7 @@ async function executenetwatchstep(step, session, plan, tabid2, origin) {
11043
12610
  const limit = typeof options.limit === "number" && Number.isInteger(options.limit) && options.limit >= 1 ? options.limit : void 0;
11044
12611
  const before = await bridgecall(tabid2, "resourcerecords");
11045
12612
  const known = new Set(resourcefacts(before ?? []).map((fact) => `${fact.url}@${fact.start}`));
11046
- if (window2 > 0) await waitsome(window2);
12613
+ if (window2 > 0) await waitsome2(window2);
11047
12614
  const after = await bridgecall(tabid2, "resourcerecords");
11048
12615
  const fresh = resourcefacts(after ?? []).filter((fact) => !known.has(`${fact.url}@${fact.start}`));
11049
12616
  const chosen = limit !== void 0 ? fresh.slice(0, limit) : fresh;
@@ -12193,7 +13760,8 @@ async function refreshbadge() {
12193
13760
  const locationprompts = (await memory.getlocationconsents()).filter((consent) => consent.approved === void 0).length;
12194
13761
  const emulatedlayers = [...activeemulation.values()].reduce((total2, state) => total2 + activelayers(state).length, 0);
12195
13762
  const tasktabs2 = new Set(badges.map((badge) => badge.tabid)).size;
12196
- const total = (queues?.prefetch ?? 0) + (queues?.batchopen ?? 0) + tasktabs2 + prompts + consents + quarantined + datasets + captures + media + recordingprompts + fetchprompts + consoleprompts + debuggerprompts + locationprompts + emulatedlayers + observedrequests + livechannels + activerulescount;
13763
+ const runningworkflows = (await memory.listworkflowruns()).filter((run) => run.state === "running").length + activeworkflowruns.size;
13764
+ const total = (queues?.prefetch ?? 0) + (queues?.batchopen ?? 0) + tasktabs2 + prompts + consents + quarantined + datasets + captures + media + recordingprompts + fetchprompts + consoleprompts + debuggerprompts + locationprompts + emulatedlayers + observedrequests + livechannels + activerulescount + runningworkflows;
12197
13765
  await chrome.action.setBadgeText({ text: total > 0 ? String(total) : "" }).catch(() => {
12198
13766
  });
12199
13767
  }
@@ -12477,6 +14045,324 @@ async function executesessionstep(step, session, plan, tabid2, origin) {
12477
14045
  }
12478
14046
  throw new Error(`The ${step.kind} step has no session memory executor.`);
12479
14047
  }
14048
+ var activeworkflowruns = /* @__PURE__ */ new Map();
14049
+ function runscopes(variables) {
14050
+ const root = { name: "root", variables: [] };
14051
+ if (!variables || typeof variables !== "object" || Array.isArray(variables)) return [root];
14052
+ for (const [name, value] of Object.entries(variables)) {
14053
+ if (typeof value === "number") root.variables.push({ name, kind: "number", value, setat: Date.now() });
14054
+ else if (typeof value === "boolean") root.variables.push({ name, kind: "boolean", value, setat: Date.now() });
14055
+ else if (typeof value === "string") root.variables.push({ name, kind: "string", value, setat: Date.now() });
14056
+ }
14057
+ return [root];
14058
+ }
14059
+ async function dispatchworkflowstep(step, context) {
14060
+ const settings = await memory.getsettings();
14061
+ const verdicts = await memory.getsafeties();
14062
+ const action = {
14063
+ id: step.id,
14064
+ kind: step.kind,
14065
+ summary: step.label,
14066
+ risk: actionrisk(step.kind),
14067
+ ...step.target !== void 0 ? { target: step.target } : {},
14068
+ ...step.value !== void 0 ? { value: step.value } : {},
14069
+ ...step.options !== void 0 ? { options: step.options } : {}
14070
+ };
14071
+ const gate = canexecute({ session: context.session, plan: context.plan, step: action, tabid: context.tabid, origin: context.origin, ...verdicts.length > 0 ? { verdicts } : {}, ...settings !== void 0 ? { settings } : {} });
14072
+ if (!gate.allowed) return { ok: false, summary: `The workflow step ${step.label} was refused: ${gate.reason}` };
14073
+ if (step.kind === "delay") return await executedelaystep(step);
14074
+ if (step.kind === "waitelement") return await executewaitelement(step, context.tabid);
14075
+ if (step.kind === "compute") return await executecomputestep(step, context.session);
14076
+ if (step.kind === "extractvars") return await executeextractvarsstep(step, context.session);
14077
+ const output = await executeaction(action, context.session, context.plan, context.tabid, context.origin, settings, verdicts.length > 0 ? verdicts : void 0, "run");
14078
+ return { ok: Boolean(output.ok), summary: output.summary, ...output.details !== void 0 ? { details: output.details } : {} };
14079
+ }
14080
+ async function executedelaystep(step) {
14081
+ const options = stepoptions2({ id: step.id, kind: step.kind, summary: step.label, risk: "read", ...step.options !== void 0 ? { options: step.options } : {} });
14082
+ const delay = delayof(options.delay);
14083
+ const sampled = delayjitter(delay, hashseed(`${step.id}:${Date.now()}`));
14084
+ const transport = await sleepreviewed(sampled, step.id);
14085
+ return { ok: true, summary: `Slept ${Math.round(sampled)} milliseconds inside the reviewed window of base ${delay.base} and jitter ${delay.jitter}${transport === "alarm" ? " through the alarms api" : ""}.`, details: { sampled: Math.round(sampled), base: delay.base, jitter: delay.jitter, transport } };
14086
+ }
14087
+ function delayof(value) {
14088
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("The delay needs a reviewed base and jitter window.");
14089
+ const candidate = value;
14090
+ if (typeof candidate.base !== "number" || !Number.isFinite(candidate.base) || candidate.base < 0) throw new Error("The reviewed delay base must be zero or a positive number of milliseconds.");
14091
+ if (typeof candidate.jitter !== "number" || !Number.isFinite(candidate.jitter) || candidate.jitter < 0) throw new Error("The reviewed delay jitter window must be zero or a positive number of milliseconds.");
14092
+ return { base: candidate.base, jitter: candidate.jitter };
14093
+ }
14094
+ function hashseed(text2) {
14095
+ let hash = 2166136261;
14096
+ for (let index = 0; index < text2.length; index += 1) {
14097
+ hash ^= text2.charCodeAt(index);
14098
+ hash = Math.imul(hash, 16777619) >>> 0;
14099
+ }
14100
+ return hash >>> 0;
14101
+ }
14102
+ async function sleepreviewed(sampled, stepid) {
14103
+ const alarmname = `devthinkdelay${stepid}`;
14104
+ if (sampled > 3e4 && typeof chrome.alarms?.create === "function" && typeof chrome.alarms.onAlarm?.addListener === "function") {
14105
+ try {
14106
+ await new Promise((resolve) => {
14107
+ const fallback = setTimeout(() => {
14108
+ chrome.alarms.onAlarm.removeListener(listener);
14109
+ resolve();
14110
+ }, sampled + 5e3);
14111
+ const listener = (alarm) => {
14112
+ if (alarm.name !== alarmname) return;
14113
+ clearTimeout(fallback);
14114
+ chrome.alarms.onAlarm.removeListener(listener);
14115
+ resolve();
14116
+ };
14117
+ chrome.alarms.onAlarm.addListener(listener);
14118
+ void chrome.alarms.create(alarmname, { when: Date.now() + sampled });
14119
+ });
14120
+ return "alarm";
14121
+ } catch {
14122
+ }
14123
+ }
14124
+ await waitsome2(sampled);
14125
+ return "timer";
14126
+ }
14127
+ async function executewaitelement(step, tabid2) {
14128
+ const options = stepoptions2({ id: step.id, kind: step.kind, summary: step.label, risk: "read", ...step.target !== void 0 ? { target: step.target } : {}, ...step.options !== void 0 ? { options: step.options } : {} });
14129
+ const wait = waitof(options.wait, step.target);
14130
+ const startedat = Date.now();
14131
+ const starttab = await chrome.tabs.get(tabid2).catch(() => void 0);
14132
+ const starturl = starttab?.url ?? "";
14133
+ const plan = waitelementplan(wait);
14134
+ const singlepass = plan.probes === 1;
14135
+ const deadline = startedat + wait.timeout;
14136
+ for (let pass = 0; pass < plan.probes; pass += 1) {
14137
+ const tab = await chrome.tabs.get(tabid2).catch(() => void 0);
14138
+ if (!tab || starturl !== "" && (tab.url ?? "") !== starturl) return { ok: false, summary: `The tab navigated away and the element wait for ${wait.selector} aborted cleanly after ${Date.now() - startedat} milliseconds.` };
14139
+ const probe = await bridgecall(tabid2, "elementrect", wait.selector).catch(() => void 0);
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 } };
14141
+ if (singlepass || Date.now() >= deadline) break;
14142
+ await waitsome2(pass + 1 < plan.probes ? wait.poll : plan.lastwait);
14143
+ }
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 } };
14145
+ }
14146
+ function waitof(value, target) {
14147
+ const candidate = value && typeof value === "object" && !Array.isArray(value) ? value : {};
14148
+ const selector = typeof candidate.selector === "string" && candidate.selector.trim() ? candidate.selector : target;
14149
+ if (!selector || !selector.trim()) throw new Error("The element wait needs a reviewed non-empty selector.");
14150
+ const timeout = typeof candidate.timeout === "number" && Number.isFinite(candidate.timeout) && candidate.timeout >= 0 ? candidate.timeout : -1;
14151
+ const poll = typeof candidate.poll === "number" && Number.isFinite(candidate.poll) && candidate.poll >= 0 ? candidate.poll : -1;
14152
+ if (timeout < 0 || poll < 0) throw new Error("The element wait needs a reviewed timeout and poll interval of zero or more milliseconds.");
14153
+ return { selector, timeout, poll };
14154
+ }
14155
+ async function executecomputestep(step, session) {
14156
+ const options = stepoptions2({ id: step.id, kind: step.kind, summary: step.label, risk: "read", ...step.options !== void 0 ? { options: step.options } : {} });
14157
+ const expression = options.expression;
14158
+ if (!expression || typeof expression !== "object") throw new Error("The compute step needs a reviewed expression.");
14159
+ const scopes = runscopes(options.variables);
14160
+ const value = expressioneval(expression, scopes);
14161
+ await audit("workflow", `Evaluated the reviewed expression into ${expression.result} with the value ${typeof value === "string" ? `"${value}"` : String(value)}.`, { ...session ? { sessionid: session.id } : {}, stepid: step.id });
14162
+ return { ok: true, summary: `Computed ${expression.result} = ${typeof value === "string" ? `"${value}"` : String(value)} through the ${expression.operator} operator.`, details: { result: expression.result, kind: expression.resultkind, value } };
14163
+ }
14164
+ async function executeextractvarsstep(step, session) {
14165
+ const options = stepoptions2({ id: step.id, kind: step.kind, summary: step.label, risk: "read", ...step.value !== void 0 ? { value: step.value } : {}, ...step.options !== void 0 ? { options: step.options } : {} });
14166
+ const rule = options.rule;
14167
+ if (!rule || typeof rule !== "object" || typeof rule.pattern !== "string") throw new Error("The variable extraction needs a reviewed regex rule.");
14168
+ const text2 = typeof options.text === "string" ? options.text : step.value ?? "";
14169
+ const extraction = regexextract(rule, text2, Date.now());
14170
+ if (!extraction.matched) {
14171
+ await audit("workflow", `The reviewed regex rule of the ${step.id} step matched nothing; no variable was stored.`, { ...session ? { sessionid: session.id } : {}, stepid: step.id });
14172
+ return { ok: true, summary: "The reviewed regex rule matched nothing; no variable was stored.", details: { matched: false, groups: rule.groups } };
14173
+ }
14174
+ await audit("workflow", `The reviewed regex rule captured ${extraction.variables.map((variable) => variable.name).join(", ")} as variables.`, { ...session ? { sessionid: session.id } : {}, stepid: step.id });
14175
+ return { ok: true, summary: `Captured ${extraction.variables.length} variable${extraction.variables.length === 1 ? "" : "s"} from the reviewed text.`, details: { matched: true, variables: extraction.variables } };
14176
+ }
14177
+ async function executeworkflowstep(step, session, plan, tabid2, origin) {
14178
+ const options = stepoptions2(step);
14179
+ if (step.kind === "composeworkflow") {
14180
+ const payload = options.workflow;
14181
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("The workflow composition needs the reviewed workflow payload.");
14182
+ const candidate = payload;
14183
+ const record2 = composeworkflow({
14184
+ ...typeof candidate.id === "string" && candidate.id.trim() ? { id: candidate.id } : {},
14185
+ name: String(candidate.name ?? ""),
14186
+ version: Number(candidate.version ?? 0),
14187
+ origins: Array.isArray(candidate.origins) ? candidate.origins.filter((entry) => typeof entry === "string") : [],
14188
+ steps: (Array.isArray(candidate.steps) ? candidate.steps : []).flatMap((entry) => {
14189
+ const parsed = workflowstepofentry(entry);
14190
+ return parsed !== void 0 ? [parsed] : [];
14191
+ }),
14192
+ blocks: (Array.isArray(candidate.blocks) ? candidate.blocks : []).flatMap((block) => {
14193
+ const parsed = workflowblockof(block);
14194
+ return parsed !== void 0 ? [parsed] : [];
14195
+ }),
14196
+ now: Date.now(),
14197
+ kindallowed: (kind) => {
14198
+ try {
14199
+ actionrisk(kind);
14200
+ return true;
14201
+ } catch {
14202
+ return false;
14203
+ }
14204
+ },
14205
+ riskof: (kind) => actionrisk(kind)
14206
+ });
14207
+ await memory.addworkflowrecord(record2);
14208
+ await memory.setprogress(recordworkflow(await memory.getprogress(), plan.id, step.id, { family: "compose", detail: `Composed the workflow ${record2.name} version ${record2.version}`, total: record2.steps.length }, Date.now()));
14209
+ await audit("workflow", `Composed the workflow ${record2.name} version ${record2.version} with ${record2.steps.length} expanded step${record2.steps.length === 1 ? "" : "s"} graded ${record2.risk} for review; blocks expanded so no step stayed hidden.`, { sessionid: session.id, planid: plan.id, stepid: step.id });
14210
+ return { ok: true, summary: `Composed the workflow ${record2.name} version ${record2.version} with ${record2.steps.length} expanded steps graded ${record2.risk}.`, details: { workflow: { recordid: record2.id, steps: record2.steps.length, risk: record2.risk } } };
14211
+ }
14212
+ if (step.kind === "savetemplate") {
14213
+ const template = steptemplateof({ ...options.template ?? {}, id: randomid(), origin, sharedat: Date.now() });
14214
+ if (!template) throw new Error("The step template needs a reviewed name and a valid workflow step.");
14215
+ await memory.addsteptemplate(template);
14216
+ await memory.setprogress(recordworkflow(await memory.getprogress(), plan.id, step.id, { family: "template", detail: `Shared the step template ${template.name}` }, Date.now()));
14217
+ await audit("workflow", `Shared the step template ${template.name} of the ${template.step.kind} kind for reuse across workflows.`, { sessionid: session.id, planid: plan.id, stepid: step.id });
14218
+ return { ok: true, summary: `Shared the step template ${template.name}.`, details: { workflow: { recordid: template.name, steps: 1 } } };
14219
+ }
14220
+ if (step.kind === "runworkflow") return await executeworkflowrun(step, session, plan, tabid2, origin, false);
14221
+ if (step.kind === "dryrun") return await executeworkflowrun(step, session, plan, tabid2, origin, true);
14222
+ if (step.kind === "delay") return await executedelaystep({ id: step.id, kind: "delay", label: step.summary });
14223
+ if (step.kind === "waitelement") return await executewaitelement({ id: step.id, kind: "waitelement", label: step.summary, ...step.target !== void 0 ? { target: step.target } : {}, ...step.options !== void 0 ? { options: step.options } : {} }, tabid2);
14224
+ if (step.kind === "compute") return await executecomputestep({ id: step.id, kind: "compute", label: step.summary, ...step.options !== void 0 ? { options: step.options } : {} }, session);
14225
+ if (step.kind === "extractvars") return await executeextractvarsstep({ id: step.id, kind: "extractvars", label: step.summary, ...step.value !== void 0 ? { value: step.value } : {}, ...step.options !== void 0 ? { options: step.options } : {} }, session);
14226
+ throw new Error(`The ${step.kind} step has no workflow executor.`);
14227
+ }
14228
+ function workflowstepofentry(value) {
14229
+ const parsed = workflowstepof(value);
14230
+ if (parsed) return parsed;
14231
+ return blockinvocationof(value);
14232
+ }
14233
+ async function executeworkflowrun(step, session, plan, tabid2, origin, dry) {
14234
+ const options = stepoptions2(step);
14235
+ const workflowid = typeof options.workflowid === "string" ? options.workflowid : "";
14236
+ const record2 = await memory.getworkflowrecord(workflowid);
14237
+ if (!record2) throw new Error(`No composed workflow matches ${workflowid || "the reviewed id"}.`);
14238
+ for (const workfloworigin of record2.origins) {
14239
+ if (!origingranted(session, workfloworigin)) throw new Error(`The workflow origin ${workfloworigin} falls outside the session grants.`);
14240
+ }
14241
+ const run = newworkflowrun({ workflowid: record2.id, dryrun: dry, now: Date.now() });
14242
+ await memory.setworkflowrun(run);
14243
+ await memory.setrunscopes(run.id, runscopes(options.variables));
14244
+ if (dry) {
14245
+ const evaluated = dryrunworkflow({ record: record2, run, now: Date.now(), projection: dryrunprojection });
14246
+ for (const entry of evaluated.log) await memory.addrunlogentry(run.id, entry);
14247
+ await memory.setworkflowrun(evaluated.run);
14248
+ const refused = evaluated.log.filter((entry) => entry.state === "refused").length;
14249
+ await memory.setprogress(recordworkflow(await memory.getprogress(), plan.id, step.id, { family: "dryrun", detail: `Dry ran the workflow ${record2.name}`, runid: run.id, executed: evaluated.log.length - refused, refused, total: record2.steps.length }, Date.now()));
14250
+ await audit("workflow", `The dry run of the workflow ${record2.name} evaluated ${evaluated.log.length} step${evaluated.log.length === 1 ? "" : "s"} read only; ${refused} step${refused === 1 ? "" : "s"} refused for lacking a read only projection and nothing was mutated.`, { sessionid: session.id, planid: plan.id, stepid: step.id });
14251
+ return { ok: true, summary: `The dry run evaluated ${evaluated.log.length} steps read only; ${refused} refused for lacking a read only projection.`, details: { runid: run.id, state: evaluated.run.state, dryrun: true, executed: evaluated.log.length - refused, refused, total: record2.steps.length } };
14252
+ }
14253
+ const guards = { cancelled: false };
14254
+ activeworkflowruns.set(run.id, guards);
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 });
14256
+ const context = { session, plan, tabid: tabid2, origin };
14257
+ const executesteprouted = async (workflowstep, stepcontext) => {
14258
+ if (guards.cancelled) return { ok: false, summary: `The run was cancelled before the ${workflowstep.label} step dispatched.` };
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 };
14271
+ };
14272
+ const execute = async (workflowstep, stepcontext) => await executesteprouted(workflowstep, stepcontext);
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;
14275
+ try {
14276
+ const launch = () => runworkflow({
14277
+ record: record2,
14278
+ run,
14279
+ scopes: runscopes(options.variables),
14280
+ execute,
14281
+ now: Date.now(),
14282
+ gates: { sessionactive: Boolean(session && !session.stoppedat && !session.pausedat && session.expiresat > Date.now()), planapproved: plan.state === "approved", origingranted: (workfloworigin) => origingranted(session, workfloworigin) },
14283
+ oncheckpoint: async (state) => {
14284
+ await memory.setworkflowrun(state.run);
14285
+ const last = state.log[state.log.length - 1];
14286
+ if (last) await memory.addrunlogentry(state.run.id, last);
14287
+ await memory.setrunscopes(state.run.id, state.scopes);
14288
+ for (const scope of state.scopes) for (const variable of scope.variables) await memory.addworkflowprovenance(state.run.id, { runid: state.run.id, kind: "binding", name: variable.name, value: variable.value, at: variable.setat });
14289
+ await refreshbadge();
14290
+ }
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
+ }
14304
+ } finally {
14305
+ activeworkflowruns.delete(run.id);
14306
+ }
14307
+ await memory.setworkflowrun(result.run);
14308
+ for (const entry of result.log) {
14309
+ const stored = await memory.getrunlog(run.id);
14310
+ if (!stored.some((candidate) => candidate.stepid === entry.stepid && candidate.startedat === entry.startedat)) await memory.addrunlogentry(run.id, entry);
14311
+ }
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
+ }
14332
+ const executed = result.run.cursor;
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()));
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 });
14335
+ await refreshbadge();
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 } : {} } };
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
+ }
12480
14366
  async function executestep(stepid) {
12481
14367
  const session = await memory.getsession();
12482
14368
  const plan = await memory.getplan();
@@ -12485,7 +14371,10 @@ async function executestep(stepid) {
12485
14371
  if (!step) throw new Error("Reviewed step was not found.");
12486
14372
  const settings = await memory.getsettings();
12487
14373
  const verdicts = await memory.getsafeties();
12488
- const gate = canexecute({ session, plan, step, tabid: tab.id, origin, ...verdicts.length > 0 ? { verdicts } : {}, ...settings !== void 0 ? { settings } : {} });
14374
+ return executeaction(step, session, plan, tab.id, origin, settings, verdicts.length > 0 ? verdicts : void 0, "plan");
14375
+ }
14376
+ async function executeaction(step, session, plan, tabid2, origin, settings, verdicts, mode) {
14377
+ const gate = canexecute({ session, plan, step, tabid: tabid2, origin, ...verdicts !== void 0 && verdicts.length > 0 ? { verdicts } : {}, ...settings !== void 0 ? { settings } : {} });
12489
14378
  if (!gate.allowed) throw new Error(gate.reason);
12490
14379
  const capability = requiredcapability(step.kind);
12491
14380
  if (capability) {
@@ -12499,79 +14388,82 @@ async function executestep(stepid) {
12499
14388
  await enforcewindowreview(step, session, plan);
12500
14389
  }
12501
14390
  if (istabscommandkind(step.kind)) {
12502
- output = await executetabscommand(step, session, plan, tab.id);
14391
+ output = await executetabscommand(step, session, plan, tabid2);
12503
14392
  } else if (isdatasetkind(step.kind)) {
12504
- output = await executedatastep(step, session, plan, tab.id, origin);
14393
+ output = await executedatastep(step, session, plan, tabid2, origin);
12505
14394
  } else if (isfileskind(step.kind)) {
12506
- output = await executefilesstep(step, session, plan, tab.id, origin);
14395
+ output = await executefilesstep(step, session, plan, tabid2, origin);
12507
14396
  } else if (isformkind(step.kind)) {
12508
- output = await executeformstep(step, session, plan, tab.id, origin);
14397
+ output = await executeformstep(step, session, plan, tabid2, origin);
12509
14398
  } else if (isbrowserkind(step.kind)) {
12510
- output = await runbrowseraction(step, tab.id, tab.windowId ?? chrome.windows.WINDOW_ID_CURRENT);
14399
+ output = await runbrowseraction(step, tabid2, chrome.windows.WINDOW_ID_CURRENT);
12511
14400
  } else if (step.kind === "keyhold") {
12512
- output = await executekeyhold(step, session, plan, tab.id, origin);
14401
+ output = await executekeyhold(step, session, plan, tabid2, origin);
12513
14402
  } else if (step.kind === "keyrelease") {
12514
- output = await executekeyrelease(step, session, plan, tab.id, origin);
14403
+ output = await executekeyrelease(step, session, plan, tabid2, origin);
12515
14404
  } else if (step.kind === "dismissdialog") {
12516
- output = await executedismissdialog(step, session, plan, tab.id, origin);
14405
+ output = await executedismissdialog(step, session, plan, tabid2, origin);
12517
14406
  } else if (step.kind === "retryaction") {
12518
- output = await executeretryaction(step, session, plan, tab.id, origin);
14407
+ output = await executeretryaction(step, session, plan, tabid2, origin);
12519
14408
  } else if (step.kind === "mapclicks") {
12520
- output = await executemapclicks(step, plan, tab.id, origin);
14409
+ output = await executemapclicks(step, plan, tabid2, origin);
12521
14410
  } else if (step.kind === "enterframe") {
12522
- output = await executeenterframe(step, plan, tab.id, origin);
14411
+ output = await executeenterframe(step, plan, tabid2, origin);
12523
14412
  } else if (watchstepkinds.has(step.kind)) {
12524
14413
  if (!session || !plan || plan.state !== "approved") throw new Error("Watch kinds refuse to run outside an approved session plan.");
12525
- const watched = await executewatchstep(step, session, plan, tab.id, origin);
14414
+ const watched = await executewatchstep(step, session, plan, tabid2, origin);
12526
14415
  output = watched.output;
12527
14416
  watchwindow = { startedat: watched.watch.startedat, lifetime: watched.watch.lifetime };
12528
14417
  } else if (step.kind === "diffsnapshots") {
12529
- output = await executediffsnapshots(step, session, plan, tab.id, origin);
14418
+ output = await executediffsnapshots(step, session, plan, tabid2, origin);
12530
14419
  } else if (navigationstepkinds.has(step.kind)) {
12531
- output = await executenavigationkind(step, session, plan, tab.id, origin);
14420
+ output = await executenavigationkind(step, session, plan, tabid2, origin);
12532
14421
  } else if (iscapturekind(step.kind)) {
12533
- output = await executecapturestep(step, session, plan, tab.id, origin);
14422
+ output = await executecapturestep(step, session, plan, tabid2, origin);
12534
14423
  } else if (ismediakind(step.kind)) {
12535
- output = await executemediastep(step, session, plan, tab.id, origin);
14424
+ output = await executemediastep(step, session, plan, tabid2, origin);
12536
14425
  } else if (ishttpkind(step.kind)) {
12537
- output = await executehttpstep(step, session, plan, tab.id, origin);
14426
+ output = await executehttpstep(step, session, plan, tabid2, origin);
12538
14427
  } else if (issocketkind(step.kind)) {
12539
- output = await executesocketstep(step, session, plan, tab.id, origin);
14428
+ output = await executesocketstep(step, session, plan, tabid2, origin);
12540
14429
  } else if (isnetwatchkind(step.kind)) {
12541
- output = await executenetwatchstep(step, session, plan, tab.id, origin);
14430
+ output = await executenetwatchstep(step, session, plan, tabid2, origin);
12542
14431
  } else if (iscontrolkind(step.kind)) {
12543
- output = await executenetcontrolstep(step, session, plan, tab.id, origin);
14432
+ output = await executenetcontrolstep(step, session, plan, tabid2, origin);
12544
14433
  } else if (isdebugkind(step.kind)) {
12545
14434
  if (!session || !plan || plan.state !== "approved") throw new Error("Debugging kinds refuse to run outside an approved session plan.");
12546
- output = await executetimelinestep(step, session, plan, tab.id, origin);
14435
+ output = await executetimelinestep(step, session, plan, tabid2, origin);
12547
14436
  } else if (iscdpkind(step.kind)) {
12548
14437
  if (!session || !plan || plan.state !== "approved") throw new Error("Devtools protocol kinds refuse to run outside an approved session plan.");
12549
- output = await executecdpstep(step, session, plan, tab.id, origin);
14438
+ output = await executecdpstep(step, session, plan, tabid2, origin);
12550
14439
  } else if (isprofilekind(step.kind)) {
12551
14440
  if (!session || !plan || plan.state !== "approved") throw new Error("Profiling kinds refuse to run outside an approved session plan.");
12552
- output = await executeprofilestep(step, session, plan, tab.id, origin);
14441
+ output = await executeprofilestep(step, session, plan, tabid2, origin);
12553
14442
  } else if (isemulationkind(step.kind)) {
12554
14443
  if (!session || !plan || plan.state !== "approved") throw new Error("Emulation kinds refuse to run outside an approved session plan.");
12555
- output = await executeemulationstep(step, session, plan, tab.id, origin);
14444
+ output = await executeemulationstep(step, session, plan, tabid2, origin);
12556
14445
  } else if (issessionkind(step.kind)) {
12557
14446
  if (!session || !plan || plan.state !== "approved") throw new Error("Session memory kinds refuse to run outside an approved session plan.");
12558
- output = await executesessionstep(step, session, plan, tab.id, origin);
14447
+ output = await executesessionstep(step, session, plan, tabid2, origin);
14448
+ } else if (isworkflowkind(step.kind)) {
14449
+ if (!session || !plan || plan.state !== "approved") throw new Error("Workflow kinds refuse to run outside an approved session plan.");
14450
+ output = await executeworkflowstep(step, session, plan, tabid2, origin);
12559
14451
  } else {
12560
14452
  if (step.target && freshcheckkinds.has(step.kind)) {
12561
- const fresh = await snapshot(tab.id);
14453
+ const fresh = await snapshot(tabid2);
12562
14454
  if (!fresh.interactive.some((item) => item.selector === step.target)) throw new Error("The page changed and the target must be reviewed again.");
12563
14455
  }
12564
- output = await dispatchpagestep(step, tab.id, origin, plan);
14456
+ output = await dispatchpagestep(step, tabid2, origin, plan);
12565
14457
  }
12566
14458
  return output;
12567
14459
  };
12568
14460
  const runplan = plan;
12569
14461
  const capturepolicystate = await runcapturepolicy();
12570
14462
  if (capturepolicystate === "beforeafter" && session && step.risk !== "read" && beforeafterwrapallowed(step.kind)) {
12571
- const before = await grabstateshot(step, session, runplan, tab.id, "before");
14463
+ const before = await grabstateshot(step, session, runplan, tabid2, "before");
12572
14464
  output = await dispatchreviewedstep();
12573
14465
  if (output?.ok) {
12574
- const after = await grabstateshot(step, session, runplan, tab.id, "after");
14466
+ const after = await grabstateshot(step, session, runplan, tabid2, "after");
12575
14467
  const domversion = await memory.getobservationversion();
12576
14468
  const paired = capturestates({ policy: "beforeafter", before, after, actionkind: step.kind, ...step.target ? { target: step.target } : {}, ...domversion !== void 0 ? { domsnapshotid: String(domversion) } : {}, at: Date.now(), id: randomid() });
12577
14469
  if (paired.pair) {
@@ -12583,7 +14475,7 @@ async function executestep(stepid) {
12583
14475
  } else {
12584
14476
  output = await dispatchreviewedstep();
12585
14477
  }
12586
- if (["navigate", "back", "forward"].includes(step.kind)) await recordnavigation(step, session, tab.id);
14478
+ if (["navigate", "back", "forward"].includes(step.kind)) await recordnavigation(step, session, tabid2);
12587
14479
  await recordevidence(step, output, session, plan, origin);
12588
14480
  if (output?.ok && plan && typeof output.details?.tabid === "number") {
12589
14481
  await memory.setprogress(assigntasktab(await memory.getprogress(), plan.id, output.details.tabid, Date.now()));
@@ -12591,20 +14483,20 @@ async function executestep(stepid) {
12591
14483
  const summary = output?.summary ?? "The page action returned no result.";
12592
14484
  const resolved = output?.details?.resolvedtarget;
12593
14485
  if (resolved) {
12594
- await memory.addresolution({ stepid, mode: resolved.mode, selector: resolved.selector, label: resolved.label, at: Date.now() });
14486
+ await memory.addresolution({ stepid: step.id, mode: resolved.mode, selector: resolved.selector, label: resolved.label, at: Date.now() });
12595
14487
  }
12596
- const outcome = { stepid, ok: Boolean(output?.ok), summary, ...output?.details ? { details: output.details } : {}, at: Date.now() };
14488
+ const outcome = { stepid: step.id, ok: Boolean(output?.ok), summary, ...output?.details ? { details: output.details } : {}, at: Date.now() };
12597
14489
  const auditkind = stepauditkind(step, Boolean(output?.ok));
12598
- await audit(auditkind, summary, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid });
14490
+ await audit(auditkind, summary, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid: step.id });
12599
14491
  await memory.addoutcome(outcome);
12600
- if (output?.ok && plan) {
14492
+ if (output?.ok && plan && mode === "plan") {
12601
14493
  const base = await memory.getprogress();
12602
- const completed = watchwindow ? recordwatchcompletion(base, plan.id, stepid, watchwindow.startedat, watchwindow.lifetime, Date.now()) : recordstep(base, plan.id, stepid, Date.now());
14494
+ const completed = watchwindow ? recordwatchcompletion(base, plan.id, step.id, watchwindow.startedat, watchwindow.lifetime, Date.now()) : recordstep(base, plan.id, step.id, Date.now());
12603
14495
  const tracked = recordoutcome(completed, plan.id, outcome, Date.now());
12604
14496
  await memory.setprogress(tracked);
12605
14497
  await memory.settaskstate(taskstateof({ runid: plan.id, stepcursor: tracked.completedsteps.length, outputs: tracked.outcomes ?? [], checkpointat: Date.now() }));
12606
14498
  const tracker = activememorytrackers.get(plan.id);
12607
- if (tracker) await sampleheapforstep(tracker, stepid, tab.id, origin, plan).catch(() => {
14499
+ if (tracker) await sampleheapforstep(tracker, step.id, tabid2, origin, plan).catch(() => {
12608
14500
  });
12609
14501
  await updatetaskbadges(plan, tracked);
12610
14502
  await refreshbadge();
@@ -12799,11 +14691,12 @@ async function handlerequest(message, sender) {
12799
14691
  const autosnapshot = await memory.getautosnapshot();
12800
14692
  const crashed = await memory.getcrashflag();
12801
14693
  const sessionrecords = await memory.applysessionexpiry(snapshotretentionwindow(runsettings), Date.now());
14694
+ const newestworkflowrun = (await memory.listworkflowruns())[0];
12802
14695
  const taskstate = plan ? await memory.gettaskstate(plan.id) : void 0;
12803
14696
  const livetab = session ? await chrome.tabs.get(session.tabid).catch(() => void 0) : void 0;
12804
14697
  const waitprofile = session ? waitprofiles.find((record2) => record2.origin === session.origin) : void 0;
12805
14698
  const livestate = { phase: livetab?.status === "loading" ? "loading" : "complete", ...navrecords[0] ? { finalurl: navrecords[0].finalurl, redirects: navrecords[0].chain } : {} };
12806
- 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, ...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()] } : {} };
12807
14700
  }
12808
14701
  case "capabilities":
12809
14702
  return refreshcapabilities();
@@ -13970,6 +15863,172 @@ async function handlerequest(message, sender) {
13970
15863
  await audit("session", "The user cleared the reviewed auto snapshot interval; on demand captures stay the only source of session records.");
13971
15864
  return { cleared: true };
13972
15865
  }
15866
+ case "workflowreview": {
15867
+ const inputreview = message;
15868
+ const record2 = await memory.getworkflowrecord(inputreview.workflowid?.trim() ?? "");
15869
+ if (!record2) throw new Error(`No composed workflow matches ${inputreview.workflowid ?? ""}.`);
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 };
15913
+ }
15914
+ case "approveworkflowrun": {
15915
+ const inputapprove = message;
15916
+ const session = await memory.getsession();
15917
+ if (!session || session.stoppedat || session.expiresat <= Date.now()) throw new Error("Workflow runs need an active browser session behind the consent gates.");
15918
+ const record2 = await memory.getworkflowrecord(inputapprove.workflowid?.trim() ?? "");
15919
+ if (!record2) throw new Error(`No composed workflow matches ${inputapprove.workflowid ?? ""}.`);
15920
+ await audit("workflow", `The user approved the run review of the workflow ${record2.name} version ${record2.version} with its ${record2.steps.length} expanded step${record2.steps.length === 1 ? "" : "s"} shown; the run still passes every consent gate per step.`, { sessionid: session.id });
15921
+ return { approved: true, steps: record2.steps.length, risk: record2.risk };
15922
+ }
15923
+ case "executeworkflowstep": {
15924
+ const inputsingle = message;
15925
+ const session = await memory.getsession();
15926
+ const plan = await memory.getplan();
15927
+ if (!session || !plan || plan.state !== "approved") throw new Error("Single step execution needs an approved session plan.");
15928
+ const stored = await memory.getrun(inputsingle.runid?.trim() ?? "");
15929
+ if (!stored) throw new Error(`No workflow run matches ${inputsingle.runid ?? ""}.`);
15930
+ const record2 = await memory.getworkflowrecord(stored.run.workflowid);
15931
+ if (!record2) throw new Error("The workflow of the run is no longer composed in the library.");
15932
+ const step = record2.steps.find((entry) => entry.id === (inputsingle.stepid ?? ""));
15933
+ if (!step) throw new Error(`No step of the workflow matches ${inputsingle.stepid ?? ""}.`);
15934
+ const { tab, origin } = await activecontext();
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 } : {} });
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
+ }
15953
+ await memory.setrunscopes(stored.run.id, executed.scopes);
15954
+ const stepindex = record2.steps.findIndex((entry) => entry.id === step.id);
15955
+ const isthenext = stepindex === stored.run.cursor && executed.output.ok;
15956
+ if (isthenext) await memory.setworkflowrun({ ...stored.run, cursor: stored.run.cursor + 1 });
15957
+ await audit("workflow", `Executed the single step ${step.label} of the run ${stored.run.id} outside the run loop${isthenext ? " and advanced its checkpoint" : ""}.`, { sessionid: session.id, planid: plan.id, stepid: step.id });
15958
+ return workflowoutcome({ run: { ...stored.run, ...isthenext ? { cursor: stored.run.cursor + 1 } : {} }, entries: await memory.getrunlog(stored.run.id), stepid: step.id });
15959
+ }
15960
+ case "pauseworkflowrun": {
15961
+ const inputpause = message;
15962
+ const stored = (await memory.listworkflowruns()).find((entry) => entry.id === (inputpause.runid ?? ""));
15963
+ if (!stored) throw new Error(`No workflow run matches ${inputpause.runid ?? ""}.`);
15964
+ const guards = activeworkflowruns.get(stored.id);
15965
+ if (guards) guards.cancelled = true;
15966
+ const paused = pauserun(stored, Date.now());
15967
+ await memory.setworkflowrun(paused);
15968
+ await audit("workflow", `Paused the workflow run ${paused.id} at the checkpoint of step cursor ${paused.cursor}; the resume continues exactly there.`, {});
15969
+ await refreshbadge();
15970
+ return { runid: paused.id, state: paused.state, cursor: paused.cursor };
15971
+ }
15972
+ case "resumeworkflowrun": {
15973
+ const inputresume = message;
15974
+ const session = await memory.getsession();
15975
+ const plan = await memory.getplan();
15976
+ if (!session || !plan || plan.state !== "approved") throw new Error("The run resume needs an approved session plan.");
15977
+ const stored = await memory.getrun(inputresume.runid?.trim() ?? "");
15978
+ if (!stored) throw new Error(`No workflow run matches ${inputresume.runid ?? ""}.`);
15979
+ if (stored.run.state !== "paused" && stored.run.state !== "running") throw new Error(`The workflow run is already ${stored.run.state}.`);
15980
+ const record2 = await memory.getworkflowrecord(stored.run.workflowid);
15981
+ if (!record2) throw new Error("The workflow of the run is no longer composed in the library.");
15982
+ const scopes = await memory.getrunscopes(stored.run.id);
15983
+ const log = await memory.getrunlog(stored.run.id);
15984
+ const { tab, origin } = await activecontext();
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) => {
15994
+ await memory.setworkflowrun(state.run);
15995
+ for (const entry of state.log.slice(storedentries)) await memory.addrunlogentry(state.run.id, entry);
15996
+ storedentries = state.log.length;
15997
+ await memory.setrunscopes(state.run.id, state.scopes);
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
+ }
16008
+ await memory.setworkflowrun(resumed.run);
16009
+ await memory.setrunscopes(stored.run.id, resumed.scopes);
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 });
16011
+ await refreshbadge();
16012
+ return { runid: resumed.run.id, state: resumed.run.state, cursor: resumed.run.cursor };
16013
+ }
16014
+ case "cancelworkflowrun": {
16015
+ const inputcancel = message;
16016
+ const stored = (await memory.listworkflowruns()).find((entry) => entry.id === (inputcancel.runid ?? ""));
16017
+ if (!stored) throw new Error(`No workflow run matches ${inputcancel.runid ?? ""}.`);
16018
+ const guards = activeworkflowruns.get(stored.id);
16019
+ if (guards) guards.cancelled = true;
16020
+ const cancelled = cancelrun(stored, typeof inputcancel.reason === "string" && inputcancel.reason.trim() ? inputcancel.reason.trim() : "user cancel", Date.now());
16021
+ await memory.setworkflowrun(cancelled);
16022
+ await audit("workflow", `Cancelled the workflow run ${cancelled.id} with the reason ${cancelled.cancelreason ?? "user cancel"} at step cursor ${cancelled.cursor}.`, {});
16023
+ await refreshbadge();
16024
+ return { runid: cancelled.id, state: cancelled.state, cancelreason: cancelled.cancelreason };
16025
+ }
16026
+ case "workflowoutcome": {
16027
+ const inputoutcome = message;
16028
+ const stored = await memory.getrun(inputoutcome.runid?.trim() ?? "");
16029
+ if (!stored) throw new Error(`No workflow run matches ${inputoutcome.runid ?? ""}.`);
16030
+ return workflowoutcome({ run: stored.run, entries: stored.log, ...inputoutcome.stepid !== void 0 && inputoutcome.stepid !== "" ? { stepid: inputoutcome.stepid } : {} });
16031
+ }
13973
16032
  default:
13974
16033
  throw new Error("Unknown Devthink request.");
13975
16034
  }
@@ -13994,7 +16053,15 @@ async function detectcrash() {
13994
16053
  }
13995
16054
  chrome.runtime.onStartup.addListener(() => {
13996
16055
  void detectcrash();
16056
+ void pauseinterruptedworkflowruns();
13997
16057
  });
16058
+ async function pauseinterruptedworkflowruns() {
16059
+ for (const run of await memory.listworkflowruns()) {
16060
+ if (run.state !== "running") continue;
16061
+ await memory.setworkflowrun({ ...run, state: "paused", pausedat: Date.now() });
16062
+ await audit("workflow", `The service worker restart paused the workflow run ${run.id} at its last checkpoint of step cursor ${run.cursor}; the resume continues exactly there.`, {});
16063
+ }
16064
+ }
13998
16065
  async function maybeautosnapshot() {
13999
16066
  const state = await memory.getautosnapshot();
14000
16067
  if (!state || !Number.isFinite(state.interval.period)) return;