@promptdriven/pds 0.1.3 → 0.1.4

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.
@@ -25,9 +25,26 @@ const RELEASE_VIDEO_REATTACH_INITIAL_DELAY_MS = 250;
25
25
  const RELEASE_VIDEO_REATTACH_MAX_DELAY_MS = 5_000;
26
26
  const RELEASE_VIDEO_WAIT_MAX_CONFIRMATIONS = 60;
27
27
  const RELEASE_VIDEO_WAIT_POLL_MS = 1_000;
28
+ const PIPELINE_REATTACH_MAX_ATTEMPTS = 8;
29
+ const PIPELINE_REATTACH_INITIAL_DELAY_MS = 250;
30
+ const PIPELINE_REATTACH_MAX_DELAY_MS = 5_000;
31
+ const PIPELINE_WAIT_MAX_CONFIRMATIONS = 60;
32
+ const PIPELINE_WAIT_POLL_MS = 1_000;
33
+ const JOBS_WATCH_MAX_CONFIRMATIONS = 60;
34
+ const JOBS_WATCH_POLL_MS = 1_000;
35
+ const DISTRIBUTION_PUBLISH_REATTACH_MAX_ATTEMPTS = 8;
36
+ const DISTRIBUTION_PUBLISH_REATTACH_INITIAL_DELAY_MS = 250;
37
+ const DISTRIBUTION_PUBLISH_REATTACH_MAX_DELAY_MS = 5_000;
38
+ const DISTRIBUTION_PUBLISH_WAIT_MAX_CONFIRMATIONS = 60;
39
+ const DISTRIBUTION_PUBLISH_WAIT_POLL_MS = 1_000;
28
40
  const PRIVATE_INTERNAL_ERROR_DETAIL_KEY_PATTERN = /^(?:errorName|rootCause|raw(?:Provider)?(?:Payload|Response|Error|Message)?|providerRaw(?:Payload|Response|Error|Message)?)$/i;
29
41
  const REFERENCE_LIBRARY_SCOPES = ["user", "workspace", "organization"];
30
42
  const REFERENCE_LIBRARY_UPDATE_POLICIES = ["notify", "pinned"];
43
+ const REFERENCE_LIBRARY_CAPTURE_POLICIES = ["off", "private-draft"];
44
+ const REFERENCE_LIBRARY_GRANT_PRESETS = {
45
+ "can-use": ["library:view", "library:use"],
46
+ admin: ["library:view", "library:use", "library:admin"]
47
+ };
31
48
  async function dispatchCommand(commandPath, commandArgs, context) {
32
49
  const command = commandPath.join(" ");
33
50
  switch (command) {
@@ -78,28 +95,40 @@ async function dispatchCommand(commandPath, commandArgs, context) {
78
95
  "dry-run": "boolean",
79
96
  "force-regenerate": "boolean",
80
97
  wait: "boolean",
81
- "idempotency-key": "string"
98
+ "idempotency-key": "string",
99
+ sections: "string[]",
100
+ files: "string[]",
101
+ segments: "string[]",
102
+ clips: "string[]"
82
103
  });
83
104
  const dryRun = values["dry-run"] === true;
84
- const response = await context.client.runPipeline(compact({
105
+ const wait = !dryRun;
106
+ const requestPayload = compact({
85
107
  projectId: requireProject(context.config),
86
108
  to: optionalString(values.to),
87
109
  stage: optionalString(values.stage),
88
110
  mode: optionalString(values.mode),
111
+ sections: optionalStringList(values.sections),
112
+ files: optionalStringList(values.files),
113
+ segments: optionalStringList(values.segments),
114
+ clips: optionalStringList(values.clips),
89
115
  dryRun: dryRun || undefined,
90
116
  forceRegenerate: values["force-regenerate"] === true || undefined,
91
117
  wait: values.wait === true || undefined,
92
118
  idempotencyKey: mutationIdempotencyKey(values["idempotency-key"], dryRun)
93
- }));
119
+ });
120
+ const response = wait
121
+ ? await createPipelineRunWithReattach(requestPayload, context)
122
+ : await context.client.runPipeline(requestPayload);
123
+ if (wait) {
124
+ return await waitForPipelineRun(response, context);
125
+ }
94
126
  await followAgentRunEvents(response, context);
95
127
  return response;
96
128
  }
97
129
  case "jobs watch": {
98
130
  const values = parse(commandArgs, { "job-id": "string", "run-id": "string" });
99
- return await context.client.watchJob({
100
- jobId: requiredRunOrJobId(values),
101
- onEvent: context.emitJsonlEvent
102
- });
131
+ return await watchJobUntilTerminal(requiredRunOrJobId(values), context);
103
132
  }
104
133
  case "jobs cancel": {
105
134
  const values = parse(commandArgs, { "job-id": "string" });
@@ -124,6 +153,8 @@ async function dispatchCommand(commandPath, commandArgs, context) {
124
153
  platform: optionalString(values.platform)
125
154
  }));
126
155
  }
156
+ case "distribution thumbnails upload":
157
+ return await distributionThumbnailsUpload(commandArgs, context);
127
158
  case "distribution connections list": {
128
159
  const values = parse(commandArgs, { platform: "string" });
129
160
  return await context.client.listDistributionConnections(compact({
@@ -152,7 +183,8 @@ async function dispatchCommand(commandPath, commandArgs, context) {
152
183
  "idempotency-key": "string"
153
184
  });
154
185
  const dryRun = values["dry-run"] === true;
155
- const response = await context.client.publishDistribution(compact({
186
+ const wait = !dryRun;
187
+ const requestPayload = compact({
156
188
  projectId: requireProject(context.config),
157
189
  platform: optionalString(values.platform),
158
190
  privacy: optionalString(values.privacy),
@@ -163,7 +195,13 @@ async function dispatchCommand(commandPath, commandArgs, context) {
163
195
  auditOverrideRationale: optionalString(values["audit-override-rationale"]),
164
196
  wait: values.wait === true || undefined,
165
197
  idempotencyKey: mutationIdempotencyKey(values["idempotency-key"], dryRun)
166
- }));
198
+ });
199
+ const response = wait
200
+ ? await publishDistributionWithReattach(requestPayload, context)
201
+ : await context.client.publishDistribution(requestPayload);
202
+ if (wait) {
203
+ return await waitForDistributionPublishRun(response, context);
204
+ }
167
205
  await followAgentRunEvents(response, context);
168
206
  return response;
169
207
  }
@@ -174,6 +212,21 @@ async function dispatchCommand(commandPath, commandArgs, context) {
174
212
  scope: referenceLibraryScope(values.scope)
175
213
  }));
176
214
  }
