@wenathlan/extension 1.1.51 → 1.1.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -335,6 +335,14 @@ function teardowncdpsession(input) {
335
335
 
336
336
  // workflow.ts
337
337
  var workflowkinds = ["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars"];
338
+ function nestedparamof(value) {
339
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
340
+ const candidate = value;
341
+ if (typeof candidate.name !== "string" || !/^[a-z][a-z0-9]*$/.test(candidate.name)) return void 0;
342
+ if (!variablekinds.includes(candidate.kind)) return void 0;
343
+ if (candidate.default !== void 0 && !["string", "number", "boolean"].includes(typeof candidate.default) && !Array.isArray(candidate.default)) return void 0;
344
+ return { name: candidate.name, kind: candidate.kind, ...candidate.default !== void 0 ? { default: candidate.default } : {} };
345
+ }
338
346
  function workflowstepof(value) {
339
347
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
340
348
  const candidate = value;
@@ -344,6 +352,7 @@ function workflowstepof(value) {
344
352
  if (candidate.target !== void 0 && (typeof candidate.target !== "string" || !candidate.target)) return void 0;
345
353
  if (candidate.value !== void 0 && typeof candidate.value !== "string") return void 0;
346
354
  if (candidate.options !== void 0 && typeof candidate.options !== "string") return void 0;
355
+ if (candidate.breakpoint !== void 0 && typeof candidate.breakpoint !== "boolean") return void 0;
347
356
  const bindings = Array.isArray(candidate.bindings) ? candidate.bindings.flatMap((binding) => bindingof(binding) !== void 0 ? [bindingof(binding)] : []) : void 0;
348
357
  if (candidate.bindings !== void 0 && bindings === void 0) return void 0;
349
358
  if (Array.isArray(candidate.bindings) && bindings !== void 0 && bindings.length !== candidate.bindings.length) return void 0;
@@ -351,14 +360,20 @@ function workflowstepof(value) {
351
360
  if (candidate.expression !== void 0 && expression === void 0) return void 0;
352
361
  const extract = candidate.extract === void 0 ? void 0 : regexruleof(candidate.extract);
353
362
  if (candidate.extract !== void 0 && extract === void 0) return void 0;
354
- return { id: candidate.id, kind: candidate.kind, label: candidate.label, ...candidate.target !== void 0 ? { target: candidate.target } : {}, ...candidate.value !== void 0 ? { value: candidate.value } : {}, ...candidate.options !== void 0 ? { options: candidate.options } : {}, ...bindings !== void 0 && bindings.length > 0 ? { bindings } : {}, ...expression !== void 0 ? { expression } : {}, ...extract !== void 0 ? { extract } : {} };
363
+ const params = Array.isArray(candidate.params) ? candidate.params.flatMap((param) => nestedparamof(param) !== void 0 ? [nestedparamof(param)] : []) : void 0;
364
+ if (candidate.params !== void 0 && params === void 0) return void 0;
365
+ if (Array.isArray(candidate.params) && params !== void 0 && params.length !== candidate.params.length) return void 0;
366
+ 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 } : {}, ...candidate.breakpoint === true ? { breakpoint: true } : {}, ...params !== void 0 && params.length > 0 ? { params } : {} };
355
367
  }
356
368
  function blockinvocationof(value) {
357
369
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
358
370
  const candidate = value;
359
371
  if (typeof candidate.block !== "string" || !candidate.block.trim()) return void 0;
360
372
  if (typeof candidate.label !== "string" || !candidate.label.trim()) return void 0;
361
- return { block: candidate.block, label: candidate.label };
373
+ const params = Array.isArray(candidate.params) ? candidate.params.flatMap((param) => nestedparamof(param) !== void 0 ? [nestedparamof(param)] : []) : void 0;
374
+ if (candidate.params !== void 0 && params === void 0) return void 0;
375
+ if (Array.isArray(candidate.params) && params !== void 0 && params.length !== candidate.params.length) return void 0;
376
+ return { block: candidate.block, label: candidate.label, ...params !== void 0 && params.length > 0 ? { params } : {} };
362
377
  }
363
378
  function workflowblockof(value) {
364
379
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
@@ -437,10 +452,15 @@ function regexruleof(value) {
437
452
  function expandblocks(steps, blocks) {
438
453
  const byname = new Map(blocks.map((block) => [block.name, block]));
439
454
  const expanded = [];
440
- const visit = (entries, path, inside) => {
455
+ const visit = (entries, path, inside, params) => {
456
+ let stamped = params === void 0;
441
457
  for (const entry of entries) {
442
458
  if ("kind" in entry && "label" in entry && !("block" in entry)) {
443
- expanded.push(inside === void 0 ? entry : { ...entry, block: inside });
459
+ const marked = inside === void 0 ? entry : { ...entry, block: inside };
460
+ if (!stamped && params !== void 0) {
461
+ expanded.push({ ...marked, params });
462
+ stamped = true;
463
+ } else expanded.push(marked);
444
464
  continue;
445
465
  }
446
466
  const invocation = blockinvocationof(entry);
@@ -448,7 +468,7 @@ function expandblocks(steps, blocks) {
448
468
  if (path.includes(invocation.block)) throw new Error(`The block ${invocation.block} recurs inside itself and cannot expand.`);
449
469
  const block = byname.get(invocation.block);
450
470
  if (!block) throw new Error(`The block ${invocation.block} is not defined in the workflow.`);
451
- visit(block.steps, [...path, invocation.block], invocation.block);
471
+ visit(block.steps, [...path, invocation.block], invocation.block, invocation.params ?? params);
452
472
  }
453
473
  };
454
474
  visit(steps, [], void 0);
@@ -816,6 +836,17 @@ async function runworkflow(input) {
816
836
  if (step.block !== void 0 && step.block !== activeblock) {
817
837
  scopes = pushscope(scopes, step.block, scopes[scopes.length - 1].name);
818
838
  activeblock = step.block;
839
+ if (step.params) {
840
+ try {
841
+ for (const param of step.params) {
842
+ if (param.default === void 0) continue;
843
+ scopes = setvariable(scopes, param.name, param.kind, coercevariable(param.default, param.kind), input.now);
844
+ }
845
+ } catch (error) {
846
+ const reason = error instanceof Error ? error.message : String(error);
847
+ return { run: { ...run, state: "failed", endedat: Date.now(), failreason: `The nested parameter of block ${step.block} failed: ${reason}` }, scopes, log, outputs };
848
+ }
849
+ }
819
850
  } else if (step.block === void 0 && activeblock !== void 0) {
820
851
  while (scopes.length > 1) scopes = popscope(scopes);
821
852
  activeblock = void 0;
@@ -848,6 +879,27 @@ function dryrunworkflow(input) {
848
879
  }
849
880
  return { run: { ...run, state: "done", cursor: input.record.steps.length, endedat: input.now }, scopes, log };
850
881
  }
882
+ function watchdogpass(input) {
883
+ const verdicts = [];
884
+ for (const run of input.runs) {
885
+ if (run.state !== "running") continue;
886
+ const lastcompletedat = input.lastcompletedat[run.id] ?? run.startedat;
887
+ const live = input.liveexecutors.includes(run.id);
888
+ const silence = input.now - lastcompletedat;
889
+ if (!live && input.config.zombiewindow !== void 0 && silence >= input.config.zombiewindow) {
890
+ verdicts.push({ runid: run.id, verdict: "zombie", action: "reap", reason: `The run ${run.id} lost its executor ${silence} ms ago and reaps as a zombie of a browser shutdown at its last checkpoint ${run.cursor}.`, ...lastcompletedat !== run.startedat ? { lastcompletedat } : {} });
891
+ continue;
892
+ }
893
+ if (!live) continue;
894
+ if (silence >= input.config.stallthreshold) {
895
+ const action = input.config.action;
896
+ verdicts.push({ runid: run.id, verdict: "stalled", action, reason: `The run ${run.id} completed no step for ${silence} ms past the reviewed threshold and the watchdog recovers it with ${action} at cursor ${run.cursor}.`, ...lastcompletedat !== run.startedat ? { lastcompletedat } : {} });
897
+ continue;
898
+ }
899
+ verdicts.push({ runid: run.id, verdict: "healthy", action: "none", reason: `The run ${run.id} completed its last step ${silence} ms ago and stays healthy.`, ...lastcompletedat !== run.startedat ? { lastcompletedat } : {} });
900
+ }
901
+ return verdicts;
902
+ }
851
903
 
852
904
  // controlflow.ts
853
905
  var controlflowkinds = ["condition", "branch", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"];
@@ -4363,6 +4415,178 @@ var sessionmemory = class {
4363
4415
  }
4364
4416
  return history;
4365
4417
  }
4418
+ /** Stores one armed trigger rule with its workflow reference; re-arming the same id replaces the rule while the fire history survives. */
4419
+ async addtriggerule(rule) {
4420
+ const rules = (await this.gettriggerules()).filter((entry) => entry.id !== rule.id);
4421
+ await this.adapter.set("triggerules", [rule, ...rules]);
4422
+ }
4423
+ /** Returns every armed trigger rule, newest first. */
4424
+ async gettriggerules() {
4425
+ return await this.adapter.get("triggerules") ?? [];
4426
+ }
4427
+ /** Returns one armed trigger rule by its id. */
4428
+ async gettriggerule(id) {
4429
+ return (await this.gettriggerules()).find((rule) => rule.id === id);
4430
+ }
4431
+ /** Replaces one stored rule after an enable, disable, pause, resume, cooldown or fire bookkeeping change. */
4432
+ async settriggerule(rule) {
4433
+ const rules = await this.gettriggerules();
4434
+ await this.adapter.set("triggerules", rules.map((entry) => entry.id === rule.id ? rule : entry));
4435
+ }
4436
+ /** Replaces every stored rule at once so the session pause and resume suspend and release the whole rule set atomically. */
4437
+ async settriggerules(rules) {
4438
+ return this.adapter.set("triggerules", rules);
4439
+ }
4440
+ /** Removes one armed rule when the user disarms it; the fire history survives for the audit trail. */
4441
+ async removetriggerule(id) {
4442
+ await this.adapter.set("triggerules", (await this.gettriggerules()).filter((rule) => rule.id !== id));
4443
+ }
4444
+ /** Lists every armed rule joined with the name of its composed workflow so the trigger list shows what each rule launches. */
4445
+ async listtriggers() {
4446
+ const rules = await this.gettriggerules();
4447
+ const names = new Map((await this.listworkflows()).map((record2) => [record2.id, record2.name]));
4448
+ return rules.map((rule) => ({ rule, ...names.has(rule.workflowid) ? { workflowname: names.get(rule.workflowid) } : {} }));
4449
+ }
4450
+ /** Records one trigger fire with the reviewed retention window; an absent window keeps every fire record while the rule counters always survive. */
4451
+ async addtriggerfire(fire) {
4452
+ const fires = await this.listtriggerfires();
4453
+ const combined = [fire, ...fires];
4454
+ const retention = (await this.getsettings())?.triggerretention;
4455
+ await this.adapter.set("triggerfires", retention === void 0 ? combined : combined.slice(0, retention));
4456
+ }
4457
+ /** Returns every stored trigger fire record, newest first, optionally filtered to one rule. */
4458
+ async listtriggerfires(ruleid) {
4459
+ const fires = await this.adapter.get("triggerfires") ?? [];
4460
+ return ruleid === void 0 ? fires : fires.filter((fire) => fire.ruleid === ruleid);
4461
+ }
4462
+ /** Stores the pending trigger queue: fires that arrived while the target run was busy or the session paused; the resume drains them through the same gates. */
4463
+ async settriggerqueue(queue) {
4464
+ return this.adapter.set("triggerqueue", queue);
4465
+ }
4466
+ /** Returns the pending trigger queue, oldest first. */
4467
+ async gettriggerqueue() {
4468
+ return await this.adapter.get("triggerqueue") ?? [];
4469
+ }
4470
+ /** Stores one verified webhook payload of a rule; the executor verifies the shared secret and the schema before anything persists. */
4471
+ async addwebhookpayload(ruleid, payload, at) {
4472
+ const stored = await this.adapter.get("webhookpayloads") ?? [];
4473
+ await this.adapter.set("webhookpayloads", [{ ruleid, payload, at }, ...stored]);
4474
+ }
4475
+ /** Returns the stored webhook payloads of one rule, newest first; only secret verified deliveries ever reach this store. */
4476
+ async listwebhookpayloads(ruleid) {
4477
+ const stored = await this.adapter.get("webhookpayloads") ?? [];
4478
+ return stored.filter((entry) => entry.ruleid === ruleid);
4479
+ }
4480
+ /** Stores one manual run preview with its step list so the panel renders it before confirmation. */
4481
+ async addmanualrun(preview) {
4482
+ const runs = (await this.listmanualruns()).filter((entry) => entry.id !== preview.id);
4483
+ await this.adapter.set("manualruns", [preview, ...runs]);
4484
+ }
4485
+ /** Returns every stored manual run preview with its confirmation outcome, newest first. */
4486
+ async listmanualruns() {
4487
+ return await this.adapter.get("manualruns") ?? [];
4488
+ }
4489
+ /** Stores one workflow version record with its change note; saving the same version again replaces its note while older versions survive for the timeline. */
4490
+ async addworkflowversion(version) {
4491
+ const versions = (await this.listworkflowversions()).filter((entry) => !(entry.workflowid === version.workflowid && entry.version === version.version));
4492
+ await this.adapter.set("workflowversions", [version, ...versions]);
4493
+ }
4494
+ /** Returns every stored workflow version record, newest first, optionally filtered to one workflow. */
4495
+ async listworkflowversions(workflowid) {
4496
+ const versions = await this.adapter.get("workflowversions") ?? [];
4497
+ return workflowid === void 0 ? versions : versions.filter((entry) => entry.workflowid === workflowid);
4498
+ }
4499
+ /** Stores one version diff result for the history view. */
4500
+ async addversiondiff(diff) {
4501
+ const diffs = (await this.listversiondiffs()).filter((entry) => !(entry.workflowid === diff.workflowid && entry.from === diff.from && entry.to === diff.to));
4502
+ await this.adapter.set("versiondiffs", [diff, ...diffs]);
4503
+ }
4504
+ /** Returns every stored version diff result, newest first, optionally filtered to one workflow. */
4505
+ async listversiondiffs(workflowid) {
4506
+ const diffs = await this.adapter.get("versiondiffs") ?? [];
4507
+ return workflowid === void 0 ? diffs : diffs.filter((entry) => entry.workflowid === workflowid);
4508
+ }
4509
+ /** Records one run history entry — the outcome, duration and trigger cause of one execution — under the user configured retention window with no code ceiling. */
4510
+ async addrunhistory(entry) {
4511
+ const entries = await this.gethistory();
4512
+ const combined = [entry, ...entries];
4513
+ const retention = (await this.getsettings())?.runhistoryretention;
4514
+ await this.adapter.set("runhistory", retention === void 0 ? combined : combined.slice(0, retention));
4515
+ }
4516
+ /** Returns the stored run history, newest first, filtered by workflow, outcome and time floor; the filters stay user choices. */
4517
+ async gethistory(filter) {
4518
+ const entries = await this.adapter.get("runhistory") ?? [];
4519
+ let filtered = entries;
4520
+ if (filter?.workflowid !== void 0) filtered = filtered.filter((entry) => entry.workflowid === filter.workflowid);
4521
+ if (filter?.outcome !== void 0) filtered = filtered.filter((entry) => entry.outcome === filter.outcome);
4522
+ if (filter?.since !== void 0) filtered = filtered.filter((entry) => entry.endedat >= filter.since);
4523
+ if (filter?.limit !== void 0) filtered = filtered.slice(0, filter.limit);
4524
+ return filtered;
4525
+ }
4526
+ /** Stores the editor layout of one workflow so the canvas reopens exactly as left. */
4527
+ async seteditorlayout(workflowid, layout) {
4528
+ return this.adapter.set(`editorlayout${workflowid}`, layout);
4529
+ }
4530
+ /** Returns the stored editor layout of one workflow. */
4531
+ async geteditorlayout(workflowid) {
4532
+ return await this.adapter.get(`editorlayout${workflowid}`) ?? void 0;
4533
+ }
4534
+ /** Stores the breakpoint step ids of one workflow. */
4535
+ async setworkflowbreakpoints(workflowid, stepids) {
4536
+ return this.adapter.set(`workflowbreakpoints${workflowid}`, stepids);
4537
+ }
4538
+ /** Returns the stored breakpoint step ids of one workflow, oldest first. */
4539
+ async getworkflowbreakpoints(workflowid) {
4540
+ return await this.adapter.get(`workflowbreakpoints${workflowid}`) ?? [];
4541
+ }
4542
+ /** Stores one per site policy override; re-adding the same id replaces its deltas. */
4543
+ async addsiteoverride(override) {
4544
+ const overrides = (await this.listsiteoverrides()).filter((entry) => entry.id !== override.id);
4545
+ await this.adapter.set("siteoverrides", [override, ...overrides]);
4546
+ }
4547
+ /** Returns every stored per site override, newest first, optionally filtered to one workflow. */
4548
+ async listsiteoverrides(workflowid) {
4549
+ const overrides = await this.adapter.get("siteoverrides") ?? [];
4550
+ return workflowid === void 0 ? overrides : overrides.filter((entry) => entry.workflowid === workflowid);
4551
+ }
4552
+ /** Removes one per site override when the user deletes it. */
4553
+ async removesiteoverride(id) {
4554
+ await this.adapter.set("siteoverrides", (await this.listsiteoverrides()).filter((entry) => entry.id !== id));
4555
+ }
4556
+ /** Stores one watchdog event with its recovery outcome; the event history keeps the audit trail of every scan. */
4557
+ async addwatchdogevent(event) {
4558
+ const events = (await this.listwatchdogevents()).filter((entry) => entry.id !== event.id);
4559
+ await this.adapter.set("watchdogevents", [event, ...events]);
4560
+ }
4561
+ /** Returns every stored watchdog event, newest first. */
4562
+ async listwatchdogevents() {
4563
+ return await this.adapter.get("watchdogevents") ?? [];
4564
+ }
4565
+ /** Stores one pending workflow import held for review; approving it later stores the record as runnable. */
4566
+ async addworkflowimport(entry) {
4567
+ const imports = (await this.listworkflowimports()).filter((candidate) => candidate.id !== entry.id);
4568
+ await this.adapter.set("workflowimports", [entry, ...imports]);
4569
+ }
4570
+ /** Returns every pending workflow import, newest first. */
4571
+ async listworkflowimports() {
4572
+ return await this.adapter.get("workflowimports") ?? [];
4573
+ }
4574
+ /** Removes one pending import when the user approves or rejects it. */
4575
+ async removeworkflowimport(id) {
4576
+ await this.adapter.set("workflowimports", (await this.listworkflowimports()).filter((entry) => entry.id !== id));
4577
+ }
4578
+ /** Stores the per workflow background run flags so a workflow keeps running with the panel closed. */
4579
+ async setbackgroundruns(flags) {
4580
+ return this.adapter.set("backgroundruns", flags);
4581
+ }
4582
+ /** Returns the per workflow background run flags. */
4583
+ async getbackgroundruns() {
4584
+ return await this.adapter.get("backgroundruns") ?? {};
4585
+ }
4586
+ /** Removes one stored workflow record version; a rejected import or rollback disappears from the library while every other version survives. */
4587
+ async removeworkflowversion(id, version) {
4588
+ await this.adapter.set("workflowrecords", (await this.getworkflowrecordversions()).filter((entry) => !(entry.id === id && entry.version === version)));
4589
+ }
4366
4590
  };
4367
4591
  function mediakindof(record2) {
4368
4592
  if ("pages" in record2) return "pdf";
@@ -4899,193 +5123,399 @@ function extractvalues(body, paths) {
4899
5123
  return fields.map((field) => ({ path: field.path, ...field.value !== void 0 ? { value: field.value } : {}, ...field.missing ? { missing: true } : {} }));
4900
5124
  }
4901
5125
 
4902
- // runtimeline.ts
4903
- var timelinekinds = ["watchconsole", "watcherrors", "watchtasks"];
4904
- var loglevels = ["error", "warn", "info", "log", "debug", "trace"];
4905
- var timelinesources = ["console", "error", "rejection", "resource", "longtask", "network", "cdp"];
4906
- function levelrank(level) {
4907
- return loglevels.indexOf(level);
5126
+ // trigger.ts
5127
+ var triggerkinds = ["visitrule", "urlrule", "menurule", "keyrule", "buttonrule", "cronrule", "intervalrule", "urllistrule", "webhookrule", "eventrule"];
5128
+ var triggerfamilies = ["visit", "url", "menu", "key", "button", "cron", "interval", "urllist", "webhook", "event"];
5129
+ var triggereventcatalog = ["mutate", "focus", "banner", "console", "error", "navigate"];
5130
+ var defaulttriggercooldown = 1e4;
5131
+ function istriggerkind(kind) {
5132
+ return triggerkinds.includes(kind);
5133
+ }
5134
+ function triggerfamilyof(kind) {
5135
+ const index = triggerkinds.indexOf(kind);
5136
+ return index >= 0 ? triggerfamilies[index] : void 0;
5137
+ }
5138
+ function triggerlabel(value) {
5139
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
5140
+ }
5141
+ function positivewindow(value) {
5142
+ if (value === void 0) return void 0;
5143
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
4908
5144
  }
4909
- function redactconsoletext(text2, patterns) {
4910
- let redacted = text2;
4911
- for (const pattern of patterns) {
4912
- if (!pattern) continue;
4913
- while (redacted.includes(pattern)) redacted = redacted.replace(pattern, "[redacted]");
5145
+ function jitterwindow(value) {
5146
+ if (value === void 0) return void 0;
5147
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
5148
+ }
5149
+ function httpsorigin(value) {
5150
+ if (typeof value !== "string" || !value.trim()) return void 0;
5151
+ try {
5152
+ const parsed = new URL(value.trim());
5153
+ if (parsed.protocol !== "https:") return void 0;
5154
+ return parsed.origin;
5155
+ } catch {
5156
+ return void 0;
4914
5157
  }
4915
- return redacted;
4916
5158
  }
4917
- function argkind(value) {
4918
- if (value === null) return "null";
4919
- if (Array.isArray(value)) return "array";
4920
- if (value instanceof Error) return "error";
4921
- switch (typeof value) {
4922
- case "string":
4923
- return "string";
4924
- case "number":
4925
- return "number";
4926
- case "boolean":
4927
- return "boolean";
4928
- case "bigint":
4929
- return "bigint";
4930
- case "symbol":
4931
- return "symbol";
4932
- case "function":
4933
- return "function";
4934
- case "undefined":
4935
- return "undefined";
4936
- default:
4937
- return "object";
5159
+ function webhookfieldof(value) {
5160
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
5161
+ const candidate = value;
5162
+ if (typeof candidate.name !== "string" || !/^[a-z][a-z0-9]*$/i.test(candidate.name)) return void 0;
5163
+ if (candidate.kind !== "string" && candidate.kind !== "number" && candidate.kind !== "boolean") return void 0;
5164
+ if (candidate.required !== void 0 && typeof candidate.required !== "boolean") return void 0;
5165
+ return { name: candidate.name, kind: candidate.kind, ...candidate.required === true ? { required: true } : {} };
5166
+ }
5167
+ function triggerpayloadof(family, value) {
5168
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
5169
+ const candidate = value;
5170
+ if (family === "visit") {
5171
+ if (!Array.isArray(candidate.origins) || candidate.origins.length === 0) return void 0;
5172
+ const origins = candidate.origins.map((origin) => httpsorigin(origin));
5173
+ if (origins.some((origin) => origin === void 0)) return void 0;
5174
+ return { origins: [...new Set(origins)] };
5175
+ }
5176
+ if (family === "url") {
5177
+ if (typeof candidate.pattern !== "string" || !candidate.pattern.trim()) return void 0;
5178
+ if (httpsorigin(candidate.pattern) === void 0) return void 0;
5179
+ return { pattern: candidate.pattern.trim() };
5180
+ }
5181
+ if (family === "menu") {
5182
+ const title = triggerlabel(candidate.title);
5183
+ if (!title) return void 0;
5184
+ return { title };
5185
+ }
5186
+ if (family === "key") {
5187
+ if (typeof candidate.command !== "string" || !/^[a-z][a-z0-9-]*$/.test(candidate.command)) return void 0;
5188
+ if (candidate.key !== void 0 && (typeof candidate.key !== "string" || !candidate.key.trim())) return void 0;
5189
+ return { command: candidate.command, ...candidate.key !== void 0 ? { key: candidate.key } : {} };
5190
+ }
5191
+ if (family === "button") return {};
5192
+ if (family === "cron") {
5193
+ if (typeof candidate.cron !== "string" || !candidate.cron.trim()) return void 0;
5194
+ if (cronparse(candidate.cron) === void 0) return void 0;
5195
+ if (candidate.timezone !== void 0 && (typeof candidate.timezone !== "string" || !timezonevalid(candidate.timezone))) return void 0;
5196
+ return { cron: candidate.cron.trim(), ...candidate.timezone !== void 0 ? { timezone: candidate.timezone } : {} };
5197
+ }
5198
+ if (family === "interval") {
5199
+ const period = positivewindow(candidate.period);
5200
+ if (period === void 0) return void 0;
5201
+ const jitter = jitterwindow(candidate.jitter);
5202
+ if (candidate.jitter !== void 0 && jitter === void 0) return void 0;
5203
+ return { period, ...jitter !== void 0 ? { jitter } : {} };
5204
+ }
5205
+ if (family === "urllist") {
5206
+ if (!Array.isArray(candidate.urls) || candidate.urls.length === 0) return void 0;
5207
+ const urls = candidate.urls.map((url) => httpsorigin(url) === void 0 ? void 0 : url.trim());
5208
+ if (urls.some((url) => url === void 0)) return void 0;
5209
+ return { urls };
5210
+ }
5211
+ if (family === "webhook") {
5212
+ if (typeof candidate.secret !== "string" || !webhooksecretok(candidate.secret)) return void 0;
5213
+ if (!Array.isArray(candidate.schema) || candidate.schema.length === 0) return void 0;
5214
+ const schema = candidate.schema.map((field) => webhookfieldof(field));
5215
+ if (schema.some((field) => field === void 0)) return void 0;
5216
+ const names = schema.map((field) => field.name);
5217
+ if (new Set(names).size !== names.length) return void 0;
5218
+ return { secret: candidate.secret, schema };
5219
+ }
5220
+ const events = candidate.events;
5221
+ if (!Array.isArray(events) || events.length === 0) return void 0;
5222
+ if (!events.every((name) => typeof name === "string" && triggereventcatalog.includes(name))) return void 0;
5223
+ return { events: [...new Set(events)] };
5224
+ }
5225
+ function webhooksecretok(secret) {
5226
+ if (secret.length < 24) return false;
5227
+ if (/^(.)\1+$/.test(secret)) return false;
5228
+ return /[a-z]/i.test(secret) && /\d/.test(secret);
5229
+ }
5230
+ function timezonevalid(timezone) {
5231
+ try {
5232
+ new Intl.DateTimeFormat("en-US", { timeZone: timezone });
5233
+ return true;
5234
+ } catch {
5235
+ return false;
4938
5236
  }
4939
5237
  }
4940
- function serializearg(value, depth) {
4941
- const render = (item, remaining) => {
4942
- if (item instanceof Error) return `${item.name}: ${item.message}`;
4943
- if (typeof item === "string") return item;
4944
- if (typeof item === "function") return `[function ${item.name || "anonymous"}]`;
4945
- if (typeof item === "bigint") return `${item}n`;
4946
- if (typeof item === "symbol") return item.toString();
4947
- if (item === null || item === void 0 || typeof item !== "object") return String(item);
4948
- if (remaining <= 0) {
4949
- const tag = Array.isArray(item) ? "Array" : item.constructor?.name ?? "Object";
4950
- return `[${tag}]`;
4951
- }
4952
- if (Array.isArray(item)) return `[${item.map((entry) => render(entry, remaining - 1)).join(", ")}]`;
4953
- const record2 = item;
4954
- return `{${Object.keys(record2).map((key) => `${key}: ${render(record2[key], remaining - 1)}`).join(", ")}}`;
4955
- };
4956
- return render(value, Math.max(0, depth));
5238
+ function armrule(input) {
5239
+ if (typeof input.workflowid !== "string" || !input.workflowid.trim()) return void 0;
5240
+ const payload = triggerpayloadof(input.family, input.payload);
5241
+ if (!payload) return void 0;
5242
+ if (input.cooldown !== void 0 && (typeof input.cooldown !== "number" || !Number.isFinite(input.cooldown) || input.cooldown <= 0)) return void 0;
5243
+ const cooldown = input.cooldown ?? (input.family === "webhook" || input.family === "event" ? defaulttriggercooldown : 0);
5244
+ const label = input.label ?? `The ${input.family} rule of ${input.workflowid}`;
5245
+ return { id: input.id ?? crypto.randomUUID(), kind: input.family, workflowid: input.workflowid, label, ...payload, cooldown, state: { enabled: true, cooldown }, stats: { fires: 0, launches: 0, suppressions: 0 }, createdat: input.now };
4957
5246
  }
4958
- function consolecapture(input) {
4959
- const parts = input.args.map((arg) => serializearg(arg, input.depth));
4960
- return { level: input.level, text: redactconsoletext(parts.join(" "), input.redact), argkinds: input.args.map((arg) => argkind(arg)), repeat: 1 };
5247
+ function updaterule(rule, patch) {
5248
+ return { ...rule, ...patch.state !== void 0 ? { state: { ...rule.state, ...patch.state } } : {}, ...patch.stats !== void 0 ? { stats: { ...rule.stats, ...patch.stats } } : {} };
4961
5249
  }
4962
- function stackframes(stacktext) {
4963
- const frames = [];
4964
- for (const row of stacktext.split("\n")) {
4965
- const trimmed = row.trim();
4966
- if (!trimmed.startsWith("at ")) continue;
4967
- const body = trimmed.slice(3).trim();
4968
- const location = body.match(/\(([^()]*:\d+:\d+)\)$/) ?? body.match(/^(.*:\d+:\d+)$/);
4969
- const located = location?.[1];
4970
- if (!located) continue;
4971
- const segments = located.split(":");
4972
- const column = Number.parseInt(segments.pop() ?? "", 10);
4973
- const lineno = Number.parseInt(segments.pop() ?? "", 10);
4974
- const url = segments.join(":");
4975
- if (!Number.isFinite(lineno) || lineno < 0) continue;
4976
- const name = body.endsWith(`(${located})`) ? body.slice(0, body.length - located.length - 2).trim() : "";
4977
- frames.push({ ...name ? { functionname: name } : {}, url, line: lineno, ...Number.isFinite(column) ? { column } : {} });
5250
+ function matchurl(pattern, url) {
5251
+ let parsedpattern;
5252
+ let parsedurl;
5253
+ try {
5254
+ parsedpattern = new URL(pattern);
5255
+ parsedurl = new URL(url);
5256
+ } catch {
5257
+ return false;
4978
5258
  }
4979
- return frames;
5259
+ if (parsedpattern.protocol !== parsedurl.protocol) return false;
5260
+ if (parsedpattern.hostname !== parsedurl.hostname) return false;
5261
+ if (parsedpattern.port !== "" && parsedpattern.port !== parsedurl.port) return false;
5262
+ return globmatch(`${parsedpattern.pathname}${parsedpattern.search}`, `${parsedurl.pathname}${parsedurl.search}`);
4980
5263
  }
4981
- function errorcapture(input) {
4982
- return { message: redactconsoletext(input.message, input.redact), frames: input.stacktext !== void 0 ? stackframes(input.stacktext) : [], sourceurl: input.sourceurl, line: input.line };
4983
- }
4984
- function rejectioncapture(input) {
4985
- return { reason: redactconsoletext(input.reason, input.redact), frames: input.stacktext !== void 0 ? stackframes(input.stacktext) : [] };
5264
+ function globmatch(pattern, text2) {
5265
+ const escaped = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5266
+ const expression = new RegExp(["^", pattern.split("**").map((segment) => segment.split("*").map(escaped).join("[^/]*")).join(".*"), "$"].join(""));
5267
+ return expression.test(text2);
4986
5268
  }
4987
- function longtaskcapture(input) {
4988
- return input.entries.filter((entry) => entry.duration >= input.threshold).map((entry) => ({ duration: Math.round(entry.duration), starttime: Math.round(entry.starttime), attributions: [...entry.attributions] }));
5269
+ function visitmatch(origins, url) {
5270
+ let origin = "";
5271
+ try {
5272
+ origin = new URL(url).origin;
5273
+ } catch {
5274
+ return false;
5275
+ }
5276
+ return origins.includes(origin);
5277
+ }
5278
+ function cronparse(expression) {
5279
+ const fields = expression.trim().split(/\s+/);
5280
+ if (fields.length !== 5) return void 0;
5281
+ const minutes = cronfield(fields[0] ?? "", 0, 59);
5282
+ const hours = cronfield(fields[1] ?? "", 0, 23);
5283
+ const daysofmonth = cronfield(fields[2] ?? "", 1, 31);
5284
+ const months = cronfield(fields[3] ?? "", 1, 12, monthnames);
5285
+ const daysofweek = cronfield(fields[4] ?? "", 0, 7, weekdaynames, true);
5286
+ if (!minutes || !hours || !daysofmonth || !months || !daysofweek) return void 0;
5287
+ return { minutes, hours, daysofmonth, months, daysofweek: [...new Set(daysofweek.map((day) => day % 7))].sort((left, right) => left - right) };
5288
+ }
5289
+ var weekdaynames = { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 };
5290
+ var monthnames = { jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6, jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12 };
5291
+ function cronfield(field, min, max, names, sundayseven = false) {
5292
+ const values = /* @__PURE__ */ new Set();
5293
+ for (const part of field.split(",")) {
5294
+ if (!part) return void 0;
5295
+ const [range, stepstring] = part.split("/");
5296
+ const step = stepstring === void 0 ? 1 : Number(stepstring);
5297
+ if (!Number.isInteger(step) || step < 1) return void 0;
5298
+ let low = min;
5299
+ let high = max;
5300
+ if (range !== void 0 && range !== "*") {
5301
+ const bounds = range.split("-");
5302
+ if (bounds.length > 2) return void 0;
5303
+ const lowvalue = cronvalue(bounds[0] ?? "", min, max, names);
5304
+ if (lowvalue === void 0) return void 0;
5305
+ low = lowvalue;
5306
+ high = lowvalue;
5307
+ if (bounds.length === 2) {
5308
+ const highvalue = cronvalue(bounds[1] ?? "", min, max, names);
5309
+ if (highvalue === void 0 || highvalue < lowvalue) return void 0;
5310
+ high = highvalue;
5311
+ }
5312
+ }
5313
+ for (let value = low; value <= high; value += step) values.add(value);
5314
+ }
5315
+ const list = [...values];
5316
+ if (list.some((value) => value < min || value > max)) return void 0;
5317
+ if (sundayseven && values.has(7)) {
5318
+ values.delete(7);
5319
+ values.add(0);
5320
+ }
5321
+ return [...values].sort((left, right) => left - right);
4989
5322
  }
4990
- function attachtimeline(input) {
4991
- return { runid: input.runid, origin: input.origin, stepids: [...input.stepids], attachedat: input.now, entries: [] };
5323
+ function cronvalue(value, min, max, names) {
5324
+ const candidate = names?.[value.toLowerCase()];
5325
+ if (candidate !== void 0) return candidate;
5326
+ if (!/^\d+$/.test(value)) return void 0;
5327
+ const parsed = Number(value);
5328
+ if (parsed < min || parsed > max) return void 0;
5329
+ return parsed;
4992
5330
  }
4993
- function filterentries(entries, levelset) {
4994
- return entries.filter((entry) => {
4995
- const floor = levelset.floors?.[entry.stepid] ?? levelset.floors?.["*"];
4996
- if (floor !== void 0 && levelrank(entry.level) > levelrank(floor)) return false;
4997
- if (levelset.sources !== void 0 && levelset.sources.length > 0 && !levelset.sources.includes(entry.source)) return false;
4998
- return true;
4999
- });
5331
+ function calendarparts(at, timezone) {
5332
+ if (timezone === void 0) {
5333
+ const date = new Date(at);
5334
+ return { minute: date.getUTCMinutes(), hour: date.getUTCHours(), day: date.getUTCDate(), month: date.getUTCMonth() + 1, weekday: date.getUTCDay() };
5335
+ }
5336
+ const parts = new Intl.DateTimeFormat("en-US", { timeZone: timezone, hourCycle: "h23", minute: "numeric", hour: "numeric", day: "numeric", month: "short", weekday: "short" }).formatToParts(new Date(at));
5337
+ const pick = (type) => parts.find((part) => part.type === type)?.value ?? "";
5338
+ const weekday = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].indexOf(pick("weekday"));
5339
+ const month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"].indexOf(pick("month")) + 1;
5340
+ return { minute: Number(pick("minute")), hour: Number(pick("hour")), day: Number(pick("day")), month, weekday };
5341
+ }
5342
+ function crondaymatch(schedule, parts) {
5343
+ if (!schedule.months.includes(parts.month)) return false;
5344
+ const domfull = schedule.daysofmonth.length === 31;
5345
+ const dowfull = schedule.daysofweek.length === 7;
5346
+ const dommatch = schedule.daysofmonth.includes(parts.day);
5347
+ const dowmatch = schedule.daysofweek.includes(parts.weekday);
5348
+ if (!domfull && !dowfull) return dommatch || dowmatch;
5349
+ if (!domfull) return dommatch;
5350
+ if (!dowfull) return dowmatch;
5351
+ return true;
5000
5352
  }
5001
- function spamdetect(entries, rule) {
5002
- const collapsed = [];
5003
- const counts = /* @__PURE__ */ new Map();
5004
- for (const entry of entries) {
5005
- if (rule.pattern !== "" && !entry.message.includes(rule.pattern)) {
5006
- collapsed.push({ ...entry, repeat: 1 });
5353
+ function cronnext(expression, from, timezone) {
5354
+ const schedule = cronparse(expression);
5355
+ if (!schedule) return void 0;
5356
+ const minute = 6e4;
5357
+ const hour = 60 * minute;
5358
+ const day = 24 * hour;
5359
+ let candidate = Math.floor(from / minute) * minute + minute;
5360
+ const horizon = from + 4 * 366 * day;
5361
+ while (candidate <= horizon) {
5362
+ const parts = calendarparts(candidate, timezone);
5363
+ if (!crondaymatch(schedule, parts)) {
5364
+ candidate += day - parts.hour * hour - parts.minute * minute;
5007
5365
  continue;
5008
5366
  }
5009
- const key = `${entry.level}|${entry.source}|${entry.message}`;
5010
- const previous = collapsed[collapsed.length - 1];
5011
- if (previous && previous.repeat !== void 0 && `${previous.level}|${previous.source}|${previous.message}` === key && entry.time - previous.time <= rule.windowsize) {
5012
- previous.repeat += 1;
5367
+ if (!schedule.hours.includes(parts.hour)) {
5368
+ const later = schedule.hours.find((value) => value > parts.hour);
5369
+ candidate += later === void 0 ? day - parts.hour * hour - parts.minute * minute : (later - parts.hour) * hour - parts.minute * minute;
5013
5370
  continue;
5014
5371
  }
5015
- collapsed.push({ ...entry, repeat: 1 });
5372
+ if (!schedule.minutes.includes(parts.minute)) {
5373
+ const later = schedule.minutes.find((value) => value > parts.minute);
5374
+ candidate += later === void 0 ? (60 - parts.minute) * minute : (later - parts.minute) * minute;
5375
+ continue;
5376
+ }
5377
+ return candidate;
5016
5378
  }
5017
- for (const entry of collapsed) {
5018
- if (entry.repeat > 1) counts.set(`${entry.level}|${entry.source}|${entry.message}`, entry.repeat);
5379
+ return void 0;
5380
+ }
5381
+ function schedulecron(rule, from) {
5382
+ return cronnext(rule.cron, from, rule.timezone);
5383
+ }
5384
+ function scheduleinterval(rule, lastfire, armedat, seed) {
5385
+ const base = (lastfire ?? armedat) + rule.period;
5386
+ const jitter = rule.jitter ?? 0;
5387
+ if (jitter <= 0) return base;
5388
+ return Math.max(0, Math.round(base - jitter / 2 + seededrandom(seed) * jitter));
5389
+ }
5390
+ function listdue(rules, now) {
5391
+ return rules.flatMap((rule) => {
5392
+ if (rule.state.nextfireat === void 0 || rule.state.nextfireat > now) return [];
5393
+ if (!rule.state.enabled || rule.state.pausedat !== void 0) return [];
5394
+ return [{ rule, overdueby: now - rule.state.nextfireat }];
5395
+ });
5396
+ }
5397
+ function applycooldown(rule, now) {
5398
+ const lastfireat = rule.state.lastfireat;
5399
+ if (lastfireat === void 0 || rule.state.cooldown <= 0) return { suppressed: false, remaining: 0 };
5400
+ const remaining = lastfireat + rule.state.cooldown - now;
5401
+ return { suppressed: remaining > 0, remaining: Math.max(0, remaining) };
5402
+ }
5403
+ function evaluatetrigger(input) {
5404
+ const rule = input.rule;
5405
+ if (!rule.state.enabled) return { fired: false, suppressed: "disabled" };
5406
+ if (rule.state.pausedat !== void 0) return { fired: false, suppressed: "paused" };
5407
+ if (!input.workflowreviewed) return { fired: false, suppressed: "unreviewed" };
5408
+ if (input.runactive) return { fired: false, suppressed: "dedupe" };
5409
+ const cooldown = applycooldown(rule, input.now);
5410
+ if (cooldown.suppressed) return { fired: false, suppressed: "cooldown", remaining: cooldown.remaining };
5411
+ const fire = { id: crypto.randomUUID(), ruleid: rule.id, at: input.now, cause: input.cause, ...input.url !== void 0 ? { url: input.url } : {}, ...input.title !== void 0 ? { title: input.title } : {}, ...input.payload !== void 0 ? { payload: input.payload } : {} };
5412
+ return { fired: true, fire };
5413
+ }
5414
+ function queuefire(queue, fire) {
5415
+ if (queue.some((pending) => pending.ruleid === fire.ruleid)) return { queue, queued: false, deduped: true };
5416
+ return { queue: [...queue, fire], queued: true, deduped: false };
5417
+ }
5418
+ async function drainqueue(queue, launch) {
5419
+ let remaining = [...queue];
5420
+ let launched = 0;
5421
+ while (remaining.length > 0) {
5422
+ const fire = remaining[0];
5423
+ try {
5424
+ await launch(fire);
5425
+ } catch {
5426
+ return { launched, remaining };
5427
+ }
5428
+ remaining = remaining.slice(1);
5429
+ launched += 1;
5430
+ }
5431
+ return { launched, remaining };
5432
+ }
5433
+ function runurllist(rule, now) {
5434
+ const urls = rule.urls ?? [];
5435
+ return urls.map((url) => ({ id: crypto.randomUUID(), ruleid: rule.id, at: now, cause: "urllist", url }));
5436
+ }
5437
+ function verifywebhook(input) {
5438
+ if (typeof input.rule.secret !== "string" || !input.rule.secret) return { verified: false, reason: "The webhook rule carries no reviewed secret." };
5439
+ if (!secrectsmatch(input.secret, input.rule.secret)) return { verified: false, reason: "The webhook secret does not match the reviewed secret of the rule." };
5440
+ const payload = input.payload;
5441
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { verified: false, reason: "The webhook payload must be a JSON object." };
5442
+ const candidate = payload;
5443
+ for (const field of input.rule.schema ?? []) {
5444
+ const value = candidate[field.name];
5445
+ if (value === void 0) {
5446
+ if (field.required === true) return { verified: false, reason: `The required webhook field ${field.name} is missing.` };
5447
+ continue;
5448
+ }
5449
+ if (typeof value !== field.kind) return { verified: false, reason: `The webhook field ${field.name} is not a ${field.kind}.` };
5019
5450
  }
5020
- const flagged = [...counts.entries()].filter(([, count]) => count > rule.collapse).map(([key, count]) => ({ message: key.split("|").slice(2).join("|"), count }));
5021
- return { entries: collapsed, flagged };
5451
+ return { verified: true };
5022
5452
  }
5023
- function rotatelogs(entries, rule) {
5024
- if (entries.length <= rule.maxentries) return { kept: [...entries], overflow: [] };
5025
- const kept = entries.slice(entries.length - rule.maxentries);
5026
- const overflow = entries.slice(0, entries.length - rule.maxentries);
5027
- return { kept, overflow };
5453
+ function secrectsmatch(left, right) {
5454
+ if (left.length !== right.length) return false;
5455
+ let same = true;
5456
+ for (let index = 0; index < left.length; index += 1) if (left.charCodeAt(index) !== right.charCodeAt(index)) same = false;
5457
+ return same;
5028
5458
  }
5029
- function timelinecounts(entries) {
5030
- const counts = {};
5031
- for (const level of loglevels) counts[level] = 0;
5032
- for (const entry of entries) counts[entry.level] = (counts[entry.level] ?? 0) + 1;
5033
- return counts;
5459
+ function eventrulematches(rule, event) {
5460
+ return (rule.events ?? []).includes(event);
5034
5461
  }
5035
- function blockingduration(tasks, stepid, window) {
5036
- const inside = tasks.filter((task) => task.starttime >= window.startedat && task.starttime <= window.endedat);
5037
- return { stepid, blocking: inside.reduce((total, task) => total + task.duration, 0), tasks: inside.length };
5462
+ function observeevents(rules, event) {
5463
+ if (!triggereventcatalog.includes(event)) return [];
5464
+ return rules.filter((rule) => rule.kind === "event" && rule.state.enabled && rule.state.pausedat === void 0 && eventrulematches(rule, event));
5038
5465
  }
5039
- function netfailureentryof(input) {
5040
- const exchange = input.exchange;
5041
- if (exchange.errorclass === void 0 && exchange.status < 400) return null;
5042
- return { id: input.id, runid: exchange.runid, stepid: exchange.stepid, url: exchange.url, status: exchange.status, errorclass: exchange.errorclass ?? "httperror", correlationid: exchange.correlationid, at: input.at };
5466
+ function pauseall(rules, now) {
5467
+ return rules.map((rule) => rule.state.enabled && rule.state.pausedat === void 0 ? updaterule(rule, { state: { pausedat: now } }) : rule);
5043
5468
  }
5044
- function watcherdetached(input) {
5045
- for (const navigation of input.navigations) {
5046
- if (navigation >= input.startedat && navigation <= input.startedat + input.lifetime) return { detached: true, at: navigation };
5047
- }
5048
- return { detached: false };
5469
+ function resumeall(rules) {
5470
+ return rules.map((rule) => {
5471
+ if (rule.state.pausedat === void 0) return rule;
5472
+ const state = { ...rule.state };
5473
+ delete state.pausedat;
5474
+ return { ...rule, state };
5475
+ });
5049
5476
  }
5050
- function consolediff(input) {
5051
- const base = input.baselines;
5052
- const target = input.targetlines;
5053
- const basemap = /* @__PURE__ */ new Map();
5054
- for (const line of base) basemap.set(line, (basemap.get(line) ?? 0) + 1);
5055
- const targetmap = /* @__PURE__ */ new Map();
5056
- for (const line of target) targetmap.set(line, (targetmap.get(line) ?? 0) + 1);
5057
- const lines = [];
5058
- const added = [];
5059
- const removed = [];
5060
- const repeated = [];
5061
- for (const [line, count] of targetmap) {
5062
- const basecount = basemap.get(line) ?? 0;
5063
- if (basecount === 0) {
5064
- for (let index = 0; index < count; index += 1) {
5065
- lines.push({ kind: "added", text: line });
5066
- added.push(line);
5067
- }
5068
- continue;
5069
- }
5070
- const share = Math.min(basecount, count);
5071
- for (let index = 0; index < share; index += 1) {
5072
- lines.push({ kind: "repeated", text: line, count: share });
5073
- repeated.push(line);
5074
- }
5075
- for (let index = share; index < count; index += 1) {
5076
- lines.push({ kind: "added", text: line });
5077
- added.push(line);
5078
- }
5477
+ function manualpreview(record2, now) {
5478
+ return { id: crypto.randomUUID(), workflowid: record2.id, preview: record2.steps.map((step) => ({ stepid: step.id, kind: step.kind, label: step.label, ...step.block !== void 0 ? { block: step.block } : {}, ...controlsummary(step) !== void 0 ? { control: controlsummary(step) } : {} })), at: now };
5479
+ }
5480
+ function confirmmanualrun(preview, confirmed, now) {
5481
+ return { ...preview, confirmed, decidedat: now };
5482
+ }
5483
+ function triggersummary(rule) {
5484
+ return {
5485
+ kind: rule.kind,
5486
+ workflowid: rule.workflowid,
5487
+ label: rule.label,
5488
+ ...rule.origins !== void 0 ? { origins: rule.origins } : {},
5489
+ ...rule.pattern !== void 0 ? { pattern: rule.pattern } : {},
5490
+ ...rule.title !== void 0 ? { title: rule.title } : {},
5491
+ ...rule.command !== void 0 ? { command: rule.command } : {},
5492
+ ...rule.key !== void 0 ? { key: rule.key } : {},
5493
+ ...rule.cron !== void 0 ? { cron: rule.cron, ...rule.timezone !== void 0 ? { timezone: rule.timezone } : {} } : {},
5494
+ ...rule.period !== void 0 ? { period: rule.period, ...rule.jitter !== void 0 ? { jitter: rule.jitter } : {} } : {},
5495
+ ...rule.urls !== void 0 ? { urls: rule.urls } : {},
5496
+ ...rule.events !== void 0 ? { events: rule.events } : {},
5497
+ ...rule.schema !== void 0 ? { fields: rule.schema.length } : {},
5498
+ cooldown: rule.state.cooldown,
5499
+ enabled: rule.state.enabled,
5500
+ ...rule.state.nextfireat !== void 0 ? { nextfireat: rule.state.nextfireat } : {}
5501
+ };
5502
+ }
5503
+ function ruleorigins(rule) {
5504
+ const origins = /* @__PURE__ */ new Set();
5505
+ for (const origin of rule.origins ?? []) origins.add(origin);
5506
+ if (rule.pattern !== void 0) {
5507
+ const origin = httpsorigin(rule.pattern);
5508
+ if (origin !== void 0) origins.add(origin);
5079
5509
  }
5080
- for (const [line, count] of basemap) {
5081
- const targetcount = targetmap.get(line) ?? 0;
5082
- const missing = Math.max(0, count - targetcount);
5083
- for (let index = 0; index < missing; index += 1) {
5084
- lines.push({ kind: "removed", text: line });
5085
- removed.push(line);
5086
- }
5510
+ for (const url of rule.urls ?? []) {
5511
+ const origin = httpsorigin(url);
5512
+ if (origin !== void 0) origins.add(origin);
5087
5513
  }
5088
- return { base: input.baseid, target: input.targetid, lines, added: added.length, removed: removed.length, repeated: repeated.length, at: input.now };
5514
+ return [...origins];
5515
+ }
5516
+ function ruleoriginsgranted(rule, workfloworigins) {
5517
+ const granted = new Set(workfloworigins);
5518
+ return ruleorigins(rule).every((origin) => granted.has(origin));
5089
5519
  }
5090
5520
 
5091
5521
  // socketbus.ts
@@ -5308,38 +5738,228 @@ function polldecision(input) {
5308
5738
  return { continue: true, reason: "The long poll loop continues.", ...value !== void 0 ? { cursor: value } : {}, next: { ...next, wait: input.cursor.interval } };
5309
5739
  }
5310
5740
 
5311
- // policy.ts
5312
- var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript", "heapshot", "profilecpu", "capturesourcemaps", "emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "restoresession", "exportsessions", "importsessions", "runworkflow"]);
5313
- var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction", "capturebodies", "setbreakpoint", "stepcode", "watchexpr", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"]);
5314
- var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet", "capturepdf", "captureframe", "readmedia", "readassets", "probestream", "timelapse", "shotcanvas", "convertimage", "makethumbs", "fetchurl", "parsejson", "parsehtml", "opensocket", "waitmessage", "watchrequests", "readheaders", "mapapi", "subscribesse", "longpoll", "extractapi", "readcookies", "watchconsole", "watcherrors", "watchtasks", "watchcdp", "measureflow", "trackmemory", "watchshifts", "traceload", "annotatetrace", "replaytrace", "blackboxscripts", "persiststate", "capturesession", "namedsessions", "diffsessions", "searchsessions", "composeworkflow", "savetemplate", "dryrun", "delay", "waitelement", "compute", "extractvars", "condition", "branch"]);
5315
- var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
5316
- var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
5317
- var targetactions = /* @__PURE__ */ new Set(["inspect", "focus", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "deriveselector", "fingerprintsection", "submitform", "retryform", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "scrapetable", "paginateextract", "shotelement", "captureframe", "shotcanvas"]);
5318
- var valueactions = /* @__PURE__ */ new Set(["presskey", "drag", "drop", "upload", "readattribute", "removeattribute", "waittext", "evaluate", "zoomset", "tabactivate", "tabclose", "tabreload", "windowclose", "windowresize", "tabcreate", "windowcreate", "downloadfile", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "chooseradio", "setslider", "setdate", "setcolor", "followlink", "setfragment", "handleauth", "navintent", "openclipboard", "checksafe", "reopentab", "spanav", "duplicatetab", "pintab", "mutetab", "movetab", "movetabwindow", "searchtabs", "badgetab", "attachmeta", "focuswindow", "maximizewindow", "minimizewindow", "restorewindow", "incognitowindow", "asksubmit", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "pausedownload", "resumedownload", "verifydownload", "writeclipboard", "quarantinedownload", "scanvirus"]);
5319
- var tabscommandactions = /* @__PURE__ */ new Set(["querytabs", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "watchtab", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "savelayout", "restorelayout", "findclones", "searchtabs", "badgetab", "attachmeta", "listaudio", "reopenrun", "snapshotsession"]);
5320
- var formactions = /* @__PURE__ */ new Set(["fillform", "filllabel", "fillplaceholder", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "submitform", "readerrors", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "handoffcaptcha", "fillcard", "fillcode", "consentpassword", "skiphoneypot", "detectlogin", "detecttemplate"]);
5321
- var datasetactions = /* @__PURE__ */ new Set(["scrapetable", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "importcsv", "looprows", "transformvalues", "deduperows", "paginateextract", "mergepages", "stamplerows", "previewgrid", "streamdisk", "resumeextract", "logprovenance"]);
5322
- var exportactions = /* @__PURE__ */ new Set(["exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk"]);
5323
- var filesactions = /* @__PURE__ */ new Set(["batchdownload", "pausedownload", "resumedownload", "verifydownload", "interceptmime", "exportnetlog", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "namecaptures", "cleanupartifacts"]);
5324
- var captureactions = /* @__PURE__ */ new Set(["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"]);
5325
- var mediaactions = /* @__PURE__ */ new Set(["capturepdf", "recordscreen", "captureaudio", "captureframe", "downloadimages", "shotcanvas", "probestream", "readmedia", "readassets", "timelapse", "convertimage", "makethumbs"]);
5326
- var httpactions = /* @__PURE__ */ new Set(["fetchurl", "parsejson", "parsehtml", "callrest", "callgraphql"]);
5327
- var socketactions = /* @__PURE__ */ new Set(["opensocket", "sendmessage", "waitmessage", "subscribesse", "longpoll"]);
5328
- var netwatchactions = /* @__PURE__ */ new Set(["watchrequests", "readheaders", "capturebodies", "mapapi", "extractapi"]);
5329
- var controlactions = /* @__PURE__ */ new Set(["blockrequest", "mockresponse", "rewriteheaders", "setcookies", "readcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles"]);
5330
- var debugactions = /* @__PURE__ */ new Set(["watchconsole", "watcherrors", "watchtasks"]);
5331
- var cdpactions = /* @__PURE__ */ new Set(["attachcdp", "detachcdp", "cdpcmd", "watchcdp", "setbreakpoint", "stepcode", "watchexpr", "overridescript"]);
5332
- var profileractions = /* @__PURE__ */ new Set(["measureflow", "heapshot", "trackmemory", "profilecpu", "watchshifts", "traceload", "annotatetrace", "replaytrace", "capturesourcemaps"]);
5333
- var emulationactions = /* @__PURE__ */ new Set(["emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "blackboxscripts"]);
5334
- var sessionactions = /* @__PURE__ */ new Set(["persiststate", "capturesession", "restoresession", "namedsessions", "diffsessions", "searchsessions", "exportsessions", "importsessions"]);
5335
- var workflowactions = /* @__PURE__ */ new Set(["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars", "condition", "branch", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"]);
5336
- var credentialheaders = /* @__PURE__ */ new Set(["authorization", "proxy-authorization", "cookie", "cookie2", "set-cookie", "api-key", "x-api-key", "x-auth-token", "x-session-token", "proxy-authorization"]);
5337
- var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
5338
- var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
5339
- var groupcolors = ["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"];
5340
- function normalizeendpoint(value) {
5341
- const endpoint = new URL(value.trim());
5342
- if (endpoint.protocol !== "https:") throw new Error("Devthink accepts HTTPS endpoints only.");
5741
+ // runtimeline.ts
5742
+ var timelinekinds = ["watchconsole", "watcherrors", "watchtasks"];
5743
+ var loglevels = ["error", "warn", "info", "log", "debug", "trace"];
5744
+ var timelinesources = ["console", "error", "rejection", "resource", "longtask", "network", "cdp"];
5745
+ function levelrank(level) {
5746
+ return loglevels.indexOf(level);
5747
+ }
5748
+ function redactconsoletext(text2, patterns) {
5749
+ let redacted = text2;
5750
+ for (const pattern of patterns) {
5751
+ if (!pattern) continue;
5752
+ while (redacted.includes(pattern)) redacted = redacted.replace(pattern, "[redacted]");
5753
+ }
5754
+ return redacted;
5755
+ }
5756
+ function argkind(value) {
5757
+ if (value === null) return "null";
5758
+ if (Array.isArray(value)) return "array";
5759
+ if (value instanceof Error) return "error";
5760
+ switch (typeof value) {
5761
+ case "string":
5762
+ return "string";
5763
+ case "number":
5764
+ return "number";
5765
+ case "boolean":
5766
+ return "boolean";
5767
+ case "bigint":
5768
+ return "bigint";
5769
+ case "symbol":
5770
+ return "symbol";
5771
+ case "function":
5772
+ return "function";
5773
+ case "undefined":
5774
+ return "undefined";
5775
+ default:
5776
+ return "object";
5777
+ }
5778
+ }
5779
+ function serializearg(value, depth) {
5780
+ const render = (item, remaining) => {
5781
+ if (item instanceof Error) return `${item.name}: ${item.message}`;
5782
+ if (typeof item === "string") return item;
5783
+ if (typeof item === "function") return `[function ${item.name || "anonymous"}]`;
5784
+ if (typeof item === "bigint") return `${item}n`;
5785
+ if (typeof item === "symbol") return item.toString();
5786
+ if (item === null || item === void 0 || typeof item !== "object") return String(item);
5787
+ if (remaining <= 0) {
5788
+ const tag = Array.isArray(item) ? "Array" : item.constructor?.name ?? "Object";
5789
+ return `[${tag}]`;
5790
+ }
5791
+ if (Array.isArray(item)) return `[${item.map((entry) => render(entry, remaining - 1)).join(", ")}]`;
5792
+ const record2 = item;
5793
+ return `{${Object.keys(record2).map((key) => `${key}: ${render(record2[key], remaining - 1)}`).join(", ")}}`;
5794
+ };
5795
+ return render(value, Math.max(0, depth));
5796
+ }
5797
+ function consolecapture(input) {
5798
+ const parts = input.args.map((arg) => serializearg(arg, input.depth));
5799
+ return { level: input.level, text: redactconsoletext(parts.join(" "), input.redact), argkinds: input.args.map((arg) => argkind(arg)), repeat: 1 };
5800
+ }
5801
+ function stackframes(stacktext) {
5802
+ const frames = [];
5803
+ for (const row of stacktext.split("\n")) {
5804
+ const trimmed = row.trim();
5805
+ if (!trimmed.startsWith("at ")) continue;
5806
+ const body = trimmed.slice(3).trim();
5807
+ const location = body.match(/\(([^()]*:\d+:\d+)\)$/) ?? body.match(/^(.*:\d+:\d+)$/);
5808
+ const located = location?.[1];
5809
+ if (!located) continue;
5810
+ const segments = located.split(":");
5811
+ const column = Number.parseInt(segments.pop() ?? "", 10);
5812
+ const lineno = Number.parseInt(segments.pop() ?? "", 10);
5813
+ const url = segments.join(":");
5814
+ if (!Number.isFinite(lineno) || lineno < 0) continue;
5815
+ const name = body.endsWith(`(${located})`) ? body.slice(0, body.length - located.length - 2).trim() : "";
5816
+ frames.push({ ...name ? { functionname: name } : {}, url, line: lineno, ...Number.isFinite(column) ? { column } : {} });
5817
+ }
5818
+ return frames;
5819
+ }
5820
+ function errorcapture(input) {
5821
+ return { message: redactconsoletext(input.message, input.redact), frames: input.stacktext !== void 0 ? stackframes(input.stacktext) : [], sourceurl: input.sourceurl, line: input.line };
5822
+ }
5823
+ function rejectioncapture(input) {
5824
+ return { reason: redactconsoletext(input.reason, input.redact), frames: input.stacktext !== void 0 ? stackframes(input.stacktext) : [] };
5825
+ }
5826
+ function longtaskcapture(input) {
5827
+ return input.entries.filter((entry) => entry.duration >= input.threshold).map((entry) => ({ duration: Math.round(entry.duration), starttime: Math.round(entry.starttime), attributions: [...entry.attributions] }));
5828
+ }
5829
+ function attachtimeline(input) {
5830
+ return { runid: input.runid, origin: input.origin, stepids: [...input.stepids], attachedat: input.now, entries: [] };
5831
+ }
5832
+ function filterentries(entries, levelset) {
5833
+ return entries.filter((entry) => {
5834
+ const floor = levelset.floors?.[entry.stepid] ?? levelset.floors?.["*"];
5835
+ if (floor !== void 0 && levelrank(entry.level) > levelrank(floor)) return false;
5836
+ if (levelset.sources !== void 0 && levelset.sources.length > 0 && !levelset.sources.includes(entry.source)) return false;
5837
+ return true;
5838
+ });
5839
+ }
5840
+ function spamdetect(entries, rule) {
5841
+ const collapsed = [];
5842
+ const counts = /* @__PURE__ */ new Map();
5843
+ for (const entry of entries) {
5844
+ if (rule.pattern !== "" && !entry.message.includes(rule.pattern)) {
5845
+ collapsed.push({ ...entry, repeat: 1 });
5846
+ continue;
5847
+ }
5848
+ const key = `${entry.level}|${entry.source}|${entry.message}`;
5849
+ const previous = collapsed[collapsed.length - 1];
5850
+ if (previous && previous.repeat !== void 0 && `${previous.level}|${previous.source}|${previous.message}` === key && entry.time - previous.time <= rule.windowsize) {
5851
+ previous.repeat += 1;
5852
+ continue;
5853
+ }
5854
+ collapsed.push({ ...entry, repeat: 1 });
5855
+ }
5856
+ for (const entry of collapsed) {
5857
+ if (entry.repeat > 1) counts.set(`${entry.level}|${entry.source}|${entry.message}`, entry.repeat);
5858
+ }
5859
+ const flagged = [...counts.entries()].filter(([, count]) => count > rule.collapse).map(([key, count]) => ({ message: key.split("|").slice(2).join("|"), count }));
5860
+ return { entries: collapsed, flagged };
5861
+ }
5862
+ function rotatelogs(entries, rule) {
5863
+ if (entries.length <= rule.maxentries) return { kept: [...entries], overflow: [] };
5864
+ const kept = entries.slice(entries.length - rule.maxentries);
5865
+ const overflow = entries.slice(0, entries.length - rule.maxentries);
5866
+ return { kept, overflow };
5867
+ }
5868
+ function timelinecounts(entries) {
5869
+ const counts = {};
5870
+ for (const level of loglevels) counts[level] = 0;
5871
+ for (const entry of entries) counts[entry.level] = (counts[entry.level] ?? 0) + 1;
5872
+ return counts;
5873
+ }
5874
+ function blockingduration(tasks, stepid, window) {
5875
+ const inside = tasks.filter((task) => task.starttime >= window.startedat && task.starttime <= window.endedat);
5876
+ return { stepid, blocking: inside.reduce((total, task) => total + task.duration, 0), tasks: inside.length };
5877
+ }
5878
+ function netfailureentryof(input) {
5879
+ const exchange = input.exchange;
5880
+ if (exchange.errorclass === void 0 && exchange.status < 400) return null;
5881
+ return { id: input.id, runid: exchange.runid, stepid: exchange.stepid, url: exchange.url, status: exchange.status, errorclass: exchange.errorclass ?? "httperror", correlationid: exchange.correlationid, at: input.at };
5882
+ }
5883
+ function watcherdetached(input) {
5884
+ for (const navigation of input.navigations) {
5885
+ if (navigation >= input.startedat && navigation <= input.startedat + input.lifetime) return { detached: true, at: navigation };
5886
+ }
5887
+ return { detached: false };
5888
+ }
5889
+ function consolediff(input) {
5890
+ const base = input.baselines;
5891
+ const target = input.targetlines;
5892
+ const basemap = /* @__PURE__ */ new Map();
5893
+ for (const line of base) basemap.set(line, (basemap.get(line) ?? 0) + 1);
5894
+ const targetmap = /* @__PURE__ */ new Map();
5895
+ for (const line of target) targetmap.set(line, (targetmap.get(line) ?? 0) + 1);
5896
+ const lines = [];
5897
+ const added = [];
5898
+ const removed = [];
5899
+ const repeated = [];
5900
+ for (const [line, count] of targetmap) {
5901
+ const basecount = basemap.get(line) ?? 0;
5902
+ if (basecount === 0) {
5903
+ for (let index = 0; index < count; index += 1) {
5904
+ lines.push({ kind: "added", text: line });
5905
+ added.push(line);
5906
+ }
5907
+ continue;
5908
+ }
5909
+ const share = Math.min(basecount, count);
5910
+ for (let index = 0; index < share; index += 1) {
5911
+ lines.push({ kind: "repeated", text: line, count: share });
5912
+ repeated.push(line);
5913
+ }
5914
+ for (let index = share; index < count; index += 1) {
5915
+ lines.push({ kind: "added", text: line });
5916
+ added.push(line);
5917
+ }
5918
+ }
5919
+ for (const [line, count] of basemap) {
5920
+ const targetcount = targetmap.get(line) ?? 0;
5921
+ const missing = Math.max(0, count - targetcount);
5922
+ for (let index = 0; index < missing; index += 1) {
5923
+ lines.push({ kind: "removed", text: line });
5924
+ removed.push(line);
5925
+ }
5926
+ }
5927
+ return { base: input.baseid, target: input.targetid, lines, added: added.length, removed: removed.length, repeated: repeated.length, at: input.now };
5928
+ }
5929
+
5930
+ // policy.ts
5931
+ 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", "visitrule", "urlrule", "menurule", "keyrule", "buttonrule", "cronrule", "intervalrule", "urllistrule", "webhookrule", "eventrule"]);
5932
+ 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"]);
5933
+ 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"]);
5934
+ var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
5935
+ var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
5936
+ 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"]);
5937
+ var valueactions = /* @__PURE__ */ new Set(["presskey", "drag", "drop", "upload", "readattribute", "removeattribute", "waittext", "evaluate", "zoomset", "tabactivate", "tabclose", "tabreload", "windowclose", "windowresize", "tabcreate", "windowcreate", "downloadfile", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "chooseradio", "setslider", "setdate", "setcolor", "followlink", "setfragment", "handleauth", "navintent", "openclipboard", "checksafe", "reopentab", "spanav", "duplicatetab", "pintab", "mutetab", "movetab", "movetabwindow", "searchtabs", "badgetab", "attachmeta", "focuswindow", "maximizewindow", "minimizewindow", "restorewindow", "incognitowindow", "asksubmit", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "pausedownload", "resumedownload", "verifydownload", "writeclipboard", "quarantinedownload", "scanvirus"]);
5938
+ var tabscommandactions = /* @__PURE__ */ new Set(["querytabs", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "watchtab", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "savelayout", "restorelayout", "findclones", "searchtabs", "badgetab", "attachmeta", "listaudio", "reopenrun", "snapshotsession"]);
5939
+ var formactions = /* @__PURE__ */ new Set(["fillform", "filllabel", "fillplaceholder", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "submitform", "readerrors", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "handoffcaptcha", "fillcard", "fillcode", "consentpassword", "skiphoneypot", "detectlogin", "detecttemplate"]);
5940
+ var datasetactions = /* @__PURE__ */ new Set(["scrapetable", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "importcsv", "looprows", "transformvalues", "deduperows", "paginateextract", "mergepages", "stamplerows", "previewgrid", "streamdisk", "resumeextract", "logprovenance"]);
5941
+ var exportactions = /* @__PURE__ */ new Set(["exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk"]);
5942
+ var filesactions = /* @__PURE__ */ new Set(["batchdownload", "pausedownload", "resumedownload", "verifydownload", "interceptmime", "exportnetlog", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "namecaptures", "cleanupartifacts"]);
5943
+ var captureactions = /* @__PURE__ */ new Set(["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"]);
5944
+ var mediaactions = /* @__PURE__ */ new Set(["capturepdf", "recordscreen", "captureaudio", "captureframe", "downloadimages", "shotcanvas", "probestream", "readmedia", "readassets", "timelapse", "convertimage", "makethumbs"]);
5945
+ var httpactions = /* @__PURE__ */ new Set(["fetchurl", "parsejson", "parsehtml", "callrest", "callgraphql"]);
5946
+ var socketactions = /* @__PURE__ */ new Set(["opensocket", "sendmessage", "waitmessage", "subscribesse", "longpoll"]);
5947
+ var netwatchactions = /* @__PURE__ */ new Set(["watchrequests", "readheaders", "capturebodies", "mapapi", "extractapi"]);
5948
+ var controlactions = /* @__PURE__ */ new Set(["blockrequest", "mockresponse", "rewriteheaders", "setcookies", "readcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles"]);
5949
+ var debugactions = /* @__PURE__ */ new Set(["watchconsole", "watcherrors", "watchtasks"]);
5950
+ var cdpactions = /* @__PURE__ */ new Set(["attachcdp", "detachcdp", "cdpcmd", "watchcdp", "setbreakpoint", "stepcode", "watchexpr", "overridescript"]);
5951
+ var profileractions = /* @__PURE__ */ new Set(["measureflow", "heapshot", "trackmemory", "profilecpu", "watchshifts", "traceload", "annotatetrace", "replaytrace", "capturesourcemaps"]);
5952
+ var emulationactions = /* @__PURE__ */ new Set(["emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "blackboxscripts"]);
5953
+ var sessionactions = /* @__PURE__ */ new Set(["persiststate", "capturesession", "restoresession", "namedsessions", "diffsessions", "searchsessions", "exportsessions", "importsessions"]);
5954
+ var workflowactions = /* @__PURE__ */ new Set(["composeworkflow", "savetemplate", "runworkflow", "dryrun", "delay", "waitelement", "compute", "extractvars", "condition", "branch", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"]);
5955
+ var triggeractions = /* @__PURE__ */ new Set(["visitrule", "urlrule", "menurule", "keyrule", "buttonrule", "cronrule", "intervalrule", "urllistrule", "webhookrule", "eventrule"]);
5956
+ 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"]);
5957
+ var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
5958
+ var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
5959
+ var groupcolors = ["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"];
5960
+ function normalizeendpoint(value) {
5961
+ const endpoint = new URL(value.trim());
5962
+ if (endpoint.protocol !== "https:") throw new Error("Devthink accepts HTTPS endpoints only.");
5343
5963
  if (endpoint.username || endpoint.password) throw new Error("Endpoint credentials are not allowed in the URL.");
5344
5964
  return { endpoint: endpoint.toString(), origin: endpoint.origin, configuredat: Date.now() };
5345
5965
  }
@@ -5354,6 +5974,9 @@ function issessionkind(kind) {
5354
5974
  function isworkflowkind(kind) {
5355
5975
  return workflowactions.has(kind);
5356
5976
  }
5977
+ function istriggeraction(kind) {
5978
+ return triggeractions.has(kind);
5979
+ }
5357
5980
  function iswatchkind(kind) {
5358
5981
  return watchactions.has(kind);
5359
5982
  }
@@ -7028,6 +7651,79 @@ function workflowgate(input) {
7028
7651
  }
7029
7652
  return { allowed: true };
7030
7653
  }
7654
+ function validatetriggergrammar(step, options) {
7655
+ const family = triggerfamilyof(step.kind);
7656
+ if (family === void 0) return { allowed: false, reason: "The trigger step is not a reviewed trigger kind." };
7657
+ if (typeof options.workflowid !== "string" || !options.workflowid.trim()) return { allowed: false, reason: "Every trigger rule needs the reviewed id of the composed workflow it launches." };
7658
+ if (options.reviewed !== true) return { allowed: false, reason: "Every trigger rule needs the explicit arm review with its match fields and bound workflow shown before it arms." };
7659
+ if (options.label !== void 0 && (typeof options.label !== "string" || !options.label.trim())) return { allowed: false, reason: "The reviewed trigger label must be a non-empty string." };
7660
+ if (options.cooldown !== void 0 && (typeof options.cooldown !== "number" || !Number.isFinite(options.cooldown) || options.cooldown <= 0)) return { allowed: false, reason: "The reviewed cooldown window must be a positive number of milliseconds with no code ceiling; the webhook and event families keep the documented default when the review configures none." };
7661
+ const payload = options.rule;
7662
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { allowed: false, reason: `The ${step.kind} step needs its reviewed rule payload in options.` };
7663
+ if (triggerpayloadof(family, payload) === void 0) {
7664
+ if (family === "visit") return { allowed: false, reason: "The visit rule needs a non-empty reviewed list of HTTPS origins it fires on." };
7665
+ if (family === "url") return { allowed: false, reason: "The url rule needs a reviewed HTTPS glob url pattern; `*` spans one path segment and `**` spans across segments." };
7666
+ if (family === "menu") return { allowed: false, reason: "The menu rule needs a reviewed non-empty context menu entry title." };
7667
+ if (family === "key") return { allowed: false, reason: "The keyboard shortcut rule needs a reviewed lowercase command name and an optional suggested key binding." };
7668
+ if (family === "cron") return { allowed: false, reason: "The cron rule needs a reviewed five field cron expression of minutes, hours, days, months and weekdays with named weekdays and months and an optional resolvable timezone; unparseable schedules are refused." };
7669
+ if (family === "interval") return { allowed: false, reason: "The interval rule needs a reviewed positive period in milliseconds with an optional zero or positive jitter window." };
7670
+ if (family === "urllist") return { allowed: false, reason: "The url list rule needs a reviewed non-empty list of HTTPS urls its workflow runs across." };
7671
+ if (family === "webhook") return { allowed: false, reason: `The webhook rule needs a reviewed shared secret of at least twenty four characters mixing letters and digits and a non-empty payload schema of named string, number or boolean fields.` };
7672
+ if (family === "event") return { allowed: false, reason: `The page event rule needs a reviewed non-empty list of event names of the observed event catalog: ${triggereventcatalog.join(", ")}.` };
7673
+ return { allowed: false, reason: "The trigger rule payload does not follow its family grammar." };
7674
+ }
7675
+ if (family === "cron") {
7676
+ const candidate = payload;
7677
+ if (typeof candidate.cron === "string" && cronparse(candidate.cron) === void 0) return { allowed: false, reason: "The cron expression does not parse as a five field schedule and is refused." };
7678
+ }
7679
+ if (family === "webhook") {
7680
+ const candidate = payload;
7681
+ if (typeof candidate.secret === "string" && !webhooksecretok(candidate.secret)) return { allowed: false, reason: "The webhook shared secret must hold at least twenty four characters mixing letters and digits; the entropy floor is a floor, never a cap." };
7682
+ }
7683
+ const armed = armrule({ family, workflowid: options.workflowid, ...typeof options.label === "string" && options.label.trim() ? { label: options.label } : {}, payload, ...typeof options.cooldown === "number" ? { cooldown: options.cooldown } : {}, now: 0 });
7684
+ if (armed === void 0) return { allowed: false, reason: "The trigger rule payload does not arm as a reviewed rule." };
7685
+ return { allowed: true };
7686
+ }
7687
+ function triggergate(input) {
7688
+ const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: "arm the trigger rule" });
7689
+ if (!gate.allowed) return gate;
7690
+ if (!input.plan || input.plan.state !== "approved") return { allowed: false, reason: "Trigger rules need the approved plan review before they arm." };
7691
+ let triggeroptions = {};
7692
+ try {
7693
+ triggeroptions = parseoptions(input.step);
7694
+ } catch {
7695
+ triggeroptions = {};
7696
+ }
7697
+ if (triggeroptions.reviewed !== true) return { allowed: false, reason: "Every trigger rule needs the explicit arm review with its match fields and bound workflow shown before it arms." };
7698
+ return { allowed: true };
7699
+ }
7700
+ function triggerorigins(step) {
7701
+ let triggeroptions = {};
7702
+ try {
7703
+ triggeroptions = parseoptions(step);
7704
+ } catch {
7705
+ return [];
7706
+ }
7707
+ const family = triggerfamilyof(step.kind);
7708
+ if (family === void 0) return [];
7709
+ const armed = armrule({ family, workflowid: typeof triggeroptions.workflowid === "string" ? triggeroptions.workflowid : "", payload: triggeroptions.rule, ...typeof triggeroptions.cooldown === "number" ? { cooldown: triggeroptions.cooldown } : {}, now: 0 });
7710
+ if (armed === void 0) return [];
7711
+ const origins = [];
7712
+ for (const origin of armed.origins ?? []) origins.push(origin);
7713
+ if (armed.pattern !== void 0) {
7714
+ try {
7715
+ origins.push(new URL(armed.pattern).origin);
7716
+ } catch {
7717
+ }
7718
+ }
7719
+ for (const url of armed.urls ?? []) {
7720
+ try {
7721
+ origins.push(new URL(url).origin);
7722
+ } catch {
7723
+ }
7724
+ }
7725
+ return [...new Set(origins)];
7726
+ }
7031
7727
  function dryrunprojection(step) {
7032
7728
  if (iscontrolflowkind(step.kind)) {
7033
7729
  for (const child of controlsteps(step)) {
@@ -7624,6 +8320,10 @@ function validatestep(step, origin) {
7624
8320
  const workflowcheck = validateworkflowgrammar(step, options);
7625
8321
  if (!workflowcheck.allowed) return workflowcheck;
7626
8322
  }
8323
+ if (istriggeraction(step.kind)) {
8324
+ const triggercheck = validatetriggergrammar(step, options);
8325
+ if (!triggercheck.allowed) return triggercheck;
8326
+ }
7627
8327
  if (step.kind === "tabcreate") {
7628
8328
  if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
7629
8329
  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." };
@@ -7818,6 +8518,10 @@ function canexecute(input) {
7818
8518
  const workflowgatecheck = workflowgate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });
7819
8519
  if (!workflowgatecheck.allowed) return workflowgatecheck;
7820
8520
  }
8521
+ if (istriggeraction(input.step.kind)) {
8522
+ const triggergatecheck = triggergate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });
8523
+ if (!triggergatecheck.allowed) return triggergatecheck;
8524
+ }
7821
8525
  if (iscontrolkind(input.step.kind)) {
7822
8526
  const controlgate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: "control the network" });
7823
8527
  if (!controlgate.allowed) return controlgate;
@@ -7899,9 +8603,137 @@ function canexecute(input) {
7899
8603
  }
7900
8604
  return validatestep(input.step, input.origin);
7901
8605
  }
8606
+ function reviewedkinds() {
8607
+ return [...allowedactions].sort();
8608
+ }
8609
+ function editorsavegate(input) {
8610
+ const gate = sessiongate({ session: input.session, tabid: input.session?.tabid ?? 0, origin: input.session?.origin ?? "https://example.com", now: input.now, action: "save the workflow editor canvas" });
8611
+ if (!gate.allowed) return gate;
8612
+ if (!input.plan || input.plan.state !== "approved") return { allowed: false, reason: "Editor saves need the approved plan review before a new workflow version composes." };
8613
+ const model = input.model;
8614
+ if (typeof model.name !== "string" || !model.name.trim()) return { allowed: false, reason: "The workflow name of the canvas must be a non-empty string." };
8615
+ if (typeof model.version !== "number" || !Number.isInteger(model.version) || model.version < 1) return { allowed: false, reason: "The workflow version of the canvas must be a positive integer." };
8616
+ if (!Array.isArray(model.origins) || model.origins.length === 0) return { allowed: false, reason: "The canvas needs at least one granted HTTPS origin." };
8617
+ const ids = /* @__PURE__ */ new Set();
8618
+ for (const node of model.nodes) {
8619
+ if (node.step === void 0 === (node.invocation === void 0)) return { allowed: false, reason: "Every canvas node must be exactly one workflow step or one block invocation." };
8620
+ const id = node.id ?? (node.step !== void 0 ? node.step.id : node.invocation.block);
8621
+ if (!id || ids.has(id)) return { allowed: false, reason: `The canvas node id ${id || "(empty)"} must be unique.` };
8622
+ ids.add(id);
8623
+ }
8624
+ const reachable = /* @__PURE__ */ new Set();
8625
+ for (const node of model.nodes) {
8626
+ if (node.step !== void 0) {
8627
+ reachable.add(node.step.id);
8628
+ continue;
8629
+ }
8630
+ const walk = (entries) => {
8631
+ for (const entry of entries) {
8632
+ if (typeof entry.id === "string" && typeof entry.kind === "string") {
8633
+ reachable.add(entry.id);
8634
+ continue;
8635
+ }
8636
+ if (typeof entry.block === "string") {
8637
+ const nested = model.blocks.find((candidate) => candidate.name === entry.block);
8638
+ if (nested) walk(nested.steps);
8639
+ }
8640
+ }
8641
+ };
8642
+ const block = model.blocks.find((candidate) => candidate.name === node.invocation.block);
8643
+ if (!block) return { allowed: false, reason: `The block ${node.invocation.block} of the canvas has no definition.` };
8644
+ walk(block.steps);
8645
+ }
8646
+ let order = 0;
8647
+ const positionof = /* @__PURE__ */ new Map();
8648
+ for (const node of model.nodes) {
8649
+ if (node.step !== void 0) {
8650
+ positionof.set(node.step.id, order);
8651
+ order += 1;
8652
+ continue;
8653
+ }
8654
+ const walk = (entries) => {
8655
+ for (const entry of entries) {
8656
+ if (typeof entry.id === "string" && typeof entry.kind === "string") {
8657
+ positionof.set(entry.id, order);
8658
+ order += 1;
8659
+ continue;
8660
+ }
8661
+ if (typeof entry.block === "string") {
8662
+ const nested = model.blocks.find((candidate) => candidate.name === entry.block);
8663
+ if (nested) walk(nested.steps);
8664
+ }
8665
+ }
8666
+ };
8667
+ walk(model.blocks.find((candidate) => candidate.name === node.invocation.block).steps);
8668
+ }
8669
+ for (const edge of model.edges) {
8670
+ if (!reachable.has(edge.from)) return { allowed: false, reason: `The canvas edge of ${edge.variable} references the unknown source step ${edge.from}.` };
8671
+ if (!reachable.has(edge.to)) return { allowed: false, reason: `The canvas edge of ${edge.variable} references the unknown target step ${edge.to}.` };
8672
+ if ((positionof.get(edge.from) ?? -1) >= (positionof.get(edge.to) ?? -1)) return { allowed: false, reason: `The canvas edge of ${edge.variable} runs backwards and would form a cycle.` };
8673
+ }
8674
+ return { allowed: true };
8675
+ }
8676
+ function runreviewgranted(record2) {
8677
+ if (record2.reviewstate === "pending") return { allowed: false, reason: "The workflow stays unreviewed: the import or rollback review must approve its expanded step list before any run." };
8678
+ return { allowed: true };
8679
+ }
8680
+ var overrideknobs = ["loopbound", "stepms", "runms", "waitms", "delaybase"];
8681
+ function validatesiteoverride(override) {
8682
+ if (typeof override.pattern !== "string" || !override.pattern.startsWith("https://") || !/[a-z0-9.-]+/i.test(override.pattern.slice(8))) return { allowed: false, reason: "The override pattern must be an https origin or a `*` subdomain glob of one." };
8683
+ if (!override.pattern.includes("*")) {
8684
+ try {
8685
+ if (new URL(override.pattern).origin !== override.pattern) return { allowed: false, reason: "The override pattern must be a bare https origin or a `*` subdomain glob, never a path." };
8686
+ } catch {
8687
+ return { allowed: false, reason: "The override pattern must parse as an https origin or a `*` subdomain glob of one." };
8688
+ }
8689
+ }
8690
+ for (const [knob, delta] of Object.entries(override.deltas)) {
8691
+ if (!overrideknobs.includes(knob)) return { allowed: false, reason: `The override knob ${knob} is not one of the reviewed knobs: ${overrideknobs.join(", ")}.` };
8692
+ if (typeof delta !== "number" || !Number.isFinite(delta) || delta <= 0) return { allowed: false, reason: `The override delta of ${knob} must be a positive user value with no code ceiling.` };
8693
+ }
8694
+ return { allowed: true };
8695
+ }
8696
+ function exportcontentreview(file) {
8697
+ const secretkeys = /(secret|token|apikey|api_key|password|authorization|credential)/i;
8698
+ const scan = (label, options) => {
8699
+ if (options === void 0) return void 0;
8700
+ let payload;
8701
+ try {
8702
+ payload = JSON.parse(options);
8703
+ } catch {
8704
+ return void 0;
8705
+ }
8706
+ const walk = (value, path) => {
8707
+ if (!value || typeof value !== "object") return void 0;
8708
+ for (const [key, entry] of Object.entries(value)) {
8709
+ if (secretkeys.test(key)) return { allowed: false, reason: `The export of ${label} carries the secret field ${path}${key} and secrets never leave the browser.` };
8710
+ const nested = walk(entry, `${path}${key}.`);
8711
+ if (nested !== void 0) return nested;
8712
+ }
8713
+ return void 0;
8714
+ };
8715
+ return walk(payload, "");
8716
+ };
8717
+ for (const step of file.workflow.steps) {
8718
+ const refusal = scan(`the step ${step.id}`, step.options);
8719
+ if (refusal !== void 0) return refusal;
8720
+ }
8721
+ for (const template of file.templates) {
8722
+ const refusal = scan(`the template ${template.name}`, template.step.options);
8723
+ if (refusal !== void 0) return refusal;
8724
+ }
8725
+ return { allowed: true };
8726
+ }
8727
+ function watchdogconfigvalid(config) {
8728
+ if (typeof config.enabled !== "boolean") return { allowed: false, reason: "The watchdog enabled flag must be a boolean." };
8729
+ if (typeof config.stallthreshold !== "number" || !Number.isFinite(config.stallthreshold) || config.stallthreshold <= 0) return { allowed: false, reason: "The watchdog stall threshold must be a positive number of milliseconds with no code ceiling." };
8730
+ if (!["retry", "pause", "cancel"].includes(config.action)) return { allowed: false, reason: "The watchdog recovery action must be retry, pause or cancel." };
8731
+ if (config.zombiewindow !== void 0 && (typeof config.zombiewindow !== "number" || !Number.isFinite(config.zombiewindow) || config.zombiewindow <= 0)) return { allowed: false, reason: "The watchdog zombie window, when configured, must be a positive number of milliseconds with no code ceiling." };
8732
+ return { allowed: true };
8733
+ }
7902
8734
 
7903
8735
  // version.ts
7904
- var packageversion = "1.1.51";
8736
+ var packageversion = "1.1.53";
7905
8737
 
7906
8738
  // types.ts
7907
8739
  var protocolversion = packageversion;
@@ -8140,6 +8972,25 @@ function parseproposal(value, origin, grants) {
8140
8972
  }
8141
8973
  if (step.kind === "runworkflow" && workflowoptions.reviewed !== true) throw new Error("Workflow runs without the explicit run review of the expanded step list are refused.");
8142
8974
  }
8975
+ if (istriggeraction(step.kind)) {
8976
+ let triggeroptions = {};
8977
+ try {
8978
+ triggeroptions = parseoptions(step);
8979
+ } catch {
8980
+ triggeroptions = {};
8981
+ }
8982
+ if (triggeroptions.reviewed !== true) throw new Error("Trigger rules without the explicit arm review of their match fields and bound workflow are refused.");
8983
+ for (const ruleorigin of triggerorigins(step)) {
8984
+ const granted = covered.some((pattern) => {
8985
+ try {
8986
+ return new URL(ruleorigin).origin === new URL(pattern).origin;
8987
+ } catch {
8988
+ return false;
8989
+ }
8990
+ });
8991
+ if (!granted) throw new Error(`The trigger on ${ruleorigin} stays outside the grants.`);
8992
+ }
8993
+ }
8143
8994
  const evaluation = validatestep(step, origin);
8144
8995
  if (!evaluation.allowed) throw new Error(evaluation.reason);
8145
8996
  const target = outboundtarget(step);
@@ -8282,7 +9133,7 @@ function requestbody(input) {
8282
9133
  return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
8283
9134
  }
8284
9135
  function outcomeresponse(input) {
8285
- return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome, ...input.resolvedtarget ? { resolvedtarget: input.resolvedtarget } : {}, ...input.capture ? { capture: input.capture } : {}, ...input.media ? { media: input.media } : {}, ...input.transport ? { transport: input.transport } : {}, ...input.network ? { network: input.network } : {}, ...input.control ? { control: input.control } : {}, ...input.timeline ? { timeline: input.timeline } : {}, ...input.cdp ? { cdp: input.cdp } : {}, ...input.profile ? { profile: input.profile } : {}, ...input.emulation ? { emulation: input.emulation } : {}, ...input.session ? { session: input.session } : {}, ...input.workflow ? { workflow: { runid: input.workflow.runid, state: input.workflow.state, ...input.workflow.dryrun === true ? { dryrun: true } : {}, produced: input.workflow.produced, consumed: input.workflow.consumed, ...input.workflow.timeout !== void 0 ? { timeout: input.workflow.timeout } : {}, ...input.workflow.retry !== void 0 ? { retry: input.workflow.retry } : {} } } : {} });
9136
+ 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 } : {} } } : {}, ...input.trigger ? { trigger: { ruleid: input.trigger.ruleid, kind: input.trigger.kind, enabled: input.trigger.enabled, ...input.trigger.nextfireat !== void 0 ? { nextfireat: input.trigger.nextfireat } : {} } } : {} });
8286
9137
  }
8287
9138
  function mapresponse(input) {
8288
9139
  return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
@@ -8434,8 +9285,789 @@ function sessionreport(input) {
8434
9285
  function workflowreport(input) {
8435
9286
  return { version: protocolversion, workflows: input.workflows, runs: input.runs, templates: input.templates, log: input.log ?? [], scopes: input.scopes ?? [], provenance: input.provenance ?? [], control: input.control ?? [] };
8436
9287
  }
9288
+ function triggerlist(input) {
9289
+ const names = new Map(input.workflows.map((record2) => [record2.id, record2.name]));
9290
+ const rules = input.rules.map((rule) => {
9291
+ const workflowname = names.get(rule.workflowid);
9292
+ return {
9293
+ id: rule.id,
9294
+ kind: rule.kind,
9295
+ workflowid: rule.workflowid,
9296
+ ...workflowname !== void 0 ? { workflowname } : {},
9297
+ label: rule.label,
9298
+ enabled: rule.state.enabled,
9299
+ ...rule.state.pausedat !== void 0 ? { paused: true } : {},
9300
+ cooldown: rule.state.cooldown,
9301
+ ...rule.state.lastfireat !== void 0 ? { lastfireat: rule.state.lastfireat } : {},
9302
+ ...rule.state.nextfireat !== void 0 ? { nextfireat: rule.state.nextfireat } : {},
9303
+ fires: rule.stats.fires,
9304
+ launches: rule.stats.launches,
9305
+ suppressions: rule.stats.suppressions,
9306
+ summary: triggersummaryof(rule)
9307
+ };
9308
+ });
9309
+ return { version: protocolversion, rules, queued: (input.queue ?? []).length };
9310
+ }
9311
+ function triggersummaryof(rule) {
9312
+ const summary = { kind: rule.kind, workflowid: rule.workflowid };
9313
+ if (rule.origins !== void 0) summary.origins = rule.origins;
9314
+ if (rule.pattern !== void 0) summary.pattern = rule.pattern;
9315
+ if (rule.title !== void 0) summary.title = rule.title;
9316
+ if (rule.command !== void 0) summary.command = rule.command;
9317
+ if (rule.key !== void 0) summary.key = rule.key;
9318
+ if (rule.cron !== void 0) summary.cron = rule.cron;
9319
+ if (rule.timezone !== void 0) summary.timezone = rule.timezone;
9320
+ if (rule.period !== void 0) summary.period = rule.period;
9321
+ if (rule.jitter !== void 0) summary.jitter = rule.jitter;
9322
+ if (rule.urls !== void 0) summary.urls = rule.urls;
9323
+ if (rule.events !== void 0) summary.events = rule.events;
9324
+ if (rule.schema !== void 0) summary.fields = rule.schema.length;
9325
+ return summary;
9326
+ }
9327
+ function triggerfired(input) {
9328
+ return { version: protocolversion, triggerfired: { fireid: input.fire.id, ruleid: input.fire.ruleid, workflowid: input.workflowid, at: input.fire.at, cause: input.fire.cause, ...input.fire.url !== void 0 ? { url: input.fire.url } : {}, ...input.fire.title !== void 0 ? { title: input.fire.title } : {}, ...input.runid !== void 0 ? { runid: input.runid } : {} } };
9329
+ }
9330
+ function manualrunpreview(input) {
9331
+ return { version: protocolversion, manualrun: input.preview, ...input.workflowname !== void 0 ? { workflowname: input.workflowname } : {} };
9332
+ }
9333
+ var workflowfileversion = 1;
9334
+ function editorstate(input) {
9335
+ const editor = { versions: input.versions, diffs: input.diffs ?? [], history: input.history, breakpoints: input.breakpoints ?? [], overrides: input.overrides, imports: input.imports, backgroundruns: input.backgroundruns ?? {}, watchdog: { ...input.watchdog.config !== void 0 ? { config: input.watchdog.config } : {}, events: input.watchdog.events } };
9336
+ return { version: protocolversion, editor, ...input.model !== void 0 ? { model: input.model } : {} };
9337
+ }
9338
+ function runhistoryquery(value) {
9339
+ if (value === void 0 || value === null) return {};
9340
+ const candidate = record(value);
9341
+ const query = {};
9342
+ if (candidate.workflowid !== void 0) {
9343
+ if (typeof candidate.workflowid !== "string" || !candidate.workflowid.trim()) throw new Error("The run history workflow filter must be a non-empty string.");
9344
+ query.workflowid = candidate.workflowid;
9345
+ }
9346
+ if (candidate.outcome !== void 0) {
9347
+ if (typeof candidate.outcome !== "string" || !candidate.outcome.trim()) throw new Error("The run history outcome filter must be a non-empty string.");
9348
+ query.outcome = candidate.outcome;
9349
+ }
9350
+ if (candidate.since !== void 0) {
9351
+ if (typeof candidate.since !== "number" || !Number.isFinite(candidate.since)) throw new Error("The run history time floor must be a finite timestamp.");
9352
+ query.since = candidate.since;
9353
+ }
9354
+ if (candidate.limit !== void 0) {
9355
+ if (typeof candidate.limit !== "number" || !Number.isInteger(candidate.limit) || candidate.limit < 1) throw new Error("The run history entry count must be a positive integer with no code ceiling.");
9356
+ query.limit = candidate.limit;
9357
+ }
9358
+ return query;
9359
+ }
9360
+ function runhistoryreport(input) {
9361
+ return { version: protocolversion, entries: input.entries, query: input.query ?? {} };
9362
+ }
9363
+
9364
+ // workfloweditor.ts
9365
+ var palettecategories = ["actions", "controlflow", "waits", "variables", "triggers"];
9366
+ var palettenodes = [
9367
+ { kind: "click", label: "Click an element", category: "actions", description: "Clicks the reviewed selector target." },
9368
+ { kind: "type", label: "Type text", category: "actions", description: "Types the reviewed text into the target field." },
9369
+ { kind: "navigate", label: "Navigate", category: "actions", description: "Navigates the tab to the reviewed url." },
9370
+ { kind: "readtext", label: "Read text", category: "actions", description: "Reads the text of the target element." },
9371
+ { kind: "scrapetable", label: "Scrape a table", category: "actions", description: "Extracts the reviewed table into a dataset." },
9372
+ { kind: "fillform", label: "Fill a form", category: "actions", description: "Fills the reviewed form fields from a saved profile." },
9373
+ { kind: "querytabs", label: "Query tabs", category: "actions", description: "Lists the tabs matching the reviewed query." },
9374
+ { kind: "fetchurl", label: "Fetch a url", category: "actions", description: "Fetches the reviewed endpoint behind the call consent." },
9375
+ { kind: "condition", label: "Condition", category: "controlflow", description: "Evaluates one reviewed boolean expression with no page side effect." },
9376
+ { kind: "branch", label: "Branch", category: "controlflow", description: "Chooses one reviewed path by page state with a mandatory else path." },
9377
+ { kind: "loop", label: "Loop a list", category: "controlflow", description: "Iterates a list variable binding the item and index per pass." },
9378
+ { kind: "repeatuntil", label: "Repeat until", category: "controlflow", description: "Reruns the body until the convergence expression holds." },
9379
+ { kind: "whileloop", label: "While loop", category: "controlflow", description: "Loops while the condition holds inside the reviewed bound." },
9380
+ { kind: "foreach", label: "For each element", category: "controlflow", description: "Iterates the elements of the reviewed selector." },
9381
+ { kind: "parallel", label: "Parallel branches", category: "controlflow", description: "Runs branches concurrently and joins them under the reviewed strategy." },
9382
+ { kind: "trycatch", label: "Try catch", category: "controlflow", description: "Wraps fragile steps with a catch handler, retries and timeouts." },
9383
+ { kind: "delay", label: "Delay", category: "waits", description: "Sleeps the reviewed base inside the jitter window." },
9384
+ { kind: "waitelement", label: "Wait for element", category: "waits", description: "Polls the reviewed selector until appearance or timeout." },
9385
+ { kind: "wait", label: "Wait", category: "waits", description: "Waits the reviewed duration." },
9386
+ { kind: "waitfor", label: "Wait for target", category: "waits", description: "Waits until the reviewed target exists." },
9387
+ { kind: "waittext", label: "Wait for text", category: "waits", description: "Waits until the reviewed text appears." },
9388
+ { kind: "waitquiet", label: "Wait for quiet", category: "waits", description: "Waits until the page stops mutating." },
9389
+ { kind: "waitload", label: "Wait for load", category: "waits", description: "Waits until the navigation settles." },
9390
+ { kind: "compute", label: "Compute", category: "variables", description: "Evaluates one reviewed expression into the result variable." },
9391
+ { kind: "extractvars", label: "Extract variables", category: "variables", description: "Applies the reviewed regex and stores the named captures." },
9392
+ { kind: "savetemplate", label: "Save template", category: "variables", description: "Shares the reviewed step as a reusable template." },
9393
+ { kind: "visitrule", label: "Visit rule", category: "triggers", description: "Fires on navigations to the reviewed origins." },
9394
+ { kind: "urlrule", label: "Url rule", category: "triggers", description: "Fires when the url matches the reviewed glob pattern." },
9395
+ { kind: "cronrule", label: "Cron rule", category: "triggers", description: "Fires on the reviewed five field cron schedule." },
9396
+ { kind: "intervalrule", label: "Interval rule", category: "triggers", description: "Fires every reviewed period with the jitter spread." },
9397
+ { kind: "webhookrule", label: "Webhook rule", category: "triggers", description: "Fires on a secret verified webhook delivery." },
9398
+ { kind: "eventrule", label: "Event rule", category: "triggers", description: "Fires on the observed page events of the catalog." }
9399
+ ];
9400
+ var optionschemas = {
9401
+ delay: [{ name: "base", kind: "number", required: true }, { name: "jitter", kind: "number" }],
9402
+ waitelement: [{ name: "timeout", kind: "number" }, { name: "poll", kind: "number" }],
9403
+ compute: [{ name: "expression", kind: "string", required: true }],
9404
+ extractvars: [{ name: "rule", kind: "string", required: true }],
9405
+ composeworkflow: [{ name: "name", kind: "string", required: true }, { name: "version", kind: "number" }],
9406
+ runworkflow: [{ name: "workflowid", kind: "string", required: true }, { name: "reviewed", kind: "boolean", required: true }, { name: "variables", kind: "string" }, { name: "background", kind: "boolean" }],
9407
+ dryrun: [{ name: "workflowid", kind: "string", required: true }],
9408
+ loop: [{ name: "loop", kind: "string", required: true }],
9409
+ repeatuntil: [{ name: "repeatuntil", kind: "string", required: true }],
9410
+ whileloop: [{ name: "whileloop", kind: "string", required: true }],
9411
+ foreach: [{ name: "foreach", kind: "string", required: true }],
9412
+ parallel: [{ name: "parallel", kind: "string", required: true }],
9413
+ trycatch: [{ name: "trycatch", kind: "string", required: true }]
9414
+ };
9415
+ function stepcategory(kind) {
9416
+ if (triggerkinds.includes(kind)) return "triggers";
9417
+ if (controlflowkinds.includes(kind)) return "controlflow";
9418
+ if (kind.startsWith("wait") || kind === "spawait" || kind === "delay") return "waits";
9419
+ if (kind === "compute" || kind === "extractvars" || kind === "savetemplate") return "variables";
9420
+ return "actions";
9421
+ }
9422
+ function buildsteplibrary(kinds) {
9423
+ return [...new Set(kinds)].sort().map((kind) => ({ kind, category: stepcategory(kind), optionschema: optionschemas[kind] ?? [] }));
9424
+ }
9425
+ var noderowheight = 96;
9426
+ var blockcolumnwidth = 280;
9427
+ var canvasoriginx = 40;
9428
+ function snapshotof(model) {
9429
+ const { undo, redo, dirty, ...rest } = model;
9430
+ void undo;
9431
+ void redo;
9432
+ void dirty;
9433
+ return { ...rest, dirty: true };
9434
+ }
9435
+ function withundo(model, next) {
9436
+ const undo = [...model.undo ?? [], snapshotof(model)];
9437
+ const { redo, ...rest } = next;
9438
+ void redo;
9439
+ return { ...rest, dirty: true, undo };
9440
+ }
9441
+ function nodeidof(node) {
9442
+ return node.id ?? (node.step !== void 0 ? node.step.id : node.invocation !== void 0 ? node.invocation.block : "");
9443
+ }
9444
+ function layoutsizeof(nodes) {
9445
+ const width = Math.max(640, ...nodes.map((node) => node.x + blockcolumnwidth)) + 40;
9446
+ const height = Math.max(480, ...nodes.map((node) => node.y + noderowheight)) + 40;
9447
+ return { width, height };
9448
+ }
9449
+ function loadworkflow(record2, layout) {
9450
+ const blocks = record2.blocks.map((block) => ({ ...block, steps: block.steps.map((entry) => ({ ...entry })) }));
9451
+ const blockcolumn = (blockname) => {
9452
+ const index2 = blocks.findIndex((block) => block.name === blockname);
9453
+ return index2 < 0 ? canvasoriginx : canvasoriginx + (index2 + 1) * blockcolumnwidth;
9454
+ };
9455
+ const invocationcount = /* @__PURE__ */ new Map();
9456
+ const nodes = [];
9457
+ const edges = [];
9458
+ let index = 0;
9459
+ while (index < record2.steps.length) {
9460
+ const step = record2.steps[index];
9461
+ for (const binding of step.bindings ?? []) edges.push({ from: binding.stepid, to: step.id, variable: binding.variable, kind: binding.kind, ...binding.path !== void 0 ? { path: binding.path } : {} });
9462
+ if (step.block === void 0) {
9463
+ const { bindings, block, params: params2, ...rest } = step;
9464
+ void bindings;
9465
+ void block;
9466
+ void params2;
9467
+ nodes.push({ step: { ...rest }, x: canvasoriginx, y: 60 + nodes.length * noderowheight });
9468
+ index += 1;
9469
+ continue;
9470
+ }
9471
+ const blockname = step.block;
9472
+ let end = index;
9473
+ while (end < record2.steps.length && record2.steps[end].block === blockname) end += 1;
9474
+ const region = record2.steps.slice(index, end);
9475
+ const count = (invocationcount.get(blockname) ?? 0) + 1;
9476
+ invocationcount.set(blockname, count);
9477
+ const params = region.flatMap((entry) => entry.params ?? []);
9478
+ nodes.push({ id: count === 1 ? blockname : `${blockname}${count}`, invocation: { block: blockname, label: blockname, ...params.length > 0 ? { params: params.map((param) => ({ ...param })) } : {} }, x: blockcolumn(blockname), y: 60 + nodes.length * noderowheight });
9479
+ index = end;
9480
+ }
9481
+ const size = layouttypeof(nodes, layout);
9482
+ const model = { workflowid: record2.id, name: record2.name, version: record2.version, origins: [...record2.origins], nodes, edges, blocks, layout: size, minimap: emptyminimap(), dirty: false };
9483
+ return { ...model, minimap: renderminimap(model).minimap };
9484
+ }
9485
+ function layouttypeof(nodes, layout) {
9486
+ const size = layoutsizeof(nodes);
9487
+ if (!layout) return { width: size.width, height: size.height, viewportx: 0, viewporty: 0, zoom: 1 };
9488
+ return { width: Math.max(size.width, layout.width), height: Math.max(size.height, layout.height), viewportx: layout.viewportx, viewporty: layout.viewporty, zoom: layout.zoom };
9489
+ }
9490
+ function emptyminimap() {
9491
+ return { width: 160, height: 100, scale: 0, zoom: 1, viewport: { x: 0, y: 0, width: 0, height: 0 } };
9492
+ }
9493
+ function saveworkflow(model, input) {
9494
+ if (typeof model.name !== "string" || !model.name.trim()) throw new Error("The workflow name must be a non-empty string.");
9495
+ if (typeof model.version !== "number" || !Number.isInteger(model.version) || model.version < 1) throw new Error("The workflow version must be a positive integer.");
9496
+ if (!Array.isArray(model.origins) || model.origins.length === 0) throw new Error("A workflow needs at least one granted HTTPS origin.");
9497
+ const ids = /* @__PURE__ */ new Set();
9498
+ for (const node of model.nodes) {
9499
+ if (node.step === void 0 === (node.invocation === void 0)) throw new Error("Every canvas node must be exactly one workflow step or one block invocation.");
9500
+ const id = nodeidof(node);
9501
+ if (!id || ids.has(id)) throw new Error(`The canvas node id ${id || "(empty)"} must be unique.`);
9502
+ ids.add(id);
9503
+ }
9504
+ const positionof = /* @__PURE__ */ new Map();
9505
+ let position = 0;
9506
+ for (const node of model.nodes) {
9507
+ if (node.step !== void 0) {
9508
+ positionof.set(node.step.id, position);
9509
+ position += 1;
9510
+ continue;
9511
+ }
9512
+ const block = model.blocks.find((entry) => entry.name === node.invocation?.block);
9513
+ if (!block) throw new Error(`The block ${node.invocation?.block ?? ""} of the canvas has no definition.`);
9514
+ const walk = (entries2) => {
9515
+ for (const entry of entries2) {
9516
+ if ("kind" in entry && "label" in entry && !("block" in entry)) {
9517
+ positionof.set(entry.id, position);
9518
+ position += 1;
9519
+ continue;
9520
+ }
9521
+ const nested = model.blocks.find((candidate) => candidate.name === entry.block);
9522
+ if (!nested) throw new Error(`The block ${entry.block} of the canvas has no definition.`);
9523
+ walk(nested.steps);
9524
+ }
9525
+ };
9526
+ walk(block.steps);
9527
+ }
9528
+ for (const edge of model.edges) {
9529
+ if (!positionof.has(edge.from)) throw new Error(`The edge of ${edge.variable} references the unknown source step ${edge.from}.`);
9530
+ if (!positionof.has(edge.to)) throw new Error(`The edge of ${edge.variable} references the unknown target step ${edge.to}.`);
9531
+ if (positionof.get(edge.from) >= positionof.get(edge.to)) throw new Error(`The edge of ${edge.variable} runs backwards from ${edge.from} into ${edge.to} and would form a cycle.`);
9532
+ }
9533
+ const bindingsof = (stepid) => model.edges.filter((edge) => edge.to === stepid).map((edge) => ({ variable: edge.variable, kind: edge.kind, stepid: edge.from, ...edge.path !== void 0 ? { path: edge.path } : {} }));
9534
+ const entries = [];
9535
+ const attached = /* @__PURE__ */ new Map();
9536
+ for (const node of model.nodes) {
9537
+ if (node.invocation !== void 0) {
9538
+ entries.push({ ...node.invocation });
9539
+ continue;
9540
+ }
9541
+ const step = node.step;
9542
+ const bindings = bindingsof(step.id);
9543
+ const { block, params, ...rest } = { ...step, ...bindings.length > 0 ? { bindings } : {} };
9544
+ void params;
9545
+ const carried = rest;
9546
+ if (block !== void 0) {
9547
+ if (!model.blocks.some((candidate) => candidate.name === block)) throw new Error(`The step ${step.id} attaches to the unknown block ${block}.`);
9548
+ const list = attached.get(block) ?? [];
9549
+ list.push(carried);
9550
+ attached.set(block, list);
9551
+ continue;
9552
+ }
9553
+ entries.push(carried);
9554
+ }
9555
+ const blocks = model.blocks.map((block) => {
9556
+ const snapped = attached.get(block.name) ?? [];
9557
+ const snappedids = new Set(snapped.map((step) => step.id));
9558
+ const carried = [];
9559
+ for (const entry of block.steps) {
9560
+ if ("kind" in entry && "label" in entry && !("block" in entry) && snappedids.has(entry.id)) continue;
9561
+ carried.push(entry);
9562
+ }
9563
+ const steps = [...carried, ...snapped];
9564
+ const withbindings = [];
9565
+ for (const entry of steps) {
9566
+ if (!("kind" in entry && "label" in entry && !("block" in entry))) {
9567
+ withbindings.push(entry);
9568
+ continue;
9569
+ }
9570
+ const bindings = bindingsof(entry.id);
9571
+ const { block: inner, params, ...rest } = { ...entry, ...bindings.length > 0 ? { bindings } : {} };
9572
+ void inner;
9573
+ void params;
9574
+ withbindings.push(rest);
9575
+ }
9576
+ return { ...block, steps: withbindings };
9577
+ });
9578
+ const composed = composeworkflow({ id: model.workflowid, name: model.name, version: model.version, origins: [...model.origins], steps: entries, blocks: blocks.map((block) => ({ ...block })), now: input.now, ...input.kindallowed !== void 0 ? { kindallowed: input.kindallowed } : {}, ...input.riskof !== void 0 ? { riskof: input.riskof } : {} });
9579
+ const checked = validateworkflow(composed, input.kindallowed !== void 0 ? { kindallowed: input.kindallowed } : {});
9580
+ if (!checked.allowed) throw new Error(checked.reason ?? "The canvas model failed the workflow grammar.");
9581
+ return composed;
9582
+ }
9583
+ function snapnode(model, nodeid, x, y, grid = 20) {
9584
+ if (!Number.isFinite(grid) || grid <= 0) throw new Error("The snap grid must be a positive number.");
9585
+ const index = model.nodes.findIndex((node2) => nodeidof(node2) === nodeid);
9586
+ if (index < 0) throw new Error(`No canvas node matches ${nodeid}.`);
9587
+ const node = model.nodes[index];
9588
+ if (node.step === void 0) throw new Error("A block invocation node attaches through its own definition, not through snapping.");
9589
+ const snappedx = Math.round(x / grid) * grid;
9590
+ const snappedy = Math.round(y / grid) * grid;
9591
+ let attached;
9592
+ for (const [blockindex, block] of model.blocks.entries()) {
9593
+ const columnx = canvasoriginx + (blockindex + 1) * blockcolumnwidth;
9594
+ if (Math.abs(snappedx - columnx) <= blockcolumnwidth / 2) attached = block.name;
9595
+ }
9596
+ const { block: priorblock, ...rest } = node.step;
9597
+ void priorblock;
9598
+ const step = { ...rest, ...attached !== void 0 ? { block: attached } : {} };
9599
+ const nodes = model.nodes.map((candidate, position) => position === index ? { step, x: snappedx, y: snappedy } : candidate);
9600
+ const size = layouttypeof(nodes, model.layout);
9601
+ const next = { ...model, nodes, layout: size };
9602
+ return withundo(model, { ...next, minimap: renderminimap(next).minimap });
9603
+ }
9604
+ function reordersteps(model, nodeid, index) {
9605
+ const current = model.nodes.findIndex((node) => nodeidof(node) === nodeid);
9606
+ if (current < 0) throw new Error(`No canvas node matches ${nodeid}.`);
9607
+ if (!Number.isInteger(index) || index < 0 || index > model.nodes.length - 1) throw new Error("The reorder index must address an existing position of the canvas list.");
9608
+ const nodes = [...model.nodes];
9609
+ const [moved] = nodes.splice(current, 1);
9610
+ if (!moved) throw new Error("The reordered canvas node vanished.");
9611
+ nodes.splice(index, 0, moved);
9612
+ const next = { ...model, nodes };
9613
+ return withundo(model, { ...next, minimap: renderminimap(next).minimap });
9614
+ }
9615
+ function groupselect(model, nodeids, blockname) {
9616
+ if (!/^[a-z][a-z0-9]*$/.test(blockname)) throw new Error("The block name must be a unique lowercase word.");
9617
+ if (model.blocks.some((block) => block.name === blockname)) throw new Error(`The block name ${blockname} already exists on the canvas.`);
9618
+ const selected = nodeids.map((id) => {
9619
+ const node = model.nodes.find((candidate) => nodeidof(candidate) === id);
9620
+ if (!node || node.step === void 0) throw new Error(`The grouping selection must address step nodes; ${id} is not one.`);
9621
+ return node;
9622
+ });
9623
+ if (selected.length === 0) throw new Error("The grouping selection needs at least one step node.");
9624
+ const steps = selected.map((node) => node.step);
9625
+ const blocks = [...model.blocks, { name: blockname, label: blockname, steps: steps.map((step) => ({ ...step })) }];
9626
+ const firstindex = model.nodes.findIndex((node) => nodeidof(node) === nodeids[0]);
9627
+ const invocationnode = { id: blockname, invocation: { block: blockname, label: blockname }, x: selected[0].x, y: selected[0].y };
9628
+ const nodes = [];
9629
+ model.nodes.forEach((node, index) => {
9630
+ if (nodeids.includes(nodeidof(node))) {
9631
+ if (index === firstindex) nodes.push(invocationnode);
9632
+ return;
9633
+ }
9634
+ nodes.push(node);
9635
+ });
9636
+ const next = { ...model, nodes, blocks };
9637
+ return withundo(model, { ...next, minimap: renderminimap(next).minimap });
9638
+ }
9639
+ function expandtemplate(model, template, params = [], index) {
9640
+ const parsed = steptemplateof(template);
9641
+ if (!parsed) throw new Error("The template does not carry one reviewed workflow step.");
9642
+ let id = parsed.step.id;
9643
+ let suffix = 2;
9644
+ const taken = new Set(model.nodes.map((node) => nodeidof(node)));
9645
+ while (taken.has(id)) {
9646
+ id = `${parsed.step.id}${suffix}`;
9647
+ suffix += 1;
9648
+ }
9649
+ const step = { ...parsed.step, id, ...params.length > 0 ? { params: params.map((param) => ({ ...param })) } : {} };
9650
+ const position = index !== void 0 && Number.isInteger(index) && index >= 0 && index <= model.nodes.length ? index : model.nodes.length;
9651
+ const nodes = [...model.nodes.slice(0, position), { step, x: canvasoriginx, y: 60 + position * noderowheight }, ...model.nodes.slice(position)];
9652
+ const next = { ...model, nodes };
9653
+ return withundo(model, { ...next, minimap: renderminimap(next).minimap });
9654
+ }
9655
+ function addnode(model, step, index) {
9656
+ const normalized = workflowstepof(step);
9657
+ if (!normalized) throw new Error("The canvas insertion needs one reviewed workflow step.");
9658
+ let id = normalized.id;
9659
+ let suffix = 2;
9660
+ const taken = new Set(model.nodes.map((node) => nodeidof(node)));
9661
+ while (taken.has(id)) {
9662
+ id = `${normalized.id}${suffix}`;
9663
+ suffix += 1;
9664
+ }
9665
+ const position = index !== void 0 && Number.isInteger(index) && index >= 0 && index <= model.nodes.length ? index : model.nodes.length;
9666
+ const nodes = [...model.nodes.slice(0, position), { step: { ...normalized, id }, x: canvasoriginx, y: 60 + position * noderowheight }, ...model.nodes.slice(position)];
9667
+ const next = { ...model, nodes };
9668
+ return withundo(model, { ...next, minimap: renderminimap(next).minimap });
9669
+ }
9670
+ function editstep(model, step) {
9671
+ const normalized = workflowstepof(step);
9672
+ if (!normalized) throw new Error("The step inspector edit needs one reviewed workflow step.");
9673
+ const index = model.nodes.findIndex((node2) => node2.step?.id === normalized.id);
9674
+ if (index < 0) throw new Error(`No canvas step matches ${normalized.id}.`);
9675
+ const node = model.nodes[index];
9676
+ const nodes = model.nodes.map((candidate, position) => position === index ? { step: { ...normalized, ...node.step?.block !== void 0 ? { block: node.step.block } : {}, ...node.step?.breakpoint === true ? { breakpoint: true } : {} }, x: node.x, y: node.y } : candidate);
9677
+ const next = { ...model, nodes };
9678
+ return withundo(model, { ...next, minimap: renderminimap(next).minimap });
9679
+ }
9680
+ function renderminimap(model, width = 160, height = 100) {
9681
+ if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) throw new Error("The mini map size must be positive.");
9682
+ const canvaswidth = Math.max(1, model.layout.width);
9683
+ const canvasheight = Math.max(1, model.layout.height);
9684
+ const scale = Math.min(width / canvaswidth, height / canvasheight);
9685
+ const zoom = model.layout.zoom > 0 ? model.layout.zoom : 1;
9686
+ const visiblewidth = canvaswidth / zoom;
9687
+ const visibleheight = canvasheight / zoom;
9688
+ const viewport = {
9689
+ x: Math.max(0, Math.min(model.layout.viewportx, canvaswidth)) * scale,
9690
+ y: Math.max(0, Math.min(model.layout.viewporty, canvasheight)) * scale,
9691
+ width: visiblewidth * scale,
9692
+ height: visibleheight * scale
9693
+ };
9694
+ const nodes = model.nodes.map((node) => ({ id: nodeidof(node), x: node.x * scale, y: node.y * scale }));
9695
+ return { minimap: { width, height, scale, zoom, viewport }, nodes };
9696
+ }
9697
+ function minimapfocus(model, x, y, width = 160, height = 100) {
9698
+ const projection = renderminimap(model, width, height);
9699
+ if (projection.minimap.scale <= 0) return model;
9700
+ const canvasx = x / projection.minimap.scale;
9701
+ const canvasy = y / projection.minimap.scale;
9702
+ const zoom = model.layout.zoom > 0 ? model.layout.zoom : 1;
9703
+ const visiblewidth = model.layout.width / zoom;
9704
+ const visibleheight = model.layout.height / zoom;
9705
+ const viewportx = Math.max(0, Math.min(canvasx - visiblewidth / 2, Math.max(0, model.layout.width - visiblewidth)));
9706
+ const viewporty = Math.max(0, Math.min(canvasy - visibleheight / 2, Math.max(0, model.layout.height - visibleheight)));
9707
+ const next = { ...model, layout: { ...model.layout, viewportx, viewporty } };
9708
+ return { ...next, minimap: renderminimap(next).minimap };
9709
+ }
9710
+ function zoomcanvas(model, zoom) {
9711
+ if (!Number.isFinite(zoom) || zoom <= 0) throw new Error("The canvas zoom must be a positive number with no code ceiling.");
9712
+ const next = { ...model, layout: { ...model.layout, zoom } };
9713
+ const labelscale = zoom < 1 ? 1 / zoom : 1;
9714
+ return { model: { ...next, minimap: renderminimap(next).minimap }, labelscale };
9715
+ }
9716
+ function searchsteps(model, query) {
9717
+ const needle = query.trim().toLowerCase();
9718
+ if (!needle) return [];
9719
+ const results = [];
9720
+ for (const node of model.nodes) {
9721
+ if (node.step === void 0) continue;
9722
+ const matched = [];
9723
+ if (node.step.label.toLowerCase().includes(needle)) matched.push("label");
9724
+ if (node.step.kind.toLowerCase().includes(needle)) matched.push("kind");
9725
+ const variables = [
9726
+ ...model.edges.filter((edge) => edge.to === node.step?.id || edge.from === node.step?.id).map((edge) => edge.variable),
9727
+ ...node.step.expression !== void 0 ? [node.step.expression.result] : [],
9728
+ ...node.step.extract !== void 0 ? node.step.extract.groups : []
9729
+ ];
9730
+ if (variables.some((name) => name.toLowerCase().includes(needle))) matched.push("variable");
9731
+ if (matched.length > 0) results.push({ id: node.step.id, label: node.step.label, kind: node.step.kind, matched });
9732
+ }
9733
+ return results;
9734
+ }
9735
+ function markbreakpoint(model, stepid) {
9736
+ const toggle = (step) => {
9737
+ const { breakpoint, ...rest } = step;
9738
+ void breakpoint;
9739
+ return breakpoint === true ? rest : { ...rest, breakpoint: true };
9740
+ };
9741
+ const index = model.nodes.findIndex((node) => node.step?.id === stepid);
9742
+ if (index >= 0) {
9743
+ const node = model.nodes[index];
9744
+ const step = node.step;
9745
+ const nodes = model.nodes.map((candidate, position) => position === index ? { step: toggle(step), x: candidate.x, y: candidate.y } : candidate);
9746
+ const next2 = { ...model, nodes };
9747
+ return withundo(model, { ...next2, minimap: renderminimap(next2).minimap });
9748
+ }
9749
+ const blocks = model.blocks.map((block) => {
9750
+ const stepindex = block.steps.findIndex((entry) => "kind" in entry && "label" in entry && !("block" in entry) && entry.id === stepid);
9751
+ if (stepindex < 0) return block;
9752
+ const steps = block.steps.map((entry, position) => position === stepindex ? toggle(entry) : entry);
9753
+ return { ...block, steps };
9754
+ });
9755
+ if (blocks.every((block, position) => block === model.blocks[position])) throw new Error(`No canvas step matches ${stepid}.`);
9756
+ const next = { ...model, blocks };
9757
+ return withundo(model, next);
9758
+ }
9759
+ function runtobreakpoint(input) {
9760
+ const cursor = input.cursor !== void 0 && Number.isInteger(input.cursor) && input.cursor >= 0 ? input.cursor : 0;
9761
+ const marked = new Set(input.breakpoints);
9762
+ for (let index = cursor; index < input.record.steps.length; index += 1) {
9763
+ const step = input.record.steps[index];
9764
+ if (step.breakpoint === true || marked.has(step.id)) {
9765
+ return { until: index, pausat: step.id, remaining: input.record.steps.length - index };
9766
+ }
9767
+ }
9768
+ return { until: input.record.steps.length, pausat: void 0, remaining: 0 };
9769
+ }
9770
+ function diffversions(from, to, now) {
9771
+ const fromsteps = new Map(from.steps.map((step) => [step.id, step]));
9772
+ const tosteps = new Map(to.steps.map((step) => [step.id, step]));
9773
+ const added = [];
9774
+ const removed = [];
9775
+ const changed = [];
9776
+ for (const step of to.steps) {
9777
+ const prior = fromsteps.get(step.id);
9778
+ if (!prior) {
9779
+ added.push({ stepid: step.id, kind: step.kind, label: step.label });
9780
+ continue;
9781
+ }
9782
+ const changes = [];
9783
+ if (prior.label !== step.label) changes.push("label");
9784
+ if (prior.kind !== step.kind) changes.push("kind");
9785
+ if (prior.target !== step.target) changes.push("target");
9786
+ if (prior.value !== step.value) changes.push("value");
9787
+ if (prior.options !== step.options) changes.push("options");
9788
+ if (JSON.stringify(prior.expression) !== JSON.stringify(step.expression)) changes.push("expression");
9789
+ if (JSON.stringify(prior.extract) !== JSON.stringify(step.extract)) changes.push("extract");
9790
+ if (JSON.stringify(prior.bindings) !== JSON.stringify(step.bindings)) changes.push("bindings");
9791
+ if (changes.length > 0) changed.push({ stepid: step.id, kind: step.kind, label: step.label, changes });
9792
+ }
9793
+ for (const step of from.steps) {
9794
+ if (!tosteps.has(step.id)) removed.push({ stepid: step.id, kind: step.kind, label: step.label });
9795
+ }
9796
+ return { workflowid: to.id, from: from.version, to: to.version, added, removed, changed, at: now };
9797
+ }
9798
+ function exportworkflow(record2, format, note, now) {
9799
+ const file = { format: workflowfileversion, exportedat: now ?? Date.now(), workflow: record2, ...note !== void 0 && note.trim() !== "" ? { note } : {}, templates: [] };
9800
+ return { format, contents: serializefile(file, format), file };
9801
+ }
9802
+ function shareworkflow(record2, templates, format, note, now) {
9803
+ const file = { format: workflowfileversion, exportedat: now ?? Date.now(), workflow: record2, ...note !== void 0 && note.trim() !== "" ? { note } : {}, templates: templates.map((template) => ({ ...template })) };
9804
+ return { format, contents: serializefile(file, format), file };
9805
+ }
9806
+ function importworkflow(input) {
9807
+ const format = input.format ?? (input.contents.trimStart().startsWith("{") ? "json" : "yaml");
9808
+ const parsed = parsefile(input.contents, format);
9809
+ if (parsed.format !== workflowfileversion) throw new Error(`The workflow file format ${String(parsed.format)} is not the reviewed format ${workflowfileversion}.`);
9810
+ const candidate = parsed.workflow;
9811
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) throw new Error("The workflow file carries no workflow record.");
9812
+ const fields = candidate;
9813
+ const stepsvalue = fields.steps;
9814
+ if (!Array.isArray(stepsvalue) || stepsvalue.length === 0) throw new Error("An imported workflow needs at least one step.");
9815
+ const steps = [];
9816
+ for (const entry of stepsvalue) {
9817
+ const step = workflowstepof(entry);
9818
+ if (step) {
9819
+ steps.push(step);
9820
+ continue;
9821
+ }
9822
+ throw new Error("Every imported workflow entry must be a reviewed step.");
9823
+ }
9824
+ const composed = composeworkflow({
9825
+ id: typeof fields.id === "string" && fields.id.trim() !== "" ? fields.id : crypto.randomUUID(),
9826
+ name: typeof fields.name === "string" ? fields.name : "",
9827
+ version: typeof fields.version === "number" ? fields.version : 1,
9828
+ origins: Array.isArray(fields.origins) ? fields.origins.filter((origin) => typeof origin === "string") : [],
9829
+ steps,
9830
+ now: input.now ?? Date.now(),
9831
+ ...input.kindallowed !== void 0 ? { kindallowed: input.kindallowed } : {},
9832
+ ...input.riskof !== void 0 ? { riskof: input.riskof } : {}
9833
+ });
9834
+ const templatesvalue = parsed.templates;
9835
+ if (templatesvalue !== void 0 && !Array.isArray(templatesvalue)) throw new Error("The packed templates of the workflow file must be a list.");
9836
+ const templates = [];
9837
+ for (const entry of templatesvalue ?? []) {
9838
+ const template = steptemplateof(entry);
9839
+ if (!template) throw new Error("A packed template of the workflow file does not carry one reviewed step.");
9840
+ templates.push(template);
9841
+ }
9842
+ const record2 = { ...composed, reviewstate: "pending" };
9843
+ return { record: record2, templates, file: { ...parsed, workflow: record2 } };
9844
+ }
9845
+ function bindparam(model, blockname, param) {
9846
+ if (!/^[a-z][a-z0-9]*$/.test(param.name)) throw new Error("The nested parameter name must be a lowercase word.");
9847
+ const index = model.nodes.findIndex((node2) => node2.invocation?.block === blockname);
9848
+ if (index < 0) throw new Error(`No block invocation of ${blockname} sits on the canvas.`);
9849
+ const node = model.nodes[index];
9850
+ const invocation = node.invocation;
9851
+ const params = [...(invocation.params ?? []).filter((existing) => existing.name !== param.name), { ...param }];
9852
+ const nodes = model.nodes.map((candidate, position) => position === index ? { invocation: { ...invocation, params }, x: candidate.x, y: candidate.y } : candidate);
9853
+ const next = { ...model, nodes };
9854
+ return withundo(model, { ...next, minimap: renderminimap(next).minimap });
9855
+ }
9856
+ function originmatches(pattern, origin) {
9857
+ if (pattern === origin) return true;
9858
+ const glob = pattern.replace(/\./g, "\\.").replace(/\*/g, "[^.]+");
9859
+ if (!glob.startsWith("https://")) return false;
9860
+ return new RegExp(`^${glob}$`).test(origin);
9861
+ }
9862
+ function applyoverride(record2, override) {
9863
+ const matching = record2.origins.filter((origin) => originmatches(override.pattern, origin));
9864
+ if (matching.length === 0) throw new Error(`The override pattern ${override.pattern} matches none of the workflow origins ${record2.origins.join(", ")}.`);
9865
+ const knobs = /* @__PURE__ */ new Set(["loopbound", "stepms", "runms", "waitms", "delaybase"]);
9866
+ for (const knob of Object.keys(override.deltas)) {
9867
+ if (!knobs.has(knob)) throw new Error(`The override knob ${knob} is not one of the reviewed knobs: ${[...knobs].join(", ")}.`);
9868
+ if (typeof override.deltas[knob] !== "number" || !Number.isFinite(override.deltas[knob]) || override.deltas[knob] <= 0) throw new Error(`The override delta of ${knob} must be a positive number with no code ceiling.`);
9869
+ }
9870
+ const apply = (step) => {
9871
+ if (Object.keys(override.deltas).length === 0) return step;
9872
+ let payload = {};
9873
+ try {
9874
+ payload = step.options !== void 0 ? JSON.parse(step.options) : {};
9875
+ } catch {
9876
+ payload = {};
9877
+ }
9878
+ const bodyof = (key) => payload[key] !== void 0 && typeof payload[key] === "object" && !Array.isArray(payload[key]) ? payload[key] : {};
9879
+ if (override.deltas.loopbound !== void 0 && ["loop", "repeatuntil", "whileloop"].includes(step.kind)) {
9880
+ const body = bodyof(step.kind);
9881
+ body.bound = override.deltas.loopbound;
9882
+ payload[step.kind] = body;
9883
+ }
9884
+ if ((override.deltas.stepms !== void 0 || override.deltas.runms !== void 0) && step.kind === "trycatch") {
9885
+ const body = bodyof("trycatch");
9886
+ const timeout = body.timeout !== void 0 && typeof body.timeout === "object" && !Array.isArray(body.timeout) ? body.timeout : {};
9887
+ if (override.deltas.stepms !== void 0) timeout.stepms = override.deltas.stepms;
9888
+ if (override.deltas.runms !== void 0) timeout.runms = override.deltas.runms;
9889
+ body.timeout = timeout;
9890
+ payload.trycatch = body;
9891
+ }
9892
+ if (override.deltas.waitms !== void 0 && step.kind === "waitelement") {
9893
+ payload.timeout = override.deltas.waitms;
9894
+ }
9895
+ if (override.deltas.delaybase !== void 0 && step.kind === "delay") {
9896
+ payload.base = override.deltas.delaybase;
9897
+ }
9898
+ const changed = Object.keys(payload).length > 0;
9899
+ return changed ? { ...step, options: JSON.stringify(payload) } : step;
9900
+ };
9901
+ return { ...record2, steps: record2.steps.map(apply) };
9902
+ }
9903
+ function addedge(model, edge) {
9904
+ const from = model.nodes.findIndex((node) => nodeidof(node) === edge.from);
9905
+ const to = model.nodes.findIndex((node) => nodeidof(node) === edge.to);
9906
+ if (from < 0) throw new Error(`The canvas edge references the unknown source step ${edge.from}.`);
9907
+ if (to < 0) throw new Error(`The canvas edge references the unknown target step ${edge.to}.`);
9908
+ if (from >= to) throw new Error(`The canvas edge of ${edge.variable} would run backwards from ${edge.from} into ${edge.to} and form a cycle.`);
9909
+ if (!/^[a-z][a-z0-9]*$/.test(edge.variable)) throw new Error("The bound variable name must be a lowercase word.");
9910
+ const edges = [...model.edges.filter((candidate) => !(candidate.from === edge.from && candidate.to === edge.to && candidate.variable === edge.variable)), { ...edge, ...edge.path !== void 0 ? { path: edge.path } : {} }];
9911
+ const next = { ...model, edges };
9912
+ return withundo(model, next);
9913
+ }
9914
+ function removeedge(model, from, to, variable) {
9915
+ const edges = model.edges.filter((candidate) => !(candidate.from === from && candidate.to === to && candidate.variable === variable));
9916
+ if (edges.length === model.edges.length) throw new Error(`No canvas edge of ${variable} links ${from} into ${to}.`);
9917
+ const next = { ...model, edges };
9918
+ return withundo(model, next);
9919
+ }
9920
+ function removenode(model, nodeid) {
9921
+ const index = model.nodes.findIndex((node) => nodeidof(node) === nodeid);
9922
+ if (index < 0) throw new Error(`No canvas node matches ${nodeid}.`);
9923
+ const nodes = model.nodes.filter((_, position) => position !== index);
9924
+ const edges = model.edges.filter((edge) => edge.from !== nodeid && edge.to !== nodeid);
9925
+ const next = { ...model, nodes, edges };
9926
+ return withundo(model, { ...next, minimap: renderminimap(next).minimap });
9927
+ }
9928
+ function undoedit(model) {
9929
+ const undo = model.undo ?? [];
9930
+ if (undo.length === 0) return model;
9931
+ const previous = undo[undo.length - 1];
9932
+ const current = snapshotof(model);
9933
+ return { ...previous, undo: undo.slice(0, -1), redo: [...model.redo ?? [], current] };
9934
+ }
9935
+ function redoedit(model) {
9936
+ const redo = model.redo ?? [];
9937
+ if (redo.length === 0) return model;
9938
+ const next = redo[redo.length - 1];
9939
+ const current = snapshotof(model);
9940
+ return { ...next, redo: redo.slice(0, -1), undo: [...model.undo ?? [], current] };
9941
+ }
9942
+ function serializefile(file, format) {
9943
+ if (format === "json") return JSON.stringify(file, null, 2);
9944
+ return yamlvalue(file, 0).join("\n") + "\n";
9945
+ }
9946
+ function parsefile(contents, format) {
9947
+ if (format === "json") {
9948
+ const parsed = JSON.parse(contents);
9949
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("The workflow file is not a json object.");
9950
+ return parsed;
9951
+ }
9952
+ const lines = contents.split(/\r?\n/).map((line) => line.replace(/\t/g, " ")).filter((line) => line.trim() !== "" && !line.trim().startsWith("#"));
9953
+ if (lines.length === 0) throw new Error("The yaml workflow file is empty.");
9954
+ const { value, next } = yamlblock(lines, 0, indentof(lines[0]));
9955
+ if (next < lines.length) throw new Error("The yaml workflow file carries content outside the documented subset.");
9956
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("The yaml workflow file is not a mapping.");
9957
+ return value;
9958
+ }
9959
+ function indentof(line) {
9960
+ const match = /^ */.exec(line);
9961
+ return match ? match[0].length : 0;
9962
+ }
9963
+ function yamlscalar(value) {
9964
+ if (value === null || value === void 0) return "null";
9965
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
9966
+ return JSON.stringify(String(value));
9967
+ }
9968
+ function yamlvalue(value, indent) {
9969
+ const pad = " ".repeat(indent);
9970
+ if (value === null || value === void 0 || typeof value !== "object") return [`${pad}${yamlscalar(value)}`];
9971
+ if (Array.isArray(value)) {
9972
+ if (value.length === 0) return [`${pad}[]`];
9973
+ const lines2 = [];
9974
+ for (const item of value) {
9975
+ if (item !== null && typeof item === "object") {
9976
+ lines2.push(`${pad}-`);
9977
+ lines2.push(...yamlvalue(item, indent + 2));
9978
+ } else {
9979
+ lines2.push(`${pad}- ${yamlscalar(item)}`);
9980
+ }
9981
+ }
9982
+ return lines2;
9983
+ }
9984
+ const entries = Object.entries(value);
9985
+ if (entries.length === 0) return [`${pad}{}`];
9986
+ const lines = [];
9987
+ for (const [key, entry] of entries) {
9988
+ if (entry !== null && typeof entry === "object") {
9989
+ if (Array.isArray(entry) && entry.length === 0) {
9990
+ lines.push(`${pad}${key}: []`);
9991
+ continue;
9992
+ }
9993
+ if (!Array.isArray(entry) && Object.keys(entry).length === 0) {
9994
+ lines.push(`${pad}${key}: {}`);
9995
+ continue;
9996
+ }
9997
+ lines.push(`${pad}${key}:`);
9998
+ lines.push(...yamlvalue(entry, indent + 2));
9999
+ } else {
10000
+ lines.push(`${pad}${key}: ${yamlscalar(entry)}`);
10001
+ }
10002
+ }
10003
+ return lines;
10004
+ }
10005
+ function yamlblock(lines, start, indent) {
10006
+ const first = lines[start];
10007
+ if (/^\s*-\s/.test(first) || /^\s*-$/.test(first)) {
10008
+ const items = [];
10009
+ let index2 = start;
10010
+ while (index2 < lines.length) {
10011
+ const line = lines[index2];
10012
+ if (indentof(line) !== indent || !/^\s*-\s?/.test(line)) break;
10013
+ const rest = line.slice(indent + 1).trim();
10014
+ if (rest !== "") {
10015
+ items.push(yamlscalarvalue(rest));
10016
+ index2 += 1;
10017
+ continue;
10018
+ }
10019
+ const nested = yamlblock(lines, index2 + 1, indent + 2);
10020
+ items.push(nested.value);
10021
+ index2 = nested.next;
10022
+ }
10023
+ return { value: items, next: index2 };
10024
+ }
10025
+ const mapping = {};
10026
+ let index = start;
10027
+ while (index < lines.length) {
10028
+ const line = lines[index];
10029
+ if (indentof(line) !== indent) break;
10030
+ const match = /^([A-Za-z][A-Za-z0-9]*):(?:\s(.*))?$/.exec(line.slice(indent));
10031
+ if (!match) break;
10032
+ const key = match[1];
10033
+ const rest = match[2];
10034
+ if (rest !== void 0 && rest !== "") {
10035
+ if (rest === "[]") {
10036
+ mapping[key] = [];
10037
+ index += 1;
10038
+ continue;
10039
+ }
10040
+ if (rest === "{}") {
10041
+ mapping[key] = {};
10042
+ index += 1;
10043
+ continue;
10044
+ }
10045
+ mapping[key] = yamlscalarvalue(rest);
10046
+ index += 1;
10047
+ continue;
10048
+ }
10049
+ const nested = yamlblock(lines, index + 1, indent + 2);
10050
+ mapping[key] = nested.value;
10051
+ index = nested.next;
10052
+ }
10053
+ if (index === start) throw new Error("The yaml workflow file left the documented subset.");
10054
+ return { value: mapping, next: index };
10055
+ }
10056
+ function yamlscalarvalue(text2) {
10057
+ if (text2.startsWith('"')) {
10058
+ const parsed = JSON.parse(text2);
10059
+ return typeof parsed === "string" ? parsed : text2;
10060
+ }
10061
+ if (text2 === "true") return true;
10062
+ if (text2 === "false") return false;
10063
+ if (text2 === "null") return null;
10064
+ if (/^-?\d+(?:\.\d+)?$/.test(text2)) return Number(text2);
10065
+ return text2;
10066
+ }
8437
10067
  export {
8438
10068
  activelayers,
10069
+ addedge,
10070
+ addnode,
8439
10071
  agentgrammarvalid,
8440
10072
  agentpresetof,
8441
10073
  allowlistcovers,
@@ -8445,12 +10077,15 @@ export {
8445
10077
  apientries,
8446
10078
  apikeyconsentgranted,
8447
10079
  apireplayspecof,
10080
+ applycooldown,
8448
10081
  applyheaderules,
8449
10082
  applylayer,
10083
+ applyoverride,
8450
10084
  applyretry,
8451
10085
  applyruntimeout,
8452
10086
  applytimeout,
8453
10087
  argkind,
10088
+ armrule,
8454
10089
  assetentries,
8455
10090
  attachcdpsession,
8456
10091
  attachtargetof,
@@ -8460,6 +10095,7 @@ export {
8460
10095
  authreport,
8461
10096
  autointervalof,
8462
10097
  backoffdelay,
10098
+ bindparam,
8463
10099
  bindvariables,
8464
10100
  blackboxedurls,
8465
10101
  blackboxmatches,
@@ -8479,6 +10115,7 @@ export {
8479
10115
  buildname,
8480
10116
  buildpdf,
8481
10117
  buildsheet,
10118
+ buildsteplibrary,
8482
10119
  buildstitchplan,
8483
10120
  callgraphql,
8484
10121
  callrest,
@@ -8513,6 +10150,7 @@ export {
8513
10150
  collectmessages,
8514
10151
  composeworkflow,
8515
10152
  conditionof,
10153
+ confirmmanualrun,
8516
10154
  consolecapture,
8517
10155
  consoleconsentcovers,
8518
10156
  consolediff,
@@ -8531,6 +10169,8 @@ export {
8531
10169
  correlationid,
8532
10170
  cpusnap,
8533
10171
  crashinterrupted,
10172
+ cronnext,
10173
+ cronparse,
8534
10174
  croprect,
8535
10175
  crossesviewport,
8536
10176
  cursorfrom,
@@ -8540,6 +10180,7 @@ export {
8540
10180
  debugwaitbudgetallowed,
8541
10181
  dedupeimages,
8542
10182
  defaultloopbound,
10183
+ defaulttriggercooldown,
8543
10184
  delayjitter,
8544
10185
  actionrisk as deriveactionrisk,
8545
10186
  detachcdpsession,
@@ -8547,9 +10188,14 @@ export {
8547
10188
  diffresponse,
8548
10189
  diffreviewgrade,
8549
10190
  diffsessionrecords,
10191
+ diffversions,
8550
10192
  downloadreport,
10193
+ drainqueue,
8551
10194
  dryrunprojection,
8552
10195
  dryrunworkflow,
10196
+ editorsavegate,
10197
+ editorstate,
10198
+ editstep,
8553
10199
  emugate,
8554
10200
  emulationkinds,
8555
10201
  emulationreport,
@@ -8559,14 +10205,19 @@ export {
8559
10205
  errorcapture,
8560
10206
  errorreportresponse,
8561
10207
  evaluatecondition,
10208
+ evaluatetrigger,
8562
10209
  eventresponse,
10210
+ eventrulematches,
8563
10211
  exchangesreport,
8564
10212
  expandblocks,
10213
+ expandtemplate,
8565
10214
  expirelayers,
8566
10215
  expireprofilerecords,
8567
10216
  expiresessions,
10217
+ exportcontentreview,
8568
10218
  exportpresetlibrary,
8569
10219
  exportsessionfile,
10220
+ exportworkflow,
8570
10221
  expressioneval,
8571
10222
  expressionof,
8572
10223
  expressionoperators,
@@ -8590,6 +10241,7 @@ export {
8590
10241
  generatedvalueallowed,
8591
10242
  graphqlopenvelope,
8592
10243
  graphqlrequestof,
10244
+ groupselect,
8593
10245
  growsampleof,
8594
10246
  growthtrend,
8595
10247
  headerfilterof,
@@ -8606,6 +10258,7 @@ export {
8606
10258
  imagenames,
8607
10259
  importpresetlibrary,
8608
10260
  importsessionfile,
10261
+ importworkflow,
8609
10262
  iscdpkind,
8610
10263
  iscontrolflowkind,
8611
10264
  iscontrolkind,
@@ -8616,6 +10269,8 @@ export {
8616
10269
  isprofilekind,
8617
10270
  issessionkind,
8618
10271
  issocketkind,
10272
+ istriggeraction,
10273
+ istriggerkind,
8619
10274
  iswatchkind,
8620
10275
  isworkflowkind,
8621
10276
  joinbranches,
@@ -8625,6 +10280,8 @@ export {
8625
10280
  layernames,
8626
10281
  layoutreport,
8627
10282
  levelrank,
10283
+ listdue,
10284
+ loadworkflow,
8628
10285
  locationconsentcovers,
8629
10286
  locationconsentgate,
8630
10287
  locationpresetof,
@@ -8632,9 +10289,13 @@ export {
8632
10289
  loglevels,
8633
10290
  longtaskcapture,
8634
10291
  loopof,
10292
+ manualpreview,
10293
+ manualrunpreview,
8635
10294
  mapresponse,
8636
10295
  mapurlof,
10296
+ markbreakpoint,
8637
10297
  matchmessage,
10298
+ matchurl,
8638
10299
  matchurlpattern,
8639
10300
  measure,
8640
10301
  mediaentries,
@@ -8642,6 +10303,7 @@ export {
8642
10303
  mediareport,
8643
10304
  messagefilterof,
8644
10305
  methoddomain,
10306
+ minimapfocus,
8645
10307
  mockfor,
8646
10308
  mockspecof,
8647
10309
  multipartchunks,
@@ -8665,12 +10327,15 @@ export {
8665
10327
  oauthflowof,
8666
10328
  observationmodeof,
8667
10329
  observationresponse,
10330
+ observeevents,
8668
10331
  openchannel,
8669
10332
  outcomeresponse,
8670
10333
  overrideinputof,
8671
10334
  overridematches,
8672
10335
  pairexchange,
8673
10336
  pairstates,
10337
+ palettecategories,
10338
+ palettenodes,
8674
10339
  parallelof,
8675
10340
  parsehtmlbody,
8676
10341
  parseproposal,
@@ -8679,6 +10344,7 @@ export {
8679
10344
  parseworkflowproposal,
8680
10345
  passwordconsentgranted,
8681
10346
  patternorigin,
10347
+ pauseall,
8682
10348
  pauseretentionwindow,
8683
10349
  pauserun,
8684
10350
  payloadshapeof,
@@ -8710,6 +10376,7 @@ export {
8710
10376
  publishmessage,
8711
10377
  pushscope,
8712
10378
  quarantinereport,
10379
+ queuefire,
8713
10380
  randomid,
8714
10381
  rankapis,
8715
10382
  ratelimitbudgetallowed,
@@ -8723,10 +10390,15 @@ export {
8723
10390
  recordwatchvalue,
8724
10391
  redactconsoletext,
8725
10392
  redactedcookies,
10393
+ redoedit,
8726
10394
  regexextract,
8727
10395
  regexruleof,
8728
10396
  regionsteps,
8729
10397
  rejectioncapture,
10398
+ removeedge,
10399
+ removenode,
10400
+ renderminimap,
10401
+ reordersteps,
8730
10402
  repeatuntilof,
8731
10403
  replaytrace,
8732
10404
  replayurl,
@@ -8738,31 +10410,44 @@ export {
8738
10410
  restoreoriginsgranted,
8739
10411
  restoreplanof,
8740
10412
  restorereviewgranted,
10413
+ resumeall,
8741
10414
  retryafterof,
8742
10415
  revertalllayers,
8743
10416
  revertlayer,
8744
10417
  revertplanof,
8745
10418
  revertrule,
10419
+ reviewedkinds,
8746
10420
  revocationruleof,
8747
10421
  rewritesourcelocation,
8748
10422
  rotatelogs,
8749
10423
  rotationruleof,
10424
+ ruleorigins,
10425
+ ruleoriginsgranted,
8750
10426
  runcatch,
8751
10427
  runcontrolstep,
8752
10428
  runforeach,
10429
+ runhistoryquery,
10430
+ runhistoryreport,
8753
10431
  runloop,
8754
10432
  runparallel,
8755
10433
  runrepeatuntil,
10434
+ runreviewgranted,
8756
10435
  runstep,
10436
+ runtobreakpoint,
8757
10437
  runtry,
10438
+ runurllist,
8758
10439
  runwhile,
8759
10440
  runworkflow,
8760
10441
  safetyresponse,
10442
+ saveworkflow,
8761
10443
  scaledrect,
10444
+ schedulecron,
10445
+ scheduleinterval,
8762
10446
  seamweights,
8763
10447
  searchfields,
8764
10448
  searchqueryof,
8765
10449
  searchsessionrecords,
10450
+ searchsteps,
8766
10451
  seededrandom,
8767
10452
  selectorresponse,
8768
10453
  sendcdpcommand,
@@ -8780,8 +10465,10 @@ export {
8780
10465
  sessionrestoregate,
8781
10466
  sessiontabof,
8782
10467
  setvariable,
10468
+ shareworkflow,
8783
10469
  shiftentryof,
8784
10470
  signalsreport,
10471
+ snapnode,
8785
10472
  snapshotplanof,
8786
10473
  snapshotretentionwindow,
8787
10474
  snapshotsections,
@@ -8818,6 +10505,7 @@ export {
8818
10505
  timelinereport,
8819
10506
  timelineretentionwindow,
8820
10507
  timelinesources,
10508
+ timezonevalid,
8821
10509
  tokenrequest,
8822
10510
  tracecategories,
8823
10511
  traceceilingof,
@@ -8825,30 +10513,50 @@ export {
8825
10513
  tracetofile,
8826
10514
  trailreport,
8827
10515
  transformgrammar,
10516
+ triggereventcatalog,
10517
+ triggerfamilies,
10518
+ triggerfamilyof,
10519
+ triggerfired,
10520
+ triggergate,
10521
+ triggerkinds,
10522
+ triggerlist,
10523
+ triggerorigins,
10524
+ triggerpayloadof,
10525
+ triggersummary,
8828
10526
  tryof,
10527
+ undoedit,
8829
10528
  unwrapgraphql,
10529
+ updaterule,
8830
10530
  urlencodeform,
8831
10531
  validatebreakpointcondition,
8832
10532
  validatecontrolpayload,
8833
10533
  validatefieldmatch,
8834
10534
  validateformrecord,
8835
10535
  validateregexrule,
10536
+ validatesiteoverride,
8836
10537
  validatestep,
8837
10538
  validatetargetref,
8838
10539
  validatevaluegen,
8839
10540
  validateworkflow,
10541
+ verifywebhook,
10542
+ visitmatch,
8840
10543
  waitelementplan,
8841
10544
  watchcdpevents,
10545
+ watchdogconfigvalid,
10546
+ watchdogpass,
8842
10547
  watcherdetached,
8843
10548
  watchexpressionof,
8844
10549
  watchgate,
10550
+ webhooksecretok,
8845
10551
  whileof,
8846
10552
  wizardreport,
8847
10553
  workflowblockof,
10554
+ workflowfileversion,
8848
10555
  workflowgate,
8849
10556
  workflowkinds,
8850
10557
  workflowoutcome,
8851
10558
  workflowreport,
8852
- workflowstepof
10559
+ workflowstepof,
10560
+ zoomcanvas
8853
10561
  };
8854
10562
  //# sourceMappingURL=index.js.map