windmill-client 1.770.0 → 1.771.1

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/client.d.ts CHANGED
@@ -448,7 +448,11 @@ export declare let _workflowCtx: WorkflowCtx | null;
448
448
  export declare function setWorkflowCtx(ctx: WorkflowCtx | null): void;
449
449
  export declare class WorkflowCtx {
450
450
  private completed;
451
+ /** Null-prototype: step keys are caller-supplied, and a plain object would
452
+ * resolve `toString`/`constructor`/`__proto__` off `Object.prototype`. */
451
453
  private counters;
454
+ /** Every key handed out by `_allocKey`, so distinct names can't alias one key. */
455
+ private _usedKeys;
452
456
  private pending;
453
457
  private _suspended;
454
458
  /** When set, the task matching this key executes its inner function directly */
@@ -464,7 +468,10 @@ export declare class WorkflowCtx {
464
468
  * `completed_steps[key]`. Initialized to a resolved promise. */
465
469
  private _inlineChain;
466
470
  constructor(checkpoint?: Record<string, any>);
467
- /** Name-based key: `double` for first call, `double_2`, `double_3` for subsequent. */
471
+ /** Name-based key: `double` for first call, `double_2`, `double_3` for subsequent.
472
+ * Suffixing alone can alias — a second `step("x")` and a first `step("x_2")` both
473
+ * want `x_2` — so keep bumping past keys already handed out. Allocation order is
474
+ * fixed by the workflow body, so replays reproduce the same keys. */
468
475
  _allocKey(name: string): string;
469
476
  _nextStep(name: string, script: string, args?: Record<string, any>, dispatch_type?: string, options?: TaskOptions): PromiseLike<any>;
470
477
  /** Return and clear any pending (unawaited) steps. */
@@ -479,6 +486,7 @@ export declare class WorkflowCtx {
479
486
  timeout?: number;
480
487
  form?: object;
481
488
  selfApproval?: boolean;
489
+ key?: string;
482
490
  }): PromiseLike<{
483
491
  value: any;
484
492
  approver: string;
@@ -529,23 +537,51 @@ export declare function workflow<T>(fn: (...args: any[]) => Promise<T>): (...arg
529
537
  /**
530
538
  * Suspend the workflow and wait for an external approval.
531
539
  *
532
- * Use `getResumeUrls()` (wrapped in `step()`) to obtain resume/cancel/approvalPage
533
- * URLs before calling this function.
540
+ * Pass `key` to name the step, then `getApprovalUrls(key)` yields the URLs that
541
+ * resume exactly this approval — route them through your own channel. Without a
542
+ * key the steps are named `approval`, `approval_2`, ...
534
543
  *
535
544
  * @example
536
- * const urls = await step("urls", () => getResumeUrls());
537
- * await step("notify", () => sendEmail(urls.approvalPage));
538
- * const { value, approver } = await waitForApproval({ timeout: 3600 });
545
+ * const urls = await step("urls", () => getApprovalUrls("manager"));
546
+ * await step("notify", () => sendEmail(urls.resume, urls.cancel));
547
+ * const { value, approver } = await waitForApproval({ key: "manager", timeout: 3600 });
539
548
  */
540
549
  export declare function waitForApproval(options?: {
541
550
  timeout?: number;
542
551
  form?: object;
543
552
  selfApproval?: boolean;
553
+ key?: string;
544
554
  }): PromiseLike<{
545
555
  value: any;
546
556
  approver: string;
547
557
  approved: boolean;
548
558
  }>;
559
+ /**
560
+ * Resume/cancel/approval-page URLs bound to one `waitForApproval` step.
561
+ *
562
+ * Unlike `getResumeUrls()`, which signs a random nonce, these address the very
563
+ * `resume_job` record the step's built-in approval buttons use, so they are
564
+ * stable across replays and safe to embed in a custom notification.
565
+ *
566
+ * `stepKey` must match the `key` given to `waitForApproval`. Keys must be unique
567
+ * within a workflow; reusing one throws rather than silently renaming it. The URL
568
+ * only resumes while that step is awaiting approval; used at any other moment it is
569
+ * rejected rather than banking a row a different approval would consume. Send it
570
+ * ahead of time — approvers just cannot act before the workflow reaches the step.
571
+ *
572
+ * `resume` and `cancel` are step-bound; `approvalPage` is not — it opens the job's
573
+ * approval page, which acts on whichever approval is pending when it is used.
574
+ *
575
+ * @example
576
+ * const urls = await step("urls", () => getApprovalUrls("manager"));
577
+ * await step("notify", () => sendEmail(urls.resume, urls.cancel));
578
+ * await waitForApproval({ key: "manager" });
579
+ */
580
+ export declare function getApprovalUrls(stepKey?: string, approver?: string): Promise<{
581
+ approvalPage: string;
582
+ resume: string;
583
+ cancel: string;
584
+ }>;
549
585
  /**
550
586
  * Process items in parallel with optional concurrency control.
551
587
  *
package/dist/client.mjs CHANGED
@@ -958,6 +958,13 @@ var StepSuspend = class extends Error {
958
958
  this.name = "StepSuspend";
959
959
  }
960
960
  };
961
+ /** A step key travels as one path segment when its URLs are minted, so it must be
962
+ * non-empty and free of `/` and dot segments — otherwise `waitForApproval` would
963
+ * accept a key `getApprovalUrls` can never address. */
964
+ function assertUsableStepKey(key, what) {
965
+ const k = key.trim();
966
+ if (k === "" || k === "." || k === ".." || key.includes("/") || key.includes("\\")) throw new Error(`${what} must be a non-empty step name without \`/\` or dot segments`);
967
+ }
961
968
  let _workflowCtx = null;
962
969
  function setWorkflowCtx(ctx) {
963
970
  _workflowCtx = ctx;
@@ -965,7 +972,11 @@ function setWorkflowCtx(ctx) {
965
972
  }
966
973
  var WorkflowCtx = class {
967
974
  completed;
968
- counters = {};
975
+ /** Null-prototype: step keys are caller-supplied, and a plain object would
976
+ * resolve `toString`/`constructor`/`__proto__` off `Object.prototype`. */
977
+ counters = Object.create(null);
978
+ /** Every key handed out by `_allocKey`, so distinct names can't alias one key. */
979
+ _usedKeys = new Set();
969
980
  pending = [];
970
981
  _suspended = false;
971
982
  /** When set, the task matching this key executes its inner function directly */
@@ -981,14 +992,23 @@ var WorkflowCtx = class {
981
992
  * `completed_steps[key]`. Initialized to a resolved promise. */
982
993
  _inlineChain = Promise.resolve();
983
994
  constructor(checkpoint = {}) {
984
- this.completed = checkpoint?.completed_steps ?? {};
995
+ this.completed = Object.assign(Object.create(null), checkpoint?.completed_steps ?? {});
985
996
  this._executingKey = checkpoint?._executing_key ?? null;
986
997
  }
987
- /** Name-based key: `double` for first call, `double_2`, `double_3` for subsequent. */
998
+ /** Name-based key: `double` for first call, `double_2`, `double_3` for subsequent.
999
+ * Suffixing alone can alias — a second `step("x")` and a first `step("x_2")` both
1000
+ * want `x_2` — so keep bumping past keys already handed out. Allocation order is
1001
+ * fixed by the workflow body, so replays reproduce the same keys. */
988
1002
  _allocKey(name) {
989
- const n = (this.counters[name] ?? 0) + 1;
1003
+ let n = (this.counters[name] ?? 0) + 1;
1004
+ let key = n === 1 ? name : `${name}_${n}`;
1005
+ while (this._usedKeys.has(key)) {
1006
+ n++;
1007
+ key = `${name}_${n}`;
1008
+ }
990
1009
  this.counters[name] = n;
991
- return n === 1 ? name : `${name}_${n}`;
1010
+ this._usedKeys.add(key);
1011
+ return key;
992
1012
  }
993
1013
  _nextStep(name, script, args = {}, dispatch_type = "inline", options) {
994
1014
  const key = this._allocKey(name || script || "step");
@@ -1048,7 +1068,9 @@ var WorkflowCtx = class {
1048
1068
  return steps;
1049
1069
  }
1050
1070
  _waitForApproval(options) {
1051
- const key = this._allocKey("approval");
1071
+ if (options?.key !== void 0) assertUsableStepKey(options.key, "waitForApproval key");
1072
+ const key = this._allocKey(options?.key || "approval");
1073
+ if (options?.key && key !== options.key) throw new Error(`WAC step key "${options.key}" is already used in this workflow. Give each waitForApproval() its own key so getApprovalUrls() can address it.`);
1052
1074
  if (key in this.completed) {
1053
1075
  const value = this.completed[key];
1054
1076
  return { then: (resolve) => resolve(value) };
@@ -1287,13 +1309,14 @@ function workflow(fn) {
1287
1309
  /**
1288
1310
  * Suspend the workflow and wait for an external approval.
1289
1311
  *
1290
- * Use `getResumeUrls()` (wrapped in `step()`) to obtain resume/cancel/approvalPage
1291
- * URLs before calling this function.
1312
+ * Pass `key` to name the step, then `getApprovalUrls(key)` yields the URLs that
1313
+ * resume exactly this approval — route them through your own channel. Without a
1314
+ * key the steps are named `approval`, `approval_2`, ...
1292
1315
  *
1293
1316
  * @example
1294
- * const urls = await step("urls", () => getResumeUrls());
1295
- * await step("notify", () => sendEmail(urls.approvalPage));
1296
- * const { value, approver } = await waitForApproval({ timeout: 3600 });
1317
+ * const urls = await step("urls", () => getApprovalUrls("manager"));
1318
+ * await step("notify", () => sendEmail(urls.resume, urls.cancel));
1319
+ * const { value, approver } = await waitForApproval({ key: "manager", timeout: 3600 });
1297
1320
  */
1298
1321
  function waitForApproval(options) {
1299
1322
  const ctx = _workflowCtx ?? Reflect.get(globalThis, "__wmill_wf_ctx");
@@ -1301,6 +1324,37 @@ function waitForApproval(options) {
1301
1324
  return ctx._waitForApproval(options);
1302
1325
  }
1303
1326
  /**
1327
+ * Resume/cancel/approval-page URLs bound to one `waitForApproval` step.
1328
+ *
1329
+ * Unlike `getResumeUrls()`, which signs a random nonce, these address the very
1330
+ * `resume_job` record the step's built-in approval buttons use, so they are
1331
+ * stable across replays and safe to embed in a custom notification.
1332
+ *
1333
+ * `stepKey` must match the `key` given to `waitForApproval`. Keys must be unique
1334
+ * within a workflow; reusing one throws rather than silently renaming it. The URL
1335
+ * only resumes while that step is awaiting approval; used at any other moment it is
1336
+ * rejected rather than banking a row a different approval would consume. Send it
1337
+ * ahead of time — approvers just cannot act before the workflow reaches the step.
1338
+ *
1339
+ * `resume` and `cancel` are step-bound; `approvalPage` is not — it opens the job's
1340
+ * approval page, which acts on whichever approval is pending when it is used.
1341
+ *
1342
+ * @example
1343
+ * const urls = await step("urls", () => getApprovalUrls("manager"));
1344
+ * await step("notify", () => sendEmail(urls.resume, urls.cancel));
1345
+ * await waitForApproval({ key: "manager" });
1346
+ */
1347
+ async function getApprovalUrls(stepKey = "approval", approver) {
1348
+ assertUsableStepKey(stepKey, "getApprovalUrls stepKey");
1349
+ const workspace = getWorkspace();
1350
+ return await JobService.getWacApprovalUrls({
1351
+ workspace,
1352
+ stepKey,
1353
+ approver,
1354
+ id: getEnv("WM_JOB_ID") ?? "NO_JOB_ID"
1355
+ });
1356
+ }
1357
+ /**
1304
1358
  * Process items in parallel with optional concurrency control.
1305
1359
  *
1306
1360
  * Each item is processed by calling `fn(item)`, which should be a task().
@@ -1342,4 +1396,4 @@ async function commitKafkaOffsets(triggerPath, topic, partition, offset) {
1342
1396
  }
1343
1397
 
1344
1398
  //#endregion
1345
- export { SHARED_FOLDER, StepSuspend, WorkflowCtx, _workflowCtx, appendToResultStream, base64ToUint8Array, commitKafkaOffsets, databaseUrlFromResource, deleteS3File, denoS3LightClientSettings, getEnv, getFlowUserState, getIdToken, getInternalState, getPresignedS3PublicUrl, getPresignedS3PublicUrls, getProgress, getResource, getResult, getResultMaybe, getResumeEndpoints, getResumeUrls, getRootJobId, getState, getStatePath, getVariable, getWorkspace, loadS3File, loadS3FileStream, parallel, requestInteractiveSlackApproval, requestInteractiveTeamsApproval, resolveDefaultResource, runFlow, runFlowAsync, runScript, runScriptAsync, runScriptByHash, runScriptByHashAsync, runScriptByPath, runScriptByPathAsync, setClient, setFlowUserState, setInternalState, setProgress, setResource, setState, setVariable, setWorkflowCtx, signS3Object, signS3Objects, sleep, step, streamResult, task, taskFlow, taskScript, uint8ArrayToBase64, usernameToEmail, waitForApproval, waitJob, workerHasInternalServer, workflow, writeS3File };
1399
+ export { SHARED_FOLDER, StepSuspend, WorkflowCtx, _workflowCtx, appendToResultStream, base64ToUint8Array, commitKafkaOffsets, databaseUrlFromResource, deleteS3File, denoS3LightClientSettings, getApprovalUrls, getEnv, getFlowUserState, getIdToken, getInternalState, getPresignedS3PublicUrl, getPresignedS3PublicUrls, getProgress, getResource, getResult, getResultMaybe, getResumeEndpoints, getResumeUrls, getRootJobId, getState, getStatePath, getVariable, getWorkspace, loadS3File, loadS3FileStream, parallel, requestInteractiveSlackApproval, requestInteractiveTeamsApproval, resolveDefaultResource, runFlow, runFlowAsync, runScript, runScriptAsync, runScriptByHash, runScriptByHashAsync, runScriptByPath, runScriptByPathAsync, setClient, setFlowUserState, setInternalState, setProgress, setResource, setState, setVariable, setWorkflowCtx, signS3Object, signS3Objects, sleep, step, streamResult, task, taskFlow, taskScript, uint8ArrayToBase64, usernameToEmail, waitForApproval, waitJob, workerHasInternalServer, workflow, writeS3File };
@@ -29,7 +29,7 @@ const OpenAPI = {
29
29
  PASSWORD: void 0,
30
30
  TOKEN: getEnv("WM_TOKEN"),
31
31
  USERNAME: void 0,
32
- VERSION: "1.770.0",
32
+ VERSION: "1.771.1",
33
33
  WITH_CREDENTIALS: true,
34
34
  interceptors: {
35
35
  request: new Interceptors(),
package/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@ export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI';
4
4
  export * from './services.gen';
5
5
  export * from './types.gen';
6
6
  export type { DenoS3LightClientSettings } from "./s3Types";
7
- export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, taskScript, taskFlow, workflow, step, sleep, parallel, waitForApproval, type TaskOptions, WorkflowCtx, _workflowCtx, setWorkflowCtx, StepSuspend, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, type Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, type DucklakeMaterializeOptions, type SqlStatement, type DatatableSqlTemplateFunction, type SqlTemplateFunction, type S3Object, type S3ObjectRecord, type S3ObjectURI, commitKafkaOffsets } from "./client";
8
- import { setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, taskScript, taskFlow, workflow, step, sleep, parallel, waitForApproval, WorkflowCtx, setWorkflowCtx, StepSuspend, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, getWorkspace, getStatePath, getInternalState, setInternalState, getResumeEndpoints, getResult, getResultMaybe, resolveDefaultResource, databaseUrlFromResource, base64ToUint8Array, uint8ArrayToBase64, parseS3Object, commitKafkaOffsets } from "./client";
7
+ export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, taskScript, taskFlow, workflow, step, sleep, parallel, waitForApproval, getApprovalUrls, type TaskOptions, WorkflowCtx, _workflowCtx, setWorkflowCtx, StepSuspend, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, type Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, type DucklakeMaterializeOptions, type SqlStatement, type DatatableSqlTemplateFunction, type SqlTemplateFunction, type S3Object, type S3ObjectRecord, type S3ObjectURI, commitKafkaOffsets } from "./client";
8
+ import { setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, taskScript, taskFlow, workflow, step, sleep, parallel, waitForApproval, getApprovalUrls, WorkflowCtx, setWorkflowCtx, StepSuspend, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, getWorkspace, getStatePath, getInternalState, setInternalState, getResumeEndpoints, getResult, getResultMaybe, resolveDefaultResource, databaseUrlFromResource, base64ToUint8Array, uint8ArrayToBase64, parseS3Object, commitKafkaOffsets } from "./client";
9
9
  import { AdminService, AuditService, FlowService, GranularAclService, GroupService, JobService, ResourceService, VariableService, ScriptService, ScheduleService, SettingsService, UserService, WorkspaceService, TeamsService } from "./services.gen";
10
10
  declare const wmill: {
11
11
  setClient: typeof setClient;
@@ -36,6 +36,7 @@ declare const wmill: {
36
36
  sleep: typeof sleep;
37
37
  parallel: typeof parallel;
38
38
  waitForApproval: typeof waitForApproval;
39
+ getApprovalUrls: typeof getApprovalUrls;
39
40
  WorkflowCtx: typeof WorkflowCtx;
40
41
  _workflowCtx: WorkflowCtx | null;
41
42
  setWorkflowCtx: typeof setWorkflowCtx;
package/dist/index.js CHANGED
@@ -126,7 +126,7 @@ const OpenAPI = {
126
126
  PASSWORD: void 0,
127
127
  TOKEN: getEnv$1("WM_TOKEN"),
128
128
  USERNAME: void 0,
129
- VERSION: "1.770.0",
129
+ VERSION: "1.771.1",
130
130
  WITH_CREDENTIALS: true,
131
131
  interceptors: {
132
132
  request: new Interceptors(),
@@ -7445,6 +7445,42 @@ var ScriptService = class {
7445
7445
  mediaType: "application/json"
7446
7446
  });
7447
7447
  }
7448
+ /**
7449
+ * list runnables (scripts, flows, apps) merged, ordered and keyset-paginated
7450
+ * @param data The data for the request.
7451
+ * @param data.workspace
7452
+ * @param data.orderBy sort key: 'updated' (default) or 'name'
7453
+ * @param data.orderDesc order by desc order (default true)
7454
+ * @param data.kinds comma-separated subset of script,flow,app (default all)
7455
+ * @param data.showArchived
7456
+ * @param data.includeWithoutMain include library scripts (no runnable main)
7457
+ * @param data.pathStart restrict to paths under this prefix
7458
+ * @param data.label
7459
+ * @param data.search case-insensitive substring match on summary or path
7460
+ * @param data.perPage number of items to return for a given page (default 30, max 100)
7461
+ * @param data.cursor opaque keyset cursor from a previous page's next_cursor
7462
+ * @returns unknown a page of merged, ordered runnables
7463
+ * @throws ApiError
7464
+ */
7465
+ static listRunnables(data) {
7466
+ return request(OpenAPI, {
7467
+ method: "GET",
7468
+ url: "/w/{workspace}/runnables/list",
7469
+ path: { workspace: data.workspace },
7470
+ query: {
7471
+ order_by: data.orderBy,
7472
+ order_desc: data.orderDesc,
7473
+ kinds: data.kinds,
7474
+ show_archived: data.showArchived,
7475
+ include_without_main: data.includeWithoutMain,
7476
+ path_start: data.pathStart,
7477
+ label: data.label,
7478
+ search: data.search,
7479
+ per_page: data.perPage,
7480
+ cursor: data.cursor
7481
+ }
7482
+ });
7483
+ }
7448
7484
  };
7449
7485
  var DraftService = class {
7450
7486
  /**
@@ -8975,6 +9011,7 @@ var JobService = class {
8975
9011
  * @param data.page which page to return (start at 1, default 1)
8976
9012
  * @param data.perPage number of items to return for a given page (default 30, max 100)
8977
9013
  * @param data.isSkipped is the job skipped
9014
+ * @param data.resolved filter on whether a failure has been marked as handled. true keeps only resolved failures, false hides them
8978
9015
  * @param data.isFlowStep is the job a flow step
8979
9016
  * @param data.hasNullParent has null parent
8980
9017
  * @param data.success filter on successful jobs
@@ -9016,6 +9053,7 @@ var JobService = class {
9016
9053
  page: data.page,
9017
9054
  per_page: data.perPage,
9018
9055
  is_skipped: data.isSkipped,
9056
+ resolved: data.resolved,
9019
9057
  is_flow_step: data.isFlowStep,
9020
9058
  has_null_parent: data.hasNullParent,
9021
9059
  success: data.success,
@@ -9153,6 +9191,7 @@ var JobService = class {
9153
9191
  * @param data.page which page to return (start at 1, default 1)
9154
9192
  * @param data.perPage number of items to return for a given page (default 30, max 100)
9155
9193
  * @param data.isSkipped is the job skipped
9194
+ * @param data.resolved filter on whether a failure has been marked as handled. true keeps only resolved failures, false hides them
9156
9195
  * @param data.isFlowStep is the job a flow step
9157
9196
  * @param data.hasNullParent has null parent
9158
9197
  * @param data.isNotSchedule is not a scheduled job
@@ -9186,6 +9225,7 @@ var JobService = class {
9186
9225
  page: data.page,
9187
9226
  per_page: data.perPage,
9188
9227
  is_skipped: data.isSkipped,
9228
+ resolved: data.resolved,
9189
9229
  is_flow_step: data.isFlowStep,
9190
9230
  has_null_parent: data.hasNullParent,
9191
9231
  is_not_schedule: data.isNotSchedule
@@ -9314,6 +9354,7 @@ var JobService = class {
9314
9354
  * @param data.perPage number of items to return for a given page (default 30, max 100)
9315
9355
  * @param data.triggerKind filter by trigger kind. Supports comma-separated list (e.g. 'schedule,webhook') and negation by prefixing all values with '!' (e.g. '!schedule,!webhook')
9316
9356
  * @param data.isSkipped is the job skipped
9357
+ * @param data.resolved filter on whether a failure has been marked as handled. true keeps only resolved failures, false hides them
9317
9358
  * @param data.isFlowStep is the job a flow step
9318
9359
  * @param data.hasNullParent has null parent
9319
9360
  * @param data.success filter on successful jobs
@@ -9358,6 +9399,7 @@ var JobService = class {
9358
9399
  per_page: data.perPage,
9359
9400
  trigger_kind: data.triggerKind,
9360
9401
  is_skipped: data.isSkipped,
9402
+ resolved: data.resolved,
9361
9403
  is_flow_step: data.isFlowStep,
9362
9404
  has_null_parent: data.hasNullParent,
9363
9405
  success: data.success,
@@ -9796,6 +9838,41 @@ var JobService = class {
9796
9838
  });
9797
9839
  }
9798
9840
  /**
9841
+ * mark failed jobs as resolved
9842
+ * Marks failed jobs as handled so triage surfaces stop showing them as failures. The job status itself is unchanged. Returns the ids actually resolved: ids that are not visible to the caller or did not fail are silently skipped.
9843
+ * @param data The data for the request.
9844
+ * @param data.workspace
9845
+ * @param data.requestBody
9846
+ * @returns string ids of the jobs that were resolved
9847
+ * @throws ApiError
9848
+ */
9849
+ static resolveCompletedJobs(data) {
9850
+ return request(OpenAPI, {
9851
+ method: "POST",
9852
+ url: "/w/{workspace}/jobs/completed/resolve",
9853
+ path: { workspace: data.workspace },
9854
+ body: data.requestBody,
9855
+ mediaType: "application/json"
9856
+ });
9857
+ }
9858
+ /**
9859
+ * remove the resolution of failed jobs
9860
+ * @param data The data for the request.
9861
+ * @param data.workspace
9862
+ * @param data.requestBody
9863
+ * @returns string ids of the jobs that were unresolved
9864
+ * @throws ApiError
9865
+ */
9866
+ static unresolveCompletedJobs(data) {
9867
+ return request(OpenAPI, {
9868
+ method: "POST",
9869
+ url: "/w/{workspace}/jobs/completed/unresolve",
9870
+ path: { workspace: data.workspace },
9871
+ body: data.requestBody,
9872
+ mediaType: "application/json"
9873
+ });
9874
+ }
9875
+ /**
9799
9876
  * cancel queued or running job
9800
9877
  * @param data The data for the request.
9801
9878
  * @param data.workspace
@@ -9943,6 +10020,28 @@ var JobService = class {
9943
10020
  });
9944
10021
  }
9945
10022
  /**
10023
+ * get the resume urls bound to a specific wait_for_approval step of a workflow-as-code job
10024
+ * @param data The data for the request.
10025
+ * @param data.workspace
10026
+ * @param data.id
10027
+ * @param data.stepKey checkpoint key of the wait_for_approval step, as passed to `wait_for_approval(key=...)`
10028
+ * @param data.approver
10029
+ * @returns unknown url endpoints
10030
+ * @throws ApiError
10031
+ */
10032
+ static getWacApprovalUrls(data) {
10033
+ return request(OpenAPI, {
10034
+ method: "GET",
10035
+ url: "/w/{workspace}/jobs/wac_approval_urls/{id}/{step_key}",
10036
+ path: {
10037
+ workspace: data.workspace,
10038
+ id: data.id,
10039
+ step_key: data.stepKey
10040
+ },
10041
+ query: { approver: data.approver }
10042
+ });
10043
+ }
10044
+ /**
9946
10045
  * generate interactive slack approval for suspended job
9947
10046
  * @param data The data for the request.
9948
10047
  * @param data.workspace
@@ -10289,6 +10388,7 @@ var JobService = class {
10289
10388
  * @param data.perPage number of items to return for a given page (default 30, max 100)
10290
10389
  * @param data.triggerKind filter by trigger kind. Supports comma-separated list (e.g. 'schedule,webhook') and negation by prefixing all values with '!' (e.g. '!schedule,!webhook')
10291
10390
  * @param data.isSkipped is the job skipped
10391
+ * @param data.resolved filter on whether a failure has been marked as handled. true keeps only resolved failures, false hides them
10292
10392
  * @param data.isFlowStep is the job a flow step
10293
10393
  * @param data.hasNullParent has null parent
10294
10394
  * @param data.success filter on successful jobs
@@ -10330,6 +10430,7 @@ var JobService = class {
10330
10430
  per_page: data.perPage,
10331
10431
  trigger_kind: data.triggerKind,
10332
10432
  is_skipped: data.isSkipped,
10433
+ resolved: data.resolved,
10333
10434
  is_flow_step: data.isFlowStep,
10334
10435
  has_null_parent: data.hasNullParent,
10335
10436
  success: data.success,
@@ -13857,6 +13958,8 @@ var FolderService = class {
13857
13958
  * list folder names
13858
13959
  * @param data The data for the request.
13859
13960
  * @param data.workspace
13961
+ * @param data.page which page to return (start at 1, default 1)
13962
+ * @param data.perPage number of items to return for a given page (default 30, max 100)
13860
13963
  * @param data.onlyMemberOf only list the folders the user is member of (default false)
13861
13964
  * @returns string folder list
13862
13965
  * @throws ApiError
@@ -13866,7 +13969,11 @@ var FolderService = class {
13866
13969
  method: "GET",
13867
13970
  url: "/w/{workspace}/folders/listnames",
13868
13971
  path: { workspace: data.workspace },
13869
- query: { only_member_of: data.onlyMemberOf }
13972
+ query: {
13973
+ page: data.page,
13974
+ per_page: data.perPage,
13975
+ only_member_of: data.onlyMemberOf
13976
+ }
13870
13977
  });
13871
13978
  }
13872
13979
  /**
@@ -15366,6 +15473,7 @@ var ConcurrencyGroupsService = class {
15366
15473
  * @param data.perPage number of items to return for a given page (default 30, max 100)
15367
15474
  * @param data.triggerKind filter by trigger kind. Supports comma-separated list (e.g. 'schedule,webhook') and negation by prefixing all values with '!' (e.g. '!schedule,!webhook')
15368
15475
  * @param data.isSkipped is the job skipped
15476
+ * @param data.resolved filter on whether a failure has been marked as handled. true keeps only resolved failures, false hides them
15369
15477
  * @param data.isFlowStep is the job a flow step
15370
15478
  * @param data.hasNullParent has null parent
15371
15479
  * @param data.success filter on successful jobs
@@ -15407,6 +15515,7 @@ var ConcurrencyGroupsService = class {
15407
15515
  per_page: data.perPage,
15408
15516
  trigger_kind: data.triggerKind,
15409
15517
  is_skipped: data.isSkipped,
15518
+ resolved: data.resolved,
15410
15519
  is_flow_step: data.isFlowStep,
15411
15520
  has_null_parent: data.hasNullParent,
15412
15521
  success: data.success,
@@ -15969,7 +16078,7 @@ var HubPublishService = class {
15969
16078
  });
15970
16079
  }
15971
16080
  /**
15972
- * set or clear the embed url of a hub raw app
16081
+ * attach a recorded session to a hub raw app
15973
16082
  * Requires the caller to be a workspace admin. Forwards the request to the
15974
16083
  * configured Hub scoped to the `{workspace}:{folder}` source and returns
15975
16084
  * the Hub's status code and raw response body.
@@ -15985,10 +16094,10 @@ var HubPublishService = class {
15985
16094
  * @returns string raw Hub response body (status code is passed through from the Hub)
15986
16095
  * @throws ApiError
15987
16096
  */
15988
- static publishHubRawAppEmbed(data) {
16097
+ static publishHubRawAppRecording(data) {
15989
16098
  return request(OpenAPI, {
15990
16099
  method: "POST",
15991
- url: "/w/{workspace}/hub/raw_apps/{id}/embed",
16100
+ url: "/w/{workspace}/hub/raw_apps/{id}/recording",
15992
16101
  path: {
15993
16102
  workspace: data.workspace,
15994
16103
  id: data.id
@@ -17636,6 +17745,13 @@ var StepSuspend = class extends Error {
17636
17745
  this.name = "StepSuspend";
17637
17746
  }
17638
17747
  };
17748
+ /** A step key travels as one path segment when its URLs are minted, so it must be
17749
+ * non-empty and free of `/` and dot segments — otherwise `waitForApproval` would
17750
+ * accept a key `getApprovalUrls` can never address. */
17751
+ function assertUsableStepKey(key, what) {
17752
+ const k = key.trim();
17753
+ if (k === "" || k === "." || k === ".." || key.includes("/") || key.includes("\\")) throw new Error(`${what} must be a non-empty step name without \`/\` or dot segments`);
17754
+ }
17639
17755
  let _workflowCtx = null;
17640
17756
  function setWorkflowCtx(ctx) {
17641
17757
  _workflowCtx = ctx;
@@ -17643,7 +17759,11 @@ function setWorkflowCtx(ctx) {
17643
17759
  }
17644
17760
  var WorkflowCtx = class {
17645
17761
  completed;
17646
- counters = {};
17762
+ /** Null-prototype: step keys are caller-supplied, and a plain object would
17763
+ * resolve `toString`/`constructor`/`__proto__` off `Object.prototype`. */
17764
+ counters = Object.create(null);
17765
+ /** Every key handed out by `_allocKey`, so distinct names can't alias one key. */
17766
+ _usedKeys = new Set();
17647
17767
  pending = [];
17648
17768
  _suspended = false;
17649
17769
  /** When set, the task matching this key executes its inner function directly */
@@ -17659,14 +17779,23 @@ var WorkflowCtx = class {
17659
17779
  * `completed_steps[key]`. Initialized to a resolved promise. */
17660
17780
  _inlineChain = Promise.resolve();
17661
17781
  constructor(checkpoint = {}) {
17662
- this.completed = checkpoint?.completed_steps ?? {};
17782
+ this.completed = Object.assign(Object.create(null), checkpoint?.completed_steps ?? {});
17663
17783
  this._executingKey = checkpoint?._executing_key ?? null;
17664
17784
  }
17665
- /** Name-based key: `double` for first call, `double_2`, `double_3` for subsequent. */
17785
+ /** Name-based key: `double` for first call, `double_2`, `double_3` for subsequent.
17786
+ * Suffixing alone can alias — a second `step("x")` and a first `step("x_2")` both
17787
+ * want `x_2` — so keep bumping past keys already handed out. Allocation order is
17788
+ * fixed by the workflow body, so replays reproduce the same keys. */
17666
17789
  _allocKey(name) {
17667
- const n = (this.counters[name] ?? 0) + 1;
17790
+ let n = (this.counters[name] ?? 0) + 1;
17791
+ let key = n === 1 ? name : `${name}_${n}`;
17792
+ while (this._usedKeys.has(key)) {
17793
+ n++;
17794
+ key = `${name}_${n}`;
17795
+ }
17668
17796
  this.counters[name] = n;
17669
- return n === 1 ? name : `${name}_${n}`;
17797
+ this._usedKeys.add(key);
17798
+ return key;
17670
17799
  }
17671
17800
  _nextStep(name, script, args = {}, dispatch_type = "inline", options) {
17672
17801
  const key = this._allocKey(name || script || "step");
@@ -17726,7 +17855,9 @@ var WorkflowCtx = class {
17726
17855
  return steps;
17727
17856
  }
17728
17857
  _waitForApproval(options) {
17729
- const key = this._allocKey("approval");
17858
+ if (options?.key !== void 0) assertUsableStepKey(options.key, "waitForApproval key");
17859
+ const key = this._allocKey(options?.key || "approval");
17860
+ if (options?.key && key !== options.key) throw new Error(`WAC step key "${options.key}" is already used in this workflow. Give each waitForApproval() its own key so getApprovalUrls() can address it.`);
17730
17861
  if (key in this.completed) {
17731
17862
  const value = this.completed[key];
17732
17863
  return { then: (resolve$1) => resolve$1(value) };
@@ -17965,13 +18096,14 @@ function workflow(fn) {
17965
18096
  /**
17966
18097
  * Suspend the workflow and wait for an external approval.
17967
18098
  *
17968
- * Use `getResumeUrls()` (wrapped in `step()`) to obtain resume/cancel/approvalPage
17969
- * URLs before calling this function.
18099
+ * Pass `key` to name the step, then `getApprovalUrls(key)` yields the URLs that
18100
+ * resume exactly this approval — route them through your own channel. Without a
18101
+ * key the steps are named `approval`, `approval_2`, ...
17970
18102
  *
17971
18103
  * @example
17972
- * const urls = await step("urls", () => getResumeUrls());
17973
- * await step("notify", () => sendEmail(urls.approvalPage));
17974
- * const { value, approver } = await waitForApproval({ timeout: 3600 });
18104
+ * const urls = await step("urls", () => getApprovalUrls("manager"));
18105
+ * await step("notify", () => sendEmail(urls.resume, urls.cancel));
18106
+ * const { value, approver } = await waitForApproval({ key: "manager", timeout: 3600 });
17975
18107
  */
17976
18108
  function waitForApproval(options) {
17977
18109
  const ctx = _workflowCtx ?? Reflect.get(globalThis, "__wmill_wf_ctx");
@@ -17979,6 +18111,37 @@ function waitForApproval(options) {
17979
18111
  return ctx._waitForApproval(options);
17980
18112
  }
17981
18113
  /**
18114
+ * Resume/cancel/approval-page URLs bound to one `waitForApproval` step.
18115
+ *
18116
+ * Unlike `getResumeUrls()`, which signs a random nonce, these address the very
18117
+ * `resume_job` record the step's built-in approval buttons use, so they are
18118
+ * stable across replays and safe to embed in a custom notification.
18119
+ *
18120
+ * `stepKey` must match the `key` given to `waitForApproval`. Keys must be unique
18121
+ * within a workflow; reusing one throws rather than silently renaming it. The URL
18122
+ * only resumes while that step is awaiting approval; used at any other moment it is
18123
+ * rejected rather than banking a row a different approval would consume. Send it
18124
+ * ahead of time — approvers just cannot act before the workflow reaches the step.
18125
+ *
18126
+ * `resume` and `cancel` are step-bound; `approvalPage` is not — it opens the job's
18127
+ * approval page, which acts on whichever approval is pending when it is used.
18128
+ *
18129
+ * @example
18130
+ * const urls = await step("urls", () => getApprovalUrls("manager"));
18131
+ * await step("notify", () => sendEmail(urls.resume, urls.cancel));
18132
+ * await waitForApproval({ key: "manager" });
18133
+ */
18134
+ async function getApprovalUrls(stepKey = "approval", approver) {
18135
+ assertUsableStepKey(stepKey, "getApprovalUrls stepKey");
18136
+ const workspace = getWorkspace();
18137
+ return await JobService.getWacApprovalUrls({
18138
+ workspace,
18139
+ stepKey,
18140
+ approver,
18141
+ id: getEnv("WM_JOB_ID") ?? "NO_JOB_ID"
18142
+ });
18143
+ }
18144
+ /**
17982
18145
  * Process items in parallel with optional concurrency control.
17983
18146
  *
17984
18147
  * Each item is processed by calling `fn(item)`, which should be a task().
@@ -18050,6 +18213,7 @@ const wmill = {
18050
18213
  sleep,
18051
18214
  parallel,
18052
18215
  waitForApproval,
18216
+ getApprovalUrls,
18053
18217
  WorkflowCtx,
18054
18218
  _workflowCtx,
18055
18219
  setWorkflowCtx,
@@ -18189,6 +18353,7 @@ exports.default = src_default;
18189
18353
  exports.deleteS3File = deleteS3File;
18190
18354
  exports.denoS3LightClientSettings = denoS3LightClientSettings;
18191
18355
  exports.ducklake = ducklake;
18356
+ exports.getApprovalUrls = getApprovalUrls;
18192
18357
  exports.getFlowUserState = getFlowUserState;
18193
18358
  exports.getIdToken = getIdToken;
18194
18359
  exports.getPresignedS3PublicUrl = getPresignedS3PublicUrl;