215
+ case "reference-library create": {
216
+ const values = parse(commandArgs, {
217
+ label: "string",
218
+ slug: "string",
219
+ "library-id": "string",
220
+ "idempotency-key": "string"
221
+ });
222
+ return await context.client.createReferenceLibrary(compact({
223
+ projectId: requireProject(context.config),
224
+ label: requiredString(values.label, "--label"),
225
+ slug: optionalString(values.slug),
226
+ libraryId: optionalString(values["library-id"]),
227
+ idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
228
+ }));
229
+ }
177
230
  case "reference-library items list": {
178
231
  const values = parse(commandArgs, { "library-id": "string" });
179
232
  return await context.client.listReferenceLibraryItems(compact({
@@ -187,12 +240,26 @@ async function dispatchCommand(commandPath, commandArgs, context) {
187
240
  projectId: requireProject(context.config)
188
241
  });
189
242
  }
243
+ case "reference-library policy get": {
244
+ ensureNoCommandArgs(commandArgs);
245
+ return await context.client.getReferenceLibraryProjectPolicy({
246
+ projectId: requireProject(context.config)
247
+ });
248
+ }
249
+ case "reference-library policy set":
250
+ return await referenceLibraryPolicySet(commandArgs, context);
190
251
  case "reference-library bindings attach":
191
252
  return await referenceLibraryBindingsAttach(commandArgs, context);
192
253
  case "reference-library bindings update":
193
254
  return await referenceLibraryBindingsUpdate(commandArgs, context);
194
255
  case "reference-library bindings detach":
195
256
  return await referenceLibraryBindingsDetach(commandArgs, context);
257
+ case "reference-library grants list":
258
+ return await referenceLibraryGrantsList(commandArgs, context);
259
+ case "reference-library grants add":
260
+ return await referenceLibraryGrantsAdd(commandArgs, context);
261
+ case "reference-library grants revoke":
262
+ return await referenceLibraryGrantsRevoke(commandArgs, context);
196
263
  case "release-video create":
197
264
  return await releaseVideoCreate(commandArgs, context);
198
265
  case "release-video preflight":
@@ -428,6 +495,134 @@ async function projectResetPipeline(commandArgs, context) {
428
495
  idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key"),
429
496
  }));
430
497
  }
498
+ async function distributionThumbnailsUpload(commandArgs, context) {
499
+ const values = parse(commandArgs, {
500
+ platform: "string",
501
+ file: "string",
502
+ "target-candidate": "string",
503
+ "target-candidate-id": "string",
504
+ "confirm-rights": "boolean",
505
+ "rights-confirmed": "boolean",
506
+ "replace-live": "boolean",
507
+ "idempotency-key": "string"
508
+ });
509
+ const idempotencyKey = requiredString(values["idempotency-key"], "--idempotency-key");
510
+ if (values["confirm-rights"] !== true && values["rights-confirmed"] !== true) {
511
+ throw new errors_1.CliError("Missing required option --confirm-rights", {
512
+ code: "missing_required_option",
513
+ exitCode: errors_1.EXIT_CODES.usage
514
+ });
515
+ }
516
+ const sourcePath = requiredString(values.file, "--file");
517
+ const targetCandidateId = targetCandidateOption(values);
518
+ const fileRequest = await thumbnailFileRequest(sourcePath);
519
+ return await context.client.uploadDistributionThumbnail(compact({
520
+ projectId: requireProject(context.config),
521
+ platform: optionalString(values.platform),
522
+ targetCandidateId,
523
+ rightsConfirmed: true,
524
+ replaceLive: values["replace-live"] === true || undefined,
525
+ file: fileRequest.file,
526
+ fileName: fileRequest.fileName,
527
+ idempotencyKey
528
+ }));
529
+ }
530
+ function targetCandidateOption(values) {
531
+ const shortFlag = optionalString(values["target-candidate"]);
532
+ const longFlag = optionalString(values["target-candidate-id"]);
533
+ if (shortFlag && longFlag && shortFlag !== longFlag) {
534
+ throw new errors_1.CliError("Use only one of --target-candidate or --target-candidate-id", {
535
+ code: "invalid_arguments",
536
+ exitCode: errors_1.EXIT_CODES.usage
537
+ });
538
+ }
539
+ return shortFlag ?? longFlag;
540
+ }
541
+ async function thumbnailFileRequest(sourcePath) {
542
+ try {
543
+ const fileBytes = new Uint8Array(await (0, promises_1.readFile)(sourcePath));
544
+ return {
545
+ file: new Blob([fileBytes]),
546
+ fileName: node_path_1.default.basename(sourcePath)
547
+ };
548
+ }
549
+ catch (error) {
550
+ if (isNodeFileNotFound(error)) {
551
+ throw new errors_1.CliError(`Thumbnail file not found: ${sourcePath}`, {
552
+ code: "file_not_found",
553
+ exitCode: errors_1.EXIT_CODES.usage
554
+ });
555
+ }
556
+ throw error;
557
+ }
558
+ }
559
+ function isNodeFileNotFound(error) {
560
+ return Boolean(error) &&
561
+ typeof error === "object" &&
562
+ error.code === "ENOENT";
563
+ }
564
+ async function referenceLibraryPolicySet(commandArgs, context) {
565
+ const values = parse(commandArgs, {
566
+ "capture-policy": "string",
567
+ "default-capture-library-id": "string",
568
+ "auto-select-from-library": "boolean",
569
+ "no-auto-select-from-library": "boolean",
570
+ "auto-attach-confidence": "string",
571
+ "suggest-confidence": "string",
572
+ "idempotency-key": "string"
573
+ });
574
+ const capturePolicy = referenceLibraryCapturePolicy(values["capture-policy"]);
575
+ const defaultCaptureLibraryId = optionalString(values["default-capture-library-id"]);
576
+ if (capturePolicy === "private-draft" && !defaultCaptureLibraryId) {
577
+ throw new errors_1.CliError("Missing required option --default-capture-library-id when --capture-policy private-draft", { code: "missing_required_option", exitCode: errors_1.EXIT_CODES.usage });
578
+ }
579
+ return await context.client.setReferenceLibraryProjectPolicy(compact({
580
+ projectId: requireProject(context.config),
581
+ capturePolicy,
582
+ defaultCaptureLibraryId,
583
+ autoSelectFromLibrary: referenceLibraryAutoSelectFromFlags(values),
584
+ autoAttachConfidence: optionalReferenceLibraryConfidence(values["auto-attach-confidence"], "--auto-attach-confidence"),
585
+ suggestConfidence: optionalReferenceLibraryConfidence(values["suggest-confidence"], "--suggest-confidence"),
586
+ idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
587
+ }));
588
+ }
589
+ async function referenceLibraryGrantsList(commandArgs, context) {
590
+ const values = parse(commandArgs, { "library-id": "string" });
591
+ return await context.client.listReferenceLibraryGrants({
592
+ projectId: requireProject(context.config),
593
+ libraryId: requiredString(values["library-id"], "--library-id")
594
+ });
595
+ }
596
+ async function referenceLibraryGrantsAdd(commandArgs, context) {
597
+ const values = parse(commandArgs, {
598
+ "library-id": "string",
599
+ "user-id": "string",
600
+ permission: "string",
601
+ "expires-at": "string",
602
+ "idempotency-key": "string"
603
+ });
604
+ return await context.client.addReferenceLibraryGrant(compact({
605
+ projectId: requireProject(context.config),
606
+ libraryId: requiredString(values["library-id"], "--library-id"),
607
+ userId: requiredString(values["user-id"], "--user-id"),
608
+ permissions: referenceLibraryGrantPermissions(values.permission),
609
+ expiresAt: optionalString(values["expires-at"]),
610
+ idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
611
+ }));
612
+ }
613
+ async function referenceLibraryGrantsRevoke(commandArgs, context) {
614
+ const values = parse(commandArgs, {
615
+ "library-id": "string",
616
+ "grant-id": "string",
617
+ "idempotency-key": "string"
618
+ });
619
+ return await context.client.revokeReferenceLibraryGrant({
620
+ projectId: requireProject(context.config),
621
+ libraryId: requiredString(values["library-id"], "--library-id"),
622
+ grantId: requiredString(values["grant-id"], "--grant-id"),
623
+ idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
624
+ });
625
+ }
431
626
  async function referenceLibraryBindingsAttach(commandArgs, context) {
432
627
  const values = parse(commandArgs, {
433
628
  "library-id": "string",
@@ -533,6 +728,36 @@ function referenceLibraryScope(value) {
533
728
  exitCode: errors_1.EXIT_CODES.usage
534
729
  });
535
730
  }
731
+ function referenceLibraryCapturePolicy(value) {
732
+ const capturePolicy = optionalString(value);
733
+ if (!capturePolicy) {
734
+ throw new errors_1.CliError("Missing required option --capture-policy", {
735
+ code: "missing_required_option",
736
+ exitCode: errors_1.EXIT_CODES.usage
737
+ });
738
+ }
739
+ if (REFERENCE_LIBRARY_CAPTURE_POLICIES.includes(capturePolicy)) {
740
+ return capturePolicy;
741
+ }
742
+ throw new errors_1.CliError(`Unsupported --capture-policy: ${capturePolicy}`, {
743
+ code: "invalid_arguments",
744
+ exitCode: errors_1.EXIT_CODES.usage
745
+ });
746
+ }
747
+ function referenceLibraryAutoSelectFromFlags(values) {
748
+ const enable = values["auto-select-from-library"] === true;
749
+ const disable = values["no-auto-select-from-library"] === true;
750
+ if (enable && disable) {
751
+ throw new errors_1.CliError("Use only one of --auto-select-from-library or --no-auto-select-from-library", { code: "invalid_arguments", exitCode: errors_1.EXIT_CODES.usage });
752
+ }
753
+ if (disable) {
754
+ return false;
755
+ }
756
+ if (enable) {
757
+ return true;
758
+ }
759
+ return undefined;
760
+ }
536
761
  function referenceLibraryUpdatePolicy(value) {
537
762
  const updatePolicy = optionalString(value);
538
763
  if (!updatePolicy) {
@@ -546,6 +771,36 @@ function referenceLibraryUpdatePolicy(value) {
546
771
  exitCode: errors_1.EXIT_CODES.usage
547
772
  });
548
773
  }
774
+ function referenceLibraryGrantPermissions(value) {
775
+ const permission = optionalString(value);
776
+ if (!permission) {
777
+ throw new errors_1.CliError("Missing required option --permission", {
778
+ code: "missing_required_option",
779
+ exitCode: errors_1.EXIT_CODES.usage
780
+ });
781
+ }
782
+ if (permission === "can-use" || permission === "admin") {
783
+ return [...REFERENCE_LIBRARY_GRANT_PRESETS[permission]];
784
+ }
785
+ throw new errors_1.CliError(`Unsupported --permission: ${permission}`, {
786
+ code: "invalid_arguments",
787
+ exitCode: errors_1.EXIT_CODES.usage
788
+ });
789
+ }
790
+ function optionalReferenceLibraryConfidence(value, flag) {
791
+ const raw = optionalString(value);
792
+ if (!raw) {
793
+ return undefined;
794
+ }
795
+ const confidence = Number(raw);
796
+ if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
797
+ throw new errors_1.CliError(`${flag} must be a number between 0 and 1`, {
798
+ code: "invalid_arguments",
799
+ exitCode: errors_1.EXIT_CODES.usage
800
+ });
801
+ }
802
+ return confidence;
803
+ }
549
804
  function validateReferenceLibraryTargetJson(target) {
550
805
  const artifact = optionalString(target.artifact);
551
806
  if (!artifact) {
@@ -610,6 +865,7 @@ async function releaseVideoCreate(commandArgs, context) {
610
865
  "repo-name": "string",
611
866
  "git-sha": "string",
612
867
  "release-tag": "string",
868
+ "bootstrap-selected-project": "boolean",
613
869
  preset: "string",
614
870
  target: "string",
615
871
  platform: "string",
@@ -637,6 +893,7 @@ async function releaseVideoCreate(commandArgs, context) {
637
893
  repoName: optionalString(values["repo-name"]),
638
894
  gitSha: optionalString(values["git-sha"]),
639
895
  releaseTag: optionalString(values["release-tag"]),
896
+ bootstrapSelectedProject: values["bootstrap-selected-project"] === true || undefined,
640
897
  preset: optionalString(values.preset),
641
898
  target: optionalString(values.target),
642
899
  platform: optionalString(values.platform),
@@ -652,6 +909,7 @@ async function releaseVideoCreate(commandArgs, context) {
652
909
  ? await createReleaseVideoWithReattach(requestPayload, context)
653
910
  : await context.client.createReleaseVideo(requestPayload);
654
911
  if (wait && !dryRun) {
912
+ emitReleaseVideoWaitHandle(response, context);
655
913
  return await waitForReleaseVideoRun(response, context);
656
914
  }
657
915
  await followAgentRunEvents(response, context);
@@ -667,6 +925,7 @@ async function releaseVideoPreflight(commandArgs, context) {
667
925
  "repo-name": "string",
668
926
  "git-sha": "string",
669
927
  "release-tag": "string",
928
+ "bootstrap-selected-project": "boolean",
670
929
  preset: "string",
671
930
  target: "string",
672
931
  platform: "string",
@@ -689,6 +948,7 @@ async function releaseVideoPreflight(commandArgs, context) {
689
948
  repoName: optionalString(values["repo-name"]),
690
949
  gitSha: optionalString(values["git-sha"]),
691
950
  releaseTag: optionalString(values["release-tag"]),
951
+ bootstrapSelectedProject: values["bootstrap-selected-project"] === true || undefined,
692
952
  preset: optionalString(values.preset),
693
953
  target: optionalString(values.target),
694
954
  platform: optionalString(values.platform),
@@ -706,13 +966,37 @@ async function releaseVideoStatus(commandArgs, context) {
706
966
  const jobResponse = await context.client.getJob({ jobId: runId });
707
967
  return releaseVideoStatusFromJob(jobResponse, runId);
708
968
  }
969
+ function emitReleaseVideoWaitHandle(response, context) {
970
+ const writer = context.waitNotice ?? context.notice;
971
+ if (!writer) {
972
+ return;
973
+ }
974
+ const runId = agentRunIdFromResponse(response);
975
+ if (!runId) {
976
+ return;
977
+ }
978
+ const data = readEnvelopeData(response);
979
+ const project = isRecord(data.project) ? data.project : {};
980
+ const projectId = optionalString(project.id) ??
981
+ optionalString(data.projectId) ??
982
+ readNestedString(data, ["pipeline", "run", "projectId"]);
983
+ const status = optionalString(data.status) ??
984
+ readNestedString(data, ["pipeline", "run", "status"]);
985
+ writer(`[pds] release-video run handle: runId=${runId}` +
986
+ `${projectId ? ` projectId=${projectId}` : ""}` +
987
+ `${status ? ` status=${status}` : ""}`);
988
+ writer(`[pds] recover: pds release-video status --run-id ${runId} --json`);
989
+ writer(`[pds] watch: pds jobs watch --run-id ${runId} --jsonl`);
990
+ }
709
991
  async function createReleaseVideoWithReattach(request, context) {
710
992
  let attempt = 0;
711
993
  let delayMs = RELEASE_VIDEO_REATTACH_INITIAL_DELAY_MS;
712
994
  let sawDroppedCreate = false;
713
995
  while (true) {
714
996
  try {
715
- return await context.client.createReleaseVideo(request);
997
+ return await context.client.createReleaseVideo(request, {
998
+ timeoutMs: context.config.timeoutMs
999
+ });
716
1000
  }
717
1001
  catch (error) {
718
1002
  if (isRecoverableCreateWaitDrop(error)) {
@@ -769,7 +1053,12 @@ function parse(commandArgs, options) {
769
1053
  args: commandArgs,
770
1054
  allowPositionals: false,
771
1055
  strict: true,
772
- options: Object.fromEntries(Object.entries(options).map(([name, type]) => [name, { type }]))
1056
+ options: Object.fromEntries(Object.entries(options).map(([name, type]) => [
1057
+ name,
1058
+ type === "string[]"
1059
+ ? { type: "string", multiple: true }
1060
+ : { type }
1061
+ ]))
773
1062
  }).values;
774
1063
  }
775
1064
  catch (error) {
@@ -806,6 +1095,19 @@ function requiredRunOrJobId(values) {
806
1095
  function optionalString(value) {
807
1096
  return typeof value === "string" && value.length > 0 ? value : undefined;
808
1097
  }
1098
+ function optionalStringList(value) {
1099
+ const values = Array.isArray(value)
1100
+ ? value
1101
+ : typeof value === "string"
1102
+ ? [value]
1103
+ : [];
1104
+ const items = values
1105
+ .filter((item) => typeof item === "string")
1106
+ .flatMap((item) => item.split(","))
1107
+ .map((item) => item.trim())
1108
+ .filter(Boolean);
1109
+ return items.length > 0 ? items : undefined;
1110
+ }
809
1111
  function mutationIdempotencyKey(value, dryRun) {
810
1112
  if (dryRun) {
811
1113
  return optionalString(value);
@@ -843,11 +1145,65 @@ async function followAgentRunEvents(response, context) {
843
1145
  onEvent: context.emitJsonlEvent
844
1146
  });
845
1147
  }
846
- async function waitForReleaseVideoRun(response, context) {
1148
+ async function publishDistributionWithReattach(request, context) {
1149
+ let attempt = 0;
1150
+ let delayMs = DISTRIBUTION_PUBLISH_REATTACH_INITIAL_DELAY_MS;
1151
+ let sawDroppedCreate = false;
1152
+ while (true) {
1153
+ try {
1154
+ return await context.client.publishDistribution(request, {
1155
+ timeoutMs: context.config.timeoutMs
1156
+ });
1157
+ }
1158
+ catch (error) {
1159
+ if (isRecoverableCreateWaitDrop(error)) {
1160
+ if (attempt >= DISTRIBUTION_PUBLISH_REATTACH_MAX_ATTEMPTS) {
1161
+ throw error;
1162
+ }
1163
+ attempt += 1;
1164
+ sawDroppedCreate = true;
1165
+ context.emitJsonlEvent?.({
1166
+ type: "wait_reconnect",
1167
+ command: "distribution.publish",
1168
+ reason: "create_request_dropped"
1169
+ });
1170
+ continue;
1171
+ }
1172
+ if (sawDroppedCreate && isPendingIdempotencyReplayWithoutRunId(error)) {
1173
+ if (attempt >= DISTRIBUTION_PUBLISH_REATTACH_MAX_ATTEMPTS) {
1174
+ throw new errors_1.CliError("Distribution publish run handle was not available before retry limit", {
1175
+ code: "distribution_publish_reattach_timeout",
1176
+ exitCode: errors_1.EXIT_CODES.timeout,
1177
+ details: {
1178
+ attempts: attempt,
1179
+ reason: "idempotency_in_progress_without_run_id"
1180
+ }
1181
+ });
1182
+ }
1183
+ context.emitJsonlEvent?.({
1184
+ type: "wait_reconnect",
1185
+ command: "distribution.publish",
1186
+ reason: "idempotency_in_progress_without_run_id",
1187
+ attempt: attempt + 1
1188
+ });
1189
+ await (context.sleep ?? defaultSleep)(delayMs);
1190
+ delayMs = Math.min(delayMs * 2, DISTRIBUTION_PUBLISH_REATTACH_MAX_DELAY_MS);
1191
+ attempt += 1;
1192
+ continue;
1193
+ }
1194
+ throw error;
1195
+ }
1196
+ }
1197
+ }
1198
+ async function waitForDistributionPublishRun(response, context) {
847
1199
  const runId = agentRunIdFromResponse(response);
848
1200
  if (!runId) {
849
1201
  return response;
850
1202
  }
1203
+ if (isTerminalDistributionPublishResponse(response)) {
1204
+ await followAgentRunEvents(response, context);
1205
+ return response;
1206
+ }
851
1207
  let confirmations = 0;
852
1208
  const seenEvents = new Set();
853
1209
  const emitRunEvent = context.emitJsonlEvent
@@ -861,10 +1217,277 @@ async function waitForReleaseVideoRun(response, context) {
861
1217
  }
862
1218
  : undefined;
863
1219
  while (true) {
864
- await context.client.watchJob({
865
- jobId: runId,
866
- onEvent: emitRunEvent
1220
+ try {
1221
+ await context.client.watchJob({
1222
+ jobId: runId,
1223
+ onEvent: emitRunEvent
1224
+ });
1225
+ }
1226
+ catch (error) {
1227
+ if (!isRecoverableWatchDrop(error)) {
1228
+ throw error;
1229
+ }
1230
+ context.emitJsonlEvent?.({
1231
+ type: "wait_reconnect",
1232
+ command: "distribution.publish",
1233
+ reason: "event_stream_dropped",
1234
+ runId,
1235
+ attempt: confirmations + 1
1236
+ });
1237
+ }
1238
+ const jobResponse = await context.client.getJob({ jobId: runId });
1239
+ const run = readJobRun(jobResponse);
1240
+ assertTerminalDistributionPublishRunSucceeded(run, runId);
1241
+ const merged = mergeDistributionPublishRun(response, jobResponse);
1242
+ if (isTerminalDistributionPublishRun(run)) {
1243
+ return merged;
1244
+ }
1245
+ if (confirmations >= DISTRIBUTION_PUBLISH_WAIT_MAX_CONFIRMATIONS) {
1246
+ throw new errors_1.CliError("Distribution publish run did not reach a terminal state before wait limit", {
1247
+ code: "distribution_publish_wait_timeout",
1248
+ exitCode: errors_1.EXIT_CODES.timeout,
1249
+ details: {
1250
+ runId,
1251
+ status: run ? optionalString(run.status) ?? null : null,
1252
+ confirmations
1253
+ }
1254
+ });
1255
+ }
1256
+ context.emitJsonlEvent?.({
1257
+ type: "wait_reconnect",
1258
+ command: "distribution.publish",
1259
+ reason: "event_stream_closed_before_terminal",
1260
+ runId,
1261
+ status: run ? optionalString(run.status) ?? null : null,
1262
+ attempt: confirmations + 1
867
1263
  });
1264
+ await (context.sleep ?? defaultSleep)(DISTRIBUTION_PUBLISH_WAIT_POLL_MS);
1265
+ confirmations += 1;
1266
+ }
1267
+ }
1268
+ async function createPipelineRunWithReattach(request, context) {
1269
+ let attempt = 0;
1270
+ let delayMs = PIPELINE_REATTACH_INITIAL_DELAY_MS;
1271
+ let sawDroppedCreate = false;
1272
+ while (true) {
1273
+ try {
1274
+ return await context.client.runPipeline(request, {
1275
+ timeoutMs: context.config.timeoutMs
1276
+ });
1277
+ }
1278
+ catch (error) {
1279
+ if (isRecoverableCreateWaitDrop(error)) {
1280
+ if (attempt >= PIPELINE_REATTACH_MAX_ATTEMPTS) {
1281
+ throw error;
1282
+ }
1283
+ attempt += 1;
1284
+ sawDroppedCreate = true;
1285
+ context.emitJsonlEvent?.({
1286
+ type: "wait_reconnect",
1287
+ command: "pipeline.run",
1288
+ reason: "create_request_dropped"
1289
+ });
1290
+ continue;
1291
+ }
1292
+ if (sawDroppedCreate && isPendingIdempotencyReplayWithoutRunId(error)) {
1293
+ if (attempt >= PIPELINE_REATTACH_MAX_ATTEMPTS) {
1294
+ throw new errors_1.CliError("Pipeline run handle was not available before retry limit", {
1295
+ code: "pipeline_reattach_timeout",
1296
+ exitCode: errors_1.EXIT_CODES.timeout,
1297
+ details: {
1298
+ attempts: attempt,
1299
+ reason: "idempotency_in_progress_without_run_id"
1300
+ }
1301
+ });
1302
+ }
1303
+ context.emitJsonlEvent?.({
1304
+ type: "wait_reconnect",
1305
+ command: "pipeline.run",
1306
+ reason: "idempotency_in_progress_without_run_id",
1307
+ attempt: attempt + 1
1308
+ });
1309
+ await (context.sleep ?? defaultSleep)(delayMs);
1310
+ delayMs = Math.min(delayMs * 2, PIPELINE_REATTACH_MAX_DELAY_MS);
1311
+ attempt += 1;
1312
+ continue;
1313
+ }
1314
+ throw error;
1315
+ }
1316
+ }
1317
+ }
1318
+ async function waitForPipelineRun(response, context) {
1319
+ const runId = agentRunIdFromResponse(response);
1320
+ if (!runId) {
1321
+ return response;
1322
+ }
1323
+ let confirmations = 0;
1324
+ const seenEvents = new Set();
1325
+ const emitRunEvent = context.emitJsonlEvent
1326
+ ? (event) => {
1327
+ const eventKey = JSON.stringify(event);
1328
+ if (seenEvents.has(eventKey)) {
1329
+ return;
1330
+ }
1331
+ context.emitJsonlEvent?.(event);
1332
+ seenEvents.add(eventKey);
1333
+ }
1334
+ : undefined;
1335
+ while (true) {
1336
+ try {
1337
+ await context.client.watchJob({
1338
+ jobId: runId,
1339
+ onEvent: emitRunEvent
1340
+ });
1341
+ }
1342
+ catch (error) {
1343
+ if (!isRecoverableWatchDrop(error)) {
1344
+ throw error;
1345
+ }
1346
+ context.emitJsonlEvent?.({
1347
+ type: "wait_reconnect",
1348
+ command: "pipeline.run",
1349
+ reason: "event_stream_dropped",
1350
+ runId,
1351
+ attempt: confirmations + 1
1352
+ });
1353
+ }
1354
+ const jobResponse = await context.client.getJob({ jobId: runId });
1355
+ const run = readJobRun(jobResponse);
1356
+ assertTerminalPipelineRunSucceeded(run, runId);
1357
+ const merged = mergePipelineRun(response, jobResponse);
1358
+ if (isTerminalJobRun(run)) {
1359
+ return merged;
1360
+ }
1361
+ if (confirmations >= PIPELINE_WAIT_MAX_CONFIRMATIONS) {
1362
+ throw new errors_1.CliError("Pipeline run did not reach a terminal state before wait limit", {
1363
+ code: "pipeline_wait_timeout",
1364
+ exitCode: errors_1.EXIT_CODES.timeout,
1365
+ details: {
1366
+ runId,
1367
+ status: run ? optionalString(run.status) ?? null : null,
1368
+ confirmations
1369
+ }
1370
+ });
1371
+ }
1372
+ context.emitJsonlEvent?.({
1373
+ type: "wait_reconnect",
1374
+ command: "pipeline.run",
1375
+ reason: "event_stream_closed_before_terminal",
1376
+ runId,
1377
+ status: run ? optionalString(run.status) ?? null : null,
1378
+ attempt: confirmations + 1
1379
+ });
1380
+ await (context.sleep ?? defaultSleep)(PIPELINE_WAIT_POLL_MS);
1381
+ confirmations += 1;
1382
+ }
1383
+ }
1384
+ async function watchJobUntilTerminal(jobId, context) {
1385
+ let confirmations = 0;
1386
+ const events = [];
1387
+ const seenEvents = new Set();
1388
+ let sawTerminalEvent = false;
1389
+ const emitJobEvent = (event) => {
1390
+ const eventKey = JSON.stringify(event);
1391
+ if (seenEvents.has(eventKey)) {
1392
+ return;
1393
+ }
1394
+ seenEvents.add(eventKey);
1395
+ if (isTerminalWatchEvent(event)) {
1396
+ sawTerminalEvent = true;
1397
+ }
1398
+ events.push(event);
1399
+ context.emitJsonlEvent?.(event);
1400
+ };
1401
+ while (true) {
1402
+ let streamDropped = false;
1403
+ try {
1404
+ const watchResponse = await context.client.watchJob({
1405
+ jobId,
1406
+ onEvent: emitJobEvent
1407
+ });
1408
+ collectWatchEvents(watchResponse, emitJobEvent);
1409
+ }
1410
+ catch (error) {
1411
+ if (!isRecoverableWatchDrop(error)) {
1412
+ throw error;
1413
+ }
1414
+ streamDropped = true;
1415
+ }
1416
+ const jobResponse = await context.client.getJob({ jobId });
1417
+ const run = readJobRun(jobResponse);
1418
+ if (isTerminalJobRun(run)) {
1419
+ if (!sawTerminalEvent) {
1420
+ emitJobEvent(terminalJobWatchEvent(run, jobId));
1421
+ }
1422
+ return context.emitJsonlEvent ? { events: [] } : { events };
1423
+ }
1424
+ if (confirmations >= JOBS_WATCH_MAX_CONFIRMATIONS) {
1425
+ throw new errors_1.CliError("Agent job did not reach a terminal state before watch limit", {
1426
+ code: "job_watch_timeout",
1427
+ exitCode: errors_1.EXIT_CODES.timeout,
1428
+ details: {
1429
+ runId: jobId,
1430
+ status: run ? optionalString(run.status) ?? null : null,
1431
+ confirmations
1432
+ }
1433
+ });
1434
+ }
1435
+ context.emitJsonlEvent?.({
1436
+ type: "wait_reconnect",
1437
+ command: "jobs.watch",
1438
+ reason: streamDropped
1439
+ ? "event_stream_dropped"
1440
+ : "event_stream_closed_before_terminal",
1441
+ runId: jobId,
1442
+ status: run ? optionalString(run.status) ?? null : null,
1443
+ attempt: confirmations + 1
1444
+ });
1445
+ await (context.sleep ?? defaultSleep)(JOBS_WATCH_POLL_MS);
1446
+ confirmations += 1;
1447
+ }
1448
+ }
1449
+ async function waitForReleaseVideoRun(response, context) {
1450
+ const runId = agentRunIdFromResponse(response);
1451
+ if (!runId) {
1452
+ return response;
1453
+ }
1454
+ let confirmations = 0;
1455
+ const seenEvents = new Set();
1456
+ const emitRunEvent = context.emitJsonlEvent
1457
+ ? (event) => {
1458
+ const eventKey = JSON.stringify(event);
1459
+ if (seenEvents.has(eventKey)) {
1460
+ return;
1461
+ }
1462
+ context.emitJsonlEvent?.(event);
1463
+ seenEvents.add(eventKey);
1464
+ }
1465
+ : undefined;
1466
+ while (true) {
1467
+ try {
1468
+ await context.client.watchJob({
1469
+ jobId: runId,
1470
+ onEvent: emitRunEvent
1471
+ });
1472
+ }
1473
+ catch (error) {
1474
+ if (!isRecoverableWatchDrop(error)) {
1475
+ throw error;
1476
+ }
1477
+ context.emitJsonlEvent?.({
1478
+ type: "wait_reconnect",
1479
+ command: "release-video.create",
1480
+ reason: "event_stream_dropped",
1481
+ runId,
1482
+ attempt: confirmations + 1
1483
+ });
1484
+ emitWaitStatusNotice(context, {
1485
+ command: "release-video.create",
1486
+ runId,
1487
+ reason: "event stream dropped; polling durable status",
1488
+ attempt: confirmations + 1
1489
+ });
1490
+ }
868
1491
  const jobResponse = await context.client.getJob({ jobId: runId });
869
1492
  const run = readJobRun(jobResponse);
870
1493
  assertTerminalReleaseVideoRunSucceeded(run, runId);
@@ -876,11 +1499,13 @@ async function waitForReleaseVideoRun(response, context) {
876
1499
  throw new errors_1.CliError("Release video run did not reach a terminal state before wait limit", {
877
1500
  code: "release_video_wait_timeout",
878
1501
  exitCode: errors_1.EXIT_CODES.timeout,
879
- details: {
1502
+ details: compact({
880
1503
  runId,
1504
+ eventsUrl: releaseVideoEventsUrl(response, run),
881
1505
  status: run ? optionalString(run.status) ?? null : null,
1506
+ activeStep: activeStepId(run),
882
1507
  confirmations
883
- }
1508
+ })
884
1509
  });
885
1510
  }
886
1511
  context.emitJsonlEvent?.({
@@ -891,26 +1516,144 @@ async function waitForReleaseVideoRun(response, context) {
891
1516
  status: run ? optionalString(run.status) ?? null : null,
892
1517
  attempt: confirmations + 1
893
1518
  });
1519
+ emitWaitStatusNotice(context, {
1520
+ command: "release-video.create",
1521
+ runId,
1522
+ reason: "event stream closed before terminal state",
1523
+ status: run ? optionalString(run.status) ?? null : null,
1524
+ activeStep: activeStepId(run),
1525
+ attempt: confirmations + 1
1526
+ });
894
1527
  await (context.sleep ?? defaultSleep)(RELEASE_VIDEO_WAIT_POLL_MS);
895
1528
  confirmations += 1;
896
1529
  }
897
1530
  }
1531
+ function emitWaitStatusNotice(context, input) {
1532
+ const writer = context.waitNotice ?? context.notice;
1533
+ if (!writer) {
1534
+ return;
1535
+ }
1536
+ writer(`[pds] ${input.command} wait: ${input.reason}; ` +
1537
+ `runId=${input.runId}` +
1538
+ `${input.status ? ` status=${input.status}` : ""}` +
1539
+ `${input.activeStep ? ` activeStep=${input.activeStep}` : ""}` +
1540
+ ` attempt=${input.attempt}`);
1541
+ }
1542
+ function releaseVideoEventsUrl(response, run) {
1543
+ return optionalString(run?.eventsUrl) ??
1544
+ readNestedString(readEnvelopeData(response), ["pipeline", "run", "eventsUrl"]);
1545
+ }
1546
+ function activeStepId(run) {
1547
+ if (!run || !Array.isArray(run.steps)) {
1548
+ return undefined;
1549
+ }
1550
+ const activeStep = run.steps.find((step) => isRecord(step) && step.status === "running");
1551
+ return isRecord(activeStep)
1552
+ ? optionalString(activeStep.id) ?? optionalString(activeStep.stage)
1553
+ : undefined;
1554
+ }
898
1555
  function agentRunIdFromResponse(value) {
899
1556
  const data = readEnvelopeData(value);
900
1557
  return (optionalString(data.runId) ??
901
1558
  runIdFromNested(data.agentRun) ??
902
1559
  runIdFromNested(data.pipeline, "run"));
903
1560
  }
904
- function mergeReleaseVideoRun(response, jobResponse) {
1561
+ function isTerminalDistributionPublishResponse(response) {
1562
+ const data = readEnvelopeData(response);
1563
+ const status = optionalString(data.status);
1564
+ return isTerminalAgentRunStatus(status);
1565
+ }
1566
+ function mergeDistributionPublishRun(response, jobResponse) {
905
1567
  const run = readJobRun(jobResponse);
906
1568
  if (!run || !isRecord(response)) {
907
1569
  return response;
908
1570
  }
1571
+ const data = readEnvelopeData(response);
1572
+ const terminalResult = isRecord(run.terminalResult) ? run.terminalResult : {};
1573
+ const responseAgentRun = isRecord(data.agentRun) ? data.agentRun : {};
1574
+ const runId = optionalString(run.runId) ??
1575
+ optionalString(run.id) ??
1576
+ agentRunIdFromResponse(response);
1577
+ const normalizedAgentRun = {
1578
+ ...responseAgentRun,
1579
+ ...run,
1580
+ ...(runId ? { runId } : {})
1581
+ };
1582
+ const responsePublishStatus = isRecord(data.publishStatus) ? data.publishStatus : {};
1583
+ const terminalPublishStatus = isRecord(terminalResult.publishStatus)
1584
+ ? terminalResult.publishStatus
1585
+ : undefined;
1586
+ const mergedData = compact({
1587
+ ...data,
1588
+ ...terminalResult,
1589
+ status: optionalString(run.status) ??
1590
+ optionalString(terminalResult.status) ??
1591
+ optionalString(data.status) ??
1592
+ "running",
1593
+ publishStatus: terminalPublishStatus
1594
+ ? {
1595
+ ...responsePublishStatus,
1596
+ ...terminalPublishStatus
1597
+ }
1598
+ : data.publishStatus,
1599
+ agentRun: normalizedAgentRun
1600
+ });
1601
+ if (isRecord(response.data)) {
1602
+ return {
1603
+ ...response,
1604
+ data: mergedData
1605
+ };
1606
+ }
1607
+ return mergedData;
1608
+ }
1609
+ function mergePipelineRun(response, jobResponse) {
1610
+ const run = readJobRun(jobResponse);
1611
+ if (!run || !isRecord(response)) {
1612
+ return response;
1613
+ }
1614
+ const runId = optionalString(run.runId) ??
1615
+ optionalString(run.id) ??
1616
+ agentRunIdFromResponse(response);
909
1617
  const normalizedRun = {
910
1618
  ...run,
911
- runId: optionalString(run.runId) ?? optionalString(run.id)
1619
+ ...(runId ? { runId } : {})
912
1620
  };
913
1621
  const data = readEnvelopeData(response);
1622
+ const mergedData = compact({
1623
+ ...data,
1624
+ runId,
1625
+ projectId: optionalString(run.projectId) ?? optionalString(data.projectId),
1626
+ target: optionalString(run.target) ?? optionalString(data.target),
1627
+ status: optionalString(run.status) ?? optionalString(data.status) ?? "running",
1628
+ run: normalizedRun,
1629
+ steps: Array.isArray(run.steps) ? run.steps : data.steps,
1630
+ terminalResult: run.terminalResult !== undefined
1631
+ ? run.terminalResult
1632
+ : data.terminalResult
1633
+ });
1634
+ if (isRecord(response.data)) {
1635
+ return {
1636
+ ...response,
1637
+ data: mergedData
1638
+ };
1639
+ }
1640
+ return mergedData;
1641
+ }
1642
+ function mergeReleaseVideoRun(response, jobResponse) {
1643
+ const run = readJobRun(jobResponse);
1644
+ if (!run || !isRecord(response)) {
1645
+ return response;
1646
+ }
1647
+ const data = readEnvelopeData(response);
1648
+ const responsePipeline = isRecord(data.pipeline) ? data.pipeline : {};
1649
+ const responseRun = isRecord(responsePipeline.run) ? responsePipeline.run : {};
1650
+ const normalizedRun = {
1651
+ ...responseRun,
1652
+ ...run,
1653
+ runId: optionalString(run.runId) ??
1654
+ optionalString(run.id) ??
1655
+ optionalString(responseRun.runId)
1656
+ };
914
1657
  if (!isRecord(data.pipeline)) {
915
1658
  return response;
916
1659
  }
@@ -1033,6 +1776,70 @@ function readJobRun(value) {
1033
1776
  }
1034
1777
  return null;
1035
1778
  }
1779
+ function collectWatchEvents(value, onEvent) {
1780
+ if (!isRecord(value) || !Array.isArray(value.events)) {
1781
+ return;
1782
+ }
1783
+ for (const event of value.events) {
1784
+ onEvent(event);
1785
+ }
1786
+ }
1787
+ function isTerminalWatchEvent(event) {
1788
+ if (!isRecord(event)) {
1789
+ return false;
1790
+ }
1791
+ const type = optionalString(event.type);
1792
+ return type === "run_succeeded" ||
1793
+ type === "run_failed" ||
1794
+ type === "run_cancelled" ||
1795
+ type === "job_terminal";
1796
+ }
1797
+ function terminalJobWatchEvent(run, fallbackRunId) {
1798
+ return {
1799
+ type: "job_terminal",
1800
+ command: "jobs.watch",
1801
+ runId: optionalString(run?.runId) ??
1802
+ optionalString(run?.id) ??
1803
+ fallbackRunId,
1804
+ status: run ? optionalString(run.status) ?? null : null
1805
+ };
1806
+ }
1807
+ function assertTerminalDistributionPublishRunSucceeded(run, fallbackRunId) {
1808
+ if (!run) {
1809
+ return;
1810
+ }
1811
+ const status = optionalString(run.status);
1812
+ if (!status || status === "succeeded") {
1813
+ return;
1814
+ }
1815
+ if (isTerminalAgentRunStatus(status)) {
1816
+ const error = isRecord(run.error) ? run.error : null;
1817
+ const publicError = publicReleaseVideoFailureError(error);
1818
+ const terminalCode = status === "timed_out"
1819
+ ? "timeout"
1820
+ : status === "cancelled"
1821
+ ? "cancelled"
1822
+ : undefined;
1823
+ const errorCode = optionalString(error?.code);
1824
+ const code = terminalCode ??
1825
+ errorCode ??
1826
+ "agent_run_failed";
1827
+ const message = errorCode === code
1828
+ ? optionalString(error?.message) ?? distributionPublishFailureMessage(status)
1829
+ : distributionPublishFailureMessage(status);
1830
+ throw new errors_1.CliError(message, {
1831
+ code,
1832
+ exitCode: (0, errors_1.mapApiErrorToExitCode)(code, undefined),
1833
+ details: compact({
1834
+ runId: optionalString(run.runId) ?? optionalString(run.id) ?? fallbackRunId,
1835
+ status,
1836
+ failedStage: failedStepStage(run),
1837
+ logRef: pipelineFailureLogRef(run),
1838
+ error: publicError
1839
+ })
1840
+ });
1841
+ }
1842
+ }
1036
1843
  function assertTerminalReleaseVideoRunSucceeded(run, fallbackRunId) {
1037
1844
  if (!run) {
1038
1845
  return;
@@ -1069,6 +1876,42 @@ function assertTerminalReleaseVideoRunSucceeded(run, fallbackRunId) {
1069
1876
  });
1070
1877
  }
1071
1878
  }
1879
+ function assertTerminalPipelineRunSucceeded(run, fallbackRunId) {
1880
+ if (!run) {
1881
+ return;
1882
+ }
1883
+ const status = optionalString(run.status);
1884
+ if (!status || status === "succeeded") {
1885
+ return;
1886
+ }
1887
+ if (status === "failed" || status === "cancelled" || status === "timed_out") {
1888
+ const error = isRecord(run.error) ? run.error : null;
1889
+ const publicError = publicReleaseVideoFailureError(error);
1890
+ const terminalCode = status === "timed_out"
1891
+ ? "timeout"
1892
+ : status === "cancelled"
1893
+ ? "cancelled"
1894
+ : undefined;
1895
+ const errorCode = optionalString(error?.code);
1896
+ const code = terminalCode ??
1897
+ errorCode ??
1898
+ "agent_run_failed";
1899
+ const message = errorCode === code
1900
+ ? optionalString(error?.message) ?? pipelineRunFailureMessage(status)
1901
+ : pipelineRunFailureMessage(status);
1902
+ throw new errors_1.CliError(message, {
1903
+ code,
1904
+ exitCode: (0, errors_1.mapApiErrorToExitCode)(code, undefined),
1905
+ details: compact({
1906
+ runId: optionalString(run.runId) ?? optionalString(run.id) ?? fallbackRunId,
1907
+ status,
1908
+ failedStage: failedStepStage(run),
1909
+ logRef: pipelineFailureLogRef(run),
1910
+ error: publicError
1911
+ })
1912
+ });
1913
+ }
1914
+ }
1072
1915
  function isTerminalReleaseVideoRun(run) {
1073
1916
  const status = run ? optionalString(run.status) : undefined;
1074
1917
  return status === "succeeded" ||
@@ -1076,6 +1919,56 @@ function isTerminalReleaseVideoRun(run) {
1076
1919
  status === "cancelled" ||
1077
1920
  status === "timed_out";
1078
1921
  }
1922
+ function isTerminalPipelineRun(run) {
1923
+ return isTerminalJobRun(run);
1924
+ }
1925
+ function isTerminalDistributionPublishRun(run) {
1926
+ return isTerminalJobRun(run);
1927
+ }
1928
+ function isTerminalJobRun(run) {
1929
+ const status = run ? optionalString(run.status) : undefined;
1930
+ return isTerminalAgentRunStatus(status);
1931
+ }
1932
+ function isTerminalAgentRunStatus(status) {
1933
+ return status === "succeeded" ||
1934
+ status === "failed" ||
1935
+ status === "cancelled" ||
1936
+ status === "timed_out";
1937
+ }
1938
+ function distributionPublishFailureMessage(status) {
1939
+ if (status === "timed_out") {
1940
+ return "Distribution publish run timed out";
1941
+ }
1942
+ if (status === "cancelled") {
1943
+ return "Distribution publish run cancelled";
1944
+ }
1945
+ return "Distribution publish run failed";
1946
+ }
1947
+ function pipelineRunFailureMessage(status) {
1948
+ if (status === "timed_out") {
1949
+ return "Pipeline run timed out";
1950
+ }
1951
+ if (status === "cancelled") {
1952
+ return "Pipeline run cancelled";
1953
+ }
1954
+ return "Pipeline run failed";
1955
+ }
1956
+ function pipelineFailureLogRef(run) {
1957
+ const error = isRecord(run.error) ? run.error : null;
1958
+ const details = isRecord(error?.details) ? error.details : null;
1959
+ if (isRecord(details?.logRef)) {
1960
+ return details.logRef;
1961
+ }
1962
+ const failedStage = failedStepStage(run);
1963
+ const runId = optionalString(run.runId) ?? optionalString(run.id);
1964
+ if (!failedStage || !runId) {
1965
+ return undefined;
1966
+ }
1967
+ return {
1968
+ runId,
1969
+ stage: failedStage,
1970
+ };
1971
+ }
1079
1972
  function releaseRunFailureMessage(status) {
1080
1973
  if (status === "timed_out") {
1081
1974
  return "Release video run timed out";
@@ -1156,6 +2049,12 @@ function isRecoverableCreateWaitDrop(error) {
1156
2049
  }
1157
2050
  return error instanceof TypeError || isTransportLikeError(error);
1158
2051
  }
2052
+ function isRecoverableWatchDrop(error) {
2053
+ if (error instanceof errors_1.CliError && error.code === "watch_reconnect_exhausted") {
2054
+ return true;
2055
+ }
2056
+ return isRecoverableCreateWaitDrop(error);
2057
+ }
1159
2058
  function isClientTimeoutDrop(error) {
1160
2059
  return error.code === "timeout" &&
1161
2060
  error.status === undefined &&